diff --git a/pkg/fswallet/config.go b/pkg/fswallet/config.go index 188ada0..a3b821e 100644 --- a/pkg/fswallet/config.go +++ b/pkg/fswallet/config.go @@ -17,7 +17,10 @@ package fswallet import ( + "context" + "github.com/hyperledger/firefly-common/pkg/config" + "github.com/hyperledger/firefly-signer/pkg/keystorev3" ) const ( @@ -61,6 +64,12 @@ type Config struct { Metadata MetadataConfig } +type ConfigGeneric struct { + Config + WalletFileValidator func(ctx context.Context, addrString string, kv3 keystorev3.WalletFile) error + AddressValidator func(ctx context.Context, addrString string) (string, error) +} + type FilenamesConfig struct { PrimaryMatchRegex string PrimaryExt string diff --git a/pkg/fswallet/fslistener_test.go b/pkg/fswallet/fslistener_test.go index 9f6211d..0572d61 100644 --- a/pkg/fswallet/fslistener_test.go +++ b/pkg/fswallet/fslistener_test.go @@ -31,7 +31,7 @@ import ( "github.com/stretchr/testify/assert" ) -func newEmptyWalletTestDir(t *testing.T, init bool) (context.Context, *fsWallet, chan ethtypes.Address0xHex, func()) { +func newEmptyWalletTestDir(t *testing.T, init bool) (context.Context, *walletEthAddr, chan ethtypes.Address0xHex, func()) { config.RootConfigReset() logrus.SetLevel(logrus.TraceLevel) @@ -50,7 +50,7 @@ func newEmptyWalletTestDir(t *testing.T, init bool) (context.Context, *fsWallet, assert.NoError(t, err) } - return ctx, ff.(*fsWallet), listener, func() { + return ctx, ff.(*walletEthAddr), listener, func() { ff.Close() } } @@ -67,13 +67,14 @@ func TestFileListener(t *testing.T) { testPWFIle, err := os.ReadFile("../../test/keystore_toml/1f185718734552d08278aa70f804580bab5fd2b4.pwd") assert.NoError(t, err) - err = os.WriteFile(path.Join(f.conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.pwd"), testPWFIle, 0644) + conf := &f.gw.(*fsWallet).conf + err = os.WriteFile(path.Join(conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.pwd"), testPWFIle, 0644) assert.NoError(t, err) testKeyFIle, err := os.ReadFile("../../test/keystore_toml/1f185718734552d08278aa70f804580bab5fd2b4.key.json") assert.NoError(t, err) - err = os.WriteFile(path.Join(f.conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.key.json"), testKeyFIle, 0644) + err = os.WriteFile(path.Join(conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.key.json"), testKeyFIle, 0644) assert.NoError(t, err) newAddr1 := <-listener1 @@ -93,7 +94,8 @@ func TestFileListenerStartFail(t *testing.T) { ctx, f, _, done := newEmptyWalletTestDir(t, false) defer done() - os.RemoveAll(f.conf.Path) + conf := &f.gw.(*fsWallet).conf + os.RemoveAll(conf.Path) err := f.Initialize(ctx) assert.Regexp(t, "FF22060", err) @@ -101,7 +103,7 @@ func TestFileListenerStartFail(t *testing.T) { func TestFileListenerRemoveDirWhileListening(t *testing.T) { - ctx, f, _, done := newEmptyWalletTestDir(t, true) + ctx, ew, _, done := newEmptyWalletTestDir(t, true) defer done() errs := make(chan error, 1) @@ -111,6 +113,7 @@ func TestFileListenerRemoveDirWhileListening(t *testing.T) { time.Sleep(10 * time.Millisecond) cancelCtx() }() + f := ew.gw.(*fsWallet) f.fsListenerLoop(ctx, func() {}, make(chan fsnotify.Event), errs) } diff --git a/pkg/fswallet/fswallet.go b/pkg/fswallet/fswallet.go index 953c634..5348a75 100644 --- a/pkg/fswallet/fswallet.go +++ b/pkg/fswallet/fswallet.go @@ -32,33 +32,33 @@ import ( "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly-signer/internal/signermsgs" - "github.com/hyperledger/firefly-signer/pkg/eip712" - "github.com/hyperledger/firefly-signer/pkg/ethsigner" - "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/hyperledger/firefly-signer/pkg/keystorev3" - "github.com/hyperledger/firefly-signer/pkg/secp256k1" "github.com/karlseguin/ccache" "github.com/pelletier/go-toml" "gopkg.in/yaml.v2" ) -type SyncAddressCallback func(context.Context, ethtypes.Address0xHex) error +type SyncCallback func(context.Context, string) error // Wallet is a directory containing a set of KeystoreV3 files, conforming // to the ethsigner.Wallet interface and providing notifications when new // keys are added to the wallet (via FS listener). -type Wallet interface { - ethsigner.WalletTypedData - GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex) (keystorev3.WalletFile, error) - SetSyncAddressCallback(SyncAddressCallback) - AddListener(listener chan<- ethtypes.Address0xHex) +type WalletGeneric interface { + Initialize(ctx context.Context) error + Refresh(ctx context.Context) error + Close() error + + GetAccounts(ctx context.Context) ([]string, error) + GetWalletFile(ctx context.Context, addr string) (keystorev3.WalletFile, error) + SetSyncCallback(SyncCallback) + AddListener(listener chan<- string) } -func NewFilesystemWallet(ctx context.Context, conf *Config, initialListeners ...chan<- ethtypes.Address0xHex) (ww Wallet, err error) { +func NewFilesystemWalletGeneric(ctx context.Context, conf *ConfigGeneric, initialListeners ...chan<- string) (ww WalletGeneric, err error) { w := &fsWallet{ conf: *conf, listeners: initialListeners, - addressToFileMap: make(map[ethtypes.Address0xHex]string), + addressToFileMap: make(map[string]string), } w.signerCache = ccache.New( // We use a LRU cache with a size-aware max @@ -96,39 +96,23 @@ func goTemplateFromConfig(ctx context.Context, name string, templateStr string) } type fsWallet struct { - conf Config + conf ConfigGeneric signerCache *ccache.Cache signerCacheTTL time.Duration metadataKeyFileProperty *template.Template metadataPasswordFileProperty *template.Template primaryMatchRegex *regexp.Regexp - syncAddressCallback SyncAddressCallback + syncCallback SyncCallback mux sync.Mutex - addressToFileMap map[ethtypes.Address0xHex]string // map for lookup to filename - addressList []*ethtypes.Address0xHex // ordered list in filename at startup, then notification order - listeners []chan<- ethtypes.Address0xHex + addressToFileMap map[string]string // map for lookup to filename + addressList []string // ordered list in filename at startup, then notification order + listeners []chan<- string fsListenerCancel context.CancelFunc fsListenerStarted chan error fsListenerDone chan struct{} } -func (w *fsWallet) Sign(ctx context.Context, txn *ethsigner.Transaction, chainID int64) ([]byte, error) { - keypair, err := w.getSignerForJSONAccount(ctx, txn.From) - if err != nil { - return nil, err - } - return txn.Sign(keypair, chainID) -} - -func (w *fsWallet) SignTypedDataV4(ctx context.Context, from ethtypes.Address0xHex, payload *eip712.TypedData) (*ethsigner.EIP712Result, error) { - keypair, err := w.getSignerForAddr(ctx, from) - if err != nil { - return nil, err - } - return ethsigner.SignTypedDataV4(ctx, keypair, payload) -} - func (w *fsWallet) Initialize(ctx context.Context) error { // Run a get accounts pass, to check all is ok lCtx, lCancel := context.WithCancel(log.WithLogField(ctx, "fswallet", w.conf.Path)) @@ -144,7 +128,7 @@ func (w *fsWallet) Initialize(ctx context.Context) error { } // Asynchronously listen for all addresses as they are detected - during startup, or after startup -func (w *fsWallet) AddListener(listener chan<- ethtypes.Address0xHex) { +func (w *fsWallet) AddListener(listener chan<- string) { w.mux.Lock() defer w.mux.Unlock() w.listeners = append(w.listeners, listener) @@ -162,47 +146,54 @@ func (w *fsWallet) AddListener(listener chan<- ethtypes.Address0xHex) { // will be called concurrently. However, it is guaranteed to be called in-line with the // initialize/refresh so you know once that calls returns all new keys detected by it have // driven the callback. -func (w *fsWallet) SetSyncAddressCallback(callback SyncAddressCallback) { - w.syncAddressCallback = callback +func (w *fsWallet) SetSyncCallback(callback SyncCallback) { + w.syncCallback = callback } // GetAccounts returns the currently cached list of known addresses -func (w *fsWallet) GetAccounts(_ context.Context) ([]*ethtypes.Address0xHex, error) { +func (w *fsWallet) GetAccounts(_ context.Context) ([]string, error) { w.mux.Lock() defer w.mux.Unlock() - accounts := make([]*ethtypes.Address0xHex, len(w.addressList)) + accounts := make([]string, len(w.addressList)) copy(accounts, w.addressList) return accounts, nil } -func (w *fsWallet) matchFilename(ctx context.Context, f fs.FileInfo) *ethtypes.Address0xHex { +func (w *fsWallet) matchFilename(ctx context.Context, f fs.FileInfo) string { if f.IsDir() { log.L(ctx).Tracef("Ignoring '%s/%s: directory", w.conf.Path, f.Name()) - return nil + return "" } if w.primaryMatchRegex != nil { match := w.primaryMatchRegex.FindStringSubmatch(f.Name()) if match == nil { log.L(ctx).Tracef("Ignoring '%s/%s': does not match regexp", w.conf.Path, f.Name()) - return nil + return "" } - addr, err := ethtypes.NewAddress(match[1]) // safe due to SubexpNames() length check - if err != nil { - log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), match[1], err) - return nil + var err error + addrString := match[1] + if w.conf.AddressValidator != nil { + addrString, err = w.conf.AddressValidator(ctx, addrString) + if err != nil { + log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), match[1], err) + return "" + } } - return addr + return addrString } if !strings.HasSuffix(f.Name(), w.conf.Filenames.PrimaryExt) { log.L(ctx).Tracef("Ignoring '%s/%s: does not match extension '%s'", w.conf.Path, f.Name(), w.conf.Filenames.PrimaryExt) } addrString := strings.TrimSuffix(f.Name(), w.conf.Filenames.PrimaryExt) - addr, err := ethtypes.NewAddress(addrString) - if err != nil { - log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), addrString, err) - return nil + if w.conf.AddressValidator != nil { + var err error + addrString, err = w.conf.AddressValidator(ctx, addrString) + if err != nil { + log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), addrString, err) + return "" + } } - return addr + return addrString } func (w *fsWallet) Refresh(ctx context.Context) error { @@ -221,16 +212,16 @@ func (w *fsWallet) Refresh(ctx context.Context) error { return w.notifyNewFiles(ctx, files...) } -func (w *fsWallet) processNewFiles(ctx context.Context, files ...fs.FileInfo) (listeners []chan<- ethtypes.Address0xHex, newAddresses []*ethtypes.Address0xHex) { +func (w *fsWallet) processNewFiles(ctx context.Context, files ...fs.FileInfo) (listeners []chan<- string, newAddresses []string) { // Lock now we have the list w.mux.Lock() defer w.mux.Unlock() - newAddresses = make([]*ethtypes.Address0xHex, 0) + newAddresses = make([]string, 0) for _, f := range files { addr := w.matchFilename(ctx, f) - if addr != nil { - if existingFilename, exists := w.addressToFileMap[*addr]; existingFilename != f.Name() { - w.addressToFileMap[*addr] = f.Name() + if addr != "" { + if existingFilename, exists := w.addressToFileMap[addr]; existingFilename != f.Name() { + w.addressToFileMap[addr] = f.Name() if !exists { log.L(ctx).Debugf("Added address: %s (file=%s)", addr, f.Name()) w.addressList = append(w.addressList, addr) @@ -239,7 +230,7 @@ func (w *fsWallet) processNewFiles(ctx context.Context, files ...fs.FileInfo) (l } } } - listeners = make([]chan<- ethtypes.Address0xHex, len(w.listeners)) + listeners = make([]chan<- string, len(w.listeners)) copy(listeners, w.listeners) log.L(ctx).Debugf("Processed %d files. Found %d new addresses", len(files), len(newAddresses)) return listeners, newAddresses @@ -252,10 +243,10 @@ func (w *fsWallet) notifyNewFiles(ctx context.Context, files ...fs.FileInfo) err if len(newAddresses) > 0 { - if w.syncAddressCallback != nil { + if w.syncCallback != nil { // Sync callbacks are called here in-line, but outside the lock. for _, addr := range newAddresses { - if err := w.syncAddressCallback(ctx, *addr); err != nil { + if err := w.syncCallback(ctx, addr); err != nil { log.L(ctx).Errorf("sync listener returned error for address %s: %s", addr, err) return err } @@ -267,7 +258,7 @@ func (w *fsWallet) notifyNewFiles(ctx context.Context, files ...fs.FileInfo) err go func() { for _, l := range listeners { for _, addr := range newAddresses { - l <- *addr + l <- addr } } }() @@ -286,30 +277,8 @@ func (w *fsWallet) Close() error { return nil } -func (w *fsWallet) getSignerForJSONAccount(ctx context.Context, rawAddrJSON json.RawMessage) (*secp256k1.KeyPair, error) { +func (w *fsWallet) GetWalletFile(ctx context.Context, addrString string) (keystorev3.WalletFile, error) { - // We require an ethereum address in the "from" field - var from ethtypes.Address0xHex - err := json.Unmarshal(rawAddrJSON, &from) - if err != nil { - return nil, err - } - return w.getSignerForAddr(ctx, from) -} - -func (w *fsWallet) getSignerForAddr(ctx context.Context, from ethtypes.Address0xHex) (*secp256k1.KeyPair, error) { - - wf, err := w.GetWalletFile(ctx, from) - if err != nil { - return nil, err - } - return wf.KeyPair(), nil - -} - -func (w *fsWallet) GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex) (keystorev3.WalletFile, error) { - - addrString := addr.String() cached := w.signerCache.Get(addrString) if cached != nil { cached.Extend(w.signerCacheTTL) @@ -317,28 +286,28 @@ func (w *fsWallet) GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex } w.mux.Lock() - primaryFilename, ok := w.addressToFileMap[addr] + primaryFilename, ok := w.addressToFileMap[addrString] w.mux.Unlock() if !ok { - return nil, i18n.NewError(ctx, signermsgs.MsgWalletNotAvailable, addr) + return nil, i18n.NewError(ctx, signermsgs.MsgWalletNotAvailable, addrString) } - kv3, err := w.loadWalletFile(ctx, addr, path.Join(w.conf.Path, primaryFilename)) + kv3, err := w.loadWalletFile(ctx, addrString, path.Join(w.conf.Path, primaryFilename)) if err != nil { return nil, err } - keypair := kv3.KeyPair() - if keypair.Address != addr { - return nil, i18n.NewError(ctx, signermsgs.MsgAddressMismatch, keypair.Address, addr) + if w.conf.WalletFileValidator != nil { + if err := w.conf.WalletFileValidator(ctx, addrString, kv3); err != nil { + return nil, err + } } w.signerCache.Set(addrString, kv3, w.signerCacheTTL) return kv3, err - } -func (w *fsWallet) loadWalletFile(ctx context.Context, addr ethtypes.Address0xHex, primaryFilename string) (keystorev3.WalletFile, error) { +func (w *fsWallet) loadWalletFile(ctx context.Context, addr string, primaryFilename string) (keystorev3.WalletFile, error) { b, err := os.ReadFile(primaryFilename) if err != nil { @@ -395,7 +364,7 @@ func (w *fsWallet) loadWalletFile(ctx context.Context, addr ethtypes.Address0xHe } -func (w *fsWallet) getKeyAndPasswordFiles(ctx context.Context, addr ethtypes.Address0xHex, primaryFilename string, primaryFile []byte) (kf string, pf string, err error) { +func (w *fsWallet) getKeyAndPasswordFiles(ctx context.Context, addr string, primaryFilename string, primaryFile []byte) (kf string, pf string, err error) { if strings.ToLower(w.conf.Metadata.Format) == "auto" { w.conf.Metadata.Format = strings.TrimPrefix(w.conf.Filenames.PrimaryExt, ".") } @@ -414,7 +383,7 @@ func (w *fsWallet) getKeyAndPasswordFiles(ctx context.Context, addr ethtypes.Add if passwordPath == "" { passwordPath = w.conf.Path } - passwordFilename := addr.String() + passwordFilename := addr if !w.conf.Filenames.With0xPrefix { passwordFilename = strings.TrimPrefix(passwordFilename, "0x") } diff --git a/pkg/fswallet/fswallet_ethaddr.go b/pkg/fswallet/fswallet_ethaddr.go new file mode 100644 index 0000000..737aceb --- /dev/null +++ b/pkg/fswallet/fswallet_ethaddr.go @@ -0,0 +1,174 @@ +// Copyright © 2023 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fswallet + +import ( + "context" + "encoding/json" + + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-signer/internal/signermsgs" + "github.com/hyperledger/firefly-signer/pkg/eip712" + "github.com/hyperledger/firefly-signer/pkg/ethsigner" + "github.com/hyperledger/firefly-signer/pkg/ethtypes" + "github.com/hyperledger/firefly-signer/pkg/keystorev3" + "github.com/hyperledger/firefly-signer/pkg/secp256k1" +) + +type SyncAddressCallback func(context.Context, ethtypes.Address0xHex) error + +// This is a wrapper on the WalletStrIDs capability, that requires all lookups to keys +// to be ethereum addresses, and adds some other ethereum specific functionality. +// +// Originally this library only provided this utility, and later we made the concept +// of an address more generic. +type Wallet interface { + ethsigner.WalletTypedData + GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex) (keystorev3.WalletFile, error) + SetSyncAddressCallback(SyncAddressCallback) + AddListener(listener chan<- ethtypes.Address0xHex) +} + +type walletEthAddr struct { + gw WalletGeneric +} + +func NewFilesystemWallet(ctx context.Context, conf *Config, initialListeners ...chan<- ethtypes.Address0xHex) (ww Wallet, err error) { + gw, err := NewFilesystemWalletGeneric(ctx, &ConfigGeneric{ + Config: *conf, + WalletFileValidator: func(ctx context.Context, addrString string, kv3 keystorev3.WalletFile) error { + addr, err := ethtypes.NewAddress(addrString) + if err == nil { + keypair := kv3.KeyPair() + if keypair.Address != *addr { + err = i18n.NewError(ctx, signermsgs.MsgAddressMismatch, keypair.Address, addr) + } + } + return err + }, + AddressValidator: func(ctx context.Context, addrString string) (standardized string, err error) { + addr, err := ethtypes.NewAddress(addrString) + if err == nil { + standardized = addr.String() // ensures lookups match + } + return standardized, err + }, + }, ethProxyListeners(initialListeners...)...) + if err != nil { + return nil, err + } + return &walletEthAddr{ + gw: gw, + }, nil +} + +func ethProxyListeners(listeners ...chan<- ethtypes.Address0xHex) []chan<- string { + genericListeners := make([]chan<- string, len(listeners)) + for i, listener := range listeners { + genericListener := make(chan string) + go func() { + for addrString := range genericListener { + addr, _ := ethtypes.NewAddress(addrString) + if addr != nil { + listener <- *addr // note we have a validation function to ensure this + } + } + }() + genericListeners[i] = genericListener + } + return genericListeners +} + +func (e *walletEthAddr) AddListener(listener chan<- ethtypes.Address0xHex) { + e.gw.AddListener(ethProxyListeners(listener)[0]) +} + +func (e *walletEthAddr) Close() error { + return e.gw.Close() +} + +func (e *walletEthAddr) GetAccounts(ctx context.Context) (addrs []*ethtypes.Address0xHex, err error) { + addrStrs, err := e.gw.GetAccounts(ctx) + if err == nil { + addrs = make([]*ethtypes.Address0xHex, len(addrStrs)) + for i, addrStr := range addrStrs { + if err == nil { + addrs[i], err = ethtypes.NewAddress(addrStr) + } + } + } + return addrs, err +} + +func (e *walletEthAddr) GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex) (keystorev3.WalletFile, error) { + return e.gw.GetWalletFile(ctx, addr.String()) +} + +func (e *walletEthAddr) Initialize(ctx context.Context) error { + return e.gw.Initialize(ctx) +} + +func (e *walletEthAddr) Refresh(ctx context.Context) error { + return e.gw.Refresh(ctx) +} + +func (e *walletEthAddr) SetSyncAddressCallback(cb SyncAddressCallback) { + e.gw.SetSyncCallback(func(ctx context.Context, s string) error { + addr, err := ethtypes.NewAddress(s) + if err == nil { + err = cb(ctx, *addr) + } + return err + }) +} + +func (e *walletEthAddr) getSignerForJSONAccount(ctx context.Context, rawAddrJSON json.RawMessage) (*secp256k1.KeyPair, error) { + + // We require an ethereum address in the "from" field + var from ethtypes.Address0xHex + err := json.Unmarshal(rawAddrJSON, &from) + if err != nil { + return nil, err + } + return e.getSignerForAddr(ctx, from) +} + +func (e *walletEthAddr) getSignerForAddr(ctx context.Context, from ethtypes.Address0xHex) (*secp256k1.KeyPair, error) { + + wf, err := e.GetWalletFile(ctx, from) + if err != nil { + return nil, err + } + return wf.KeyPair(), nil + +} + +func (e *walletEthAddr) Sign(ctx context.Context, txn *ethsigner.Transaction, chainID int64) ([]byte, error) { + keypair, err := e.getSignerForJSONAccount(ctx, txn.From) + if err != nil { + return nil, err + } + return txn.Sign(keypair, chainID) +} + +func (e *walletEthAddr) SignTypedDataV4(ctx context.Context, from ethtypes.Address0xHex, payload *eip712.TypedData) (*ethsigner.EIP712Result, error) { + keypair, err := e.getSignerForAddr(ctx, from) + if err != nil { + return nil, err + } + return ethsigner.SignTypedDataV4(ctx, keypair, payload) +} diff --git a/pkg/fswallet/fswallet_test.go b/pkg/fswallet/fswallet_test.go index 4f8b782..a338394 100644 --- a/pkg/fswallet/fswallet_test.go +++ b/pkg/fswallet/fswallet_test.go @@ -30,9 +30,10 @@ import ( "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func newTestRegexpFilenameOnlyWallet(t *testing.T, init bool) (context.Context, *fsWallet, func()) { +func newTestRegexpFilenameOnlyWallet(t *testing.T, init bool) (context.Context, *walletEthAddr, func()) { config.RootConfigReset() logrus.SetLevel(logrus.TraceLevel) @@ -51,12 +52,12 @@ func newTestRegexpFilenameOnlyWallet(t *testing.T, init bool) (context.Context, assert.NoError(t, err) } - return ctx, ff.(*fsWallet), func() { + return ctx, ff.(*walletEthAddr), func() { ff.Close() } } -func newTestTOMLMetadataWallet(t *testing.T, init bool) (context.Context, *fsWallet, func()) { +func newTestTOMLMetadataWallet(t *testing.T, init bool) (context.Context, *walletEthAddr, func()) { config.RootConfigReset() logrus.SetLevel(logrus.TraceLevel) @@ -75,7 +76,7 @@ func newTestTOMLMetadataWallet(t *testing.T, init bool) (context.Context, *fsWal err = ff.Initialize(ctx) assert.NoError(t, err) } - return ctx, ff.(*fsWallet), func() { + return ctx, ff.(*walletEthAddr), func() { ff.Close() } } @@ -95,7 +96,8 @@ func TestGetAccountSimpleFilenamesMissingPWD(t *testing.T) { ctx, f, done := newTestRegexpFilenameOnlyWallet(t, true) defer done() - f.conf.Filenames.PasswordExt = ".wrong" + conf := &f.gw.(*fsWallet).conf + conf.Filenames.PasswordExt = ".wrong" _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) @@ -164,7 +166,8 @@ func TestListAccountsBadDir(t *testing.T) { ctx, f, done := newTestTOMLMetadataWallet(t, false) defer done() - f.conf.Path = "!!!" + conf := &f.gw.(*fsWallet).conf + conf.Path = "!!!" err := f.Initialize(ctx) assert.Regexp(t, "FF22013", err) @@ -275,7 +278,8 @@ func TestGetAccountBadYAML(t *testing.T) { ctx, f, done := newTestTOMLMetadataWallet(t, true) defer done() - f.conf.Metadata.Format = "yaml" + conf := &f.gw.(*fsWallet).conf + conf.Metadata.Format = "yaml" _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) @@ -296,7 +300,8 @@ func TestGetAccountBadJSON(t *testing.T) { ctx, f, done := newTestTOMLMetadataWallet(t, true) defer done() - f.conf.Metadata.Format = "json" + conf := &f.gw.(*fsWallet).conf + conf.Metadata.Format = "json" _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) @@ -342,9 +347,12 @@ func TestGetAccountBadTOMLRefKey(t *testing.T) { ctx := context.Background() ff, err := NewFilesystemWallet(ctx, ReadConfig(unitTestConfig)) - defer ff.Close() + defer func() { + err := ff.Close() + require.NoError(t, err) + }() assert.NoError(t, err) - f := ff.(*fsWallet) + f := ff.(*walletEthAddr) _, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22014", err) @@ -359,9 +367,12 @@ func TestGetAccountNoTemplates(t *testing.T) { ctx := context.Background() ff, err := NewFilesystemWallet(ctx, ReadConfig(unitTestConfig)) - defer ff.Close() + defer func() { + err := ff.Close() + require.NoError(t, err) + }() assert.NoError(t, err) - f := ff.(*fsWallet) + f := ff.(*walletEthAddr) _, err = f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22014", err) @@ -369,71 +380,77 @@ func TestGetAccountNoTemplates(t *testing.T) { func TestGetAccountBadKeyfile(t *testing.T) { - ctx, f, done := newTestTOMLMetadataWallet(t, true) + ctx, ew, done := newTestTOMLMetadataWallet(t, true) defer done() + f := ew.gw.(*fsWallet) f.conf.Metadata.Format = "none" // tell it to read the TOML directly as a kv3 f.metadataPasswordFileProperty = nil f.conf.DefaultPasswordFile = "../../test/keystore_toml/1f185718734552d08278aa70f804580bab5fd2b4.pwd" - _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := ew.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } func TestGetAccountBadDefaultPasswordfile(t *testing.T) { - ctx, f, done := newTestTOMLMetadataWallet(t, true) + ctx, ew, done := newTestTOMLMetadataWallet(t, true) defer done() + f := ew.gw.(*fsWallet) f.conf.Metadata.Format = "none" // tell it to read the TOML directly as a kv3 f.metadataPasswordFileProperty = nil f.conf.DefaultPasswordFile = "!!!" - _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := ew.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } func TestGetAccountNoPassword(t *testing.T) { - ctx, f, done := newTestTOMLMetadataWallet(t, true) + ctx, ew, done := newTestTOMLMetadataWallet(t, true) defer done() + f := ew.gw.(*fsWallet) f.metadataPasswordFileProperty = nil - _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) + _, err := ew.getSignerForJSONAccount(ctx, json.RawMessage(`"0x1f185718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22015", err) } func TestGetAccountWrongPath(t *testing.T) { - ctx, f, done := newTestTOMLMetadataWallet(t, true) + ctx, ew, done := newTestTOMLMetadataWallet(t, true) defer done() + f := ew.gw.(*fsWallet) f.metadataPasswordFileProperty = nil - _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"5d093e9b41911be5f5c4cf91b108bac5d130fa83"`)) + _, err := ew.getSignerForJSONAccount(ctx, json.RawMessage(`"5d093e9b41911be5f5c4cf91b108bac5d130fa83"`)) assert.Regexp(t, "FF22015", err) } func TestGetAccountNotFound(t *testing.T) { - ctx, f, done := newTestTOMLMetadataWallet(t, true) + ctx, ew, done := newTestTOMLMetadataWallet(t, true) defer done() + f := ew.gw.(*fsWallet) f.conf.Metadata.Format = "none" // tell it to read the TOML directly as a kv3 f.metadataPasswordFileProperty = nil f.conf.DefaultPasswordFile = "!!!" - _, err := f.getSignerForJSONAccount(ctx, json.RawMessage(`"0xFFFF5718734552d08278aa70f804580bab5fd2b4"`)) + _, err := ew.getSignerForJSONAccount(ctx, json.RawMessage(`"0xFFFF5718734552d08278aa70f804580bab5fd2b4"`)) assert.Regexp(t, "FF22014", err) } func TestLoadKeyBadPath(t *testing.T) { - ctx, f, done := newTestRegexpFilenameOnlyWallet(t, false) + ctx, ew, done := newTestRegexpFilenameOnlyWallet(t, false) defer done() - _, err := f.loadWalletFile(ctx, *ethtypes.MustNewAddress("0xFFFF5718734552d08278aa70f804580bab5fd2b4"), "../../test/keystore_toml/wrong.txt") + f := ew.gw.(*fsWallet) + _, err := f.loadWalletFile(ctx, "0xFFFF5718734552d08278aa70f804580bab5fd2b4", "../../test/keystore_toml/wrong.txt") assert.Regexp(t, "FF22015", err) }