Skip to content

Commit fad5eb0

Browse files
committed
accounts, cmd, eth, internal, miner, node: wallets and HD APIs
1 parent b3c0e9d commit fad5eb0

File tree

23 files changed

+1502
-603
lines changed

23 files changed

+1502
-603
lines changed

accounts/accounts.go

Lines changed: 111 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -18,162 +18,128 @@
1818
package accounts
1919

2020
import (
21-
"encoding/json"
22-
"errors"
2321
"math/big"
24-
"reflect"
25-
"sync"
2622

2723
"github.com/ethereum/go-ethereum/common"
2824
"github.com/ethereum/go-ethereum/core/types"
25+
"github.com/ethereum/go-ethereum/event"
2926
)
3027

31-
// ErrUnknownAccount is returned for any requested operation for which no backend
32-
// provides the specified account.
33-
var ErrUnknownAccount = errors.New("unknown account")
34-
35-
// ErrNotSupported is returned when an operation is requested from an account
36-
// backend that it does not support.
37-
var ErrNotSupported = errors.New("not supported")
38-
39-
// Account represents a stored key.
40-
// When used as an argument, it selects a unique key to act on.
28+
// Account represents an Ethereum account located at a specific location defined
29+
// by the optional URL field.
4130
type Account struct {
42-
Address common.Address // Ethereum account address derived from the key
43-
URL string // Optional resource locator within a backend
44-
backend Backend // Backend where this account originates from
45-
}
46-
47-
func (acc *Account) MarshalJSON() ([]byte, error) {
48-
return []byte(`"` + acc.Address.Hex() + `"`), nil
49-
}
50-
51-
func (acc *Account) UnmarshalJSON(raw []byte) error {
52-
return json.Unmarshal(raw, &acc.Address)
53-
}
54-
55-
// Manager is an overarching account manager that can communicate with various
56-
// backends for signing transactions.
57-
type Manager struct {
58-
backends []Backend // List of currently registered backends (ordered by registration)
59-
index map[reflect.Type]Backend // Set of currently registered backends
60-
lock sync.RWMutex
61-
}
62-
63-
// NewManager creates a generic account manager to sign transaction via various
64-
// supported backends.
65-
func NewManager(backends ...Backend) *Manager {
66-
am := &Manager{
67-
backends: backends,
68-
index: make(map[reflect.Type]Backend),
69-
}
70-
for _, backend := range backends {
71-
am.index[reflect.TypeOf(backend)] = backend
72-
}
73-
return am
74-
}
75-
76-
// Backend retrieves the backend with the given type from the account manager.
77-
func (am *Manager) Backend(backend reflect.Type) Backend {
78-
return am.index[backend]
31+
Address common.Address `json:"address"` // Ethereum account address derived from the key
32+
URL string `json:"url"` // Optional resource locator within a backend
7933
}
8034

