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);
        }
Beispiel #2
0
        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);
        }
Beispiel #5
0
        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);
                }
            }
        }
Beispiel #11
0
        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);
                }
            }
        }
Beispiel #12
0
        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();
            }
        }