From ff46e3c7f868d44de61c595ea79265ed259d350a Mon Sep 17 00:00:00 2001 From: Austin Roberts Date: Mon, 15 Mar 2021 15:02:37 -0500 Subject: [PATCH] Add plugin folder --- plugins/interface.go | 74 ++++++++++++++++++++ plugins/plugin_loader.go | 142 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 plugins/interface.go create mode 100644 plugins/plugin_loader.go diff --git a/plugins/interface.go b/plugins/interface.go new file mode 100644 index 000000000..ef9665cf5 --- /dev/null +++ b/plugins/interface.go @@ -0,0 +1,74 @@ +package plugins + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +// Backend interface provides the common API services (that are provided by +// both full and light clients) with access to necessary functions. +type Backend interface { + // General Ethereum API + Downloader() *downloader.Downloader + SuggestPrice(ctx context.Context) (*big.Int, error) + ChainDb() ethdb.Database + AccountManager() *accounts.Manager + ExtRPCEnabled() bool + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection + RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs + UnprotectedAllowed() bool // allows only for EIP155 transactions. + + // Blockchain API + SetHead(number uint64) + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) + CurrentHeader() *types.Header + CurrentBlock() *types.Block + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) + StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*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) + 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) + SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription + SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription + SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription + + // Transaction pool API + SendTx(ctx context.Context, signedTx *types.Transaction) error + GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) + GetPoolTransactions() (types.Transactions, error) + GetPoolTransaction(txHash common.Hash) *types.Transaction + GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) + Stats() (pending int, queued int) + TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) + SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription + + // Filter API + BloomStatus() (uint64, uint64) + GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) + ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) + SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription + SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription + SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription + + ChainConfig() *params.ChainConfig + Engine() consensus.Engine +} diff --git a/plugins/plugin_loader.go b/plugins/plugin_loader.go new file mode 100644 index 000000000..ad0a58d3c --- /dev/null +++ b/plugins/plugin_loader.go @@ -0,0 +1,142 @@ +package plugins + +import ( + "plugin" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/log" + "gopkg.in/urfave/cli.v1" + "flag" + "io/ioutil" + "strings" + "path" + "fmt" +) + + +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 + StateHooks []interface{} // TODO: Set interface + ChainEventHooks []interface{} // TODO: Set interface + RPCPlugins []APILoader + Subcommands map[string]Subcommand + Flags []*flag.FlagSet +} + + +func NewPluginLoader(target string) (*PluginLoader, error) { + pl := &PluginLoader{ + RPCPlugins: []APILoader{}, + Subcommands: make(map[string]Subcommand), + Flags: []*flag.FlagSet{}, + } + files, err := ioutil.ReadDir(target) + if err != nil { + log.Warn("Could not load plugins directory. Skipping.", "path", target) + return pl, nil + } + 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) + continue + } + plug, err := plugin.Open(fpath) + if err != nil { + log.Warn("File in plugin directory could not be loaded: %v", "file", fpath, "error", err.Error()) + continue + } + // Any type of plugin can potentially specify flags + f, err := plug.Lookup("Flags") + if err == nil { + flagset, ok := f.(*flag.FlagSet) + if !ok { + log.Warn("Found plugin.Flags, but it its not a *FlagSet", "file", fpath) + } else { + 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 + } + apiLoader, ok := fn.(func(*node.Node, Backend) []rpc.API) + if !ok { + log.Warn("Could not cast plugin.GetAPIs to APILoader", "file", fpath) + continue + } + 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) + if !ok { + log.Warn("Could not cast plugin.Name to string", "file", fpath) + continue + } + 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 (pl *PluginLoader) RunSubcommand(ctx *cli.Context) (bool, error) { + args := ctx.Args() + if len(args) == 0 { return false, fmt.Errorf("No subcommand arguments")} + subcommand, ok := pl.Subcommands[args[0]] + if !ok { return false, fmt.Errorf("Subcommand %v does not exist", args[0])} + return true, subcommand(ctx, args[1:]) +} + +func (pl *PluginLoader) ParseFlags(args []string) bool { + for _, flagset := range pl.Flags { + flagset.Parse(args) + } + return len(pl.Flags) > 0 +} + +func (pl *PluginLoader) GetAPIs(stack *node.Node, backend Backend) []rpc.API { + apis := []rpc.API{} + for _, apiLoader := range pl.RPCPlugins { + apis = append(apis, apiLoader(stack, backend)...) + } + return apis +}