Inheritance: BitSharp.Core.Domain.MerkleTreeNode
Exemple #1
0
 public void ValidationTransactionScript(Chain newChain, BlockTx tx, TxInput txInput, int txInputIndex, PrevTxOutput prevTxOutput)
 {
     if (ValidationTransactionScriptAction == null)
         coreRules.ValidationTransactionScript(newChain, tx, txInput, txInputIndex, prevTxOutput);
     else
         ValidationTransactionScriptAction(newChain, tx, txInput, txInputIndex, prevTxOutput);
 }
Exemple #2
0
        public static Block Create(BlockHeader header, ImmutableArray <Transaction> transactions)
        {
            var blockTxes = ImmutableArray.CreateRange(transactions.Select((tx, txIndex) =>
                                                                           (BlockTx)BlockTx.Create(txIndex, tx)));

            return(new Block(header, blockTxes));
        }
Exemple #3
0
        public static Block DecodeBlock(UInt256 blockHash, byte[] buffer, ref int offset)
        {
            var header = DecodeBlockHeader(blockHash, buffer, ref offset);

            var blockTxesCount = buffer.ReadVarInt(ref offset).ToIntChecked();
            var blockTxes = ImmutableArray.CreateBuilder<BlockTx>(blockTxesCount);
            for (var i = 0; i < blockTxesCount; i++)
            {
                var encodedTx = DecodeEncodedTx(null, buffer, ref offset);
                var blockTx = new BlockTx(i, encodedTx);

                blockTxes.Add(blockTx);

            }

            return new Block(header, blockTxes.MoveToImmutable());
        }
