예제 #1
0
        public void TestReplayBlock()
        {
            using (var simulator = new MainnetSimulator())
            {
                simulator.AddBlockRange(0, 9999);
                simulator.WaitForUpdate();

                using (var chainState = simulator.CoreDaemon.GetChainState())
                {
                    Assert.AreEqual(9999, chainState.Chain.Height);

                    for (var blockHeight = 0; blockHeight <= chainState.Chain.Height; blockHeight++)
                    {
                        var blockHash = chainState.Chain.Blocks[blockHeight].Hash;

                        var expectedTransactions = simulator.BlockProvider.GetBlock(blockHeight).Transactions;
                        var actualTransactions   = BlockReplayer.ReplayBlock(simulator.CoreDaemon.CoreStorage, chainState, blockHash, replayForward: true)
                                                   .ToEnumerable().ToList();

                        CollectionAssert.AreEqual(
                            expectedTransactions.Select(x => x.Hash).ToList(),
                            actualTransactions.Select(x => x.Transaction.Hash).ToList(),
                            $"Transactions differ at block {blockHeight:N0}");
                    }
                }
            }
        }
예제 #2
0
        private async Task ScanBlock(ICoreStorage coreStorage, IChainState chainState, ChainedHeader scanBlock, bool forward, CancellationToken cancelToken = default(CancellationToken))
        {
            var replayTxes = BlockReplayer.ReplayBlock(coreStorage, chainState, scanBlock.Hash, forward, cancelToken);

            var txScanner = new ActionBlock <ValidatableTx>(
                validatableTx =>
            {
                var tx      = validatableTx.Transaction;
                var txIndex = validatableTx.Index;

                if (!validatableTx.IsCoinbase)
                {
                    for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
                    {
                        var input                = tx.Inputs[inputIndex];
                        var prevOutput           = validatableTx.PrevTxOutputs[inputIndex];
                        var prevOutputScriptHash = new UInt256(SHA256Static.ComputeHash(prevOutput.ScriptPublicKey));

                        var chainPosition = ChainPosition.Fake();
                        var entryType     = forward ? EnumWalletEntryType.Spend : EnumWalletEntryType.UnSpend;

                        ScanForEntry(chainPosition, entryType, (TxOutput)prevOutput, prevOutputScriptHash);
                    }
                }

                for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++)
                {
                    var output           = tx.Outputs[outputIndex];
                    var outputScriptHash = new UInt256(SHA256Static.ComputeHash(output.ScriptPublicKey));

                    var chainPosition = ChainPosition.Fake();
                    var entryType     =
                        validatableTx.IsCoinbase ?
                        (forward ? EnumWalletEntryType.Mine : EnumWalletEntryType.UnMine)
                                : (forward ? EnumWalletEntryType.Receive : EnumWalletEntryType.UnReceieve);

                    ScanForEntry(chainPosition, entryType, output, outputScriptHash);
                }
            });

            replayTxes.LinkTo(txScanner, new DataflowLinkOptions {
                PropagateCompletion = true
            });
            await txScanner.Completion;
        }
