diff --git a/chains/cosmos/runner/runner.go b/chains/cosmos/runner/runner.go index e176a4d..6064d85 100644 --- a/chains/cosmos/runner/runner.go +++ b/chains/cosmos/runner/runner.go @@ -100,7 +100,20 @@ func NewRunner(ctx context.Context, spec loadtesttypes.LoadTestSpec) (*Runner, e logger.Info("deriving wallet keys", zap.Int("num_wallets", spec.NumWallets)) keyStart := time.Now() privKeys := make([]types.PrivKey, 0, spec.NumWallets) - if spec.NumWallets > 0 { + if spec.Cache.ReadWalletsFrom != "" { + logger.Info( + "Reading cached private keys", + zap.String("file", spec.Cache.ReadWalletsFrom), + zap.Int("num_wallets", spec.NumWallets), + ) + + cachedPKs, err := wallet.ReadCachedPrivateKeys(spec.Cache.ReadWalletsFrom, spec.NumWallets) + if err != nil { + return nil, fmt.Errorf("failed to read cached private keys: %w", err) + } + + privKeys = cachedPKs + } else if spec.NumWallets > 0 { for i := range spec.NumWallets { derivedPrivKey, err := hd.Secp256k1.Derive()( spec.BaseMnemonic, @@ -112,12 +125,13 @@ func NewRunner(ctx context.Context, spec loadtesttypes.LoadTestSpec) (*Runner, e } privKeys = append(privKeys, &secp256k1.PrivKey{Key: derivedPrivKey}) } + + logger.Info( + "derived wallet keys", + zap.Int("num_keys", len(privKeys)), + zap.Duration("duration", time.Since(keyStart)), + ) } - logger.Info( - "derived wallet keys", - zap.Int("num_keys", len(privKeys)), - zap.Duration("duration", time.Since(keyStart)), - ) if len(privKeys) == 0 { return nil, fmt.Errorf("no private keys available: either provide base mnemonic or private keys") diff --git a/chains/cosmos/wallet/cache.go b/chains/cosmos/wallet/cache.go new file mode 100644 index 0000000..5cb1e4a --- /dev/null +++ b/chains/cosmos/wallet/cache.go @@ -0,0 +1,56 @@ +package wallet + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/evm/crypto/ethsecp256k1" + "github.com/ethereum/go-ethereum/crypto" +) + +// ReadCachedPrivateKeys reads the cached private keys from the file and returns them as types.PrivKey +// The file is expected to be in the same format as the Ethereum keys cache. +// See chains/ethereum/wallet/ReadWalletsFromCache() for more details. +func ReadCachedPrivateKeys(filename string, num int) ([]types.PrivKey, error) { + bz, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("unable to open cache file %s: %w", filename, err) + } + + type cachedKey struct { + Signer struct { + PK []byte + } + } + + var cachedKeys []cachedKey + if err := json.Unmarshal(bz, &cachedKeys); err != nil { + return nil, fmt.Errorf("decoding cached wallets: %w", err) + } + + if len(cachedKeys) < num { + return nil, fmt.Errorf("not enough cached keys (got %d, want %d)", len(cachedKeys), num) + } + + if num > 0 && num < len(cachedKeys) { + cachedKeys = cachedKeys[:num] + } + + privateKeys := make([]types.PrivKey, len(cachedKeys)) + for i, key := range cachedKeys { + rawPK := key.Signer.PK + + ecdsaPK, err := crypto.ToECDSA(rawPK) + if err != nil { + return nil, fmt.Errorf("parsing cached wallet %d private key: %w", i, err) + } + + privateKeys[i] = ðsecp256k1.PrivKey{ + Key: crypto.FromECDSA(ecdsaPK), + } + } + + return privateKeys, nil +}