Skip to content

Commit

Permalink
Merge pull request #4734 from xoriole/trust-graph-pyqtgraph
Browse files Browse the repository at this point in the history
Trust graph pyqtgraph version
  • Loading branch information
qstokkink authored Aug 16, 2019
2 parents 46bfd42 + 3883eee commit f5cedb2
Show file tree
Hide file tree
Showing 6 changed files with 535 additions and 447 deletions.
234 changes: 144 additions & 90 deletions Tribler/Core/Modules/restapi/trustview_endpoint.py
Original file line number Diff line number Diff line change
@@ -1,120 +1,174 @@
from __future__ import absolute_import

import logging
import math
from binascii import hexlify, unhexlify

from networkx.readwrite import json_graph
import networkx as nx

from twisted.web import resource

import Tribler.Core.Utilities.json_util as json
from Tribler.Core.Modules.TrustCalculation.local_view import NodeVision
from Tribler.Core.Modules.TrustCalculation.graph_positioning import GraphPositioning as gpos
from Tribler.Core.simpledefs import DOWNLOAD, UPLOAD


class TrustViewEndpoint(resource.Resource):

def __init__(self, session):
resource.Resource.__init__(self)
self.session = session
self._logger = logging.getLogger(self.__class__.__name__)
class TrustGraph(nx.DiGraph):

self.root_public_key = None
self.local_view = None
def __init__(self, root_node):
nx.DiGraph.__init__(self)
self.root_node = 0

self.bootstrap = None
self.peers = []
self.get_node(root_node)

self.transactions = {}
self.token_balance = {}
self.initialized = False

def get_node(self, peer_key):
if peer_key not in self.peers:
node_id = len(self.peers)
self.peers.append(peer_key)
super(TrustGraph, self).add_node(node_id, id=node_id, key=peer_key)
return self.node[self.peers.index(peer_key)]

def add_block(self, block):
if block.hash not in self.transactions and block.type == 'tribler_bandwidth':
peer1_key = hexlify(block.public_key)
peer2_key = hexlify(block.link_public_key)

peer1 = self.get_node(peer1_key)
peer2 = self.get_node(peer2_key)

if block.sequence_number > peer1.get('sequence_number', 0):
peer1['sequence_number'] = block.sequence_number
peer1['total_up'] = block.transaction["total_up"]
peer1['total_down'] = block.transaction["total_down"]

diff = block.transaction['up'] - block.transaction['down']
if peer2['id'] not in self.successors(peer1['id']):
self.add_edge(peer1['id'], peer2['id'], weight=diff)

def add_blocks(self, blocks):
for block in blocks:
self.add_block(block)

def compute_node_graph(self):
gr_undirected = self.to_undirected()
num_nodes = len(gr_undirected.node)

# Remove disconnected nodes from the graph
component_nodes = nx.node_connected_component(gr_undirected, self.root_node)
for node in list(gr_undirected.nodes()):
if node not in component_nodes:
gr_undirected.remove_node(node)

# Find bfs tree of the connected components
bfs_tree = nx.bfs_tree(gr_undirected, self.root_node)

# Position the nodes in a circular fashion according to the bfs tree
pos = gpos.hierarchy_pos(bfs_tree, root=self.root_node, width=2 * math.pi, xcenter=0.5)

graph_nodes = []
graph_edges = []
index_mapper = {}

node_id = 0
max_x = max_y = 0.0001
for _id, (theta, r) in pos.items():
index_mapper[_id] = node_id
node = gr_undirected.node[_id]
node['id'] = node_id
node_id += 1

# convert from polar coordinates to cartesian coordinates
x = r * math.sin(theta) * num_nodes
y = r * math.cos(theta) * num_nodes
node['pos'] = [x, y]
graph_nodes.append(node)

# max values to be used for normalization
max_x = max(abs(x), max_x)
max_y = max(abs(y), max_y)

# Normalize the positions
for node in graph_nodes:
node['pos'][0] /= max_x
node['pos'][1] /= max_y

for edge in gr_undirected.edges():
graph_edges.append((index_mapper[edge[0]], index_mapper[edge[1]]))

