-
Notifications
You must be signed in to change notification settings - Fork 452
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4473 from xoriole/trust-graph
Trust graph
- Loading branch information
Showing
17 changed files
with
1,251 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
""" | ||
The TrustCalculation package contains the procedures required for | ||
the monitoring of transactions and the calculation of trust | ||
""" |
80 changes: 80 additions & 0 deletions
80
Tribler/Core/Modules/TrustCalculation/graph_positioning.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from __future__ import absolute_import, division | ||
|
||
import random | ||
|
||
import networkx as nx | ||
|
||
|
||
class GraphPositioning(object): | ||
""" | ||
This class is for the calculation of the positions of the nodes of | ||
a given tree and from the perspective of a given central node | ||
""" | ||
|
||
@staticmethod | ||
def hierarchy_pos(G, root=None, width=1., vert_gap=0.2, | ||
vert_loc=0, xcenter=0.5): | ||
""" | ||
Taken from: https://bit.ly/2tetWxf | ||
If the graph is a tree this will return the positions to plot this in a | ||
hierarchical layout. | ||
G: the graph (must be a tree) | ||
root: the root node of current branch | ||
- if the tree is directed and this is not given, | ||
the root will be found and used | ||
- if the tree is directed and this is given, then the positions | ||
will be just for the descendants of this node. | ||
- if the tree is undirected and not given, then a random | ||
choice will be used. | ||
width: horizontal space allocated for this branch - avoids overlap | ||
with other branches | ||
vert_gap: gap between levels of hierarchy | ||
vert_loc: vertical location of root | ||
xcenter: horizontal location of root | ||
""" | ||
|
||
if not nx.is_tree(G): | ||
raise TypeError('cannot use hierarchy_pos on a' | ||
' graph that is not a tree') | ||
|
||
if root is None: | ||
if isinstance(G, nx.DiGraph): | ||
# allows back compatibility with nx version 1.11 | ||
root = next(iter(nx.topological_sort(G))) | ||
else: | ||
root = random.choice(list(G.nodes)) | ||
|
||
def _hierarchy_pos(G, root, width=1., vert_gap=0.2, vert_loc=0, | ||
xcenter=0.5, pos=None, parent=None): | ||
""" | ||
see hierarchy_pos docstring for most arguments | ||
pos: a dict saying where all nodes go if they have been assigned | ||
parent: parent of this branch. - only affects it if non-directed | ||
""" | ||
|
||
if pos is None: | ||
pos = {root: (xcenter, vert_loc)} | ||
else: | ||
pos[root] = (xcenter, vert_loc) | ||
children = list(G.neighbors(root)) | ||
if not isinstance(G, nx.DiGraph) and parent is not None: | ||
children.remove(parent) | ||
if children: | ||
dx = width/len(children) | ||
nextx = xcenter - width/2 - dx/2 | ||
for child in children: | ||
nextx += dx | ||
pos = _hierarchy_pos(G, child, width=dx, vert_gap=vert_gap, | ||
vert_loc=vert_loc-vert_gap, | ||
xcenter=nextx, pos=pos, parent=root) | ||
return pos | ||
|
||
return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
from __future__ import absolute_import, division | ||
|
||
import math | ||
|
||
import networkx as nx | ||
|
||
from Tribler.Core.Modules.TrustCalculation.graph_positioning import GraphPositioning as gpos | ||
|
||
|
||
class NodeVision(object): | ||
""" | ||
This object is used for laying out the nodes of a graph according to \ | ||
the local vision of a specific node (root node) | ||
""" | ||
|
||
def __init__(self, root_node): | ||
""" | ||
:param graph: Peer network | ||
:param root_node: Transaction keypair of the node | ||
""" | ||
|
||
self.graph = nx.DiGraph() | ||
self.graph.add_node(root_node) | ||
|
||
self.root_node = root_node | ||
self.bfs_tree = {} # Bfs tree rooted at the peer | ||
self.component = None # The connected component which includes the peer | ||
|
||
self.pos = self.lay_down_nodes() | ||
self.component_pos = dict(self.pos) | ||
|
||
self.update_component() | ||
|
||
def set_root_node(self, rootnode): | ||
self.root_node = rootnode | ||
self.pos = self.lay_down_nodes() | ||
|
||
@property | ||
def n_nodes(self): | ||
return self.graph.number_of_nodes() | ||
|
||
@property | ||
def node_positions(self): | ||
return dict(self.graph.nodes(data='pos')) | ||
|
||
def reposition_nodes(self): | ||
self.pos = self.lay_down_nodes() | ||
self.component_pos = self.normalize_positions_dict() | ||
|
||
def lay_down_nodes(self): | ||
""" | ||
Given a directed graph, finds the connected component which includes | ||
the root node and then determines positions in the circular view. | ||
:return: Positions of the nodes from the perspective of the root node | ||
!!! returned dict does not contain positions of unconnected nodes! | ||
""" | ||
# Find undirected graph (needed for connected component discovery) | ||
gr_undirected = self.graph.to_undirected() | ||
|
||
# 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) | ||
self.bfs_tree[self.root_node] = bfs_tree | ||
|
||
# Position the nodes in a circular fashion according to the bfs tree | ||
pos = gpos.hierarchy_pos(bfs_tree, self.root_node, | ||
width=2 * math.pi, xcenter=0.5) | ||
new_pos = {u: (r * math.cos(theta), r * math.sin(theta)) | ||
for u, (theta, r) in pos.items()} | ||
|
||
# Set positions to the networkx object | ||
nx.set_node_attributes(self.graph, new_pos, 'pos') | ||
|
||
# Also, return the positions | ||
return new_pos | ||
|
||
def normalize_positions_dict(self, width=0.80, margin=0.05): | ||
poslist = [v for v in self.pos.values() if v is not None] | ||
minx = min(poslist, key=lambda t: t[0])[0] | ||
miny = min(poslist, key=lambda t: t[1])[1] | ||
maxx = max(poslist, key=lambda t: t[0])[0] | ||
maxy = max(poslist, key=lambda t: t[1])[1] | ||
xinterval = (maxx - minx) | ||
yinterval = (maxy - miny) | ||
|
||
# To escape from division by zero: | ||
if xinterval == 0: | ||
xinterval = 1.0 | ||
if yinterval == 0: | ||
yinterval = 1.0 | ||
|
||
newposlist = {} | ||
|
||
for node, pos in self.pos.items(): | ||
if pos is not None: | ||
nposx = ((pos[0] - minx) / xinterval) * width + margin | ||
nposy = ((pos[1] - miny) / yinterval) * width + margin | ||
newposlist[node] = (nposx, nposy) | ||
|
||
return newposlist | ||
|
||
def add_transactions(self, transactions): | ||
for tr in transactions: | ||
self.add_edge_to_graph(tr['downloader'], | ||
tr['uploader'], | ||
tr['amount']) | ||
|
||
def add_edge_to_graph(self, n1, n2, w): | ||
if n1 in self.graph and n2 in self.graph.successors(n1): | ||
self.graph[n1][n2]['weight'] *= 0.8 | ||
self.graph[n1][n2]['weight'] += (0.2 * w) | ||
else: | ||
self.graph.add_edge(n1, n2, weight=w) | ||
|
||
def update_component(self): | ||
H = self.graph.to_undirected() | ||
self.component = nx.DiGraph(self.graph) | ||
|
||
component_nodes = nx.node_connected_component(H, self.root_node) | ||
for node in self.graph: | ||
if node not in component_nodes: | ||
self.component.remove_node(node) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
from __future__ import absolute_import | ||
|
||
import logging | ||
from binascii import hexlify, unhexlify | ||
|
||
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.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__) | ||
|
||
self.node_id = None | ||
self.local_view = None | ||
|
||
self.bootstrap = None | ||
self.peers = [] | ||
self.transactions = {} | ||
self.initialized = False | ||
self.trustchain_db = 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.node_id = hexlify(pub_key) | ||
self.local_view = NodeVision(self.node_id) | ||
self.trustchain_db = self.session.lm.trustchain_community.persistence | ||
self.initialized = True | ||
|
||
# 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: | ||
self.transactions[block.hash] = self.block_to_edge(block) | ||
|
||
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 25 latest blocks of all the users in the database | ||
userblocks = self.trustchain_db.get_users() | ||
for userblock in userblocks: | ||
blocks = self.trustchain_db.get_latest_blocks(unhexlify(userblock['public_key']), limit=25) | ||
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 = nx.node_link_data(self.local_view.component) | ||
|
||
return json.twisted_dumps({'node_id': self.node_id, | ||
'graph_data': graph_data, | ||
'positions': positions, | ||
'bootstrap': self.get_bootstrap_info(), | ||
'num_tx': len(self.transactions) | ||
}) | ||
|
||
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': 0, 'upload': 0, 'progress': 0} |
Oops, something went wrong.