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(); } }
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); }
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); }
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); }
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); }
/// <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); }
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); }
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)); }
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); }
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); }
private void ReadingFromEmptyInbox() { Assert.Equal(0, _inbox.Peek().Length); }
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. } }
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); }