PluGeth¶
The Geth fork to end all Forks.
PluGeth is a fork of the Go Ethereum Client, Geth, implementing the Golang plugin architecture allowing developers to adapt and extend Geth’s capabilities using plugins rather than having to create additional new forks.
The PluGeth project aims to provide a secure and versitile tool for anyone who needs to run a Geth (or Geth-derived) node client that supports features beyond those offered by Geth’s vanilla EVM.
All dependencies and updates are handled by the PluGeth project, and so, PluGeth enables developers to focus on their projects without having to maintian upstream code.
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. 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, 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 PluGeth-utils Subpackages. 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¶
Before using Plugeth users are enocuraged to familiarize themselves with the version control scheme of the project.
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.
RPC Methods¶
These plugins provide new json rpc methods to access several objects containing real time and historic data.
Subcommand¶
A subcommand redefines 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 the 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 are used to collect information about a transaction or block during EVM execution. They can be written to refine the results of a debug
trace or written to delver custom data using various other objects available from the PluGeth APIs.
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.
Install¶
Note
Prior to install make sure to be familiar with system requirements.
PluGeth can be installed in two ways. The repositories can be cloned and compiled from the source code. Alternatively PluGeth provides binaries of a PluGeth node as well as plugins.
In order to run PluGeth without the source code, download the latest release here.
The curated list of plugin builds can be found here
Note
Make sure versions of PluGeth and plugins are compatable see: version control.
After downloading plugins, move the .so
files into the ~/.ethereum/plugins
directory.
Note
The above location may change when changing --datadir
.
Build and Deploy¶
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.
For the purposes 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 PluGeth though the PluGeth project. Navigate to plugeth/cmd/geth
and run:
$ 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:
$ go build
At this point you are ready to start downloading local ethereum nodes. In order to do so, from plugeth/cmd/geth
run:
$ ./geth
Build your first 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:
$ 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=mynamespace
flag. Additionally you will need to include a --http
flag in order to access the standard json rpc methods.
Note
The above location may change when changing --datadir
.
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:
$ 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:
``{"jsonrpc":"2.0","id":0,"result":"Hello world"}``
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.
Writing a Custom Plugin¶
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.
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
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=mynamespace
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
.
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.
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 implements a timer.
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 --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:
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:
{"jsonrpc":"2.0","method":"mynamespace_hello","params":[],"id":0}
You should see that the network has responded with:
``{"jsonrpc":"2.0","id":0,"result":"Hello world"}``
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.
type MyService struct {
}
Map¶
Next, a map of tracers to functions returning a core.TracerResult
which will be implemented like so:
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.
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:
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:
{"jsonrpc":"2.0","id":0,"result":"hello world"}
Before setting out to write a plugin it will be helpful to be familiar with the Basic Types of Plugins. Different plugins will require different implementation.
Basic Implementation¶
All plugins will share some common aspects.
Package¶
Any plugin will need its own package located in plugeth-plugins/packages
. 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 a func Initialize
. The initialize function will need to be passed at least three arguments: cli.Context
, core.PluginLoader
, core.Logger
.
All plugins could have an intial template that looks something like this:
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 func InitializeNode
.
func InitializeNode(stack core.Node, b core.Backend) {
backend = b
log.Info("Initialized node and backend")
}
InitializeNode
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 core.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:
Existing Pluings¶
getRPCCalls¶
getRPCCalls is a subcommand written to print a log containing information about RPC methods upon execution. Namely the id, method name, and parameters into the method.
Usage¶
Once compiled the plugin will execute automatically as RPC methods are passed into the api.
isSynced¶
The isSynced plugin was designed as an extention of the eth_syncing
method available on standard Geth. plugeth_isSynced
was desinged to return a status object such that a status report could be given as to the current state of the node as opposed to eth_syncing
which returns the status object only if the node is actively syncing and a simple false if frozen or fully synced.
Usage¶
As with all rpc
methods, isSynced is available by curl
or the javascript console.
From the command line using the curl
command:
{"method": "plugeth_isSynced", "params": []}
Which will return:
"activePeers": true,
"currentBlock": "0x60e880",
"healedBytecodeBytes": "0x0",
"healedBytecodes": "0x0",
"healedTrienodeBytes": "0x0",
"healedTrienodes": "0x0",
"healingBytecode": "0x0",
"healingTrienodes": "0x0",
"highestBlock": "0x60e880",
"nodeIsSynced": true,
"startingBlock": "0x0",
"syncedAccountBytes": "0x0",
"syncedAccounts": "0x0",
"syncedBytecodeBytes": "0x0",
"syncedBytecodes": "0x0",
"syncedStorage": "0x0",
"syncedStorageBytes": "0x0"
blockTracer¶
Blocktracer is an subscription plugin written such that for each block mined, blockTracer will return a json payload reporting the type, from and to addresses, gas, gas used, input, output, and calls made for each transaction. The data will stream in real time as the block is mined.
Usage¶
As with any websocket an initial connection will need to be established.
Here we are using wscat to connect to local host port 8556.
wscat -c "http://127.0.0.1:8556"
Once the connection has been made the method as well as blockTracer parameter will be passed in.
{"method":"plugeth_subscribe","params":["traceBlock"],"id":0}
Which will return a streaming result similar to the one below.
"type":"CALL","from":"0x75d5e88adf8f3597c7c3e4a930544fb48089c779","to":"0x9ac40b4e6a0c60ca54a7fa2753d65448e6a71ecb","gas":"0x58cc2","gasUsed":"0x6007","input":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028d2f41e4c1dfca58114457fbe07632cabbfb9d900000000000000000000000000000000000000000000000000000000001db898fbdbdd5c","output":"0x0000000000000000000000000000000000000000000000000000000000000000","calls":[{"type":"DELEGATECALL","from":"0x9ac40b4e6a0c60ca54a7fa2753d65448e6a71ecb","to":"0xae9a8ae28d55325dff2af4ed5fe2335c1a39139b","gas":"0x56308","gasUsed":"0x4c07","input":"0x0000000000000000000000000000000000000000000000000000000000000000abbfb9d900000000000000000000000000000000000000000000000000000000001db8980000000000000000000000000000000000000000035298ac0ba8bb05fbdbdd5c","output":"0x0000000000000000000000000000000000000000000000000000000000000000"}]}]}]}}
System Requirements¶
If compiling from source code Plugeth requires that the user have Go version 1.17 or later.
Our provided release binaries require Glibc 2.29.
Note
System requirements vary depending on the network being run. For detailed requirements Refer to Geth documentation.
Warning
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.
Version Control¶
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:
geth-$PLUGETH_UTILS_VERSION-$GETH_VERSION-$RELEASE
For example:
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:
$PLUGIN_NAME-$PLUGETH_UTILS_VERSION-$PLUGIN_VERSION
For example:
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.
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 configure the behavior of the plugin. Passed flags must follow
--
to be parsed by this FlagSet, which is necessary to avoid Geth 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 forYOUR_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:
``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 withrpcSub := notifier.CreateSubscription()
, and JSON marshallable data can be sent to the subscriber withnotifier.Notify(rpcSub.ID, b)
.
A very simple MyService might look like:
``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.
Logger¶
The Logger object is injected by the Initialize()
function. It implements
logging based on the interfaces of Log15.
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:
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 use for publish/subscribe models.
For example:
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.
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)
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 one plugin as is the case with the BlockUpdater plugin.
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 logs.
Note
If several 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, hash, 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.
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 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:
// 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.
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.
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.