def _connect_to_peers(self): min_peer_count = self.Config.get("InitialConnectivity", 1) current_peer_count = len(self.Ledger.peer_list()) logger.debug("peer count is %d of %d", current_peer_count, min_peer_count) if current_peer_count < min_peer_count: peerset = self._get_candidate_peers() # Add the candidate nodes to the gossip object so we can send # connect requests to them for peername in peerset: peer = self.NodeMap.get(peername) if peer: logger.info('add peer %s with identifier %s', peername, peer.Identifier) connect_message.send_connection_request(self.Ledger, peer) self.Ledger.add_node(peer) else: logger.info('requested connection to unknown peer %s', peername) return False else: return True
def initialize_ledger_connection(self): """ Connect the ledger to the rest of the network. """ assert self.Ledger self.status = 'waiting for initial connections' min_peer_count = self.Config.get("InitialConnectivity", 1) current_peer_count = len(self.Ledger.peer_list()) logger.debug("initial peer count is %d of %d", current_peer_count, min_peer_count) if current_peer_count < min_peer_count: peerset = self._get_candidate_peers() # Add the candidate nodes to the gossip object so we can send # connect requests to them for peername in peerset: peer = self.NodeMap.get(peername) if peer: logger.info('add peer %s with identifier %s', peername, peer.Identifier) connect_message.send_connection_request(self.Ledger, peer) self.Ledger.add_node(peer) else: logger.info('requested connection to unknown peer %s', peername) reactor.callLater(2.0, self.initialize_ledger_connection) else: reactor.callLater(2.0, self.initialize_ledger_topology, self.start_journal_transfer)
def _connect_to_peers(self): min_peer_count = self.config.get("InitialConnectivity", 1) current_peer_count = len(self.gossip.peer_list()) logger.debug("peer count is %d of %d", current_peer_count, min_peer_count) if current_peer_count < min_peer_count: peerset = self._get_candidate_peers() # Add the candidate nodes to the gossip object so we can send # connect requests to them for peername in peerset: peer = self.NodeMap.get(peername) if peer: logger.info('add peer %s with identifier %s', peername, peer.Identifier) connect_message.send_connection_request(self.gossip, peer) self.gossip.add_node(peer) else: logger.info('requested connection to unknown peer %s', peername) return False else: return True
def update_connections(gossiper, topology, oncomplete): """Connects the node to the network by building a Barabasi-Albert graph. Note: For more information see http://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model Args: gossiper (Node): The local node. topology (dict): Map of nodes to connections. oncomplete (function): The function to call once the topology update has completed. """ logger.info("update connections from topology probe") for peer, connections in topology.iteritems(): logger.debug("node %s --> %s", peer.Name, len(connections)) # First pass through the topology information that was collected, compute # the total number of connections per node which will give us a # distribution for connections, Bara total = 0 candidates = {} for peer, connections in topology.iteritems(): if peer.Identifier == gossiper.LocalNode.Identifier: continue if peer.Identifier in gossiper.NodeMap: continue # this is strictly NOT part of the Barabasi graph construction because # it forces a limit on connectivity, however it removes some of the # worst hotspots without fundamentally changing the graph structure count = len(connections) if count > MaximumConnectivity: continue candidates[peer] = count total += count # Second pass selects some subset of nodes based on the number of existing # connections and sends out a connection request to each if total > 0: for peer, count in candidates.iteritems(): # the FudgeFactor is used to increase the chance that we'll connect # to a node, strictly speaking the fudge factor should be 0 if random.randint(0, total - 1) < count + ConnectivityFudgeFactor: connect_message.send_connection_request(gossiper, peer) # call the final handler oncomplete(gossiper)
def random_walk_handler(msg, gossiper): """Function called when the gossiper receives a RandomWalkMessage from one of its peers. Args: msg (message.Message): The received random walk message. gossiper (Node): The local node. """ if msg.OriginatorID == gossiper.LocalNode.Identifier: logger.debug('node %s received its own random walk request, ignore', gossiper.LocalNode) return logger.debug('random walk request %s from %s with ttl %d', msg.Identifier[:8], msg.Name, msg.TimeToLive) peers = gossiper.peer_id_list() # if the source is not already one of our peers, then check to see if we # should add it to our list if msg.OriginatorID not in peers: if len(peers) < random_connections(): logger.debug( 'add connection to node %s based on random walk request %s', msg.Name, msg.Identifier[:8]) onode = node.Node(address=msg.NetAddress, identifier=msg.NodeIdentifier, name=msg.Name) onode.enable() send_connection_request(gossiper, onode) return # if there is still life in the message, then see if we should forward it # to another node if msg.TimeToLive > 0: # see if we can find a peer other than the peer who forwarded the # message to us, if not then we'll just drop the request try: peers.remove(msg.SenderID) peers.remove(msg.OriginatorID) except: pass if len(peers) > 0: peerid = random.choice(peers) gossiper.send_message(msg, peerid, initialize=False)
def initialize_ledger_topology(self, callback): """ Make certain that there is at least one connected peer and then kick off the configured topology generation protocol. """ logger.debug('initialize ledger topology') # make sure there is at least one connection already confirmed if len(self.Ledger.peer_list()) == 0: self._connectionattempts -= 1 if self._connectionattempts > 0: logger.info( 'initial connection attempts failed, ' 'try again [%s]', self._connectionattempts) for peer in self.Ledger.peer_list(allflag=True): connect_message.send_connection_request(self.Ledger, peer) reactor.callLater(2.0, self.initialize_ledger_topology, callback) return else: logger.critical('failed to connect to selected peers, ' 'shutting down') self.shutdown() return self._connectionattempts = 0 # and now its time to pick the topology protocol topology = self.Config.get("TopologyAlgorithm", "RandomWalk") if topology == "RandomWalk": if 'TargetConnectivity' in self.Config: random_walk.TargetConnectivity = self.Config[ 'TargetConnectivity'] self.random_walk_initialization(callback) elif topology == "BarabasiAlbert": if 'MaximumConnectivity' in self.Config: barabasi_albert.MaximumConnectivity = self.Config[ 'MaximumConnectivity'] if 'MinimumConnectivity' in self.Config: barabasi_albert.MinimumConnectivity = self.Config[ 'MinimumConnectivity'] self.barabasi_initialization(callback) else: logger.error("unknown topology protocol %s", topology) self.shutdown() return
def initialize_ledger_topology(self, callback): """ Make certain that there is at least one connected peer and then kick off the configured topology generation protocol. """ logger.debug('initialize ledger topology') # make sure there is at least one connection already confirmed if len(self.Ledger.peer_list()) == 0: self._connectionattempts -= 1 if self._connectionattempts > 0: logger.info('initial connection attempts failed, ' 'try again [%s]', self._connectionattempts) for peer in self.Ledger.peer_list(allflag=True): connect_message.send_connection_request(self.Ledger, peer) reactor.callLater(2.0, self.initialize_ledger_topology, callback) return else: logger.critical('failed to connect to selected peers, ' 'shutting down') self.shutdown() return self._connectionattempts = 0 # and now its time to pick the topology protocol topology = self.Config.get("TopologyAlgorithm", "RandomWalk") if topology == "RandomWalk": if 'TargetConnectivity' in self.Config: random_walk.TargetConnectivity = self.Config[ 'TargetConnectivity'] self.random_walk_initialization(callback) elif topology == "BarabasiAlbert": if 'MaximumConnectivity' in self.Config: barabasi_albert.MaximumConnectivity = self.Config[ 'MaximumConnectivity'] if 'MinimumConnectivity' in self.Config: barabasi_albert.MinimumConnectivity = self.Config[ 'MinimumConnectivity'] self.barabasi_initialization(callback) else: logger.error("unknown topology protocol %s", topology) self.shutdown() return
def _get_quorum(gossiper, callback): """Attempts to connect gossiper to new available quorum nodes Args: gossiper (Node): The local node. callback (function): The function to call once the quorum topology update has completed. """ # find out how many we have and how many nodes we still need need count = max(0, TargetConnectivity - len(gossiper.VotingQuorum.keys())) # we have all the nodes we need; do next operation (the callback) if count <= 0: logger.debug('sufficiently connected via %s', [str(x.Name) for x in gossiper.VotingQuorum.itervalues()]) callback() return # add nodes we don't have already, in random order candidates = [ x for x in gossiper.quorum_list() if gossiper.VotingQuorum.get(x.Identifier, None) is None ] random.shuffle(candidates) logger.debug('trying to increase working quorum by %d from candidates %s', count, [str(x.Name) for x in candidates]) for nd in candidates: lwc = LedgerWebClient('http://{0}:{1}'.format(nd.NetHost, nd.HttpPort)) try: status = lwc.get_status(verbose=False, timeout=2) except MessageException as e: logger.debug(e.message) continue status = status.get('Status', '') if status in [ 'started', "transferring ledger", "waiting for initial connections" ]: # candidate node is live; add it logger.debug('adding %s to quorum', nd.Name) gossiper.add_quorum_node(nd) if nd.Identifier not in gossiper.peer_id_list(): send_connection_request(gossiper, nd) count -= 1 if count == 0: logger.debug('now sufficiently connected') break # try again (or possibly execute the piggybacked callback) reactor.callLater(TimeBetweenProbes, _get_quorum, gossiper, callback)
def _get_quorum(gossiper, callback): """Attempts to connect gossiper to new available quorum nodes Args: gossiper (Node): The local node. callback (function): The function to call once the quorum topology update has completed. """ # find out how many we have and how many nodes we still need need count = max(0, TargetConnectivity - len(gossiper.VotingQuorum.keys())) # we have all the nodes we need; do next operation (the callback) if count <= 0: logger.debug('sufficiently connected via %s', [str(x.Name) for x in gossiper.VotingQuorum.itervalues()]) callback() return # add nodes we don't have already, in random order candidates = [x for x in gossiper.quorum_list() if gossiper.VotingQuorum.get(x.Identifier, None) is None] random.shuffle(candidates) logger.debug('trying to increase working quorum by %d from candidates %s', count, [str(x.Name) for x in candidates]) for nd in candidates: client = SawtoothClient('http://{0}:{1}'.format(nd.NetHost, nd.HttpPort)) try: status = client.get_status(timeout=2) except MessageException as e: logger.debug(e.message) continue status = status.get('Status', '') if status in ['started', "transferring ledger", "waiting for initial connections"]: # candidate node is live; add it logger.debug('adding %s to quorum', nd.Name) gossiper.add_quorum_node(nd) if nd.Identifier not in gossiper.peer_id_list(): send_connection_request(gossiper, nd) count -= 1 if count == 0: logger.debug('now sufficiently connected') break # try again (or possibly execute the piggybacked callback) reactor.callLater(TimeBetweenProbes, _get_quorum, gossiper, callback)
def initialize_ledger_connection(self): """ Connect the ledger to the rest of the network; in addition to the list of nodes directly specified in the configuration file, pull a list from the LedgerURL. Once the list of potential peers is constructed, pick from it those specified in the Peers configuration variable. If that is not enough, then pick more at random from the list. """ assert self.Ledger url = self.Config.get('LedgerURL', '**none**') if url != '**none**': logger.info('load peers using url %s', self.Config['LedgerURL']) try: peers = self.get_endpoints(0, self.EndpointDomain) for peer in peers: self.NodeMap[peer.Name] = peer except ledger_web_client.MessageException as e: logger.error("Unable to get endpoints from LedgerURL: %s", str(e)) # Build a list of nodes that we can use for the initial connection minpeercount = self.Config.get("InitialConnectivity", 1) peerset = set(self.Config.get('Peers', [])) nodeset = set(self.NodeMap.keys()) if len(peerset) < minpeercount and len(nodeset) > 0: nodeset.discard(self.Ledger.LocalNode.Name) nodeset = nodeset.difference(peerset) peerset = peerset.union( random.sample(list(nodeset), min(minpeercount - len(peerset), len(nodeset)))) # Add the candidate nodes to the gossip object so we can send connect # requests to them for peername in peerset: peer = self.NodeMap.get(peername) if peer: logger.info('add peer %s with identifier %s', peername, peer.Identifier) self.Ledger.add_node(peer) else: logger.info('requested connection to unknown peer %s', peername) # the pathological case is that there was nothing specified and since # we already know we aren't the genesis block, we can just shut down if len(self.Ledger.peer_list(allflag=True)) == 0: logger.critical('unable to find a valid peer') self.shutdown() return # and send all the connection requests, must use allflag because we # added the nodes disabled, the connect response will mark them # enabled for peer in self.Ledger.peer_list(allflag=True): connect_message.send_connection_request(self.Ledger, peer) logger.debug("initial ledger connection requests sent") # Wait for the connection message to be processed before jumping to the # next state a better technique would be to add an event in sawtooth # when a new node is connected self._connectionattempts = 3 reactor.callLater(2.0, self.initialize_ledger_topology, self.start_journal_transfer)
def initialize_ledger_connection(self): """ Connect the ledger to the rest of the network; in addition to the list of nodes directly specified in the configuration file, pull a list from the LedgerURL. Once the list of potential peers is constructed, pick from it those specified in the Peers configuration variable. If that is not enough, then pick more at random from the list. """ assert self.Ledger url = self.Config.get('LedgerURL', '**none**') if url != '**none**': logger.info('load peers using url %s', self.Config['LedgerURL']) try: peers = self.get_endpoints(0, self.EndpointDomain) for peer in peers: self.NodeMap[peer.Name] = peer except ledger_web_client.MessageException as e: logger.error("Unable to get endpoints from LedgerURL: %s", str(e)) # Build a list of nodes that we can use for the initial connection minpeercount = self.Config.get("InitialConnectivity", 1) peerset = set(self.Config.get('Peers', [])) nodeset = set(self.NodeMap.keys()) if len(peerset) < minpeercount and len(nodeset) > 0: nodeset.discard(self.Ledger.LocalNode.Name) nodeset = nodeset.difference(peerset) peerset = peerset.union(random.sample(list(nodeset), min( minpeercount - len(peerset), len(nodeset)))) # Add the candidate nodes to the gossip object so we can send connect # requests to them for peername in peerset: peer = self.NodeMap.get(peername) if peer: logger.info('add peer %s with identifier %s', peername, peer.Identifier) self.Ledger.add_node(peer) else: logger.info('requested connection to unknown peer %s', peername) # the pathological case is that there was nothing specified and since # we already know we aren't the genesis block, we can just shut down if len(self.Ledger.peer_list(allflag=True)) == 0: logger.critical('unable to find a valid peer') self.shutdown() return # and send all the connection requests, must use allflag because we # added the nodes disabled, the connect response will mark them # enabled for peer in self.Ledger.peer_list(allflag=True): connect_message.send_connection_request(self.Ledger, peer) logger.debug("initial ledger connection requests sent") # Wait for the connection message to be processed before jumping to the # next state a better technique would be to add an event in sawtooth # when a new node is connected self._connectionattempts = 3 reactor.callLater(2.0, self.initialize_ledger_topology, self.start_journal_transfer)