From d60376e731d96fa846bbe8f3cd9aa31289f323e8 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 7 Sep 2024 15:09:34 +0200 Subject: [PATCH 1/7] feat: new cosmos keyring helper for injective client. See client/keyring/README.md for more details. --- client/keyring/README.md | 103 ++++ client/keyring/errors.go | 20 + client/keyring/key_config.go | 69 +++ client/keyring/keyring.go | 491 ++++++++++++++++++ client/keyring/keyring_config.go | 120 +++++ client/keyring/keyring_errors_test.go | 193 +++++++ client/keyring/keyring_test.go | 302 +++++++++++ ...aad9ebf5759a8272ad2ae4968dd12d4602.address | 1 + ...fcb0937ade77a9ba0d128f9e7f17312796.address | 1 + client/keyring/testdata/keyring-file/keyhash | 1 + .../keyring/testdata/keyring-file/test.info | 1 + .../keyring/testdata/keyring-file/test2.info | 1 + go.mod | 6 +- go.sum | 20 + 14 files changed, 1328 insertions(+), 1 deletion(-) create mode 100644 client/keyring/README.md create mode 100644 client/keyring/errors.go create mode 100644 client/keyring/key_config.go create mode 100644 client/keyring/keyring.go create mode 100644 client/keyring/keyring_config.go create mode 100644 client/keyring/keyring_errors_test.go create mode 100644 client/keyring/keyring_test.go create mode 100644 client/keyring/testdata/keyring-file/263117aad9ebf5759a8272ad2ae4968dd12d4602.address create mode 100644 client/keyring/testdata/keyring-file/310322fcb0937ade77a9ba0d128f9e7f17312796.address create mode 100644 client/keyring/testdata/keyring-file/keyhash create mode 100644 client/keyring/testdata/keyring-file/test.info create mode 100644 client/keyring/testdata/keyring-file/test2.info diff --git a/client/keyring/README.md b/client/keyring/README.md new file mode 100644 index 00000000..97872e75 --- /dev/null +++ b/client/keyring/README.md @@ -0,0 +1,103 @@ +# Injective Chain Keyring Helper + +Creates a new keyring from a variety of options. See `ConfigOpt` and related options. This keyring helper allows to initialize Cosmos SDK keyring used for signing transactions. + +It allows flexibly define a static configuration of keys, supports multiple pre-defined keys in the same keyring and allows to load keys from a file, derive from mnemonic or read plain private key bytes from a HEX string. Extremely useful for testing and local development, but also robust for production use cases. + +## Usage + +```go +NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmkeyring.Keyring, error) +``` + +**ConfigOpts:** + +These options are global on the keyring level. + +* `WithKeyringDir` option sets keyring path in the filesystem, useful when keyring backend is `file`. +* `WithKeyringAppName` option sets keyring application name (defaults to `injectived`) +* `WithKeyringBackend` sets the keyring backend. Expected values: `test`, `file`, `os`. +* `WithUseLedger` sets the option to use hardware wallet, if available on the system. + +These options allow to add keys to the keyring during initialization. + +* `WithKey` adds a single key to the keyring, without having alias name. +* `WithNamedKey` addes a single key to the keyring, with a name. +* `WithDefaultKey` sets a default key reference to use for signing (by name). + +**KeyConfigOpts:** + +These options are set per key. + +* `WithKeyFrom` sets the key name to use for signing. Must exist in the provided keyring. +* `WithKeyPassphrase` sets the passphrase for keyring files. The package will fallback to `os.Stdin` if this option was not provided, but passphrase is required. +* `WithPrivKeyHex` allows to specify a private key as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring holding that key, to meet all the interfaces. +* `WithMnemonic` allows to specify a mnemonic pharse as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring to derive the keys and meet all the interfaces. + +## Examples + +Initialize an in-memory keyring with a private key hex: + +```go +NewCosmosKeyring( + cdc, + WithKey( + WithPrivKeyHex("e6888cb164d52e4880e08a8a5dbe69cd62f67fde3d5906f2c5c951be553b2267"), + WithKeyFrom("sender"), + ), +) +``` + +Initialize an in-memory keyring with a mnemonic phrase: + +```go +NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic("real simple naive ....... love"), + WithKeyFrom("sender"), + ), +) +``` + +Real world use case of keyring initialization from CLI flags, with a single named key set as default: + +```go +NewCosmosKeyring( + cdc, + WithKeyringDir(*keyringDir), + WithKeyringAppName(*keyringAppName), + WithKeyringBackend(Backend(*keyringBackend)), + + WithNamedKey( + "dispatcher", + WithKeyFrom(*dispatcherKeyFrom), + WithKeyPassphrase(*dispatcherKeyPassphrase), + WithPrivKeyHex(*dispatcherKeyPrivateHex), + WithMnemonic(*dispatcherKeyMnemonic), + ), + + WithDefaultKey( + "dispatcher", + ), +) +``` + +## Testing + +```bash +go test -v -cover + +PASS +coverage: 83.1% of statements +``` + +## Generating a Test Fixture + +```bash +> cd testdata + +> injectived keys --keyring-dir `pwd` --keyring-backend file add test +``` + +Passphrase should be `test12345678` for this fixture to work. diff --git a/client/keyring/errors.go b/client/keyring/errors.go new file mode 100644 index 00000000..bd591844 --- /dev/null +++ b/client/keyring/errors.go @@ -0,0 +1,20 @@ +package keyring + +import "github.com/pkg/errors" + +var ( + ErrCosmosKeyringCreationFailed = errors.New("cosmos keyring creation failed") + ErrCosmosKeyringImportFailed = errors.New("cosmos keyring unable to import key") + ErrDeriveFailed = errors.New("key derivation failed") + ErrFailedToApplyConfigOption = errors.New("failed to apply config option") + ErrFailedToApplyKeyConfigOption = errors.New("failed to apply a key config option") + ErrFilepathIncorrect = errors.New("incorrect filepath") + ErrHexFormatError = errors.New("hex format error") + ErrIncompatibleOptionsProvided = errors.New("incompatible keyring options provided") + ErrInsufficientKeyDetails = errors.New("insufficient cosmos key details provided") + ErrKeyIncompatible = errors.New("provided key is incompatible with requested config") + ErrKeyRecordNotFound = errors.New("key record not found") + ErrPrivkeyConflict = errors.New("privkey conflict") + ErrUnexpectedAddress = errors.New("unexpected address") + ErrMultipleKeysWithDifferentSecurity = errors.New("key security is different: cannot mix keyring with privkeys") +) diff --git a/client/keyring/key_config.go b/client/keyring/key_config.go new file mode 100644 index 00000000..79b74ef2 --- /dev/null +++ b/client/keyring/key_config.go @@ -0,0 +1,69 @@ +package keyring + +import ( + bip39 "github.com/cosmos/go-bip39" + "github.com/pkg/errors" +) + +type cosmosKeyConfig struct { + Name string + KeyFrom string + KeyPassphrase string + PrivKeyHex string + Mnemonic string +} + +// KeyConfigOpt defines a known cosmos keyring key option. +type KeyConfigOpt func(c *cosmosKeyConfig) error + +// WithKeyFrom sets the key name to use for signing. Must exist in the provided keyring. +func WithKeyFrom(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + c.KeyFrom = v + } + + return nil + } +} + +// WithKeyPassphrase sets the passphrase for keyring files. Insecure option, use for testing only. +// The package will fallback to os.Stdin if this option was not provided, but pass is required. +func WithKeyPassphrase(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + c.KeyPassphrase = v + } + + return nil + } +} + +// WithPrivKeyHex allows to specify a private key as plaintext hex. Insecure option, use for testing only. +// The package will create a virtual keyring holding that key, to meet all the interfaces. +func WithPrivKeyHex(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + c.PrivKeyHex = v + } + + return nil + } +} + +// WithMnemonic allows to specify a mnemonic pharse as plaintext. Insecure option, use for testing only. +// The package will create a virtual keyring to derive the keys and meet all the interfaces. +func WithMnemonic(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + if !bip39.IsMnemonicValid(v) { + err := errors.New("provided mnemonic is not a valid BIP39 mnemonic") + return err + } + + c.Mnemonic = v + } + + return nil + } +} diff --git a/client/keyring/keyring.go b/client/keyring/keyring.go new file mode 100644 index 00000000..d9052b7f --- /dev/null +++ b/client/keyring/keyring.go @@ -0,0 +1,491 @@ +package keyring + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + "os" + "path/filepath" + "strings" + + "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" + "github.com/cosmos/cosmos-sdk/codec" + cosmcrypto "github.com/cosmos/cosmos-sdk/crypto" + cosmkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" +) + +var ( + defaultKeyringKeyName = "default" + emptyCosmosAddress = sdk.AccAddress{} +) + +// NewCosmosKeyring creates a new keyring from a variety of options. See ConfigOpt and related options. +func NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmkeyring.Keyring, error) { + config := &cosmosKeyringConfig{} + for optIdx, optFn := range opts { + if err := optFn(config); err != nil { + err = errors.Wrapf(ErrFailedToApplyConfigOption, "option #%d: %s", optIdx+1, err.Error()) + return emptyCosmosAddress, nil, err + } + } + + if len(config.Keys) == 0 { + return emptyCosmosAddress, nil, ErrInsufficientKeyDetails + } + + var kb cosmkeyring.Keyring + var realKB cosmkeyring.Keyring + var usingRealKeyring bool + var firstKey *sdk.AccAddress + + for keyIdx, keyConfig := range config.Keys { + switch { + case len(keyConfig.Mnemonic) > 0: + if usingRealKeyring { + return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity + } else if kb == nil { + kb = cosmkeyring.NewInMemory(cdc, hd.EthSecp256k1Option()) + } + + if config.UseLedger { + err := errors.Wrap(ErrIncompatibleOptionsProvided, "cannot combine ledger and mnemonic options") + return emptyCosmosAddress, nil, err + } + + addr, err := fromMnemonic(kb, keyConfig) + if err != nil { + return addr, kb, err + } + + if keyIdx == 0 { + firstKey = &addr + } + + case len(keyConfig.PrivKeyHex) > 0: + if usingRealKeyring { + return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity + } else if kb == nil { + kb = cosmkeyring.NewInMemory(cdc, hd.EthSecp256k1Option()) + } + + if config.UseLedger { + err := errors.Wrap(ErrIncompatibleOptionsProvided, "cannot combine ledger and privkey options") + return emptyCosmosAddress, nil, err + } + + addr, err := fromPrivkeyHex(kb, keyConfig) + if err != nil { + return addr, kb, err + } + + if keyIdx == 0 { + firstKey = &addr + } + + case len(keyConfig.KeyFrom) > 0: + if kb != nil { + return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity + } else { + usingRealKeyring = true + } + + var fromIsAddress bool + + addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) + if err == nil { + fromIsAddress = true + } + + addr, kb, err := fromCosmosKeyring(cdc, config, keyConfig, addressFrom, fromIsAddress) + if err != nil { + return addr, kb, err + } + + realKB = kb + if keyIdx == 0 { + firstKey = &addr + } + + default: + err := errors.Wrapf(ErrInsufficientKeyDetails, "key %d details", keyIdx+1) + return emptyCosmosAddress, nil, err + } + } + + if realKB != nil { + if len(config.DefaultKey) > 0 { + defaultKeyAddr, err := findKeyInKeyring(realKB, config, config.DefaultKey) + if err != nil { + return emptyCosmosAddress, nil, err + } + + return defaultKeyAddr, realKB, nil + } + + return *firstKey, realKB, nil + } + + if len(config.DefaultKey) > 0 { + defaultKeyAddr, err := findKeyInKeyring(kb, config, config.DefaultKey) + if err != nil { + return emptyCosmosAddress, nil, err + } + + return defaultKeyAddr, kb, nil + } + + return *firstKey, kb, nil +} + +func fromPrivkeyHex( + kb cosmkeyring.Keyring, + keyConfig *cosmosKeyConfig, +) (sdk.AccAddress, error) { + pkBytes, err := hexToBytes(keyConfig.PrivKeyHex) + if err != nil { + err = errors.Wrapf(ErrHexFormatError, "failed to decode cosmos account privkey: %s", err.Error()) + return emptyCosmosAddress, err + } + + cosmosAccPk := hd.EthSecp256k1.Generate()(pkBytes) + addressFromPk := sdk.AccAddress(cosmosAccPk.PubKey().Address().Bytes()) + + keyName := keyConfig.Name + + // check that if cosmos 'From' specified separately, it must match the provided privkey + if len(keyConfig.KeyFrom) > 0 { + addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) + if err == nil { + if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) { + err = errors.Wrapf( + ErrUnexpectedAddress, + "expected account address %s but got %s from the private key", + addressFrom.String(), addressFromPk.String(), + ) + + return emptyCosmosAddress, err + } + } else if len(keyName) == 0 { + // use it as a name then + keyName = keyConfig.KeyFrom + } else if keyName != keyConfig.KeyFrom { + err := errors.Errorf( + "key 'from' opt is a name, but doesn't match given key name: %s != %s", + keyConfig.KeyFrom, keyName, + ) + return emptyCosmosAddress, err + } + } + + if len(keyName) == 0 { + keyName = defaultKeyringKeyName + } + + // add a PK into a Keyring + err = addFromPrivKey(kb, keyName, cosmosAccPk) + if err != nil { + err = errors.WithStack(err) + } + + return addressFromPk, err +} + +func fromMnemonic( + kb cosmkeyring.Keyring, + keyConfig *cosmosKeyConfig, +) (sdk.AccAddress, error) { + cfg := sdk.GetConfig() + + pkBytes, err := hd.EthSecp256k1.Derive()( + keyConfig.Mnemonic, + cosmkeyring.DefaultBIP39Passphrase, + cfg.GetFullBIP44Path(), + ) + if err != nil { + err = errors.Wrapf(ErrDeriveFailed, "failed to derive secp256k1 private key: %s", err.Error()) + return emptyCosmosAddress, err + } + + cosmosAccPk := hd.EthSecp256k1.Generate()(pkBytes) + addressFromPk := sdk.AccAddress(cosmosAccPk.PubKey().Address().Bytes()) + + keyName := keyConfig.Name + + // check that if cosmos 'From' specified separately, it must match the derived privkey + if len(keyConfig.KeyFrom) > 0 { + addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) + if err == nil { + if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) { + err = errors.Wrapf( + ErrUnexpectedAddress, + "expected account address %s but got %s from the mnemonic at /0", + addressFrom.String(), addressFromPk.String(), + ) + + return emptyCosmosAddress, err + } + } else if len(keyName) == 0 { + // use it as a name then + keyName = keyConfig.KeyFrom + } else if keyName != keyConfig.KeyFrom { + err := errors.Errorf( + "key 'from' opt is a name, but doesn't match given key name: %s != %s", + keyConfig.KeyFrom, keyName, + ) + return emptyCosmosAddress, err + } + } + + // check that if 'PrivKeyHex' specified separately, it must match the derived privkey too + if len(keyConfig.PrivKeyHex) > 0 { + if err := checkPrivkeyHexMatchesMnemonic(keyConfig.PrivKeyHex, pkBytes); err != nil { + return emptyCosmosAddress, err + } + } + + if len(keyName) == 0 { + keyName = defaultKeyringKeyName + } + + // add a PK into a Keyring + err = addFromPrivKey(kb, keyName, cosmosAccPk) + if err != nil { + err = errors.WithStack(err) + } + + return addressFromPk, err +} + +func checkPrivkeyHexMatchesMnemonic(pkHex string, mnemonicDerivedPkBytes []byte) error { + pkBytesFromHex, err := hexToBytes(pkHex) + if err != nil { + err = errors.Wrapf(ErrHexFormatError, "failed to decode cosmos account privkey: %s", err.Error()) + return err + } + + if !bytes.Equal(mnemonicDerivedPkBytes, pkBytesFromHex) { + err := errors.Wrap( + ErrPrivkeyConflict, + "both mnemonic and privkey hex options provided, but privkey doesn't match mnemonic", + ) + return err + } + + return nil +} + +func fromCosmosKeyring( + cdc codec.Codec, + config *cosmosKeyringConfig, + keyConfig *cosmosKeyConfig, + fromAddress sdk.AccAddress, + fromIsAddress bool, +) (sdk.AccAddress, cosmkeyring.Keyring, error) { + var passReader io.Reader = os.Stdin + if len(keyConfig.KeyPassphrase) > 0 { + passReader = newPassReader(keyConfig.KeyPassphrase) + } + + var err error + absoluteKeyringDir := config.KeyringDir + if !filepath.IsAbs(config.KeyringDir) { + absoluteKeyringDir, err = filepath.Abs(config.KeyringDir) + if err != nil { + err = errors.Wrapf(ErrFilepathIncorrect, "failed to get abs path for keyring dir: %s", err.Error()) + return emptyCosmosAddress, nil, err + } + } + + kb, err := cosmkeyring.New( + config.KeyringAppName, + string(config.KeyringBackend), + absoluteKeyringDir, + passReader, + cdc, + hd.EthSecp256k1Option(), + ) + if err != nil { + err = errors.Wrapf(ErrCosmosKeyringCreationFailed, "failed to init cosmos keyring: %s", err.Error()) + return emptyCosmosAddress, nil, err + } + + var keyRecord *cosmkeyring.Record + if fromIsAddress { + keyRecord, err = kb.KeyByAddress(fromAddress) + } else { + keyName := keyConfig.Name + if len(keyName) > 0 && keyConfig.KeyFrom != keyName { + err := errors.Errorf( + "key 'from' opt is a name, but doesn't match given key name: %s != %s", + keyConfig.KeyFrom, keyName, + ) + + return emptyCosmosAddress, nil, err + } + + keyRecord, err = kb.Key(keyConfig.KeyFrom) + } + + if err != nil { + err = errors.Wrapf( + ErrKeyRecordNotFound, "couldn't find an entry for the key '%s' in keybase: %s", + keyConfig.KeyFrom, err.Error()) + + return emptyCosmosAddress, nil, err + } + + if err := checkKeyRecord(config, keyRecord); err != nil { + return emptyCosmosAddress, nil, err + } + + addr, err := keyRecord.GetAddress() + if err != nil { + return emptyCosmosAddress, nil, err + } + + return addr, kb, nil +} + +func findKeyInKeyring(kb cosmkeyring.Keyring, config *cosmosKeyringConfig, fromSpec string) (sdk.AccAddress, error) { + var fromIsAddress bool + + addressFrom, err := sdk.AccAddressFromBech32(fromSpec) + if err == nil { + fromIsAddress = true + } + + var keyRecord *cosmkeyring.Record + if fromIsAddress { + keyRecord, err = kb.KeyByAddress(addressFrom) + } else { + keyRecord, err = kb.Key(fromSpec) + } + + if err != nil { + err = errors.Wrapf( + ErrKeyRecordNotFound, "couldn't find an entry for the key '%s' in keybase: %s", + fromSpec, err.Error()) + + return emptyCosmosAddress, err + } + + if err := checkKeyRecord(config, keyRecord); err != nil { + return emptyCosmosAddress, err + } + + addr, err := keyRecord.GetAddress() + if err != nil { + return emptyCosmosAddress, err + } + + return addr, nil +} + +func checkKeyRecord( + config *cosmosKeyringConfig, + keyRecord *cosmkeyring.Record, +) error { + switch keyType := keyRecord.GetType(); keyType { + case cosmkeyring.TypeLocal: + // kb has a key and it's totally usable + return nil + + case cosmkeyring.TypeLedger: + // the kb stores references to ledger keys, so we must explicitly + // check that. kb doesn't know how to scan HD keys - they must be added manually before + if config.UseLedger { + return nil + } + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key is a ledger reference, enable ledger option", + keyRecord.Name, + ) + return err + + case cosmkeyring.TypeOffline: + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key is an offline key, not supported yet", + keyRecord.Name, + ) + return err + + case cosmkeyring.TypeMulti: + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key is an multisig key, not supported yet", + keyRecord.Name, + ) + return err + + default: + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key has unsupported type: %s", + keyRecord.Name, keyType, + ) + return err + } +} + +func newPassReader(pass string) io.Reader { + return &passReader{ + pass: pass, + buf: new(bytes.Buffer), + } +} + +type passReader struct { + pass string + buf *bytes.Buffer +} + +var _ io.Reader = &passReader{} + +func (r *passReader) Read(p []byte) (n int, err error) { + n, err = r.buf.Read(p) + if err == io.EOF || n == 0 { + r.buf.WriteString(r.pass + "\n") + + n, err = r.buf.Read(p) + } + + return n, err +} + +// addFromPrivKey adds a PrivKey into temporary in-mem keyring. +// Allows to init Context when the key has been provided in plaintext and parsed. +func addFromPrivKey(kb cosmkeyring.Keyring, name string, privKey cryptotypes.PrivKey) error { + tmpPhrase := randPhrase(64) + armored := cosmcrypto.EncryptArmorPrivKey(privKey, tmpPhrase, privKey.Type()) + err := kb.ImportPrivKey(name, armored, tmpPhrase) + if err != nil { + err = errors.Wrapf(ErrCosmosKeyringImportFailed, "failed to import privkey: %s", err.Error()) + return err + } + + return nil +} + +func hexToBytes(str string) ([]byte, error) { + data, err := hex.DecodeString(strings.TrimPrefix(str, "0x")) + if err != nil { + return nil, err + } + + return data, nil +} + +func randPhrase(size int) string { + buf := make([]byte, size) + if _, err := rand.Read(buf); err != nil { + panic(err) + } + + return string(buf) +} diff --git a/client/keyring/keyring_config.go b/client/keyring/keyring_config.go new file mode 100644 index 00000000..42ac4cbc --- /dev/null +++ b/client/keyring/keyring_config.go @@ -0,0 +1,120 @@ +package keyring + +import ( + "github.com/pkg/errors" +) + +// ConfigOpt defines a known cosmos keyring option. +type ConfigOpt func(c *cosmosKeyringConfig) error + +type cosmosKeyringConfig struct { + KeyringDir string + KeyringAppName string + KeyringBackend Backend + UseLedger bool + + Keys []*cosmosKeyConfig + DefaultKey string +} + +// Backend defines a known keyring backend name. +type Backend string + +const ( + // BackendTest is a testing backend, no passphrases required. + BackendTest Backend = "test" + // BackendFile is a backend where keys are stored as encrypted files. + BackendFile Backend = "file" + // BackendOS is a backend where keys are stored in the OS key chain. Platform specific. + BackendOS Backend = "os" +) + +// WithKeyringDir option sets keyring path in the filesystem, useful when keyring backend is `file`. +func WithKeyringDir(v string) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.KeyringDir = v + } + + return nil + } +} + +// WithKeyringAppName option sets keyring application name (used by Cosmos to separate keyrings). +func WithKeyringAppName(v string) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.KeyringAppName = v + } + + return nil + } +} + +// WithKeyringBackend sets the keyring backend. Expected values: test, file, os. +func WithKeyringBackend(v Backend) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.KeyringBackend = v + } + + return nil + } +} + +// WithUseLedger sets the option to use hardware wallet, if available on the system. +func WithUseLedger(b bool) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + c.UseLedger = b + + return nil + } +} + +// WithKey adds an unnamed key into the keyring, based on its individual options. +func WithKey(opts ...KeyConfigOpt) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + config := &cosmosKeyConfig{} + + for optIdx, optFn := range opts { + if err := optFn(config); err != nil { + err = errors.Wrapf(ErrFailedToApplyKeyConfigOption, "key option #%d: %s", optIdx+1, err.Error()) + return err + } + } + + c.Keys = append(c.Keys, config) + return nil + } +} + +// WithNamedKey adds a key into the keyring, based on its individual options, with a given name (alias). +func WithNamedKey(name string, opts ...KeyConfigOpt) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + config := &cosmosKeyConfig{ + Name: name, + } + + for optIdx, optFn := range opts { + if err := optFn(config); err != nil { + err = errors.Wrapf(ErrFailedToApplyKeyConfigOption, "key option #%d: %s", optIdx+1, err.Error()) + return err + } + } + + c.Keys = append(c.Keys, config) + return nil + } +} + +// WithDefaultKey specifies the default key (name or address) to be fetched during keyring init. +// This key must exist in specified keys. +func WithDefaultKey(v string) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.DefaultKey = v + } + + return nil + } +} diff --git a/client/keyring/keyring_errors_test.go b/client/keyring/keyring_errors_test.go new file mode 100644 index 00000000..8ff13cb0 --- /dev/null +++ b/client/keyring/keyring_errors_test.go @@ -0,0 +1,193 @@ +package keyring + +import ( + "os" + + "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" + cosmkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" +) + +func (s *KeyringTestSuite) TestErrCosmosKeyringCreationFailed() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend("kowabunga"), + WithKey( + WithKeyFrom(testAccAddressBech), + ), + ) + + requireT.ErrorIs(err, ErrCosmosKeyringCreationFailed) +} + +func (s *KeyringTestSuite) TestErrFailedToApplyConfigOption() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(`???`), + ), + ) + + requireT.ErrorIs(err, ErrFailedToApplyConfigOption) +} + +func (s *KeyringTestSuite) TestErrHexFormatError() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex("nothex"), + ), + ) + + requireT.ErrorIs(err, ErrHexFormatError) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithPrivKeyHex("nothex"), + ), + ) + + requireT.ErrorIs(err, ErrHexFormatError) +} + +func (s *KeyringTestSuite) TestErrIncompatibleOptionsProvided() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithUseLedger(true), + WithKey( + WithMnemonic(testMnemonic), + ), + ) + + requireT.ErrorIs(err, ErrIncompatibleOptionsProvided) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithUseLedger(true), + WithKey( + WithPrivKeyHex(testPrivKeyHex), + ), + ) + + requireT.ErrorIs(err, ErrIncompatibleOptionsProvided) +} + +func (s *KeyringTestSuite) TestErrInsufficientKeyDetails() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring(s.cdc) + + requireT.ErrorIs(err, ErrInsufficientKeyDetails) +} + +func (s *KeyringTestSuite) TestErrKeyIncompatible() { + requireT := s.Require() + + addr, kb, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testPrivKeyHex), + ), + ) + requireT.NoError(err) + + testRecord, err := kb.KeyByAddress(addr) + requireT.NoError(err) + testRecordPubKey, err := testRecord.GetPubKey() + requireT.NoError(err) + + kbDir, err := os.MkdirTemp(os.TempDir(), "keyring-test-kbroot-*") + requireT.NoError(err) + s.T().Cleanup(func() { + _ = os.RemoveAll(kbDir) + }) + + testKeyring, err := cosmkeyring.New( + "keyring_test", + cosmkeyring.BackendTest, + kbDir, + nil, + s.cdc, + hd.EthSecp256k1Option(), + ) + requireT.NoError(err) + + _, err = testKeyring.SaveOfflineKey("test_pubkey", testRecordPubKey) + requireT.NoError(err) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendTest), + WithKeyringDir(kbDir), + WithKeyringAppName("keyring_test"), + WithKey( + WithKeyFrom("test_pubkey"), + ), + ) + requireT.ErrorIs(err, ErrKeyIncompatible) + + // TODO: add test for unsupported multisig keys +} + +func (s *KeyringTestSuite) TestErrKeyRecordNotFound() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendFile), + WithKeyringDir("./testdata"), + WithKey( + WithKeyFrom("kowabunga"), + WithKeyPassphrase("test12345678"), + ), + ) + + requireT.ErrorIs(err, ErrKeyRecordNotFound) +} + +func (s *KeyringTestSuite) TestErrPrivkeyConflict() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testOtherPrivKeyHex), + WithMnemonic(testMnemonic), // different mnemonic + ), + ) + + requireT.ErrorIs(err, ErrPrivkeyConflict) +} + +func (s *KeyringTestSuite) TestErrUnexpectedAddress() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testOtherPrivKeyHex), + WithKeyFrom(testAccAddressBech), // will not match privkey above + ), + ) + + requireT.ErrorIs(err, ErrUnexpectedAddress) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithKeyFrom("inj1xypj9l9sjdaduaafhgx39ru70utnzfuklcpxz9"), // will not match mnemonic above + ), + ) + + requireT.ErrorIs(err, ErrUnexpectedAddress) +} diff --git a/client/keyring/keyring_test.go b/client/keyring/keyring_test.go new file mode 100644 index 00000000..990e1645 --- /dev/null +++ b/client/keyring/keyring_test.go @@ -0,0 +1,302 @@ +package keyring + +import ( + "encoding/hex" + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + cosmcrypto "github.com/cosmos/cosmos-sdk/crypto" + cosmkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + crypto_cdc "github.com/InjectiveLabs/sdk-go/chain/crypto/codec" + "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" + ctypes "github.com/InjectiveLabs/sdk-go/chain/types" + "github.com/InjectiveLabs/sdk-go/client/chain" +) + +type KeyringTestSuite struct { + suite.Suite + + cdc codec.Codec +} + +func TestKeyringTestSuite(t *testing.T) { + suite.Run(t, new(KeyringTestSuite)) +} + +func getCryptoCodec() *codec.ProtoCodec { + registry := chain.NewInterfaceRegistry() + crypto_cdc.RegisterInterfaces(registry) + return codec.NewProtoCodec(registry) +} + +func (s *KeyringTestSuite) SetupTest() { + config := sdk.GetConfig() + ctypes.SetBech32Prefixes(config) + ctypes.SetBip44CoinType(config) + + s.cdc = getCryptoCodec() +} + +func (s *KeyringTestSuite) TestKeyFromPrivkey() { + requireT := s.Require() + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testPrivKeyHex), + WithKeyFrom(testAccAddressBech), // must match the privkey above + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.EqualValues(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestKeyFromMnemonic() { + requireT := s.Require() + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithPrivKeyHex(testPrivKeyHex), // must match mnemonic above + WithKeyFrom(testAccAddressBech), // must match mnemonic above + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.Equal(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestKeyringFile() { + requireT := s.Require() + + accAddr, _, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendFile), + WithKeyringDir("./testdata"), + WithKey( + WithKeyFrom("test"), + WithKeyPassphrase("test12345678"), + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendFile), + WithKeyringDir("./testdata"), + WithKey( + WithKeyFrom(testAccAddressBech), + WithKeyPassphrase("test12345678"), + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + requireT.Equal("test", record.Name) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.Equal(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestKeyringOsWithAppName() { + if testing.Short() { + s.T().Skip("skipping testing in short mode") + return + } + + requireT := require.New(s.T()) + + osKeyring, err := cosmkeyring.New( + "keyring_test", + cosmkeyring.BackendOS, + "", + nil, + s.cdc, + hd.EthSecp256k1Option(), + ) + requireT.NoError(err) + + var accRecord *cosmkeyring.Record + if accRecord, err = osKeyring.Key("test"); err != nil { + accRecord, err = osKeyring.NewAccount( + "test", + testMnemonic, + cosmkeyring.DefaultBIP39Passphrase, + sdk.GetConfig().GetFullBIP44Path(), + hd.EthSecp256k1, + ) + + requireT.NoError(err) + + accAddr, err := accRecord.GetAddress() + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + } + + s.T().Cleanup(func() { + // cleanup + addr, err := accRecord.GetAddress() + if err == nil { + _ = osKeyring.DeleteByAddress(addr) + } + }) + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendOS), + WithKeyringAppName("keyring_test"), + WithKey( + WithKeyFrom("test"), + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + requireT.Equal("test", record.Name) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.Equal(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestUseFromAsName() { + requireT := s.Require() + + accAddr, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testPrivKeyHex), + WithKeyFrom("kowabunga"), + ), + WithDefaultKey("kowabunga"), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + accAddr, _, err = NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithKeyFrom("kowabunga"), + ), + WithDefaultKey("kowabunga"), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) +} + +func (s *KeyringTestSuite) TestNamedKeys() { + requireT := s.Require() + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithNamedKey( + "bad", + WithPrivKeyHex(testOtherPrivKeyHex), + ), + + WithNamedKey( + "good", + WithPrivKeyHex(testPrivKeyHex), + ), + + WithDefaultKey("good"), + ) + + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.EqualValues(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +const expectedPubKeyType = "/injective.crypto.v1beta1.ethsecp256k1.PubKey" + +const testAccAddressBech = "inj1ycc302kea06htx5zw2kj4eyk3hgj63sz206fq0" + +//nolint:lll // mnemonic fixture +const testMnemonic = `real simple naive tissue alcohol bar short joy maze shoe reason item tray attitude panda century pulse skirt original autumn sea shop exhaust love` + +var testPrivKeyHex = "e6888cb164d52e4880e08a8a5dbe69cd62f67fde3d5906f2c5c951be553b2267" +var testOtherPrivKeyHex = "ef3bc8bc1e1bae12268e0192787673a4137af840bfcbd1aa4c535bbd95fe6837" + +var testSig = []byte{ + 0xf9, 0x04, 0x3e, 0x81, 0x83, 0xb2, 0x73, 0xf6, + 0xdd, 0xf7, 0xd6, 0x91, 0x6f, 0xb5, 0x63, 0xf4, + 0x8a, 0xa2, 0x4a, 0x51, 0x63, 0xe1, 0x04, 0x18, + 0xd2, 0xe6, 0xed, 0x9e, 0xda, 0x52, 0x2f, 0x0a, + 0x69, 0x74, 0x04, 0x73, 0x7b, 0x9a, 0xf1, 0xc8, + 0xdf, 0xe7, 0xf3, 0x4a, 0x48, 0xe6, 0x5f, 0xc0, + 0x69, 0x5e, 0x6e, 0x03, 0x9e, 0x6e, 0x5f, 0x31, + 0xa6, 0x40, 0x19, 0x1b, 0x76, 0x07, 0xd9, 0x65, + 0x00, +} + +func logPrivKey(t *testing.T, kb cosmkeyring.Keyring, accAddr sdk.AccAddress) { + armor, _ := kb.ExportPrivKeyArmorByAddress(accAddr, "") + privKey, _, _ := cosmcrypto.UnarmorDecryptPrivKey(armor, "") + t.Log("[PRIV]", hex.EncodeToString(privKey.Bytes())) +} diff --git a/client/keyring/testdata/keyring-file/263117aad9ebf5759a8272ad2ae4968dd12d4602.address b/client/keyring/testdata/keyring-file/263117aad9ebf5759a8272ad2ae4968dd12d4602.address new file mode 100644 index 00000000..4730fa37 --- /dev/null +++ b/client/keyring/testdata/keyring-file/263117aad9ebf5759a8272ad2ae4968dd12d4602.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo0OTowNi42NDcwMjMgKzAyMDAgQ0VTVCBtPSs4MS42NDQ1NDE5MTgiLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJyY1pRdHMtbzJFaDFGZHhCIn0.-BfYEQoZEiTDwc9fsuLawiEIE_P8Q8KAKhbk3aU0b1-YQv8Brjsihg.bwXK-xKtobJd3mWC.c5rNN9FoqUpnrIjwqU3xPqcgCUgbCF8GgAUibcQfmyYk1MIvbM7aSx1y6ngO0UCRLCZdPhJgxxfBAbPZrbFe7DL4XKn5RfdbPO0a43pN0CHiHu3z86YlPwTS2ADQSta1Zup_ek2boS39GOAgXgC1kYIiox8b1aM_zOvx7M1ASdhnRqMoGu-kYth0FQoocbYRlTF9WMGR40iW-xfSFSYxvoORZDYvfgy8_hIjAYw7bfFkHQ.WWP2VXAeTBZ5L30NTqNbfg \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/310322fcb0937ade77a9ba0d128f9e7f17312796.address b/client/keyring/testdata/keyring-file/310322fcb0937ade77a9ba0d128f9e7f17312796.address new file mode 100644 index 00000000..7b5e5e2c --- /dev/null +++ b/client/keyring/testdata/keyring-file/310322fcb0937ade77a9ba0d128f9e7f17312796.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo1MzozMi41MjczNjIgKzAyMDAgQ0VTVCBtPSswLjU0NDc3NTc1MSIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjgxOTIsInAycyI6IjhGTDlhUXNlSzVZV2RlblcifQ.cc0WF0wygByoLnNwnrAeFMqRbzFduFelXwQlKVbnYg7Y7sQoVCtVzQ.qC1rIh3zSt9Lfc-V.6MPAr7OMxRwq91SM3o8G43d-NtwTqzCmjoYXFxH2bvYmxA0i2L-EH6-_MzZaR7UBV_wJk130vYM0BIrgyWhWpNIHDf0xATBq6rMhVuhEUP4WLgiQQp_cAR0AJ1qZ2pcJyxCnWpDHSdg1D3vP734H6djM77guObVRmGrk5Xp2eRcC4EEEP1DsF53xHxR_ciH8mq1RO5G5GWmitVPzPrrZGYoD1XQudT300kk3mPGnEUP6uVs-JO4.ER5W3QEeONNz7lUZAAbLIg \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/keyhash b/client/keyring/testdata/keyring-file/keyhash new file mode 100644 index 00000000..5d41bad2 --- /dev/null +++ b/client/keyring/testdata/keyring-file/keyhash @@ -0,0 +1 @@ +$2a$10$dwTjlfhSfydxZcMd8dqWxOjZ06RgbCv3oCrmrkv8.M6jzKGCzx5r2 \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/test.info b/client/keyring/testdata/keyring-file/test.info new file mode 100644 index 00000000..5580cfdb --- /dev/null +++ b/client/keyring/testdata/keyring-file/test.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo0OTowNi42MzkyNjIgKzAyMDAgQ0VTVCBtPSs4MS42MzY3ODE1NDMiLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJGMUxoTER3ajhPR2VBRVh0In0.0Sa9gloQcB_4t5RAkS1kgqlCgBu0NwZG_WCHpA7eU3B7bD2zZjKM-A.uLO-mtT3vfO1pJLx.q7FZhW_tKnFTL80UuJU9LPj8T0nFS5UOcy_j23G3UGUs-gstVs9cJEtSNZOCz14-EcsRdJtWm5T9nx0Aauh_48LySQ-LDBbbycH1BJjyEMDhxn8zf0En8uIpZWiWa6vgfeomJE7BY_tRVFnMtzXvFJ69Ky37wFjSKeBac0XaxRc2XsBYUIRJY3xqnni53nvjw55fmyHo5-gBV-OC3ZDX4LmeTQcKw71LCVfGA1oxuk7BQcsYHc2_v2Lxr5rDuoZJl1Do32r63ss2fee85-q0Htjw_unaswZa0KLfMyyMOyRyTPsw0NHik8YjqkGJt9EOfARgu_IFGYoxaTotBBnjbpxOg7dpX4467WnFjkUbXtVFWqEAvIOhRmMbobUqJIPz1Ai5t3jYN-PIKfElgXciXdXPVEVe0j7ABJsNZCFJ11FueRqWhgTw_1B0OWrXjyWQ9TM-Yba9h_v7Cw0AP4lhvk6nm95H_pwYXX_dURj_5w.DUxlLK9M5aJbnHeYmWoHZQ \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/test2.info b/client/keyring/testdata/keyring-file/test2.info new file mode 100644 index 00000000..a78d4339 --- /dev/null +++ b/client/keyring/testdata/keyring-file/test2.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo1MzozMi41MTY1MTcgKzAyMDAgQ0VTVCBtPSswLjUzMzkzMDc1MSIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjgxOTIsInAycyI6InFkWl9BbUtFUTg4UE8zb1EifQ.0XmW52t7Bn9bZKbjRLTLz0t8xPkgspFZNpzEgWgWaVUBugjlHqXm3w.mDeWYJuwq7t8UPy9.RDkxLQVylI0V1q1Ibihp73y5EnfqKYAwpQtdAKC9zcTOLVNIziYBjNBfgFKRbNE6q38AZo2_mN4GqH3o-9OwEWlj-qWe0H1EjXVuWqpteaT4EpYC7ZX9uMrk6yQyf_lfniGnL0f5j_hzMl_CVC6lGUgOf6nS1fOxZ-we00rcLNu-3W9ZZreTVwDQG-w2sCV95nreMTmkGd-z8BmgZsDDDB0YB0gnW_TDHVyn3zNi8S3SQxXEgKqXGi5KdsJTBQHkl6fx55LYb4o1sSvgiA8JcNtqtwvARNCq2tLS8ADGMMrrbIPEpmHS69Gd-6UEFpmP8vOeWKwKk52y9ozL___q9yMC-y71DkRpbOcM5zYEBZmStD2gucpYxlEJY5hEVN3szHQZz7MfW8MqiESlRi1-cRCanVDZOl3K6AbRix_F75yMxkWg3OEjwueecsKXFlqzz_Zv8MxMFJQWAYiEloXLZaNPS9GIUrWnALLO2BvySlE2lwf9.yzPjJMAGieybi_UccRHU2w \ No newline at end of file diff --git a/go.mod b/go.mod index 34e0b5b1..25c1e09d 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/cometbft/cometbft v0.38.9 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.50.7 + github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogoproto v1.5.0 github.com/cosmos/ibc-go/modules/capability v1.0.0 github.com/cosmos/ibc-go/v8 v8.2.0 @@ -85,7 +86,6 @@ require ( github.com/cometbft/cometbft-db v0.9.1 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.0.2 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v1.1.2 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect @@ -123,6 +123,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -136,6 +137,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect @@ -150,12 +152,14 @@ require ( github.com/klauspost/compress v1.17.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.7 // indirect github.com/linxGnu/grocksdb v1.8.14 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect diff --git a/go.sum b/go.sum index a9b25d4b..20ae5357 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CosmWasm/wasmvm/v2 v2.0.0 h1:IqNCI2G0mvs7K6ej17/I28805rVqnu+Y1cWDqIdwb08= @@ -87,6 +89,8 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -97,6 +101,8 @@ github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= +github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -170,6 +176,7 @@ github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywR github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= @@ -224,6 +231,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/cometbft/cometbft-db v0.9.1 h1:MIhVX5ja5bXNHF8EYrThkG9F7r9kSfv8BX4LWaxWJ4M= github.com/cometbft/cometbft-db v0.9.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -287,6 +296,10 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -728,6 +741,10 @@ github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdM github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -738,6 +755,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= @@ -1081,6 +1100,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From fedf1a8af2d1a8d3a18a4a9b21bb3a56ec2cc5f2 Mon Sep 17 00:00:00 2001 From: Pavel Shibaev Date: Sat, 7 Sep 2024 00:11:29 +0200 Subject: [PATCH 2/7] (feat) Add OFAC list check --- client/chain/chain.go | 27 +++++- client/chain/chain_test.go | 71 +++++++++++++- client/chain/ofac.go | 97 +++++++++++++++++++ client/metadata/ofac.json | 48 +++++++++ .../chain/ofac/1_DownloadOfacList/example.go | 12 +++ 5 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 client/chain/ofac.go create mode 100644 client/metadata/ofac.json create mode 100644 examples/chain/ofac/1_DownloadOfacList/example.go diff --git a/client/chain/chain.go b/client/chain/chain.go index 3f9f5a17..a78826c5 100644 --- a/client/chain/chain.go +++ b/client/chain/chain.go @@ -321,6 +321,8 @@ type chainClient struct { sessionEnabled bool + ofacChecker *OfacChecker + authQueryClient authtypes.QueryClient authzQueryClient authztypes.QueryClient bankQueryClient banktypes.QueryClient @@ -440,15 +442,23 @@ func NewChainClient( subaccountToNonce: make(map[ethcommon.Hash]uint32), } + _ = NewTxFactory(ctx).WithSequence(0).WithAccountNumber(0).WithGas(0) + + cc.ofacChecker, err = NewOfacChecker() + if err != nil { + return nil, errors.Wrap(err, "Error creating OFAC checker") + } if cc.canSign { var err error - - cc.accNum, cc.accSeq, err = cc.txFactory.AccountRetriever().GetAccountNumberSequence(ctx, ctx.GetFromAddress()) + account, err := cc.txFactory.AccountRetriever().GetAccount(ctx, ctx.GetFromAddress()) if err != nil { - err = errors.Wrap(err, "failed to get initial account num and seq") + err = errors.Wrapf(err, "failed to get account") return nil, err } - + if cc.ofacChecker.IsBlacklisted(account.GetAddress().String()) { + return nil, errors.Errorf("Address %s is in the OFAC list", account.GetAddress()) + } + cc.accNum, cc.accSeq = account.GetAccountNumber(), account.GetSequence() go cc.runBatchBroadcast() go cc.syncTimeoutHeight() } @@ -1153,6 +1163,9 @@ func (c *chainClient) GetAuthzGrants(ctx context.Context, req authztypes.QueryGr } func (c *chainClient) BuildGenericAuthz(granter, grantee, msgtype string, expireIn time.Time) *authztypes.MsgGrant { + if c.ofacChecker.IsBlacklisted(granter) { + panic("Address is in the OFAC list") // panics should generally be avoided, but otherwise function signature should be changed + } authz := authztypes.NewGenericAuthorization(msgtype) authzAny := codectypes.UnsafePackAny(authz) return &authztypes.MsgGrant{ @@ -1184,6 +1197,9 @@ var ( ) func (c *chainClient) BuildExchangeAuthz(granter, grantee string, authzType ExchangeAuthz, subaccountId string, markets []string, expireIn time.Time) *authztypes.MsgGrant { + if c.ofacChecker.IsBlacklisted(granter) { + panic("Address is in the OFAC list") // panics should generally be avoided, but otherwise function signature should be changed + } var typedAuthzAny codectypes.Any var typedAuthzBytes []byte switch authzType { @@ -1279,6 +1295,9 @@ func (c *chainClient) BuildExchangeBatchUpdateOrdersAuthz( derivativeMarkets []string, expireIn time.Time, ) *authztypes.MsgGrant { + if c.ofacChecker.IsBlacklisted(granter) { + panic("Address is in the OFAC list") // panics should generally be avoided, but otherwise function signature should be changed + } typedAuthz := &exchangetypes.BatchUpdateOrdersAuthz{ SubaccountId: subaccountId, SpotMarkets: spotMarkets, diff --git a/client/chain/chain_test.go b/client/chain/chain_test.go index 0016f37f..57466e42 100644 --- a/client/chain/chain_test.go +++ b/client/chain/chain_test.go @@ -1,9 +1,13 @@ package chain import ( + "encoding/json" + "io" "os" "testing" + "github.com/stretchr/testify/suite" + "github.com/InjectiveLabs/sdk-go/client" "github.com/InjectiveLabs/sdk-go/client/common" rpchttp "github.com/cometbft/cometbft/rpc/client/http" @@ -51,6 +55,72 @@ func createClient(senderAddress cosmtypes.AccAddress, cosmosKeyring keyring.Keyr return chainClient, err } +type OfacTestSuite struct { + suite.Suite + network common.Network + tmClient *rpchttp.HTTP + senderAddress cosmtypes.AccAddress + cosmosKeyring keyring.Keyring +} + +func (suite *OfacTestSuite) SetupTest() { + var err error + suite.network = common.LoadNetwork("testnet", "lb") + suite.tmClient, err = rpchttp.New(suite.network.TmEndpoint, "/websocket") + suite.NoError(err) + + suite.senderAddress, suite.cosmosKeyring, err = accountForTests() + suite.NoError(err) + + // Prepare OFAC list file + testList := []string{ + suite.senderAddress.String(), + } + jsonData, err := json.Marshal(testList) + suite.NoError(err) + + ofacListFilename = "ofac_test.json" + file, err := os.Create(getOfacListPath()) + suite.NoError(err) + + _, err = io.WriteString(file, string(jsonData)) + suite.NoError(err) + + err = file.Close() + suite.NoError(err) +} + +func (suite *OfacTestSuite) TearDownTest() { + err := os.Remove(getOfacListPath()) + suite.NoError(err) + ofacListFilename = defaultofacListFilename +} + +func (suite *OfacTestSuite) TestOfacList() { + clientCtx, err := NewClientContext( + suite.network.ChainId, + suite.senderAddress.String(), + suite.cosmosKeyring, + ) + suite.NoError(err) + + clientCtx = clientCtx.WithNodeURI(suite.network.TmEndpoint).WithClient(suite.tmClient) + testChecker, err := NewOfacChecker() + suite.NoError(err) + suite.Equal(1, len(testChecker.ofacList)) + + _, err = NewChainClient( + clientCtx, + suite.network, + common.OptionGasPrices(client.DefaultGasPriceWithDenom), + ) + suite.Error(err) +} + +func TestOfacTestSuite(t *testing.T) { + suite.Run(t, new(OfacTestSuite)) +} + func TestDefaultSubaccount(t *testing.T) { network := common.LoadNetwork("devnet", "lb") senderAddress, cosmosKeyring, err := accountForTests() @@ -103,5 +173,4 @@ func TestGetSubaccountWithIndex(t *testing.T) { if subaccountThirty != expectedSubaccountThirtyIdHash { t.Error("The subaccount with index 30 was calculated incorrectly") } - } diff --git a/client/chain/ofac.go b/client/chain/ofac.go new file mode 100644 index 00000000..0a8b3eee --- /dev/null +++ b/client/chain/ofac.go @@ -0,0 +1,97 @@ +package chain + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" +) + +const ( + defaultOfacListURL = "https://raw.githubusercontent.com/InjectiveLabs/injective-lists/master/wallets/ofac.json" + defaultofacListFilename = "ofac.json" +) + +var ( + ofacListFilename = defaultofacListFilename +) + +type OfacChecker struct { + ofacListPath string + ofacList map[string]bool +} + +func NewOfacChecker() (*OfacChecker, error) { + checker := &OfacChecker{ + ofacListPath: getOfacListPath(), + } + if _, err := os.Stat(checker.ofacListPath); os.IsNotExist(err) { + if err := DownloadOfacList(); err != nil { + return nil, err + } + } + if err := checker.loadOfacList(); err != nil { + return nil, err + } + return checker, nil +} + +func getOfacListPath() string { + currentDirectory, _ := os.Getwd() + for !strings.HasSuffix(currentDirectory, "sdk-go") { + currentDirectory = filepath.Dir(currentDirectory) + } + return filepath.Join(filepath.Join(filepath.Join(currentDirectory, "client"), "metadata"), ofacListFilename) +} + +func DownloadOfacList() error { + resp, err := http.Get(defaultOfacListURL) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to download OFAC list, status code: %d", resp.StatusCode) + } + + outFile, err := os.Create(getOfacListPath()) + if err != nil { + return err + } + defer outFile.Close() + + _, err = io.Copy(outFile, resp.Body) + if err != nil { + return err + } + _, err = outFile.WriteString("\n") + if err != nil { + return err + } + return nil +} + +func (oc *OfacChecker) loadOfacList() error { + file, err := os.ReadFile(oc.ofacListPath) + if err != nil { + return err + } + var list []string + err = json.Unmarshal(file, &list) + if err != nil { + return err + } + oc.ofacList = make(map[string]bool) + for _, item := range list { + oc.ofacList[item] = true + } + return nil +} + +func (oc *OfacChecker) IsBlacklisted(address string) bool { + return oc.ofacList[address] +} diff --git a/client/metadata/ofac.json b/client/metadata/ofac.json new file mode 100644 index 00000000..59b4160e --- /dev/null +++ b/client/metadata/ofac.json @@ -0,0 +1,48 @@ +[ + "0x179f48c78f57a3a78f0608cc9197b8972921d1d2", + "0x1967d8af5bd86a497fb3dd7899a020e47560daaf", + "0x19aa5fe80d33a56d56c78e82ea5e50e5d80b4dff", + "0x19aa5fe80d33a56d56c78e82ea5e50e5d80b4dff", + "0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a", + "0x2f389ce8bd8ff92de3402ffce4691d17fc4f6535", + "0x2f389ce8bd8ff92de3402ffce4691d17fc4f6535", + "0x2f50508a8a3d323b91336fa3ea6ae50e55f32185", + "0x308ed4b7b49797e1a98d3818bff6fe5385410370", + "0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d", + "0x3efa30704d2b8bbac821307230376556cf8cc39e", + "0x48549a34ae37b12f6a30566245176994e17c6b4a", + "0x4f47bc496083c727c5fbe3ce9cdf2b0f6496270c", + "0x4f47bc496083c727c5fbe3ce9cdf2b0f6496270c", + "0x4f47bc496083c727c5fbe3ce9cdf2b0f6496270c", + "0x530a64c0ce595026a4a556b703644228179e2d57", + "0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0", + "0x5a7a51bfb49f190e5a6060a5bc6052ac14a3b59f", + "0x5f48c2a71b2cc96e3f0ccae4e39318ff0dc375b2", + "0x6be0ae71e6c41f2f9d0d1a3b8d0f75e6f6a0b46e", + "0x6f1ca141a28907f78ebaa64fb83a9088b02a8352", + "0x746aebc06d2ae31b71ac51429a19d54e797878e9", + "0x77777feddddffc19ff86db637967013e6c6a116c", + "0x797d7ae72ebddcdea2a346c1834e04d1f8df102b", + "0x8576acc5c05d6ce88f4e49bf65bdf0c62f91353c", + "0x901bb9583b24d97e995513c6778dc6888ab6870e", + "0x961c5be54a2ffc17cf4cb021d863c42dacd47fc1", + "0x97b1043abd9e6fc31681635166d430a458d14f9c", + "0x9c2bc757b66f24d60f016b6237f8cdd414a879fa", + "0x9f4cda013e354b8fc285bf4b9a60460cee7f7ea9", + "0xa7e5d5a720f06526557c513402f2e6b5fa20b008", + "0xb6f5ec1a0a9cd1526536d3f0426c429529471f40", + "0xb6f5ec1a0a9cd1526536d3f0426c429529471f40", + "0xb6f5ec1a0a9cd1526536d3f0426c429529471f40", + "0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a", + "0xca0840578f57fe71599d29375e16783424023357", + "0xd0975b32cea532eadddfc9c60481976e39db3472", + "0xd882cfc20f52f2599d84b8e8d58c7fb62cfe344b", + "0xd882cfc20f52f2599d84b8e8d58c7fb62cfe344b", + "0xe1d865c3d669dcc8c57c8d023140cb204e672ee4", + "0xe7aa314c77f4233c18c6cc84384a9247c0cf367b", + "0xed6e0a7e4ac94d976eebfb82ccf777a3c6bad921", + "0xf3701f445b6bdafedbca97d1e477357839e4120d", + "0xfac583c0cf07ea434052c49115a4682172ab6b4f", + "0xfec8a60023265364d066a1212fde3930f6ae8da7", + "0xffbac21a641dcfe4552920138d90f3638b3c9fba" +] diff --git a/examples/chain/ofac/1_DownloadOfacList/example.go b/examples/chain/ofac/1_DownloadOfacList/example.go new file mode 100644 index 00000000..4b6f7992 --- /dev/null +++ b/examples/chain/ofac/1_DownloadOfacList/example.go @@ -0,0 +1,12 @@ +package main + +import ( + chainclient "github.com/InjectiveLabs/sdk-go/client/chain" +) + +func main() { + err := chainclient.DownloadOfacList() + if err != nil { + panic(err) + } +} From e009b64630842d6004ee62558255340c259f0cc0 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 11 Sep 2024 16:07:57 +0200 Subject: [PATCH 3/7] fix linter --- client/keyring/key_config.go | 8 +++--- client/keyring/keyring.go | 42 ++++++++++++++++++-------------- client/keyring/keyring_config.go | 6 ++--- go.mod | 4 --- go.sum | 20 --------------- 5 files changed, 31 insertions(+), 49 deletions(-) diff --git a/client/keyring/key_config.go b/client/keyring/key_config.go index 79b74ef2..51bb7b45 100644 --- a/client/keyring/key_config.go +++ b/client/keyring/key_config.go @@ -19,7 +19,7 @@ type KeyConfigOpt func(c *cosmosKeyConfig) error // WithKeyFrom sets the key name to use for signing. Must exist in the provided keyring. func WithKeyFrom(v string) KeyConfigOpt { return func(c *cosmosKeyConfig) error { - if len(v) > 0 { + if v != "" { c.KeyFrom = v } @@ -31,7 +31,7 @@ func WithKeyFrom(v string) KeyConfigOpt { // The package will fallback to os.Stdin if this option was not provided, but pass is required. func WithKeyPassphrase(v string) KeyConfigOpt { return func(c *cosmosKeyConfig) error { - if len(v) > 0 { + if v != "" { c.KeyPassphrase = v } @@ -43,7 +43,7 @@ func WithKeyPassphrase(v string) KeyConfigOpt { // The package will create a virtual keyring holding that key, to meet all the interfaces. func WithPrivKeyHex(v string) KeyConfigOpt { return func(c *cosmosKeyConfig) error { - if len(v) > 0 { + if v != "" { c.PrivKeyHex = v } @@ -55,7 +55,7 @@ func WithPrivKeyHex(v string) KeyConfigOpt { // The package will create a virtual keyring to derive the keys and meet all the interfaces. func WithMnemonic(v string) KeyConfigOpt { return func(c *cosmosKeyConfig) error { - if len(v) > 0 { + if v != "" { if !bip39.IsMnemonicValid(v) { err := errors.New("provided mnemonic is not a valid BIP39 mnemonic") return err diff --git a/client/keyring/keyring.go b/client/keyring/keyring.go index d9052b7f..18fb8473 100644 --- a/client/keyring/keyring.go +++ b/client/keyring/keyring.go @@ -44,7 +44,7 @@ func NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmk for keyIdx, keyConfig := range config.Keys { switch { - case len(keyConfig.Mnemonic) > 0: + case keyConfig.Mnemonic != "": if usingRealKeyring { return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity } else if kb == nil { @@ -65,7 +65,7 @@ func NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmk firstKey = &addr } - case len(keyConfig.PrivKeyHex) > 0: + case keyConfig.PrivKeyHex != "": if usingRealKeyring { return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity } else if kb == nil { @@ -86,7 +86,7 @@ func NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmk firstKey = &addr } - case len(keyConfig.KeyFrom) > 0: + case keyConfig.KeyFrom != "": if kb != nil { return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity } else { @@ -117,7 +117,7 @@ func NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmk } if realKB != nil { - if len(config.DefaultKey) > 0 { + if config.DefaultKey != "" { defaultKeyAddr, err := findKeyInKeyring(realKB, config, config.DefaultKey) if err != nil { return emptyCosmosAddress, nil, err @@ -129,7 +129,7 @@ func NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmk return *firstKey, realKB, nil } - if len(config.DefaultKey) > 0 { + if config.DefaultKey != "" { defaultKeyAddr, err := findKeyInKeyring(kb, config, config.DefaultKey) if err != nil { return emptyCosmosAddress, nil, err @@ -157,9 +157,11 @@ func fromPrivkeyHex( keyName := keyConfig.Name // check that if cosmos 'From' specified separately, it must match the provided privkey - if len(keyConfig.KeyFrom) > 0 { + if keyConfig.KeyFrom != "" { addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) - if err == nil { + + switch { + case err == nil: if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) { err = errors.Wrapf( ErrUnexpectedAddress, @@ -169,19 +171,22 @@ func fromPrivkeyHex( return emptyCosmosAddress, err } - } else if len(keyName) == 0 { + + case keyName == "": // use it as a name then keyName = keyConfig.KeyFrom - } else if keyName != keyConfig.KeyFrom { + + case keyName != keyConfig.KeyFrom: err := errors.Errorf( "key 'from' opt is a name, but doesn't match given key name: %s != %s", keyConfig.KeyFrom, keyName, ) + return emptyCosmosAddress, err } } - if len(keyName) == 0 { + if keyName == "" { keyName = defaultKeyringKeyName } @@ -216,9 +221,10 @@ func fromMnemonic( keyName := keyConfig.Name // check that if cosmos 'From' specified separately, it must match the derived privkey - if len(keyConfig.KeyFrom) > 0 { + if keyConfig.KeyFrom != "" { addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) - if err == nil { + switch { + case err == nil: if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) { err = errors.Wrapf( ErrUnexpectedAddress, @@ -228,10 +234,10 @@ func fromMnemonic( return emptyCosmosAddress, err } - } else if len(keyName) == 0 { + case keyName == "": // use it as a name then keyName = keyConfig.KeyFrom - } else if keyName != keyConfig.KeyFrom { + case keyName != keyConfig.KeyFrom: err := errors.Errorf( "key 'from' opt is a name, but doesn't match given key name: %s != %s", keyConfig.KeyFrom, keyName, @@ -241,13 +247,13 @@ func fromMnemonic( } // check that if 'PrivKeyHex' specified separately, it must match the derived privkey too - if len(keyConfig.PrivKeyHex) > 0 { + if keyConfig.PrivKeyHex != "" { if err := checkPrivkeyHexMatchesMnemonic(keyConfig.PrivKeyHex, pkBytes); err != nil { return emptyCosmosAddress, err } } - if len(keyName) == 0 { + if keyName == "" { keyName = defaultKeyringKeyName } @@ -286,7 +292,7 @@ func fromCosmosKeyring( fromIsAddress bool, ) (sdk.AccAddress, cosmkeyring.Keyring, error) { var passReader io.Reader = os.Stdin - if len(keyConfig.KeyPassphrase) > 0 { + if keyConfig.KeyPassphrase != "" { passReader = newPassReader(keyConfig.KeyPassphrase) } @@ -318,7 +324,7 @@ func fromCosmosKeyring( keyRecord, err = kb.KeyByAddress(fromAddress) } else { keyName := keyConfig.Name - if len(keyName) > 0 && keyConfig.KeyFrom != keyName { + if keyName != "" && keyConfig.KeyFrom != keyName { err := errors.Errorf( "key 'from' opt is a name, but doesn't match given key name: %s != %s", keyConfig.KeyFrom, keyName, diff --git a/client/keyring/keyring_config.go b/client/keyring/keyring_config.go index 42ac4cbc..ebf3e74b 100644 --- a/client/keyring/keyring_config.go +++ b/client/keyring/keyring_config.go @@ -32,7 +32,7 @@ const ( // WithKeyringDir option sets keyring path in the filesystem, useful when keyring backend is `file`. func WithKeyringDir(v string) ConfigOpt { return func(c *cosmosKeyringConfig) error { - if len(v) > 0 { + if v != "" { c.KeyringDir = v } @@ -43,7 +43,7 @@ func WithKeyringDir(v string) ConfigOpt { // WithKeyringAppName option sets keyring application name (used by Cosmos to separate keyrings). func WithKeyringAppName(v string) ConfigOpt { return func(c *cosmosKeyringConfig) error { - if len(v) > 0 { + if v != "" { c.KeyringAppName = v } @@ -111,7 +111,7 @@ func WithNamedKey(name string, opts ...KeyConfigOpt) ConfigOpt { // This key must exist in specified keys. func WithDefaultKey(v string) ConfigOpt { return func(c *cosmosKeyringConfig) error { - if len(v) > 0 { + if v != "" { c.DefaultKey = v } diff --git a/go.mod b/go.mod index 25c1e09d..411a8b43 100644 --- a/go.mod +++ b/go.mod @@ -123,7 +123,6 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -137,7 +136,6 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect @@ -152,14 +150,12 @@ require ( github.com/klauspost/compress v1.17.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.7 // indirect github.com/linxGnu/grocksdb v1.8.14 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect diff --git a/go.sum b/go.sum index 20ae5357..a9b25d4b 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,6 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CosmWasm/wasmvm/v2 v2.0.0 h1:IqNCI2G0mvs7K6ej17/I28805rVqnu+Y1cWDqIdwb08= @@ -89,8 +87,6 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -101,8 +97,6 @@ github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= -github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -176,7 +170,6 @@ github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywR github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= @@ -231,8 +224,6 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/cometbft/cometbft-db v0.9.1 h1:MIhVX5ja5bXNHF8EYrThkG9F7r9kSfv8BX4LWaxWJ4M= github.com/cometbft/cometbft-db v0.9.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -296,10 +287,6 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -741,10 +728,6 @@ github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdM github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= -github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -755,8 +738,6 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= -github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= @@ -1100,7 +1081,6 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 20919a13680f0ed12a8f641d6cb6f6b45ef0f300 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 11 Sep 2024 16:35:51 +0200 Subject: [PATCH 4/7] fix pre-commit --- .pre-commit-config.yaml | 3 ++- client/keyring/README.md | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6641de94..ac692ca7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,8 @@ exclude: | (?x)^( chain/.*| exchange/.*| - proto/.* + proto/.*| + client/keyring/testdata/.* )$ repos: - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/client/keyring/README.md b/client/keyring/README.md index 97872e75..5bdcc60c 100644 --- a/client/keyring/README.md +++ b/client/keyring/README.md @@ -68,7 +68,6 @@ NewCosmosKeyring( WithKeyringDir(*keyringDir), WithKeyringAppName(*keyringAppName), WithKeyringBackend(Backend(*keyringBackend)), - WithNamedKey( "dispatcher", WithKeyFrom(*dispatcherKeyFrom), @@ -76,7 +75,6 @@ NewCosmosKeyring( WithPrivKeyHex(*dispatcherKeyPrivateHex), WithMnemonic(*dispatcherKeyMnemonic), ), - WithDefaultKey( "dispatcher", ), From 973cf1367b8a775ac6496374144b7e6a17a4e46c Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 11 Sep 2024 18:33:04 +0200 Subject: [PATCH 5/7] fix: keyring unit test when using OS keyring --- .github/workflows/run-tests.yml | 7 +++++++ client/keyring/keyring_errors_test.go | 4 ++-- client/keyring/keyring_test.go | 6 ++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9236e281..4845bdaf 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -15,6 +15,13 @@ jobs: with: go-version-file: "go.mod" check-latest: true + - name: Install pass helper + run: sudo apt-get update && sudo apt-get install -y pass + - name: Generate GPG key + run: " + echo \"%no-protection\nKey-Type: 1\nKey-Length: 4096\nSubkey-Type: 1\nSubkey-Length: 4096\nName-Comment: keyring_test\nExpire-Date: 0\" > genkey && gpg --gen-key --batch genkey" + - name: Setup OS keystore + run: pass init keyring_test - name: Run test and calculate coverage run: make coverage - name: Upload coverage to Codecov diff --git a/client/keyring/keyring_errors_test.go b/client/keyring/keyring_errors_test.go index 8ff13cb0..f2f3d52c 100644 --- a/client/keyring/keyring_errors_test.go +++ b/client/keyring/keyring_errors_test.go @@ -112,7 +112,7 @@ func (s *KeyringTestSuite) TestErrKeyIncompatible() { }) testKeyring, err := cosmkeyring.New( - "keyring_test", + KeyringAppName, cosmkeyring.BackendTest, kbDir, nil, @@ -128,7 +128,7 @@ func (s *KeyringTestSuite) TestErrKeyIncompatible() { s.cdc, WithKeyringBackend(BackendTest), WithKeyringDir(kbDir), - WithKeyringAppName("keyring_test"), + WithKeyringAppName(KeyringAppName), WithKey( WithKeyFrom("test_pubkey"), ), diff --git a/client/keyring/keyring_test.go b/client/keyring/keyring_test.go index 990e1645..38693b7b 100644 --- a/client/keyring/keyring_test.go +++ b/client/keyring/keyring_test.go @@ -18,6 +18,8 @@ import ( "github.com/InjectiveLabs/sdk-go/client/chain" ) +const KeyringAppName = "keyring_test" + type KeyringTestSuite struct { suite.Suite @@ -151,7 +153,7 @@ func (s *KeyringTestSuite) TestKeyringOsWithAppName() { requireT := require.New(s.T()) osKeyring, err := cosmkeyring.New( - "keyring_test", + KeyringAppName, cosmkeyring.BackendOS, "", nil, @@ -188,7 +190,7 @@ func (s *KeyringTestSuite) TestKeyringOsWithAppName() { accAddr, kb, err := NewCosmosKeyring( s.cdc, WithKeyringBackend(BackendOS), - WithKeyringAppName("keyring_test"), + WithKeyringAppName(KeyringAppName), WithKey( WithKeyFrom("test"), ), From f4899c9fda96a811dc4207b5bbc29e164accd092 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 11 Sep 2024 18:39:49 +0200 Subject: [PATCH 6/7] chore: fix keyring README for grammar issues --- client/keyring/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/keyring/README.md b/client/keyring/README.md index 5bdcc60c..824e6402 100644 --- a/client/keyring/README.md +++ b/client/keyring/README.md @@ -1,8 +1,8 @@ # Injective Chain Keyring Helper -Creates a new keyring from a variety of options. See `ConfigOpt` and related options. This keyring helper allows to initialize Cosmos SDK keyring used for signing transactions. +Creates a new keyring from a variety of options. See `ConfigOpt` and related options. This keyring helper allows initializing the Cosmos SDK keyring used for signing transactions. -It allows flexibly define a static configuration of keys, supports multiple pre-defined keys in the same keyring and allows to load keys from a file, derive from mnemonic or read plain private key bytes from a HEX string. Extremely useful for testing and local development, but also robust for production use cases. +It allows flexibly defining a static configuration of keys, supports multiple pre-defined keys in the same keyring, and allows loading keys from a file, deriving from mnemonic, or reading plain private key bytes from a HEX string. Extremely useful for testing and local development, but also robust for production use cases. ## Usage @@ -19,7 +19,7 @@ These options are global on the keyring level. * `WithKeyringBackend` sets the keyring backend. Expected values: `test`, `file`, `os`. * `WithUseLedger` sets the option to use hardware wallet, if available on the system. -These options allow to add keys to the keyring during initialization. +These options allow adding keys to the keyring during initialization. * `WithKey` adds a single key to the keyring, without having alias name. * `WithNamedKey` addes a single key to the keyring, with a name. @@ -31,8 +31,8 @@ These options are set per key. * `WithKeyFrom` sets the key name to use for signing. Must exist in the provided keyring. * `WithKeyPassphrase` sets the passphrase for keyring files. The package will fallback to `os.Stdin` if this option was not provided, but passphrase is required. -* `WithPrivKeyHex` allows to specify a private key as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring holding that key, to meet all the interfaces. -* `WithMnemonic` allows to specify a mnemonic pharse as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring to derive the keys and meet all the interfaces. +* `WithPrivKeyHex` allows specifying a private key as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring holding that key, to meet all the interfaces. +* `WithMnemonic` allows specifying a mnemonic phrase as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring to derive the keys and meet all the interfaces. ## Examples From 0d5726e9b91c354ac514df4ef396cf7bf1ca5f76 Mon Sep 17 00:00:00 2001 From: Pavel Shibaev Date: Wed, 11 Sep 2024 20:54:41 +0200 Subject: [PATCH 7/7] Remove unused code in chain.go --- client/chain/chain.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/chain/chain.go b/client/chain/chain.go index a78826c5..de5f06dd 100644 --- a/client/chain/chain.go +++ b/client/chain/chain.go @@ -442,8 +442,6 @@ func NewChainClient( subaccountToNonce: make(map[ethcommon.Hash]uint32), } - _ = NewTxFactory(ctx).WithSequence(0).WithAccountNumber(0).WithGas(0) - cc.ofacChecker, err = NewOfacChecker() if err != nil { return nil, errors.Wrap(err, "Error creating OFAC checker") @@ -465,7 +463,6 @@ func NewChainClient( return cc, nil } - func (c *chainClient) syncNonce() { num, seq, err := c.txFactory.AccountRetriever().GetAccountNumberSequence(c.ctx, c.ctx.GetFromAddress()) if err != nil {