Exemplo n.º 1
0
        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());
            }
        }
Exemplo n.º 2
0
        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());
        }
Exemplo n.º 3
0
        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));
        }
Exemplo n.º 4
0
        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));
        }
Exemplo n.º 5
0
        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();
        }
Exemplo n.º 6
0
        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);
        }
Exemplo n.º 7
0
        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);
        }