Exemple #1
0
        private void Work()
        {
            var txoIn  = TxoPack.Create();
            var txoOut = TxoPack.Create();

            // local array for capturing outpoints from inbox then sorting
            var outpoints = new Outpoint[Constants.MaxOutpointsCount];

            // array of inbox messages headers
            var messageHeaders = new byte[Constants.MaxOutpointsCount][];

            // array of inbox messages payloads
            // var payloads = new byte[MaxTxoCount][]; // not used in read request

            // provide index array for argsort
            var argsort = new int[Constants.MaxOutpointsCount];

            int txoCount = 0;

            while (!_source.Token.IsCancellationRequested)
            {
                var message = _inbox.Peek();
                if (message.Length > 0)
                {
                    // parse current message and add txo to txopack
                    var outpoint =
                        Outpoint.CreateNaked(
                            message.Slice(ClientServerMessage.PayloadStart, Outpoint.KeySizeInBytes));
                    outpoints[txoCount]      = outpoint;
                    messageHeaders[txoCount] = message.Slice(0, ClientServerMessage.PayloadStart).ToArray();
                    // payloads[messageCount] = message.Slice(ClientServerMessage.PayloadStart).ToArray();
                    // not used in read request
                    argsort[txoCount] = txoCount;
                    ++txoCount;

                    Debug.Assert(txoCount <= Constants.MaxOutpointsCount,
                                 "MaxTxoCount reached when looping inbox messages.");

                    _inbox.Next(message.Length);
                }
                else
                {
                    // current round of inbox message picking is over
                    Array.Sort(outpoints, argsort, 0, txoCount);
                    // sort txopack
                    ref var writer = ref txoIn.GetResetWriter();

                    // identify identical outpoint, and write querying txopack
                    ref var previous = ref outpoints[argsort[0]];
                    writer.Write(in previous, ReadOnlySpan <byte> .Empty);
                    for (ushort index = 1; index < txoCount; ++index)
                    {
                        ref var current = ref outpoints[argsort[index]];
                        if (previous != current)
                        {
                            previous = current;
                            writer.Write(in previous, ReadOnlySpan <byte> .Empty);
                        }
                    }
        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);
        }
Exemple #3
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);
        }
Exemple #4
0
 private void ReadingMessageFromInbox()
 {
     // only works if inbox was filled with _bytes first and then _bytesRandom1
     PrepareBytesRandom();
     _inbox.TryWrite(_bytesRandom1);
     Assert.Equal(_bytes, _inbox.Peek().ToArray());
     _inbox.Next();
     Assert.Equal(_bytesRandom1, _inbox.Peek().ToArray());
 }
Exemple #5
0
        public void SendResponses()
        {
            var toSend = _outbox.Peek();

            while (toSend.Length != 0)
            {
                if (_activeConnections.TryGetValue(ClientServerMessage.GetClientId(toSend), out var client))
                {
                    client.TryWrite(toSend);
                }

                _outbox.Next();
                toSend = _outbox.Peek();
            }
        }
Exemple #6
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);
        }
Exemple #7
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.
            }
        }
Exemple #8
0
        public bool HandleResponse()
        {
            if (!_outbox.CanPeek)
            {
                return(false);
            }

            var next = _outbox.Peek().Span;

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

                // Remove client ID from message
                message.Header.ClientId = default;

                if (_requestsInProgress >= ResponseBatchSize || _responseCountInPool > 0)
                {
                    var nextResponse = _responsePool.GetSpan(next.Length);
                    next.CopyTo(nextResponse);
                    _responseCountInPool++;

                    if (_responseCountInPool >= ResponseBatchSize)
                    {
                        _socket.Send(_responsePool.Allocated());
                        _responsePool.Reset();
                        _responseCountInPool = 0;
                    }
                }
                else
                {
                    _socket.Send(next);
                }

                Interlocked.Decrement(ref _requestsInProgress);

                // Some responses trigger the termination of the controller.
                if (kind == MessageKind.CloseConnectionResponse ||
                    kind == MessageKind.ProtocolErrorResponse)
                {
                    if (kind == MessageKind.ProtocolErrorResponse)
                    {
                        var protocolResponse = new ProtocolErrorResponse(next);
                        _log?.Log(LogSeverity.Info, $"ConnectionController({ClientId}) on protocol error {protocolResponse.Status}.");
                    }

                    if (_responseCountInPool > 0)
                    {
                        _socket.Send(_responsePool.Allocated());
                        _responsePool.Reset();
                        _responseCountInPool = 0;
                    }

                    _tokenSource.Cancel();
                }
            }
            finally
            {
                _outbox.Next();
            }

            return(true);
        }