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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -39,6 +40,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/plugins"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"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
|
// It creates a default node based on the command line arguments and runs it in
|
||||||
// blocking mode, waiting for it to be shut down.
|
// blocking mode, waiting for it to be shut down.
|
||||||
func geth(ctx *cli.Context) error {
|
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 {
|
if args := ctx.Args(); len(args) > 0 {
|
||||||
return fmt.Errorf("invalid command: %q", args[0])
|
return fmt.Errorf("invalid command: %q", args[0])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prepare(ctx)
|
prepare(ctx)
|
||||||
stack, backend := makeFullNode(ctx)
|
stack, backend := makeFullNode(ctx)
|
||||||
defer stack.Close()
|
defer stack.Close()
|
||||||
|
stack.RegisterAPIs(plugins.GetAPIs(stack, backend))
|
||||||
|
|
||||||
startNode(ctx, stack, backend)
|
startNode(ctx, stack, backend)
|
||||||
stack.Wait()
|
stack.Wait()
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
type Backend interface {
|
type Backend interface {
|
||||||
// General Ethereum API
|
// General Ethereum API
|
||||||
Downloader() *downloader.Downloader
|
Downloader() *downloader.Downloader
|
||||||
SuggestPrice(ctx context.Context) (*big.Int, error)
|
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
||||||
ChainDb() ethdb.Database
|
ChainDb() ethdb.Database
|
||||||
AccountManager() *accounts.Manager
|
AccountManager() *accounts.Manager
|
||||||
ExtRPCEnabled() bool
|
ExtRPCEnabled() bool
|
||||||
@ -46,7 +46,7 @@ type Backend interface {
|
|||||||
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
|
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
|
||||||
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
|
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
|
||||||
GetTd(ctx context.Context, hash common.Hash) *big.Int
|
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
|
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
||||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||||
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
|
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
|
||||||
|
@ -17,16 +17,6 @@ import (
|
|||||||
type APILoader func(*node.Node, Backend) []rpc.API
|
type APILoader func(*node.Node, Backend) []rpc.API
|
||||||
type Subcommand func(*cli.Context, []string) error
|
type Subcommand func(*cli.Context, []string) error
|
||||||
|
|
||||||
type PluginType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
TracerPluginType PluginType = iota
|
|
||||||
StateHookType
|
|
||||||
ChainEventHookType
|
|
||||||
RPCPluginType
|
|
||||||
SubcommandType
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
type PluginLoader struct{
|
type PluginLoader struct{
|
||||||
TracerPlugins map[string]interface{} // TODO: Set interface
|
TracerPlugins map[string]interface{} // TODO: Set interface
|
||||||
@ -37,6 +27,8 @@ type PluginLoader struct{
|
|||||||
Flags []*flag.FlagSet
|
Flags []*flag.FlagSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultPluginLoader *PluginLoader
|
||||||
|
|
||||||
|
|
||||||
func NewPluginLoader(target string) (*PluginLoader, error) {
|
func NewPluginLoader(target string) (*PluginLoader, error) {
|
||||||
pl := &PluginLoader{
|
pl := &PluginLoader{
|
||||||
@ -52,7 +44,7 @@ func NewPluginLoader(target string) (*PluginLoader, error) {
|
|||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
fpath := path.Join(target, file.Name())
|
fpath := path.Join(target, file.Name())
|
||||||
if !strings.HasSuffix(file.Name(), ".so") {
|
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
|
continue
|
||||||
}
|
}
|
||||||
plug, err := plugin.Open(fpath)
|
plug, err := plugin.Open(fpath)
|
||||||
@ -70,54 +62,39 @@ func NewPluginLoader(target string) (*PluginLoader, error) {
|
|||||||
pl.Flags = append(pl.Flags, flagset)
|
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")
|
fn, err := plug.Lookup("GetAPIs")
|
||||||
if err != nil {
|
if err == nil {
|
||||||
log.Warn("Could not load GetAPIs from plugin", "file", fpath, "error", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
apiLoader, ok := fn.(func(*node.Node, Backend) []rpc.API)
|
apiLoader, ok := fn.(func(*node.Node, Backend) []rpc.API)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warn("Could not cast plugin.GetAPIs to APILoader", "file", fpath)
|
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 {
|
if !ok {
|
||||||
log.Warn("Could not cast plugin.Name to string", "file", fpath)
|
log.Warn("Could not cast plugin.Subocmmands to `map[string]func(*cli.Context, []string) error`", "file", fpath)
|
||||||
continue
|
} 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")
|
pl.Subcommands[k] = v
|
||||||
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
|
return pl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Initialize(target string) (err error) {
|
||||||
|
defaultPluginLoader, err = NewPluginLoader(target)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (pl *PluginLoader) RunSubcommand(ctx *cli.Context) (bool, error) {
|
func (pl *PluginLoader) RunSubcommand(ctx *cli.Context) (bool, error) {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
if len(args) == 0 { return false, fmt.Errorf("No subcommand arguments")}
|
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:])
|
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 {
|
func (pl *PluginLoader) ParseFlags(args []string) bool {
|
||||||
for _, flagset := range pl.Flags {
|
for _, flagset := range pl.Flags {
|
||||||
flagset.Parse(args)
|
flagset.Parse(args)
|
||||||
@ -133,6 +115,14 @@ func (pl *PluginLoader) ParseFlags(args []string) bool {
|
|||||||
return len(pl.Flags) > 0
|
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 {
|
func (pl *PluginLoader) GetAPIs(stack *node.Node, backend Backend) []rpc.API {
|
||||||
apis := []rpc.API{}
|
apis := []rpc.API{}
|
||||||
for _, apiLoader := range pl.RPCPlugins {
|
for _, apiLoader := range pl.RPCPlugins {
|
||||||
@ -140,3 +130,11 @@ func (pl *PluginLoader) GetAPIs(stack *node.Node, backend Backend) []rpc.API {
|
|||||||
}
|
}
|
||||||
return apis
|
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