示例#1
0
        public MemoryBlockchain(Block? genesisBlock = null)
        {
            this.shutdownToken = new CancellationToken();
            this.random = new Random();

            // create the key pair that block rewards will be sent to
            var keyPair = TransactionManager.CreateKeyPair();
            this._coinbasePrivateKey = keyPair.Item1;
            this._coinbasePublicKey = keyPair.Item2;

            // initialize unit test storage
            this._storageContext = new MemoryStorageContext();
            this._cacheContext = new CacheContext(this._storageContext);

            // initialize unit test rules
            this._rules = new UnitTestRules(this._cacheContext);

            // initialize blockchain calculator
            this._calculator = new BlockchainCalculator(this._rules, this._cacheContext, this.shutdownToken);

            // create and mine the genesis block
            this._genesisBlock = genesisBlock ?? MineEmptyBlock(0);

            // update genesis blockchain and add to storage
            this._rules.SetGenesisBlock(this._genesisBlock);
            this._currentBlockchain = this._rules.GenesisBlockchain;
            this._genesisChainedBlock = AddBlock(this._genesisBlock, null).Item2;
        }
示例#2
0
        public void SetGenesisBlock(Block genesisBlock)
        {
            this._genesisBlock = genesisBlock;

            this._genesisChainedBlock =
                new ChainedBlock
                (
                    blockHash: this._genesisBlock.Hash,
                    previousBlockHash: this._genesisBlock.Header.PreviousBlock,
                    height: 0,
                    totalWork: this._genesisBlock.Header.CalculateWork()
                );

            this._genesisBlockchain =
                new Data.Blockchain
                (
                    blockList: ImmutableList.Create(this._genesisChainedBlock),
                    blockListHashes: ImmutableHashSet.Create(this._genesisBlock.Hash),
                    utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() // genesis block coinbase is not included in utxo, it is unspendable
                );
        }
示例#3
0
        public Block CreateEmptyBlock(UInt256 previousBlockHash)
        {
            var coinbaseTx = new Transaction
            (
                version: 0,
                inputs: ImmutableArray.Create
                (
                    new TxInput
                    (
                        previousTxOutputKey: new TxOutputKey
                        (
                            txHash: 0,
                            txOutputIndex: 0
                        ),
                        scriptSignature: ImmutableArray.Create(random.NextBytes(100)),
                        sequence: 0
                    )
                ),
                outputs: ImmutableArray.Create
                (
                    new TxOutput
                    (
                        value: 50 * SATOSHI_PER_BTC,
                        scriptPublicKey: ImmutableArray.Create(TransactionManager.CreatePublicKeyScript(_coinbasePublicKey))
                    )
                ),
                lockTime: 0
            );

            //Debug.WriteLine("Coinbase Tx Created: {0}".Format2(coinbaseTx.Hash.ToHexNumberString()));

            var transactions = ImmutableArray.Create(coinbaseTx);
            var merkleRoot = DataCalculator.CalculateMerkleRoot(transactions);

            var block = new Block
            (
                header: new BlockHeader
                (
                    version: 0,
                    previousBlock: previousBlockHash,
                    merkleRoot: merkleRoot,
                    time: 0,
                    bits: DataCalculator.TargetToBits(this._rules.HighestTarget),
                    nonce: 0
                ),
                transactions: transactions
            );

            return block;
        }
示例#4
0
        public Tuple<Block, ChainedBlock> AddBlock(Block block, ChainedBlock? prevChainedBlock)
        {
            if (prevChainedBlock != null)
                Assert.AreEqual(block.Header.PreviousBlock, prevChainedBlock.Value.BlockHash);

            var chainedBlock = new ChainedBlock
            (
                block.Hash,
                block.Header.PreviousBlock,
                prevChainedBlock != null ? prevChainedBlock.Value.Height + 1 : 0,
                prevChainedBlock != null ? prevChainedBlock.Value.TotalWork + block.Header.CalculateWork() : block.Header.CalculateWork()
            );

            this.CacheContext.BlockCache.CreateValue(block.Hash, block);

            this.CacheContext.ChainedBlockCache.CreateValue(block.Hash, chainedBlock);

            ChooseNewWinner();

            return Tuple.Create(block, chainedBlock);
        }
示例#5
0
 public static byte[] EncodeBlock(Block block)
 {
     var stream = new MemoryStream();
     EncodeBlock(stream, block);
     return stream.ToArray();
 }
