예제 #1
0
        /// <summary>
        /// Thread-safe. Signals the thread blocked on 'LoopOut' to wake up.
        /// </summary>
        public void Send(Span <byte> response)
        {
            if (!_outbox.TryWrite(response))
            {
                if (!_tokenSource.IsCancellationRequested)
                {
                    _log?.Log(LogSeverity.Warning, $"Connection({ClientId}): outbox full.");
                }

                // If outbox is full, terminate the connection.
                Stop();
            }

            _mre.Set();
        }
예제 #2
0
        public void RemoveDeadClients()
        {
            var socket          = new MockSocket();
            var dispatcherInbox = new BoundedInbox();
            var client          = new ConnectionController(dispatcherInbox, socket, ClientId.Next());

            client.OnRequestReceived = () => { };

            socket.ExpectConnected(() => false);

            var dispatcher = new DispatchController(dispatcherInbox, new BoundedInbox(), new BoundedInbox[4],
                                                    new IdentityHash());

            dispatcher.OnConnectionAccepted = (_) => { };
            dispatcher.AddConnection(client);
            dispatcher.HandleNewConnection();

            var firstMessage = new byte[100];
            var message      = new Message(firstMessage);

            message.Header.ClientId           = client.ClientId;
            message.Header.MessageSizeInBytes = 100;
            message.Header.MessageKind        = MessageKind.CloseConnection;

            Assert.True(dispatcherInbox.TryWrite(firstMessage));

            dispatcher.HandleRequest();

            socket.ExpectAllDone();
        }
예제 #3
0
        public void TryWriteCoinEmptyPayload()
        {
            var sozu = new VolatileCoinStore();

            var inbox      = new BoundedInbox();
            var outbox     = new BoundedInbox();
            var controller = new CoinController(inbox, outbox, sozu, _hash);

            controller.Lineage = MakeLineage();

            var clientId = new ClientId();
            var reqId    = new RequestId(1);

            var coin = GetCoin(_rand);

            var writeCoinRequest = ProduceCoinRequest.From(reqId, clientId, coin.Outpoint,
                                                           (OutpointFlags)coin.OutpointFlags,
                                                           new BlockAlias(3), 100, 50, Span <byte> .Empty, clientId.Mask);

            inbox.TryWrite(writeCoinRequest.Span);
            controller.HandleRequest();

            var raw = outbox.Peek();

            Assert.Equal(ProduceCoinResponse.SizeInBytes, raw.Length);
            var response = new ProduceCoinResponse(raw.Span);

            // verify response contents
            Assert.Equal(reqId, response.MessageHeader.RequestId);
            Assert.Equal(clientId, response.MessageHeader.ClientId);
            Assert.Equal(MessageKind.ProduceCoinResponse, response.MessageHeader.MessageKind);
            Assert.Equal(ChangeCoinStatus.InvalidContext, response.Status);
        }
예제 #4
0
        public void TryReadingNonExistentCoin()
        {
            var sozu = new VolatileCoinStore();

            var inbox      = new BoundedInbox();
            var outbox     = new BoundedInbox();
            var controller = new CoinController(inbox, outbox, sozu, _hash);

            var clientId = new ClientId();
            var reqId    = new RequestId(1);

            var coin = GetCoin(_rand);

            var readCoinRequest = GetCoinRequest.From(reqId, clientId, coin.Outpoint,
                                                      new BlockAlias(3), clientId.Mask);

            inbox.TryWrite(readCoinRequest.Span);
            controller.HandleRequest();

            var raw      = outbox.Peek();
            var response = new GetCoinResponse(raw.Span);

            // verify response contents
            Assert.Equal(reqId, response.MessageHeader.RequestId);
            Assert.Equal(clientId, response.MessageHeader.ClientId);
            Assert.Equal(GetCoinStatus.OutpointNotFound, response.Status);
        }
