plugeth/README.md

200 lines
8.7 KiB
Markdown
Raw Normal View History

# PluGeth
PluGeth is a fork of the [Go Ethereum Client](https://github.com/ethereum/go-ethereum)
(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 of Geth.
2021-06-28 18:02:05 +00:00
## WARNING: UNSTABLE API
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
2021-06-30 13:49:23 +00:00
perspective, PluGeth should be as stable as upstream Geth less whatever
2021-06-28 18:02:05 +00:00
instability is added by plugins you might run. But if you plan to run PluGeth
today, be aware that future updates will likely break your plugins.
2021-06-28 17:53:50 +00:00
## 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.
## Design Goals
The upstream Geth client 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 from Geth;
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](https://blog.openrelay.xyz/vulnerability-lifecycle-framework-geth/)
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 way.
## Anatomy of a Plugin
Plugins for Plugeth use Golang's [Native Plugin System](https://golang.org/pkg/plugin/).
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.
### API
#### Flags
* **Name**: Flags
* **Type**: [flag.FlagSet](https://golang.org/pkg/flag/#FlagSet)
* **Behavior**: This FlagSet will be parsed and your plugin will be able to
access the resulting flags. 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](https://pkg.go.dev/github.com/urfave/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.
#### Tracers
* **Name**: Tracer
* **Type**: map[string]TracerResult
* **Behavior**: When calling debug.traceX functions (such as debug_traceCall
and debug_traceTransaction) the tracer can be specified as a key to this map
and the tracer used will be the TracerResult specified here. TracerResult
objects must match the interface:
2015-02-05 20:34:47 +00:00
2019-06-13 13:23:22 +00:00
```
// CaptureStart is called at the start of each transaction
2021-07-14 18:50:17 +00:00
CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {}
// CaptureState is called for each opcode
2021-07-14 18:50:17 +00:00
CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {}
// CaptureFault is called when an error occurs in the EVM
2021-07-14 18:50:17 +00:00
CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {}
// CaptureEnd is called at the end of each transaction
2021-07-14 18:50:17 +00:00
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
// GetResult should return a JSON serializable result object to respond to the trace call
2021-07-14 18:50:17 +00:00
GetResult() (interface{}, error) {}
2015-04-28 10:13:42 +00:00
2019-06-13 13:23:22 +00:00
```
* **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 by reference.
#### LiveTracer
2014-01-11 14:27:08 +00:00
* **Name**: LiveTracer
* **Type**: vm.Tracer
* **Behavior**: This tracer is used for tracing transactions as they are
processed within blocks. Note that if a block does not validate, some
transactions may be processed that don't end up in blocks, so be sure to
check transactions against finalized blocks.
2014-02-15 10:49:29 +00:00
2021-06-28 17:53:50 +00:00
The interface for a vm.Tracer is similar to a TracerResult (above), but does
not require a `GetResult()` function.
2021-06-28 17:53:50 +00:00
2021-07-14 18:50:17 +00:00
#### GetAPIs
* **Name**: GetAPIs
* **Type**: func(*node.Node, interfaces.Backend) []rpc.API
* **Behavior**: This allows you to register new RPC methods to run within Geth.
* **Example**:
The GetAPIs function itself will generally be fairly brief, and will looks
something like this:
```
func GetAPIs(stack *node.Node, backend plugins.Backend) []rpc.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:
```
type MyService struct{}
func (h *MyService) HelloWorld(ctx context.Context) string {
return "Hello World"
}
```
And the user could then access this with
2021-06-28 17:53:50 +00:00
## Extending The Plugin API
2021-07-14 18:50:17 +00:00
.Lookup("GetAPIs", func(item interface{}) bool {
.Lookup("InitializeNode", func(item interface{}) bool {
.Lookup("PreProcessBlock", func(item interface{}) bool {
.Lookup("PreProcessTransaction", func(item interface{}) bool {
.Lookup("BlockProcessingError", func(item interface{}) bool {
.Lookup("PostProcessTransaction", func(item interface{}) bool {
.Lookup("PostProcessBlock", func(item interface{}) bool {
.Lookup("NewHead", func(item interface{}) bool {
.Lookup("NewSideBlock", func(item interface{}) bool {
.Lookup("Reorg", func(item interface{}) bool {
.Lookup("AppendAncient", func(item interface{}) bool {
.Lookup("StateUpdate", func(item interface{}) bool {
.Lookup("CreateConsensusEngine", func(item interface{}) bool {
.Lookup("UpdateBlockchainVMConfig", func(item interface{}) bool {
.Lookup("BlockUpdates", func(item interface{}) bool {
.Lookup("Subcommands")
.Lookup("Initialize", func(i interface{}) bool {
2021-06-28 17:53:50 +00:00
While we can imagine lots of ways plugins might like to extract or change
information in Geth, we're trying not to go too crazy with the plugin API based
purely on hypotheticals. The Plugin API in its current form reflects the needs
of projects currently building on PluGeth, and we're happy to extend it for
people who are building something. If you're trying to do something that isn't
supported by the current plugin system, we're happy to help. Reach out to us on
2021-06-28 17:55:48 +00:00
[Discord](https://discord.gg/Epf7b7Gr) and we'll help you figure out how to
make it work.