return {'node': graph_nodes, 'edge': graph_edges}


class TrustViewEndpoint(resource.Resource):
def __init__(self, session):
resource.Resource.__init__(self)
self.session = session

self.trustchain_db = None
self.trust_graph = None
self.public_key = None

def initialize_graph(self):
if not self.initialized and self.session.lm.trustchain_community and not self.local_view:
pub_key = self.session.lm.trustchain_community.my_peer.public_key.key_to_bin()
self.root_public_key = hexlify(pub_key)
self.local_view = NodeVision(self.root_public_key)
if self.session.lm.trustchain_community:
self.trustchain_db = self.session.lm.trustchain_community.persistence
self.initialized = True
self.public_key = self.session.lm.trustchain_community.my_peer.public_key.key_to_bin()
self.trust_graph = TrustGraph(hexlify(self.public_key))

# Start bootstrap download if not already done
if not self.session.lm.bootstrap:
self.session.lm.start_bootstrap_download()

@staticmethod
def block_to_edge(block):
if not block:
return None

diff = block.transaction['up'] - block.transaction['down']
if diff < 0:
return {'downloader': hexlify(block.public_key),
'uploader': hexlify(block.link_public_key),
'amount': diff * -1
}
return {'downloader': hexlify(block.link_public_key),
'uploader': hexlify(block.public_key),
'amount': diff
}

def load_single_block(self, block):
if block.hash not in self.transactions and block.type == 'tribler_bandwidth':
self.transactions[block.hash] = self.block_to_edge(block)
# Update token balance
hex_public_key = hexlify(block.public_key)
node_balance = self.token_balance.get(hex_public_key, dict())
if block.sequence_number > node_balance.get('sequence_number', 0):
node_balance['sequence_number'] = block.sequence_number
node_balance['total_up'] = block.transaction["total_up"]
node_balance['total_down'] = block.transaction["total_down"]
self.token_balance[hex_public_key] = node_balance

def load_blocks(self, blocks):
for block in blocks:
self.load_single_block(block)

def render_GET(self, _):
self.initialize_graph()

# Load your 25 latest trustchain blocks
pub_key = self.session.lm.trustchain_community.my_peer.public_key.key_to_bin()
blocks = self.trustchain_db.get_latest_blocks(pub_key)
self.load_blocks(blocks)

# Load 5 latest blocks of all the connected users in the database
connected_blocks = self.trustchain_db.get_connected_users(pub_key)
for connected_block in connected_blocks:
blocks = self.trustchain_db.get_latest_blocks(unhexlify(connected_block['public_key']), limit=5)
self.load_blocks(blocks)

# Load 5 latest blocks of all the users in the database
user_blocks = self.trustchain_db.get_users(limit=-1)
for user_block in user_blocks:
blocks = self.trustchain_db.get_latest_blocks(unhexlify(user_block['public_key']), limit=5)
self.load_blocks(blocks)

# Add blocks to graph and update your local view
self.local_view.add_transactions(self.transactions.values())
self.local_view.lay_down_nodes()
self.local_view.reposition_nodes()
self.local_view.update_component()

positions = self.local_view.normalize_positions_dict()
graph_data = json_graph.node_link_data(self.local_view.component)

return json.twisted_dumps({'root_public_key': self.root_public_key,
'graph_data': graph_data,
'positions': positions,
'bootstrap': self.get_bootstrap_info(),
'num_tx': len(self.transactions),
'token_balance': self.token_balance
})
def render_GET(self, request):
if not self.trust_graph:
self.initialize_graph()

def get_bandwidth_blocks(public_key, limit=64):
return self.trustchain_db.get_latest_blocks(public_key, limit=limit, block_types=['tribler_bandwidth'])

def get_friends(public_key, limit=64):
return self.trustchain_db.get_connected_users(public_key, limit=limit)

depth = 0
if 'depth' in request.args:
depth = int(request.args['depth'][0])

# If depth is zero or not provided then fetch all depth levels
fetch_all = depth == 0

