account_cache_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // Copyright 2017 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package keystore
  17. import (
  18. "fmt"
  19. "io/ioutil"
  20. "math/rand"
  21. "os"
  22. "path/filepath"
  23. "reflect"
  24. "sort"
  25. "testing"
  26. "time"
  27. "github.com/cespare/cp"
  28. "github.com/davecgh/go-spew/spew"
  29. "github.com/ethereum/go-ethereum/accounts"
  30. "github.com/ethereum/go-ethereum/common"
  31. )
  32. var (
  33. cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore"))
  34. cachetestAccounts = []accounts.Account{
  35. {
  36. Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
  37. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
  38. },
  39. {
  40. Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
  41. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
  42. },
  43. {
  44. Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
  45. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
  46. },
  47. }
  48. )
  49. func TestWatchNewFile(t *testing.T) {
  50. t.Parallel()
  51. dir, ks := tmpKeyStore(t, false)
  52. defer os.RemoveAll(dir)
  53. // Ensure the watcher is started before adding any files.
  54. ks.Accounts()
  55. time.Sleep(1000 * time.Millisecond)
  56. // Move in the files.
  57. wantAccounts := make([]accounts.Account, len(cachetestAccounts))
  58. for i := range cachetestAccounts {
  59. wantAccounts[i] = accounts.Account{
  60. Address: cachetestAccounts[i].Address,
  61. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
  62. }
  63. if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
  64. t.Fatal(err)
  65. }
  66. }
  67. // ks should see the accounts.
  68. var list []accounts.Account
  69. for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
  70. list = ks.Accounts()
  71. if reflect.DeepEqual(list, wantAccounts) {
  72. // ks should have also received change notifications
  73. select {
  74. case <-ks.changes:
  75. default:
  76. t.Fatalf("wasn't notified of new accounts")
  77. }
  78. return
  79. }
  80. time.Sleep(d)
  81. }
  82. t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
  83. }
  84. func TestWatchNoDir(t *testing.T) {
  85. t.Parallel()
  86. // Create ks but not the directory that it watches.
  87. rand.Seed(time.Now().UnixNano())
  88. dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
  89. ks := NewKeyStore(dir, LightScryptN, LightScryptP)
  90. list := ks.Accounts()
  91. if len(list) > 0 {
  92. t.Error("initial account list not empty:", list)
  93. }
  94. time.Sleep(100 * time.Millisecond)
  95. // Create the directory and copy a key file into it.
  96. os.MkdirAll(dir, 0700)
  97. defer os.RemoveAll(dir)
  98. file := filepath.Join(dir, "aaa")
  99. if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
  100. t.Fatal(err)
  101. }
  102. // ks should see the account.
  103. wantAccounts := []accounts.Account{cachetestAccounts[0]}
  104. wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
  105. for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
  106. list = ks.Accounts()
  107. if reflect.DeepEqual(list, wantAccounts) {
  108. // ks should have also received change notifications
  109. select {
  110. case <-ks.changes:
  111. default:
  112. t.Fatalf("wasn't notified of new accounts")
  113. }
  114. return
  115. }
  116. time.Sleep(d)
  117. }
  118. t.Errorf("\ngot %v\nwant %v", list, wantAccounts)
  119. }
  120. func TestCacheInitialReload(t *testing.T) {
  121. cache, _ := newAccountCache(cachetestDir)
  122. accounts := cache.accounts()
  123. if !reflect.DeepEqual(accounts, cachetestAccounts) {
  124. t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
  125. }
  126. }
  127. func TestCacheAddDeleteOrder(t *testing.T) {
  128. cache, _ := newAccountCache("testdata/no-such-dir")
  129. cache.watcher.running = true // prevent unexpected reloads
  130. accs := []accounts.Account{
  131. {
  132. Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
  133. URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
  134. },
  135. {
  136. Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
  137. URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
  138. },
  139. {
  140. Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
  141. URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
  142. },
  143. {
  144. Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
  145. URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
  146. },
  147. {
  148. Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
  149. URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
  150. },
  151. {
  152. Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
  153. URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
  154. },
  155. {
  156. Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
  157. URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
  158. },
  159. }
  160. for _, a := range accs {
  161. cache.add(a)
  162. }
  163. // Add some of them twice to check that they don't get reinserted.
  164. cache.add(accs[0])
  165. cache.add(accs[2])
  166. // Check that the account list is sorted by filename.
  167. wantAccounts := make([]accounts.Account, len(accs))
  168. copy(wantAccounts, accs)
  169. sort.Sort(accountsByURL(wantAccounts))
  170. list := cache.accounts()
  171. if !reflect.DeepEqual(list, wantAccounts) {
  172. t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
  173. }
  174. for _, a := range accs {
  175. if !cache.hasAddress(a.Address) {
  176. t.Errorf("expected hasAccount(%x) to return true", a.Address)
  177. }
  178. }
  179. if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
  180. t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
  181. }
  182. // Delete a few keys from the cache.
  183. for i := 0; i < len(accs); i += 2 {
  184. cache.delete(wantAccounts[i])
  185. }
  186. cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
  187. // Check content again after deletion.
  188. wantAccountsAfterDelete := []accounts.Account{
  189. wantAccounts[1],
  190. wantAccounts[3],
  191. wantAccounts[5],
  192. }
  193. list = cache.accounts()
  194. if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
  195. t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
  196. }
  197. for _, a := range wantAccountsAfterDelete {
  198. if !cache.hasAddress(a.Address) {
  199. t.Errorf("expected hasAccount(%x) to return true", a.Address)
  200. }
  201. }
  202. if cache.hasAddress(wantAccounts[0].Address) {
  203. t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
  204. }
  205. }
  206. func TestCacheFind(t *testing.T) {
  207. dir := filepath.Join("testdata", "dir")
  208. cache, _ := newAccountCache(dir)
  209. cache.watcher.running = true // prevent unexpected reloads
  210. accs := []accounts.Account{
  211. {
  212. Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
  213. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
  214. },
  215. {
  216. Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
  217. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
  218. },
  219. {
  220. Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
  221. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
  222. },
  223. {
  224. Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
  225. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
  226. },
  227. }
  228. for _, a := range accs {
  229. cache.add(a)
  230. }
  231. nomatchAccount := accounts.Account{
  232. Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
  233. URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
  234. }
  235. tests := []struct {
  236. Query accounts.Account
  237. WantResult accounts.Account
  238. WantError error
  239. }{
  240. // by address
  241. {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
  242. // by file
  243. {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
  244. // by basename
  245. {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
  246. // by file and address
  247. {Query: accs[0], WantResult: accs[0]},
  248. // ambiguous address, tie resolved by file
  249. {Query: accs[2], WantResult: accs[2]},
  250. // ambiguous address error
  251. {
  252. Query: accounts.Account{Address: accs[2].Address},
  253. WantError: &AmbiguousAddrError{
  254. Addr: accs[2].Address,
  255. Matches: []accounts.Account{accs[2], accs[3]},
  256. },
  257. },
  258. // no match error
  259. {Query: nomatchAccount, WantError: ErrNoMatch},
  260. {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
  261. {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
  262. {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
  263. }
  264. for i, test := range tests {
  265. a, err := cache.find(test.Query)
  266. if !reflect.DeepEqual(err, test.WantError) {
  267. t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
  268. continue
  269. }
  270. if a != test.WantResult {
  271. t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
  272. continue
  273. }
  274. }
  275. }
  276. func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
  277. var list []accounts.Account
  278. for d := 200 * time.Millisecond; d < 16*time.Second; d *= 2 {
  279. list = ks.Accounts()
  280. if reflect.DeepEqual(list, wantAccounts) {
  281. // ks should have also received change notifications
  282. select {
  283. case <-ks.changes:
  284. default:
  285. return fmt.Errorf("wasn't notified of new accounts")
  286. }
  287. return nil
  288. }
  289. time.Sleep(d)
  290. }
  291. return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
  292. }
  293. // TestUpdatedKeyfileContents tests that updating the contents of a keystore file
  294. // is noticed by the watcher, and the account cache is updated accordingly
  295. func TestUpdatedKeyfileContents(t *testing.T) {
  296. t.Parallel()
  297. // Create a temporary kesytore to test with
  298. rand.Seed(time.Now().UnixNano())
  299. dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
  300. ks := NewKeyStore(dir, LightScryptN, LightScryptP)
  301. list := ks.Accounts()
  302. if len(list) > 0 {
  303. t.Error("initial account list not empty:", list)
  304. }
  305. time.Sleep(100 * time.Millisecond)
  306. // Create the directory and copy a key file into it.
  307. os.MkdirAll(dir, 0700)
  308. defer os.RemoveAll(dir)
  309. file := filepath.Join(dir, "aaa")
  310. // Place one of our testfiles in there
  311. if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
  312. t.Fatal(err)
  313. }
  314. // ks should see the account.
  315. wantAccounts := []accounts.Account{cachetestAccounts[0]}
  316. wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
  317. if err := waitForAccounts(wantAccounts, ks); err != nil {
  318. t.Error(err)
  319. return
  320. }
  321. // needed so that modTime of `file` is different to its current value after forceCopyFile
  322. time.Sleep(1000 * time.Millisecond)
  323. // Now replace file contents
  324. if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
  325. t.Fatal(err)
  326. return
  327. }
  328. wantAccounts = []accounts.Account{cachetestAccounts[1]}
  329. wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
  330. if err := waitForAccounts(wantAccounts, ks); err != nil {
  331. t.Errorf("First replacement failed")
  332. t.Error(err)
  333. return
  334. }
  335. // needed so that modTime of `file` is different to its current value after forceCopyFile
  336. time.Sleep(1000 * time.Millisecond)
  337. // Now replace file contents again
  338. if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
  339. t.Fatal(err)
  340. return
  341. }
  342. wantAccounts = []accounts.Account{cachetestAccounts[2]}
  343. wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
  344. if err := waitForAccounts(wantAccounts, ks); err != nil {
  345. t.Errorf("Second replacement failed")
  346. t.Error(err)
  347. return
  348. }
  349. // needed so that modTime of `file` is different to its current value after ioutil.WriteFile
  350. time.Sleep(1000 * time.Millisecond)
  351. // Now replace file contents with crap
  352. if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
  353. t.Fatal(err)
  354. return
  355. }
  356. if err := waitForAccounts([]accounts.Account{}, ks); err != nil {
  357. t.Errorf("Emptying account file failed")
  358. t.Error(err)
  359. return
  360. }
  361. }
  362. // forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
  363. func forceCopyFile(dst, src string) error {
  364. data, err := ioutil.ReadFile(src)
  365. if err != nil {
  366. return err
  367. }
  368. return ioutil.WriteFile(dst, data, 0644)
  369. }