示例#6
0
        public void RevalidateBlockchain(Data.Blockchain blockchain, Block genesisBlock)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            try
            {
                //TODO delete corrupted data? could get stuck in a fail-loop on the winning chain otherwise

                // verify blockchain has blocks
                if (blockchain.BlockList.Count == 0)
                    throw new ValidationException();

                // verify genesis block hash
                if (blockchain.BlockList[0].BlockHash != genesisBlock.Hash)
                    throw new ValidationException();

                // get genesis block header
                var chainGenesisBlockHeader = this.CacheContext.GetBlockHeader(blockchain.BlockList[0].BlockHash);

                // verify genesis block header
                if (
                    genesisBlock.Header.Version != chainGenesisBlockHeader.Version
                    || genesisBlock.Header.PreviousBlock != chainGenesisBlockHeader.PreviousBlock
                    || genesisBlock.Header.MerkleRoot != chainGenesisBlockHeader.MerkleRoot
                    || genesisBlock.Header.Time != chainGenesisBlockHeader.Time
                    || genesisBlock.Header.Bits != chainGenesisBlockHeader.Bits
                    || genesisBlock.Header.Nonce != chainGenesisBlockHeader.Nonce
                    || genesisBlock.Hash != chainGenesisBlockHeader.Hash
                    || genesisBlock.Hash != CalculateHash(chainGenesisBlockHeader))
                {
                    throw new ValidationException();
                }

                // setup expected previous block hash value to verify each chain actually does link
                var expectedPreviousBlockHash = genesisBlock.Header.PreviousBlock;
                for (var height = 0; height < blockchain.BlockList.Count; height++)
                {
                    // cooperative loop
                    this.shutdownToken.ThrowIfCancellationRequested();

                    // get the current link in the chain
                    var chainedBlock = blockchain.BlockList[height];

                    // verify height
                    if (chainedBlock.Height != height)
                        throw new ValidationException();

                    // verify blockchain linking
                    if (chainedBlock.PreviousBlockHash != expectedPreviousBlockHash)
                        throw new ValidationException();

                    // verify block exists
                    var blockHeader = this.CacheContext.GetBlockHeader(chainedBlock.BlockHash);

                    // verify block metadata matches header values
                    if (blockHeader.PreviousBlock != chainedBlock.PreviousBlockHash)
                        throw new ValidationException();

                    // verify block header hash
                    if (CalculateHash(blockHeader) != chainedBlock.BlockHash)
                        throw new ValidationException();

                    // next block metadata should have the current metadata's hash as its previous hash value
                    expectedPreviousBlockHash = chainedBlock.BlockHash;
                }

                // all validation passed
            }
            finally
            {
                stopwatch.Stop();
                Debug.WriteLine("Blockchain revalidation: {0:#,##0.000000}s".Format2(stopwatch.ElapsedSecondsFloat()));
            }
        }
