public static NodeContact[] GetClosestContacts(IEnumerable <NodeContact> contacts, BinaryID nodeID, int count) { NodeContact[] closestContacts = new NodeContact[count]; BinaryID[] min = new BinaryID[count]; BinaryID distance; int ubound = count - 1; int i; int j; foreach (NodeContact contact in contacts) { distance = nodeID ^ contact.NodeID; for (i = 0; i < count; i++) { if ((min[i] == null) || (distance < min[i])) { //demote existing values for (j = ubound; j > i; j--) { min[j] = min[j - 1]; closestContacts[j] = closestContacts[j - 1]; } //place current on top min[i] = distance; closestContacts[i] = contact; break; } } } return(closestContacts); }
private DhtRpcPacket(int transactionID, NodeContact sourceNode, RpcPacketType type, RpcQueryType queryType) { _transactionID = transactionID; _sourceNode = sourceNode; _type = type; _queryType = queryType; }
public static NodeContact[] GetClosestContacts(ICollection <NodeContact> contacts, BinaryNumber nodeID, int count) { if (contacts.Count < count) { count = contacts.Count; } NodeContact[] closestContacts = new NodeContact[count]; BinaryNumber[] closestContactDistances = new BinaryNumber[count]; foreach (NodeContact contact in contacts) { BinaryNumber distance = nodeID ^ contact.NodeID; for (int i = 0; i < count; i++) { if ((closestContactDistances[i] == null) || (distance < closestContactDistances[i])) { //demote existing values for (int j = count - 1; j > i; j--) { closestContactDistances[j] = closestContactDistances[j - 1]; closestContacts[j] = closestContacts[j - 1]; } //place current on top closestContactDistances[i] = distance; closestContacts[i] = contact; break; } } } return(closestContacts); }
private void CheckContactHealthAsync(object state) { object[] param = state as object[]; DhtClient dhtClient = param[0] as DhtClient; NodeContact contact = param[1] as NodeContact; int retries = 0; do { try { if (dhtClient.Ping(contact)) { return; //contact replied; do nothing. } } catch { } retries++; }while (retries < HEALTH_PING_MAX_RETRIES); try { //remove stale node RemoveContactFromCurrentBucket(contact); } catch { } }
public static DhtRpcPacket CreateFindPeersPacketQuery(NodeContact sourceNode, BinaryID networkID) { DhtRpcPacket packet = new DhtRpcPacket(GetRandomTransactionID(), sourceNode, RpcPacketType.Query, RpcQueryType.FIND_PEERS); packet._networkID = networkID; return(packet); }
public static DhtRpcPacket CreateFindNodePacketResponse(int transactionID, NodeContact sourceNode, BinaryID networkID, NodeContact[] contacts) { DhtRpcPacket packet = new DhtRpcPacket(transactionID, sourceNode, RpcPacketType.Response, RpcQueryType.FIND_NODE); packet._networkID = networkID; packet._contacts = contacts; return(packet); }
public KBucket(NodeContact currentNode) { _bucketDepth = 0; _contacts = new NodeContact[DhtNode.KADEMLIA_K * 2]; _contacts[0] = currentNode; _contactCount = 1; _lastChanged = DateTime.UtcNow; }
public static DhtRpcPacket CreateAnnouncePeerPacketQuery(NodeContact sourceNode, BinaryID networkID, ushort servicePort, BinaryID token) { DhtRpcPacket packet = new DhtRpcPacket(GetRandomTransactionID(), sourceNode, RpcPacketType.Query, RpcQueryType.ANNOUNCE_PEER); packet._networkID = networkID; packet._servicePort = servicePort; packet._token = token; return(packet); }
public override bool Equals(object obj) { NodeContact contact = obj as NodeContact; if (contact == null) { return(false); } return(_nodeID.Equals(contact._nodeID)); }
private DhtRpcPacket Query(DhtRpcPacket query, NodeContact contact) { if (_currentNode.NodeEP.AddressFamily != contact.NodeEP.AddressFamily) { return(null); } Stream s = null; try { s = _manager.GetConnectionStream(contact.NodeEP); //set timeout s.WriteTimeout = QUERY_TIMEOUT; s.ReadTimeout = QUERY_TIMEOUT; //send query query.WriteTo(s); s.Flush(); //read response DhtRpcPacket response = new DhtRpcPacket(s); //auto add contact or update last seen time { NodeContact bucketContact = _routingTable.FindContact(contact.NodeID); if (bucketContact == null) { contact.UpdateLastSeenTime(); _routingTable.AddContact(contact); } else { bucketContact.UpdateLastSeenTime(); } } return(response); } catch { contact.IncrementRpcFailCount(); return(null); } finally { if (s != null) { s.Dispose(); } } }
public static DhtRpcPacket CreateFindPeersPacketResponse(int transactionID, NodeContact sourceNode, BinaryID networkID, NodeContact[] contacts, PeerEndPoint[] peers, BinaryID token) { DhtRpcPacket packet = new DhtRpcPacket(transactionID, sourceNode, RpcPacketType.Response, RpcQueryType.FIND_PEERS); packet._networkID = networkID; packet._contacts = contacts; packet._peers = peers; packet._token = token; return(packet); }
public KBucket(NodeContact currentNode) { _bucketDepth = 0; _contacts = new Dictionary <BinaryID, NodeContact>(); _replacementContacts = new Dictionary <BinaryID, NodeContact>(); _contacts.Add(currentNode.NodeID, currentNode); _bucketContainsCurrentNode = true; _lastChanged = DateTime.UtcNow; _lock = new ReaderWriterLockSlim(); }
public void AddNode(NodeContact contact) { if (!NetUtilities.IsPrivateIP(contact.NodeEP.Address) && (contact.NodeEP.AddressFamily == _currentNode.NodeEP.AddressFamily)) { if (_routingTable.AddContact(contact)) { ThreadPool.QueueUserWorkItem(delegate(object state) { Query(DhtRpcPacket.CreatePingPacket(_currentNode), contact); }); } } }
private static void SplitBucket(KBucket bucket, NodeContact newContact) { if (bucket._contacts == null) { return; } if (!bucket._contacts[0].IsCurrentNode) { throw new ArgumentException("Cannot split this k-bucket: must contain current node to split."); } KBucket leftBucket = new KBucket(bucket, true); KBucket rightBucket = new KBucket(bucket, false); foreach (NodeContact contact in bucket._contacts) { if ((leftBucket._bucketID & contact.NodeID) == leftBucket._bucketID) { leftBucket._contacts[leftBucket._contactCount++] = contact; } else { rightBucket._contacts[rightBucket._contactCount++] = contact; } } KBucket selectedBucket; if ((leftBucket._bucketID & newContact.NodeID) == leftBucket._bucketID) { selectedBucket = leftBucket; } else { selectedBucket = rightBucket; } if (selectedBucket._contactCount == selectedBucket._contacts.Length) { SplitBucket(selectedBucket, newContact); } else { selectedBucket._contacts[selectedBucket._contactCount++] = newContact; } bucket._contacts = null; bucket._leftBucket = leftBucket; bucket._rightBucket = rightBucket; }
private static void SplitBucket(KBucket bucket, NodeContact newContact) { if (bucket._contacts == null) { return; } KBucket leftBucket = new KBucket(bucket, true); KBucket rightBucket = new KBucket(bucket, false); foreach (NodeContact contact in bucket._contacts) { if (contact != null) { if ((leftBucket._bucketID & contact.NodeID) == leftBucket._bucketID) { leftBucket._contacts[leftBucket._contactCount++] = contact; } else { rightBucket._contacts[rightBucket._contactCount++] = contact; } } } KBucket selectedBucket; if ((leftBucket._bucketID & newContact.NodeID) == leftBucket._bucketID) { selectedBucket = leftBucket; } else { selectedBucket = rightBucket; } if (selectedBucket._contactCount == selectedBucket._contacts.Length) { SplitBucket(selectedBucket, newContact); selectedBucket._contactCount++; } else { selectedBucket._contacts[selectedBucket._contactCount++] = newContact; } bucket._contacts = null; bucket._leftBucket = leftBucket; bucket._rightBucket = rightBucket; }
internal bool Ping(NodeContact contact) { DhtRpcPacket response = Query(DhtRpcPacket.CreatePingPacketQuery(_currentNode), contact); if (response == null) { return(false); } else { if (contact.Equals(response.SourceNode)) { return(true); } else { contact.IncrementRpcFailCount(); return(false); } } }
private void QueryAnnounceAsync(object state) { try { object[] parameters = state as object[]; NodeContact contact = parameters[0] as NodeContact; BinaryID networkID = parameters[1] as BinaryID; List <PeerEndPoint> peers = parameters[2] as List <PeerEndPoint>; ushort servicePort = (ushort)parameters[3]; DhtRpcPacket responsePacket = Query(DhtRpcPacket.CreateFindPeersPacketQuery(_currentNode, networkID), contact); if ((responsePacket != null) && (responsePacket.QueryType == RpcQueryType.FIND_PEERS)) { if (responsePacket.Peers.Length > 0) { lock (peers) { foreach (PeerEndPoint peer in responsePacket.Peers) { if (!peers.Contains(peer)) { peers.Add(peer); } } //Monitor.Pulse(peers); //removed so that response from multiple nodes is collected till query times out } } Query(DhtRpcPacket.CreateAnnouncePeerPacketQuery(_currentNode, networkID, servicePort, responsePacket.Token), contact); } } catch { } }
public static DhtRpcPacket CreateAnnouncePeerPacketQuery(NodeContact sourceNode, BinaryNumber networkID, ushort servicePort) { return(new DhtRpcPacket(Convert.ToUInt16(sourceNode.NodeEP.Port), DhtRpcType.ANNOUNCE_PEER, networkID, null, null, servicePort)); }
public static DhtRpcPacket CreateFindPeersPacketResponse(NodeContact sourceNode, BinaryNumber networkID, NodeContact[] contacts, PeerEndPoint[] peers) { return(new DhtRpcPacket(Convert.ToUInt16(sourceNode.NodeEP.Port), DhtRpcType.FIND_PEERS, networkID, contacts, peers, 0)); }
public static DhtRpcPacket CreateFindPeersPacketQuery(NodeContact sourceNode, BinaryNumber networkID) { return(new DhtRpcPacket(Convert.ToUInt16(sourceNode.NodeEP.Port), DhtRpcType.FIND_PEERS, networkID, null, null, 0)); }
public static DhtRpcPacket CreateFindNodePacketResponse(NodeContact sourceNode, BinaryNumber networkID, NodeContact[] contacts) { return(new DhtRpcPacket(Convert.ToUInt16(sourceNode.NodeEP.Port), DhtRpcType.FIND_NODE, networkID, contacts, null, 0)); }
public static DhtRpcPacket CreatePingPacket(NodeContact sourceNode) { return(new DhtRpcPacket(Convert.ToUInt16(sourceNode.NodeEP.Port), DhtRpcType.PING, null, null, null, 0)); }
private void QueryFindAsync(object state) { try { object[] parameters = state as object[]; object lockObj = parameters[0] as object; RpcQueryType queryType = (RpcQueryType)parameters[1]; NodeContact contact = parameters[2] as NodeContact; BinaryID nodeID = parameters[3] as BinaryID; List <NodeContact> availableContacts = parameters[4] as List <NodeContact>; List <NodeContact> respondedContacts = parameters[5] as List <NodeContact>; List <NodeContact> failedContacts = parameters[6] as List <NodeContact>; List <NodeContact> receivedContacts = parameters[7] as List <NodeContact>; DhtRpcPacket responsePacket; if (queryType == RpcQueryType.FIND_NODE) { responsePacket = Query(DhtRpcPacket.CreateFindNodePacketQuery(_currentNode, nodeID), contact); } else { responsePacket = Query(DhtRpcPacket.CreateFindPeersPacketQuery(_currentNode, nodeID), contact); } if ((responsePacket == null) || (responsePacket.QueryType != queryType)) { //time out //add contact to failed contacts lock (failedContacts) { if (!failedContacts.Contains(contact)) { failedContacts.Add(contact); } } return; } //got reply! switch (queryType) { case RpcQueryType.FIND_NODE: lock (receivedContacts) { lock (respondedContacts) { //add contact to responded contacts list if (!respondedContacts.Contains(contact)) { respondedContacts.Add(contact); } lock (failedContacts) { //add received contacts to received contacts list foreach (NodeContact receivedContact in responsePacket.Contacts) { if (!respondedContacts.Contains(receivedContact) && !failedContacts.Contains(receivedContact)) { receivedContacts.Add(receivedContact); } } } } //add received contacts to available contacts list lock (availableContacts) { foreach (NodeContact receivedContact in receivedContacts) { if (!availableContacts.Contains(receivedContact)) { availableContacts.Add(receivedContact); } } } } if (responsePacket.Contacts.Length > 0) { //pulse only if the contact has sent next level contacts list lock (lockObj) { Monitor.Pulse(lockObj); } } break; case RpcQueryType.FIND_PEERS: if (responsePacket.Peers.Length > 0) { List <PeerEndPoint> receivedPeers = parameters[8] as List <PeerEndPoint>; lock (receivedPeers) { foreach (PeerEndPoint peer in responsePacket.Peers) { if (!receivedPeers.Contains(peer)) { receivedPeers.Add(peer); } } } lock (lockObj) { Monitor.Pulse(lockObj); } } break; } } catch { } }
private object QueryFind(NodeContact[] initialContacts, BinaryNumber nodeID, DhtRpcType queryType) { if (initialContacts.Length < 1) { return(null); } List <NodeContact> seenContacts = new List <NodeContact>(initialContacts); List <NodeContact> learnedNotQueriedContacts = new List <NodeContact>(initialContacts); List <NodeContact> respondedContacts = new List <NodeContact>(); List <PeerEndPoint> receivedPeers = null; int alpha = KADEMLIA_ALPHA; bool finalRound = false; bool checkTerminationCondition = false; if (queryType == DhtRpcType.FIND_PEERS) { receivedPeers = new List <PeerEndPoint>(); } NodeContact previousClosestSeenContact = KBucket.SelectClosestContacts(seenContacts, nodeID, 1)[0]; while (true) { NodeContact[] alphaContacts; //pick alpha contacts to query from learned contacts lock (learnedNotQueriedContacts) { alphaContacts = KBucket.SelectClosestContacts(learnedNotQueriedContacts, nodeID, alpha); //remove selected alpha contacts from learned not queries contacts list foreach (NodeContact alphaContact in alphaContacts) { learnedNotQueriedContacts.Remove(alphaContact); } } if (alphaContacts.Length < 1) { checkTerminationCondition = true; } else { object lockObj = new object(); lock (lockObj) { //query each alpha contact async foreach (NodeContact alphaContact in alphaContacts) { Thread t = new Thread(delegate(object state) { DhtRpcPacket response; if (queryType == DhtRpcType.FIND_NODE) { response = Query(DhtRpcPacket.CreateFindNodePacketQuery(_currentNode, nodeID), alphaContact); } else { response = Query(DhtRpcPacket.CreateFindPeersPacketQuery(_currentNode, nodeID), alphaContact); } if ((response == null) || (response.Type != queryType)) { //time out or error //ignore contact by removing from seen contacts list lock (seenContacts) { seenContacts.Remove(alphaContact); } return; } //got reply! if ((queryType == DhtRpcType.FIND_PEERS) && (response.Peers.Length > 0)) { lock (receivedPeers) { foreach (PeerEndPoint peer in response.Peers) { if (!receivedPeers.Contains(peer)) { receivedPeers.Add(peer); } } } } //add alpha contact to responded contacts list lock (respondedContacts) { if (!respondedContacts.Contains(alphaContact)) { respondedContacts.Add(alphaContact); } } //add received contacts to learned contacts list lock (seenContacts) { lock (learnedNotQueriedContacts) { foreach (NodeContact contact in response.Contacts) { if (!seenContacts.Contains(contact)) { seenContacts.Add(contact); learnedNotQueriedContacts.Add(contact); } } } } //no pulse for final round to wait for all k contacts to respond. this allows any failed node contact to be removed from seen contacts list during the wait. lock (lockObj) { Monitor.Pulse(lockObj); } }); t.IsBackground = true; t.Start(); } //wait for any of the node contact to return new contacts if (Monitor.Wait(lockObj, QUERY_TIMEOUT)) { //got reply or final round! NodeContact currentClosestSeenContact; lock (seenContacts) { currentClosestSeenContact = KBucket.SelectClosestContacts(seenContacts, nodeID, 1)[0]; } BinaryNumber previousDistance = nodeID ^ previousClosestSeenContact.NodeID; BinaryNumber currentDistance = nodeID ^ currentClosestSeenContact.NodeID; if (previousDistance <= currentDistance) { //current round failed to return a node contact any closer than the closest already seen if (finalRound) { //final round over, check for termination condition checkTerminationCondition = true; } else { //resend query to k closest node not already queried finalRound = true; alpha = KADEMLIA_K; } } else { //current closest seen contact is closer than previous closest seen contact previousClosestSeenContact = currentClosestSeenContact; finalRound = false; alpha = KADEMLIA_ALPHA; } } } } if (checkTerminationCondition) { checkTerminationCondition = false; //reset if (queryType == DhtRpcType.FIND_PEERS) { //check only in final round to get most peers lock (receivedPeers) { if (receivedPeers.Count > 0) { return(receivedPeers.ToArray()); } return(null); } } //lookup terminates when k closest seen contacts have responded NodeContact[] kClosestSeenContacts; lock (seenContacts) { kClosestSeenContacts = KBucket.SelectClosestContacts(seenContacts, nodeID, KADEMLIA_K); } lock (respondedContacts) { bool success = true; foreach (NodeContact contact in kClosestSeenContacts) { if (!respondedContacts.Contains(contact)) { success = false; break; } } if (success) { return(kClosestSeenContacts); } if (alphaContacts.Length < 1) { return(KBucket.SelectClosestContacts(respondedContacts, nodeID, KADEMLIA_K)); } } } } }
internal bool Ping(NodeContact contact) { DhtRpcPacket response = Query(DhtRpcPacket.CreatePingPacket(_currentNode), contact); return(response != null); }
public NodeContact[] GetAllContacts(bool includeReplacementCache) { _lock.TryEnterReadLock(LOCK_TIMEOUT); try { NodeContact[] contacts; if (_contacts == null) { NodeContact[] leftContacts = _leftBucket.GetAllContacts(includeReplacementCache); NodeContact[] rightContacts = _rightBucket.GetAllContacts(includeReplacementCache); contacts = new NodeContact[leftContacts.Length + rightContacts.Length]; Array.Copy(leftContacts, contacts, leftContacts.Length); Array.Copy(rightContacts, 0, contacts, leftContacts.Length, rightContacts.Length); } else { int cacheCount; if (includeReplacementCache) { cacheCount = _totalReplacementContacts; } else { cacheCount = 0; } List <NodeContact> contactsList = new List <NodeContact>(_totalContacts + cacheCount); foreach (NodeContact contact in _contacts.Values) { if (!contact.IsStale() && !contact.IsCurrentNode) { contactsList.Add(contact); } } if (includeReplacementCache) { foreach (NodeContact contact in _replacementContacts.Values) { if (!contact.IsStale()) { contactsList.Add(contact); } } } contacts = contactsList.ToArray(); } return(contacts); } finally { _lock.ExitReadLock(); } }
public bool RemoveContactFromCurrentBucket(NodeContact contact) { _lock.TryEnterWriteLock(LOCK_TIMEOUT); try { if (_contacts == null) { return(false); } if (_replacementContacts.Count < 1) { return(false); } if (_contacts.Remove(contact.NodeID)) { if (_replacementContacts.Count > 0) { //add good replacement contact to main contacts NodeContact goodContact = null; foreach (NodeContact replacementContact in _replacementContacts.Values) { if (!replacementContact.IsStale()) { if ((goodContact == null) || (replacementContact.LastSeen > goodContact.LastSeen)) { goodContact = replacementContact; } } } if (goodContact == null) { //no good replacement contact available DecrementContactCount(); } else { //move good replacement contact to main contacts _replacementContacts.Remove(goodContact.NodeID); _contacts.Add(goodContact.NodeID, goodContact); DecrementReplacementContactCount(); _lastChanged = DateTime.UtcNow; } } else { //no replacement contacts available DecrementContactCount(); } //check parent bucket contact count and join the parent buckets KBucket parentBucket = this._parentBucket; ReaderWriterLockSlim currentLock; while ((parentBucket != null) && (parentBucket._totalContacts <= DhtClient.KADEMLIA_K)) { currentLock = parentBucket._lock; currentLock.TryEnterWriteLock(LOCK_TIMEOUT); try { JoinBucket(parentBucket); parentBucket = parentBucket._parentBucket; } finally { currentLock.ExitWriteLock(); } } return(true); } else { if (_replacementContacts.Remove(contact.NodeID)) { DecrementReplacementContactCount(); return(true); } else { return(false); } } } finally { _lock.ExitWriteLock(); } }
public static DhtRpcPacket CreateAnnouncePeerPacketResponse(NodeContact sourceNode, BinaryNumber networkID, PeerEndPoint[] peers) { return(new DhtRpcPacket(Convert.ToUInt16(sourceNode.NodeEP.Port), DhtRpcType.ANNOUNCE_PEER, networkID, null, peers, 0)); }
public DhtRpcPacket(Stream s) { int version = s.ReadByte(); switch (version) { case -1: throw new EndOfStreamException(); case 2: byte[] buffer = new byte[20]; OffsetStream.StreamRead(s, buffer, 0, 2); _sourceNodePort = BitConverter.ToUInt16(buffer, 0); _type = (DhtRpcType)s.ReadByte(); switch (_type) { case DhtRpcType.PING: break; case DhtRpcType.FIND_NODE: { OffsetStream.StreamRead(s, buffer, 0, 20); _networkID = BinaryNumber.Clone(buffer, 0, 20); int count = s.ReadByte(); _contacts = new NodeContact[count]; for (int i = 0; i < count; i++) { _contacts[i] = new NodeContact(s); } } break; case DhtRpcType.FIND_PEERS: { OffsetStream.StreamRead(s, buffer, 0, 20); _networkID = BinaryNumber.Clone(buffer, 0, 20); int count = s.ReadByte(); _contacts = new NodeContact[count]; for (int i = 0; i < count; i++) { _contacts[i] = new NodeContact(s); } count = s.ReadByte(); _peers = new PeerEndPoint[count]; for (int i = 0; i < count; i++) { _peers[i] = new PeerEndPoint(s); } } break; case DhtRpcType.ANNOUNCE_PEER: { OffsetStream.StreamRead(s, buffer, 0, 20); _networkID = BinaryNumber.Clone(buffer, 0, 20); OffsetStream.StreamRead(s, buffer, 0, 2); _servicePort = BitConverter.ToUInt16(buffer, 0); int count = s.ReadByte(); _peers = new PeerEndPoint[count]; for (int i = 0; i < count; i++) { _peers[i] = new PeerEndPoint(s); } } break; default: throw new IOException("Invalid DHT-RPC type."); } break; default: throw new IOException("DHT-RPC packet version not supported: " + version); } }
public bool AddContactInCurrentBucket(NodeContact contact) { _lock.TryEnterWriteLock(LOCK_TIMEOUT); try { if (_contacts == null) { return(false); } if (_contacts.ContainsKey(contact.NodeID)) { _contacts[contact.NodeID].UpdateLastSeenTime(); return(true); } if (_contacts.Count < DhtClient.KADEMLIA_K) { _contacts.Add(contact.NodeID, contact); IncrementContactCount(); _lastChanged = DateTime.UtcNow; if (contact.IsCurrentNode) { _bucketContainsCurrentNode = true; } return(true); } if (_bucketContainsCurrentNode) { _contacts.Add(contact.NodeID, contact); _lastChanged = DateTime.UtcNow; //remove any stale node contact NodeContact staleContact = null; foreach (NodeContact existingContact in _contacts.Values) { if (existingContact.IsStale()) { staleContact = existingContact; break; } } if (staleContact != null) { //remove stale contact _contacts.Remove(staleContact.NodeID); return(true); } //no stale contact found to replace with current contact IncrementContactCount(); //split current bucket since count > k SplitBucket(this); return(true); } //never split buckets that arent on the same side of the tree as the current node if (_replacementContacts.ContainsKey(contact.NodeID)) { _replacementContacts[contact.NodeID].UpdateLastSeenTime(); return(true); } if (_replacementContacts.Count < DhtClient.KADEMLIA_K) { //keep the current node contact in replacement cache _replacementContacts.Add(contact.NodeID, contact); IncrementReplacementContactCount(); return(true); } //find stale contact from replacement cache and replace with current contact NodeContact staleReplacementContact = null; foreach (NodeContact replacementContact in _replacementContacts.Values) { if (replacementContact.IsStale()) { staleReplacementContact = replacementContact; break; } } if (staleReplacementContact == null) { return(false); } //remove bad contact & keep the current node contact in replacement cache _replacementContacts.Remove(staleReplacementContact.NodeID); _replacementContacts.Add(contact.NodeID, contact); return(true); } finally { _lock.ExitWriteLock(); } }