Merge pull request #4 from openrelayxyz/documentation

Documentation
This commit is contained in:
philip-morlier 2021-10-18 16:41:22 -07:00 committed by GitHub
commit 25a7f188e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1424 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
docs/_build/

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/plugeth-utils.iml" filepath="$PROJECT_DIR$/.idea/plugeth-utils.iml" />
</modules>
</component>
</project>

9
.idea/plugeth-utils.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

20
docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

54
docs/RPC_method.rst Normal file
View File

@ -0,0 +1,54 @@
.. _RPC_method:
===========
RPC Methods
===========
GetAPIs
*******
For **RPC Methods** a Get APIs method is required in the body of the plugin in order to make the plugin available. The bulk of the implementation will be in the MyService struct. MyService should be a struct which includes two public functions.
.. code-block:: Go
type MyService struct {
backend core.Backend
stack core.Node
}
func GetAPIs(stack core.Node, backend core.Backend) []core.API {
return []core.API{
{
Namespace: "plugeth",
Version: "1.0",
Service: &MyService{backend, stack},
Public: true,
},
}
}
RPC Method
**********
(**accurate heading?**)
For RPC calls, a function should have a ``context.Context`` object as the first argument, followed by an arbitrary number of JSON marshallable arguments, and return either a single JSON marshal object, or a JSON marshallable object and an error. The RPC framework will take care of decoding inputs to this function and encoding outputs, and if the error is non-nil it will serve an error response.
A simple implimentation would look like so:
**eventual link to documentation for hello or some other rpc plugin**
.. code-block:: Go
func (h *MyService) HelloWorld(ctx context.Context) string {
return "Hello World"
}
.. Note:: For plugins such as RPC Methods whcih impliment a
GetAPIs function, an **Initialize Node** function may not be necesary as the ``core.Node`` and ``core.Backend`` will be made available with GetAPIs.
Access
******
As with pre-built plugins, a``.so`` will need to be built from``main.go`` and moved into ``~/.ethereum/plugins``. Geth will need to be started with with a ``http.api=mymamespace`` flag. Additionally you will need to include a ``--http`` flag in order to access the standard json rpc methods.
The plugin can now be accessed with an rpc call to ``mynamespace_helloWorld``.

359
docs/api.rst Normal file
View File

