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); } }
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); }