示例#7
0
        private ImmutableDictionary<UInt256, UnspentTx> CalculateUtxo(long blockHeight, Block block, ImmutableDictionary<UInt256, UnspentTx> currentUtxo, out ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions, out long txCount, out long inputCount)
        {
            txCount = 0;
            inputCount = 0;

            // create builder for new utxo
            var newUtxoBuilder = currentUtxo.ToBuilder();
            var newTransactionsBuilder = ImmutableDictionary.CreateBuilder<UInt256, ImmutableHashSet<int>>();

            // don't include genesis block coinbase in utxo
            if (blockHeight > 0)
            {
                //TODO apply real coinbase rule
                // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219
                var coinbaseTx = block.Transactions[0];

                // add the coinbase outputs to the utxo
                var coinbaseUnspentTx = new UnspentTx(block.Hash, 0, coinbaseTx.Hash, new ImmutableBitArray(coinbaseTx.Outputs.Length, true));

                // add transaction output to to the utxo
                if (newUtxoBuilder.ContainsKey(coinbaseTx.Hash))
                {
                    // duplicate transaction output
                    Debug.WriteLine("Duplicate transaction at block {0:#,##0}, {1}, coinbase".Format2(blockHeight, block.Hash.ToHexNumberString()));

                    if ((blockHeight == 91842 && coinbaseTx.Hash == UInt256.Parse("d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599", NumberStyles.HexNumber))
                        || (blockHeight == 91880 && coinbaseTx.Hash == UInt256.Parse("e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468", NumberStyles.HexNumber)))
                    {
                        newUtxoBuilder.Remove(coinbaseTx.Hash);
                    }
                    else
                    {
                        throw new ValidationException();
                    }
                }

                newTransactionsBuilder.Add(coinbaseTx.Hash, ImmutableHashSet.Create(0));
                newUtxoBuilder.Add(coinbaseTx.Hash, coinbaseUnspentTx);
            }

            // check for double spends
            for (var txIndex = 1; txIndex < block.Transactions.Length; txIndex++)
            {
                var tx = block.Transactions[txIndex];
                txCount++;

                for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
                {
                    var input = tx.Inputs[inputIndex];
                    inputCount++;

                    if (!newUtxoBuilder.ContainsKey(input.PreviousTxOutputKey.TxHash))
                    {
                        // output wasn't present in utxo, invalid block
                        throw new ValidationException();
                    }

                    var prevUnspentTx = newUtxoBuilder[input.PreviousTxOutputKey.TxHash];

                    if (input.PreviousTxOutputKey.TxOutputIndex >= prevUnspentTx.UnspentOutputs.Length)
                    {
                        // output was out of bounds
                        throw new ValidationException();
                    }

                    if (!prevUnspentTx.UnspentOutputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()])
                    {                        // output was already spent
                        throw new ValidationException();
                    }


                    // remove the output from the utxo
                    newUtxoBuilder[input.PreviousTxOutputKey.TxHash] =
                        new UnspentTx(prevUnspentTx.BlockHash, prevUnspentTx.TxIndex, prevUnspentTx.TxHash,
                            prevUnspentTx.UnspentOutputs.Set(input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked(), false));

                    // remove fully spent transaction from the utxo
                    if (newUtxoBuilder[input.PreviousTxOutputKey.TxHash].UnspentOutputs.All(x => !x))
                        newUtxoBuilder.Remove(input.PreviousTxOutputKey.TxHash);
                }

                // add the output to the list to be added to the utxo
                var unspentTx = new UnspentTx(block.Hash, (UInt32)txIndex, tx.Hash, new ImmutableBitArray(tx.Outputs.Length, true));

                // add transaction output to to the utxo
                if (newUtxoBuilder.ContainsKey(tx.Hash))
                {
                    // duplicate transaction output
                    Debug.WriteLine("Duplicate transaction at block {0:#,##0}, {1}, tx {2}".Format2(blockHeight, block.Hash.ToHexNumberString(), txIndex));
                    //Debugger.Break();
                    //TODO throw new Validation();

                    //TODO this needs to be tracked so that blocks can be rolled back accurately
                    //TODO track these separately on the blockchain info? gonna be costly to track on every transaction
                }

                if (!newTransactionsBuilder.ContainsKey(tx.Hash))
                    newTransactionsBuilder.Add(tx.Hash, ImmutableHashSet.Create(txIndex));
                else
                    newTransactionsBuilder[tx.Hash] = newTransactionsBuilder[tx.Hash].Add(txIndex);
                newUtxoBuilder.Add(tx.Hash, unspentTx);
            }

            // validation successful, return the new utxo
            newTransactions = newTransactionsBuilder.ToImmutable();
            return newUtxoBuilder.ToImmutable();
        }
示例#8
0
 public Data.Blockchain RollbackBlockchain(Data.Blockchain blockchain, Block block)
 {
     List<TxOutputKey> spendOutputs, receiveOutputs;
     return RollbackBlockchain(blockchain, block, out spendOutputs, out receiveOutputs);
 }
示例#9
0
 public Block MineEmptyBlock(Block previousBlock)
 {
     return MineEmptyBlock(previousBlock.Hash);
 }
示例#10
0
        public virtual void ValidateBlock(Block block, Data.Blockchain blockchain, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions/*, ImmutableDictionary<UInt256, Transaction> transactions*/)
        {
            //TODO
            if (BypassValidation)
                return;

            // calculate the next required target
            var requiredTarget = GetRequiredNextTarget(blockchain);

            // validate block's target against the required target
            var blockTarget = block.Header.CalculateTarget();
            if (blockTarget > requiredTarget)
            {
                throw new ValidationException("Failing block {0} at height {1}: Block target {2} did not match required target of {3}".Format2(block.Hash.ToHexNumberString(), blockchain.Height, blockTarget.ToHexNumberString(), requiredTarget.ToHexNumberString()));
            }

            // validate block's proof of work against its stated target
            if (block.Hash > blockTarget || block.Hash > requiredTarget)
            {
                throw new ValidationException("Failing block {0} at height {1}: Block did not match its own target of {2}".Format2(block.Hash.ToHexNumberString(), blockchain.Height, blockTarget.ToHexNumberString()));
            }

            // ensure there is at least 1 transaction
            if (block.Transactions.Length == 0)
            {
                throw new ValidationException("Failing block {0} at height {1}: Zero transactions present".Format2(block.Hash.ToHexNumberString(), blockchain.Height));
            }

            //TODO apply real coinbase rule
            // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219
            var coinbaseTx = block.Transactions[0];

            // check that coinbase has only one input
            if (coinbaseTx.Inputs.Length != 1)
            {
                throw new ValidationException("Failing block {0} at height {1}: Coinbase transaction does not have exactly one input".Format2(block.Hash.ToHexNumberString(), blockchain.Height));
            }

            // validate transactions in parallel
            long unspentValue = 0L;
            try
            {
                Parallel.For(1, block.Transactions.Length, (txIndex, loopState) =>
                {
                    var tx = block.Transactions[txIndex];

                    long unspentValueInner;
                    //TODO utxo will not be correct at transaction if a tx hash is reused within the same block
                    ValidateTransaction(blockchain.Height, block, tx, txIndex, utxo, newTransactions, out unspentValueInner);

                    Interlocked.Add(ref unspentValue, unspentValueInner);
                });
            }
            catch (AggregateException e)
            {
                var validationException = e.InnerExceptions.FirstOrDefault(x => x is ValidationException);
                var missingDataException = e.InnerExceptions.FirstOrDefault(x => x is MissingDataException);

                if (validationException != null)
                    throw validationException;

                if (missingDataException != null)
                    throw missingDataException;

                throw;
            }

            //TODO utxo will not be correct at transaction if a tx hash is reused within the same block
            ValidateTransactionScripts(block, utxo, newTransactions);

            // calculate the expected reward in coinbase
            var expectedReward = (long)(50 * SATOSHI_PER_BTC);
            if (blockchain.Height / 210000 <= 32)
                expectedReward /= (long)Math.Pow(2, blockchain.Height / 210000);
            expectedReward += unspentValue;

            // calculate the actual reward in coinbase
            var actualReward = 0L;
            foreach (var txOutput in coinbaseTx.Outputs)
                actualReward += (long)txOutput.Value;

            // ensure coinbase has correct reward
            if (actualReward > expectedReward)
            {
                throw new ValidationException("Failing block {0} at height {1}: Coinbase value is greater than reward + fees".Format2(block.Hash.ToHexNumberString(), blockchain.Height));
            }

            // all validation has passed
        }