예제 #3
0
        public void ReplayBlockExample()
        {
            // create example core daemon
            BlockProvider embeddedBlocks; IStorageManager storageManager;

            using (var coreDaemon = CreateExampleDaemon(out embeddedBlocks, out storageManager, maxHeight: 999))
                using (embeddedBlocks)
                    using (storageManager)
                    {
                        // start a chain at the genesis block to represent the processed progress
                        var processedChain = Chain.CreateForGenesisBlock(coreDaemon.ChainParams.GenesisChainedHeader).ToBuilder();

                        // a dictionary of public key script hashes can be created for any addresses of interest, allowing for quick checking
                        var scriptHashesOfInterest = new HashSet <UInt256>();

                        // retrieve a chainstate to replay blocks with
                        using (var chainState = coreDaemon.GetChainState())
                        {
                            // enumerate the steps needed to take the currently processed chain towards the current chainstate
                            foreach (var pathElement in processedChain.NavigateTowards(chainState.Chain))
                            {
                                // retrieve the next block to replay and whether to replay forwards, or backwards for a re-org
                                var replayForward = pathElement.Item1 > 0;
                                var replayBlock   = pathElement.Item2;

                                // begin replaying the transactions in the replay block
                                // if this is a re-org, the transactions will be replayed in reverse block order
                                var replayTxes = BlockReplayer.ReplayBlock(coreDaemon.CoreStorage, chainState, replayBlock.Hash, replayForward);

                                // prepare the tx scanner
                                var txScanner = new ActionBlock <ValidatableTx>(
                                    validatableTx =>
                                {
                                    // the transaction being replayed
                                    var tx = validatableTx.Transaction;

                                    // the previous tx outputs for each of the replay transaction's inputs
                                    var prevTxOutputs = validatableTx.PrevTxOutputs;

                                    // scan the replay transaction's inputs
                                    if (!validatableTx.IsCoinbase)
                                    {
                                        for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
                                        {
                                            var input             = tx.Inputs[inputIndex];
                                            var inputPrevTxOutput = validatableTx.PrevTxOutputs[inputIndex];

                                            // check if the input's previous transaction output is of interest
                                            var inputPrevTxOutputPublicScriptHash = new UInt256(SHA256Static.ComputeHash(inputPrevTxOutput.ScriptPublicKey));
                                            if (scriptHashesOfInterest.Contains(inputPrevTxOutputPublicScriptHash))
                                            {
                                                if (replayForward)
                                                { /* An output for an address of interest is being spent. */
                                                }
                                                else
                                                { /* An output for an address of interest is being "unspent", on re-org. */
                                                }
                                            }
                                        }
                                    }

                                    // scan the replay transaction's outputs
                                    for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++)
                                    {
                                        var output = tx.Outputs[outputIndex];

                                        // check if the output is of interest
                                        var outputPublicScriptHash = new UInt256(SHA256Static.ComputeHash(output.ScriptPublicKey));
                                        if (scriptHashesOfInterest.Contains(outputPublicScriptHash))
                                        {
                                            if (replayForward)
                                            { /* An output for an address of interest is being minted. */
                                            }
                                            else
                                            { /* An output for an address of interest is being "unminted", on re-org. */
                                            }
                                        }
                                    }
                                });

                                // hook up and wait for the tx scanner
                                replayTxes.LinkTo(txScanner, new DataflowLinkOptions {
                                    PropagateCompletion = true
                                });
                                txScanner.Completion.Wait();

                                // a wallet would now commit its progress

                                /*
                                 * walletDatabase.CurrentBlock = replayBlock.Hash;
                                 * walletDatabase.Commit();
                                 */

                                // TODO: after successfully committing, a wallet would notify CoreDaemon of its current progress
                                // TODO: CoreDaemon will use this information in order to determine how far in the current chainstate it is safe to prune
                                // TODO: with this in place, if a wallet suffers a failure to commit it can just replay the block
                                // TODO: wallets can also remain disconnected from CoreDaemon, and just replay blocks to catch up when they are reconnected

                                // update the processed chain so that the next step towards the current chainstate can be taken
                                if (replayForward)
                                {
                                    processedChain.AddBlock(replayBlock);
                                }
                                else
                                {
                                    processedChain.RemoveBlock(replayBlock);
                                }
                            }
                        }

                        logger.Info("Processed chain height: {0:N0}", processedChain.Height);
                    }
        }
