github.com/ConsenSys/Quorum@v20.10.0+incompatible/cmd/geth/accountcmd_plugin.go (about) 1 package main 2 3 import ( 4 "encoding/hex" 5 "encoding/json" 6 "errors" 7 "fmt" 8 9 "github.com/ethereum/go-ethereum/accounts" 10 "github.com/ethereum/go-ethereum/accounts/pluggable" 11 "github.com/ethereum/go-ethereum/cmd/utils" 12 "github.com/ethereum/go-ethereum/crypto" 13 "github.com/ethereum/go-ethereum/log" 14 "github.com/ethereum/go-ethereum/node" 15 "github.com/ethereum/go-ethereum/plugin" 16 "gopkg.in/urfave/cli.v1" 17 ) 18 19 var ( 20 quorumAccountPluginCommands = cli.Command{ 21 Name: "plugin", 22 Usage: "Manage 'account' plugin accounts", 23 Description: ` 24 geth account plugin 25 26 Quorum supports alternate account management methods through the use of 'account' plugins. 27 28 See docs.goquorum.com for more info. 29 `, 30 Subcommands: []cli.Command{ 31 { 32 Name: "list", 33 Usage: "Print summary of existing 'account' plugin accounts", 34 Action: utils.MigrateFlags(listPluginAccountsCLIAction), 35 Flags: []cli.Flag{ 36 utils.PluginSettingsFlag, // flag is used implicitly by makeConfigNode() 37 utils.PluginLocalVerifyFlag, 38 utils.PluginPublicKeyFlag, 39 utils.PluginSkipVerifyFlag, 40 }, 41 Description: ` 42 geth account plugin list 43 Print a short summary of all accounts for the given plugin settings`, 44 }, 45 { 46 Name: "new", 47 Usage: "Create a new account using an 'account' plugin", 48 Action: utils.MigrateFlags(createPluginAccountCLIAction), 49 Flags: []cli.Flag{ 50 utils.PluginSettingsFlag, 51 utils.PluginLocalVerifyFlag, 52 utils.PluginPublicKeyFlag, 53 utils.PluginSkipVerifyFlag, 54 utils.AccountPluginNewAccountConfigFlag, 55 }, 56 Description: fmt.Sprintf(` 57 geth account plugin new 58 59 Creates a new account using an 'account' plugin and prints the address. 60 61 --%v and --%v flags are required. 62 63 Each 'account' plugin will have different requirements for the value of --%v. 64 For more info see the documentation for the particular 'account' plugin being used. 65 `, utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name), 66 }, 67 { 68 Name: "import", 69 Usage: "Import a private key into a new account using an 'account' plugin", 70 Action: utils.MigrateFlags(importPluginAccountCLIAction), 71 Flags: []cli.Flag{ 72 utils.PluginSettingsFlag, 73 utils.PluginLocalVerifyFlag, 74 utils.PluginPublicKeyFlag, 75 utils.PluginSkipVerifyFlag, 76 utils.AccountPluginNewAccountConfigFlag, 77 }, 78 ArgsUsage: "<keyFile>", 79 Description: ` 80 geth account plugin import <keyfile> 81 82 Imports an unencrypted private key from <keyfile> and creates a new account using an 'account' plugin. 83 Prints the address. 84 85 The keyfile must contain an unencrypted private key in hexadecimal format. 86 87 --%v and --%v flags are required. 88 89 Note: 90 Before using this import mechanism to transfer accounts that are already 'account' plugin-managed between nodes, consult 91 the documentation for the particular 'account' plugin being used as it may support alternate methods for transferring. 92 `, 93 }, 94 }, 95 } 96 97 // supportedPlugins is the list of supported plugins for the account subcommand 98 supportedPlugins = []plugin.PluginInterfaceName{plugin.AccountPluginInterfaceName} 99 100 invalidPluginFlagsErr = fmt.Errorf("--%v and --%v flags must be set", utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name) 101 102 // makeConfigNodeDelegate is a wrapper for the makeConfigNode function. 103 // It can be replaced with a stub for testing. 104 makeConfigNodeDelegate configNodeMaker = standardConfigNodeMaker{} 105 ) 106 107 func listPluginAccountsCLIAction(ctx *cli.Context) error { 108 accts, err := listPluginAccounts(ctx) 109 if err != nil { 110 utils.Fatalf("%v", err) 111 } 112 113 var index int 114 for _, acct := range accts { 115 fmt.Printf("Account #%d: {%x} %s\n", index, acct.Address, &acct.URL) 116 index++ 117 } 118 119 return nil 120 } 121 122 func listPluginAccounts(ctx *cli.Context) ([]accounts.Account, error) { 123 if !ctx.IsSet(utils.PluginSettingsFlag.Name) { 124 return []accounts.Account{}, fmt.Errorf("--%v required", utils.PluginSettingsFlag.Name) 125 } 126 127 p, err := setupAccountPluginForCLI(ctx) 128 if err != nil { 129 return []accounts.Account{}, err 130 } 131 defer func() { 132 if err := p.teardown(); err != nil { 133 log.Error("error tearing down account plugin", "err", err) 134 } 135 }() 136 137 return p.accounts(), nil 138 } 139 140 func createPluginAccountCLIAction(ctx *cli.Context) error { 141 account, err := createPluginAccount(ctx) 142 if err != nil { 143 utils.Fatalf("unable to create plugin-backed account: %v", err) 144 } 145 writePluginAccountToStdOut(account) 146 return nil 147 } 148 149 func createPluginAccount(ctx *cli.Context) (accounts.Account, error) { 150 if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) { 151 return accounts.Account{}, invalidPluginFlagsErr 152 } 153 154 newAcctCfg, err := getNewAccountConfigFromCLI(ctx) 155 if err != nil { 156 return accounts.Account{}, err 157 } 158 159 p, err := setupAccountPluginForCLI(ctx) 160 if err != nil { 161 return accounts.Account{}, err 162 } 163 defer func() { 164 if err := p.teardown(); err != nil { 165 log.Error("error tearing down account plugin", "err", err) 166 } 167 }() 168 169 return p.NewAccount(newAcctCfg) 170 } 171 172 func importPluginAccountCLIAction(ctx *cli.Context) error { 173 account, err := importPluginAccount(ctx) 174 if err != nil { 175 utils.Fatalf("unable to import key and create plugin-backed account: %v", err) 176 } 177 writePluginAccountToStdOut(account) 178 return nil 179 } 180 181 func importPluginAccount(ctx *cli.Context) (accounts.Account, error) { 182 keyfile := ctx.Args().First() 183 if len(keyfile) == 0 { 184 return accounts.Account{}, errors.New("keyfile must be given as argument") 185 } 186 key, err := crypto.LoadECDSA(keyfile) 187 if err != nil { 188 return accounts.Account{}, fmt.Errorf("Failed to load the private key: %v", err) 189 } 190 keyBytes := crypto.FromECDSA(key) 191 keyHex := hex.EncodeToString(keyBytes) 192 193 if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) { 194 return accounts.Account{}, invalidPluginFlagsErr 195 } 196 197 newAcctCfg, err := getNewAccountConfigFromCLI(ctx) 198 if err != nil { 199 return accounts.Account{}, err 200 } 201 202 p, err := setupAccountPluginForCLI(ctx) 203 if err != nil { 204 return accounts.Account{}, err 205 } 206 defer func() { 207 if err := p.teardown(); err != nil { 208 log.Error("error tearing down account plugin", "err", err) 209 } 210 }() 211 212 return p.ImportRawKey(keyHex, newAcctCfg) 213 } 214 215 func getNewAccountConfigFromCLI(ctx *cli.Context) (map[string]interface{}, error) { 216 data := ctx.String(utils.AccountPluginNewAccountConfigFlag.Name) 217 conf, err := plugin.ReadMultiFormatConfig(data) 218 if err != nil { 219 return nil, fmt.Errorf("invalid account creation config provided: %v", err) 220 } 221 // plugin backend expects config to be json map 222 confMap := new(map[string]interface{}) 223 if err := json.Unmarshal(conf, confMap); err != nil { 224 return nil, fmt.Errorf("invalid account creation config provided: %v", err) 225 } 226 return *confMap, nil 227 } 228 229 type accountPlugin struct { 230 pluggable.AccountCreator 231 am *accounts.Manager 232 pm *plugin.PluginManager 233 } 234 235 func (c *accountPlugin) teardown() error { 236 return c.pm.Stop() 237 } 238 239 func (c *accountPlugin) accounts() []accounts.Account { 240 b := c.am.Backends(pluggable.BackendType) 241 if b == nil { 242 return []accounts.Account{} 243 } 244 245 var accts []accounts.Account 246 for _, wallet := range b[0].Wallets() { 247 accts = append(accts, wallet.Accounts()...) 248 } 249 return accts 250 } 251 252 // startPluginManagerForAccountCLI is a helper func for use with the account plugin CLI. 253 // It creates and starts a new PluginManager with the provided CLI flags. 254 // The caller should call teardown on the returned accountPlugin to stop the plugin after use. 255 // The returned accountPlugin provides several methods necessary for the account plugin CLI, abstracting the underlying plugin/account types. 256 // 257 // This func should not be used for anything other than the account CLI. 258 // The account plugin, if present, is registered with the existing pluggable.Backend in the stack's AccountManager. 259 // This allows the AccountManager to use the account plugin even though the PluginManager is not registered with the stack. 260 // Instead of registering a plugin manager with the stack this is manually creating a plugin manager. 261 // This means that the plugin manager can be started without having to start the whole stack (P2P client, IPC interface, ...). 262 // The purpose of this is to help prevent issues/conflicts if an existing node is already running on this host. 263 // 264 func setupAccountPluginForCLI(ctx *cli.Context) (*accountPlugin, error) { 265 stack, cfg := makeConfigNodeDelegate.makeConfigNode(ctx) 266 267 if cfg.Node.Plugins == nil { 268 return nil, errors.New("no plugin config provided") 269 } 270 if err := cfg.Node.Plugins.CheckSettingsAreSupported(supportedPlugins); err != nil { 271 return nil, err 272 } 273 if err := cfg.Node.ResolvePluginBaseDir(); err != nil { 274 return nil, fmt.Errorf("unable to resolve plugin base dir due to %s", err) 275 } 276 277 pm, err := plugin.NewPluginManager( 278 cfg.Node.UserIdent, 279 cfg.Node.Plugins, 280 ctx.Bool(utils.PluginSkipVerifyFlag.Name), 281 ctx.Bool(utils.PluginLocalVerifyFlag.Name), 282 ctx.String(utils.PluginPublicKeyFlag.Name), 283 ) 284 if err != nil { 285 return nil, fmt.Errorf("unable to create plugin manager: %v", err) 286 } 287 if err := pm.Start(nil); err != nil { 288 return nil, fmt.Errorf("unable to start plugin manager: %v", err) 289 } 290 291 b := stack.AccountManager().Backends(pluggable.BackendType)[0].(*pluggable.Backend) 292 if err := pm.AddAccountPluginToBackend(b); err != nil { 293 return nil, fmt.Errorf("unable to load pluggable account backend: %v", err) 294 } 295 296 return &accountPlugin{ 297 AccountCreator: b, 298 am: stack.AccountManager(), 299 pm: pm, 300 }, nil 301 } 302 303 func writePluginAccountToStdOut(account accounts.Account) { 304 fmt.Printf("\nYour new plugin-backed account was generated\n\n") 305 fmt.Printf("Public address of the account: %s\n", account.Address.Hex()) 306 fmt.Printf("Account URL: %s\n\n", account.URL.Path) 307 fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") 308 fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") 309 fmt.Printf("- Consider BACKING UP your account! The specifics of backing up will depend on the plugin backend being used.\n") 310 fmt.Printf("- The plugin backend may require you to REMEMBER part/all of the new account config to retrieve the key in the future!\n See the plugin specific documentation for more info.\n\n") 311 fmt.Printf("- See the documentation for the plugin being used for more info.\n\n") 312 } 313 314 type configNodeMaker interface { 315 makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) 316 } 317 318 // standardConfigNodeMaker is a wrapper around the makeConfigNode function to enable mocking in testing 319 type standardConfigNodeMaker struct{} 320 321 func (f standardConfigNodeMaker) makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { 322 return makeConfigNode(ctx) 323 }