示例#11
0
 private void OnBlockModification(UInt256 blockHash, Block block)
 {
     OnBlockAddition(blockHash);
 }
示例#12
0
 public bool TryGetBlock(UInt256 blockHash, out Block block, bool saveInCache = true)
 {
     if (this.CacheContext.BlockCache.TryGetValue(blockHash, out block, saveInCache))
     {
         this.missingBlocks.Remove(blockHash);
         return true;
     }
     else
     {
         this.missingBlocks.Add(blockHash);
         block = default(Block);
         return false;
     }
 }
示例#13
0
        public Testnet2Rules(CacheContext cacheContext)
            : base(cacheContext)
        {
            this._genesisBlock =
                new Block
                (
                    header: new BlockHeader
                    (
                        version: 1,
                        previousBlock: 0,
                        merkleRoot: UInt256.Parse("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", NumberStyles.HexNumber),
                        time: 1296688602,
                        bits: 0x207FFFFF,
                        nonce: 2
                    ),
                    transactions: ImmutableArray.Create
                    (
                        new Transaction
                        (
                            version: 1,
                            inputs: ImmutableArray.Create
                            (
                                new TxInput
                                (
                                    previousTxOutputKey: new TxOutputKey
                                    (
                                        txHash: 0,
                                        txOutputIndex: 0xFFFFFFFF
                                    ),
                                    scriptSignature: ImmutableArray.Create<byte>
                                    (
                                        0x04, 0xFF, 0xFF, 0x00, 0x1D, 0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6D, 0x65,
                                        0x73, 0x20, 0x30, 0x33, 0x2F, 0x4A, 0x61, 0x6E, 0x2F, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68,
                                        0x61, 0x6E, 0x63, 0x65, 0x6C, 0x6C, 0x6F, 0x72, 0x20, 0x6F, 0x6E, 0x20, 0x62, 0x72, 0x69, 0x6E,
                                        0x6B, 0x20, 0x6F, 0x66, 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6C,
                                        0x6F, 0x75, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x62, 0x61, 0x6E, 0x6B, 0x73
                                    ),
                                    sequence: 0xFFFFFFFF
                                )
                            ),
                            outputs: ImmutableArray.Create
                            (
                                new TxOutput
                                (
                                    value: (UInt64)(50L * 100.MILLION()),
                                    scriptPublicKey: ImmutableArray.Create<byte>
                                    (
                                        0x41, 0x04, 0x67, 0x8A, 0xFD, 0xB0, 0xFE, 0x55, 0x48, 0x27, 0x19, 0x67, 0xF1, 0xA6, 0x71, 0x30,
                                        0xB7, 0x10, 0x5C, 0xD6, 0xA8, 0x28, 0xE0, 0x39, 0x09, 0xA6, 0x79, 0x62, 0xE0, 0xEA, 0x1F, 0x61,
                                        0xDE, 0xB6, 0x49, 0xF6, 0xBC, 0x3F, 0x4C, 0xEF, 0x38, 0xC4, 0xF3, 0x55, 0x04, 0xE5, 0x1E, 0xC1,
                                        0x12, 0xDE, 0x5C, 0x38, 0x4D, 0xF7, 0xBA, 0x0B, 0x8D, 0x57, 0x8A, 0x4C, 0x70, 0x2B, 0x6B, 0xF1,
                                        0x1D, 0x5F, 0xAC
                                    )
                                )
                            ),
                            lockTime: 0
                        )
                    )
                );

            Debug.Assert(_genesisBlock.Hash == UInt256.Parse("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206", NumberStyles.HexNumber));

            this._genesisChainedBlock =
                new ChainedBlock
                (
                    blockHash: this._genesisBlock.Hash,
                    previousBlockHash: this._genesisBlock.Header.PreviousBlock,
                    height: 0,
                    totalWork: this._genesisBlock.Header.CalculateWork()
                );

            this._genesisBlockchain =
                new Data.Blockchain
                (
                    blockList: ImmutableList.Create(this._genesisChainedBlock),
                    blockListHashes: ImmutableHashSet.Create(this._genesisBlock.Hash),
                    utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() // genesis block coinbase is not included in utxo, it is unspendable
                );
        }
