Check plugins for some easy addition of analytical tools
You can also read more about some useful interfaces below:
There are multiple extension points where you can add custom analytics to your Nethermind node if you know some C#. Below you will find an example of using two very useful interfaces - IBlockVisitor
and ITreeVisitor
.
Just to execute the code I have added one new initialization step that invokes two custom verifiers that I have used for calculating total supply in two different ways - by calculating mining rewards and by summing up all account balances:
[RunnerStepDependencies(typeof(ReviewBlockTree))]public class RunCustomTools : IStep{private readonly EthereumRunnerContext _context;public RunCustomTools(EthereumRunnerContext context){_context = context;}public Task Execute(CancellationToken cancellationToken){ILogger logger = _context.LogManager.GetClassLogger();IInitConfig initConfig = _context.Config<IInitConfig>();switch (initConfig.DiagnosticMode){case DiagnosticMode.VerifySupply:{logger.Info("Genesis supply:");SupplyVerifier supplyVerifier = new SupplyVerifier(logger);StateDb stateDb = new StateDb(_context.DbProvider.StateDb.Innermost);StateDb codeDb = new StateDb(_context.DbProvider.StateDb.Innermost);StateReader stateReader = new StateReader(stateDb, codeDb, _context.LogManager);stateReader.RunTreeVisitor(supplyVerifier, _context.BlockTree!.Genesis.StateRoot);Block head = _context.BlockTree!.Head;logger.Info($"Head ({head.Number}) block supply:");supplyVerifier = new SupplyVerifier(logger);stateReader.RunTreeVisitor(supplyVerifier, head.StateRoot);break;}case DiagnosticMode.VerifyRewards:_context.BlockTree!.Accept(new RewardsVerifier(_context.LogManager), cancellationToken);break;}return Task.CompletedTask;}}
Below you will see an example of using ITreeVisitor that allows to check all the blocks, including some of the discarded branches if you wish so:
public class RewardsVerifier : IBlockTreeVisitor{private ILogger _logger;public bool PreventsAcceptingNewBlocks => true;public long StartLevelInclusive => 0;public long EndLevelExclusive => 10618000;private UInt256 _genesisAllocations = UInt256.Parse("72009990499480000000000000");private UInt256 _uncles;private UInt256 _blockRewards;public RewardsVerifier(ILogManager logManager){_logger = logManager.GetClassLogger();}private RewardCalculator _rewardCalculator = new RewardCalculator(MainnetSpecProvider.Instance);public Task<BlockVisitOutcome> VisitBlock(Block block, CancellationToken cancellationToken){BlockReward[] rewards = _rewardCalculator.CalculateRewards(block);for (int i = 0; i < rewards.Length; i++){if (rewards[i].RewardType == BlockRewardType.Uncle){_uncles += rewards[i].Value;}else{_blockRewards += rewards[i].Value;}}_logger.Info($"Visiting block {block.Number}, total supply is (genesis + miner rewards + uncle rewards) | {_genesisAllocations} + {_blockRewards} + {_uncles}");return Task.FromResult(BlockVisitOutcome.None);}public Task<LevelVisitOutcome> VisitLevelStart(ChainLevelInfo chainLevelInfo, CancellationToken cancellationToken)=> Task.FromResult(LevelVisitOutcome.None);public Task<bool> VisitMissing(Keccak hash, CancellationToken cancellationToken)=> Task.FromResult(true);public Task<bool> VisitHeader(BlockHeader header, CancellationToken cancellationToken)=> Task.FromResult(true);public Task<LevelVisitOutcome> VisitLevelEnd(CancellationToken cancellationToken)=> Task.FromResult(LevelVisitOutcome.None);}
And here you will find an example of a tree visitor that sums up all the account balances:
public class SupplyVerifier : ITreeVisitor{private readonly ILogger _logger;private UInt256 _balance = UInt256.Zero;private int _accountsVisited;public SupplyVerifier(ILogger logger){_logger = logger;}public bool ShouldVisit(Keccak nextNode) { return true; }public void VisitTree(Keccak rootHash, TrieVisitContext trieVisitContext) { }public void VisitMissingNode(Keccak nodeHash, TrieVisitContext trieVisitContext) { }public void VisitBranch(TrieNode node, TrieVisitContext trieVisitContext) { }public void VisitExtension(TrieNode node, TrieVisitContext trieVisitContext) { }public void VisitLeaf(TrieNode node, TrieVisitContext trieVisitContext, byte[] value = null){if (trieVisitContext.IsStorage){return;}AccountDecoder accountDecoder = new AccountDecoder();Account account = accountDecoder.Decode(node.Value.AsRlpStream());_balance += account.Balance;_accountsVisited++;_logger.Info($"Balance after visiting {_accountsVisited}: {_balance}");}public void VisitCode(Keccak codeHash, TrieVisitContext trieVisitContext) { }}