public Task RequestBlocksAsync(string peerId, Root peerHeadRoot, Slot finalizedSlot, Slot peerHeadSlot) { // NOTE: Currently just requests entire range, one at a time, to get small testnet working. // Will need more sophistication in future, e.g. request interleaved blocks and stuff. // Start with the block for the next slot after our finalized state Slot startSlot = finalizedSlot + Slot.One; ulong count = peerHeadSlot - finalizedSlot; ulong step = 1; BeaconBlocksByRange beaconBlocksByRange = new BeaconBlocksByRange(peerHeadRoot, startSlot, count, step); byte[] peerUtf8 = Encoding.UTF8.GetBytes(peerId); Span <byte> encoded = new byte[Ssz.Ssz.BeaconBlocksByRangeLength]; Ssz.Ssz.Encode(encoded, beaconBlocksByRange); if (_logger.IsDebug()) { LogDebug.RpcSend(_logger, RpcDirection.Request, nameof(MethodUtf8.BeaconBlocksByRange), peerId, encoded.Length, null); } if (!_mothraLibp2p.SendRpcRequest(MethodUtf8.BeaconBlocksByRange, peerUtf8, encoded)) { if (_logger.IsWarn()) { Log.RpcRequestNotSentAsPeeeringNotStarted(_logger, nameof(MethodUtf8.BeaconBlocksByRange), null); } } return(Task.CompletedTask); }
private async Task EnsureInitializedWithAnchorState(CancellationToken stoppingToken) { // If the store is not initialized (no anchor block), then can not participate peer-to-peer // e.g. can't send status message if we don't have a status. // If this is pre-genesis, then the peering will wait until genesis has created the anchor block. ulong counter = 0; while (!_store.IsInitialized) { await Task.Delay(TimeSpan.FromMilliseconds(1000), stoppingToken).ConfigureAwait(false); counter++; if (counter % 10 == 0) { if (_logger.IsDebug()) { LogDebug.PeeringWaitingForAnchorState(_logger, counter, null); } } } // Secondary instances wait an additional time, to allow the primary node to initialize if (_mothraConfigurationOptions.CurrentValue.BootNodes.Length > 0) { await Task.Delay(TimeSpan.FromMilliseconds(500), stoppingToken).ConfigureAwait(false); } }
public Task PublishAttestationAsync(Attestation signedAttestation) { // TODO: Basic validation before broadcasting, e.g. signature is correct ulong attestationSubnetIndex = (ulong)signedAttestation.Data.Index % _networkingConfigurationOptions.CurrentValue.AttestationSubnetCount; // TODO: Maybe cache the subnet topic UTF8 byte values (concurrent dictionary?) string topicTemplate = "/eth2/committee_index{0}_beacon_attestation/ssz"; string topic = string.Format(topicTemplate, attestationSubnetIndex); byte[] topicUtf8 = Encoding.UTF8.GetBytes(topic); Span <byte> encoded = new byte[Ssz.Ssz.AttestationLength(signedAttestation)]; Ssz.Ssz.Encode(encoded, signedAttestation); if (_logger.IsDebug()) { LogDebug.GossipSend(_logger, topic, encoded.Length, null); } if (!_mothraLibp2p.SendGossip(topicUtf8, encoded)) { if (_logger.IsWarn()) { Log.GossipNotPublishedAsPeeeringNotStarted(_logger, topic, null); } } return(Task.CompletedTask); }
public Task SendStatusAsync(string peerId, RpcDirection rpcDirection, PeeringStatus peeringStatus) { byte[] peerUtf8 = Encoding.UTF8.GetBytes(peerId); Span <byte> encoded = new byte[Ssz.Ssz.PeeringStatusLength]; Ssz.Ssz.Encode(encoded, peeringStatus); if (_logger.IsDebug()) { LogDebug.RpcSend(_logger, rpcDirection, nameof(MethodUtf8.Status), peerId, encoded.Length, null); } if (rpcDirection == RpcDirection.Request) { if (!_mothraLibp2p.SendRpcRequest(MethodUtf8.Status, peerUtf8, encoded)) { if (_logger.IsWarn()) { Log.RpcRequestNotSentAsPeeeringNotStarted(_logger, nameof(MethodUtf8.Status), null); } } } else { if (!_mothraLibp2p.SendRpcResponse(MethodUtf8.Status, peerUtf8, encoded)) { if (_logger.IsWarn()) { Log.RpcResponseNotSentAsPeeeringNotStarted(_logger, nameof(MethodUtf8.Status), null); } } } return(Task.CompletedTask); }
public override async Task StopAsync(CancellationToken cancellationToken) { await _peerDiscoveredProcessor.StopAsync(cancellationToken).ConfigureAwait(false); await _rpcPeeringStatusProcessor.StopAsync(cancellationToken).ConfigureAwait(false); await _rpcBeaconBlocksByRangeProcessor.StopAsync(cancellationToken).ConfigureAwait(false); await _signedBeaconBlockProcessor.StopAsync(cancellationToken).ConfigureAwait(false); if (_logger.IsDebug()) { LogDebug.PeeringWorkerStopping(_logger, null); } await base.StopAsync(cancellationToken).ConfigureAwait(false); }
private void OnGossipReceived(ReadOnlySpan <byte> topicUtf8, ReadOnlySpan <byte> data) { Activity activity = new Activity("gossip-received"); activity.Start(); using var activityScope = _logger.BeginScope("[TraceId, {TraceId}], [SpanId, {SpanId}]", activity.TraceId, activity.SpanId); try { // TODO: handle other topics if (topicUtf8.SequenceEqual(TopicUtf8.BeaconBlock)) { if (_logger.IsDebug()) { LogDebug.GossipReceived(_logger, Encoding.UTF8.GetString(topicUtf8), data.Length, null); } // Need to deserialize in synchronous context (can't pass Span async) SignedBeaconBlock signedBeaconBlock = Ssz.Ssz.DecodeSignedBeaconBlock(data); _signedBeaconBlockProcessor.EnqueueGossip(signedBeaconBlock); // TODO: After receiving a gossip, should we re-gossip it, i.e. to our peers? // NOTE: Need to apply validations, from spec, before forwarding; also, avoid loops (i.e. don't gossip twice) } else { if (_logger.IsWarn()) { Log.UnknownGossipReceived(_logger, Encoding.UTF8.GetString(topicUtf8), data.Length, null); } } } catch (Exception ex) { if (_logger.IsError()) { Log.GossipReceivedError(_logger, Encoding.UTF8.GetString(topicUtf8), ex.Message, ex); } } finally { activity.Stop(); } }
public Task PublishBeaconBlockAsync(SignedBeaconBlock signedBlock) { // TODO: Validate signature before broadcasting (if not already validated) Span <byte> encoded = new byte[Ssz.Ssz.SignedBeaconBlockLength(signedBlock)]; Ssz.Ssz.Encode(encoded, signedBlock); if (_logger.IsDebug()) { LogDebug.GossipSend(_logger, nameof(TopicUtf8.BeaconBlock), encoded.Length, null); } if (!_mothraLibp2p.SendGossip(TopicUtf8.BeaconBlock, encoded)) { if (_logger.IsWarn()) { Log.GossipNotPublishedAsPeeeringNotStarted(_logger, nameof(TopicUtf8.BeaconBlock), null); } } return(Task.CompletedTask); }
protected override async Task ProcessItemAsync(string rpcMessage) { try { if (_logger.IsDebug()) { LogDebug.ProcessPeerDiscovered(_logger, rpcMessage, null); } Session session = _peerManager.AddPeerSession(rpcMessage); if (session.Direction == ConnectionDirection.Out) { await _synchronizationManager.OnPeerDialOutConnected(rpcMessage).ConfigureAwait(false); } } catch (Exception ex) { if (_logger.IsError()) { Log.HandlePeerDiscoveredError(_logger, rpcMessage, ex.Message, ex); } } }
public Task SendBlockAsync(string peerId, SignedBeaconBlock signedBlock) { byte[] peerUtf8 = Encoding.UTF8.GetBytes(peerId); Span <byte> encoded = new byte[Ssz.Ssz.SignedBeaconBlockLength(signedBlock)]; Ssz.Ssz.Encode(encoded, signedBlock); if (_logger.IsDebug()) { LogDebug.RpcSend(_logger, RpcDirection.Response, nameof(MethodUtf8.BeaconBlocksByRange), peerId, encoded.Length, null); } if (!_mothraLibp2p.SendRpcResponse(MethodUtf8.BeaconBlocksByRange, peerUtf8, encoded)) { if (_logger.IsWarn()) { Log.RpcResponseNotSentAsPeeeringNotStarted(_logger, nameof(MethodUtf8.BeaconBlocksByRange), null); } } return(Task.CompletedTask); }
protected override async Task ProcessItemAsync(RpcMessage <BeaconBlocksByRange> rpcMessage) { try { if (rpcMessage.Direction == RpcDirection.Request) { if (_logger.IsDebug()) { LogDebug.ProcessBeaconBlocksByRange(_logger, rpcMessage.Content, null); } // TODO: Add some sanity checks on request to prevent DoS // TODO: Maybe add limit on number of blocks (as allowed by spec) Slot slot = new Slot(rpcMessage.Content.StartSlot + rpcMessage.Content.Step * (rpcMessage.Content.Count - 1)); Stack <(Root root, SignedBeaconBlock signedBlock)> signedBlocks = new Stack <(Root, SignedBeaconBlock)>(); // Search backwards from head for the requested slots Root root = rpcMessage.Content.HeadBlockRoot; while (slot >= rpcMessage.Content.StartSlot) { root = await _forkChoice.GetAncestorAsync(_store, root, slot); SignedBeaconBlock signedBlock = await _store.GetSignedBlockAsync(root); if (signedBlock.Message.Slot == slot) { signedBlocks.Push((root, signedBlock)); } else { // block is skipped if (_logger.IsWarn()) { Log.RequestedBlockSkippedSlot(_logger, slot, rpcMessage.Content.HeadBlockRoot, null); } } // If they requested for slot 0, then include it (anchor block is usually null), but don't underflow if (slot == Slot.Zero) { break; } slot = slot - Slot.One; } // Send each block in a response chunk, in slot order foreach (var data in signedBlocks) { if (_logger.IsDebug()) { LogDebug.SendingRequestBlocksByRangeResponse(_logger, data.signedBlock.Message, data.root, null); } await _networkPeering.SendBlockAsync(rpcMessage.PeerId, data.signedBlock); } } else { throw new Exception($"Unexpected direction {rpcMessage.Direction}"); } } catch (Exception ex) { if (_logger.IsError()) { Log.HandleRpcStatusError(_logger, rpcMessage.PeerId, ex.Message, ex); } } }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { if (_logger.IsDebug()) { LogDebug.PeeringWorkerExecute(_logger, null); } await EnsureInitializedWithAnchorState(stoppingToken).ConfigureAwait(false); if (_logger.IsDebug()) { LogDebug.StoreInitializedStartingPeering(_logger, null); } await _peerDiscoveredProcessor.StartAsync(stoppingToken).ConfigureAwait(false); await _rpcPeeringStatusProcessor.StartAsync(stoppingToken).ConfigureAwait(false); await _rpcBeaconBlocksByRangeProcessor.StartAsync(stoppingToken).ConfigureAwait(false); await _signedBeaconBlockProcessor.StartAsync(stoppingToken).ConfigureAwait(false); _mothraLibp2P.PeerDiscovered += OnPeerDiscovered; _mothraLibp2P.GossipReceived += OnGossipReceived; _mothraLibp2P.RpcReceived += OnRpcReceived; string mothraDataDirectory = Path.Combine(_dataDirectory.ResolvedPath, MothraDirectory); MothraSettings mothraSettings = new MothraSettings() { DataDirectory = mothraDataDirectory, //Topics = { Topic.BeaconBlock } }; MothraConfiguration mothraConfiguration = _mothraConfigurationOptions.CurrentValue; mothraSettings.DiscoveryAddress = mothraConfiguration.DiscoveryAddress; mothraSettings.DiscoveryPort = mothraConfiguration.DiscoveryPort; mothraSettings.ListenAddress = mothraConfiguration.ListenAddress; mothraSettings.MaximumPeers = mothraConfiguration.MaximumPeers; mothraSettings.Port = mothraConfiguration.Port; foreach (string bootNode in mothraConfiguration.BootNodes) { mothraSettings.BootNodes.Add(bootNode); _peerManager.AddExpectedPeer(bootNode); } if (_logger.IsDebug()) { LogDebug.MothraStarting(_logger, mothraSettings.ListenAddress, mothraSettings.Port, mothraSettings.BootNodes.Count, null); } _mothraLibp2P.Start(mothraSettings); if (_logger.IsDebug()) { LogDebug.PeeringWorkerExecuteCompleted(_logger, null); } } catch (Exception ex) { if (_logger.IsError()) { Log.PeeringWorkerCriticalError(_logger, ex); } } }
private void OnRpcReceived(ReadOnlySpan <byte> methodUtf8, int requestResponseFlag, ReadOnlySpan <byte> peerUtf8, ReadOnlySpan <byte> data) { Activity activity = new Activity("rpc-received"); activity.Start(); try { string peerId = Encoding.UTF8.GetString(peerUtf8); RpcDirection rpcDirection = requestResponseFlag == 0 ? RpcDirection.Request : RpcDirection.Response; // Even though the value '/eth2/beacon_chain/req/status/1/' is sent, when Mothra calls the received event it is 'HELLO' // if (methodUtf8.SequenceEqual(MethodUtf8.Status) // || methodUtf8.SequenceEqual(MethodUtf8.StatusMothraAlternative)) if (data.Length == Ssz.Ssz.PeeringStatusLength) { if (_logger.IsDebug()) { LogDebug.RpcReceived(_logger, rpcDirection, requestResponseFlag, Encoding.UTF8.GetString(methodUtf8), peerId, data.Length, nameof(MethodUtf8.Status), null); } PeeringStatus peeringStatus = Ssz.Ssz.DecodePeeringStatus(data); RpcMessage <PeeringStatus> statusRpcMessage = new RpcMessage <PeeringStatus>(peerId, rpcDirection, peeringStatus); _rpcPeeringStatusProcessor.Enqueue(statusRpcMessage); } //else if (methodUtf8.SequenceEqual(MethodUtf8.BeaconBlocksByRange)) else if (data.Length == Ssz.Ssz.BeaconBlocksByRangeLength || data.Length >= _minimumSignedBeaconBlockLength) { if (_logger.IsDebug()) { LogDebug.RpcReceived(_logger, rpcDirection, requestResponseFlag, Encoding.UTF8.GetString(methodUtf8), peerId, data.Length, nameof(MethodUtf8.BeaconBlocksByRange), null); } //if (rpcDirection == RpcDirection.Request) if (data.Length == Ssz.Ssz.BeaconBlocksByRangeLength) { BeaconBlocksByRange beaconBlocksByRange = Ssz.Ssz.DecodeBeaconBlocksByRange(data); RpcMessage <BeaconBlocksByRange> rpcMessage = new RpcMessage <BeaconBlocksByRange>(peerId, rpcDirection, beaconBlocksByRange); _rpcBeaconBlocksByRangeProcessor.Enqueue(rpcMessage); } else { SignedBeaconBlock signedBeaconBlock = Ssz.Ssz.DecodeSignedBeaconBlock(data); _signedBeaconBlockProcessor.Enqueue(signedBeaconBlock, peerId); } } else { // TODO: handle other RPC if (_logger.IsWarn()) { Log.UnknownRpcReceived(_logger, rpcDirection, requestResponseFlag, Encoding.UTF8.GetString(methodUtf8), peerId, data.Length, null); } } } catch (Exception ex) { if (_logger.IsError()) { Log.RpcReceivedError(_logger, Encoding.UTF8.GetString(methodUtf8), ex.Message, ex); } } finally { activity.Stop(); } }