示例#14
0
 public static void GetTransaction(BlockProvider blockProvider, int blockIndex, int txIndex, out Block block, out Transaction tx)
 {
     block = blockProvider.GetBlock(blockIndex);
     tx = block.Transactions[txIndex];
 }
示例#15
0
        public static void GetFirstTransaction(BlockProvider blockProvider, out Block block, out Transaction tx, out IDictionary<UInt256, Transaction> txLookup)
        {
            txLookup = new Dictionary<UInt256, Transaction>();

            // prior outputs for first transaction
            GetTransaction(blockProvider, 9, 0, out block, out tx);
            txLookup.Add(tx.Hash, tx);

            // first transaction
            // do this last so its output is what is returned
            GetTransaction(blockProvider, 170, 1, out block, out tx);
            txLookup.Add(tx.Hash, tx);
        }
示例#16
0
        public Tuple<Block, ChainedBlock> MineAndAddBlock(Block block, ChainedBlock? prevChainedBlock)
        {
            var minedHeader = Miner.MineBlockHeader(block.Header, this._rules.HighestTarget);
            if (minedHeader == null)
                Assert.Fail("No block could be mined for test data header.");

            var minedBlock = block.With(Header: minedHeader);

            return AddBlock(minedBlock, prevChainedBlock);
        }
示例#17
0
        public Block MineBlock(Block block)
        {
            var minedHeader = Miner.MineBlockHeader(block.Header, this._rules.HighestTarget);
            if (minedHeader == null)
                Assert.Fail("No block could be mined for test data header.");

            block = block.With(Header: minedHeader);

            return block;
        }
示例#18
0
        //TODO utxo needs to be as-at transaction, with regards to a transaction being fully spent and added back in in the same block
        public virtual void ValidateTransaction(long blockHeight, Block block, Transaction tx, int txIndex, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions, out long unspentValue /*, ImmutableDictionary<UInt256, Transaction> transactions*/)
        {
            unspentValue = -1;

            // lookup all previous outputs
            var prevOutputMissing = false;
            var previousOutputs = new Dictionary<TxOutputKey, Tuple<TxInput, int, TxOutput>>();
            for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
            {
                var input = tx.Inputs[inputIndex];

                // find previous transaction
                var prevTx = GetPreviousTransaction(block, txIndex, input.PreviousTxOutputKey, utxo, newTransactions);

                // find previous transaction output
                if (input.PreviousTxOutputKey.TxOutputIndex >= prevTx.Outputs.Length)
                    throw new ValidationException();
                var prevOutput = prevTx.Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()];

                previousOutputs.Add(input.PreviousTxOutputKey, Tuple.Create(input, inputIndex, prevOutput));
            }

            if (prevOutputMissing)
            {
                throw new ValidationException();
            }

            // verify spend amounts
            var txInputValue = (UInt64)0;
            var txOutputValue = (UInt64)0;

            for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
            {
                var input = tx.Inputs[inputIndex];

                // add transactions previous value to unspent amount (used to calculate allowed coinbase reward)
                var prevOutput = previousOutputs[input.PreviousTxOutputKey].Item3;
                txInputValue += prevOutput.Value;
            }

            for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++)
            {
                // remove transactions spend value from unspent amount (used to calculate allowed coinbase reward)
                var output = tx.Outputs[outputIndex];
                txOutputValue += output.Value;
            }

            // ensure that amount being output from transaction isn't greater than amount being input
            if (txOutputValue > txInputValue)
            {
                throw new ValidationException("Failing tx {0}: Transaction output value is greater than input value".Format2(tx.Hash.ToHexNumberString()));
            }

            // calculate unspent value
            unspentValue = (long)(txInputValue - txOutputValue);

            // sanity check
            if (unspentValue < 0)
            {
                throw new ValidationException();
            }

            // all validation has passed
        }
