public async Task DisconnectAsync_Test() { _grpcPeer.IsReady.ShouldBeTrue(); _grpcPeer.IsShutdown.ShouldBeFalse(); _grpcPeer.IsConnected.ShouldBeTrue(); _grpcPeer.IsInvalid.ShouldBeFalse(); await _grpcPeer.DisconnectAsync(false); _grpcPeer.IsShutdown.ShouldBeTrue(); _grpcPeer.IsConnected.ShouldBeFalse(); _grpcPeer.IsReady.ShouldBeFalse(); _grpcPeer.IsInvalid.ShouldBeFalse(); _grpcPeer.ConnectionStatus.ShouldBe("Shutdown"); _grpcPeer.Info.ConnectionTime = TimestampHelper.GetUtcNow().AddSeconds(-11); _grpcPeer.IsInvalid.ShouldBeTrue(); }
/// <summary> /// Connects to a node with the given ip address and adds it to the node's peer pool. /// </summary> /// <param name="endpoint">the ip address of the distant node</param> /// <returns>True if the connection was successful, false otherwise</returns> public async Task <bool> ConnectAsync(DnsEndPoint endpoint) { Logger.LogDebug($"Attempting to reach {endpoint}."); var dialedPeer = await GetDialedPeerWithEndpointAsync(endpoint); if (dialedPeer == null) { return(false); } var inboundPeer = _peerPool.FindPeerByPublicKey(dialedPeer.Info.Pubkey) as GrpcPeer; /* A connection already exists, this can happen when both peers dial each other at the same time. To make * sure both sides close the same connection, they both decide based on the times of the handshakes. * Scenario steps, chronologically: * 1) P1 (hsk_time: t1) --> dials P2 --and-- P1 <-- P2 dials (hsk_time: t2) * 2) P2 receives P1s dial with t1 (in the hsk) and add to the pool * 3) P1 receives P2s dial with and adds to pool * 4) both dials finish and find that the pool already contains the dialed node. * To resolve this situation, both peers will choose the connection that was initiated the earliest, * so either P1s dial or P2s. */ GrpcPeer currentPeer = dialedPeer; if (inboundPeer != null) { Logger.LogDebug("Duplicate peer connection detected: " + $"{inboundPeer} ({inboundPeer.LastReceivedHandshakeTime}) " + $"vs {dialedPeer} ({dialedPeer.LastSentHandshakeTime})."); if (inboundPeer.LastReceivedHandshakeTime > dialedPeer.LastSentHandshakeTime) { // we started the dial first, replace the inbound connection with the dialed if (!_peerPool.TryReplace(inboundPeer.Info.Pubkey, inboundPeer, dialedPeer)) { Logger.LogWarning("Replacing the inbound connection failed."); } await inboundPeer.DisconnectAsync(false); Logger.LogDebug($"Replaced the inbound connection with the dialed peer {inboundPeer} ."); } else { // keep the inbound connection await dialedPeer.DisconnectAsync(false); currentPeer = inboundPeer; Logger.LogDebug($"Disconnected dialed peer {dialedPeer}."); } } else { if (!_peerPool.TryAddPeer(dialedPeer)) { Logger.LogWarning($"Peer add to the failed {dialedPeer.Info.Pubkey}."); await dialedPeer.DisconnectAsync(false); return(false); } Logger.LogDebug($"Added to pool {dialedPeer.RemoteEndpoint} - {dialedPeer.Info.Pubkey}."); } try { await currentPeer.ConfirmHandshakeAsync(); } catch (Exception e) { Logger.LogDebug(e, $"Confirm handshake error. Peer: {currentPeer.Info.Pubkey}."); _peerPool.RemovePeer(currentPeer.Info.Pubkey); await currentPeer.DisconnectAsync(false); throw; } currentPeer.IsConnected = true; currentPeer.SyncState = SyncState.Syncing; Logger.LogInformation($"Connected to: {currentPeer.RemoteEndpoint} - {currentPeer.Info.Pubkey.Substring(0, 45)}" + $" - in-token {currentPeer.InboundSessionId?.ToHex()}, out-token {currentPeer.OutboundSessionId?.ToHex()}" + $" - LIB height {currentPeer.LastKnownLibHeight}" + $" - best chain [{currentPeer.CurrentBlockHeight}, {currentPeer.CurrentBlockHash}]"); FireConnectionEvent(currentPeer); return(true); }