github.com/Consensys/quorum@v21.1.0+incompatible/plugin/base.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	slog "log"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"reflect"
    13  	"time"
    14  
    15  	"github.com/ethereum/go-ethereum/common"
    16  	iplugin "github.com/ethereum/go-ethereum/internal/plugin"
    17  	"github.com/ethereum/go-ethereum/log"
    18  	"github.com/ethereum/go-ethereum/plugin/initializer"
    19  	"github.com/hashicorp/go-hclog"
    20  	"github.com/hashicorp/go-plugin"
    21  )
    22  
    23  type managedPlugin interface {
    24  	Start() error
    25  	Stop() error
    26  
    27  	Info() (PluginInterfaceName, interface{})
    28  }
    29  
    30  // Plugin-meta.json
    31  type MetaData struct {
    32  	Version    string   `json:"version"`
    33  	Os         string   `json:"os"`
    34  	Arch       string   `json:"arch"`
    35  	EntryPoint string   `json:"entrypoint"`
    36  	Parameters []string `json:"parameters,omitempty"`
    37  }
    38  
    39  type basePlugin struct {
    40  	pm               *PluginManager
    41  	pluginInterface  PluginInterfaceName // plugin provider name
    42  	pluginDefinition *PluginDefinition
    43  	client           *plugin.Client
    44  	gateways         plugin.PluginSet // gateways to invoke RPC API implementation of interfaces supported by this plugin
    45  	pluginWorkspace  string           // plugin workspace
    46  	commands         []string         // plugin executable commands
    47  	logger           log.Logger
    48  }
    49  
    50  var basePluginPointerType = reflect.TypeOf(&basePlugin{})
    51  
    52  func newBasePlugin(pm *PluginManager, pluginInterface PluginInterfaceName, pluginDefinition PluginDefinition, gateways plugin.PluginSet) (*basePlugin, error) {
    53  	gateways[initializer.ConnectorName] = &initializer.PluginConnector{}
    54  
    55  	// build basePlugin
    56  	return &basePlugin{
    57  		pm:               pm,
    58  		pluginInterface:  pluginInterface,
    59  		logger:           log.New("provider", pluginInterface, "plugin", pluginDefinition.Name, "version", pluginDefinition.Version),
    60  		pluginDefinition: &pluginDefinition,
    61  		gateways:         gateways,
    62  	}, nil
    63  
    64  }
    65  
    66  // metadata.Command must be populated correctly here
    67  func (bp *basePlugin) load() error {
    68  	// Get plugin distribution path
    69  	pluginDistFilePath, err := bp.pm.downloader.Download(bp.pluginDefinition)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	// get file checksum
    74  	pluginChecksum, err := bp.checksum(pluginDistFilePath)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	bp.logger.Info("verifying plugin integrity", "checksum", pluginChecksum)
    79  	if err := bp.pm.verifier.VerifySignature(bp.pluginDefinition, pluginChecksum); err != nil {
    80  		return fmt.Errorf("unable to verify plugin signature: %v", err)
    81  	}
    82  	bp.logger.Info("unpacking plugin", "checksum", pluginChecksum)
    83  	// Unpack plugin
    84  	unPackDir, pluginMeta, err := unpackPlugin(pluginDistFilePath)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	// Create Execution Command
    89  	var command *exec.Cmd
    90  	executable := path.Join(unPackDir, pluginMeta.EntryPoint)
    91  	if !common.FileExist(executable) {
    92  		return fmt.Errorf("entry point does not exist")
    93  	}
    94  	bp.logger.Debug("Plugin executable", "path", executable)
    95  	if len(pluginMeta.Parameters) == 0 {
    96  		command = exec.Command(executable)
    97  		bp.commands = []string{executable}
    98  	} else {
    99  		command = exec.Command(executable, pluginMeta.Parameters...)
   100  		bp.commands = append([]string{executable}, pluginMeta.Parameters...)
   101  	}
   102  	command.Dir = unPackDir
   103  	bp.client = plugin.NewClient(&plugin.ClientConfig{
   104  		HandshakeConfig:  iplugin.DefaultHandshakeConfig,
   105  		Plugins:          bp.gateways,
   106  		AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
   107  		Cmd:              command,
   108  		AutoMTLS:         true,
   109  		Logger:           &logDelegate{bp.logger.New("from", "plugin")},
   110  	})
   111  
   112  	bp.pluginWorkspace = unPackDir
   113  	return nil
   114  }
   115  
   116  func (bp *basePlugin) Start() (err error) {
   117  	startTime := time.Now()
   118  	defer func(startTime time.Time) {
   119  		if err == nil {
   120  			bp.logger.Info("Plugin started", "took", time.Since(startTime))
   121  		} else {
   122  			bp.logger.Error("Plugin failed to start", "error", err, "took", time.Since(startTime))
   123  			_ = bp.Stop()
   124  		}
   125  	}(startTime)
   126  	bp.logger.Info("Starting plugin")
   127  	bp.logger.Debug("Starting plugin: Loading")
   128  	err = bp.load()
   129  	if err != nil {
   130  		return
   131  	}
   132  	bp.logger.Debug("Starting plugin: Creating client")
   133  	_, err = bp.client.Client()
   134  	if err != nil {
   135  		return
   136  	}
   137  	bp.logger.Debug("Starting plugin: Initializing")
   138  	err = bp.init()
   139  	return
   140  }
   141  
   142  func (bp *basePlugin) Stop() error {
   143  	if bp.client != nil {
   144  		bp.client.Kill()
   145  	}
   146  	if bp.pluginWorkspace == "" {
   147  		return nil
   148  	}
   149  	return bp.cleanPluginWorkspace()
   150  }
   151  
   152  func (bp *basePlugin) cleanPluginWorkspace() error {
   153  	workspace, err := os.Open(bp.pluginWorkspace)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	defer workspace.Close()
   158  	names, err := workspace.Readdirnames(-1)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	for _, name := range names {
   163  		err = os.RemoveAll(filepath.Join(bp.pluginWorkspace, name))
   164  		if err != nil {
   165  			return err
   166  		}
   167  	}
   168  	return nil
   169  }
   170  
   171  func (bp *basePlugin) init() error {
   172  	bp.logger.Info("Initializing plugin")
   173  	raw, err := bp.dispense(initializer.ConnectorName)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	c, ok := raw.(initializer.PluginInitializer)
   178  	if !ok {
   179  		return fmt.Errorf("missing plugin initializer. Make sure it is in the plugin set")
   180  	}
   181  	rawConfig, err := ReadMultiFormatConfig(bp.pluginDefinition.Config)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	return c.Init(context.Background(), bp.pm.nodeName, rawConfig)
   186  }
   187  
   188  func (bp *basePlugin) dispense(name string) (interface{}, error) {
   189  	rpcClient, err := bp.client.Client()
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	return rpcClient.Dispense(name)
   194  }
   195  
   196  func (bp *basePlugin) Config() *PluginDefinition {
   197  	return bp.pluginDefinition
   198  }
   199  
   200  func (bp *basePlugin) checksum(pluginFile string) (string, error) {
   201  	return getSha256Checksum(pluginFile)
   202  }
   203  
   204  func (bp *basePlugin) Info() (PluginInterfaceName, interface{}) {
   205  	info := make(map[string]interface{})
   206  	info["name"] = bp.pluginDefinition.Name
   207  	info["version"] = bp.pluginDefinition.Version
   208  	info["config"] = bp.pluginDefinition.Config
   209  	info["executable"] = bp.commands
   210  	return bp.pluginInterface, info
   211  }
   212  
   213  type logDelegate struct {
   214  	eLogger log.Logger
   215  }
   216  
   217  func (ld *logDelegate) Trace(msg string, args ...interface{}) {
   218  	ld.eLogger.Trace(msg, args...)
   219  }
   220  
   221  func (ld *logDelegate) Debug(msg string, args ...interface{}) {
   222  	ld.eLogger.Debug(msg, args...)
   223  }
   224  
   225  func (ld *logDelegate) Info(msg string, args ...interface{}) {
   226  	ld.eLogger.Info(msg, args...)
   227  }
   228  
   229  func (ld *logDelegate) Warn(msg string, args ...interface{}) {
   230  	ld.eLogger.Warn(msg, args...)
   231  }
   232  
   233  func (ld *logDelegate) Error(msg string, args ...interface{}) {
   234  	ld.eLogger.Error(msg, args...)
   235  }
   236  
   237  func (ld *logDelegate) IsTrace() bool {
   238  	return true
   239  }
   240  
   241  func (*logDelegate) IsDebug() bool {
   242  	return true
   243  }
   244  
   245  func (*logDelegate) IsInfo() bool {
   246  	return true
   247  }
   248  
   249  func (*logDelegate) IsWarn() bool {
   250  	return true
   251  }
   252  
   253  func (*logDelegate) IsError() bool {
   254  	return true
   255  }
   256  
   257  func (ld *logDelegate) With(args ...interface{}) hclog.Logger {
   258  	return &logDelegate{ld.eLogger.New(args...)}
   259  }
   260  
   261  func (ld *logDelegate) Named(name string) hclog.Logger {
   262  	return ld
   263  }
   264  
   265  func (ld *logDelegate) ResetNamed(name string) hclog.Logger {
   266  	return ld
   267  }
   268  
   269  func (ld *logDelegate) SetLevel(level hclog.Level) {
   270  }
   271  
   272  func (*logDelegate) StandardLogger(opts *hclog.StandardLoggerOptions) *slog.Logger {
   273  	return nil
   274  }
   275  
   276  func (*logDelegate) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
   277  	return nil
   278  }