示例#19
0
        public MainnetRules(CacheContext cacheContext)
        {
            this._cacheContext = cacheContext;

            this._highestTarget = UInt256.Parse("00000000FFFF0000000000000000000000000000000000000000000000000000", NumberStyles.HexNumber);

            this._genesisBlock =
                new Block
                (
                    header: new BlockHeader
                    (
                        version: 1,
                        previousBlock: 0,
                        merkleRoot: UInt256.Parse("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", NumberStyles.HexNumber),
                        time: 1231006505,
                        bits: 486604799,
                        nonce: 2083236893
                    ),
                    transactions: ImmutableArray.Create
                    (
                        new Transaction
                        (
                            version: 1,
                            inputs: ImmutableArray.Create
                            (
                                new TxInput
                                (
                                    previousTxOutputKey: new TxOutputKey
                                    (
                                        txHash: 0,
                                        txOutputIndex: 0xFFFFFFFF
                                    ),
                                    scriptSignature: ImmutableArray.Create<byte>
                                    (
                                        0x04, 0xFF, 0xFF, 0x00, 0x1D, 0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6D, 0x65,
                                        0x73, 0x20, 0x30, 0x33, 0x2F, 0x4A, 0x61, 0x6E, 0x2F, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68,
                                        0x61, 0x6E, 0x63, 0x65, 0x6C, 0x6C, 0x6F, 0x72, 0x20, 0x6F, 0x6E, 0x20, 0x62, 0x72, 0x69, 0x6E,
                                        0x6B, 0x20, 0x6F, 0x66, 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6C,
                                        0x6F, 0x75, 0x74, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x62, 0x61, 0x6E, 0x6B, 0x73
                                    ),
                                    sequence: 0xFFFFFFFF
                                )
                            ),
                            outputs: ImmutableArray.Create
                            (
                                new TxOutput
                                (
                                    value: 50 * SATOSHI_PER_BTC,
                                    scriptPublicKey: ImmutableArray.Create<byte>
                                    (
                                        0x41, 0x04, 0x67, 0x8A, 0xFD, 0xB0, 0xFE, 0x55, 0x48, 0x27, 0x19, 0x67, 0xF1, 0xA6, 0x71, 0x30,
                                        0xB7, 0x10, 0x5C, 0xD6, 0xA8, 0x28, 0xE0, 0x39, 0x09, 0xA6, 0x79, 0x62, 0xE0, 0xEA, 0x1F, 0x61,
                                        0xDE, 0xB6, 0x49, 0xF6, 0xBC, 0x3F, 0x4C, 0xEF, 0x38, 0xC4, 0xF3, 0x55, 0x04, 0xE5, 0x1E, 0xC1,
                                        0x12, 0xDE, 0x5C, 0x38, 0x4D, 0xF7, 0xBA, 0x0B, 0x8D, 0x57, 0x8A, 0x4C, 0x70, 0x2B, 0x6B, 0xF1,
                                        0x1D, 0x5F, 0xAC
                                    )
                                )
                            ),
                            lockTime: 0
                        )
                    )
                );

            this._genesisChainedBlock =
                new ChainedBlock
                (
                    blockHash: this._genesisBlock.Hash,
                    previousBlockHash: this._genesisBlock.Header.PreviousBlock,
                    height: 0,
                    totalWork: this._genesisBlock.Header.CalculateWork()
                );

            this._genesisBlockchain =
                new Data.Blockchain
                (
                    blockList: ImmutableList.Create(this._genesisChainedBlock),
                    blockListHashes: ImmutableHashSet.Create(this._genesisBlock.Hash),
                    utxo: ImmutableDictionary.Create<UInt256, UnspentTx>() // genesis block coinbase is not included in utxo, it is unspendable
                );
        }
