From a674c0b8ed012ce0d9458c4409ed6938e659642d Mon Sep 17 00:00:00 2001 From: Huiyi Li Date: Thu, 26 Mar 2020 19:35:25 -0700 Subject: [PATCH] accounts/keystore, finish db_keystore_test --- accounts/keystore/db_keystore_test.go | 204 ++++++++++++++++++ accounts/keystore/keystore.go | 12 ++ .../keystore/testdata/db_keystore_test.yaml | 1 + 3 files changed, 217 insertions(+) create mode 100644 accounts/keystore/db_keystore_test.go create mode 100644 accounts/keystore/testdata/db_keystore_test.yaml diff --git a/accounts/keystore/db_keystore_test.go b/accounts/keystore/db_keystore_test.go new file mode 100644 index 0000000000..fe575cb809 --- /dev/null +++ b/accounts/keystore/db_keystore_test.go @@ -0,0 +1,204 @@ +// Copyright 2017 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 . + +package keystore + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/cmd/clef/dbutil" + "github.com/ethereum/go-ethereum/common" + _ "github.com/mattn/go-sqlite3" +) + +func TestKeyStoreDB(t *testing.T) { + ks := tmpKeyStoreDB(t) + + acc, err := ks.NewAccount("foo") + if err != nil { + t.Fatal(err) + } + if !ks.HasAddress(acc.Address) { + t.Errorf("HasAccount(%x) should've returned true", acc.Address) + } + if err := ks.Update(acc, "foo", "bar"); err != nil { + t.Errorf("Update error: %v", err) + } + if err := ks.Delete(acc, "bar"); err != nil { + t.Errorf("Delete error: %v", err) + } + if ks.HasAddress(acc.Address) { + t.Errorf("HasAccount(%x) should've returned true after Delete", acc.Address) + } +} + +func TestSignDB(t *testing.T) { + ks := tmpKeyStoreDB(t) + + pass := "" // not used by required by API + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + if err := ks.Unlock(acc, ""); err != nil { + t.Fatal(err) + } + if _, err := ks.SignHash(accounts.Account{Address: acc.Address}, testSigData); err != nil { + t.Fatal(err) + } +} + +func TestSignWithPassphraseDB(t *testing.T) { + ks := tmpKeyStoreDB(t) + + pass := "passwd" + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + _, err = ks.SignHashWithPassphrase(acc, pass, testSigData) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { + t.Fatal("expected SignHashWithPassphrase to fail with invalid password") + } +} + +func TestTimedUnlockDB(t *testing.T) { + ks := tmpKeyStoreDB(t) + + pass := "foo" + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Signing without passphrase fails because account is locked + _, err = ks.SignHash(accounts.Account{Address: acc.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) + } + + // Signing with passphrase works + if err = ks.TimedUnlock(acc, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: acc.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: acc.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +func TestOverrideUnlockDB(t *testing.T) { + ks := tmpKeyStoreDB(t) + + pass := "foo" + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Unlock indefinitely. + if err = ks.TimedUnlock(acc, pass, 5*time.Minute); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: acc.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // reset unlock to a shorter period, invalidates the previous unlock + if err = ks.TimedUnlock(acc, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase still works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: acc.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: acc.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +// This test should fail under -race if signing races the expiration goroutine. +func TestSignRaceDB(t *testing.T) { + ks := tmpKeyStoreDB(t) + + pass := "" + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + if err := ks.TimedUnlock(acc, "", 15*time.Millisecond); err != nil { + t.Fatal("could not unlock the test account", err) + } + end := time.Now().Add(500 * time.Millisecond) + for time.Now().Before(end) { + if _, err := ks.SignHash(accounts.Account{Address: acc.Address}, testSigData); err == ErrLocked { + return + } else if err != nil { + t.Errorf("Sign error: %v", err) + return + } + time.Sleep(1 * time.Millisecond) + } + t.Errorf("Account did not lock within the timeout") +} + +func tmpKeyStoreDB(t *testing.T) *keyStoreDB { + // ks, err := NewKeyStoreDB("./testdata/db_keystore_test.yaml", "testTable", veryLightScryptN, veryLightScryptP) + kvstore, err := dbutil.NewKVStore("./testdata/db_keystore_test.yaml", "testTable") + if err != nil { + t.Fatal(err) + } + storage := &keyStorePassphraseDB{kvstore, veryLightScryptN, veryLightScryptP, false} + ks := &keyStoreDB{storage: storage, unlocked: make(map[common.Address]*unlocked)} + if err != nil { + t.Fatal("Cannot initiate database keystore: ", err) + } + return ks +} diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index d3faf7b77a..6a14bfad9d 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/cmd/clef/dbutil" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" @@ -117,3 +118,14 @@ func NewPlaintextKeyStore(keydir string) KeyStore { ks.init(keydir) return ks } + +// NewKeyStoreDB creates a keystore for the given database +func NewKeyStoreDB(path, table string, scryptN, scryptP int) (KeyStore, error) { + kvstore, err := dbutil.NewKVStore(path, table) + if err != nil { + return nil, err + } + storage := &keyStorePassphraseDB{kvstore, scryptN, scryptP, false} + ks := &keyStoreDB{storage: storage, unlocked: make(map[common.Address]*unlocked)} + return ks, nil +} diff --git a/accounts/keystore/testdata/db_keystore_test.yaml b/accounts/keystore/testdata/db_keystore_test.yaml new file mode 100644 index 0000000000..590686e67e --- /dev/null +++ b/accounts/keystore/testdata/db_keystore_test.yaml @@ -0,0 +1 @@ +adapter: sqlite3 \ No newline at end of file