Exemple #4
0
        public static Block DecodeBlock(BinaryReader reader)
        {
            var header = DecodeBlockHeader(null, reader.ReadExactly(80));

            var blockTxesCount = reader.ReadVarInt().ToIntChecked();
            var blockTxes = ImmutableArray.CreateBuilder<BlockTx>(blockTxesCount);
            for (var i = 0; i < blockTxesCount; i++)
            {
                var txBytes = ReadTransaction(reader);
                var encodedTx = DecodeEncodedTx(null, txBytes);
                var blockTx = new BlockTx(i, encodedTx);

                blockTxes.Add(blockTx);

            }

            return new Block(header, blockTxes.MoveToImmutable());
        }
        public bool TryGetTransaction(UInt256 blockHash, int txIndex, out BlockTx transaction)
        {
            ImmutableSortedDictionary<int, BlockTxNode> blockTxes;
            BlockTxNode blockTxNode;

            if (this.allBlockTxNodes.TryGetValue(blockHash, out blockTxes)
                && blockTxes.TryGetValue(txIndex, out blockTxNode)
                && !blockTxNode.Pruned)
            {
                transaction = blockTxNode.ToBlockTx();
                return true;
            }
            else
            {
                transaction = null;
                return false;
            }
        }
        public void TestDoubleSpend()
        {
            // prepare block
            var fakeHeaders = new FakeHeaders();
            var chainedHeader0 = fakeHeaders.GenesisChained();
            var chainedHeader1 = fakeHeaders.NextChained();
            var chainedHeader2 = fakeHeaders.NextChained();
            var chain = Chain.CreateForGenesisBlock(chainedHeader0).ToBuilder();
            var emptyCoinbaseTx0 = new BlockTx(0, new Transaction(0, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0));
            var emptyCoinbaseTx1 = new BlockTx(0, new Transaction(1, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0));

            // initialize memory utxo builder storage
            var memoryStorage = new MemoryStorageManager();
            var memoryChainStateCursor = memoryStorage.OpenChainStateCursor().Item;
            memoryChainStateCursor.BeginTransaction();

            // initialize utxo builder
            var utxoBuilder = new UtxoBuilder();

            // prepare an unspent transaction
            var txHash = new UInt256(100);
            var unspentTx = new UnspentTx(txHash, chainedHeader1.Height, 0, 1, OutputState.Unspent);

            // add the unspent transaction
            memoryChainStateCursor.TryAddUnspentTx(unspentTx);

            // create an input to spend the unspent transaction
            var input = new TxInput(new TxOutputKey(txHash, txOutputIndex: 0), ImmutableArray.Create<byte>(), 0);
            var tx = new BlockTx(1, new Transaction(0, ImmutableArray.Create(input), ImmutableArray.Create<TxOutput>(), 0));

            // spend the input
            chain.AddBlock(chainedHeader1);
            utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx0, tx }.ToBufferBlock()).ToEnumerable().ToList();

            // verify utxo storage
            UnspentTx actualUnspentTx;
            Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx));
            Assert.IsTrue(actualUnspentTx.IsFullySpent);

            // attempt to spend the input again, validation exception should be thrown
            chain.AddBlock(chainedHeader2);
            AssertMethods.AssertAggregateThrows<ValidationException>(() =>
                utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx1, tx }.ToBufferBlock()).ToEnumerable().ToList());
        }
        public void TestUtxoLookAheadCoinbaseOnly()
        {
            var blockTxes = new BufferBlock<BlockTx>();

            var deferredCursor = new Mock<IDeferredChainStateCursor>();
            deferredCursor.Setup(x => x.CursorCount).Returns(4);

            var lookAhead = UtxoLookAhead.LookAhead(blockTxes, deferredCursor.Object);

            // post a coinbase transaction, the inputs should not be looked up
            var blockTx = new BlockTx(0, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));
            blockTxes.Post(blockTx);
            blockTxes.Complete();

            // verify coinbase tx was forarded, with no inputs used from the chain state (no inputs were mocked)
            var warmedTxes = lookAhead.ReceiveAllAsync().Result;
            Assert.AreEqual(1, warmedTxes.Count);
            Assert.AreEqual(warmedTxes[0].Transaction.Hash, blockTx.Transaction.Hash);

            Assert.IsTrue(lookAhead.Completion.Wait(2000));
        }
        public bool TryAddBlockTransactions(UInt256 blockHash, IEnumerable<EncodedTx> blockTxes)
        {
            if (ContainsBlock(blockHash))
                return false;

            var writeBatch = new WriteBatch();
            try
            {
                int txCount;
                using (var snapshot = db.GetSnapshot())
                {
                    var readOptions = new ReadOptions { Snapshot = snapshot };

                    var txIndex = 0;
                    foreach (var tx in blockTxes)
                    {
                        var key = DbEncoder.EncodeBlockHashTxIndex(blockHash, txIndex);

                        Slice existingValue;
                        if (db.TryGet(readOptions, key, out existingValue))
                            return false;

                        var blockTx = new BlockTx(txIndex, tx);
                        var value = DataEncoder.EncodeBlockTxNode(blockTx);

                        writeBatch.Put(key, value);

                        txIndex++;
                    }

                    txCount = txIndex;
                }

                return TryAddBlockInner(blockHash, txCount, writeBatch);
            }
            finally
            {
                writeBatch.Dispose();
            }
        }
        public void TestUtxoLookAheadWarmupException()
        {
            var deferredCursor = new Mock<IDeferredChainStateCursor>();
            deferredCursor.Setup(x => x.CursorCount).Returns(4);

            var blockTxes = new BufferBlock<BlockTx>();

            var lookAhead = UtxoLookAhead.LookAhead(blockTxes, deferredCursor.Object);

            var blockTx0 = new BlockTx(0, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));
            var blockTx1 = new BlockTx(1, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));

            var expectedException = new InvalidOperationException();
            deferredCursor.Setup(x => x.WarmUnspentTx(blockTx1.Transaction.Inputs[0].PreviousTxOutputKey.TxHash)).Throws(expectedException);

            blockTxes.Post(blockTx0);
            blockTxes.Post(blockTx1);
            blockTxes.Complete();

            Exception actualEx;
            AssertMethods.AssertAggregateThrows<Exception>(() =>
                lookAhead.Completion.Wait(2000), out actualEx);
            Assert.AreSame(expectedException, actualEx);
        }
        public bool TryGetTransaction(UInt256 blockHash, int txIndex, out BlockTx transaction)
        {
            using (var handle = this.cursorCache.TakeItem())
            {
                var cursor = handle.Item;

                using (var jetTx = cursor.jetSession.BeginTransaction())
                {
                    int blockIndex;
                    if (!TryGetBlockIndex(cursor, blockHash, out blockIndex))
                    {
                        transaction = null;
                        return false;
                    }

                    Api.JetSetCurrentIndex(cursor.jetSession, cursor.blocksTableId, "IX_BlockIndexTxIndex");
                    Api.MakeKey(cursor.jetSession, cursor.blocksTableId, blockIndex, MakeKeyGrbit.NewKey);
                    Api.MakeKey(cursor.jetSession, cursor.blocksTableId, txIndex, MakeKeyGrbit.None);
                    if (Api.TrySeek(cursor.jetSession, cursor.blocksTableId, SeekGrbit.SeekEQ))
                    {
                        var blockTxHashColumn = new BytesColumnValue { Columnid = cursor.blockTxHashColumnId };
                        var blockTxBytesColumn = new BytesColumnValue { Columnid = cursor.blockTxBytesColumnId };
                        Api.RetrieveColumns(cursor.jetSession, cursor.blocksTableId, blockTxHashColumn, blockTxBytesColumn);

                        if (blockTxBytesColumn.Value != null)
                        {
                            var txHash = DbEncoder.DecodeUInt256(blockTxHashColumn.Value);
                            transaction = new BlockTx(txIndex, txHash, blockTxBytesColumn.Value.ToImmutableArray());
                            return true;
                        }
                        else
                        {
                            transaction = null;
                            return false;
                        }
                    }
                    else
                    {
                        transaction = null;
                        return false;
                    }
                }
            }
        }
        public bool TryAddBlockTransactions(UInt256 blockHash, IEnumerable<Transaction> blockTxes)
        {
            try
            {
                if (this.ContainsBlock(blockHash))
                    return false;

                using (var txn = this.jetInstance.BeginTransaction())
                {
                    var txIndex = 0;
                    foreach (var tx in blockTxes)
                    {
                        var blockTx = new BlockTx(txIndex, 0, tx.Hash, false, tx);

                        var key = DbEncoder.EncodeBlockHashTxIndex(blockHash, txIndex);
                        var value = DataEncoder.EncodeBlockTx(blockTx);

                        txn.Put(blocksTableId, key, value);
                        txIndex++;
                    }

                    // increase block count
                    txn.Put(globalsTableId, blockCountKey,
                        Bits.ToInt32(txn.Get(globalsTableId, blockCountKey)) + 1);

                    txn.Commit();
                    return true;
                }
            }
            catch (Exception)
            {
                return false;
            }
        }
 public bool TryGetTransaction(UInt256 blockHash, int txIndex, out BlockTx transaction)
 {
     return GetStorage(blockHash).TryGetTransaction(blockHash, txIndex, out transaction);
 }