if fetch_all or depth == 1:
self.trust_graph.add_blocks(get_bandwidth_blocks(self.public_key))
if fetch_all or depth == 2:
for friend in get_friends(self.public_key):
self.trust_graph.add_blocks(get_bandwidth_blocks(unhexlify(friend['public_key'])))
if fetch_all or depth == 3:
for friend in get_friends(self.public_key):
self.trust_graph.add_blocks(get_bandwidth_blocks(unhexlify(friend['public_key'])))
for fof in get_friends(unhexlify(friend['public_key'])):
self.trust_graph.add_blocks(get_bandwidth_blocks(unhexlify(fof['public_key'])))
if fetch_all or depth == 4:
for user_block in self.trustchain_db.get_users():
self.trust_graph.add_blocks(get_bandwidth_blocks(unhexlify(user_block['public_key'])))

graph_data = self.trust_graph.compute_node_graph()

return json.twisted_dumps(
{
'root_public_key': hexlify(self.public_key),
'graph': graph_data,
'bootstrap': self.get_bootstrap_info(),
'num_tx': len(graph_data['edge']),
'depth': depth,
}
)

def get_bootstrap_info(self):
if self.session.lm.bootstrap.download and self.session.lm.bootstrap.download.get_state():
state = self.session.lm.bootstrap.download.get_state()
return {'download': state.get_total_transferred(DOWNLOAD),
'upload': state.get_total_transferred(UPLOAD),
'progress': state.get_progress()
}
return {
'download': state.get_total_transferred(DOWNLOAD),
'upload': state.get_total_transferred(UPLOAD),
'progress': state.get_progress(),
}
return {'download': 0, 'upload': 0, 'progress': 0}
101 changes: 86 additions & 15 deletions Tribler/Test/Core/Modules/RestApi/test_trustview_endpoint.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import absolute_import

import random
from binascii import unhexlify

from ipv8.attestation.trustchain.block import TrustChainBlock
from ipv8.attestation.trustchain.community import TrustChainCommunity
Expand All @@ -12,11 +13,9 @@
import Tribler.Core.Utilities.json_util as json
from Tribler.Test.Core.Modules.RestApi.base_api_test import AbstractApiTest
from Tribler.Test.Core.base_test import MockObject
from Tribler.Test.tools import trial_timeout


class TestTrustViewEndpoint(AbstractApiTest):

@inlineCallbacks
def setUp(self):
yield super(TestTrustViewEndpoint, self).setUp()
Expand All @@ -42,26 +41,98 @@ def tearDown(self):
yield self.mock_ipv8.unload()
yield super(TestTrustViewEndpoint, self).tearDown()

@trial_timeout(10)
@inlineCallbacks
def test_trustview_response(self):
"""
Test whether the trust graph response is correctly returned.
"""

def verify_response(response):
root_key = self.session.lm.trustchain_community.my_peer.public_key.key_to_bin()
friends = [
"4c69624e61434c504b3a2ee28ce24a2259b4e585b81106cdff4359fcf48e93336c11d133b01613f30b03b4db06df27"
"80daac2cdf2ee60be611bf7367a9c1071ac50d65ca5858a50e9578",
"4c69624e61434c504b3a5368c7b39a82063e29576df6d74fba3e0dba3af8e7a304b553b71f08ea6a0730e8cef767a4"
"85dc6f390b6da5631f772941ea69ce2c098d802b7a28b500edf2b3",
"4c69624e61434c504b3a0f3f6318e49ffeb0a160e7fcac5c1d3337ba409b45e1371ddca5e3b364ebdd1b73c775318a"
"533a25335a5c36ae3695f1c3036b651893659fbf2e1f2bce66cf65",
]

fofs = [
"4c69624e61434c504b3a2ee28ce24a2259b4e585b81106cdff4359fcf48e93336c11d133b01613f30b03b4db06df27"
"80daac2cdf2ee60be611bf7367a9c1071ac50d65ca5858a50e9579",
"4c69624e61434c504b3a5368c7b39a82063e29576df6d74fba3e0dba3af8e7a304b553b71f08ea6a0730e8cef767a4"
"85dc6f390b6da5631f772941ea69ce2c098d802b7a28b500edf2b4",
"4c69624e61434c504b3a0f3f6318e49ffeb0a160e7fcac5c1d3337ba409b45e1371ddca5e3b364ebdd1b73c775318a"
"533a25335a5c36ae3695f1c3036b651893659fbf2e1f2bce66cf66",
]

