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  }