예제 #5
0
        public void TestOpenFirstBlock()
        {
            _controller = new ControllerThread(_chain, default(OptimizedLineage), _inbox = BoundedInbox.Create(),
                                               _outbox = BoundedInbox.Create());

            var messageAllocation = new Span <byte>(new byte[OpenBlock.SizeInBytes]);

            var openBlock = new OpenBlock(1, 5, BlockAlias.GenesisParent);

            // Serialize message to put it into the inbox
            MessageSerializers.ClientSerializeOpenBlock(openBlock, messageAllocation);

            _inbox.TryWrite(messageAllocation);

            _controller.DoWork();

            var result = _outbox.Peek();

            Assert.Equal(MessageType.OpenedBlock, ClientServerMessage.GetMessageType(result));
            var response = MessageSerializers.ClientDeserializeOpenedBlock(result);

            Assert.Equal(1U, response.RequestId);
            Assert.Equal(5U, response.ClientId);
            Assert.Equal(_1, response.Alias);

            var tmpId = response.UncommittedBlockId;

            Console.WriteLine(tmpId);
        }
예제 #6
0
        public void TestIsAncestor()
        {
            Setup();

            var messageAllocation = new Span <byte>(new byte[IsAncestor.SizeInBytes]);

            var isAncestorReq = new IsAncestor(1, 5, _3, _1);

            // Serialize message to put it into the inbox
            MessageSerializers.ClientSerializeIsAncestor(isAncestorReq, messageAllocation);

            _inbox.TryWrite(messageAllocation);

            _controller.DoWork();

            var result = _outbox.Peek();

            Assert.Equal(MessageType.AncestorResponse, ClientServerMessage.GetMessageType(result));
            Assert.True(ClientServerMessage.TryGetLength(result, out int responseSize));
            Assert.Equal(AncestorResponse.SizeInBytes, responseSize);

            var response = MessageSerializers.ClientDeserializeAncestorResponse(result);

            Assert.Equal(1U, response.RequestId);
            Assert.Equal(5U, response.ClientId);
            Assert.True(response.Answer);

            // get a wrong ancestor
            isAncestorReq = new IsAncestor(1, 5, _3, _5);
            // Serialize message to put it into the inbox
            MessageSerializers.ClientSerializeIsAncestor(isAncestorReq, messageAllocation);

            _inbox.TryWrite(messageAllocation);

            _controller.DoWork();

            _outbox.Next();
            result = _outbox.Peek();
            var response2 = MessageSerializers.ClientDeserializeAncestorResponse(result);

            Assert.Equal(1U, response2.RequestId);
            Assert.Equal(5U, response2.ClientId);
            Assert.Equal(MessageType.AncestorResponse, response2.ResponseType);

            Assert.False(response2.Answer);
        }
예제 #7
0
        /// <summary>
        /// Return 'true' if at least one request has been processed, 'false' otherwise.
        /// </summary>
        public bool HandleRequest()
        {
            if (!_inbox.CanPeek)
            {
                return(false);
            }

            var next = _inbox.Peek().Span;

            try
            {
                var message = new Message(next);
                var kind    = message.Header.MessageKind;
                var mask    = message.Header.ClientId.Mask;

                Span <byte> response;
                switch (kind)
                {
                case MessageKind.OpenBlock:
                    response = OpenBlock(new OpenBlockRequest(message.Span, mask)).Span;
                    break;

                case MessageKind.GetBlockHandle:
                    response = GetBlockHandle(new GetBlockHandleRequest(message.Span, mask)).Span;
                    break;

                case MessageKind.CommitBlock:
                    response = CommitBlock(new CommitBlockRequest(message.Span, mask)).Span;
                    break;

                case MessageKind.GetBlockInfo:
                    response = GetBlockInfo(new GetBlockInfoRequest(message.Span, mask));
                    break;

                default:
                    throw new NotSupportedException();
                }

                while (!_outbox.TryWrite(response))
                {
                    _log?.Log(LogSeverity.Warning, $"ChainController can't write the response to {kind}.");
                    // Pathological situation, we don't want to overflow the logger.
                    Thread.Sleep(1000);
                }
            }
            finally
            {
                _pool.Reset();
                _inbox.Next();
            }

            return(true);
        }