Exemple #13
0
 public bool TryGetTransaction(UInt256 blockHash, int txIndex, out BlockTx transaction)
 {
     return this.blockTxesStorage.Value.TryGetTransaction(blockHash, txIndex, out transaction);
 }
        public void TestUtxoLookAheadWithTransactions()
        {
            var deferredCursor = new Mock<IDeferredChainStateCursor>();
            deferredCursor.Setup(x => x.CursorCount).Returns(4);

            var blockTxes = new BufferBlock<BlockTx>();

            var lookAhead = UtxoLookAhead.LookAhead(blockTxes, deferredCursor.Object);

            var blockTx0 = new BlockTx(0, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));
            var blockTx1 = new BlockTx(1, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));
            var blockTx2 = new BlockTx(2, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));

            blockTxes.Post(blockTx0);
            blockTxes.Post(blockTx1);
            blockTxes.Post(blockTx2);
            blockTxes.Complete();

            // verify each transaction was forwarded
            var expectedBlockTxes = new[] { blockTx0, blockTx1, blockTx2 };
            var warmedTxes = lookAhead.ReceiveAllAsync().Result;
            Assert.AreEqual(3, warmedTxes.Count);
            CollectionAssert.AreEqual(expectedBlockTxes.Select(x => x.Transaction.Hash).ToList(), warmedTxes.Select(x => x.Transaction.Hash).ToList());

            // verify each non-coinbase input transaction was warmed up
            var expectedLookups = expectedBlockTxes.Skip(1).SelectMany(x => x.Transaction.Inputs.Select(input => input.PreviousTxOutputKey.TxHash));
            foreach (var txHash in expectedLookups)
                deferredCursor.Verify(x => x.WarmUnspentTx(txHash));

            Assert.IsTrue(lookAhead.Completion.Wait(2000));
        }
        public void WriteNode(MerkleTreeNode node)
        {
            if (!node.Pruned)
                throw new ArgumentException();

            var kvPair = cursor.GetCurrent().Value;

            UInt256 recordBlockHash; int txIndex;
            DbEncoder.DecodeBlockHashTxIndex(kvPair.Key, out recordBlockHash, out txIndex);

            if (this.blockHash != recordBlockHash)
                throw new InvalidOperationException();
            if (node.Index != txIndex)
                throw new InvalidOperationException();

            var key = DbEncoder.EncodeBlockHashTxIndex(blockHash, node.Index);
            var blockTx = new BlockTx(node.Index, node.Depth, node.Hash, node.Pruned, null);
            cursor.Put(key, DataEncoder.EncodeBlockTx(blockTx), CursorPutOptions.Current);
        }
        public void TestSimpleSpend()
        {
            // prepare block
            var fakeHeaders = new FakeHeaders();
            var chainedHeader0 = fakeHeaders.GenesisChained();
            var chainedHeader1 = fakeHeaders.NextChained();
            var chainedHeader2 = fakeHeaders.NextChained();
            var chainedHeader3 = fakeHeaders.NextChained();
            var chain = Chain.CreateForGenesisBlock(chainedHeader0).ToBuilder();
            var emptyCoinbaseTx0 = new BlockTx(0, new Transaction(0, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0));
            var emptyCoinbaseTx1 = new BlockTx(1, new Transaction(1, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0));
            var emptyCoinbaseTx2 = new BlockTx(2, new Transaction(2, ImmutableArray.Create<TxInput>(), ImmutableArray.Create<TxOutput>(), 0));

            // initialize memory utxo builder storage
            var memoryStorage = new MemoryStorageManager();
            var memoryChainStateCursor = memoryStorage.OpenChainStateCursor().Item;
            memoryChainStateCursor.BeginTransaction();

            // initialize utxo builder
            var utxoBuilder = new UtxoBuilder();

            // prepare an unspent transaction
            var txHash = new UInt256(100);
            var unspentTx = new UnspentTx(txHash, chainedHeader1.Height, 0, 3, OutputState.Unspent);

            // prepare unspent output
            var unspentTransactions = ImmutableDictionary.Create<UInt256, UnspentTx>().Add(txHash, unspentTx);

            // add the unspent transaction
            memoryChainStateCursor.TryAddUnspentTx(unspentTx);

            // create an input to spend the unspent transaction's first output
            var input0 = new TxInput(new TxOutputKey(txHash, txOutputIndex: 0), ImmutableArray.Create<byte>(), 0);
            var tx0 = new BlockTx(1, new Transaction(0, ImmutableArray.Create(input0), ImmutableArray.Create<TxOutput>(), 0));

            // spend the input
            chain.AddBlock(chainedHeader1);
            utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx0, tx0 }.ToBufferBlock()).ToEnumerable().ToList();

            // verify utxo storage
            UnspentTx actualUnspentTx;
            Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx));
            Assert.IsTrue(actualUnspentTx.OutputStates.Length == 3);
            Assert.IsTrue(actualUnspentTx.OutputStates[0] == OutputState.Spent);
            Assert.IsTrue(actualUnspentTx.OutputStates[1] == OutputState.Unspent);
            Assert.IsTrue(actualUnspentTx.OutputStates[2] == OutputState.Unspent);

            // create an input to spend the unspent transaction's second output
            var input1 = new TxInput(new TxOutputKey(txHash, txOutputIndex: 1), ImmutableArray.Create<byte>(), 0);
            var tx1 = new BlockTx(1, new Transaction(0, ImmutableArray.Create(input1), ImmutableArray.Create<TxOutput>(), 0));

            // spend the input
            chain.AddBlock(chainedHeader2);
            utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx1, tx1 }.ToBufferBlock()).ToEnumerable().ToList();

            // verify utxo storage
            Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx));
            Assert.IsTrue(actualUnspentTx.OutputStates.Length == 3);
            Assert.IsTrue(actualUnspentTx.OutputStates[0] == OutputState.Spent);
            Assert.IsTrue(actualUnspentTx.OutputStates[1] == OutputState.Spent);
            Assert.IsTrue(actualUnspentTx.OutputStates[2] == OutputState.Unspent);

            // create an input to spend the unspent transaction's third output
            var input2 = new TxInput(new TxOutputKey(txHash, txOutputIndex: 2), ImmutableArray.Create<byte>(), 0);
            var tx2 = new BlockTx(2, new Transaction(0, ImmutableArray.Create(input2), ImmutableArray.Create<TxOutput>(), 0));

            // spend the input
            chain.AddBlock(chainedHeader3);
            utxoBuilder.CalculateUtxo(memoryChainStateCursor, chain.ToImmutable(), new[] { emptyCoinbaseTx2, tx2 }.ToBufferBlock()).ToEnumerable().ToList();

            // verify utxo storage
            Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx));
            Assert.IsTrue(actualUnspentTx.IsFullySpent);
        }
        private IEnumerator<BlockTx> ReadBlockTransactions(UInt256 blockHash)
        {
            using (var handle = this.cursorCache.TakeItem())
            {
                var cursor = handle.Item;

                using (var jetTx = cursor.jetSession.BeginTransaction())
                {
                    int blockIndex;
                    if (!TryGetBlockIndex(cursor, blockHash, out blockIndex))
                        throw new MissingDataException(blockHash);

                    Api.JetSetCurrentIndex(cursor.jetSession, cursor.blocksTableId, "IX_BlockIndexTxIndex");

                    Api.MakeKey(cursor.jetSession, cursor.blocksTableId, blockIndex, MakeKeyGrbit.NewKey);
                    Api.MakeKey(cursor.jetSession, cursor.blocksTableId, 0, MakeKeyGrbit.None);
                    if (!Api.TrySeek(cursor.jetSession, cursor.blocksTableId, SeekGrbit.SeekGE))
                        throw new MissingDataException(blockHash);

                    Api.MakeKey(cursor.jetSession, cursor.blocksTableId, blockIndex, MakeKeyGrbit.NewKey);
                    Api.MakeKey(cursor.jetSession, cursor.blocksTableId, int.MaxValue, MakeKeyGrbit.None);
                    if (!Api.TrySetIndexRange(cursor.jetSession, cursor.blocksTableId, SetIndexRangeGrbit.RangeUpperLimit))
                        throw new MissingDataException(blockHash);

                    do
                    {
                        var txIndexColumn = new Int32ColumnValue { Columnid = cursor.txIndexColumnId };
                        var blockDepthColumn = new Int32ColumnValue { Columnid = cursor.blockDepthColumnId };
                        var blockTxHashColumn = new BytesColumnValue { Columnid = cursor.blockTxHashColumnId };
                        var blockTxBytesColumn = new BytesColumnValue { Columnid = cursor.blockTxBytesColumnId };
                        Api.RetrieveColumns(cursor.jetSession, cursor.blocksTableId, txIndexColumn, blockDepthColumn, blockTxHashColumn, blockTxBytesColumn);

                        var txIndex = txIndexColumn.Value.Value;
                        var depth = blockDepthColumn.Value.Value;
                        var txHash = DbEncoder.DecodeUInt256(blockTxHashColumn.Value);
                        var txBytes = blockTxBytesColumn.Value;

                        // determine if transaction is pruned by its depth
                        var pruned = depth >= 0;
                        depth = Math.Max(0, depth);

                        var tx = !pruned ? DataEncoder.DecodeTransaction(txBytes, txHash) : null;

                        var blockTx = new BlockTx(txIndex, depth, txHash, pruned, tx);

                        yield return blockTx;
                    }
                    while (Api.TryMoveNext(cursor.jetSession, cursor.blocksTableId));
                }
            }
        }
 public bool TryGetTransaction(UInt256 blockHash, int txIndex, out BlockTx transaction)
 {
     Slice value;
     if (db.TryGet(new ReadOptions(), DbEncoder.EncodeBlockHashTxIndex(blockHash, txIndex), out value))
     {
         var blockTxNode = DataDecoder.DecodeBlockTxNode(value.ToArray());
         if (!blockTxNode.Pruned)
         {
             transaction = blockTxNode.ToBlockTx();
             return true;
         }
         else
         {
             transaction = default(BlockTx);
             return false;
         }
     }
     else
     {
         transaction = default(BlockTx);
         return false;
     }
 }