示例#20
0
        //TODO utxo needs to be as-at transaction, with regards to a transaction being fully spent and added back in in the same block
        public virtual void ValidateTransactionScripts(Block block, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions)
        {
            if (BypassExecuteScript)
                return;

            // lookup all previous outputs
            var prevOutputMissing = false;
            var previousOutputs = new Dictionary<TxOutputKey, Tuple<Transaction, TxInput, int, TxOutput>>();
            for (var txIndex = 1; txIndex < block.Transactions.Length; txIndex++)
            {
                var tx = block.Transactions[txIndex];
                for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
                {
                    var input = tx.Inputs[inputIndex];

                    // find previous transaction
                    var prevTx = GetPreviousTransaction(block, txIndex, input.PreviousTxOutputKey, utxo, newTransactions);

                    // find previous transaction output
                    if (input.PreviousTxOutputKey.TxOutputIndex >= prevTx.Outputs.Length)
                        throw new ValidationException();
                    var prevOutput = prevTx.Outputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()];

                    previousOutputs.Add(input.PreviousTxOutputKey, Tuple.Create(tx, input, inputIndex, prevOutput));
                }
            }

            if (prevOutputMissing)
            {
                throw new ValidationException();
            }

            var exceptions = new ConcurrentBag<Exception>();

            var scriptEngine = new ScriptEngine();

            Parallel.ForEach(previousOutputs.Values, (tuple, loopState) =>
            {
                try
                {
                    var tx = tuple.Item1;
                    var input = tuple.Item2;
                    var inputIndex = tuple.Item3;
                    var prevOutput = tuple.Item4;

                    // create the transaction script from the input and output
                    var script = input.ScriptSignature.AddRange(prevOutput.ScriptPublicKey);
                    if (!scriptEngine.VerifyScript(0 /*TODO blockHash*/, 0 /*TODO txIndex*/, prevOutput.ScriptPublicKey.ToArray(), tx, inputIndex, script.ToArray()))
                    {
                        exceptions.Add(new ValidationException());
                        loopState.Break();
                    }
                }
                catch (Exception e)
                {
                    exceptions.Add(e);
                    loopState.Break();
                }
            });

            if (exceptions.Count > 0)
                throw new AggregateException(exceptions.ToArray());
        }
示例#21
0
        public Data.Blockchain RollbackBlockchain(Data.Blockchain blockchain, Block block, out List<TxOutputKey> spendOutputs, out List<TxOutputKey> receiveOutputs)
        {
            if (blockchain.BlockCount == 0 || blockchain.RootBlockHash != block.Hash)
                throw new ValidationException();

            var newUtxo = RollbackUtxo(blockchain, block, out spendOutputs, out receiveOutputs);

            return new Data.Blockchain
            (
                blockList: blockchain.BlockList.RemoveAt(blockchain.BlockCount - 1),
                blockListHashes: blockchain.BlockListHashes.Remove(blockchain.RootBlockHash),
                utxo: newUtxo
            );
        }
示例#22
0
        private Transaction GetPreviousTransaction(Block block, int txIndex, TxOutputKey prevTxOutputKey, ImmutableDictionary<UInt256, UnspentTx> utxo, ImmutableDictionary<UInt256, ImmutableHashSet<int>> newTransactions)
        {
            if (newTransactions.ContainsKey(prevTxOutputKey.TxHash))
            {
                var eligible = newTransactions[prevTxOutputKey.TxHash].Where(x => x < txIndex).ToList();
                if (eligible.Count > 0)
                {
                    var max = eligible.Max();

                    if (max >= block.Transactions.Length)
                        throw new Exception();

                    var prevTx1 = block.Transactions[max];
                    if (prevTx1.Hash != prevTxOutputKey.TxHash)
                        throw new Exception();

                    return prevTx1;
                }
            }

            // find previous transaction
            if (!utxo.ContainsKey(prevTxOutputKey.TxHash))
                throw new MissingDataException(DataType.Transaction, prevTxOutputKey.TxHash);

            var prevTxKey = utxo[prevTxOutputKey.TxHash].ToTxKey();

            var prevTx2 = this.CacheContext.GetTransaction(prevTxKey);
            if (prevTx2.Hash != prevTxOutputKey.TxHash)
                throw new Exception();

            return prevTx2;
        }
