public void TestDoublePersist() { TestStorage storage = new TestStorage(); storage.AddUnspentOutput(new UnspentOutput(0, Hash1, 0, 100, new byte[16])); UnspentOutputsUpdate update = new UnspentOutputsUpdate(storage); update.Spend(Hash1, 0, Block1); update.Add(new UnspentOutput(1, Hash1, 0, 200, new byte[16])); update.Add(new UnspentOutput(1, Hash1, 1, 201, new byte[16])); update.Add(new UnspentOutput(1, Hash2, 0, 300, new byte[16])); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100})); Assert.That(storage.SpentOutputs, Is.Empty); update.Persist(); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {200, 201, 300})); Assert.That(storage.SpentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100})); update.Persist(); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {200, 201, 300})); Assert.That(storage.SpentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100})); }
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; }
public void TestSpendNonExistent() { TestStorage storage = new TestStorage(); storage.AddUnspentOutput(new UnspentOutput(0, Hash1, 0, 100, new byte[16])); UnspentOutputsUpdate update = new UnspentOutputsUpdate(storage); update.Spend(Hash1, 0, Block1); Assert.Throws<InvalidOperationException>(() => update.Spend(Hash1, 0, Block1)); Assert.Throws<InvalidOperationException>(() => update.Spend(Hash1, 1, Block1)); Assert.Throws<InvalidOperationException>(() => update.Spend(Hash2, 0, Block1)); Assert.Throws<InvalidOperationException>(() => update.Spend(Hash2, 1, Block1)); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100})); Assert.That(storage.SpentOutputs, Is.Empty); update.Persist(); Assert.That(storage.UnspentOutputs, Is.Empty); Assert.That(storage.SpentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100})); }
public void TestOverwrite() { TestStorage storage = new TestStorage(); storage.AddUnspentOutput(new UnspentOutput(0, Hash1, 0, 100, new byte[16])); storage.AddUnspentOutput(new UnspentOutput(0, Hash1, 1, 101, new byte[16])); UnspentOutputsUpdate update = new UnspentOutputsUpdate(storage); update.Spend(Hash1, 0, Block1); update.Spend(Hash1, 1, Block1); update.Add(new UnspentOutput(1, Hash1, 0, 200, new byte[16])); Assert.That(update.FindUnspentOutputs(Hash1).Select(o => o.Sum), Is.EqualTo(new ulong[] {200})); Assert.That(update.FindUnspentOutput(Hash1, 0).Sum, Is.EqualTo(200)); Assert.That(update.FindUnspentOutput(Hash1, 1), Is.Null); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100, 101})); Assert.That(storage.SpentOutputs, Is.Empty); update.Persist(); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {200})); Assert.That(storage.SpentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100, 101})); update = new UnspentOutputsUpdate(storage); update.Spend(Hash1, 0, Block2); update.Add(new UnspentOutput(2, Hash1, 0, 300, new byte[16])); update.Spend(Hash1, 0, Block2); update.Add(new UnspentOutput(2, Hash1, 0, 350, new byte[16])); Assert.That(update.FindUnspentOutputs(Hash1).Select(o => o.Sum), Is.EquivalentTo(new ulong[] {350})); Assert.That(update.FindUnspentOutput(Hash1, 0).Sum, Is.EqualTo(350)); Assert.That(update.FindUnspentOutput(Hash1, 1), Is.Null); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {200})); Assert.That(storage.SpentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100, 101})); update.Persist(); Assert.That(storage.UnspentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {350})); Assert.That(storage.SpentOutputs.Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100, 101, 200})); }