Пример #1
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();
            }
        }
Пример #2
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);
        }
Пример #3
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);
        }
Пример #4
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);
        }
Пример #5
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);
                        }
                    }
Пример #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 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);
        }
Пример #9
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));
        }
Пример #10
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);
        }
Пример #11
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);
        }
Пример #12
0
 private void ReadingFromEmptyInbox()
 {
     Assert.Equal(0, _inbox.Peek().Length);
 }
Пример #13
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.
            }
        }
Пример #14
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);
        }