fofofs = [
"4c69624e61434c504b3a2ee28ce24a2259b4e585b81106cdff4359fcf48e93336c11d133b01613f30b03b4db06df27"
"80daac2cdf2ee60be611bf7367a9c1071ac50d65ca5858a50e9580",
"4c69624e61434c504b3a5368c7b39a82063e29576df6d74fba3e0dba3af8e7a304b553b71f08ea6a0730e8cef767a4"
"85dc6f390b6da5631f772941ea69ce2c098d802b7a28b500edf2b5",
"4c69624e61434c504b3a0f3f6318e49ffeb0a160e7fcac5c1d3337ba409b45e1371ddca5e3b364ebdd1b73c775318a"
"533a25335a5c36ae3695f1c3036b651893659fbf2e1f2bce66cf67",
]

def get_dummy_tx():
return {
'up': random.randint(1, 101),
'down': random.randint(1, 101),
'total_up': random.randint(1, 101),
'total_down': random.randint(1, 101),
}

def verify_response(response, nodes, tx):
response_json = json.twisted_loads(response)
self.assertIsNotNone(response_json['graph_data'])
self.assertEqual(response_json['num_tx'], 1)
self.assertEqual(len(response_json['graph_data']['nodes']), 2)
self.assertIsNotNone(response_json['graph'])
self.assertEqual(response_json['num_tx'], tx)
self.assertEqual(len(response_json['graph']['node']), nodes)

transaction = {'up': 100, 'down': 0, 'total_up': 100, 'total_down': 0}
test_block = TrustChainBlock()
test_block.type = 'tribler_bandwidth'
test_block.transaction = transaction
test_block._transaction = encode(transaction)
test_block.public_key = self.session.lm.trustchain_community.my_peer.public_key.key_to_bin()
test_block.hash = test_block.calculate_hash()
self.session.lm.trustchain_community.persistence.add_block(test_block)

for seq, pub_key in enumerate(friends):
test_block.transaction = get_dummy_tx()
test_block._transaction = encode(test_block.transaction)

test_block.sequence_number = seq
test_block.public_key = root_key
test_block.link_public_key = unhexlify(pub_key)

test_block.hash = test_block.calculate_hash()
self.session.lm.trustchain_community.persistence.add_block(test_block)

for ind, friend in enumerate(friends):
for ind2, fof in enumerate(fofs):
test_block.transaction = get_dummy_tx()
test_block._transaction = encode(test_block.transaction)

test_block.sequence_number = ind + ind2
test_block.public_key = unhexlify(friend)
test_block.link_public_key = unhexlify(fof)

test_block.hash = test_block.calculate_hash()
self.session.lm.trustchain_community.persistence.add_block(test_block)

for ind3, fof in enumerate(fofs):
for ind4, fofof in enumerate(fofofs):
test_block.transaction = get_dummy_tx()
test_block._transaction = encode(test_block.transaction)

test_block.sequence_number = ind3 + ind4
test_block.public_key = unhexlify(fof)
test_block.link_public_key = unhexlify(fofof)

test_block.hash = test_block.calculate_hash()
self.session.lm.trustchain_community.persistence.add_block(test_block)

self.should_check_equality = False
return self.do_request(b'trustview', expected_code=200).addCallback(verify_response)
yield self.do_request(b'trustview?depth=1', expected_code=200)\
.addCallback(lambda res: verify_response(res, 4, 3))
yield self.do_request(b'trustview?depth=2', expected_code=200)\
.addCallback(lambda res: verify_response(res, 7, 12))
yield self.do_request(b'trustview?depth=3', expected_code=200)\
.addCallback(lambda res: verify_response(res, 10, 21))
yield self.do_request(b'trustview?depth=4', expected_code=200)\
.addCallback(lambda res: verify_response(res, 10, 21))
return
Loading

0 comments on commit f5cedb2

Please sign in to comment.