aboutsummaryrefslogblamecommitdiffstats
path: root/accounts/keystore/account_cache_test.go
blob: 3e68351ea5db7a91ace77e1ba044fd6694e50324 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                                                                  
                












                                         
                                                  




                                                                               
                                               

                                                                                                 
                                                                                                                                                                                          


                                                                                                 
                                                                                                                


                                                                                                 
                                                                                                                






                                     
                                        


                                                                 
                     


                                          
                                                                        
                                          




                                                                                                                                              



                                    
                                      
                                   
                                                                    
                                    
                                                          





                                                                            









                                                                               
                                                           

                                                                                                                 
                                                          
 
                             








                                                                
                                                                                


                            
                                     
                                                                
                                                                              
                                                                    
                                    
                                                          





                                                                            







                                                          
                                                 






                                                                                                                
                                                                

                                                                  
                                   

                                                                                                 
                                                                                          


                                                                                                 
                                                                                   


                                                                                                 
                                                                                                               


                                                                                                 
                                                                                             


                                                                                                 
                                                                                                                                                             


                                                                                                 
                                                                                   


                                                                                                 
                                                                                   

                  
                                

                            




                                                                             
                                                                          

                          
 




                                                                            
                                                             

                                                           
                                              

                                                   
                                                                                                 
         
                                








                                                                                                                                    
                                           

                                             
                                                                                                                                                                              
 




                                                                           
                                              
                                                      



















                                                                                                                         
                                        

                                                                  
                                   

                                                                                                 
                                                                                                         


                                                                                                 
                                                                                                         


                                                                                                 
                                                                                                         


                                                                                                 
                                                                                                          

                  
                                


                            
                                           
                                                                                         
                                                                                                     

                           

                                           


                                
                                                                                         
                          
                                                                                 
                              
                                                                                                                                                 
                                      
                                                      
                                                          
                                                      

                                          
                                                                          
                                                       

                                                                              



                                                               
                                                                                          
                                                                                                                                                          
                                                                                                  












                                                                                                                             
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package keystore

import (
    "fmt"
    "math/rand"
    "os"
    "path/filepath"
    "reflect"
    "sort"
    "testing"
    "time"

    "github.com/cespare/cp"
    "github.com/davecgh/go-spew/spew"
    "github.com/ethereum/go-ethereum/accounts"
    "github.com/ethereum/go-ethereum/common"
)

var (
    cachetestDir, _   = filepath.Abs(filepath.Join("testdata", "keystore"))
    cachetestAccounts = []accounts.Account{
        {
            Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
        },
        {
            Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
        },
        {
            Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
        },
    }
)

func TestWatchNewFile(t *testing.T) {
    t.Parallel()

    dir, ks := tmpKeyStore(t, false)
    defer os.RemoveAll(dir)

    // Ensure the watcher is started before adding any files.
    ks.Accounts()
    time.Sleep(200 * time.Millisecond)

    // Move in the files.
    wantAccounts := make([]accounts.Account, len(cachetestAccounts))
    for i := range cachetestAccounts {
        wantAccounts[i] = accounts.Account{
            Address: cachetestAccounts[i].Address,
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
        }
        if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
            t.Fatal(err)
        }
    }

    // ks should see the accounts.
    var list []accounts.Account
    for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
        list = ks.Accounts()
        if reflect.DeepEqual(list, wantAccounts) {
            // ks should have also received change notifications
            select {
            case <-ks.changes:
            default:
                t.Fatalf("wasn't notified of new accounts")
            }
            return
        }
        time.Sleep(d)
    }
    t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
}

func TestWatchNoDir(t *testing.T) {
    t.Parallel()

    // Create ks but not the directory that it watches.
    rand.Seed(time.Now().UnixNano())
    dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
    ks := NewKeyStore(dir, LightScryptN, LightScryptP)

    list := ks.Accounts()
    if len(list) > 0 {
        t.Error("initial account list not empty:", list)
    }
    time.Sleep(100 * time.Millisecond)

    // Create the directory and copy a key file into it.
    os.MkdirAll(dir, 0700)
    defer os.RemoveAll(dir)
    file := filepath.Join(dir, "aaa")
    if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
        t.Fatal(err)
    }

    // ks should see the account.
    wantAccounts := []accounts.Account{cachetestAccounts[0]}
    wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
    for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
        list = ks.Accounts()
        if reflect.DeepEqual(list, wantAccounts) {
            // ks should have also received change notifications
            select {
            case <-ks.changes:
            default:
                t.Fatalf("wasn't notified of new accounts")
            }
            return
        }
        time.Sleep(d)
    }
    t.Errorf("\ngot  %v\nwant %v", list, wantAccounts)
}

