Plugin Interface Refactor

This makes two main changes to the plugin system:

* Instead of assuming that each plugin will have exactly one type,
  inspect each plugin to see which interfaces it provides, and
  register it as a provider of each provided interface. This can
  allow a single .so file to provide multiple interfaces, which
  will likely be necessary for aggregating certain types of info.
* Rather than using dependency injection and having to propagate
  the plugin system all throughout Geth, have a default plugin
  loader so we need only import the module and make calls to it.
  If the plan were to integrate this into mainline Geth, I would
  say we use dependency injection and take the time to pass the
  plugin loader throughout the codebase, but as I expect this to
  be a fork that has to pull upstream changes, this approach
  should make merge conflicts much less common.
This commit is contained in:
Austin Roberts 2021-06-25 10:57:56 -05:00
parent ff46e3c7f8
commit 5c55657c54
4 changed files with 58 additions and 53 deletions

View File

@ -20,6 +20,7 @@ package main
import (
"fmt"
"os"
"path"
"sort"
"strconv"
"strings"
@ -39,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/plugins"
"gopkg.in/urfave/cli.v1"
)
@ -311,13 +313,18 @@ func prepare(ctx *cli.Context) {
// It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down.
func geth(ctx *cli.Context) error {
if err := plugins.Initialize(path.Join(ctx.GlobalString(utils.DataDirFlag.Name), "plugins")); err != nil { return err }
if ok, err := plugins.RunSubcommand(ctx); ok { return err }
if !plugins.ParseFlags(ctx.Args()) {
if args := ctx.Args(); len(args) > 0 {
return fmt.Errorf("invalid command: %q", args[0])
}
}
prepare(ctx)
stack, backend := makeFullNode(ctx)
defer stack.Close()
stack.RegisterAPIs(plugins.GetAPIs(stack, backend))
startNode(ctx, stack, backend)
stack.Wait()

View File

@ -24,7 +24,7 @@ import (
type Backend interface {
// General Ethereum API
Downloader() *downloader.Downloader
SuggestPrice(ctx context.Context) (*big.Int, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
ChainDb() ethdb.Database
AccountManager() *accounts.Manager
ExtRPCEnabled() bool
@ -46,7 +46,7 @@ type Backend interface {
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
GetTd(ctx context.Context, hash common.Hash) *big.Int
GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error)
GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, cfg *vm.Config) (*vm.EVM, func() error, error)
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription

View File

@ -17,16 +17,6 @@ import (
type APILoader func(*node.Node, Backend) []rpc.API
type Subcommand func(*cli.Context, []string) error
type PluginType int
const (
TracerPluginType PluginType = iota
StateHookType
ChainEventHookType
RPCPluginType
SubcommandType
)
type PluginLoader struct{
TracerPlugins map[string]interface{} // TODO: Set interface
@ -37,6 +27,8 @@ type PluginLoader struct{
Flags []*flag.FlagSet
}
var defaultPluginLoader *PluginLoader
func NewPluginLoader(target string) (*PluginLoader, error) {
pl := &PluginLoader{
@ -52,7 +44,7 @@ func NewPluginLoader(target string) (*PluginLoader, error) {
for _, file := range files {
fpath := path.Join(target, file.Name())
if !strings.HasSuffix(file.Name(), ".so") {
log.Warn("File inplugin directory is not '.so' file. Skipping.", "file", fpath)
log.Debug("File inplugin directory is not '.so' file. Skipping.", "file", fpath)
continue
}
plug, err := plugin.Open(fpath)
@ -70,54 +62,39 @@ func NewPluginLoader(target string) (*PluginLoader, error) {
pl.Flags = append(pl.Flags, flagset)
}
}
t, err := plug.Lookup("Type")
if err != nil {
log.Warn("Could not load plugin Type", "file", fpath, "error", err.Error())
continue
}
switch pt := t.(*PluginType); *pt {
case RPCPluginType:
fn, err := plug.Lookup("GetAPIs")
if err != nil {
log.Warn("Could not load GetAPIs from plugin", "file", fpath, "error", err.Error())
continue
}
if err == nil {
apiLoader, ok := fn.(func(*node.Node, Backend) []rpc.API)
if !ok {
log.Warn("Could not cast plugin.GetAPIs to APILoader", "file", fpath)
continue
}
} else {
pl.RPCPlugins = append(pl.RPCPlugins, APILoader(apiLoader))
case SubcommandType:
n, err := plug.Lookup("Name")
if err != nil {
log.Warn("Could not load Name from subcommand plugin", "file", fpath, "error", err.Error())
continue
}
name, ok := n.(*string)
} else { log.Debug("Error retrieving GetAPIs for plugin", "file", fpath, "error", err.Error()) }
sb, err := plug.Lookup("Subcommands")
if err == nil {
subcommands, ok := sb.(map[string]func(*cli.Context, []string) error)
if !ok {
log.Warn("Could not cast plugin.Name to string", "file", fpath)
continue
log.Warn("Could not cast plugin.Subocmmands to `map[string]func(*cli.Context, []string) error`", "file", fpath)
} else {
for k, v := range subcommands {
if _, ok := pl.Subcommands[k]; ok {
log.Warn("Subcommand redeclared", "file", fpath, "subcommand", k)
}
fn, err := plug.Lookup("Main")
if err != nil {
log.Warn("Could not load Main from plugin", "file", fpath, "error", err.Error())
continue
pl.Subcommands[k] = v
}
subcommand, ok := fn.(func(*cli.Context, []string) error)
if !ok {
log.Warn("Could not cast plugin.Main to Subcommand", "file", fpath)
continue
}
if _, ok := pl.Subcommands[*name]; ok {
return pl, fmt.Errorf("Multiple subcommand plugins with the same name: %v", *name)
}
pl.Subcommands[*name] = subcommand
}
}
return pl, nil
}
func Initialize(target string) (err error) {
defaultPluginLoader, err = NewPluginLoader(target)
return err
}
func (pl *PluginLoader) RunSubcommand(ctx *cli.Context) (bool, error) {
args := ctx.Args()
if len(args) == 0 { return false, fmt.Errorf("No subcommand arguments")}
@ -126,6 +103,11 @@ func (pl *PluginLoader) RunSubcommand(ctx *cli.Context) (bool, error) {
return true, subcommand(ctx, args[1:])
}
func RunSubcommand(ctx *cli.Context) (bool, error) {
if defaultPluginLoader == nil { return false, fmt.Errorf("Plugin loader not initialized") }
return defaultPluginLoader.RunSubcommand(ctx)
}
func (pl *PluginLoader) ParseFlags(args []string) bool {
for _, flagset := range pl.Flags {
flagset.Parse(args)
@ -133,6 +115,14 @@ func (pl *PluginLoader) ParseFlags(args []string) bool {
return len(pl.Flags) > 0
}
func ParseFlags(args []string) bool {
if defaultPluginLoader == nil {
log.Warn("Attempting to parse flags, but default PluginLoader has not been initialized")
return false
}
return defaultPluginLoader.ParseFlags(args)
}
func (pl *PluginLoader) GetAPIs(stack *node.Node, backend Backend) []rpc.API {
apis := []rpc.API{}
for _, apiLoader := range pl.RPCPlugins {
@ -140,3 +130,11 @@ func (pl *PluginLoader) GetAPIs(stack *node.Node, backend Backend) []rpc.API {
}
return apis
}
func GetAPIs(stack *node.Node, backend Backend) []rpc.API {
if defaultPluginLoader == nil {
log.Warn("Attempting GetAPIs ,but default PluginLoader has not been initialized")
return []rpc.API{}
}
return defaultPluginLoader.GetAPIs(stack, backend)
}

@ -1 +1 @@
Subproject commit fa0ab110f3f45d1f6786f978ea596a18ecbe8275
Subproject commit 6b85703b568f4456582a00665d8a3e5c3b20b484