예제 #4
0
        public void TestReplayBlockRollback()
        {
            using (var daemon = new TestDaemon())
            {
                // create a new keypair to spend to
                var toKeyPair    = daemon.TxManager.CreateKeyPair();
                var toPrivateKey = toKeyPair.Item1;
                var toPublicKey  = toKeyPair.Item2;

                // add block 1
                var block0 = daemon.GenesisBlock;
                var block1 = daemon.MineAndAddEmptyBlock();

                // add some blocks so coinbase is mature to spend
                Block lastBlock = null;
                for (var i = 0; i < 100; i++)
                {
                    lastBlock = daemon.MineAndAddEmptyBlock();
                }

                // add block 2, spending from block 1
                var spendTx1      = daemon.TxManager.CreateSpendTransaction(block1.Transactions[0], 0, (byte)ScriptHashType.SIGHASH_ALL, block1.Transactions[0].OutputValue(), daemon.CoinbasePrivateKey, daemon.CoinbasePublicKey, toPublicKey);
                var block2Unmined = daemon.CreateEmptyBlock(lastBlock.Hash)
                                    .CreateWithAddedTransactions(spendTx1);
                var block2 = daemon.MineAndAddBlock(block2Unmined);

                // add some blocks so coinbase is mature to spend
                for (var i = 0; i < 100; i++)
                {
                    lastBlock = daemon.MineAndAddEmptyBlock();
                }

                // add block 3, spending from block 2
                var spendTx2      = daemon.TxManager.CreateSpendTransaction(block2.Transactions[1], 0, (byte)ScriptHashType.SIGHASH_ALL, block2.Transactions[1].OutputValue(), toPrivateKey, toPublicKey, toPublicKey);
                var block3Unmined = daemon.CreateEmptyBlock(lastBlock.Hash)
                                    .CreateWithAddedTransactions(spendTx2);
                var block3 = daemon.MineAndAddBlock(block3Unmined);

                // replay all blocks up to block 3
                daemon.WaitForUpdate();
                using (var chainState = daemon.CoreDaemon.GetChainState())
                {
                    Assert.AreEqual(203, chainState.Chain.Height);

                    var replayTransactions = new List <ValidatableTx>();
                    foreach (var blockHash in chainState.Chain.Blocks.Select(x => x.Hash))
                    {
                        replayTransactions.AddRange(BlockReplayer.ReplayBlock(daemon.CoreStorage, chainState, blockHash, replayForward: true)
                                                    .ToEnumerable());
                    }

                    // verify all transactions were replayed
                    Assert.AreEqual(206, replayTransactions.Count);
                    Assert.AreEqual(block0.Transactions[0].Hash, replayTransactions[0].Transaction.Hash);
                    Assert.AreEqual(block1.Transactions[0].Hash, replayTransactions[1].Transaction.Hash);
                    Assert.AreEqual(block2.Transactions[0].Hash, replayTransactions[102].Transaction.Hash);
                    Assert.AreEqual(block2.Transactions[1].Hash, replayTransactions[103].Transaction.Hash);
                    Assert.AreEqual(block3.Transactions[0].Hash, replayTransactions[204].Transaction.Hash);
                    Assert.AreEqual(block3.Transactions[1].Hash, replayTransactions[205].Transaction.Hash);
                }

                // mark block 2 invalid, it will be rolled back
                daemon.CoreStorage.MarkBlockInvalid(block2.Hash, daemon.CoreDaemon.TargetChain);
                daemon.WaitForUpdate();

                // replay rollback of block 3
                using (var chainState = daemon.CoreDaemon.GetChainState())
                {
                    Assert.AreEqual(101, chainState.Chain.Height);

                    var replayTransactions = new List <ValidatableTx>(
                        BlockReplayer.ReplayBlock(daemon.CoreStorage, chainState, block3.Hash, replayForward: false)
                        .ToEnumerable());

                    // verify transactions were replayed
                    Assert.AreEqual(2, replayTransactions.Count);
                    Assert.AreEqual(block3.Transactions[1].Hash, replayTransactions[0].Transaction.Hash);
                    Assert.AreEqual(block3.Transactions[0].Hash, replayTransactions[1].Transaction.Hash);

                    // verify correct previous output was replayed (block 3 tx 1 spent block 2 tx 1)
                    Assert.AreEqual(1, replayTransactions[0].PrevTxOutputs.Length);
                    CollectionAssert.AreEqual(block2.Transactions[1].Outputs[0].ScriptPublicKey, replayTransactions[0].PrevTxOutputs[0].ScriptPublicKey);

                    // verify correct previous output was replayed (block 3 tx 0 spends nothing, coinbase)
                    Assert.AreEqual(0, replayTransactions[1].PrevTxOutputs.Length);
                }

                // replay rollback of block 2
                using (var chainState = daemon.CoreDaemon.GetChainState())
                {
                    Assert.AreEqual(101, chainState.Chain.Height);

                    var replayTransactions = new List <ValidatableTx>(
                        BlockReplayer.ReplayBlock(daemon.CoreStorage, chainState, block2.Hash, replayForward: false)
                        .ToEnumerable());

                    // verify transactions were replayed
                    Assert.AreEqual(2, replayTransactions.Count);
                    Assert.AreEqual(block2.Transactions[1].Hash, replayTransactions[0].Transaction.Hash);
                    Assert.AreEqual(block2.Transactions[0].Hash, replayTransactions[1].Transaction.Hash);

                    // verify correct previous output was replayed (block 2 tx 1 spent block 1 tx 0)
                    Assert.AreEqual(1, replayTransactions[0].PrevTxOutputs.Length);
                    CollectionAssert.AreEqual(block1.Transactions[0].Outputs[0].ScriptPublicKey, replayTransactions[0].PrevTxOutputs[0].ScriptPublicKey);

                    // verify correct previous output was replayed (block 2 tx 0 spends nothing, coinbase)
                    Assert.AreEqual(0, replayTransactions[1].PrevTxOutputs.Length);
                }
            }
        }