func TestCacheInitialReload(t *testing.T) {
    cache, _ := newAccountCache(cachetestDir)
    accounts := cache.accounts()
    if !reflect.DeepEqual(accounts, cachetestAccounts) {
        t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
    }
}

func TestCacheAddDeleteOrder(t *testing.T) {
    cache, notify := newAccountCache("testdata/no-such-dir")
    cache.watcher.running = true // prevent unexpected reloads

    accs := []accounts.Account{
        {
            Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
        },
        {
            Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
        },
        {
            Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
        },
        {
            Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
        },
        {
            Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
        },
        {
            Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
        },
        {
            Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
        },
    }
    for _, a := range accs {
        cache.add(a)
    }
    select {
    case <-notify:
    default:
        t.Fatalf("notifications didn't fire for adding new accounts")
    }
    // Add some of them twice to check that they don't get reinserted.
    cache.add(accs[0])
    cache.add(accs[2])

    select {
    case <-notify:
        t.Fatalf("notifications fired for adding existing accounts")
    default:
    }
    // Check that the account list is sorted by filename.
    wantAccounts := make([]accounts.Account, len(accs))
    copy(wantAccounts, accs)
    sort.Sort(accountsByURL(wantAccounts))
    list := cache.accounts()
    if !reflect.DeepEqual(list, wantAccounts) {
        t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
    }
    for _, a := range accs {
        if !cache.hasAddress(a.Address) {
            t.Errorf("expected hasAccount(%x) to return true", a.Address)
        }
    }
    if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
        t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
    }

    // Delete a few keys from the cache.
    for i := 0; i < len(accs); i += 2 {
        cache.delete(wantAccounts[i])
    }
    cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})

    select {
    case <-notify:
    default:
        t.Fatalf("notifications didn't fire for deleting accounts")
    }
    // Check content again after deletion.
    wantAccountsAfterDelete := []accounts.Account{
        wantAccounts[1],
        wantAccounts[3],
        wantAccounts[5],
    }
    list = cache.accounts()
    if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
        t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
    }
    for _, a := range wantAccountsAfterDelete {
        if !cache.hasAddress(a.Address) {
            t.Errorf("expected hasAccount(%x) to return true", a.Address)
        }
    }
    if cache.hasAddress(wantAccounts[0].Address) {
        t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
    }
}

func TestCacheFind(t *testing.T) {
    dir := filepath.Join("testdata", "dir")
    cache, _ := newAccountCache(dir)
    cache.watcher.running = true // prevent unexpected reloads

    accs := []accounts.Account{
        {
            Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
        },
        {
            Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
        },
        {
            Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
        },
        {
            Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
            URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
        },
    }
    for _, a := range accs {
        cache.add(a)
    }

    nomatchAccount := accounts.Account{
        Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
        URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
    }
    tests := []struct {
        Query      accounts.Account
        WantResult accounts.Account
        WantError  error
    }{
        // by address
        {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
        // by file
        {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
        // by basename
        {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
        // by file and address
        {Query: accs[0], WantResult: accs[0]},
        // ambiguous address, tie resolved by file
        {Query: accs[2], WantResult: accs[2]},
        // ambiguous address error
        {
            Query: accounts.Account{Address: accs[2].Address},
            WantError: &AmbiguousAddrError{
                Addr:    accs[2].Address,
                Matches: []accounts.Account{accs[2], accs[3]},
            },
        },
        // no match error
        {Query: nomatchAccount, WantError: ErrNoMatch},
        {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
        {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
        {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
    }
    for i, test := range tests {
        a, err := cache.find(test.Query)
        if !reflect.DeepEqual(err, test.WantError) {
            t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
            continue
        }
        if a != test.WantResult {
            t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
            continue
        }
    }
}