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); }
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)); }
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()); }
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); }
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; } }
public static byte[] EncodeBlockTx(BlockTx blockTx) { using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { EncodeBlockTx(writer, blockTx); return stream.ToArray(); } }
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); }
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)); }