public void AddTwoTxesToNewBlock() { var key = new Key(); var tx1 = new BlockTx() { Amount = 1, PubKeyFrom = key.Public, PubKeyTo = new Key("test").Public }; var tx2 = new BlockTx() { Amount = 5, PubKeyFrom = key.Public, PubKeyTo = new Key("test2").Public }; tx1.Sign(key); tx2.Sign(key); var block = new Block(0); block.AddTx(tx1); block.AddTx(tx2); Assert.NotNull(block); Assert.NotNull(block.Txes); Assert.AreEqual(2, block.Txes.Length); }
public void TestExceptionInLoadTxInput() { var expectedException = new Exception(); var coreStorage = new Mock <ICoreStorage>(); var chainedHeader = RandomData.RandomChainedHeader(); var tx = RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 1 }); var txLookupKey = new TxLookupKey(UInt256.Zero, 0); var inputTx = RandomData.RandomTransaction(); var loadingTx = new LoadingTx(1, tx, chainedHeader, ImmutableArray.Create(txLookupKey)); var loadingTxes = new BufferBlock <LoadingTx>(); loadingTxes.Post(loadingTx); loadingTxes.Complete(); // throw expected exception when the input transaction is looked up BlockTx outputTx = null; coreStorage.Setup(x => x.TryGetTransaction(txLookupKey.BlockHash, txLookupKey.TxIndex, out outputTx)).Throws(expectedException); var loadedTxes = TxLoader.LoadTxes(coreStorage.Object, loadingTxes); Exception actualEx; AssertMethods.AssertAggregateThrows <Exception>(() => loadedTxes.ToEnumerable().ToList(), out actualEx); Assert.AreSame(expectedException, actualEx); }
public void TestUtxoLookAheadWarmupException() { var deferredCursor = new Mock <IDeferredChainStateCursor>(); deferredCursor.Setup(x => x.CursorCount).Returns(4); var blockTxes = new BufferBlock <DecodedBlockTx>(); var lookAhead = UtxoLookAhead.LookAhead(blockTxes, deferredCursor.Object); var blockTx0 = BlockTx.Create(0, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 })); var blockTx1 = BlockTx.Create(1, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 })); var expectedException = new InvalidOperationException(); deferredCursor.Setup(x => x.WarmUnspentTx(blockTx1.Transaction.Inputs[0].PrevTxOutputKey.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 void TestUtxoLookAheadCoinbaseOnly() { var blockTxes = new BufferBlock <DecodedBlockTx>(); 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 = BlockTx.Create(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].Hash, blockTx.Hash); Assert.IsTrue(lookAhead.Completion.Wait(2000)); }
private IEnumerable <BlockTx> ReadBlockTransactions(UInt256 blockHash, int blockId) { using (var handle = this.cursorCache.TakeItem()) { var cursor = handle.Item; using (var jetTx = cursor.jetSession.BeginTransaction()) { var sha256 = new SHA256Managed(); Api.JetSetCurrentIndex(cursor.jetSession, cursor.blocksTableId, "IX_BlockIdTxIndex"); Api.MakeKey(cursor.jetSession, cursor.blocksTableId, blockId, MakeKeyGrbit.NewKey); Api.MakeKey(cursor.jetSession, cursor.blocksTableId, -1, MakeKeyGrbit.None); if (!Api.TrySeek(cursor.jetSession, cursor.blocksTableId, SeekGrbit.SeekGE)) { yield break; } do { if (blockId != Api.RetrieveColumnAsInt32(cursor.jetSession, cursor.blocksTableId, cursor.blockIdColumnId).Value) { yield break; } var txIndex = Api.RetrieveColumnAsInt32(cursor.jetSession, cursor.blocksTableId, cursor.blockTxIndexColumnId).Value; var depth = Api.RetrieveColumnAsInt32(cursor.jetSession, cursor.blocksTableId, cursor.blockDepthColumnId).Value; var txHash = DbEncoder.DecodeUInt256(Api.RetrieveColumn(cursor.jetSession, cursor.blocksTableId, cursor.blockTxHashColumnId)); var txBytes = Api.RetrieveColumn(cursor.jetSession, cursor.blocksTableId, cursor.blockTxBytesColumnId); // determine if transaction is pruned by its depth var pruned = depth >= 0; depth = Math.Max(0, depth); Transaction tx; if (!pruned) { // verify transaction is not corrupt if (txHash != new UInt256(sha256.ComputeDoubleHash(txBytes))) { throw new MissingDataException(blockHash); } tx = DataEncoder.DecodeTransaction(txBytes); } else { tx = null; } var blockTx = new BlockTx(txIndex, depth, txHash, pruned, tx); yield return(blockTx); } while (Api.TryMoveNext(cursor.jetSession, cursor.blocksTableId)); } } }
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 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 = BlockTx.Create(0, Transaction.Create(0, ImmutableArray.Create <TxInput>(), ImmutableArray.Create <TxOutput>(), 0)); var emptyCoinbaseTx1 = BlockTx.Create(0, Transaction.Create(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, 0, false, 1, OutputState.Unspent); var txOutputKey = new TxOutputKey(txHash, 0); var txOutput = new TxOutput(0, ImmutableArray <byte> .Empty); // add the unspent transaction memoryChainStateCursor.TryAddUnspentTx(unspentTx); memoryChainStateCursor.TryAddUnspentTxOutput(txOutputKey, txOutput); // create an input to spend the unspent transaction var input = new TxInput(txHash, 0, ImmutableArray.Create <byte>(), 0); var tx = BlockTx.Create(1, Transaction.Create(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; TxOutput actualTxOutput; Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTx(txHash, out actualUnspentTx)); Assert.IsTrue(actualUnspentTx.IsFullySpent); Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTxOutput(txOutputKey, out actualTxOutput)); Assert.AreEqual(txOutput, actualTxOutput); // 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 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 void SingTxWithPublicKey() { var key = new Key(); var tx = new BlockTx() { Amount = 1, PubKeyFrom = key.Public, PubKeyTo = new Key("test").Public }; var serializedTx = tx.Serialize(); tx.Sign(key); Assert.NotNull(tx.Fingerprint); Assert.NotZero(tx.Fingerprint.Length); Assert.IsTrue(key.Verify(serializedTx, ByteArray.HexToByte(tx.Fingerprint))); }
public void ValidateTxFailFromAlteredTransactionAmount() { var key = new Key(); var tx = new BlockTx() { Amount = 1, PubKeyFrom = key.Public, PubKeyTo = new Key("test").Public }; tx.Sign(key); var signKey = new Key(ByteArray.HexToByte(tx.PubKeyFrom)); tx.Amount = 2; Assert.IsFalse(signKey.Verify(tx.Serialize(), ByteArray.HexToByte(tx.Fingerprint))); }
public void ValidateTxFromPublicKey() { var key = new Key(); var tx = new BlockTx() { Amount = 1, PubKeyFrom = key.Public, PubKeyTo = new Key("test").Public }; var serializedTx = tx.Serialize(); tx.Sign(key); var signKey = new Key(ByteArray.HexToByte(tx.PubKeyFrom)); Assert.IsTrue(signKey.Verify(serializedTx, ByteArray.HexToByte(tx.Fingerprint))); }
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 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 void ValidateTxFailFromAlteredPublicKey() { var key = new Key(); var tx = new BlockTx() { Amount = 1, PubKeyFrom = key.Public, PubKeyTo = new Key("test").Public }; var serializedTx = tx.Serialize(); tx.Sign(key); var signKey = new Key(ByteArray.HexToByte(tx.PubKeyFrom)); var alteredPubKey = ByteArray.HexToByte(tx.Fingerprint.Replace("B", "C")); Assert.IsFalse(signKey.Verify(serializedTx, alteredPubKey)); }
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 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 void AddTxToNewBlock() { var key = new Key(); var tx = new BlockTx() { Amount = 1, PubKeyFrom = key.Public, PubKeyTo = new Key("test").Public }; tx.Sign(key); var block = new Block(0); block.AddTx(tx); Assert.NotNull(block); Assert.NotNull(block.Txes); Assert.AreEqual(1, block.Txes.Length); }
public void TestUtxoLookAheadWithTransactions() { var deferredCursor = new Mock <IDeferredChainStateCursor>(); deferredCursor.Setup(x => x.CursorCount).Returns(4); var blockTxes = new BufferBlock <DecodedBlockTx>(); var lookAhead = UtxoLookAhead.LookAhead(blockTxes, deferredCursor.Object); var blockTx0 = BlockTx.Create(0, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 })); var blockTx1 = BlockTx.Create(1, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 })); var blockTx2 = BlockTx.Create(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.Hash).ToList(), warmedTxes.Select(x => x.Hash).ToList()); // verify each non-coinbase input transaction was warmed up var expectedLookups = expectedBlockTxes.Skip(1).SelectMany(x => x.Transaction.Inputs.Select(input => input.PrevTxOutputKey.TxHash)); foreach (var txHash in expectedLookups) { deferredCursor.Verify(x => x.WarmUnspentTx(txHash)); } Assert.IsTrue(lookAhead.Completion.Wait(2000)); }
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 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 bool TryGetTransaction(UInt256 blockHash, int txIndex, out BlockTx transaction) { return(this.blockTxesStorage.Value.TryGetTransaction(blockHash, txIndex, out transaction)); }
public void TestReplayForward() { var coreStorage = new Mock <ICoreStorage>(); var chainState = new Mock <IChainState>(); chainState.Setup(x => x.CursorCount).Returns(4); var testBlocks = new TestBlocks(); var block = testBlocks.MineAndAddBlock(txCount: 10); var chainedHeader = testBlocks.Chain.LastBlock; chainState.Setup(x => x.Chain).Returns(() => testBlocks.Chain); // mock block txes read var blockTxes = block.Transactions.Select((tx, txIndex) => (BlockTx)BlockTx.Create(txIndex, tx)).GetEnumerator(); coreStorage.Setup(x => x.TryReadBlockTransactions(chainedHeader.Hash, out blockTxes)).Returns(true); // mock unspent tx lookup var expectedValue = 50UL * (ulong)100.MILLION(); for (var txIndex = 0; txIndex < block.Transactions.Length; txIndex++) { var tx = block.Transactions[txIndex]; for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; // create a fake unspent tx, with enough outputs for this input var unspentTx = new UnspentTx(input.PrevTxOutputKey.TxHash, blockIndex: 1, txIndex: txIndex * inputIndex, txVersion: 0, isCoinbase: false, outputStates: tx.IsCoinbase ? OutputStates.Empty : new OutputStates(input.PrevTxOutputKey.TxOutputIndex.ToIntChecked() + 1, OutputState.Unspent)); var txOutput = new TxOutput(tx.Outputs[0].Value, tx.Outputs[0].ScriptPublicKey); chainState.Setup(x => x.TryGetUnspentTx(unspentTx.TxHash, out unspentTx)).Returns(true); chainState.Setup(x => x.TryGetUnspentTxOutput(input.PrevTxOutputKey, out txOutput)).Returns(true); } } var validatableTxes = UtxoReplayer.ReplayCalculateUtxo(coreStorage.Object, chainState.Object, chainedHeader).ToEnumerable().ToList(); // verify correct number of transactions were replayed Assert.AreEqual(validatableTxes.Count, block.Transactions.Length); expectedValue = 50UL * (ulong)100.MILLION(); foreach (var validatableTx in validatableTxes) { // verify validatable tx matches original block tx Assert.AreEqual(block.Transactions[validatableTx.Index].Hash, validatableTx.Transaction.Hash); // if coinbase, verify no tx outputs for coinbase inputs if (validatableTx.IsCoinbase) { Assert.AreEqual(0, validatableTx.PrevTxOutputs.Length); } else { // verify there is a tx output for each input Assert.AreEqual(block.Transactions[validatableTx.Index].Inputs.Length, validatableTx.PrevTxOutputs.Length); // verify each tx output matches the mocked data for (var inputIndex = 0; inputIndex < validatableTx.Transaction.Inputs.Length; inputIndex++) { var prevTxOutput = validatableTx.PrevTxOutputs[inputIndex]; expectedValue -= 1; Assert.AreEqual(expectedValue, prevTxOutput.Value); CollectionAssert.AreEqual(block.Transactions[0].Outputs[0].ScriptPublicKey, prevTxOutput.ScriptPublicKey); } } } }
public bool TryGetTransaction(UInt256 blockHash, int txIndex, out BlockTx transaction) { return(GetStorage(blockHash).TryGetTransaction(blockHash, txIndex, out transaction)); }
private void TestRollback(ITestStorageProvider provider) { ConsoleLoggingModule.Configure(); var logger = LogManager.GetCurrentClassLogger(); var blockCount = 10.THOUSAND(); var checkUtxoHashFrequencey = 1000; var blockProvider = new TestNet3BlockProvider(); var blocks = blockProvider.ReadBlocks().Take(blockCount).ToList(); var genesisBlock = blocks[0]; var genesisHeader = new ChainedHeader(genesisBlock.Header, height: 0, totalWork: 0, dateSeen: DateTimeOffset.Now); var genesisChain = Chain.CreateForGenesisBlock(genesisHeader); var chainParams = new Testnet3Params(); var rules = new CoreRules(chainParams) { IgnoreScripts = true, IgnoreSignatures = true, IgnoreScriptErrors = true }; using (var storageManager = provider.OpenStorageManager()) using (var coreStorage = new CoreStorage(storageManager)) using (var chainStateBuilder = new ChainStateBuilder(rules, coreStorage, storageManager)) { // add blocks to storage coreStorage.AddGenesisBlock(ChainedHeader.CreateForGenesisBlock(blocks[0].Header)); foreach (var block in blocks) { coreStorage.TryAddBlock(block); } // store empty utxo hash var expectedUtxoHashes = new List <UInt256>(); using (var chainState = chainStateBuilder.ToImmutable()) expectedUtxoHashes.Add(UtxoCommitment.ComputeHash(chainState)); // calculate utxo forward and store its state at each step along the way for (var blockIndex = 0; blockIndex < blocks.Count; blockIndex++) { logger.Info($"Adding: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); chainStateBuilder.AddBlockAsync(chainedHeader, block.Transactions.Select( (tx, txIndex) => BlockTx.Create(txIndex, tx))).Wait(); if (blockIndex % checkUtxoHashFrequencey == 0 || blockIndex == blocks.Count - 1) { using (var chainState = chainStateBuilder.ToImmutable()) expectedUtxoHashes.Add(UtxoCommitment.ComputeHash(chainState)); } } // verify the utxo state before rolling back var expectedLastUtxoHash = UInt256.ParseHex("5f155c7d8a5c850d5fb2566aec5110caa40e270184126d17022ae9780fd65fd9"); Assert.AreEqual(expectedLastUtxoHash, expectedUtxoHashes.Last()); expectedUtxoHashes.RemoveAt(expectedUtxoHashes.Count - 1); // roll utxo backwards and validate its state at each step along the way for (var blockIndex = blocks.Count - 1; blockIndex >= 0; blockIndex--) { logger.Info($"Rolling back: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); var blockTxes = block.Transactions.Select((tx, txIndex) => BlockTx.Create(txIndex, tx)); chainStateBuilder.RollbackBlock(chainedHeader, blockTxes); if ((blockIndex - 1) % checkUtxoHashFrequencey == 0 || blockIndex == 0) { var expectedUtxoHash = expectedUtxoHashes.Last(); expectedUtxoHashes.RemoveAt(expectedUtxoHashes.Count - 1); using (var chainState = chainStateBuilder.ToImmutable()) Assert.AreEqual(expectedUtxoHash, UtxoCommitment.ComputeHash(chainState)); } } // verify chain state was rolled all the way back Assert.AreEqual(-1, chainStateBuilder.Chain.Height); Assert.AreEqual(0, expectedUtxoHashes.Count); // calculate utxo forward again for (var blockIndex = 0; blockIndex < blocks.Count; blockIndex++) { logger.Info($"Adding: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); chainStateBuilder.AddBlockAsync(chainedHeader, block.Transactions.Select( (tx, txIndex) => BlockTx.Create(txIndex, tx))).Wait(); } // verify final utxo state again using (var chainState = chainStateBuilder.ToImmutable()) Assert.AreEqual(expectedLastUtxoHash, UtxoCommitment.ComputeHash(chainState)); } }
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 = BlockTx.Create(0, Transaction.Create(0, ImmutableArray.Create <TxInput>(), ImmutableArray.Create <TxOutput>(), 0)); var emptyCoinbaseTx1 = BlockTx.Create(1, Transaction.Create(1, ImmutableArray.Create <TxInput>(), ImmutableArray.Create <TxOutput>(), 0)); var emptyCoinbaseTx2 = BlockTx.Create(2, Transaction.Create(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, 0, false, 3, OutputState.Unspent); var txOutput1Key = new TxOutputKey(txHash, 0); var txOutput1 = new TxOutput(0, ImmutableArray <byte> .Empty); var txOutput2Key = new TxOutputKey(txHash, 1); var txOutput2 = new TxOutput(1, ImmutableArray <byte> .Empty); var txOutput3Key = new TxOutputKey(txHash, 2); var txOutput3 = new TxOutput(2, ImmutableArray <byte> .Empty); // prepare unspent output var unspentTransactions = ImmutableDictionary.Create <UInt256, UnspentTx>().Add(txHash, unspentTx); // add the unspent transaction memoryChainStateCursor.TryAddUnspentTx(unspentTx); memoryChainStateCursor.TryAddUnspentTxOutput(txOutput1Key, txOutput1); memoryChainStateCursor.TryAddUnspentTxOutput(txOutput2Key, txOutput2); memoryChainStateCursor.TryAddUnspentTxOutput(txOutput3Key, txOutput3); // create an input to spend the unspent transaction's first output var input0 = new TxInput(txHash, 0, ImmutableArray.Create <byte>(), 0); var tx0 = BlockTx.Create(1, Transaction.Create(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; TxOutput actualTxOutput; 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); Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTxOutput(txOutput1Key, out actualTxOutput)); Assert.AreEqual(txOutput1, actualTxOutput); Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTxOutput(txOutput2Key, out actualTxOutput)); Assert.AreEqual(txOutput2, actualTxOutput); Assert.IsTrue(memoryChainStateCursor.TryGetUnspentTxOutput(txOutput3Key, out actualTxOutput)); Assert.AreEqual(txOutput3, actualTxOutput); // create an input to spend the unspent transaction's second output var input1 = new TxInput(txHash, 1, ImmutableArray.Create <byte>(), 0); var tx1 = BlockTx.Create(1, Transaction.Create(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(txHash, 2, ImmutableArray.Create <byte>(), 0); var tx2 = BlockTx.Create(2, Transaction.Create(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); }
public void TestUtxoLookAheadOrdering() { var deferredCursor = new Mock <IDeferredChainStateCursor>(); deferredCursor.Setup(x => x.CursorCount).Returns(4); var blockTxes = new BufferBlock <DecodedBlockTx>(); var lookAhead = UtxoLookAhead.LookAhead(blockTxes, deferredCursor.Object); var blockTx0 = BlockTx.Create(0, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 })); var blockTx1 = BlockTx.Create(1, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 })); var blockTx2 = BlockTx.Create(2, RandomData.RandomTransaction(new RandomDataOptions { TxInputCount = 2 })); var blockTx3 = BlockTx.Create(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].PrevTxOutputKey.TxHash)) .Callback(() => { blockTx3ReadEvent.Wait(); blockTx1ReadEvent.Set(); }); deferredCursor.Setup(x => x.WarmUnspentTx(blockTx2.Transaction.Inputs[0].PrevTxOutputKey.TxHash)) .Callback(() => { blockTx3ReadEvent.Wait(); blockTx1ReadEvent.Wait(); blockTx2ReadEvent.Set(); }); deferredCursor.Setup(x => x.WarmUnspentTx(blockTx3.Transaction.Inputs[0].PrevTxOutputKey.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.Hash).ToList(), warmedTxes.Select(x => x.Hash).ToList()); } Assert.IsTrue(lookAhead.Completion.Wait(2000)); }
public void TestReadOneLoadingTx() { var coreStorageMock = new Mock <ICoreStorage>(); // create a fake transaction with 4 inputs var prevTxCount = 4; var txIndex = 1; var chainedHeader = RandomData.RandomChainedHeader(); // create previous transactions for 4 inputs var prevTxes = new Transaction[prevTxCount]; var inputs = new TxInput[prevTxCount]; for (var i = 0; i < prevTxCount; i++) { var prevTx = RandomData.RandomTransaction(); var prevBlockTx = (BlockTx)BlockTx.Create(i, prevTx); prevTxes[i] = prevTx; inputs[i] = new TxInput(prevTx.Hash, 0, ImmutableArray.Create <byte>(), 0); // mock retrieval of the previous transaction coreStorageMock.Setup(coreStorage => coreStorage.TryGetTransaction(UInt256.Zero, i, out prevBlockTx)).Returns(true); } // create a loading tx with the 4 inputs referencing block hash 0 var tx = RandomData.RandomTransaction(new RandomDataOptions { TxOutputCount = 1 }) .CreateWith(Inputs: inputs.ToImmutableArray()).Transaction; var prevOutputTxKeys = ImmutableArray.CreateRange( Enumerable.Range(0, prevTxCount).Select(x => new TxLookupKey(UInt256.Zero, x))); var loadingTx = new LoadingTx(txIndex, tx, chainedHeader, prevOutputTxKeys); // begin queuing transactions to load var loadingTxes = new BufferBlock <LoadingTx>(); loadingTxes.Post(loadingTx); loadingTxes.Complete(); // begin transaction loading var txLoader = TxLoader.LoadTxes(coreStorageMock.Object, loadingTxes); // verify the loaded transaction var loadedTxesBuffer = new BufferBlock <LoadedTx>(); txLoader.LinkTo(loadedTxesBuffer, new DataflowLinkOptions { PropagateCompletion = true }); txLoader.Completion.Wait(); IList <LoadedTx> actualLoadedTxes; Assert.IsTrue(loadedTxesBuffer.TryReceiveAll(out actualLoadedTxes)); var actualLoadedTx = actualLoadedTxes.Single(); Assert.AreEqual(loadingTx.TxIndex, actualLoadedTx.TxIndex); Assert.AreEqual(loadingTx.Transaction, actualLoadedTx.Transaction); CollectionAssert.AreEqual(prevTxes.Select(x => x.Hash).ToArray(), actualLoadedTx.InputTxes.Select(x => x.Hash).ToArray()); }