예제 #8
0
        public void LoopIn()
        {
            if (OnRequestReceived == null)
            {
                throw new InvalidOperationException("OnRequestReceived is null.");
            }

            try
            {
                while (!_tokenSource.Token.IsCancellationRequested && _socket.Connected)
                {
                    // Blocking call.
                    if (HandleRequest())
                    {
                        // Notify the dispatch controller.
                        OnRequestReceived();
                    }
                }
            }
            catch (SocketException ex)
            {
                _log?.Log(LogSeverity.Info, $"ConnectionController({ClientId}) closed in LoopIn(): {ex}.");
            }
            catch (ObjectDisposedException ex)
            {
                _log?.Log(LogSeverity.Info, $"ConnectionController({ClientId}) closed in LoopIn(): {ex}.");
            }
            finally
            {
                Stop();

                // Notify the dispatcher that the connection has been closed.
                var buffer = new byte[CloseConnectionResponse.SizeInBytes]; // allocation required because of 'finally'
                if (!_dispatchInbox.TryWrite(
                        new CloseConnectionResponse(buffer, RequestId.MinRequestId, _clientId).Span))
                {
                    _log?.Log(LogSeverity.Error, $"ConnectionController({ClientId}) can't notify dispatch controller of its own termination.");
                }
            }
        }
예제 #9
0
        public void RemoveProductionAndConsumption()
        {
            var sozu = new VolatileCoinStore();

            var inbox      = new BoundedInbox();
            var outbox     = new BoundedInbox();
            var controller = new CoinController(inbox, outbox, sozu, _hash);

            controller.Lineage = new MockLineage();

            var clientId = new ClientId();
            var reqId    = new RequestId(1);

            var coin = GetCoin(_rand);

            sozu.AddProduction(
                _hash.Hash(ref coin.Outpoint),
                ref coin.Outpoint,
                false, coin.Payload,
                new BlockAlias(2),
                null);

            sozu.AddConsumption(
                _hash.Hash(ref coin.Outpoint),
                ref coin.Outpoint,
                new BlockAlias(2),
                null);

            var buffer            = new byte[RemoveCoinRequest.SizeInBytes];
            var removeCoinRequest = new RemoveCoinRequest(
                buffer, reqId, ref coin.Outpoint, new BlockAlias(2).ConvertToBlockHandle(clientId.Mask), true, true);

            inbox.TryWrite(removeCoinRequest.Span);
            controller.HandleRequest();

            var raw = outbox.Peek();

            Assert.Equal(RemoveCoinResponse.SizeInBytes, raw.Length);
            var response = new RemoveCoinResponse(raw.Span);

            // verify response contents
            Assert.Equal(reqId, response.MessageHeader.RequestId);
            Assert.Equal(clientId, response.MessageHeader.ClientId);
            Assert.Equal(MessageKind.RemoveCoinResponse, response.MessageHeader.MessageKind);
            Assert.Equal(ChangeCoinStatus.Success, response.Status);

            sozu.TryGet(_hash.Hash(ref coin.Outpoint), ref coin.Outpoint, new BlockAlias(2), null, out var coin2,
                        out var pe, out var ce);

            Assert.False(pe.IsDefined);
            Assert.False(ce.IsDefined);
        }
