Skip to content

Commit c5215fd

Browse files
committed
accounts, cmd, internal, mobile, node: canonical account URLs
1 parent fad5eb0 commit c5215fd

19 files changed

+195
-116
lines changed

accounts/accounts.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,16 @@ import (
2929
// by the optional URL field.
3030
type Account struct {
3131
Address common.Address `json:"address"` // Ethereum account address derived from the key
32-
URL string `json:"url"` // Optional resource locator within a backend
32+
URL URL `json:"url"` // Optional resource locator within a backend
3333
}
3434

3535
// Wallet represents a software or hardware wallet that might contain one or more
3636
// accounts (derived from the same seed).
3737
type Wallet interface {
38-
// Type retrieves a textual representation of the type of the wallet.
39-
Type() string
40-
4138
// URL retrieves the canonical path under which this wallet is reachable. It is
4239
// user by upper layers to define a sorting order over all wallets from multiple
4340
// backends.
44-
URL() string
41+
URL() URL
4542

4643
// Status returns a textual status to aid the user in the current state of the
4744
// wallet.

accounts/keystore/account_cache.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const minReloadInterval = 2 * time.Second
4242
type accountsByURL []accounts.Account
4343

4444
func (s accountsByURL) Len() int { return len(s) }
45-
func (s accountsByURL) Less(i, j int) bool { return s[i].URL < s[j].URL }
45+
func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
4646
func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
4747

4848
// AmbiguousAddrError is returned when attempting to unlock
@@ -55,7 +55,7 @@ type AmbiguousAddrError struct {
5555
func (err *AmbiguousAddrError) Error() string {
5656
files := ""
5757
for i, a := range err.Matches {
58-
files += a.URL
58+
files += a.URL.Path
5959
if i < len(err.Matches)-1 {
6060
files += ", "
6161
}
@@ -104,7 +104,7 @@ func (ac *accountCache) add(newAccount accounts.Account) {
104104
ac.mu.Lock()
105105
defer ac.mu.Unlock()
106106

107-
i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL >= newAccount.URL })
107+
i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
108108
if i < len(ac.all) && ac.all[i] == newAccount {
109109
return
110110
}
@@ -155,10 +155,10 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
155155
if (a.Address != common.Address{}) {
156156
matches = ac.byAddr[a.Address]
157157
}
158-
if a.URL != "" {
158+
if a.URL.Path != "" {
159159
// If only the basename is specified, complete the path.
160-
if !strings.ContainsRune(a.URL, filepath.Separator) {
161-
a.URL = filepath.Join(ac.keydir, a.URL)
160+
if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
161+
a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
162162
}
163163
for i := range matches {
164164
if matches[i].URL == a.URL {
@@ -272,7 +272,7 @@ func (ac *accountCache) scan() ([]accounts.Account, error) {
272272
case (addr == common.Address{}):
273273
glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path)
274274
default:
275-
addrs = append(addrs, accounts.Account{Address: addr, URL: path})
275+
addrs = append(addrs, accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}})
276276
}
277277
fd.Close()
278278
}

accounts/keystore/account_cache_test.go

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ var (
3737
cachetestAccounts = []accounts.Account{
3838
{
3939
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
40-
URL: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
40+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
4141
},
4242
{
4343
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
44-
URL: filepath.Join(cachetestDir, "aaa"),
44+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
4545
},
4646
{
4747
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
48-
URL: filepath.Join(cachetestDir, "zzz"),
48+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
4949
},
5050
}
5151
)
@@ -63,10 +63,11 @@ func TestWatchNewFile(t *testing.T) {
6363
// Move in the files.
6464
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
6565
for i := range cachetestAccounts {
66-
a := cachetestAccounts[i]
67-
a.URL = filepath.Join(dir, filepath.Base(a.URL))
68-
wantAccounts[i] = a
69-
if err := cp.CopyFile(a.URL, cachetestAccounts[i].URL); err != nil {
66+
wantAccounts[i] = accounts.Account{
67+
Address: cachetestAccounts[i].Address,
68+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
69+
}
70+
if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
7071
t.Fatal(err)
7172
}
7273
}
@@ -107,13 +108,13 @@ func TestWatchNoDir(t *testing.T) {
107108
os.MkdirAll(dir, 0700)
108109
defer os.RemoveAll(dir)
109110
file := filepath.Join(dir, "aaa")
110-
if err := cp.CopyFile(file, cachetestAccounts[0].URL); err != nil {
111+
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
111112
t.Fatal(err)
112113
}
113114

114115
// ks should see the account.
115116
wantAccounts := []accounts.Account{cachetestAccounts[0]}
116-
wantAccounts[0].URL = file
117+
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
117118
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
118119
list = ks.Accounts()
119120
if reflect.DeepEqual(list, wantAccounts) {
@@ -145,31 +146,31 @@ func TestCacheAddDeleteOrder(t *testing.T) {
145146
accs := []accounts.Account{
146147
{
147148
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
148-
URL: "-309830980",
149+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
149150
},
150151
{
151152
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
152-
URL: "ggg",
153+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
153154
},
154155
{
155156
Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
156-
URL: "zzzzzz-the-very-last-one.keyXXX",
157+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
157158
},
158159
{
159160
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
160-
URL: "SOMETHING.key",
161+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
161162
},
162163
{
163164
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
164-
URL: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8",
165+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
165166
},
166167
{
167168
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
168-
URL: "aaa",
169+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
169170
},
170171
{
171172
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
172-
URL: "zzz",
173+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
173174
},
174175
}
175176
for _, a := range accs {
@@ -210,7 +211,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
210211
for i := 0; i < len(accs); i += 2 {
211212
cache.delete(wantAccounts[i])
212213
}
213-
cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: "something"})
214+
cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
214215

215216
select {
216217
case <-notify:
@@ -245,19 +246,19 @@ func TestCacheFind(t *testing.T) {
245246
accs := []accounts.Account{
246247
{
247248
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
248-
URL: filepath.Join(dir, "a.key"),
249+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
249250
},
250251
{
251252
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
252-
URL: filepath.Join(dir, "b.key"),
253+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
253254
},
254255
{
255256
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
256-
URL: filepath.Join(dir, "c.key"),
257+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
257258
},
258259
{
259260
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
260-
URL: filepath.Join(dir, "c2.key"),
261+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
261262
},
262263
}
263264
for _, a := range accs {
@@ -266,7 +267,7 @@ func TestCacheFind(t *testing.T) {
266267

267268
nomatchAccount := accounts.Account{
268269
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
269-
URL: filepath.Join(dir, "something"),
270+
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
270271
}
271272
tests := []struct {
272273
Query accounts.Account
@@ -278,7 +279,7 @@ func TestCacheFind(t *testing.T) {
278279
// by file
279280
{Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
280281
// by basename
281-
{Query: accounts.Account{URL: filepath.Base(accs[0].URL)}, WantResult: accs[0]},
282+
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
282283
// by file and address
283284
{Query: accs[0], WantResult: accs[0]},
284285
// ambiguous address, tie resolved by file
@@ -294,7 +295,7 @@ func TestCacheFind(t *testing.T) {
294295
// no match error
295296
{Query: nomatchAccount, WantError: ErrNoMatch},
296297
{Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
297-
{Query: accounts.Account{URL: filepath.Base(nomatchAccount.URL)}, WantError: ErrNoMatch},
298+
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
298299
{Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
299300
}
300301
for i, test := range tests {

accounts/keystore/key.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou
181181
if err != nil {
182182
return nil, accounts.Account{}, err
183183
}
184-
a := accounts.Account{Address: key.Address, URL: ks.JoinPath(keyFileName(key.Address))}
185-
if err := ks.StoreKey(a.URL, key, auth); err != nil {
184+
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}}
185+
if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
186186
zeroKey(key.PrivateKey)
187187
return nil, a, err
188188
}

accounts/keystore/keystore.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ var (
4949
// KeyStoreType is the reflect type of a keystore backend.
5050
var KeyStoreType = reflect.TypeOf(&KeyStore{})
5151

52+
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
53+
var KeyStoreScheme = "keystore"
54+
5255
// Maximum time between wallet refreshes (if filesystem notifications don't work).
5356
const walletRefreshCycle = 3 * time.Second
5457

@@ -130,22 +133,21 @@ func (ks *KeyStore) Wallets() []accounts.Wallet {
130133
// necessary wallet refreshes.
131134
func (ks *KeyStore) refreshWallets() {
132135
// Retrieve the current list of accounts
136+
ks.mu.Lock()
133137
accs := ks.cache.accounts()
134138

135139
// Transform the current list of wallets into the new one
136-
ks.mu.Lock()
137-
138140
wallets := make([]accounts.Wallet, 0, len(accs))
139141
events := []accounts.WalletEvent{}
140142

141143
for _, account := range accs {
142144
// Drop wallets while they were in front of the next account
143-
for len(ks.wallets) > 0 && ks.wallets[0].URL() < account.URL {
145+
for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
144146
events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Arrive: false})
145147
ks.wallets = ks.wallets[1:]
146148
}
147149
// If there are no more wallets or the account is before the next, wrap new wallet
148-
if len(ks.wallets) == 0 || ks.wallets[0].URL() > account.URL {
150+
if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
149151
wallet := &keystoreWallet{account: account, keystore: ks}
150152

151153
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: true})
@@ -242,7 +244,7 @@ func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
242244
// The order is crucial here. The key is dropped from the
243245
// cache after the file is gone so that a reload happening in
244246
// between won't insert it into the cache again.
245-
err = os.Remove(a.URL)
247+
err = os.Remove(a.URL.Path)
246248
if err == nil {
247249
ks.cache.delete(a)
248250
ks.refreshWallets()
@@ -377,7 +379,7 @@ func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.A
377379
if err != nil {
378380
return a, nil, err
379381
}
380-
key, err := ks.storage.GetKey(a.Address, a.URL, auth)
382+
key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
381383
return a, key, err
382384
}
383385

@@ -453,8 +455,8 @@ func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (acco
453455
}
454456

455457
func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) {
456-
a := accounts.Account{Address: key.Address, URL: ks.storage.JoinPath(keyFileName(key.Address))}
457-
if err := ks.storage.StoreKey(a.URL, key, passphrase); err != nil {
458+
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
459+
if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
458460
return accounts.Account{}, err
459461
}
460462
ks.cache.add(a)
@@ -468,7 +470,7 @@ func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string)
468470
if err != nil {
469471
return err
470472
}
471-
return ks.storage.StoreKey(a.URL, key, newPassphrase)
473+
return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
472474
}
473475

474476
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores

accounts/keystore/keystore_plain_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestKeyStorePlain(t *testing.T) {
5252
if err != nil {
5353
t.Fatal(err)
5454
}
55-
k2, err := ks.GetKey(k1.Address, account.URL, pass)
55+
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
5656
if err != nil {
5757
t.Fatal(err)
5858
}
@@ -73,7 +73,7 @@ func TestKeyStorePassphrase(t *testing.T) {
7373
if err != nil {
7474
t.Fatal(err)
7575
}
76-
k2, err := ks.GetKey(k1.Address, account.URL, pass)
76+
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
7777
if err != nil {
7878
t.Fatal(err)
7979
}
@@ -94,7 +94,7 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
9494
if err != nil {
9595
t.Fatal(err)
9696
}
97-
if _, err = ks.GetKey(k1.Address, account.URL, "bar"); err != ErrDecrypt {
97+
if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
9898
t.Fatalf("wrong error for invalid passphrase\ngot %q\nwant %q", err, ErrDecrypt)
9999
}
100100
}
@@ -115,7 +115,7 @@ func TestImportPreSaleKey(t *testing.T) {
115115
if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") {
116116
t.Errorf("imported account has wrong address %x", account.Address)
117117
}
118-
if !strings.HasPrefix(account.URL, dir) {
118+
if !strings.HasPrefix(account.URL.Path, dir) {
119119
t.Errorf("imported account file not in keystore directory: %q", account.URL)
120120
}
121121
}

accounts/keystore/keystore_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ func TestKeyStore(t *testing.T) {
4141
if err != nil {
4242
t.Fatal(err)
4343
}
44-
if !strings.HasPrefix(a.URL, dir) {
44+
if !strings.HasPrefix(a.URL.Path, dir) {
4545
t.Errorf("account file %s doesn't have dir prefix", a.URL)
4646
}
47-
stat, err := os.Stat(a.URL)
47+
stat, err := os.Stat(a.URL.Path)
4848
if err != nil {
4949
t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
5050
}
@@ -60,7 +60,7 @@ func TestKeyStore(t *testing.T) {
6060
if err := ks.Delete(a, "bar"); err != nil {
6161
t.Errorf("Delete error: %v", err)
6262
}
63-
if common.FileExist(a.URL) {
63+
if common.FileExist(a.URL.Path) {
6464
t.Errorf("account file %s should be gone after Delete", a.URL)
6565
}
6666
if ks.HasAddress(a.Address) {
@@ -286,7 +286,7 @@ func TestWalletNotifications(t *testing.T) {
286286

287287
// Randomly add and remove account and make sure events and wallets are in sync
288288
live := make(map[common.Address]accounts.Account)
289-
for i := 0; i < 1024; i++ {
289+
for i := 0; i < 256; i++ {
290290
// Execute a creation or deletion and ensure event arrival
291291
if create := len(live) == 0 || rand.Int()%4 > 0; create {
292292
// Add a new account and ensure wallet notifications arrives
@@ -349,8 +349,6 @@ func TestWalletNotifications(t *testing.T) {
349349
}
350350
}
351351
}
352-
// Sleep a bit to avoid same-timestamp keyfiles
353-
time.Sleep(10 * time.Millisecond)
354352
}
355353
}
356354

0 commit comments

Comments
 (0)