@ -0,0 +1,359 @@
.. _api:
===
API
===
Anatomy of a Plugin
===================
Plugins for Plugeth use Golang's `Native Plugin System`_. Plugin modules must export variables using specific names and types. These will be processed by the plugin loader, and invoked at certain points during Geth's operations.
Flags
-----
* **Name:** Flags
* **Type:** `flag.FlagSet`_
* **Behavior:** This FlagSet will be parsed and your plugin will be able to access the resulting flags. Flags will be passed to Geth from the command line and are intended to of the plugin. Note that if any flags are provided, certain checks are disabled within Geth to avoid failing due to unexpected flags.
Subcommands
-----------
* **Name:** Subcommands
* **Type:** map[string]func(ctx `*cli.Context`_, args []string) error
* **Behavior:** If Geth is invoked with ``./geth YOUR_COMMAND``, the plugin loader will look for ``YOUR_COMMAND`` within this map, and invoke the corresponding function. This can be useful for certain behaviors like manipulating Geth's database without having to build a separate binary.
Initialize
----------
* **Name:** Initialize
* **Type:** func(*cli.Context, core.PluginLoader, core.logs )
* **Behavior:** Called as soon as the plugin is loaded, with the cli context and a reference to the plugin loader. This is your plugin's opportunity to initialize required variables as needed. Note that using the context object you can check arguments, and optionally can manipulate arguments if needed for your plugin.
InitializeNode
--------------
* **Name:** InitializeNode
* **Type:** func(core.Node, core.Backend)
* **Behavior:** This is called as soon as the Geth node is initialized. The core.Node object represents the running node with p2p and RPC capabilities, while the Backend gives you access to a wide array of data you may need to access.
.. note:: If a particular plugin requires access to the node.Node object it can be obtained using the restricted package located in `PluGeth-Utils`_.
GetAPIs
-------
* **Name:** GetAPIs
* **Type:** func(core.Node, core.Backend) []rpc.API
* **Behavior:** This allows you to register new RPC methods to run within Geth.
The GetAPIs function itself will generally be fairly brief, and will looks something like this:
.. code-block:: go
``func GetAPIs(stack *node.Node, backend core.Backend) []core.API {
return []rpc.API{
{
Namespace: "mynamespace",
Version: "1.0",
Service: &MyService{backend},
Public: true,
},
}
}``
The bulk of the implementation will be in the ``MyService`` struct. MyService should be a struct with public functions. These functions can have two different types of signatures:
* RPC Calls: For straight RPC calls, a function should have a ``context.Context`` object as the first argument, followed by an arbitrary number of JSON marshallable arguments, and return either a single JSON marshal object, or a JSON marshallable object and an error. The RPC framework will take care of decoding inputs to this function and encoding outputs, and if the error is non-nil it will serve an error response.
* Subscriptions: For subscriptions (supported on IPC and websockets), a function should have a ``context.Context`` object as the first argument followed by an arbitrary number of JSON marshallable arguments, and should return an ``*rpc.Subscription`` object. The subscription object can be created with ``rpcSub := notifier.CreateSubscription()``, and JSON marshallable data can be sent to the subscriber with ``notifier.Notify(rpcSub.ID, b)``.
A very simple MyService might look like:
.. code-block:: go
``type MyService struct{}
func (h MyService) HelloWorld(ctx context.Context) string {
return "Hello World"
}``
And the client could access this with an rpc call to
``mynamespace_helloworld``
Injected APIs
=============
In addition to hooks that get invoked by Geth, several objects are injected that give you access to additional information.
Backend Object
--------------
The ``core.Backend`` object is injected by the ``InitializeNode()`` and ``GetAPI()`` functions. It offers the following functions:
Downloader
**********
``Downloader() Downloader``
Returns a Downloader objects, which can provide Syncing status
SuggestGasTipCap
****************
``SuggestGasTipCap(ctx context.Context) (*big.Int, error)``
Suggests a Gas tip for the current block.
ExtRPCEnabled
*************
``ExtRPCEnabled() bool``
Returns whether RPC external RPC calls are enabled.
RPCGasCap
*********
``RPCGasCap() uint64``
Returns the maximum Gas available to RPC Calls.
RPCTxFeeCap
***********
``RPCTxFeeCap() float64``
Returns the maximum transaction fee for a transaction submitted via RPC.
UnprotectedAllowed
******************
``UnprotectedAllowed() bool``
Returns whether or not unprotected transactions can be transmitted through this
node via RPC.
SetHead
*******
``SetHead(number uint64)``
Resets the head to the specified block number.
HeaderByNumber
**************
``HeaderByNumber(ctx context.Context, number int64) ([]byte, error)``
Returns an RLP encoded block header for the specified block number.
The RLP encoded response can be decoded into a `plugeth-utils/restricted/types.Header` object.
HeaderByHash
************
``HeaderByHash(ctx context.Context, hash Hash) ([]byte, error)``
Returns an RLP encoded block header for the specified block hash.
The RLP encoded response can be decoded into a `plugeth-utils/restricted/types.Header` object.
CurrentHeader
*************
``CurrentHeader() []byte``
Returns an RLP encoded block header for the current block.
The RLP encoded response can be decoded into a `plugeth-utils/restricted/types.Header` object.
CurrentBlock
************
``CurrentBlock() []byte``
Returns an RLP encoded full block for the current block.
The RLP encoded response can be decoded into a `plugeth-utils/restricted/types.Block` object.
BlockByNumber
*************
``BlockByNumber(ctx context.Context, number int64) ([]byte, error)``
Returns an RLP encoded full block for the specified block number.
The RLP encoded response can be decoded into a `plugeth-utils/restricted/types.Block` object.
BlockByHash
***********
``BlockByHash(ctx context.Context, hash Hash) ([]byte, error)``
Returns an RLP encoded full block for the specified block hash.
The RLP encoded response can be decoded into a `plugeth-utils/restricted/types.Block` object.
GetReceipts
***********
``GetReceipts(ctx context.Context, hash Hash) ([]byte, error)``
Returns an JSON encoded list of receipts for the specified block hash.
The JSON encoded response can be decoded into a `plugeth-utils/restricted/types.Receipts` object.
GetTd
*****
``GetTd(ctx context.Context, hash Hash) *big.Int``
Returns the total difficulty for the specified block hash.
SubscribeChainEvent
*******************
``SubscribeChainEvent(ch chan<- ChainEvent) Subscription``
Subscribes the provided channel to new chain events.
SubscribeChainHeadEvent
***********************
``SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) Subscription``
Subscribes the provided channel to new chain head events.
SubscribeChainSideEvent
***********************
``SubscribeChainSideEvent(ch chan<- ChainSideEvent) Subscription``
Subscribes the provided channel to new chain side events.
SendTx
******
``SendTx(ctx context.Context, signedTx []byte) error``
Sends an RLP encoded, signed transaction to the network.
GetTransaction
**************
``GetTransaction(ctx context.Context, txHash Hash) ([]byte, Hash, uint64, uint64, error)``
Returns an RLP encoded transaction at the specified hash, along with the hash and number of the included block, and the transaction's position within that block.
GetPoolTransactions
^^^^^^^^^^^^^^^^^^^
``GetPoolTransactions() ([][]byte, error)``
Returns a list of RLP encoded transactions found in the mempool
GetPoolTransaction
******************
``GetPoolTransaction(txHash Hash) []byte``
Returns the RLP encoded transaction from the mempool at the specified hash.
GetPoolNonce
************
``GetPoolNonce(ctx context.Context, addr Address) (uint64, error)``
Returns the nonce of the last transaction for a given address, including
transactions found in the mempool.
Stats
*****
``Stats() (pending int, queued int)``
Returns the number of pending and queued transactions in the mempool.
TxPoolContent
*************
``TxPoolContent() (map[Address][][]byte, map[Address][][]byte)``
Returns a map of addresses to the list of RLP encoded transactions pending in
the mempool, and queued in the mempool.
SubscribeNewTxsEvent
********************
``SubscribeNewTxsEvent(chan<- NewTxsEvent) Subscription``
Subscribe to a feed of new transactions added to the mempool.
GetLogs
*******
``GetLogs(ctx context.Context, blockHash Hash) ([][]byte, error)``
Returns a list of RLP encoded logs found in the specified block.
SubscribeLogsEvent
******************
``SubscribeLogsEvent(ch chan<- [][]byte) Subscription``
Subscribe to logs included in a confirmed block.
SubscribePendingLogsEvent
*************************
``SubscribePendingLogsEvent(ch chan<- [][]byte) Subscription``
Subscribe to logs from pending transactions.
SubscribeRemovedLogsEvent
*************************
``SubscribeRemovedLogsEvent(ch chan<- []byte) Subscription``
Subscribe to logs removed from the canonical chain in reorged blocks.
Node Object
-----------
The ``core.Node`` object is injected by the ``InitializeNode()`` and ``GetAPI()`` functions. It offers the following functions:
Server
******
``Server() Server``
The Server object provides access to ``server.PeerCount()``, the number of peers connected to the node.
DataDir
*******
``DataDir() string``
Returns the Ethereuem datadir.
InstanceDir
***********
``InstanceDir() string``
Returns the instancedir used by the protocol stack.
IPCEndpoint
***********
``IPCEndpoint() string``
The path of the IPC Endpoint for this node.
HTTPEndpoint
************
``HTTPEndpoint() string``
The url of the HTTP Endpoint for this node.
WSEndpoint
**********
``WSEndpoint() string``
The url of the websockets Endpoint for this node.
ResolvePath
***********
``ResolvePath(x string) string``
Resolves a path within the DataDir.
.. _*cli.Context: https://pkg.go.dev/github.com/urfave/cli#Context
.. _flag.FlagSet: https://pkg.go.dev/flag#FlagSet
.. _Native Plugin System: https://pkg.go.dev/plugin
Logger
------
The Logger object is injected by the ``Initialize()`` function. It implements
logging based on the interfaces of `Log15 <https://github.com/inconshreveable/log15>`_.
.. _PluGeth-Utils: https://github.com/openrelayxyz/plugeth-utils
.. _*cli.Context: https://pkg.go.dev/github.com/urfave/cli#Context
.. _flag.FlagSet: https://pkg.go.dev/flag#FlagSet
.. _Native Plugin System: https://pkg.go.dev/plugin

