public void TestAdd()
        {
            TestStorage storage = new TestStorage();
            storage.AddUnspentOutput(new UnspentOutput(0, Hash1, 0, 100, new byte[16]));

            UnspentOutputsUpdate update = new UnspentOutputsUpdate(storage);

            Assert.Throws<InvalidOperationException>(() => update.Add(new UnspentOutput(0, Hash1, 0, 200, new byte[16])));
            update.Add(new UnspentOutput(0, Hash1, 1, 201, new byte[16]));
            update.Add(new UnspentOutput(0, Hash2, 0, 300, new byte[16]));
            update.Add(new UnspentOutput(0, Hash2, 1, 301, new byte[16]));

            Assert.Throws<InvalidOperationException>(() => update.Add(new UnspentOutput(0, Hash1, 0, 250, new byte[16])));
            Assert.Throws<InvalidOperationException>(() => update.Add(new UnspentOutput(0, Hash1, 1, 251, new byte[16])));
            Assert.Throws<InvalidOperationException>(() => update.Add(new UnspentOutput(0, Hash2, 0, 350, new byte[16])));
            Assert.Throws<InvalidOperationException>(() => update.Add(new UnspentOutput(0, Hash2, 1, 351, new byte[16])));

            Assert.That(update.FindUnspentOutputs(Hash1).Select(o => o.Sum).ToList(), Is.EquivalentTo(new ulong[] {100, 201}));
            Assert.That(update.FindUnspentOutputs(Hash2).Select(o => o.Sum).ToList(), Is.EquivalentTo(new ulong[] {300, 301}));
            Assert.That(update.FindUnspentOutput(Hash1, 0).Sum, Is.EqualTo(100));
            Assert.That(update.FindUnspentOutput(Hash1, 1).Sum, Is.EqualTo(201));
            Assert.That(update.FindUnspentOutput(Hash2, 0).Sum, Is.EqualTo(300));
            Assert.That(update.FindUnspentOutput(Hash2, 1).Sum, Is.EqualTo(301));

            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[] {100, 201, 300, 301}));
            Assert.That(storage.SpentOutputs, Is.Empty);
        }
        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 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}));
        }
        public void TestFindExistingOutputs()
        {
            TestStorage storage = new TestStorage();
            storage.AddUnspentOutput(new UnspentOutput(0, Hash1, 1, 100, new byte[16]));

            UnspentOutputsUpdate update = new UnspentOutputsUpdate(storage);

            Assert.That(update.FindUnspentOutputs(Hash1).Select(o => o.Sum), Is.EquivalentTo(new ulong[] {100}));
            Assert.That(update.FindUnspentOutputs(Hash2), Is.Empty);
        }