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