Custom Analytic Tools
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:
1
[RunnerStepDependencies(typeof(ReviewBlockTree))]
2
public class RunCustomTools : IStep
3
{
4
private readonly EthereumRunnerContext _context;
5
6
public RunCustomTools(EthereumRunnerContext context)
7
{
8
_context = context;
9
}
10
11
public Task Execute(CancellationToken cancellationToken)
12
{
13
ILogger logger = _context.LogManager.GetClassLogger();
14
IInitConfig initConfig = _context.Config<IInitConfig>();
15
16
switch (initConfig.DiagnosticMode)
17
{
18
case DiagnosticMode.VerifySupply:
19
{
20
logger.Info("Genesis supply:");
21
SupplyVerifier supplyVerifier = new SupplyVerifier(logger);
22
StateDb stateDb = new StateDb(_context.DbProvider.StateDb.Innermost);
23
StateDb codeDb = new StateDb(_context.DbProvider.StateDb.Innermost);
24
StateReader stateReader = new StateReader(stateDb, codeDb, _context.LogManager);
25
stateReader.RunTreeVisitor(supplyVerifier, _context.BlockTree!.Genesis.StateRoot);
26
27
Block head = _context.BlockTree!.Head;
28
logger.Info(quot;Head ({head.Number}) block supply:");
29
supplyVerifier = new SupplyVerifier(logger);
30
stateReader.RunTreeVisitor(supplyVerifier, head.StateRoot);
31
break;
32
}
33
case DiagnosticMode.VerifyRewards:
34
_context.BlockTree!.Accept(new RewardsVerifier(_context.LogManager), cancellationToken);
35
break;
36
}
37
38
return Task.CompletedTask;
39
}
40
}
Copied!
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:
1
public class RewardsVerifier : IBlockTreeVisitor
2
{
3
private ILogger _logger;
4
public bool PreventsAcceptingNewBlocks => true;
5
public long StartLevelInclusive => 0;
6
public long EndLevelExclusive => 10618000;
7
8
private UInt256 _genesisAllocations = UInt256.Parse("72009990499480000000000000");
9
private UInt256 _uncles;
10
private UInt256 _blockRewards;
11
12
public RewardsVerifier(ILogManager logManager)
13
{
14
_logger = logManager.GetClassLogger();
15
}
16
17
private RewardCalculator _rewardCalculator = new RewardCalculator(MainnetSpecProvider.Instance);
18
19
public Task<BlockVisitOutcome> VisitBlock(Block block, CancellationToken cancellationToken)
20
{
21
BlockReward[] rewards = _rewardCalculator.CalculateRewards(block);
22
for (int i = 0; i < rewards.Length; i++)
23
{
24
if (rewards[i].RewardType == BlockRewardType.Uncle)
25
{
26
_uncles += rewards[i].Value;
27
}
28
else
29
{
30
_blockRewards += rewards[i].Value;
31
}
32
}
33
34
_logger.Info(quot;Visiting block {block.Number}, total supply is (genesis + miner rewards + uncle rewards) | {_genesisAllocations} + {_blockRewards} + {_uncles}");
35
return Task.FromResult(BlockVisitOutcome.None);
36
}
37
38
public Task<LevelVisitOutcome> VisitLevelStart(ChainLevelInfo chainLevelInfo, CancellationToken cancellationToken)
39
=> Task.FromResult(LevelVisitOutcome.None);
40
41
public Task<bool> VisitMissing(Keccak hash, CancellationToken cancellationToken)
42
=> Task.FromResult(true);
43
44
public Task<bool> VisitHeader(BlockHeader header, CancellationToken cancellationToken)
45
=> Task.FromResult(true);
46
47
public Task<LevelVisitOutcome> VisitLevelEnd(CancellationToken cancellationToken)
48
=> Task.FromResult(LevelVisitOutcome.None);
49
}
Copied!
And here you will find an example of a tree visitor that sums up all the account balances:
1
public class SupplyVerifier : ITreeVisitor
2
{
3
private readonly ILogger _logger;
4
private UInt256 _balance = UInt256.Zero;
5
private int _accountsVisited;
6
7
public SupplyVerifier(ILogger logger)
8
{
9
_logger = logger;
10
}
11
12
public bool ShouldVisit(Keccak nextNode) { return true; }
13
14
public void VisitTree(Keccak rootHash, TrieVisitContext trieVisitContext) { }
15
16
public void VisitMissingNode(Keccak nodeHash, TrieVisitContext trieVisitContext) { }
17
18
public void VisitBranch(TrieNode node, TrieVisitContext trieVisitContext) { }
19
20
public void VisitExtension(TrieNode node, TrieVisitContext trieVisitContext) { }
21
22
public void VisitLeaf(TrieNode node, TrieVisitContext trieVisitContext, byte[] value = null)
23
{
24
if (trieVisitContext.IsStorage)
25
{
26
return;
27
}
28
29
AccountDecoder accountDecoder = new AccountDecoder();
30
Account account = accountDecoder.Decode(node.Value.AsRlpStream());
31
_balance += account.Balance;
32
_accountsVisited++;
33
34
_logger.Info(quot;Balance after visiting {_accountsVisited}: {_balance}");
35
}
36
37
public void VisitCode(Keccak codeHash, TrieVisitContext trieVisitContext) { }
38
}
Copied!
Last modified 10mo ago
Copy link