public void TestDuplicateTransaction()
        {
            BlockMessage validBlock1 = BlockMessage.Read(new BitcoinStreamReader(new MemoryStream(KnownBlocks.Block100001)));
            BlockMessage validBlock2 = BlockMessage.Read(new BitcoinStreamReader(new MemoryStream(KnownBlocks.Block100002)));

            Assert.True(BlockContentValidator.IsMerkleTreeValid(validBlock1));
            Assert.True(BlockContentValidator.IsMerkleTreeValid(validBlock2));

            Assert.That(validBlock1.Transactions.Length, Is.EqualTo(12));
            Assert.That(validBlock2.Transactions.Length, Is.EqualTo(9));

            List<Tx> transactions1 = new List<Tx>(validBlock1.Transactions);
            transactions1.Add(validBlock1.Transactions[8]);
            transactions1.Add(validBlock1.Transactions[9]);
            transactions1.Add(validBlock1.Transactions[10]);
            transactions1.Add(validBlock1.Transactions[11]);
            BlockMessage invalidBlock1 = new BlockMessage(validBlock1.BlockHeader, transactions1.ToArray());

            List<Tx> transactions2 = new List<Tx>(validBlock2.Transactions);
            transactions2.Add(validBlock2.Transactions[8]);
            BlockMessage invalidBlock2 = new BlockMessage(validBlock2.BlockHeader, transactions2.ToArray());

            Assert.That(MerkleTreeUtils.GetTreeRoot(invalidBlock1.Transactions), Is.EqualTo(invalidBlock1.BlockHeader.MerkleRoot));
            Assert.That(MerkleTreeUtils.GetTreeRoot(invalidBlock2.Transactions), Is.EqualTo(invalidBlock2.BlockHeader.MerkleRoot));

            Assert.False(BlockContentValidator.IsMerkleTreeValid(invalidBlock1));
            Assert.False(BlockContentValidator.IsMerkleTreeValid(invalidBlock2));
        }
        internal static bool IsValidCoinbaseTransaction(BlockMessage block)
        {
            if (block.Transactions == null || block.Transactions.Length == 0)
            {
                return false;
            }

            if (!IsValidCoinbaseTransaction(block.Transactions[0]))
            {
                return false;
            }

            // also checking that there is only one coinbase transaction
            for (int i = 1; i < block.Transactions.Length; i++)
            {
                Tx transaction = block.Transactions[i];
                foreach (TxIn input in transaction.Inputs)
                {
                    if (input.PreviousOutput.Hash.All(b => b == 0) || input.PreviousOutput.Index < 0)
                    {
                        return false;
                    }
                }
            }

            return true;
        }
        public void AddBlockContent(BlockMessage block)
        {
            transactionalResource.Enlist();

            byte[] blockHash = CryptoUtils.DoubleSha256(BitcoinStreamWriter.GetBytes(block.BlockHeader.Write));

            StoredBlock storedBlock = storage.FindBlockByHash(blockHash);
            if (storedBlock == null || storedBlock.HasContent)
            {
                return;
            }

            if (!BlockContentValidator.IsMerkleTreeValid(block))
            {
                // If merkle tree is invalid than we cannot rely on its header hash to mark block in storage as invalid.
                throw new BitcoinProtocolViolationException($"Merkle tree did not pass validation (block: {BitConverter.ToString(blockHash)}).");
            }

            // since block is already stored in storage we assume that its header is valid

            if (!BlockContentValidator.IsValid(storedBlock, block))
            {
                //todo: mark chain as broken
                throw new BitcoinProtocolViolationException($"Block content was invalid (block: {BitConverter.ToString(blockHash)}).");
            }

            StoredBlock updatedBlock = storage.AddBlockContent(storedBlock.Hash, BitcoinStreamWriter.GetBytes(block.Write));
            //todo: test state updates
            currentState = currentState.Update(storedBlock, updatedBlock);
        }
        //todo: add XMLDOC here and to other validators
        public static bool IsMerkleTreeValid(BlockMessage block)
        {
            if (block.Transactions == null || block.Transactions.Length == 0)
            {
                return false;
            }

            List<byte[]> transactionHashes = new List<byte[]>(block.Transactions.Length);
            HashSet<byte[]> transactionHashSet = new HashSet<byte[]>(ByteArrayComparer.Instance);
            foreach (Tx transaction in block.Transactions)
            {
                byte[] hash = CryptoUtils.DoubleSha256(BitcoinStreamWriter.GetBytes(transaction.Write));
                if (!transactionHashSet.Add(hash))
                {
                    // Duplicate transactions can be used to construct invalid block that has same merkle tree root as valid block.
                    // Such blocks should not prevent acceptance of valid blocks.
                    // See: https://en.bitcoin.it/wiki/Common_Vulnerabilities_and_Exposures#CVE-2012-2459
                    return false;
                }
                transactionHashes.Add(hash);
            }

            byte[] merkleTreeRoot = MerkleTreeUtils.GetTreeRoot(transactionHashes);
            return merkleTreeRoot.SequenceEqual(block.BlockHeader.MerkleRoot);
        }
        public void TestInvalidCoinbase()
        {
            BlockMessage validBlock = BitcoinStreamReader.FromBytes(KnownBlocks.Block100000, BlockMessage.Read);
            Assert.True(BlockContentValidator.IsValidCoinbaseTransaction(validBlock));

            Tx validCoinbaseTransaction = validBlock.Transactions[0];
            TxIn validCoinbaseInput = validCoinbaseTransaction.Inputs[0];

            // first checking that we reconstruct block correctly
            {
                List<Tx> transactions = new List<Tx>();
                transactions.Add(new Tx(
                    validCoinbaseTransaction.Version,
                    new TxIn[] {new TxIn(new TxOutPoint(new byte[32], -1), validCoinbaseInput.SignatureScript, validCoinbaseInput.Sequence)},
                    validCoinbaseTransaction.Outputs,
                    validCoinbaseTransaction.LockTime));
                transactions.AddRange(validBlock.Transactions.Skip(1));
                BlockMessage invalidBlock = new BlockMessage(validBlock.BlockHeader, transactions.ToArray());
                Assert.True(BlockContentValidator.IsValidCoinbaseTransaction(invalidBlock));
            }

            // cheking invalid signature
            {
                List<Tx> transactions = new List<Tx>();
                transactions.Add(new Tx(
                    validCoinbaseTransaction.Version,
                    new TxIn[] {new TxIn(new TxOutPoint(new byte[32], -1), new byte[101], validCoinbaseInput.Sequence)},
                    validCoinbaseTransaction.Outputs,
                    validCoinbaseTransaction.LockTime));
                transactions.AddRange(validBlock.Transactions.Skip(1));
                BlockMessage invalidBlock = new BlockMessage(validBlock.BlockHeader, transactions.ToArray());
                Assert.False(BlockContentValidator.IsValidCoinbaseTransaction(invalidBlock));
            }

            // cheking invalid output index
            {
                List<Tx> transactions = new List<Tx>();
                transactions.Add(new Tx(
                    validCoinbaseTransaction.Version,
                    new TxIn[] {new TxIn(new TxOutPoint(new byte[32], 0), validCoinbaseInput.SignatureScript, validCoinbaseInput.Sequence)},
                    validCoinbaseTransaction.Outputs,
                    validCoinbaseTransaction.LockTime));
                transactions.AddRange(validBlock.Transactions.Skip(1));
                BlockMessage invalidBlock = new BlockMessage(validBlock.BlockHeader, transactions.ToArray());
                Assert.False(BlockContentValidator.IsValidCoinbaseTransaction(invalidBlock));
            }

            // cheking duplicate coinbase
            {
                List<Tx> transactions = new List<Tx>();
                transactions.Add(new Tx(
                    validCoinbaseTransaction.Version,
                    new TxIn[] {new TxIn(new TxOutPoint(new byte[32], -1), new byte[10], validCoinbaseInput.Sequence)},
                    validCoinbaseTransaction.Outputs,
                    validCoinbaseTransaction.LockTime));
                transactions.Add(new Tx(
                    validCoinbaseTransaction.Version,
                    new TxIn[] {new TxIn(new TxOutPoint(new byte[32], -1), new byte[20], validCoinbaseInput.Sequence)},
                    validCoinbaseTransaction.Outputs,
                    validCoinbaseTransaction.LockTime));
                transactions.AddRange(validBlock.Transactions.Skip(1));
                BlockMessage invalidBlock = new BlockMessage(validBlock.BlockHeader, transactions.ToArray());
                Assert.False(BlockContentValidator.IsValidCoinbaseTransaction(invalidBlock));
            }
        }
        public void TestWrongMerkleTreeRoot()
        {
            BlockMessage validBlock1 = BlockMessage.Read(new BitcoinStreamReader(new MemoryStream(KnownBlocks.Block100001)));
            BlockMessage validBlock2 = BlockMessage.Read(new BitcoinStreamReader(new MemoryStream(KnownBlocks.Block100002)));
            Assert.That(BlockContentValidator.IsMerkleTreeValid(validBlock1));
            Assert.That(BlockContentValidator.IsMerkleTreeValid(validBlock2));

            BlockMessage invalidBlock1 = new BlockMessage(validBlock1.BlockHeader, validBlock2.Transactions);
            BlockMessage invalidBlock2 = new BlockMessage(validBlock2.BlockHeader, validBlock1.Transactions);
            Assert.False(BlockContentValidator.IsMerkleTreeValid(invalidBlock1));
            Assert.False(BlockContentValidator.IsMerkleTreeValid(invalidBlock2));
        }
 public void TestNoTransactions()
 {
     BlockMessage block = new BlockMessage(GenesisBlock.GetHeader(), new Tx[0]);
     Assert.False(BlockContentValidator.IsMerkleTreeValid(block));
 }
        private UnspentOutputsUpdate PrepareUnspentOutputsUpdate(StoredBlock block, BlockMessage blockMessage)
        {
            ScriptParser parser = new ScriptParser();

            ulong inputsSum = GetBlockReward(block);
            ulong outputsSum = 0;

            UnspentOutputsUpdate update = new UnspentOutputsUpdate(storage);

            for (int transactionNumber = 0; transactionNumber < blockMessage.Transactions.Length; transactionNumber++)
            {
                Tx transaction = blockMessage.Transactions[transactionNumber];

                ulong transactionInputsSum = 0;
                ulong transactionOutputsSum = 0;

                byte[] transactionHash = CryptoUtils.DoubleSha256(BitcoinStreamWriter.GetBytes(transaction.Write));

                List<UnspentOutput> unspentOutputs = update.FindUnspentOutputs(transactionHash);
                if (unspentOutputs.Any())
                {
                    //todo: use network settings
                    if (block.Height == 91842 || block.Height == 91880)
                    {
                        // this blocks are exceptions from BIP-30
                        foreach (UnspentOutput unspentOutput in unspentOutputs)
                        {
                            update.Spend(unspentOutput.TransactionHash, unspentOutput.OutputNumber, block);
                        }
                    }
                    else
                    {
                        throw new BitcoinProtocolViolationException(
                            $"The transaction '{BitConverter.ToString(transactionHash)}'" +
                            $" in block '{BitConverter.ToString(block.Hash)}'" +
                            $" has same hash as an existing unspent transaction (see BIP-30).");
                    }
                }

                //todo: check transaction hash against genesis block transaction hash
                if (transactionNumber != 0)
                {
                    foreach (TxIn input in transaction.Inputs)
                    {
                        UnspentOutput output = update.FindUnspentOutput(input.PreviousOutput.Hash, input.PreviousOutput.Index);
                        if (output == null)
                        {
                            throw new BitcoinProtocolViolationException(
                                $"The input of the transaction '{BitConverter.ToString(transactionHash)}'" +
                                $" in block '{BitConverter.ToString(block.Hash)}'" +
                                $" has been already spent or did not exist.");
                        }
                        //todo: check for overflow
                        transactionInputsSum += output.Sum;

                        List<ScriptCommand> inputCommands;
                        if (!parser.TryParse(input.SignatureScript, out inputCommands))
                        {
                            throw new BitcoinProtocolViolationException(
                                $"The transaction '{BitConverter.ToString(transactionHash)}'" +
                                $" in block '{BitConverter.ToString(block.Hash)}'" +
                                $" has an invalid signature script.");
                        }
                        if (inputCommands.Any(c => !IsValidSignatureCommand(c.Code)))
                        {
                            throw new BitcoinProtocolViolationException(
                                $"The transaction '{BitConverter.ToString(transactionHash)}'" +
                                $" in block '{BitConverter.ToString(block.Hash)}'" +
                                $" has forbidden commands in the signature script.");
                        }

                        //todo: check signature for the output
                        update.Spend(output.TransactionHash, output.OutputNumber, block);
                    }
                }

                for (int outputNumber = 0; outputNumber < transaction.Outputs.Length; outputNumber++)
                {
                    TxOut output = transaction.Outputs[outputNumber];
                    //todo: check for overflow
                    transactionOutputsSum += output.Value;

                    List<ScriptCommand> commands;
                    if (!parser.TryParse(output.PubkeyScript, out commands))
                    {
                        //todo: how Bitcoin Core works in this scenario?
                        throw new BitcoinProtocolViolationException(
                            $"The output of transaction '{BitConverter.ToString(transactionHash)}'" +
                            $" in block '{BitConverter.ToString(block.Hash)}'" +
                            $" has an invalid pubkey script script.");
                    }

                    UnspentOutput unspentOutput = UnspentOutput.Create(block, transaction, outputNumber);
                    update.Add(unspentOutput);
                }

                if (transactionNumber != 0 && transactionInputsSum < transactionOutputsSum)
                {
                    // for coinbase transaction output sum is checked later as part of total block inputs, outputs and reward sums equation
                    throw new BitcoinProtocolViolationException(
                        $"The sum of the inputs in the transaction '{BitConverter.ToString(transactionHash)}'" +
                        $" in block '{BitConverter.ToString(block.Hash)}'" +
                        $" is less than the sum of the outputs.");
                }

                //todo: check for overflow
                inputsSum += transactionInputsSum;
                //todo: check for overflow
                outputsSum += transactionOutputsSum;
            }

            if (inputsSum != outputsSum)
            {
                throw new BitcoinProtocolViolationException(
                    $"The sum of the inputs and the reward" +
                    $" in the block '{BitConverter.ToString(block.Hash)}'" +
                    $" does not match the sum of the outputs.");
            }

            return update;
        }
 private void ProcessBlockMessage(BlockMessage blockMessage)
 {
     byte[] blockHash = CryptoUtils.DoubleSha256(BitcoinStreamWriter.GetBytes(blockMessage.BlockHeader.Write));
     lock (lockObject)
     {
         if (!requiredBlocks.ContainsKey(blockHash))
         {
             return;
         }
         //todo: here there is a lock within lock
         //todo: if block is invalid don't ask for it again
         //todo: if block is invalid truncate headers chain and select other chain if necessary
         node.Blockchain.AddBlockContent(blockMessage);
         RemoveRequiredBlock(blockHash);
     }
 }
Exemplo n.º 10
0
 public void AddBlockContent(BlockMessage block)
 {
     //todo: add XMLDOC
     ExecuteInTransaction(() => blockchain.AddBlockContent(block));
 }
 public static bool IsValid(StoredBlock storedBlock, BlockMessage block)
 {
     //todo: add other validations, recheck each field constraints
     return IsValidCoinbaseTransaction(block);
 }