Nethermind plugins is a powerful way of extending your local node capabilities.
(see also an article here: https://medium.com/nethermind-eth/writing-your-first-nethermind-plugin-a9e04d81cf59)
Plugins that you can write:
Plugin Type | What can it be used for? |
RPC | Adding additional RPC modules to the client that have full access to the internal Nethermind APIs and can extend capabilities of the node when integrating with your infrastructure / systems. |
Block Tree Visitors | Code allowing you to analyze entire block tree from genesis to the head block and execute aggregated calculations and checks. |
Devp2p | Allows you to create additional devp2p network protocol for your nodes to communicate over TCP/IP. You can also build custom products that will run attached to Nethermind nodes. |
State Visitors | Allow you to run aggregated analysis on the entire raw format state (or just some accounts storages). |
Config | You can add additional configuration categories to our config files and then use them in env variables, json files or command line to configure behaviour of your plugins. |
TxPool | TxPool behaviours and listeners. |
Tracers | Custom, powerful EVM tracers capable of extracting elements of EVM execution in real time. |
CLI | Additional modules for Nethermind CLI that can allow you build some quick scratchpad style JavaScript based behaviors. |
How to build a plugin? We included an example inside the Nethermind.Analytics plugin:
[RpcModule(ModuleType.Clique)]public interface IAnalyticsModule : IModule{[JsonRpcMethod(Description = "Retrieves ETH supply counted from state.", IsImplemented = true)]ResultWrapper<UInt256> analytics_verifySupply();[JsonRpcMethod(Description = "Retrieves ETH supply counted from rewards.", IsImplemented = true)]ResultWrapper<UInt256> analytics_verifyRewards();}
[CliModule("analytics")]public class AnalyticsCliModule : CliModuleBase{[CliFunction("analytics", "verifySupply")]public string VerifySupply(){return NodeManager.Post<string>("analytics_verifySupply").Result;}â[CliFunction("analytics", "verifyRewards")]public string VerifyRewards(){return NodeManager.Post<string>("analytics_verifyRewards").Result;}âpublic AnalyticsCliModule(ICliEngine cliEngine, INodeManager nodeManager): base(cliEngine, nodeManager) { }}
public class RewardsVerifier : IBlockTreeVisitor{private ILogger _logger;public bool PreventsAcceptingNewBlocks => true;public long StartLevelInclusive => 0;public long EndLevelExclusive { get; }âprivate UInt256 _genesisAllocations = UInt256.Parse("72009990499480000000000000");private UInt256 _uncles;public UInt256 BlockRewards { get; private set; }âpublic RewardsVerifier(ILogManager logManager, long endLevelExclusive){_logger = logManager.GetClassLogger();EndLevelExclusive = endLevelExclusive;BlockRewards = _genesisAllocations;}â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<HeaderVisitOutcome> VisitHeader(BlockHeader header, CancellationToken cancellationToken)=> Task.FromResult(HeaderVisitOutcome.None);public Task<LevelVisitOutcome> VisitLevelEnd(CancellationToken cancellationToken)=> Task.FromResult(LevelVisitOutcome.None);}
public class AnalyticsConfig : IAnalyticsConfig{public bool PluginsEnabled { get; set; }public bool StreamTransactions { get; set; }public bool StreamBlocks { get; set; }public bool LogPublishedData { get; set; }}
public class SupplyVerifier : ITreeVisitor{private readonly ILogger _logger;private HashSet<Keccak> _ignoreThisOne = new HashSet<Keccak>();private int _accountsVisited;private int _nodesVisited;âpublic SupplyVerifier(ILogger logger){_logger = logger;}âpublic UInt256 Balance { get; set; } = UInt256.Zero;âpublic bool ShouldVisit(Keccak nextNode){if (_ignoreThisOne.Count > 16){_logger.Warn($"Ignore count leak -> {_ignoreThisOne.Count}");}âif (_ignoreThisOne.Contains(nextNode)){_ignoreThisOne.Remove(nextNode);return false;}âreturn true;}âpublic void VisitTree(Keccak rootHash, TrieVisitContext trieVisitContext){}âpublic void VisitMissingNode(Keccak nodeHash, TrieVisitContext trieVisitContext){_logger.Warn($"Missing node {nodeHash}");}âpublic void VisitBranch(TrieNode node, TrieVisitContext trieVisitContext){_logger.Info($"Balance after visiting {_accountsVisited} accounts and {_nodesVisited} nodes: {Balance}");_nodesVisited++;âif (trieVisitContext.IsStorage){for (int i = 0; i < 16; i++){Keccak childHash = node.GetChildHash(i);if (childHash != null){_ignoreThisOne.Add(childHash);}}}}âpublic void VisitExtension(TrieNode node, TrieVisitContext trieVisitContext){_nodesVisited++;if (trieVisitContext.IsStorage){_ignoreThisOne.Add(node.GetChildHash(0));}}âpublic void VisitLeaf(TrieNode node, TrieVisitContext trieVisitContext, byte[] value = null){_nodesVisited++;â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} accounts and {_nodesVisited} nodes: {Balance}");}âpublic void VisitCode(Keccak codeHash, TrieVisitContext trieVisitContext){_nodesVisited++;}}