示例#23
0
        private ImmutableDictionary<UInt256, UnspentTx> RollbackUtxo(Data.Blockchain blockchain, Block block, out List<TxOutputKey> spendOutputs, out List<TxOutputKey> receiveOutputs)
        {
            var blockHeight = blockchain.Height;
            var currentUtxo = blockchain.Utxo;

            // create builder for prev utxo
            var prevUtxoBuilder = currentUtxo.ToBuilder();
            spendOutputs = new List<TxOutputKey>();
            receiveOutputs = new List<TxOutputKey>();

            //TODO apply real coinbase rule
            // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219
            var coinbaseTx = block.Transactions[0];

            for (var outputIndex = 0; outputIndex < coinbaseTx.Outputs.Length; outputIndex++)
            {
                var txOutputKey = new TxOutputKey(coinbaseTx.Hash, (UInt32)outputIndex);
                if (blockHeight > 0)
                {
                    // remove new outputs from the rolled back utxo
                    if (prevUtxoBuilder.Remove(coinbaseTx.Hash))
                    {
                        receiveOutputs.Add(txOutputKey);
                    }
                    else
                    {
                        // missing transaction output
                        Debug.WriteLine("Missing transaction at block {0:#,##0}, {1}, tx {2}, output {3}".Format2(blockHeight, block.Hash.ToHexNumberString(), 0, outputIndex));
                        Debugger.Break();
                        //TODO throw new Validation();

                        //TODO this needs to be tracked so that blocks can be rolled back accurately
                        //TODO track these separately on the blockchain info? gonna be costly to track on every transaction
                    }
                }
            }

            for (var txIndex = block.Transactions.Length - 1; txIndex >= 1; txIndex--)
            {
                var tx = block.Transactions[txIndex];

                for (var outputIndex = tx.Outputs.Length - 1; outputIndex >= 0; outputIndex--)
                {
                    var output = tx.Outputs[outputIndex];
                    var txOutputKey = new TxOutputKey(tx.Hash, (UInt32)outputIndex);
                    //TODO what if a transaction wasn't added to the utxo because it already existed?
                    //TODO the block would still pass without adding the tx to its utxo, but here it would get rolled back
                    //TODO maybe a flag bit to track this?

                    // remove new outputs from the rolled back utxo
                    if (prevUtxoBuilder.Remove(tx.Hash))
                    {
                        receiveOutputs.Add(txOutputKey);
                    }
                    else
                    {
                        // missing transaction output
                        Debug.WriteLine("Missing transaction at block {0:#,##0}, {1}, tx {2}, output {3}".Format2(blockHeight, block.Hash.ToHexNumberString(), txIndex, outputIndex));
                        Debugger.Break();
                        //TODO throw new Validation();

                        //TODO this needs to be tracked so that blocks can be rolled back accurately
                        //TODO track these separately on the blockchain info? gonna be costly to track on every transaction
                    }
                }

                for (var inputIndex = tx.Inputs.Length - 1; inputIndex >= 0; inputIndex--)
                {
                    var input = tx.Inputs[inputIndex];

                    // add spent outputs back into the rolled back utxo
                    if (prevUtxoBuilder.ContainsKey(input.PreviousTxOutputKey.TxHash))
                    {
                        var prevUnspentTx = prevUtxoBuilder[input.PreviousTxOutputKey.TxHash];

                        // check if output is out of bounds
                        if (input.PreviousTxOutputKey.TxOutputIndex >= prevUnspentTx.UnspentOutputs.Length)
                            throw new ValidationException();

                        // check that output isn't already considered unspent
                        if (prevUnspentTx.UnspentOutputs[input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked()])
                            throw new ValidationException();

                        // mark output as unspent
                        prevUtxoBuilder[input.PreviousTxOutputKey.TxHash] =
                            new UnspentTx(prevUnspentTx.BlockHash, prevUnspentTx.TxIndex, prevUnspentTx.TxHash,
                                prevUnspentTx.UnspentOutputs.Set(input.PreviousTxOutputKey.TxOutputIndex.ToIntChecked(), true));
                    }
                    else
                    {
                        // fully spent transaction being added back in during roll back
                        //TODO
                        throw new NotImplementedException();
                    }

                    //TODO
                    //if (prevUtxoBuilder.Add(input.PreviousTxOutputKey))
                    //{
                    //    spendOutputs.Add(input.PreviousTxOutputKey);
                    //}
                    //else
                    //{
                    //    // missing transaction output
                    //    Debug.WriteLine("Duplicate transaction at block {0:#,##0}, {1}, tx {2}, input {3}".Format2(blockHeight, block.Hash.ToHexNumberString(), txIndex, inputIndex));
                    //    Debugger.Break();
                    //    //TODO throw new Validation();

                    //    //TODO this needs to be tracked so that blocks can be rolled back accurately
                    //    //TODO track these separately on the blockchain info? gonna be costly to track on every transaction
                    //}
                }
            }

            return prevUtxoBuilder.ToImmutable();
        }
示例#24
0
文件: Block.cs 项目: holinov/BitSharp
 public static long SizeEstimator(Block block)
 {
     return block.SizeEstimate;
 }
示例#25
0
        private void OnBlock(Block block)
        {
            //Debug.WriteLine("Received block {0}".Format2(block.Hash));

            this.requestedBlocks.TryRemove(block.Hash);
            this.blockchainDaemon.CacheContext.BlockCache.CreateValue(block.Hash, block);

            Tuple<DateTime, DateTime?> receiveTime;
            if (this.requestedBlockTimes.TryGetValue(block.Hash, out receiveTime))
            {
                if (receiveTime.Item2 == null)
                    this.requestedBlockTimes[block.Hash] = Tuple.Create(receiveTime.Item1, (DateTime?)DateTime.UtcNow);

                this.requestBlocksWorker.NotifyWork();
            }
        }
示例#26
0
 public static void EncodeBlock(Stream stream, Block block)
 {
     using (var writer = new BinaryWriter(stream, Encoding.ASCII, leaveOpen: true))
     {
         EncodeBlockHeader(stream, block.Header);
         writer.EncodeList(block.Transactions, tx => EncodeTransaction(stream, tx));
     }
 }