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