예제 #10
0
        public void ReadExistingCoin()
        {
            var sozu = new VolatileCoinStore();

            var inbox      = new BoundedInbox();
            var outbox     = new BoundedInbox();
            var controller = new CoinController(inbox, outbox, sozu, _hash);

            var coin = GetCoin(_rand);

            sozu.AddProduction(
                _hash.Hash(ref coin.Outpoint),
                ref coin.Outpoint,
                false, coin.Payload,
                new BlockAlias(3),
                null); // lineage is not used in VolatileCoinStore

            var clientId = new ClientId();
            var reqId    = new RequestId(1);

            var context = new BlockAlias(3);

            var readCoinRequest = GetCoinRequest.From(reqId, clientId, coin.Outpoint, context, clientId.Mask);

            inbox.TryWrite(readCoinRequest.Span);
            controller.HandleRequest();

            var raw      = outbox.Peek();
            var response = new GetCoinResponse(raw.Span);

            Assert.Equal(response.MessageHeader.MessageSizeInBytes, raw.Length);

            // verify response contents
            Assert.Equal(reqId, response.MessageHeader.RequestId);
            Assert.Equal(clientId, response.MessageHeader.ClientId);
            Assert.Equal(MessageKind.GetCoinResponse, response.MessageHeader.MessageKind);
            Assert.Equal(coin.Outpoint, response.Outpoint);
            Assert.Equal(OutpointFlags.None, response.OutpointFlags);
            Assert.Equal(context, response.Context.ConvertToBlockAlias(clientId.Mask));
            Assert.Equal(context, response.Production.ConvertToBlockAlias(clientId.Mask));
            Assert.Equal(BlockAlias.Undefined.ConvertToBlockHandle(clientId.Mask), response.Consumption);
            Assert.Equal(coin.Payload.Satoshis, response.Satoshis);
            Assert.Equal(coin.Payload.NLockTime, response.NLockTime);
            Assert.True(coin.Payload.Script.SequenceEqual(response.Script));
        }
예제 #11
0
        public void WriteCoinCons()
        {
            var sozu = new VolatileCoinStore();

            var inbox      = new BoundedInbox();
            var outbox     = new BoundedInbox();
            var controller = new CoinController(inbox, outbox, sozu, _hash);

            controller.Lineage = new MockLineage();

            var clientId = new ClientId();
            var reqId    = new RequestId(1);

            var coin = GetCoin(_rand);

            sozu.AddProduction(
                _hash.Hash(ref coin.Outpoint),
                ref coin.Outpoint,
                false, coin.Payload,
                new BlockAlias(2),
                null);

            var pool             = new SpanPool <byte>(4096);
            var buffer           = new byte[ConsumeCoinRequest.SizeInBytes];
            var writeCoinRequest = new ConsumeCoinRequest(
                reqId, ref coin.Outpoint, new BlockAlias(2).ConvertToBlockHandle(clientId.Mask), pool);

            inbox.TryWrite(writeCoinRequest.Span);
            controller.HandleRequest();

            var raw = outbox.Peek();

            Assert.Equal(ConsumeCoinResponse.SizeInBytes, raw.Length);
            var response = new ConsumeCoinResponse(raw.Span);

            // verify response contents
            Assert.Equal(reqId, response.MessageHeader.RequestId);
            Assert.Equal(clientId, response.MessageHeader.ClientId);
            Assert.Equal(MessageKind.ConsumeCoinResponse, response.MessageHeader.MessageKind);
            Assert.Equal(ChangeCoinStatus.Success, response.Status);
        }
