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:
parent
ff46e3c7f8
commit
5c55657c54
@ -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 args := ctx.Args(); len(args) > 0 {
|
||||
return fmt.Errorf("invalid command: %q", args[0])
|
||||
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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
fn, err := plug.Lookup("GetAPIs")
|
||||
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))
|
||||
}
|
||||
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)
|
||||
}
|
||||
pl.Subcommands[k] = v
|
||||
}
|
||||
}
|
||||
fn, err := plug.Lookup("Main")
|
||||
if err != nil {
|
||||
log.Warn("Could not load Main from plugin", "file", fpath, "error", err.Error())
|
||||
continue
|
||||
}
|
||||
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
|
Loading…
Reference in New Issue
Block a user