82
docs/build.rst Normal file
View File

@ -0,0 +1,82 @@
.. _build:
================
Build and Deploy
================
.. contents:: :local:
If you are ready to start building your own plugins go ahead and start here.
Setting up the environment
**************************
.. NOTE:: Plugeth is built on a fork of `Geth`_ and as such requires familiarity with `Go`_ and a funtional `environment`_ in which to build Go projects. Thankfully for everyone Go provides a compact and useful `tutorial`_ as well as a `space for practice`_.
PluGeth is an application built in three seperate repositories.
* `PluGeth`_
* `PluGeth-Utils`_
* `PluGeth-Plugins`_
For our purposed here you will only need to clone Plugeth and Plugeth-Plugins. Once you have them cloned you are ready to begin. First we need to build geth though the PluGeth project. Navigate to ``plugeth/cmd/geth`` and run:
.. code-block:: shell
$ go get
This will download all dependencies needed for the project. This process will take a moment or two the first time through. Next run:
.. code-block:: shell
$ go build
Once this is complete you should see a ``.ethereum`` folder in your home directory.
At this point you are ready to start downloading local ethereum nodes. In order to do so, from ``plugeth/cmd/geth`` run:
.. code-block:: shell
$ ./geth
.. NOTE:: ``./geth`` is the primary command to build a *Mainnet* node. Building a mainnet node requires at least 8 GB RAM, 2 CPUs, and 350 GB of SSD disks. However, dozens of available flags will change the behavior of whichever network you choose to connect to. ``--help`` is your friend.
Build a plugin
**************
For the sake of this tutorial we will be building the Hello plugin. Navigate to ``plugethPlugins/packages/hello``. Inside you will see a ``main.go`` file. From this location run:
.. code-block:: shell
$ go build -buildmode=plugin
This will compile the plugin and produce a ``hello.so`` file. Move ``hello.so`` into ``~/.ethereum/plugins`` . In order to use this plugin geth will need to be started with a ``http.api=mymamespace`` flag. Additionally you will need to include a ``--http`` flag in order to access the standard json rpc methods.
Once geth has started you should see that the first ``INFO`` log reads: ``initialized hello`` . A new json rpc method, called hello, has been been appended to the list of available json rpc methods. In order to access this method you will need to ``curl`` into the network with this command:
.. code-block:: shell
$ curl 127.0.0.1:8545 -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"mynamespace_hello","params":[],"id":0}'
You should see that the network has responded with:
.. code-block:: shell
``{"jsonrpc":"2.0","id":0,"result":"Hello world"}``
Congradulations. You have just built and run your first Plugeth plugin. From here you can follow the steps above to build any of the plugins you choose.
.. NOTE:: Each plugin will vary in terms of the requirements to deploy. Refer to the documentation of the plugin itself in order to assure
that you know how to use it.
.. _space for practice: https://tour.golang.org/welcome/1
.. _tutorial: https://tour.golang.org/welcome/1
.. _environment: https://golang.org/doc/code
.. _Go: https://golang.org/doc/
.. _Geth: https://geth.ethereum.org/
.. _PluGeth: https://github.com/openrelayxyz/plugeth
.. _PluGeth-Utils: https://github.com/openrelayxyz/plugeth-utils
.. _PluGeth-Plugins: https://github.com/openrelayxyz/plugeth-plugins