예제 #12
0
        public bool HandleRequest()
        {
            if (!_inbox.CanPeek)
            {
                return(false);
            }

            var next = _inbox.Peek().Span;

            try
            {
                var message   = new Message(next);
                var mask      = message.Header.ClientId.Mask;
                var kind      = message.Header.MessageKind;
                var requestId = message.Header.RequestId;
                var clientId  = message.Header.ClientId;

                if (!_connections.TryGetValue(clientId, out var connection))
                {
                    // Outdated message, drop and move on.
                    return(true);
                }

                if (!connection.Connected || kind == MessageKind.CloseConnection)
                {
                    // Client disconnected, drop message and remove connection.
                    _connections.Remove(clientId);
                    return(true);
                }

                if (kind.IsResponse())
                {
                    // Forward responses to their respective 'ConnectionController'.
                    connection.Send(next);
                    return(true);
                }

                if (kind.IsForCoinController())
                {
                    // Multiple coin controllers
                    Outpoint outpoint;
                    switch (kind)
                    {
                    case MessageKind.GetCoin:
                        outpoint = new GetCoinRequest(next, mask).Outpoint;
                        break;

                    case MessageKind.ProduceCoin:
                        outpoint = new ProduceCoinRequest(next, mask).Outpoint;
                        break;

                    case MessageKind.ConsumeCoin:
                        outpoint = new ConsumeCoinRequest(next, mask).Outpoint;
                        break;

                    case MessageKind.RemoveCoin:
                        outpoint = new RemoveCoinRequest(next, mask).Outpoint;
                        break;

                    default:
                        throw new NotSupportedException();
                    }

                    // Sharding based on the outpoint hash

                    // Beware: the factor 'BigPrime' is used to avoid accidental factor collision
                    // between the sharding performed at the dispatch controller level, and the
                    // sharding performed within the Sozu table.

                    // PERF: hashing the outpoint is repeated in the CoinController itself
                    const ulong BigPrime        = 1_000_000_007;
                    var         controllerIndex = (int)((_hash.Hash(ref outpoint) % BigPrime)
                                                        % (ulong)_coinControllerBoxes.Length);

                    var written = _coinControllerBoxes[controllerIndex].TryWrite(next);
                    if (written)
                    {
                        OnCoinMessageDispatched[controllerIndex]();
                    }
                    else
                    {
                        // Coin controller is saturated.
                        Span <byte> buffer       = stackalloc byte[ProtocolErrorResponse.SizeInBytes];
                        var         errorMessage = new ProtocolErrorResponse(
                            buffer, requestId, clientId, ProtocolErrorStatus.ServerBusy);

                        connection.Send(errorMessage.Span);
                    }

                    return(true);
                }

                {
                    // Block controller
                    var written = _chainControllerBox.TryWrite(next);
                    if (written)
                    {
                        OnBlockMessageDispatched();
                    }
                    else
                    {
                        // Block controller is saturated.
                        Span <byte> buffer       = stackalloc byte[ProtocolErrorResponse.SizeInBytes];
                        var         errorMessage = new ProtocolErrorResponse(
                            buffer, requestId, clientId, ProtocolErrorStatus.ServerBusy);

                        connection.Send(errorMessage.Span);
                    }
                }
            }
            finally
            {
                _inbox.Next();
            }

            return(true);
        }