Exemple #19
0
 public static byte[] EncodeBlockTx(BlockTx blockTx)
 {
     using (var stream = new MemoryStream())
     using (var writer = new BinaryWriter(stream))
     {
         EncodeBlockTx(writer, blockTx);
         return stream.ToArray();
     }
 }
Exemple #20
0
        public static void EncodeBlockTx(BinaryWriter writer, BlockTx blockTx)
        {
            var txBytes = blockTx.Transaction != null ? EncodeTransaction(blockTx.Transaction) : new byte[0];

            writer.WriteInt32(blockTx.Index);
            writer.WriteInt32(blockTx.Depth);
            writer.WriteUInt256(blockTx.Hash);
            writer.WriteBool(blockTx.Pruned);
            if (!blockTx.Pruned)
                EncodeTransaction(writer, blockTx.Transaction);
        }
Exemple #21
0
        public void ValidationTransactionScript(Chain newChain, BlockTx tx, TxInput txInput, int txInputIndex, PrevTxOutput prevTxOutput)
        {
            var chainedHeader = newChain.LastBlock;

            // BIP16 didn't become active until Apr 1 2012
            var nBIP16SwitchTime = DateTimeOffset.FromUnixTimeSeconds(1333238400U);
            var strictPayToScriptHash = chainedHeader.Time >= nBIP16SwitchTime;

            var flags = strictPayToScriptHash ? verify_flags_type.verify_flags_p2sh : verify_flags_type.verify_flags_none;

            // Start enforcing the DERSIG (BIP66) rules, for block.nVersion=3 blocks,
            // when 75% of the network has upgraded:
            if (chainedHeader.Version >= 3
                && IsSuperMajority(3, newChain, ChainParams.MajorityEnforceBlockUpgrade))
            {
                flags |= verify_flags_type.verify_flags_dersig;
            }

            // Start enforcing CHECKLOCKTIMEVERIFY, (BIP65) for block.nVersion=4
            // blocks, when 75% of the network has upgraded:
            if (chainedHeader.Version >= 4
                && IsSuperMajority(4, newChain, ChainParams.MajorityEnforceBlockUpgrade))
            {
                flags |= verify_flags_type.verify_flags_checklocktimeverify;
            }

            var result = LibbitcoinConsensus.VerifyScript(
                tx.TxBytes,
                prevTxOutput.ScriptPublicKey,
                txInputIndex,
                flags);

            if (!result)
            {
                logger.Debug($"Script did not pass in block: {chainedHeader.Hash}, tx: {tx.Index}, {tx.Hash}, input: {txInputIndex}");
                throw new ValidationException(chainedHeader.Hash);
            }
        }
        public void TestUtxoLookAheadOrdering()
        {
            var deferredCursor = new Mock<IDeferredChainStateCursor>();
            deferredCursor.Setup(x => x.CursorCount).Returns(4);

            var blockTxes = new BufferBlock<BlockTx>();

            var lookAhead = UtxoLookAhead.LookAhead(blockTxes, deferredCursor.Object);

            var blockTx0 = new BlockTx(0, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));
            var blockTx1 = new BlockTx(1, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));
            var blockTx2 = new BlockTx(2, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));
            var blockTx3 = new BlockTx(3, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 }));

            // setup events so that transactions finish in 0, 3, 1, 2 order
            using (var blockTx1ReadEvent = new ManualResetEventSlim())
            using (var blockTx2ReadEvent = new ManualResetEventSlim())
            using (var blockTx3ReadEvent = new ManualResetEventSlim())
            {
                deferredCursor.Setup(x => x.WarmUnspentTx(blockTx1.Transaction.Inputs[0].PreviousTxOutputKey.TxHash))
                    .Callback(() =>
                    {
                        blockTx3ReadEvent.Wait();
                        blockTx1ReadEvent.Set();
                    });

                deferredCursor.Setup(x => x.WarmUnspentTx(blockTx2.Transaction.Inputs[0].PreviousTxOutputKey.TxHash))
                    .Callback(() =>
                    {
                        blockTx3ReadEvent.Wait();
                        blockTx1ReadEvent.Wait();
                        blockTx2ReadEvent.Set();
                    });

                deferredCursor.Setup(x => x.WarmUnspentTx(blockTx3.Transaction.Inputs[0].PreviousTxOutputKey.TxHash))
                    .Callback(() =>
                    {
                        blockTx3ReadEvent.Set();
                    });

                blockTxes.Post(blockTx0);
                blockTxes.Post(blockTx1);
                blockTxes.Post(blockTx2);
                blockTxes.Post(blockTx3);
                blockTxes.Complete();

                // verify each transaction was forwarded, in the correct order
                var expectedBlockTxes = new[] { blockTx0, blockTx1, blockTx2, blockTx3 };
                var warmedTxes = lookAhead.ReceiveAllAsync().Result;
                Assert.AreEqual(4, warmedTxes.Count);
                CollectionAssert.AreEqual(expectedBlockTxes.Select(x => x.Transaction.Hash).ToList(), warmedTxes.Select(x => x.Transaction.Hash).ToList());
            }

            Assert.IsTrue(lookAhead.Completion.Wait(2000));
        }