65
docs/conf.py Normal file
View File

@ -0,0 +1,65 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'Plugeth'
copyright = '2021, Philip Morlier'
author = 'Philip Morlier, Austin Roberts'
# The full version, including alpha/beta/rc tags
release = 'Austin Roberts'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.intersphinx',
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.todo',
]
todo_include_todos=True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

10
docs/contact.rst Normal file
View File

@ -0,0 +1,10 @@
.. _contact:
====================
Get in touch with us
====================
We want to hear from you! The best way to reach the PluGeth team is through our Discord server. Drop in, say hello, we are anxious to hear your ideas and help work through any problems you may be having.
`Join our Discord <https://discord.gg/J3tQMWCVPn>`_

11
docs/core_restricted.rst Normal file
View File

@ -0,0 +1,11 @@
.. _core_restricted:
=========================
PluGeth-utils Subpackages
=========================
PluGeth-utils is separated into two main packages: core, and restricted.
The `core` package has been implemented by the Rivet team, and is licensed under the MIT license, allowing it to be used in open source and closed source plugins alike. Nearly all plugins will need to import plugeth-utils/core in order to
The `restricted` package copies code from the go-ethereum project, which means it must be licensed under the LGPL license. If you import plugeth-utils/restricted, you must be sure that your plugin complies with requirements of linking to LGPL code, which will usually require making your source code available to anyone you distribute the plugin to.

83
docs/custom.rst Normal file
View File

@ -0,0 +1,83 @@
.. _custom:
========================
Building a Custom Plugin
========================
.. toctree::
:hidden:
RPC_method
subscription
tracer
Before setting out to build a plugin it will be helpful to be familiar with the :ref:`types`. deifferent plugins will require different implimentation.
Basic Implementation
====================
In general, no matter which type of plugin you intend to build, all will share some common aspects.
Package
-------
Any plugin will need its own package located in the Plugeth-Plugins packages directory. The package will need to include a main.go from which the .so file will be built. The package and main file should share the same name and the name should be a word that describes the basic functionality of the plugin.
Initialize
----------
Most plugins will need to be initialized with an Initialize function. The initialize function will need to be passed at least three arguments: a cli.Context, core.PluginLoader, and a core.Logger.
And so, all plugins could have an intial template that looks something like this:
.. code-block:: Go
package main
import (
"github.com/openrelayxyz/plugeth-utils/core"
"gopkg.in/urfave/cli.v1"
)
var log core.Logger
func Initialize(ctx *cli.Context, loader core.PluginLoader, logger core.Logger) {
log = logger
log.Info("loaded New Custom Plugin")
}
InitializeNode
--------------
Many plugins will make use of the InitializeNode function. Implimentation will look like so:
.. code-block:: Go
func InitializeNode(stack core.Node, b core.Backend) {
backend = b
log.Info("Initialized node and backend")
}
This is called as soon as the Geth node is initialized. The core.Node object represents the running node with p2p and RPC capabilities, while the Backend gives you access to blocks and other data you may need to access.
Specialization
==============
From this point implimentation becomes more specialized to the particular plugin type. Continue from here for specific instructions for the following plugins:
* :ref:`RPC_method`
* :ref:`subscription`
* :ref:`tracer`
.. _blockupdates: https://github.com/openrelayxyz/plugeth-plugins/blob/master/packages/blockupdates/main.go
.. _hello: https://github.com/openrelayxyz/plugeth-plugins/blob/master/packages/hello/main.go