예제 #13
0
        public void SendAnswers()
        {
            var dispatcherInbox = new BoundedInbox();
            var socket1         = new MockSocket();
            var socket2         = new MockSocket();
            var client1         = new ConnectionController(dispatcherInbox, socket1, ClientId.Next());
            var client2         = new ConnectionController(dispatcherInbox, socket2, ClientId.Next());

            client1.OnRequestReceived = () => { };
            client2.OnRequestReceived = () => { };

            var dispatcher = new DispatchController(dispatcherInbox, new BoundedInbox(), new BoundedInbox[2],
                                                    new IdentityHash());

            // Nil handling of notifications
            dispatcher.OnBlockMessageDispatched = () => { };
            for (var i = 0; i < dispatcher.OnCoinMessageDispatched.Length; i++)
            {
                dispatcher.OnCoinMessageDispatched[i] = () => { }
            }
            ;
            dispatcher.OnConnectionAccepted = (_) => { };


            var firstMessage = new byte[Constants.MaxRequestSize];
            var message      = new Message(firstMessage);

            message.Header.ClientId           = client2.ClientId;
            message.Header.MessageSizeInBytes = Constants.MaxRequestSize;
            message.Header.MessageKind        = MessageKind.GetCoinResponse;


            var secondMessage = new byte[Constants.MaxRequestSize];

            message = new Message(secondMessage);
            message.Header.ClientId           = client1.ClientId;
            message.Header.MessageSizeInBytes = Constants.MaxRequestSize;
            message.Header.MessageKind        = MessageKind.GetCoinResponse;

            var thirdMessage = new byte[50];

            message = new Message(thirdMessage);
            message.Header.ClientId           = client1.ClientId;
            message.Header.MessageSizeInBytes = 50;
            message.Header.MessageKind        = MessageKind.GetCoinResponse;


            var fourthMessage = new byte[50];

            message = new Message(fourthMessage);
            message.Header.ClientId           = client2.ClientId;
            message.Header.MessageSizeInBytes = 50;
            message.Header.MessageKind        = MessageKind.GetCoinResponse;

            dispatcher.AddConnection(client1);
            dispatcher.AddConnection(client2);
            dispatcher.HandleNewConnection();
            dispatcher.HandleNewConnection();


            // Write messages into dispatcher buffer for both clients
            for (var i = 0; i < Constants.SocketSendBufferSize / Constants.MaxRequestSize; i++)
            {
                socket1.ExpectConnected(() => true);
                socket2.ExpectConnected(() => true);

                Assert.True(dispatcherInbox.TryWrite(firstMessage));
                Assert.True(dispatcherInbox.TryWrite(secondMessage));

                // Try sending the answers, fails
                dispatcher.HandleRequest();
                dispatcher.HandleRequest();
            }

            // Try sending message to first client, socket gets closed
            socket1.ExpectConnected(() => true);
            socket1.ExpectClose();
            // Try sending message to second client, socket gets closed
            socket2.ExpectConnected(() => true);
            socket2.ExpectClose();

            Assert.True(dispatcherInbox.TryWrite(thirdMessage));
            Assert.True(dispatcherInbox.TryWrite(fourthMessage));
            dispatcher.HandleRequest();
            dispatcher.HandleRequest();

            socket1.ExpectAllDone();
            socket2.ExpectAllDone();
        }
    }
예제 #14
0
 private void WritingToEmptyInbox()
 {
     Assert.True(_inbox.TryWrite(_bytes));
     Assert.Equal(_bytes, _inbox.Peek().ToArray());
 }
