public void TestReadByBlock() { var spentTx0_0 = new SpentTx((UInt256)0, 0, 1, 0); var spentTx1_0 = new SpentTx((UInt256)1, 1, 0, 0); var spentTx1_1 = new SpentTx((UInt256)2, 1, 1, 0); var spentTx2_0 = new SpentTx((UInt256)3, 2, 0, 0); var builder = new BlockSpentTxesBuilder(); builder.AddSpentTx(spentTx1_0); builder.AddSpentTx(spentTx2_0); builder.AddSpentTx(spentTx1_1); builder.AddSpentTx(spentTx0_0); var spentTxes = builder.ToImmutable(); var expectedByBlock = new[] { Tuple.Create(0, (IImmutableList<SpentTx>)ImmutableList.Create(spentTx0_0)), Tuple.Create(1, (IImmutableList<SpentTx>)ImmutableList.Create(spentTx1_0, spentTx1_1)), Tuple.Create(2, (IImmutableList<SpentTx>)ImmutableList.Create(spentTx2_0)), }.ToList(); var actualByBlock = spentTxes.ReadByBlock().ToList(); Assert.AreEqual(expectedByBlock.Count, actualByBlock.Count); for (var i = 0; i < actualByBlock.Count; i++) { Assert.AreEqual(expectedByBlock[i].Item1, actualByBlock[i].Item1); CollectionAssert.AreEqual(expectedByBlock[i].Item2.ToList(), actualByBlock[i].Item2.ToList()); } }
public void TestBlockSpentTxes() { var spentTx0_0 = new SpentTx((UInt256)0, 0, 1, 0); var spentTx1_0 = new SpentTx((UInt256)1, 1, 0, 0); var spentTx1_1 = new SpentTx((UInt256)2, 1, 1, 0); var spentTx2_0 = new SpentTx((UInt256)3, 2, 0, 0); var builder = new BlockSpentTxesBuilder(); builder.AddSpentTx(spentTx1_0); builder.AddSpentTx(spentTx2_0); builder.AddSpentTx(spentTx1_1); builder.AddSpentTx(spentTx0_0); var spentTxes = builder.ToImmutable(); CollectionAssert.AreEqual(new[] { spentTx0_0, spentTx1_0, spentTx1_1, spentTx2_0 }, spentTxes.ToList()); }
public ISourceBlock <ValidatableTx> CalculateUtxo(IChainStateCursor chainStateCursor, Chain chain, ISourceBlock <DecodedBlockTx> blockTxes, CancellationToken cancelToken = default(CancellationToken)) { var chainedHeader = chain.LastBlock; var blockSpentTxes = new BlockSpentTxesBuilder(); var utxoCalculator = new TransformBlock <DecodedBlockTx, ValidatableTx>( blockTx => { var tx = blockTx.Transaction; var txIndex = blockTx.Index; var prevTxOutputs = ImmutableArray.CreateBuilder <PrevTxOutput>(!blockTx.IsCoinbase ? tx.Inputs.Length : 0); //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 if (!blockTx.IsCoinbase) { // spend each of the transaction's inputs in the utxo for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevTxOutput = this.Spend(chainStateCursor, txIndex, tx, inputIndex, input, chainedHeader, blockSpentTxes); prevTxOutputs.Add(prevTxOutput); } } // there exist two duplicate coinbases in the blockchain, which the design assumes to be impossible // ignore the first occurrences of these duplicates so that they do not need to later be deleted from the utxo, an unsupported operation // no other duplicates will occur again, it is now disallowed var isDupeCoinbase = IsDupeCoinbase(chainedHeader, tx); // add transaction's outputs to utxo, except for the genesis block and the duplicate coinbases if (chainedHeader.Height > 0 && !isDupeCoinbase) { // mint the transaction's outputs in the utxo this.Mint(chainStateCursor, tx, txIndex, chainedHeader); // increase unspent output count chainStateCursor.UnspentOutputCount += tx.Outputs.Length; // increment unspent tx count chainStateCursor.UnspentTxCount++; chainStateCursor.TotalTxCount++; chainStateCursor.TotalInputCount += tx.Inputs.Length; chainStateCursor.TotalOutputCount += tx.Outputs.Length; } return(new ValidatableTx(blockTx, chainedHeader, prevTxOutputs.MoveToImmutable())); }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken }); blockTxes.LinkTo(utxoCalculator, new DataflowLinkOptions { PropagateCompletion = true }); return(OnCompleteBlock.Create(utxoCalculator, () => { if (!chainStateCursor.TryAddBlockSpentTxes(chainedHeader.Height, blockSpentTxes.ToImmutable())) { throw new ValidationException(chainedHeader.Hash); } }, cancelToken)); }
private PrevTxOutput Spend(IChainStateCursor chainStateCursor, int txIndex, Transaction tx, int inputIndex, TxInput input, ChainedHeader chainedHeader, BlockSpentTxesBuilder blockSpentTxes) { UnspentTx unspentTx; if (!chainStateCursor.TryGetUnspentTx(input.PrevTxOutputKey.TxHash, out unspentTx)) { // output wasn't present in utxo, invalid block throw new ValidationException(chainedHeader.Hash); } var outputIndex = unchecked ((int)input.PrevTxOutputKey.TxOutputIndex); if (outputIndex < 0 || outputIndex >= unspentTx.OutputStates.Length) { // output was out of bounds throw new ValidationException(chainedHeader.Hash); } if (unspentTx.OutputStates[outputIndex] == OutputState.Spent) { // output was already spent throw new ValidationException(chainedHeader.Hash); } // update output states unspentTx = unspentTx.SetOutputState(outputIndex, OutputState.Spent); // decrement unspent output count chainStateCursor.UnspentOutputCount--; // update transaction output states in the utxo var wasUpdated = chainStateCursor.TryUpdateUnspentTx(unspentTx); if (!wasUpdated) { throw new ValidationException(chainedHeader.Hash); } // store pruning information for a fully spent transaction if (unspentTx.IsFullySpent) { blockSpentTxes.AddSpentTx(unspentTx.ToSpentTx()); // decrement unspent tx count chainStateCursor.UnspentTxCount--; } TxOutput txOutput; if (!chainStateCursor.TryGetUnspentTxOutput(input.PrevTxOutputKey, out txOutput)) { // output missing throw new ValidationException(chainedHeader.Hash); } return(new PrevTxOutput(txOutput, unspentTx)); }
public static BlockSpentTxes DecodeBlockSpentTxes(byte[] buffer, ref int offset) { var count = buffer.ReadVarInt(ref offset).ToIntChecked(); var blockSpentTxesBuilder = new BlockSpentTxesBuilder(); for (var i = 0; i < count; i++) { var spentTx = DecodeSpentTx(buffer, ref offset); blockSpentTxesBuilder.AddSpentTx(spentTx); } return blockSpentTxesBuilder.ToImmutable(); }
public ISourceBlock<ValidatableTx> CalculateUtxo(IChainStateCursor chainStateCursor, Chain chain, ISourceBlock<DecodedBlockTx> blockTxes, CancellationToken cancelToken = default(CancellationToken)) { var chainedHeader = chain.LastBlock; var blockSpentTxes = new BlockSpentTxesBuilder(); var utxoCalculator = new TransformBlock<DecodedBlockTx, ValidatableTx>( blockTx => { var tx = blockTx.Transaction; var txIndex = blockTx.Index; var prevTxOutputs = ImmutableArray.CreateBuilder<PrevTxOutput>(!blockTx.IsCoinbase ? tx.Inputs.Length : 0); //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 if (!blockTx.IsCoinbase) { // spend each of the transaction's inputs in the utxo for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevTxOutput = this.Spend(chainStateCursor, txIndex, tx, inputIndex, input, chainedHeader, blockSpentTxes); prevTxOutputs.Add(prevTxOutput); } } // there exist two duplicate coinbases in the blockchain, which the design assumes to be impossible // ignore the first occurrences of these duplicates so that they do not need to later be deleted from the utxo, an unsupported operation // no other duplicates will occur again, it is now disallowed var isDupeCoinbase = IsDupeCoinbase(chainedHeader, tx); // add transaction's outputs to utxo, except for the genesis block and the duplicate coinbases if (chainedHeader.Height > 0 && !isDupeCoinbase) { // mint the transaction's outputs in the utxo this.Mint(chainStateCursor, tx, txIndex, chainedHeader); // increase unspent output count chainStateCursor.UnspentOutputCount += tx.Outputs.Length; // increment unspent tx count chainStateCursor.UnspentTxCount++; chainStateCursor.TotalTxCount++; chainStateCursor.TotalInputCount += tx.Inputs.Length; chainStateCursor.TotalOutputCount += tx.Outputs.Length; } return new ValidatableTx(blockTx, chainedHeader, prevTxOutputs.MoveToImmutable()); }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken }); blockTxes.LinkTo(utxoCalculator, new DataflowLinkOptions { PropagateCompletion = true }); return OnCompleteBlock.Create(utxoCalculator, () => { if (!chainStateCursor.TryAddBlockSpentTxes(chainedHeader.Height, blockSpentTxes.ToImmutable())) throw new ValidationException(chainedHeader.Hash); }, cancelToken); }
private PrevTxOutput Spend(IChainStateCursor chainStateCursor, int txIndex, Transaction tx, int inputIndex, TxInput input, ChainedHeader chainedHeader, BlockSpentTxesBuilder blockSpentTxes) { UnspentTx unspentTx; if (!chainStateCursor.TryGetUnspentTx(input.PrevTxOutputKey.TxHash, out unspentTx)) { // output wasn't present in utxo, invalid block throw new ValidationException(chainedHeader.Hash); } var outputIndex = unchecked((int)input.PrevTxOutputKey.TxOutputIndex); if (outputIndex < 0 || outputIndex >= unspentTx.OutputStates.Length) { // output was out of bounds throw new ValidationException(chainedHeader.Hash); } if (unspentTx.OutputStates[outputIndex] == OutputState.Spent) { // output was already spent throw new ValidationException(chainedHeader.Hash); } // update output states unspentTx = unspentTx.SetOutputState(outputIndex, OutputState.Spent); // decrement unspent output count chainStateCursor.UnspentOutputCount--; // update transaction output states in the utxo var wasUpdated = chainStateCursor.TryUpdateUnspentTx(unspentTx); if (!wasUpdated) throw new ValidationException(chainedHeader.Hash); // store pruning information for a fully spent transaction if (unspentTx.IsFullySpent) { blockSpentTxes.AddSpentTx(unspentTx.ToSpentTx()); // decrement unspent tx count chainStateCursor.UnspentTxCount--; } TxOutput txOutput; if (!chainStateCursor.TryGetUnspentTxOutput(input.PrevTxOutputKey, out txOutput)) // output missing throw new ValidationException(chainedHeader.Hash); return new PrevTxOutput(txOutput, unspentTx); }