81-
// Accounts returns all signer accounts registered under this account manager.
82-
func (am *Manager) Accounts() []Account {
83-
am.lock.RLock()
84-
defer am.lock.RUnlock()
85-
86-
var all []Account
87-
for _, backend := range am.backends { // TODO(karalabe): cache these after subscriptions are in
88-
accounts := backend.Accounts()
89-
for i := 0; i < len(accounts); i++ {
90-
accounts[i].backend = backend
91-
}
92-
all = append(all, accounts...)
93-
}
94-
return all
35+
// Wallet represents a software or hardware wallet that might contain one or more
36+
// accounts (derived from the same seed).
37+
type Wallet interface {
38+
// Type retrieves a textual representation of the type of the wallet.
39+
Type() string
40+
41+
// URL retrieves the canonical path under which this wallet is reachable. It is
42+
// user by upper layers to define a sorting order over all wallets from multiple
43+
// backends.
44+
URL() string
45+
46+
// Status returns a textual status to aid the user in the current state of the
47+
// wallet.
48+
Status() string
49+
50+
// Open initializes access to a wallet instance. It is not meant to unlock or
51+
// decrypt account keys, rather simply to establish a connection to hardware
52+
// wallets and/or to access derivation seeds.
53+
//
54+
// The passphrase parameter may or may not be used by the implementation of a
55+
// particular wallet instance. The reason there is no passwordless open method
56+
// is to strive towards a uniform wallet handling, oblivious to the different
57+
// backend providers.
58+
//
59+
// Please note, if you open a wallet, you must close it to release any allocated
60+
// resources (especially important when working with hardware wallets).
61+
Open(passphrase string) error
62+
63+
// Close releases any resources held by an open wallet instance.
64+
Close() error
65+
66+
// Accounts retrieves the list of signing accounts the wallet is currently aware
67+
// of. For hierarchical deterministic wallets, the list will not be exhaustive,
68+
// rather only contain the accounts explicitly pinned during account derivation.
69+
Accounts() []Account
70+
71+
// Contains returns whether an account is part of this particular wallet or not.
72+
Contains(account Account) bool
73+
74+
// Derive attempts to explicitly derive a hierarchical deterministic account at
75+
// the specified derivation path. If requested, the derived account will be added
76+
// to the wallet's tracked account list.
77+
Derive(path string, pin bool) (Account, error)
78+
79+
// SignHash requests the wallet to sign the given hash.
80+
//
81+
// It looks up the account specified either solely via its address contained within,
82+
// or optionally with the aid of any location metadata from the embedded URL field.
83+
//
84+
// If the wallet requires additional authentication to sign the request (e.g.
85+
// a password to decrypt the account, or a PIN code o verify the transaction),
86+
// an AuthNeededError instance will be returned, containing infos for the user
87+
// about which fields or actions are needed. The user may retry by providing
88+
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
89+
// the account in a keystore).
90+
SignHash(account Account, hash []byte) ([]byte, error)
91+
92+
// SignTx requests the wallet to sign the given transaction.
93+
//
94+
// It looks up the account specified either solely via its address contained within,
95+
// or optionally with the aid of any location metadata from the embedded URL field.
96+
//
97+
// If the wallet requires additional authentication to sign the request (e.g.
98+
// a password to decrypt the account, or a PIN code o verify the transaction),
99+
// an AuthNeededError instance will be returned, containing infos for the user
100+
// about which fields or actions are needed. The user may retry by providing
101+
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
102+
// the account in a keystore).
103+
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
104+
105+
// SignHashWithPassphrase requests the wallet to sign the given hash with the
106+
// given passphrase as extra authentication information.
107+
//
108+
// It looks up the account specified either solely via its address contained within,
109+
// or optionally with the aid of any location metadata from the embedded URL field.
110+
SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
111+
112+
// SignTxWithPassphrase requests the wallet to sign the given transaction, with the
113+
// given passphrase as extra authentication information.
114+
//
115+
// It looks up the account specified either solely via its address contained within,
116+
// or optionally with the aid of any location metadata from the embedded URL field.
117+
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
95118
}
96119

97-
// HasAddress reports whether a key with the given address is present.
98-
func (am *Manager) HasAddress(addr common.Address) bool {
99-
am.lock.RLock()
100-
defer am.lock.RUnlock()
101-
102-
for _, backend := range am.backends {
103-
if backend.HasAddress(addr) {
104-
return true
105-
}
106-
}
107-
return false
120+
// Backend is a "wallet provider" that may contain a batch of accounts they can
121+
// sign transactions with and upon request, do so.
122+
type Backend interface {
123+
// Wallets retrieves the list of wallets the backend is currently aware of.
124+
//
125+
// The returned wallets are not opened by default. For software HD wallets this
126+
// means that no base seeds are decrypted, and for hardware wallets that no actual
127+
// connection is established.
128+
//
129+
// The resulting wallet list will be sorted alphabetically based on its internal
130+
// URL assigned by the backend. Since wallets (especially hardware) may come and
131+
// go, the same wallet might appear at a different positions in the list during
132+
// subsequent retrievals.
133+
Wallets() []Wallet
134+
135+
// Subscribe creates an async subscription to receive notifications when the
136+
// backend detects the arrival or departure of a wallet.
137+
Subscribe(sink chan<- WalletEvent) event.Subscription
108138
}
109139

110-
// SignHash requests the account manager to get the hash signed with an arbitrary
111-
// signing backend holding the authorization for the specified account.
112-
func (am *Manager) SignHash(acc Account, hash []byte) ([]byte, error) {
113-
am.lock.RLock()
114-
defer am.lock.RUnlock()
115-
116-
if err := am.ensureBackend(&acc); err != nil {
117-
return nil, err
118-
}
119-
return acc.backend.SignHash(acc, hash)
120-
}
121-
122-
// SignTx requests the account manager to get the transaction signed with an
123-
// arbitrary signing backend holding the authorization for the specified account.
124-
func (am *Manager) SignTx(acc Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
125-
am.lock.RLock()
126-
defer am.lock.RUnlock()
127-
128-
if err := am.ensureBackend(&acc); err != nil {
129-
return nil, err
130-
}
131-
return acc.backend.SignTx(acc, tx, chainID)
132-
}
133-
134-
// SignHashWithPassphrase requests the account manager to get the hash signed with
135-
// an arbitrary signing backend holding the authorization for the specified account.
136-
func (am *Manager) SignHashWithPassphrase(acc Account, passphrase string, hash []byte) ([]byte, error) {
137-
am.lock.RLock()
138-
defer am.lock.RUnlock()
139-
140-
if err := am.ensureBackend(&acc); err != nil {
141-
return nil, err
142-
}
143-
return acc.backend.SignHashWithPassphrase(acc, passphrase, hash)
144-
}
145-
146-
// SignTxWithPassphrase requests the account manager to get the transaction signed
147-
// with an arbitrary signing backend holding the authorization for the specified
148-
// account.
149-
func (am *Manager) SignTxWithPassphrase(acc Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
150-
am.lock.RLock()
151-
defer am.lock.RUnlock()
152-
153-
if err := am.ensureBackend(&acc); err != nil {
154-
return nil, err
155-
}
156-
return acc.backend.SignTxWithPassphrase(acc, passphrase, tx, chainID)
157-
}
158-
159-
// ensureBackend ensures that the account has a correctly set backend and that
160-
// it is still alive.
161-
//
162-
// Please note, this method assumes the manager lock is held!
163-
func (am *Manager) ensureBackend(acc *Account) error {
164-
// If we have a backend, make sure it's still live
165-
if acc.backend != nil {
166-
if _, exists := am.index[reflect.TypeOf(acc.backend)]; !exists {
167-
return ErrUnknownAccount
168-
}
169-
return nil
170-
}
171-
// If we don't have a known backend, look up one that can service it
172-
for _, backend := range am.backends {
173-
if backend.HasAddress(acc.Address) { // TODO(karalabe): this assumes unique addresses per backend
174-
acc.backend = backend
175-
return nil
176-
}
177-
}
178-
return ErrUnknownAccount
140+
// WalletEvent is an event fired by an account backend when a wallet arrival or
141+
// departure is detected.
142+
type WalletEvent struct {
143+
Wallet Wallet // Wallet instance arrived or departed
144+
Arrive bool // Whether the wallet was added or removed
179145
}

accounts/backend.go

Lines changed: 0 additions & 88 deletions
This file was deleted.

accounts/errors.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,34 @@
1616

1717
package accounts
1818

19-
import "fmt"
19+
import (
20+
"errors"
21+
"fmt"
22+
)
23+
24+
// ErrUnknownAccount is returned for any requested operation for which no backend
25+
// provides the specified account.
26+
var ErrUnknownAccount = errors.New("unknown account")
27+
28+
// ErrUnknownWallet is returned for any requested operation for which no backend
29+
// provides the specified wallet.
30+
var ErrUnknownWallet = errors.New("unknown wallet")
31+
32+
// ErrNotSupported is returned when an operation is requested from an account
33+
// backend that it does not support.
34+
var ErrNotSupported = errors.New("not supported")
35+
36+
// ErrInvalidPassphrase is returned when a decryption operation receives a bad
37+
// passphrase.
38+
var ErrInvalidPassphrase = errors.New("invalid passphrase")
39+
40+
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
41+
// secodn time.
42+
var ErrWalletAlreadyOpen = errors.New("wallet already open")
43+
44+
// ErrWalletClosed is returned if a wallet is attempted to be opened the
45+
// secodn time.
46+
var ErrWalletClosed = errors.New("wallet closed")
2047

2148
// AuthNeededError is returned by backends for signing requests where the user
2249
// is required to provide further authentication before signing can succeed.

0 commit comments

Comments
 (0)