예제 #15
0
        public void DoWork()
        // TODO: [vermorel] method should return 'true' if any work has been done
        {
            var nextMessage = _inbox.Peek();

            if (nextMessage.Length != 0)
            {
                // get message type and deserialize accordingly
                var reqType = ClientServerMessage.GetMessageType(nextMessage);
                switch (reqType)
                {
// TODO: [vermorel] I don't like to too much the code below, it's highly repetitive, factorization is needed.

                case MessageType.OpenBlock:
                    var openBlock = MessageSerializers.DeserializeOpenBlock(nextMessage);
                    UncommittedBlock block;
                    if (openBlock.ParentHandle == BlockAlias.GenesisParent && _chain.BlockchainLength == 0)
                    {
                        block = _chain.OpenFirstBlock();
                    }
                    else
                    {
                        block = _chain.OpenBlock(openBlock.ParentHandle);
                    }
                    var blockHandle =
                        new OpenedBlock(openBlock.RequestId, openBlock.ClientId, block.BlockId, block.Alias);
                    MessageSerializers.SerializeOpenedBlock(blockHandle, _responseBuffer);
                    _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, OpenedBlock.SizeInBytes));
                    break;

                case MessageType.GetBlockHandle:
                    var getBlockHandle  = MessageSerializers.DeserializeGetBlockHandle(nextMessage);
                    var retrievedHandle = _chain.RetrieveAlias(getBlockHandle.BlockId);
                    var returnMessage   = new BlockHandleResponse(getBlockHandle.RequestId, getBlockHandle.ClientId,
                                                                  retrievedHandle);
                    MessageSerializers.SerializeBlockHandleResponse(returnMessage, _responseBuffer);
                    _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, BlockHandleResponse.SizeInBytes));
                    break;

                case MessageType.GetUncommittedBlockHandle:
                    var getUBlockHandle  = MessageSerializers.DeserializeGetUncommittedBlockHandle(nextMessage);
                    var retrievedUHandle = _chain.RetrieveAlias(getUBlockHandle.UncommittedBlockId);
                    var returnMes        = new BlockHandleResponse(getUBlockHandle.RequestId, getUBlockHandle.ClientId,
                                                                   retrievedUHandle);
                    MessageSerializers.SerializeBlockHandleResponse(returnMes, _responseBuffer);
                    _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, BlockHandleResponse.SizeInBytes));
                    break;

                case MessageType.CommitBlock:
                    var commitBlock = MessageSerializers.DeserializeCommitBlock(nextMessage);
                    _chain.CommitBlock(commitBlock.BlockHandle, commitBlock.BlockId);
                    var committedMessage =
                        new EverythingOkResponse(commitBlock.RequestId, commitBlock.ClientId);
                    MessageSerializers.SerializeEverythingOk(committedMessage, _responseBuffer);
                    _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, EverythingOkResponse.SizeInBytes));
                    break;

                case MessageType.IsAncestor:
                    var isAncestor        = MessageSerializers.DeserializeIsAncestor(nextMessage);
                    var res               = _opti.IsAncestor(isAncestor.BlockHandle, isAncestor.MaybeAncestorHandle);
                    var isAncestorMessage = new AncestorResponse(isAncestor.RequestId, isAncestor.ClientId, res);
                    MessageSerializers.SerializeAncestorResponse(isAncestorMessage, _responseBuffer);
                    _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, AncestorResponse.SizeInBytes));
                    break;

                case MessageType.IsPruneable:
                    var isPruneable        = MessageSerializers.DeserializeIsPruneable(nextMessage);
                    var result             = _opti.IsPruneable(isPruneable.BlockHandle);
                    var isPruneableMessage = new PruneableResponse(isPruneable.RequestId, isPruneable.ClientId, result);
                    MessageSerializers.SerializePruneableResponse(isPruneableMessage, _responseBuffer);
                    _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, PruneableResponse.SizeInBytes));
                    break;

                case MessageType.GetBlockInfo:
                    var blockInfoReq = MessageSerializers.DeserializeGetBlockInfo(nextMessage);

                    // TODO: [vermorel] clarify the purpose of this test, unclear
                    if (_chain.RetrieveCommittedBlock(blockInfoReq.BlockHandle, out var blockInfoC))
                    {
                        var blockInfo =
                            new CommittedBlockInformation(blockInfoReq.RequestId, blockInfoReq.ClientId,
                                                          blockInfoC.BlockId, blockInfoC.Alias, blockInfoC.BlockHeight, blockInfoC.Parent);
                        MessageSerializers.SerializeCommittedBlockInfo(blockInfo, _responseBuffer);
                        _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, CommittedBlockInformation.SizeInBytes));
                        break;
                    }
                    else
                    {
                        _chain.RetrieveUncommittedBlock(blockInfoReq.BlockHandle, out var blockInfoU);
                        var blockInfo =
                            new UncommittedBlockInformation(blockInfoReq.RequestId, blockInfoReq.ClientId,
                                                            blockInfoU.BlockId, blockInfoU.Alias, blockInfoU.BlockHeight, blockInfoU.Parent);
                        MessageSerializers.SerializeUncommittedBlockInfo(blockInfo, _responseBuffer);
                        _outbox.TryWrite(new Span <byte>(_responseBuffer, 0, UncommittedBlockInformation.SizeInBytes));
                        break;
                    }

// TODO: [vermorel] the 'default:' case should be covered with an NotSupportedException.
                }
                _inbox.Next();
                // TODO: errors are not yet handled.
            }
        }