105
docs/hook_writing.rst Normal file
View File

@ -0,0 +1,105 @@
.. _hook_writing:
==================
Hook Writing Guide
==================
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 <contact>`
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.

121
docs/hooks.rst Normal file
View File

@ -0,0 +1,121 @@
.. _hooks:
=====================
Selected Plugin Hooks
=====================
Plugin Hooks
************
Plugeth provides several hooks from which the plugin can capture data from Geth. Additionally in the case of **subcommands** the provided hooks are designed to change the behavior of Geth.
Hooks are called from functions within the plugin. For example, if we wanted to bring in data from the StateUpdate hook. We would impliment it like so:
(from `blockupdates`_)
.. code-block:: Go
func StateUpdate(blockRoot core.Hash, parentRoot core.Hash, destructs map[core.Hash]struct{}, accounts map[core.Hash][]byte, storage map[core.Hash]map[core.Hash][]byte, codeUpdates map[core.Hash][]byte) {
su := &stateUpdate{
Destructs: destructs,
Accounts: accounts,
Storage: storage,
Code: codeUpdates,
}
cache.Add(blockRoot, su)
data, _ := rlp.EncodeToBytes(su)
backend.ChainDb().Put(append([]byte("su"), blockRoot.Bytes()...), data)
}
Many hooks can be deployed in an one plugin as is the case with the **BlockUpdater** plugin.
.. contents:: :local:
StateUpdate
***********
**Function Signature**:``func(root common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte)``
The state update plugin provides a snapshot of the state subsystem in the form of a a stateUpdate object. The stateUpdate object contains all information transformed by a transaction but not the transaction itself.
Invoked for each new block, StateUpdate provides the changes to the blockchain state. root corresponds to the state root of the new block. parentRoot corresponds to the state root of the parent block. destructs serves as a set of accounts that self-destructed in this block. accounts maps the hash of each account address to the SlimRLP encoding of the account data. storage maps the hash of each account to a map of that account's stored data.
.. warning:: StateUpdate is only called if Geth is running with
``-snapshots=true``. This is the default behavior for Geth, but if you are explicitly running with ``--snapshot=false`` this function will not be invoked.
AppendAncient
*************
**Function Signature**:``func(number uint64, hash, header, body, receipts, td []byte)``
Invoked when the freezer moves a block from LevelDB to the ancients database. ``number`` is the number of the block. ``hash`` is the 32 byte hash of the block as a raw ``[]byte``. ``header``, ``body``, and ``receipts`` are the RLP encoded versions of their respective block elements. ``td`` is the byte encoded total difficulty of the block.
GetRPCCalls
***********
**Function Signature**:``func(string, string, string)``
Invoked when the RPC handler registers a method call. Returns the call ``id``, method ``name``, and any ``params`` that may have been passed in.
.. todo:: missing a couple of hooks
PreProcessBlock
***************
**Function Signature**:``func(*types.Block)``
Invoked before the transactions of a block are processed. Returns a block object.
PreProcessTransaction
*********************
**Function Signature**:``func(*types.Transaction, *types.Block, int)``
Invoked before each individual transaction of a block is processed. Returns a transaction, block, and index number.
BlockProcessingError
********************
**Function Signature**:``func(*types.Transaction, *types.Block, error)``
Invoked if an error occurs while processing a transaction. This only applies to errors that would unvalidate the block were this transaction is included not errors such as reverts or opcode errors. Returns a transaction, block, and error.
NewHead
*******
**Function Signature**:``func(*types.Block, common.Hash, []*types.Log)``
Invoked when a new block becomes the canonical latest block. Returns a block, hash, and log.
.. note:: If severtal blocks are processed in a group (such as
during a reorg) this may not be called for each block. You should track the prior latest head if you need to process intermediate blocks.
NewSideBlock
************
**Function Signature**:``func(*types.Block, common.Hash, []*types.Log)``
Invoked when a block is side-chained. Returns a block, has, and logs.
.. note:: Blocks passed to this method are non-canonical blocks.
Reorg
*****
**Function Signature**:``func(common *types.Block, oldChain, newChain types.Blocks)``
Invoked when a chain reorg occurs, that is; at least one block is removed and one block is added. (``oldChain`` is a list of removed blocks, ``newChain`` is a list of newliy added blocks, and ``common`` is the latest block that is an ancestor to both oldChain and newChain.) Returns a block, a list of old blocks, and a list of new blocks.
.. _blockupdates: https://github.com/openrelayxyz/plugeth-plugins/tree/master/packages/blockupdates
.. _StateUpdate: https://github.com/openrelayxyz/plugeth/blob/develop/core/state/plugin_hooks.go
.. _Invocation: https://github.com/openrelayxyz/plugeth/blob/develop/core/state/statedb.go#L955
.. _AppendAncient: https://github.com/openrelayxyz/plugeth/blob/develop/core/rawdb/plugin_hooks.go
.. _GetRPCCalls: https://github.com/openrelayxyz/plugeth/blob/develop/rpc/plugin_hooks.go
.. _NewHead: https://github.com/openrelayxyz/plugeth/blob/develop/core/plugin_hooks.go#L108

62
docs/index.rst Normal file
View File

@ -0,0 +1,62 @@
=======
PluGeth
=======
PluGeth is a fork of the Go Ethereum Client, `Geth`_, that implements a plugin architecture allowing developers to extend Geth's capabilities in a number of different ways using plugins rather than having to create additional new forks.
From Here:
----------
- Ready for an overview of the project and some context? :ref:`project`
- If your goal is to run existing plugns without sourcecode: :ref:`install`
- If your goal is to build and deploy existing plugins or make custom plugins: :ref:`build`
- If your goal is to build cutsom plugins: :ref:`custom`
.. warning:: Right now PluGeth is in early development. We are
still settling on some of the plugin APIs, and are
not yet making official releases. From an operational
perspective, PluGeth should be as stable as upstream Geth less whatever instability is added by plugins you might run. But if you plan to run PluGeth today, be aware that furture updates will likely break your plugins.
Table of Contents
*****************
.. toctree::
:maxdepth: 1
:caption: Overview
project
types
.. toctree::
:maxdepth: 1
:caption: Tutorials
install
build
custom
.. toctree::
:maxdepth: 1
:caption: Reference
system_req
version
api
plugin_loader
hooks
hook_writing
core_restricted
.. toctree::
:maxdepth: 1
:caption: Contact
contact
.. _Geth: https://geth.ethereum.org/

27
docs/install.rst Normal file
View File

@ -0,0 +1,27 @@
.. _install:
=======
Install
=======
PluGeth provides a pre-built PluGeth-Geth node as well as pre-built plugins available for download.
First download the latest PluGeth `binary release`_.
Our curated list of plugin builds can be found `here`_
After downloading, move the .so files into the ~/.ethereum/plugins directory.
.. _binary release: https://github.com/openrelayxyz/plugeth/releases
.. _here: https://github.com/openrelayxyz/plugeth-plugins/releases

35
docs/make.bat Normal file
View File

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

62
docs/plugin_loader.rst Normal file
View File

@ -0,0 +1,62 @@
.. _plugin_loader:
=============
Plugin Loader
=============
The Plugin Loader is provided to each Plugin through the Initialize()``
function. It provides plugins with:
Lookup
======
``Lookup(name string, validate func(interface{}) bool) []interface{}``
Returns a list of values from plugins identified by ``name``, which match the
provided ``validate`` predicate. For example:
.. code-block:: go
pl.Lookup("Version", func(item interface{}) bool {
_, ok := item.(int)
return ok
})
Would return a list of ``int`` objects named ``Version`` in any loaded plugins.
This can enable Plugins to interact with each other, accessing values and
functions implemented in other plugins.
GetFeed
=======
``GetFeed() Feed``
Returns a new feed that the plugin can used for publish/subscribe models.
For example:
.. code-block:: go
feed := pl.GetFeed()
go func() {
ch := make(chan string)
sub := feed.Subscribe(ch)
for {
select {
case item := <-ch:
// Do something with item
case err := <sub.Err():
log.Error("An error has occurred", "err", err)
sub.Unsubscribe()
close(ch)
return
}
}
}()
feed.Send("hello")
feed.Send("world")
Note that you can send any type through a feed, but the subscribed channel and
sent objects must be of matching types.

51
docs/project.rst Normal file
View File

@ -0,0 +1,51 @@
.. _project:
==============
Project Design
==============
Design Goals
============
Upstream Geth exists primarily to serve as a client for the Ethereum mainnet, though it also supports a number of popular testnets. Supporting the Ethereum mainnet is a big enough challenge in its own right that the Geth team generally avoids changes to support other networks, or to provide features only a small handful of users would be interested in.
The result is that many projects have forked Geth. Some implement their own consensus protocols or alter the behavior of the EVM to support other networks. Others are designed to extract information from the Ethereum mainnet in ways the standard Geth client does not support.
Creating numerous different forks to fill a variety of different needs comes with a number of drawbacks. Forks tend to drift apart from each other. Many networks that forked from Geth long ago have stopped merging updates; this makes some sense, given that those networks have moved in different directions than Geth and merging upstream changes while properly maintaining consensus rules of an existing network could prove quite challenging. But not merging changes from upstream can mean that security updates are easily missed, especially when the upstream team `obscures security updates as optimizations`_ as a matter of process.
PluGeth aims to provide a single Geth fork that developers can choose to extend rather than forking the Geth project. Out of the box, PluGeth behaves exactly like upstream Geth, but by installing plugins written in Golang, developers can extend its functionality in a wide variety of ways.
Three Repositories
==================
PluGeth is an application built in three repositories:
`PluGeth`_
----------
The largest of the three Repositories, PluGeth is a fork of Geth which has been modified to enable a plugin architecture. The Plugin loader, wrappers, and hooks all reside in this repository.
`PluGeth-Utils`_
----------------
Utils are small packages used to develop PluGeth plugins without Geth dependencies. For a more detailed analysis of the reasons see :ref:`core_restricted`. Imports from Utils happen automatically and so most users need not clone a local version.
`PluGeth-Plugins`_
------------------
The packages from which plugins are buile are stored here. This repository contains premade plugins as well as providing a location for storing new custom plugins.
Version Control
*****************
In order to ensure that the the project can compile and is up to date see: :ref:`version`; to be familiar with dependencies and requirements.
.. _obscures security updates as optimizations: https://blog.openrelay.xyz/vulnerability-lifecycle-framework-geth/
.. _PluGeth: https://github.com/openrelayxyz/plugeth
.. _PluGeth-Utils: https://github.com/openrelayxyz/plugeth-utils
.. _PluGeth-Plugins: https://github.com/openrelayxyz/plugeth-plugins

93
docs/subscription.rst Normal file
View File

@ -0,0 +1,93 @@
.. _subscription:
============
Subscription
============
In addition to the initial template containing an intialize function, plugins providing **Subscriptions** will require two additional elements.
GetAPIs
*******
A GetAPIs method is required in the body of the plugin in order to make the plugin available. The bulk of the implementation will be in the MyService struct. MyService should be a struct which includes two public functions.
.. code-block:: Go
type MyService struct {
backend core.Backend
stack core.Node
}
func GetAPIs(stack core.Node, backend core.Backend) []core.API {
return []core.API{
{
Namespace: "plugeth",
Version: "1.0",
Service: &MyService{backend, stack},
Public: true,
},
}
}
Subscription Function
*********************
For subscriptions (supported on IPC and websockets), a function should take MyService as a reciever and a context.Context object as an argument and return a channel and an error. The following is a subscription function that impliments a timer.
.. code-block:: Go
func (*myservice) Timer(ctx context.Context) (<-chan int64, error) {
ticker := time.NewTicker(time.Second)
ch := make(chan int64)
go func() {
defer ticker.Stop()
for {
select {
case <-ctx.Done():
close(ch)
return
case t := <-ticker.C:
ch <- t.UnixNano()
}
}
}()
return ch, nil
}
.. warning:: Notice in the example above, the ``ctx.Done()`` or
Context.Done() method closes the channel. If this is not present the go routine will run for the life of the process.
Access
******
.. Note:: Plugins providing subscriptions can be accessed via IPC
and websockets. In the below example we will be using `wscat`_ to connect a websocket to a local Geth node.
As with pre-built plugins, a ``.so`` will need to be built from ``main.go`` and moved into ``~/.ethereum/plugins``. Geth will need to be started with with ``--ws --ws.api=mynamespace``flags. Additionally you will need to include a ``--http`` flag in order to access the standard json rpc methods.
After starting Geth, from a seperate terminal run:
.. code-block:: shell
wscat -c ws://127.0.0.1:8546
.. Note:: Websockets are available via port 8546
Once the connection has been established from the websocket cursor enter the following argument:
.. code-block:: shell
{"jsonrpc":"2.0","method":"mynamespace_hello","params":[],"id":0}
You should see that the network has responded with:
.. code-block:: shell
``{"jsonrpc":"2.0","id":0,"result":"Hello world"}``
.. _wscat: https://www.npmjs.com/package/wscat

9
docs/system_req.rst Normal file
View File

@ -0,0 +1,9 @@
.. _system_req:
===================
System Requirements
===================
System requirements will vary depending on which network you are connecting to. On the Ethereum mainnet, you should have at least 8 GB RAM, 2 CPUs, and 350 GB of SSD disks.
PluGeth relies on Golang's Plugin implementation, which is only supported on Linux, FreeBSD, and macOS. Windows support is unlikely to be added in the foreseeable future.

75
docs/tracer.rst Normal file
View File

@ -0,0 +1,75 @@
.. _tracer:
======
Tracer
======
In addition to the initial template containing an intialize function, plugins providing **Tracers** will require three additional elements.
.. Warning:: Caution: Modifying of the values passed into tracer
functions can alter the results of the EVM execution in unpredictable ways. Additionally, some objects may be reused across calls, so data you wish to capture should be copied rather than retained be reference.
MyService Struct
****************
First an empty MyService Struct.
.. code-block:: Go
type MyService struct {
}
Map
***
Next, a map of tracers to functions returning a ``core.TracerResult`` which will be implimented like so:
.. code-block:: Go
var Tracers = map[string]func(core.StateDB) core.TracerResult{
"myTracer": func(core.StateDB) core.TracerResult {
return &MyBasicTracerService{}
},
}
TracerResult Functions
**********************
Finally a series of functions which points to the MyService struct and coresponds to the interface which geth anticipates.
.. code-block:: Go
func (b *MyBasicTracerService) CaptureStart(from core.Address, to core.Address, create bool, input []byte, gas uint64, value *big.Int) {
}
func (b *MyBasicTracerService) CaptureState(pc uint64, op core.OpCode, gas, cost uint64, scope core.ScopeContext, rData []byte, depth int, err error) {
}
func (b *MyBasicTracerService) CaptureFault(pc uint64, op core.OpCode, gas, cost uint64, scope core.ScopeContext, depth int, err error) {
}
func (b *MyBasicTracerService) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
}
func (b *MyBasicTracerService) CaptureEnter(typ core.OpCode, from core.Address, to core.Address, input []byte, gas uint64, value *big.Int) {
}
func (b *MyBasicTracerService) CaptureExit(output []byte, gasUsed uint64, err error) {
}
func (b *MyBasicTracerService) Result() (interface{}, error) { return "hello world", nil }
Access
******
As with pre-built plugins, a ``.so`` will need to be built from ``main.go`` and moved into ``~/.ethereum/plugins``. Geth will need to be started with with a ``--http.api+debug`` flag.
From a terminal pass the following argument to the api:
.. code-block:: shell
curl 127.0.0.1:8545 -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"debug_traceCall","params":[{"to":"0x32Be343B94f860124dC4fEe278FDCBD38C102D88"},"latest",{"tracer":"myTracer"}],"id":0}'
.. Note:: The address used above is a test adress and will need to
be replaced by whatever address you wish to access. Also ``traceCall`` is one of several methods available for use.
If using the template above, the call should return:
.. code-block:: shell
{"jsonrpc":"2.0","id":0,"result":"hello world"}

35
docs/types.rst Normal file
View File

@ -0,0 +1,35 @@
.. _types:
======================
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.
.. contents:: :local:
RPC Methods
-----------
These plugins provide new json rpc methods to access several objects containing real time and historic data.
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.
Tracers
-------
Tracers rely on historic data recompiled after execution to give insight into a transaction.
Subscriptions
-------------
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.
.. _blockupdates: https://github.com/openrelayxyz/plugeth-plugins/tree/master/packages/blockupdates

47
docs/version.rst Normal file
View File

@ -0,0 +1,47 @@
.. _version:
=======
Version
=======
PluGeth is separated into three packages in order to minimize dependency conflicts. Golang plugins cannot include different versions of the same packages as the program loading the plugin. If plugins had to import packages from PluGeth itself, a plugin build could only be loaded by that same version of PluGeth. By separating out the PluGeth-utils package, both PluGeth and the plugins must rely on the same version of PluGeth-utils, but plugins can be compatible with any version of PluGeth compiled with the same version of PluGeth-utils.
PluGeth builds will follow the naming convention:
.. code-block:: shell
geth-$PLUGETH_UTILS_VERSION-$GETH_VERSION-$RELEASE
For example:
.. code-block:: shell
geth-0.1.0-1.10.8-0
Tells us that:
* PluGeth-utils version is 0.1.0
* Geth version is 1.10.8
* This is the first release with that combination of dependencies.
Plugin builds will follow the naming convention:
.. code-block:: shell
$PLUGIN_NAME-$PLUGETH_UTILS_VERSION-$PLUGIN_VERSION
For example:
.. code-block:: shell
blockupdates-0.1.0-1.0.2
Tells us that:
* The plugin is "blockupdates"
* The PluGeth-utils version is 0.1.0
* The plugin version is 1.0.2
When a Geth update comes out, you can expect a release of `geth-0.1.0-1.10.9-0`, which will be compatible with the same set of plugins.
When PluGeth upgrades are necessary, plugins will need to be recompiled. Whenever possible, we will try to avoid forcing plugins to be recompiled for an immediate Geth upgrade. For example, if we have geth-0.1.0-1.10.8, and upgrade PluGeth-utils, we will have a geth-0.1.1-1.10.8, followed by a geth-0.1.1-1.10.9. This will give users time to upgrade plugins from PluGeth-utils 0.1.0 to 0.1.1 while staying on Geth 1.10.8, and when it is time to upgrade to Geth 1.10.9 they can continue using the plugins they were using with geth 1.10.8. Depending on upgrades to Geth, it may not always be possible to maintain compatibility with existing PluGeth versions, which will be noted in release notes.