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