コード例 #1
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);
        }
コード例 #2
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));
        }
コード例 #3
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);
        }
コード例 #4
0
        public void WriteReadCoins()
        {
            var instance = GetInstance();

            var clientId = new ClientId(123);

            instance.Listener.GetNextClientId = () => clientId;

            instance.Start();

            var localEndpoint = new IPEndPoint(IPAddress.Loopback, instance.Port);
            var rawSocket     = new Socket(localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            rawSocket.Connect(localEndpoint);

            var socket = new SocketLikeAdapter(rawSocket);

            try
            {
                // Open block
                var openBlockRequest = OpenBlockRequest.ForGenesis(RequestId.MinRequestId);
                socket.Send(openBlockRequest.Span);

                var openBlockResponse = new OpenBlockResponse(new byte[OpenBlockResponse.SizeInBytes]);
                socket.Receive(openBlockResponse.Span);

                Assert.Equal(OpenBlockStatus.Success, openBlockResponse.Status);

                var reqNum = 0U;

                // Write 100 coins into this open block and save the outpoints to read them later
                var outpointSaverComm         = new Outpoint[100];
                var randyRandom               = new Random(42);
                var produceCoinResponseBuffer = new byte[ProduceCoinResponse.SizeInBytes];
                var consumeCoinResponseBuffer = new byte[ConsumeCoinResponse.SizeInBytes];
                for (var i = 0; i < 100; i++)
                {
                    var coinToWrite = GetCoin(randyRandom, openBlockResponse.Handle.ConvertToBlockAlias(clientId.Mask));

                    outpointSaverComm[i] = coinToWrite.Outpoint;

                    var writeCoinRequest = ProduceCoinRequest.From(
                        new RequestId(++reqNum), clientId, coinToWrite.Outpoint,
                        (OutpointFlags)coinToWrite.OutpointFlags,
                        openBlockResponse.Handle
                        .ConvertToBlockAlias(clientId.Mask),     // reconverted to BlockHandle after object construction
                        coinToWrite.Payload.Satoshis, coinToWrite.Payload.NLockTime,
                        coinToWrite.Payload.Script, clientId.Mask);
                    socket.Send(writeCoinRequest.Span);

                    var writeCoinResponse = new ProduceCoinResponse(produceCoinResponseBuffer);
                    socket.Receive(writeCoinResponse.Span);

                    Assert.Equal(ChangeCoinStatus.Success, writeCoinResponse.Status);

                    // every second coin, write a consumption
                    if (i % 2 == 0)
                    {
                        var writeCoinConsRequest = ConsumeCoinRequest.From(
                            new RequestId(++reqNum), ref coinToWrite.Outpoint,
                            // reconverted to BlockHandle after object construction
                            openBlockResponse.Handle.ConvertToBlockAlias(clientId.Mask),
                            clientId.Mask);
                        socket.Send(writeCoinConsRequest.Span);

                        var consumeCoinResponse = new ConsumeCoinResponse(consumeCoinResponseBuffer);
                        socket.Receive(consumeCoinResponse.Span);

                        Assert.Equal(ChangeCoinStatus.Success, consumeCoinResponse.Status);
                    }
                } // end of write coins

                // Commit the block
                var commitBlockRequest = CommitBlockRequest.From(new RequestId(++reqNum), clientId,
                                                                 openBlockResponse.Handle, CommittedBlockId.Genesis);
                socket.Send(commitBlockRequest.Span);

                var commitBlockResponse = new CommitBlockResponse(new byte[CommitBlockResponse.SizeInBytes]);
                socket.Receive(commitBlockResponse.Span);

                Assert.Equal(CommitBlockStatus.Success, commitBlockResponse.Status);

                // Open a new block
                var openBlockRequest2 =
                    OpenBlockRequest.From(new RequestId(++reqNum), clientId, CommittedBlockId.Genesis);
                socket.Send(openBlockRequest2.Span);

                var openBlockResponse2 = new OpenBlockResponse(new byte[OpenBlockResponse.SizeInBytes]);
                socket.Receive(openBlockResponse2.Span);

                Assert.Equal(OpenBlockStatus.Success, openBlockResponse2.Status);

                // Write 100 coins into the new block
                var outpointSaverUncomm = new Outpoint[100];

                for (var i = 0; i < 100; i++)
                {
                    var coinToWrite = GetCoin(randyRandom,
                                              openBlockResponse2.Handle.ConvertToBlockAlias(clientId.Mask));
                    outpointSaverUncomm[i] = coinToWrite.Outpoint;
                    var writeCoinRequest = ProduceCoinRequest.From(
                        new RequestId(++reqNum), clientId, coinToWrite.Outpoint,
                        (OutpointFlags)coinToWrite.OutpointFlags,
                        openBlockResponse2.Handle
                        .ConvertToBlockAlias(clientId.Mask),     // reconverted to BlockHandle after object construction
                        coinToWrite.Payload.Satoshis, coinToWrite.Payload.NLockTime,
                        coinToWrite.Payload.Script, clientId.Mask);
                    socket.Send(writeCoinRequest.Span);

                    var writeCoinResponse = new ProduceCoinResponse(produceCoinResponseBuffer);
                    socket.Receive(writeCoinResponse.Span);
                    Assert.Equal(ChangeCoinStatus.Success, writeCoinResponse.Status);

                    // every second coin, write a consumption
                    if (i % 2 == 0)
                    {
                        var writeCoinConsRequest = ConsumeCoinRequest.From(
                            new RequestId(++reqNum), ref coinToWrite.Outpoint,
                            openBlockResponse2.Handle
                            .ConvertToBlockAlias(clientId
                                                 .Mask), // reconverted to BlockHandle after object construction
                            clientId.Mask);
                        socket.Send(writeCoinConsRequest.Span);

                        var consumeCoinResponse = new ConsumeCoinResponse(consumeCoinResponseBuffer);
                        socket.Receive(consumeCoinResponse.Span);

                        Assert.Equal(ChangeCoinStatus.Success, consumeCoinResponse.Status);
                    }
                } // end of second write coins

                // Read coins from the committed block
                var headerBuffer = new byte[MessageHeader.SizeInBytes];

                for (var i = 0; i < 100; i++)
                {
                    var readCoinRequest = GetCoinRequest.From(
                        new RequestId(++reqNum), clientId, outpointSaverComm[i],
                        openBlockResponse.Handle.ConvertToBlockAlias(clientId.Mask),
                        clientId.Mask); // reconverted to BlockHandle after object construction
                    socket.Send(readCoinRequest.Span);

                    socket.Receive(headerBuffer);
                    var header = MemoryMarshal.Cast <byte, MessageHeader>(headerBuffer)[0];
                    Assert.Equal(MessageKind.GetCoinResponse, header.MessageKind);
                    var readCoinResponseBuffer = new byte[header.MessageSizeInBytes];
                    headerBuffer.CopyTo(readCoinResponseBuffer, 0);
                    socket.Receive(readCoinResponseBuffer.AsSpan(16));

                    var readCoinResponse = new GetCoinResponse(readCoinResponseBuffer);

                    Assert.Equal(header.MessageSizeInBytes, readCoinResponse.Span.Length);
                    Assert.Equal(openBlockResponse.Handle, readCoinResponse.Production);
                    if (i % 2 == 0)
                    {
                        Assert.Equal(openBlockResponse.Handle.Value, readCoinResponse.Consumption.Value);
                    }
                    else
                    {
                        Assert.Equal(readCoinResponse.Consumption.Value,
                                     BlockAlias.Undefined.ConvertToBlockHandle(clientId.Mask).Value);
                    }

                    Assert.Equal(outpointSaverComm[i], readCoinResponse.Outpoint);
                }

                // Read coins from the uncommitted block
                for (var i = 0; i < 100; i++)
                {
                    var readCoinRequest = GetCoinRequest.From(
                        new RequestId(++reqNum), clientId, outpointSaverUncomm[i],
                        openBlockResponse2.Handle.ConvertToBlockAlias(clientId.Mask),
                        clientId.Mask); // reconverted to BlockHandle after object construction
                    socket.Send(readCoinRequest.Span);

                    socket.Receive(headerBuffer);
                    var header = MemoryMarshal.Cast <byte, MessageHeader>(headerBuffer)[0];
                    Assert.Equal(MessageKind.GetCoinResponse, header.MessageKind);
                    var readCoinResponseBuffer = new byte[header.MessageSizeInBytes];
                    headerBuffer.CopyTo(readCoinResponseBuffer, 0);
                    socket.Receive(readCoinResponseBuffer.AsSpan(16));

                    var readCoinResponse = new GetCoinResponse(readCoinResponseBuffer);

                    Assert.Equal(header.MessageSizeInBytes, readCoinResponse.Span.Length);
                    Assert.Equal(openBlockResponse2.Handle.Value, readCoinResponse.Production.Value);
                    if (i % 2 == 0)
                    {
                        Assert.Equal(openBlockResponse2.Handle.Value, readCoinResponse.Consumption.Value);
                    }
                    else
                    {
                        Assert.Equal(readCoinResponse.Consumption.Value,
                                     BlockAlias.Undefined.ConvertToBlockHandle(clientId.Mask).Value);
                    }

                    Assert.Equal(outpointSaverUncomm[i], readCoinResponse.Outpoint);
                }

                // Read a coin from the future
                var readFutureCoinRequest = GetCoinRequest.From(
                    new RequestId(++reqNum), clientId, outpointSaverUncomm[0],
                    openBlockResponse.Handle.ConvertToBlockAlias(clientId.Mask),
                    clientId.Mask); // reconverted to BlockHandle after object construction
                socket.Send(readFutureCoinRequest.Span);

                socket.Receive(headerBuffer);
                {
                    var header = MemoryMarshal.Cast <byte, MessageHeader>(headerBuffer)[0];
                    var readFutureCoinResponseBuffer = new byte[header.MessageSizeInBytes];
                    var readFutureCoinResponse       = new GetCoinResponse(readFutureCoinResponseBuffer);
                    headerBuffer.CopyTo(readFutureCoinResponseBuffer, 0);
                    socket.Receive(readFutureCoinResponseBuffer.AsSpan(16));

                    Assert.Equal(GetCoinStatus.OutpointNotFound, readFutureCoinResponse.Status);
                }
            }
            finally
            {
                socket.Close();
                instance.Stop();
            }
        }
コード例 #5
0
        private static void DoBenchmark(IPEndPoint endpoint, ILog log)
        {
            var rand = new Random(); // non-deterministic on purpose

            var genWatch = new Stopwatch();

            genWatch.Start();

            var generator = new CoinGenerator(rand);

            generator.GenerateSketches(CoinIoCount);

            log.Log(LogSeverity.Info,
                    $"{CoinIoCount} coin I/Os generated in " + (genWatch.ElapsedMilliseconds / 1000f) + " seconds");

            var rawSocket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            rawSocket.Connect(endpoint);
            var socket = new SocketLikeAdapter(rawSocket);

            EnsureGenesisExists(socket);

            var handle = OpenBlock(CommittedBlockId.Genesis, socket, log);

            // Process coins
            var requestPool    = new SpanPool <byte>(1024 * 1024);
            var responseBuffer = new Span <byte>(new byte[4096]);

            var watch        = new Stopwatch();
            var commitWatch  = new Stopwatch();
            var outpointSeed = generator.Random.Next();

            for (var i = 0; i < generator.CoinSketches.Length; i++)
            {
                if (i % EpochSize == 0)
                {
                    watch.Reset();
                    watch.Start();
                }

                var sketch   = generator.CoinSketches[i];
                var outpoint = GetOutpoint(sketch, outpointSeed);

                if (sketch.IsProduction)
                {
                    // intended side effect on 'requestPool'
                    var writeCoinRequest = new ProduceCoinRequest(
                        new RequestId((uint)i),
                        ref outpoint,
                        OutpointFlags.None,
                        handle,
                        satoshis: 123,
                        nLockTime: 42,
                        scriptSizeInBytes: 100,
                        requestPool);

                    var script = writeCoinRequest.Script;
                    script[0] = (byte)i;
                }

                if (sketch.IsRead)
                {
                    // intended side effect on 'requestPool'
                    var readCoinRequest =
                        new GetCoinRequest(new RequestId((uint)i), ref outpoint, handle, requestPool);
                }

                if (sketch.IsConsumption)
                {
                    // intended side effect on 'requestPool'
                    var writeCoinRequest = new ConsumeCoinRequest(
                        new RequestId((uint)i), ref outpoint, handle, requestPool);
                }

                if (i % BatchSize == BatchSize - 1)
                {
                    // send all the requests as a batch
                    socket.Send(requestPool.Allocated());
                    requestPool.Reset();

                    for (var j = 0; j < BatchSize; j++)
                    {
                        socket.Receive(responseBuffer.Slice(0, 4));
                        var responseSize = responseBuffer[0];
                        socket.Receive(responseBuffer.Slice(4, responseSize - 4));
                    }
                }

                if (i % EpochSize == EpochSize - 1)
                {
                    watch.Stop();

                    var nextBlockId = GetBlockId();

                    commitWatch.Reset();
                    commitWatch.Start();
                    CommitBlock(handle, nextBlockId, socket);
                    commitWatch.Stop();

                    handle = OpenBlock(nextBlockId, socket, log);

                    var elapsed = watch.ElapsedMilliseconds / 1000.0; // seconds

                    var iops = (int)(EpochSize / elapsed);

                    log.Log(LogSeverity.Info,
                            $"Epoch {i / EpochSize} at {iops} IOps. Commit in {commitWatch.Elapsed.TotalMilliseconds} ms.");
                }
            }
        }