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 }