From 9c6b92c2843c908ec04efb3abb4d20e45695f8b9 Mon Sep 17 00:00:00 2001 From: Austin Roberts Date: Fri, 15 Oct 2021 15:27:20 -0500 Subject: [PATCH] Update today, add hook writing guide --- docs/hook_writing.rst | 100 +++++++++++++++++++++++++++++++++++++++++- docs/types.rst | 21 +++++---- 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/docs/hook_writing.rst b/docs/hook_writing.rst index e4fc09f..c62c5f3 100644 --- a/docs/hook_writing.rst +++ b/docs/hook_writing.rst @@ -4,4 +4,102 @@ Hook Writing Guide ================== -.. todo:: Austin: guide to writing plugin hooks \ No newline at end of file +If you're trying to interact with Geth in a way not already supported by +PluGeth, we're happy to accept pull requests adding new hooks so long as they +comply with certain standards. We strongly encourage you to :ref:`contact us ` +first. We may have suggestions on how to do what you're trying to do without +adding new hooks, or easier ways to implement hooks to get the information you +need. + +.. warning:: + + Plugin hooks *must not* require plugins to import any packages from ``github.com/ethereum/go-ethereum``. + Doing so means that plugins must be recompiled for each version of Geth. + Many types have been re-implemented in ``github.com/openrelayxyz/plugeth-utils``. + If you need a type for your hook not already provided by plugeth-utils, you + may make a pull request to that project as well. + +When extending the plugin API, a primary concern is leaving a minimal footprint +in the core Geth codebase to avoid future merge conflicts. To achieve this, +when we want to add a hook within some existing Geth code, we create a +plugin_hooks.go in the same package. For example, in the core/rawdb package we +have: + + +.. code-block:: Go + + // This file is part of the package we are adding hooks to + package rawdb + + // Import whatever is necessary + import ( + "github.com/ethereum/go-ethereum/plugins" + "github.com/ethereum/go-ethereum/log" + ) + + + // PluginAppendAncient is the public plugin hook function, available for testing + func PluginAppendAncient(pl *plugins.PluginLoader, number uint64, hash, header, body, receipts, td []byte) { + fnList := pl.Lookup("AppendAncient", func(item interface{}) bool { + _, ok := item.(func(number uint64, hash, header, body, receipts, td []byte)) + return ok + }) + for _, fni := range fnList { + if fn, ok := fni.(func(number uint64, hash, header, body, receipts, td []byte)); ok { + fn(number, hash, header, body, receipts, td) + } + } + } + + // pluginAppendAncient is the private plugin hook function + func pluginAppendAncient(number uint64, hash, header, body, receipts, td []byte) { + if plugins.DefaultPluginLoader == nil { + log.Warn("Attempting AppendAncient, but default PluginLoader has not been initialized") + return + } + PluginAppendAncient(plugins.DefaultPluginLoader, number, hash, header, body, receipts, td) + } + +The Public Plugin Hook Function +******************************* + +The public plugin hook function should follow the naming convention +Plugin$HookName. The first argument should be a ``*plugins.PluginLoader``, followed +by any arguments required by the functions to be provided by nay plugins +implementing this hook. + +The plugin hook function should use ``PluginLoader.Lookup("$HookName", func(item interface{}) bool`` +to get a list of the plugin-provided functions to be invoked. The provided +function should verify that the provided function implements the expected +interface. After the first time a given hook is looked up through the plugin +loader, the PluginLoader will cache references to those hooks. + +Given the function list provided by the plugin loader, the public plugin hook +function should iterate over the list, cast the elements to the appropriate +type, and call the function with the provided arguments. + +Unless there is a clear justification to the contrary, the function should be +called in the current goroutine. Plugins may choose to spawn off a separate +goroutine as appropriate, but for the sake of thread safety we should generally +not assume that plugins will be implemented in a threadsafe manner. If a plugin +degrades the performance of Geth significantly, that will generally be obvious, +and plugin authors can take appropriate measures to improve performance. If a +plugin introduces thread safety issues, those can go unnoticed during testing. + +The Private Plugin Hook Function +******************************** + +The private plugin hook function should bear the same name as the public plugin +hook function, but with a lower case first letter. The signature should match +the public plugin hook function, except that the first argument referencing the +PluginLoader should be removed. It should invoke the public plugin hook +function on ``plugins.DefaultPluginLoader``. It should always verify that the +DefaultPluginLoader is non-nil, log warning and return if the +DefaultPluginLoader has not been initialized. + +In-Line Invocation +****************** + +Within the Geth codebase, the private plugin hook function should be invoked +with the appropriate arguments in a single line, to minimize unexpected +conflicts merging the upstream geth codebase into plugeth. diff --git a/docs/types.rst b/docs/types.rst index 25d8b19..e586cdf 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -4,7 +4,7 @@ Basic Types of Plugins ====================== -While PluGeth has been designed to be versatile and customizable, when learning the project it can be helpful to think of plugins as being of four different archetypes. +While PluGeth has been designed to be versatile and customizable, when learning the project it can be helpful to think of plugins as being of four different archetypes. .. contents:: :local: @@ -17,12 +17,12 @@ These plugins provide new json rpc methods to access several objects containing Subcommand ------------ -A subcommand redifines the total behavior of Geth and could stand on its own. In contrast with the other plugin types which, in general, are meant to capture and manipulate information, a subcommand is meant to change to overall behavior of Geth. It may do this in order to capture information but the primary fuctionality is a modulation of geth behaviour. +A subcommand redifines the total behavior of Geth and could stand on its own. In contrast with the other plugin types which, in general, are meant to capture and manipulate information, a subcommand is meant to change to overall behavior of Geth. It may do this in order to capture information but the primary fuctionality is a modulation of geth behaviour. Tracers ------- -Tracers rely on historic data recompiled after execution to give insight into a transaction. +Tracers rely on historic data recompiled after execution to give insight into a transaction. **placeholder for eventual discusion of LiveTracers** @@ -30,13 +30,18 @@ Tracers rely on historic data recompiled after execution to give insight into a Subscriptions ------------- -Subscriptions provide real time notification of data from the EVM as it processes transactions. +Subscriptions provide real time notification of data from the EVM as it processes transactions. -.. NOTE:: Plugins are not limited to a singular functionality and can be customized to operate as hybrids of the above. See `blockupdates`_ as an example. +.. NOTE:: Plugins are not limited to a singular functionality and can be customized to operate as hybrids of the above. See `blockupdates`_ as an example. -.. todo:: Austin: I don't love this page. The informations is too - shallow. +.. todo:: Austin: I don't love this page. The informations is too + shallow. + + Reply: I'd be inclined to add links out to the tutorial page for each + of the types. I think it's useful to have brief descriptions of each + type in one place, but putting each of the tutorials all on one page + is a bit much. It might also be a good idea to link to real plugins + that exemplify each type, though we won't have one of each type yet. .. _blockupdates: https://github.com/openrelayxyz/plugeth-plugins/tree/master/packages/blockupdates -