diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d83bab8..f8d82a9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ This PR contains a series of PRs on multi-staking support and BTC stakingintegra - [#391](https://github.com/babylonlabs-io/babylon/pull/391) Fix e2e `TestBTCRewardsDistribution` flunky check of rewards - [#419](https://github.com/babylonlabs-io/babylon/pull/419) Add new modules to swagger config +- [#429](https://github.com/babylonlabs-io/babylon/pull/429) chore: remove cosmos/relayer dependency ### Bug fixes diff --git a/client/babylonclient/account.go b/client/babylonclient/account.go new file mode 100644 index 000000000..c997cc35b --- /dev/null +++ b/client/babylonclient/account.go @@ -0,0 +1,78 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "context" + "fmt" + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +var _ client.AccountRetriever = &CosmosProvider{} + +// GetAccount queries for an account given an address and a block height. An +// error is returned if the query or decoding fails. +func (cc *CosmosProvider) GetAccount(clientCtx client.Context, addr sdk.AccAddress) (client.Account, error) { + account, _, err := cc.GetAccountWithHeight(clientCtx, addr) + return account, err +} + +// GetAccountWithHeight queries for an account given an address. Returns the +// height of the query with the account. An error is returned if the query +// or decoding fails. +func (cc *CosmosProvider) GetAccountWithHeight(_ client.Context, addr sdk.AccAddress) (client.Account, int64, error) { + var header metadata.MD + address, err := cc.EncodeBech32AccAddr(addr) + if err != nil { + return nil, 0, err + } + + queryClient := authtypes.NewQueryClient(cc) + res, err := queryClient.Account(context.Background(), &authtypes.QueryAccountRequest{Address: address}, grpc.Header(&header)) + if err != nil { + return nil, 0, err + } + + blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader) + if l := len(blockHeight); l != 1 { + return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, l, 1) + } + + nBlockHeight, err := strconv.Atoi(blockHeight[0]) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse block height: %w", err) + } + + var acc sdk.AccountI + if err := cc.Cdc.InterfaceRegistry.UnpackAny(res.Account, &acc); err != nil { + return nil, 0, err + } + + return acc, int64(nBlockHeight), nil +} + +// EnsureExists returns an error if no account exists for the given address else nil. +func (cc *CosmosProvider) EnsureExists(clientCtx client.Context, addr sdk.AccAddress) error { + if _, err := cc.GetAccount(clientCtx, addr); err != nil { + return err + } + return nil +} + +// GetAccountNumberSequence returns sequence and account number for the given address. +// It returns an error if the account couldn't be retrieved from the state. +func (cc *CosmosProvider) GetAccountNumberSequence(clientCtx client.Context, addr sdk.AccAddress) (uint64, uint64, error) { + acc, err := cc.GetAccount(clientCtx, addr) + if err != nil { + return 0, 0, err + } + return acc.GetAccountNumber(), acc.GetSequence(), nil +} diff --git a/client/babylonclient/bech32_hack.go b/client/babylonclient/bech32_hack.go new file mode 100644 index 000000000..cf66ea7c3 --- /dev/null +++ b/client/babylonclient/bech32_hack.go @@ -0,0 +1,31 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "sync" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// This file is cursed and this mutex is too +// you don't want none of this dewey cox. +var sdkConfigMutex sync.Mutex + +// SetSDKContext sets the SDK config to the proper bech32 prefixes. +// Don't use this unless you know what you're doing. +// TODO: :dagger: :knife: :chainsaw: remove this function +func (cc *CosmosProvider) SetSDKContext() func() { + return SetSDKConfigContext(cc.PCfg.AccountPrefix) +} + +// SetSDKConfigContext sets the SDK config to the given bech32 prefixes +func SetSDKConfigContext(prefix string) func() { + sdkConfigMutex.Lock() + sdkConf := sdk.GetConfig() + sdkConf.SetBech32PrefixForAccount(prefix, prefix+"pub") + sdkConf.SetBech32PrefixForValidator(prefix+"valoper", prefix+"valoperpub") + sdkConf.SetBech32PrefixForConsensusNode(prefix+"valcons", prefix+"valconspub") + return sdkConfigMutex.Unlock +} diff --git a/client/babylonclient/chain_provider.go b/client/babylonclient/chain_provider.go new file mode 100644 index 000000000..dbfddb661 --- /dev/null +++ b/client/babylonclient/chain_provider.go @@ -0,0 +1,58 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "context" +) + +type BroadcastMode string + +const ( + BroadcastModeSingle BroadcastMode = "single" + BroadcastModeBatch BroadcastMode = "batch" +) + +type ProviderConfig interface { + NewProvider(homepath string, chainName string) (ChainProvider, error) + Validate() error + BroadcastMode() BroadcastMode +} + +type RelayerMessage interface { + Type() string + MsgBytes() ([]byte, error) +} + +type RelayerTxResponse struct { + Height int64 + TxHash string + Codespace string + Code uint32 + Data string + Events []RelayerEvent +} + +type RelayerEvent struct { + EventType string + Attributes map[string]string +} + +type ChainProvider interface { + Init() error + SendMessagesToMempool( + ctx context.Context, + msgs []RelayerMessage, + memo string, + asyncCtx context.Context, + asyncCallbacks []func(*RelayerTxResponse, error), + ) error + ChainName() string + ChainId() string + ProviderConfig() ProviderConfig + Key() string + Address() (string, error) + Timeout() string + SetRpcAddr(rpcAddr string) error +} diff --git a/client/babylonclient/client_wrapper.go b/client/babylonclient/client_wrapper.go new file mode 100644 index 000000000..db5f4aee8 --- /dev/null +++ b/client/babylonclient/client_wrapper.go @@ -0,0 +1,111 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "context" + "github.com/cometbft/cometbft/libs/bytes" + rpcclient "github.com/cometbft/cometbft/rpc/client" + coretypes "github.com/cometbft/cometbft/rpc/core/types" + tmtypes "github.com/cometbft/cometbft/types" +) + +type RPCClient struct { + c rpcclient.Client +} + +func NewRPCClient(c rpcclient.Client) RPCClient { + return RPCClient{c: c} +} + +func (r RPCClient) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { + return r.c.ABCIInfo(ctx) +} + +func (r RPCClient) ABCIQuery( + ctx context.Context, + path string, + data bytes.HexBytes, +) (*coretypes.ResultABCIQuery, error) { + return r.c.ABCIQuery(ctx, path, (data)) +} + +func (r RPCClient) ABCIQueryWithOptions( + ctx context.Context, + path string, + data bytes.HexBytes, + opts rpcclient.ABCIQueryOptions, +) (*coretypes.ResultABCIQuery, error) { + return r.c.ABCIQueryWithOptions(ctx, path, data, opts) +} + +func (r RPCClient) BroadcastTxCommit(ctx context.Context, tx tmtypes.Tx) (*coretypes.ResultBroadcastTxCommit, error) { + return r.c.BroadcastTxCommit(ctx, tx) +} + +func (r RPCClient) BroadcastTxAsync(ctx context.Context, tx tmtypes.Tx) (*coretypes.ResultBroadcastTx, error) { + return r.c.BroadcastTxAsync(ctx, tx) +} + +func (r RPCClient) BroadcastTxSync(ctx context.Context, tx tmtypes.Tx) (*coretypes.ResultBroadcastTx, error) { + return r.c.BroadcastTxSync(ctx, tx) +} + +func (r RPCClient) Validators( + ctx context.Context, + height *int64, + page, perPage *int, +) (*coretypes.ResultValidators, error) { + return r.c.Validators(ctx, height, page, perPage) +} + +func (r RPCClient) Status(ctx context.Context) (*coretypes.ResultStatus, error) { + return r.c.Status(ctx) +} + +func (r RPCClient) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) { + return r.c.Block(ctx, height) +} + +func (r RPCClient) BlockByHash(ctx context.Context, hash []byte) (*coretypes.ResultBlock, error) { + return r.c.BlockByHash(ctx, hash) +} + +func (r RPCClient) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) { + return r.c.BlockResults(ctx, height) +} + +func (r RPCClient) BlockchainInfo( + ctx context.Context, + minHeight, maxHeight int64, +) (*coretypes.ResultBlockchainInfo, error) { + return r.c.BlockchainInfo(ctx, minHeight, maxHeight) +} + +func (r RPCClient) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) { + return r.c.Commit(ctx, height) +} + +func (r RPCClient) Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error) { + return r.c.Tx(ctx, hash, prove) +} + +func (r RPCClient) TxSearch( + ctx context.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, +) (*coretypes.ResultTxSearch, error) { + return r.c.TxSearch(ctx, query, prove, page, perPage, orderBy) +} + +func (r RPCClient) BlockSearch( + ctx context.Context, + query string, + page, perPage *int, + orderBy string, +) (*coretypes.ResultBlockSearch, error) { + return r.c.BlockSearch(ctx, query, page, perPage, orderBy) +} diff --git a/client/babylonclient/grpc_query.go b/client/babylonclient/grpc_query.go new file mode 100644 index 000000000..4ba264871 --- /dev/null +++ b/client/babylonclient/grpc_query.go @@ -0,0 +1,218 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "context" + "fmt" + "reflect" + "strconv" + "sync" + "time" + + sdkerrors "cosmossdk.io/errors" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + legacyerrors "github.com/cosmos/cosmos-sdk/types/errors" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + "github.com/cosmos/cosmos-sdk/types/tx" + gogogrpc "github.com/cosmos/gogoproto/grpc" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/encoding" + "google.golang.org/grpc/encoding/proto" + "google.golang.org/grpc/mem" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +var _ gogogrpc.ClientConn = &CosmosProvider{} + +var protoCodec = encoding.GetCodecV2(proto.Name) + +// Invoke implements the grpc ClientConn.Invoke method +func (cc *CosmosProvider) Invoke(ctx context.Context, method string, req, reply interface{}, opts ...grpc.CallOption) (err error) { + // Two things can happen here: + // 1. either we're broadcasting a Tx, in which call we call Tendermint's broadcast endpoint directly, + // 2. or we are querying for state, in which case we call ABCI's Querier. + + // In both cases, we don't allow empty request req (it will panic unexpectedly). + if reflect.ValueOf(req).IsNil() { + return sdkerrors.Wrap(legacyerrors.ErrInvalidRequest, "request cannot be nil") + } + + // Case 1. Broadcasting a Tx. + if reqProto, ok := req.(*tx.BroadcastTxRequest); ok { + if !ok { + return sdkerrors.Wrapf(legacyerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxRequest)(nil), req) + } + resProto, ok := reply.(*tx.BroadcastTxResponse) + if !ok { + return sdkerrors.Wrapf(legacyerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxResponse)(nil), req) + } + + broadcastRes, err := cc.TxServiceBroadcast(ctx, reqProto) + if err != nil { + return err + } + *resProto = *broadcastRes + return err + } + + // Case 2. Querying state. + inMd, _ := metadata.FromOutgoingContext(ctx) + abciRes, outMd, err := cc.RunGRPCQuery(ctx, method, req, inMd) + if err != nil { + return err + } + + if err = protoCodec.Unmarshal([]mem.Buffer{mem.NewBuffer(&abciRes.Value, nil)}, reply); err != nil { + return err + } + + for _, callOpt := range opts { + header, ok := callOpt.(grpc.HeaderCallOption) + if !ok { + continue + } + + *header.HeaderAddr = outMd + } + + if cc.Cdc.InterfaceRegistry != nil { + return types.UnpackInterfaces(reply, cc.Cdc.Codec) + } + + return nil +} + +// NewStream implements the grpc ClientConn.NewStream method +func (cc *CosmosProvider) NewStream(context.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, fmt.Errorf("streaming rpc not supported") +} + +// RunGRPCQuery runs a gRPC query from the clientCtx, given all necessary +// arguments for the gRPC method, and returns the ABCI response. It is used +// to factorize code between client (Invoke) and server (RegisterGRPCServer) +// gRPC handlers. +func (cc *CosmosProvider) RunGRPCQuery(ctx context.Context, method string, req interface{}, md metadata.MD) (abci.ResponseQuery, metadata.MD, error) { + reqBz, err := protoCodec.Marshal(req) + if err != nil { + return abci.ResponseQuery{}, nil, err + } + + // parse height header + if heights := md.Get(grpctypes.GRPCBlockHeightHeader); len(heights) > 0 { + height, err := strconv.ParseInt(heights[0], 10, 64) + if err != nil { + return abci.ResponseQuery{}, nil, err + } + if height < 0 { + return abci.ResponseQuery{}, nil, sdkerrors.Wrapf( + legacyerrors.ErrInvalidRequest, + "client.Context.Invoke: height (%d) from %q must be >= 0", height, grpctypes.GRPCBlockHeightHeader) + } + } + + height, err := GetHeightFromMetadata(md) + if err != nil { + return abci.ResponseQuery{}, nil, err + } + + prove, err := GetProveFromMetadata(md) + if err != nil { + return abci.ResponseQuery{}, nil, err + } + + abciReq := abci.RequestQuery{ + Path: method, + Data: reqBz.Materialize(), + Height: height, + Prove: prove, + } + + abciRes, err := cc.QueryABCI(ctx, abciReq) + if err != nil { + return abci.ResponseQuery{}, nil, err + } + + // Create header metadata. For now the headers contain: + // - block height + // We then parse all the call options, if the call option is a + // HeaderCallOption, then we manually set the value of that header to the + // metadata. + md = metadata.Pairs(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(abciRes.Height, 10)) + + return abciRes, md, nil +} + +// TxServiceBroadcast is a helper function to broadcast a Tx with the correct gRPC types +// from the tx service. Calls `clientCtx.BroadcastTx` under the hood. +func (cc *CosmosProvider) TxServiceBroadcast(ctx context.Context, req *tx.BroadcastTxRequest) (*tx.BroadcastTxResponse, error) { + if req == nil || req.TxBytes == nil { + return nil, status.Error(codes.InvalidArgument, "invalid empty tx") + } + + var ( + blockTimeout = defaultBroadcastWaitTimeout + err error + rlyResp *RelayerTxResponse + callbackErr error + wg sync.WaitGroup + ) + + if cc.PCfg.BlockTimeout != "" { + blockTimeout, err = time.ParseDuration(cc.PCfg.BlockTimeout) + if err != nil { + // Did you call Validate() method on CosmosProviderConfig struct + // before coming here? + return nil, err + } + } + + callback := func(rtr *RelayerTxResponse, err error) { + rlyResp = rtr + callbackErr = err + wg.Done() + } + + wg.Add(1) + + if err := cc.broadcastTx(ctx, req.TxBytes, ctx, blockTimeout, []func(*RelayerTxResponse, error){callback}); err != nil { + return nil, err + } + + wg.Wait() + + if callbackErr != nil { + return nil, callbackErr + } + + return &tx.BroadcastTxResponse{ + TxResponse: &sdk.TxResponse{ + Height: rlyResp.Height, + TxHash: rlyResp.TxHash, + Codespace: rlyResp.Codespace, + Code: rlyResp.Code, + Data: rlyResp.Data, + }, + }, nil +} + +func GetHeightFromMetadata(md metadata.MD) (int64, error) { + height := md.Get(grpctypes.GRPCBlockHeightHeader) + if len(height) == 1 { + return strconv.ParseInt(height[0], 10, 64) + } + return 0, nil +} + +func GetProveFromMetadata(md metadata.MD) (bool, error) { + prove := md.Get("x-cosmos-query-prove") + if len(prove) == 1 { + return strconv.ParseBool(prove[0]) + } + return false, nil +} diff --git a/client/babylonclient/keys.go b/client/babylonclient/keys.go new file mode 100644 index 000000000..9f7a9b064 --- /dev/null +++ b/client/babylonclient/keys.go @@ -0,0 +1,36 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetKeyAddress returns the account address representation for the currently configured key. +func (cc *CosmosProvider) GetKeyAddress(key string) (sdk.AccAddress, error) { + info, err := cc.Keybase.Key(key) + if err != nil { + return nil, err + } + return info.GetAddress() +} + +// EncodeBech32AccAddr returns the string bech32 representation for the specified account address. +// It returns an empty sting if the byte slice is 0-length. +// It returns an error if the bech32 conversion fails or the prefix is empty. +func (cc *CosmosProvider) EncodeBech32AccAddr(addr sdk.AccAddress) (string, error) { + return sdk.Bech32ifyAddressBytes(cc.PCfg.AccountPrefix, addr) +} + +func (cc *CosmosProvider) DecodeBech32AccAddr(addr string) (sdk.AccAddress, error) { + return sdk.GetFromBech32(addr, cc.PCfg.AccountPrefix) +} + +func (cc *CosmosProvider) GetKeyAddressForKey(key string) (sdk.AccAddress, error) { + info, err := cc.Keybase.Key(key) + if err != nil { + return nil, err + } + return info.GetAddress() +} diff --git a/client/babylonclient/msg.go b/client/babylonclient/msg.go new file mode 100644 index 000000000..3bc30c24a --- /dev/null +++ b/client/babylonclient/msg.go @@ -0,0 +1,44 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/gogoproto/proto" +) + +type CosmosMessage struct { + Msg sdk.Msg + SetSigner func(string) // callback to update the Msg Signer field +} + +func NewCosmosMessage(msg sdk.Msg, optionalSetSigner func(string)) RelayerMessage { + return CosmosMessage{ + Msg: msg, + SetSigner: optionalSetSigner, + } +} + +func CosmosMsgs(rm ...RelayerMessage) []sdk.Msg { + sdkMsgs := make([]sdk.Msg, 0) + for _, rMsg := range rm { + if val, ok := rMsg.(CosmosMessage); !ok { + fmt.Printf("got data of type %T but wanted CosmosMessage \n", rMsg) + return nil + } else { + sdkMsgs = append(sdkMsgs, val.Msg) + } + } + return sdkMsgs +} + +func (cm CosmosMessage) Type() string { + return sdk.MsgTypeURL(cm.Msg) +} + +func (cm CosmosMessage) MsgBytes() ([]byte, error) { + return proto.Marshal(cm.Msg) +} diff --git a/client/babylonclient/provider.go b/client/babylonclient/provider.go new file mode 100644 index 000000000..fde82657e --- /dev/null +++ b/client/babylonclient/provider.go @@ -0,0 +1,184 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "fmt" + "github.com/babylonlabs-io/babylon/app/params" + "github.com/cometbft/cometbft/rpc/client/http" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + "io" + "os" + "path" + "sync" + "time" +) + +type CosmosProviderConfig struct { + KeyDirectory string `json:"key-directory" yaml:"key-directory"` + Key string `json:"key" yaml:"key"` + ChainName string `json:"-" yaml:"-"` + ChainID string `json:"chain-id" yaml:"chain-id"` + RPCAddr string `json:"rpc-addr" yaml:"rpc-addr"` + AccountPrefix string `json:"account-prefix" yaml:"account-prefix"` + KeyringBackend string `json:"keyring-backend" yaml:"keyring-backend"` + GasAdjustment float64 `json:"gas-adjustment" yaml:"gas-adjustment"` + GasPrices string `json:"gas-prices" yaml:"gas-prices"` + MinGasAmount uint64 `json:"min-gas-amount" yaml:"min-gas-amount"` + MaxGasAmount uint64 `json:"max-gas-amount" yaml:"max-gas-amount"` + Debug bool `json:"debug" yaml:"debug"` + Timeout string `json:"timeout" yaml:"timeout"` + BlockTimeout string `json:"block-timeout" yaml:"block-timeout"` + OutputFormat string `json:"output-format" yaml:"output-format"` + SignModeStr string `json:"sign-mode" yaml:"sign-mode"` + Broadcast BroadcastMode `json:"broadcast-mode" yaml:"broadcast-mode"` +} + +type CosmosProvider struct { + PCfg CosmosProviderConfig + Keybase keyring.Keyring + KeyringOptions []keyring.Option + RPCClient RPCClient + Input io.Reader + Output io.Writer + Cdc *params.EncodingConfig + + // the map key is the TX signer (provider key) + // the purpose of the map is to lock on the signer from TX creation through submission, + // thus making TX sequencing errors less likely. + walletStateMap map[string]*WalletState +} + +func (pc CosmosProviderConfig) BroadcastMode() BroadcastMode { + return pc.Broadcast +} + +type WalletState struct { + NextAccountSequence uint64 + Mu sync.Mutex +} + +// NewProvider validates the CosmosProviderConfig, instantiates a ChainClient and then instantiates a CosmosProvider +func (pc CosmosProviderConfig) NewProvider(homepath string, chainName string) (ChainProvider, error) { + if err := pc.Validate(); err != nil { + return nil, err + } + + pc.KeyDirectory = keysDir(homepath, pc.ChainID) + pc.ChainName = chainName + + if pc.Broadcast == "" { + pc.Broadcast = BroadcastModeBatch + } + + cp := &CosmosProvider{ + PCfg: pc, + KeyringOptions: []keyring.Option{}, + Input: os.Stdin, + Output: os.Stdout, + walletStateMap: map[string]*WalletState{}, + } + + return cp, nil +} + +func (pc CosmosProviderConfig) Validate() error { + if _, err := time.ParseDuration(pc.Timeout); err != nil { + return fmt.Errorf("invalid Timeout: %w", err) + } + return nil +} + +// keysDir returns a string representing the path on the local filesystem where the keystore will be initialized. +func keysDir(home, chainID string) string { + return path.Join(home, "keys", chainID) +} + +func (cc *CosmosProvider) ProviderConfig() ProviderConfig { + return cc.PCfg +} + +func (cc *CosmosProvider) ChainId() string { + return cc.PCfg.ChainID +} + +func (cc *CosmosProvider) ChainName() string { + return cc.PCfg.ChainName +} + +func (cc *CosmosProvider) Key() string { + return cc.PCfg.Key +} + +func (cc *CosmosProvider) Timeout() string { + return cc.PCfg.Timeout +} + +// Address returns the chains configured address as a string +func (cc *CosmosProvider) Address() (string, error) { + info, err := cc.Keybase.Key(cc.PCfg.Key) + if err != nil { + return "", err + } + + acc, err := info.GetAddress() + if err != nil { + return "", err + } + + out, err := cc.EncodeBech32AccAddr(acc) + if err != nil { + return "", err + } + + return out, err +} + +func (cc *CosmosProvider) MustEncodeAccAddr(addr sdk.AccAddress) string { + enc, err := cc.EncodeBech32AccAddr(addr) + if err != nil { + panic(err) + } + return enc +} + +// SetRpcAddr sets the rpc-addr for the chain. +// It will fail if the rpcAddr is invalid(not a url). +func (cc *CosmosProvider) SetRpcAddr(rpcAddr string) error { + cc.PCfg.RPCAddr = rpcAddr + return nil +} + +// Init initializes the keystore, RPC client, amd light client provider. +// Once initialization is complete an attempt to query the underlying node's tendermint version is performed. +// NOTE: Init must be called after creating a new instance of CosmosProvider. +func (cc *CosmosProvider) Init() error { + keybase, err := keyring.New( + cc.PCfg.ChainID, + cc.PCfg.KeyringBackend, + cc.PCfg.KeyDirectory, + cc.Input, + cc.Cdc.Codec, + cc.KeyringOptions..., + ) + if err != nil { + return err + } + // TODO: figure out how to deal with input or maybe just make all keyring backends test? + timeout, err := time.ParseDuration(cc.PCfg.Timeout) + if err != nil { + return err + } + + c, err := http.NewWithTimeout(cc.PCfg.RPCAddr, "/websocket", uint(timeout.Seconds())) + if err != nil { + return err + } + + cc.RPCClient = NewRPCClient(c) + cc.Keybase = keybase + + return nil +} diff --git a/client/babylonclient/tx.go b/client/babylonclient/tx.go new file mode 100644 index 000000000..7d208e4c1 --- /dev/null +++ b/client/babylonclient/tx.go @@ -0,0 +1,650 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "context" + sdkerrors "cosmossdk.io/errors" + "cosmossdk.io/store/rootmulti" + "errors" + "fmt" + abci "github.com/cometbft/cometbft/abci/types" + client2 "github.com/cometbft/cometbft/rpc/client" + coretypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + legacyerrors "github.com/cosmos/cosmos-sdk/types/errors" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "math" + "regexp" + "strconv" + "strings" + "sync" + "time" + + "github.com/avast/retry-go/v4" +) + +var ( + rtyAttNum = uint(5) + rtyAtt = retry.Attempts(rtyAttNum) + rtyDel = retry.Delay(time.Millisecond * 400) + rtyErr = retry.LastErrorOnly(true) + accountSeqRegex = regexp.MustCompile("account sequence mismatch, expected ([0-9]+), got ([0-9]+)") + defaultBroadcastWaitTimeout = 10 * time.Minute + errUnknown = "unknown" +) + +const ( + ErrTimeoutAfterWaitingForTxBroadcast _err = "timed out after waiting for tx to get included in the block" +) + +type _err string + +func (e _err) Error() string { return string(e) } + +type intoAny interface { + AsAny() *codectypes.Any +} + +var seqGuardSingleton sync.Mutex + +// Gets the sequence guard. If it doesn't exist, initialized and returns it. +func ensureSequenceGuard(cc *CosmosProvider, key string) *WalletState { + seqGuardSingleton.Lock() + defer seqGuardSingleton.Unlock() + + if cc.walletStateMap == nil { + cc.walletStateMap = map[string]*WalletState{} + } + + sequenceGuard, ok := cc.walletStateMap[key] + if !ok { + cc.walletStateMap[key] = &WalletState{} + return cc.walletStateMap[key] + } + + return sequenceGuard +} + +// QueryABCI performs an ABCI query and returns the appropriate response and error sdk error code. +func (cc *CosmosProvider) QueryABCI(ctx context.Context, req abci.RequestQuery) (abci.ResponseQuery, error) { + opts := client2.ABCIQueryOptions{ + Height: req.Height, + Prove: req.Prove, + } + + result, err := cc.RPCClient.ABCIQueryWithOptions(ctx, req.Path, req.Data, opts) + if err != nil { + return abci.ResponseQuery{}, err + } + + if !result.Response.IsOK() { + return abci.ResponseQuery{}, sdkErrorToGRPCError(result.Response) + } + + // data from trusted node or subspace query doesn't need verification + if !opts.Prove || !isQueryStoreWithProof(req.Path) { + return result.Response, nil + } + + return result.Response, nil +} + +// broadcastTx broadcasts a transaction with the given raw bytes and then, in an async goroutine, waits for the tx to be included in the block. +// The wait will end after either the asyncTimeout has run out or the asyncCtx exits. +// If there is no error broadcasting, the asyncCallback will be called with success/failure of the wait for block inclusion. +func (cc *CosmosProvider) broadcastTx( + ctx context.Context, // context for tx broadcast + tx []byte, // raw tx to be broadcast + asyncCtx context.Context, // context for async wait for block inclusion after successful tx broadcast + asyncTimeout time.Duration, // timeout for waiting for block inclusion + asyncCallbacks []func(*RelayerTxResponse, error), // callback for success/fail of the wait for block inclusion +) error { + res, err := cc.RPCClient.BroadcastTxSync(ctx, tx) + isErr := err != nil + isFailed := res != nil && res.Code != 0 + if isErr || isFailed { + if isErr && res == nil { + // There are some cases where BroadcastTxSync will return an error but the associated + // ResultBroadcastTx will be nil. + return err + } + if isFailed { + if err = cc.sdkError(res.Codespace, res.Code); err == nil { + err = fmt.Errorf("transaction failed to execute: codespace: %s, code: %d, log: %s", res.Codespace, res.Code, res.Log) + } + } + return err + } + + if res == nil { + return fmt.Errorf("unexpected nil response from BroadcastTxSync") + } + + // TODO: maybe we need to check if the node has tx indexing enabled? + // if not, we need to find a new way to block until inclusion in a block + go cc.waitForTx(asyncCtx, res.Hash, asyncTimeout, asyncCallbacks) + + return nil +} + +// waitForTx waits for a transaction to be included in a block, logs success/fail, then invokes callback. +// This is intended to be called as an async goroutine. +func (cc *CosmosProvider) waitForTx( + ctx context.Context, + txHash []byte, + waitTimeout time.Duration, + callbacks []func(*RelayerTxResponse, error), +) { + res, err := cc.waitForBlockInclusion(ctx, txHash, waitTimeout) + if err != nil { + if len(callbacks) > 0 { + for _, cb := range callbacks { + // Call each callback in order since waitForTx is already invoked asynchronously + cb(nil, err) + } + } + return + } + + rlyResp := &RelayerTxResponse{ + Height: res.Height, + TxHash: res.TxHash, + Codespace: res.Codespace, + Code: res.Code, + Data: res.Data, + Events: parseEventsFromTxResponse(res), + } + + // NOTE: error is nil, logic should use the returned error to determine if the + // transaction was successfully executed. + if res.Code != 0 { + // Check for any registered SDK errors + err := cc.sdkError(res.Codespace, res.Code) + if err == nil { + err = fmt.Errorf("transaction failed to execute: codespace: %s, code: %d, log: %s", res.Codespace, res.Code, res.RawLog) + } + if len(callbacks) > 0 { + for _, cb := range callbacks { + // Call each callback in order since waitForTx is already invoked asynchronously + cb(nil, err) + } + } + return + } + + if len(callbacks) > 0 { + for _, cb := range callbacks { + // Call each callback in order since waitForTx is already invoked asynchronously + cb(rlyResp, nil) + } + } +} + +// waitForBlockInclusion will wait for a transaction to be included in a block, up to waitTimeout or context cancellation. +func (cc *CosmosProvider) waitForBlockInclusion( + ctx context.Context, + txHash []byte, + waitTimeout time.Duration, +) (*sdk.TxResponse, error) { + exitAfter := time.After(waitTimeout) + for { + select { + case <-exitAfter: + return nil, fmt.Errorf("timed out after: %d; %w", waitTimeout, ErrTimeoutAfterWaitingForTxBroadcast) + // This fixed poll is fine because it's only for logging and updating prometheus metrics currently. + case <-time.After(time.Millisecond * 100): + res, err := cc.RPCClient.Tx(ctx, txHash, false) + if err == nil { + return cc.mkTxResult(res) + } + if strings.Contains(err.Error(), "transaction indexing is disabled") { + return nil, fmt.Errorf("cannot determine success/failure of tx because transaction indexing is disabled on rpc url") + } + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} + +// mkTxResult decodes a comet transaction into an SDK TxResponse. +func (cc *CosmosProvider) mkTxResult(resTx *coretypes.ResultTx) (*sdk.TxResponse, error) { + txBz, err := cc.Cdc.TxConfig.TxDecoder()(resTx.Tx) + if err != nil { + return nil, err + } + + p, ok := txBz.(intoAny) + if !ok { + return nil, fmt.Errorf("expecting a type implementing intoAny, got: %T", txBz) + } + + return sdk.NewResponseResultTx(resTx, p.AsAny(), ""), nil +} + +func sdkErrorToGRPCError(resp abci.ResponseQuery) error { + switch resp.Code { + case legacyerrors.ErrInvalidRequest.ABCICode(): + return status.Error(codes.InvalidArgument, resp.Log) + case legacyerrors.ErrUnauthorized.ABCICode(): + return status.Error(codes.Unauthenticated, resp.Log) + case legacyerrors.ErrKeyNotFound.ABCICode(): + return status.Error(codes.NotFound, resp.Log) + default: + return status.Error(codes.Unknown, resp.Log) + } +} + +// isQueryStoreWithProof expects a format like /// +// queryType must be "store" and subPath must be "key" to require a proof. +func isQueryStoreWithProof(path string) bool { + if !strings.HasPrefix(path, "/") { + return false + } + + paths := strings.SplitN(path[1:], "/", 3) + + switch { + case len(paths) != 3: + return false + case paths[0] != "store": + return false + case rootmulti.RequireProof("/" + paths[2]): + return true + } + + return false +} + +// sdkError will return the Cosmos SDK registered error for a given codeSpace/code combo if registered, otherwise nil. +func (cc *CosmosProvider) sdkError(codeSpace string, code uint32) error { + // ABCIError will return an error other than "unknown" if syncRes.Code is a registered error in syncRes.CodeSpace + // This catches all the sdk errors https://github.com/cosmos/cosmos-sdk/blob/f10f5e5974d2ecbf9efc05bc0bfe1c99fdeed4b6/types/errors/errors.go + err := errors.Unwrap(sdkerrors.ABCIError(codeSpace, code, "error broadcasting transaction")) + if err == nil { + return nil + } + + if err.Error() != errUnknown { + return err + } + return nil +} + +func parseEventsFromTxResponse(resp *sdk.TxResponse) []RelayerEvent { + var events []RelayerEvent + + if resp == nil { + return events + } + + for _, logs := range resp.Logs { + for _, event := range logs.Events { + attributes := make(map[string]string) + for _, attribute := range event.Attributes { + attributes[attribute.Key] = attribute.Value + } + events = append(events, RelayerEvent{ + EventType: event.Type, + Attributes: attributes, + }) + } + } + + // After SDK v0.50, indexed events are no longer provided in the logs on + // transaction execution, the response events can be directly used + if len(events) == 0 { + for _, event := range resp.Events { + attributes := make(map[string]string) + for _, attribute := range event.Attributes { + attributes[attribute.Key] = attribute.Value + } + events = append(events, RelayerEvent{ + EventType: event.Type, + Attributes: attributes, + }) + } + } + + return events +} + +// handleAccountSequenceMismatchError will parse the error string, e.g.: +// "account sequence mismatch, expected 10, got 9: incorrect account sequence" +// and update the next account sequence with the expected value. +func (cc *CosmosProvider) handleAccountSequenceMismatchError(sequenceGuard *WalletState, err error) { + if sequenceGuard == nil { + panic("sequence guard not configured") + } + + matches := accountSeqRegex.FindStringSubmatch(err.Error()) + if len(matches) == 0 { + return + } + nextSeq, err := strconv.ParseUint(matches[1], 10, 64) + if err != nil { + return + } + sequenceGuard.NextAccountSequence = nextSeq +} + +// SendMessagesToMempool simulates and broadcasts a transaction with the given msgs and memo. +// This method will return once the transaction has entered the mempool. +// In an async goroutine, will wait for the tx to be included in the block unless asyncCtx exits. +// If there is no error broadcasting, the asyncCallback will be called with success/failure of the wait for block inclusion. +func (cc *CosmosProvider) SendMessagesToMempool( + ctx context.Context, + msgs []RelayerMessage, + memo string, + asyncCtx context.Context, + asyncCallbacks []func(*RelayerTxResponse, error), +) error { + txSignerKey := cc.PCfg.Key + + sequenceGuard := ensureSequenceGuard(cc, txSignerKey) + sequenceGuard.Mu.Lock() + defer sequenceGuard.Mu.Unlock() + + txBytes, sequence, _, err := cc.buildMessages(ctx, msgs, memo, 0, txSignerKey, sequenceGuard) + if err != nil { + // Account sequence mismatch errors can happen on the simulated transaction also. + if strings.Contains(err.Error(), legacyerrors.ErrWrongSequence.Error()) { + cc.handleAccountSequenceMismatchError(sequenceGuard, err) + } + + return err + } + + if err := cc.broadcastTx(ctx, txBytes, asyncCtx, defaultBroadcastWaitTimeout, asyncCallbacks); err != nil { + if strings.Contains(err.Error(), legacyerrors.ErrWrongSequence.Error()) { + cc.handleAccountSequenceMismatchError(sequenceGuard, err) + } + + return err + } + + // we had a successful tx broadcast with this sequence, so update it to the next + cc.updateNextAccountSequence(sequenceGuard, sequence+1) + return nil +} + +func (cc *CosmosProvider) updateNextAccountSequence(sequenceGuard *WalletState, seq uint64) { + if seq > sequenceGuard.NextAccountSequence { + sequenceGuard.NextAccountSequence = seq + } +} + +func (cc *CosmosProvider) buildMessages( + ctx context.Context, + msgs []RelayerMessage, + memo string, + gas uint64, + txSignerKey string, + sequenceGuard *WalletState, +) ( + txBytes []byte, + sequence uint64, + fees sdk.Coins, + err error, +) { + done := cc.SetSDKContext() + defer done() + + cMsgs := CosmosMsgs(msgs...) + + txf, err := cc.PrepareFactory(cc.TxFactory(), txSignerKey) + if err != nil { + return nil, 0, sdk.Coins{}, err + } + + if memo != "" { + txf = txf.WithMemo(memo) + } + + sequence = txf.Sequence() + cc.updateNextAccountSequence(sequenceGuard, sequence) + if sequence < sequenceGuard.NextAccountSequence { + sequence = sequenceGuard.NextAccountSequence + txf = txf.WithSequence(sequence) + } + + adjusted := gas + + if gas == 0 { + _, adjusted, err = cc.CalculateGas(ctx, txf, txSignerKey, cMsgs...) + + if err != nil { + return nil, 0, sdk.Coins{}, err + } + } + + // Set the gas amount on the transaction factory + txf = txf.WithGas(adjusted) + + // Build the transaction builder + txb, err := txf.BuildUnsignedTx(cMsgs...) + if err != nil { + return nil, 0, sdk.Coins{}, err + } + + if err = tx.Sign(ctx, txf, txSignerKey, txb, false); err != nil { + return nil, 0, sdk.Coins{}, err + } + + tx := txb.GetTx() + fees = tx.GetFee() + + // Generate the transaction bytes + txBytes, err = cc.Cdc.TxConfig.TxEncoder()(tx) + if err != nil { + return nil, 0, sdk.Coins{}, err + } + + return txBytes, txf.Sequence(), fees, nil +} + +// PrepareFactory mutates the tx factory with the appropriate account number, sequence number, and min gas settings. +func (cc *CosmosProvider) PrepareFactory(txf tx.Factory, signingKey string) (tx.Factory, error) { + var ( + err error + from sdk.AccAddress + num, seq uint64 + ) + + // Get key address and retry if fail + if err = retry.Do(func() error { + from, err = cc.GetKeyAddressForKey(signingKey) + if err != nil { + return err + } + return err + }, rtyAtt, rtyDel, rtyErr); err != nil { + return tx.Factory{}, err + } + + cliCtx := client.Context{}.WithClient(cc.RPCClient). + WithInterfaceRegistry(cc.Cdc.InterfaceRegistry). + WithChainID(cc.PCfg.ChainID). + WithCodec(cc.Cdc.Codec). + WithFromAddress(from) + + // Set the account number and sequence on the transaction factory and retry if fail + if err = retry.Do(func() error { + if err = txf.AccountRetriever().EnsureExists(cliCtx, from); err != nil { + return err + } + return err + }, rtyAtt, rtyDel, rtyErr); err != nil { + return txf, err + } + + // TODO: why this code? this may potentially require another query when we don't want one + initNum, initSeq := txf.AccountNumber(), txf.Sequence() + if initNum == 0 || initSeq == 0 { + if err = retry.Do(func() error { + num, seq, err = txf.AccountRetriever().GetAccountNumberSequence(cliCtx, from) + if err != nil { + return err + } + return err + }, rtyAtt, rtyDel, rtyErr); err != nil { + return txf, err + } + + if initNum == 0 { + txf = txf.WithAccountNumber(num) + } + + if initSeq == 0 { + txf = txf.WithSequence(seq) + } + } + + if cc.PCfg.MinGasAmount != 0 { + txf = txf.WithGas(cc.PCfg.MinGasAmount) + } + + if cc.PCfg.MaxGasAmount != 0 { + txf = txf.WithGas(cc.PCfg.MaxGasAmount) + } + + return txf, nil +} + +// TxFactory instantiates a new tx factory with the appropriate configuration settings for this chain. +func (cc *CosmosProvider) TxFactory() tx.Factory { + return tx.Factory{}. + WithAccountRetriever(cc). + WithChainID(cc.PCfg.ChainID). + WithTxConfig(cc.Cdc.TxConfig). + WithGasAdjustment(cc.PCfg.GasAdjustment). + WithGasPrices(cc.PCfg.GasPrices). + WithKeybase(cc.Keybase). + WithSignMode(cc.PCfg.SignMode()) +} + +// SignMode returns the SDK sign mode type reflective of the specified sign mode in the config file. +func (pc CosmosProviderConfig) SignMode() signing.SignMode { + signMode := signing.SignMode_SIGN_MODE_UNSPECIFIED + switch pc.SignModeStr { + case "direct": + signMode = signing.SignMode_SIGN_MODE_DIRECT + case "amino-json": + signMode = signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON + } + return signMode +} + +// CalculateGas simulates a tx to generate the appropriate gas settings before broadcasting a tx. +func (cc *CosmosProvider) CalculateGas(ctx context.Context, txf tx.Factory, signingKey string, msgs ...sdk.Msg) (txtypes.SimulateResponse, uint64, error) { + keyInfo, err := cc.Keybase.Key(signingKey) + if err != nil { + return txtypes.SimulateResponse{}, 0, err + } + + var txBytes []byte + if err := retry.Do(func() error { + var err error + txBytes, err = BuildSimTx(keyInfo, txf, msgs...) + if err != nil { + return err + } + return nil + }, retry.Context(ctx), rtyAtt, rtyDel, rtyErr); err != nil { + return txtypes.SimulateResponse{}, 0, err + } + + simQuery := abci.RequestQuery{ + Path: "/cosmos.tx.v1beta1.Service/Simulate", + Data: txBytes, + } + + var res abci.ResponseQuery + if err := retry.Do(func() error { + var err error + res, err = cc.QueryABCI(ctx, simQuery) + if err != nil { + return err + } + return nil + }, retry.Context(ctx), rtyAtt, rtyDel, rtyErr); err != nil { + return txtypes.SimulateResponse{}, 0, err + } + + var simRes txtypes.SimulateResponse + if err := simRes.Unmarshal(res.Value); err != nil { + return txtypes.SimulateResponse{}, 0, err + } + + gas, err := cc.AdjustEstimatedGas(simRes.GasInfo.GasUsed) + return simRes, gas, err +} + +// BuildSimTx creates an unsigned tx with an empty single signature and returns +// the encoded transaction or an error if the unsigned transaction cannot be built. +func BuildSimTx(info *keyring.Record, txf tx.Factory, msgs ...sdk.Msg) ([]byte, error) { + txb, err := txf.BuildUnsignedTx(msgs...) + if err != nil { + return nil, err + } + + var pk cryptotypes.PubKey // use default public key type + + pk, err = info.GetPubKey() + if err != nil { + return nil, err + } + + // Create an empty signature literal as the ante handler will populate with a + // sentinel pubKey. + sig := signing.SignatureV2{ + PubKey: pk, + Data: &signing.SingleSignatureData{ + SignMode: txf.SignMode(), + }, + Sequence: txf.Sequence(), + } + if err := txb.SetSignatures(sig); err != nil { + return nil, err + } + + protoProvider, ok := txb.(protoTxProvider) + if !ok { + return nil, fmt.Errorf("cannot simulate amino tx") + } + + simReq := txtypes.SimulateRequest{Tx: protoProvider.GetProtoTx()} + return simReq.Marshal() +} + +// AdjustEstimatedGas adjusts the estimated gas usage by multiplying it by the gas adjustment factor +// and return estimated gas is higher than max gas error. If the gas usage is zero, the adjusted gas +// is also zero. +func (cc *CosmosProvider) AdjustEstimatedGas(gasUsed uint64) (uint64, error) { + if gasUsed == 0 { + return gasUsed, nil + } + if cc.PCfg.MaxGasAmount > 0 && gasUsed > cc.PCfg.MaxGasAmount { + return 0, fmt.Errorf("estimated gas %d is higher than max gas %d", gasUsed, cc.PCfg.MaxGasAmount) + } + gas := cc.PCfg.GasAdjustment * float64(gasUsed) + if math.IsInf(gas, 1) { + return 0, fmt.Errorf("infinite gas used") + } + return uint64(gas), nil +} + +// protoTxProvider is a type which can provide a proto transaction. It is a +// workaround to get access to the wrapper TxBuilder's method GetProtoTx(). +type protoTxProvider interface { + GetProtoTx() *txtypes.Tx +} diff --git a/client/babylonclient/tx_test.go b/client/babylonclient/tx_test.go new file mode 100644 index 000000000..8fb8a9cb6 --- /dev/null +++ b/client/babylonclient/tx_test.go @@ -0,0 +1,80 @@ +// This file is derived from the Cosmos Relayer repository (https://github.com/cosmos/relayer), +// originally licensed under the Apache License, Version 2.0. + +package babylonclient + +import ( + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCosmosProvider_AdjustEstimatedGas(t *testing.T) { + testCases := []struct { + name string + gasUsed uint64 + gasAdjustment float64 + maxGasAmount uint64 + expectedGas uint64 + expectedErr error + }{ + { + name: "gas used is zero", + gasUsed: 0, + gasAdjustment: 1.0, + maxGasAmount: 0, + expectedGas: 0, + expectedErr: nil, + }, + { + name: "gas used is non-zero", + gasUsed: 50000, + gasAdjustment: 1.5, + maxGasAmount: 100000, + expectedGas: 75000, + expectedErr: nil, + }, + { + name: "gas used is infinite", + gasUsed: 10000, + gasAdjustment: math.Inf(1), + maxGasAmount: 0, + expectedGas: 0, + expectedErr: fmt.Errorf("infinite gas used"), + }, + { + name: "gas used is non-zero with zero max gas amount as default", + gasUsed: 50000, + gasAdjustment: 1.5, + maxGasAmount: 0, + expectedGas: 75000, + expectedErr: nil, + }, + { + name: "estimated gas is higher than max gas", + gasUsed: 50000, + gasAdjustment: 1.5, + maxGasAmount: 70000, + expectedGas: 75000, + expectedErr: fmt.Errorf("estimated gas 75000 is higher than max gas 70000"), + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + cc := &CosmosProvider{PCfg: CosmosProviderConfig{ + GasAdjustment: tc.gasAdjustment, + MaxGasAmount: tc.maxGasAmount, + }} + adjustedGas, err := cc.AdjustEstimatedGas(tc.gasUsed) + if err != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + } else { + require.Equal(t, adjustedGas, tc.expectedGas) + } + }) + } +} diff --git a/client/client/client.go b/client/client/client.go index e641c96bc..bf1df9444 100644 --- a/client/client/client.go +++ b/client/client/client.go @@ -1,26 +1,29 @@ package client import ( - "context" + "github.com/babylonlabs-io/babylon/client/babylonclient" "time" bbn "github.com/babylonlabs-io/babylon/app" "github.com/babylonlabs-io/babylon/client/config" "github.com/babylonlabs-io/babylon/client/query" rpchttp "github.com/cometbft/cometbft/rpc/client/http" - "github.com/cosmos/relayer/v2/relayer/chains/cosmos" "go.uber.org/zap" ) type Client struct { *query.QueryClient - provider *cosmos.CosmosProvider + provider *babylonclient.CosmosProvider timeout time.Duration logger *zap.Logger cfg *config.BabylonConfig } +func (c *Client) Provider() *babylonclient.CosmosProvider { + return c.provider +} + func New(cfg *config.BabylonConfig, logger *zap.Logger) (*Client, error) { var ( zapLogger *zap.Logger @@ -42,34 +45,25 @@ func New(cfg *config.BabylonConfig, logger *zap.Logger) (*Client, error) { } provider, err := cfg.ToCosmosProviderConfig().NewProvider( - zapLogger, "", // TODO: set home path - true, "babylon", ) if err != nil { return nil, err } - cp := provider.(*cosmos.CosmosProvider) + cp := provider.(*babylonclient.CosmosProvider) cp.PCfg.KeyDirectory = cfg.KeyDirectory // Create tmp Babylon app to retrieve and register codecs // Need to override this manually as otherwise option from config is ignored - encCfg := bbn.GetEncodingConfig() - cp.Cdc = cosmos.Codec{ - InterfaceRegistry: encCfg.InterfaceRegistry, - Marshaler: encCfg.Codec, - TxConfig: encCfg.TxConfig, - Amino: encCfg.Amino, - } + cp.Cdc = bbn.GetEncodingConfig() // initialise Cosmos provider // NOTE: this will create a RPC client. The RPC client will be used for // submitting txs and making ad hoc queries. It won't create WebSocket // connection with Babylon node - err = cp.Init(context.Background()) - if err != nil { + if err = cp.Init(); err != nil { return nil, err } diff --git a/client/client/tx.go b/client/client/tx.go index 891738002..c2ce56936 100644 --- a/client/client/tx.go +++ b/client/client/tx.go @@ -3,6 +3,7 @@ package client import ( "context" "fmt" + "github.com/babylonlabs-io/babylon/client/babylonclient" "sync" signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" @@ -21,16 +22,14 @@ import ( txtypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - "github.com/cosmos/relayer/v2/relayer/chains/cosmos" - pv "github.com/cosmos/relayer/v2/relayer/provider" "go.uber.org/zap" ) // ToProviderMsgs converts a list of sdk.Msg to a list of provider.RelayerMessage -func ToProviderMsgs(msgs []sdk.Msg) []pv.RelayerMessage { - relayerMsgs := []pv.RelayerMessage{} +func ToProviderMsgs(msgs []sdk.Msg) []babylonclient.RelayerMessage { + relayerMsgs := make([]babylonclient.RelayerMessage, 0, len(msgs)) for _, m := range msgs { - relayerMsgs = append(relayerMsgs, cosmos.NewCosmosMessage(m, func(signer string) {})) + relayerMsgs = append(relayerMsgs, babylonclient.NewCosmosMessage(m, func(signer string) {})) } return relayerMsgs } @@ -48,7 +47,7 @@ func (c *Client) SendMsgsToMempool(ctx context.Context, msgs []sdk.Msg) error { if err := retry.Do(func() error { var sendMsgErr error krErr := c.accessKeyWithLock(func() { - sendMsgErr = c.provider.SendMessagesToMempool(ctx, relayerMsgs, "", ctx, []func(*pv.RelayerTxResponse, error){}) + sendMsgErr = c.provider.SendMessagesToMempool(ctx, relayerMsgs, "", ctx, []func(*babylonclient.RelayerTxResponse, error){}) }) if krErr != nil { c.logger.Error("unrecoverable err when submitting the tx, skip retrying", zap.Error(krErr)) @@ -67,21 +66,21 @@ func (c *Client) SendMsgsToMempool(ctx context.Context, msgs []sdk.Msg) error { // ReliablySendMsg reliable sends a message to the chain. // It utilizes a file lock as well as a keyring lock to ensure atomic access. // TODO: needs tests -func (c *Client) ReliablySendMsg(ctx context.Context, msg sdk.Msg, expectedErrors []*errors.Error, unrecoverableErrors []*errors.Error) (*pv.RelayerTxResponse, error) { +func (c *Client) ReliablySendMsg(ctx context.Context, msg sdk.Msg, expectedErrors []*errors.Error, unrecoverableErrors []*errors.Error) (*babylonclient.RelayerTxResponse, error) { return c.ReliablySendMsgs(ctx, []sdk.Msg{msg}, expectedErrors, unrecoverableErrors) } // ReliablySendMsgs reliably sends a list of messages to the chain. // It utilizes a file lock as well as a keyring lock to ensure atomic access. // TODO: needs tests -func (c *Client) ReliablySendMsgs(ctx context.Context, msgs []sdk.Msg, expectedErrors []*errors.Error, unrecoverableErrors []*errors.Error) (*pv.RelayerTxResponse, error) { +func (c *Client) ReliablySendMsgs(ctx context.Context, msgs []sdk.Msg, expectedErrors []*errors.Error, unrecoverableErrors []*errors.Error) (*babylonclient.RelayerTxResponse, error) { var ( - rlyResp *pv.RelayerTxResponse + rlyResp *babylonclient.RelayerTxResponse callbackErr error wg sync.WaitGroup ) - callback := func(rtr *pv.RelayerTxResponse, err error) { + callback := func(rtr *babylonclient.RelayerTxResponse, err error) { rlyResp = rtr callbackErr = err wg.Done() @@ -96,7 +95,7 @@ func (c *Client) ReliablySendMsgs(ctx context.Context, msgs []sdk.Msg, expectedE if err := retry.Do(func() error { var sendMsgErr error krErr := c.accessKeyWithLock(func() { - sendMsgErr = c.provider.SendMessagesToMempool(ctx, relayerMsgs, "", ctx, []func(*pv.RelayerTxResponse, error){callback}) + sendMsgErr = c.provider.SendMessagesToMempool(ctx, relayerMsgs, "", ctx, []func(*babylonclient.RelayerTxResponse, error){callback}) }) if krErr != nil { c.logger.Error("unrecoverable err when submitting the tx, skip retrying", zap.Error(krErr)) @@ -147,9 +146,9 @@ func (c *Client) ReliablySendMsgs(ctx context.Context, msgs []sdk.Msg, expectedE // ReliablySendMsgsWithSigner reliably sends a list of messages to the chain. // It utilizes the signer private key to sign all msgs -func (c *Client) ReliablySendMsgsWithSigner(ctx context.Context, signerAddr sdk.AccAddress, signerPvKey *secp256k1.PrivKey, msgs []sdk.Msg, expectedErrors []*errors.Error, unrecoverableErrors []*errors.Error) (*pv.RelayerTxResponse, error) { +func (c *Client) ReliablySendMsgsWithSigner(ctx context.Context, signerAddr sdk.AccAddress, signerPvKey *secp256k1.PrivKey, msgs []sdk.Msg, expectedErrors []*errors.Error, unrecoverableErrors []*errors.Error) (*babylonclient.RelayerTxResponse, error) { var ( - rlyResp *pv.RelayerTxResponse + rlyResp *babylonclient.RelayerTxResponse callbackErr error wg sync.WaitGroup ) @@ -209,9 +208,9 @@ func (c *Client) SendMessageWithSigner( ctx context.Context, signerAddr sdk.AccAddress, signerPvKey *secp256k1.PrivKey, - relayerMsgs []pv.RelayerMessage, + relayerMsgs []babylonclient.RelayerMessage, ) (result *coretypes.ResultBroadcastTx, err error) { - cMsgs := cosmos.CosmosMsgs(relayerMsgs...) + cMsgs := babylonclient.CosmosMsgs(relayerMsgs...) var ( num, seq uint64 ) @@ -220,7 +219,7 @@ func (c *Client) SendMessageWithSigner( cliCtx := client.Context{}.WithClient(cc.RPCClient). WithInterfaceRegistry(cc.Cdc.InterfaceRegistry). WithChainID(cc.PCfg.ChainID). - WithCodec(cc.Cdc.Marshaler). + WithCodec(cc.Cdc.Codec). WithFromAddress(signerAddr) txf := cc.TxFactory() @@ -261,10 +260,6 @@ func (c *Client) SendMessageWithSigner( if cc.PCfg.MaxGasAmount != 0 { txf = txf.WithGas(cc.PCfg.MaxGasAmount) } - txf, err = cc.SetWithExtensionOptions(txf) - if err != nil { - return nil, err - } // txf ready _, adjusted, err := c.CalculateGas(ctx, txf, signerPvKey.PubKey(), cMsgs...) @@ -285,7 +280,7 @@ func (c *Client) SendMessageWithSigner( // c.LogFailedTx(nil, err, msgs) // Force encoding in the chain specific address for _, msg := range cMsgs { - cc.Cdc.Marshaler.MustMarshalJSON(msg) + cc.Cdc.Codec.MustMarshalJSON(msg) } if err := Sign(ctx, txf, signerPvKey, txb, cc.Cdc.TxConfig.SignModeHandler(), false); err != nil { return nil, err @@ -486,11 +481,11 @@ func Sign( // - we do not support cancellation of submitting messages // - the only timeout is the block inclusion timeout i.e block-timeout // TODO: To properly support cancellation we need to expose ctx in our client calls -func (c *Client) InsertBTCSpvProof(ctx context.Context, msg *btcctypes.MsgInsertBTCSpvProof) (*pv.RelayerTxResponse, error) { +func (c *Client) InsertBTCSpvProof(ctx context.Context, msg *btcctypes.MsgInsertBTCSpvProof) (*babylonclient.RelayerTxResponse, error) { return c.ReliablySendMsg(ctx, msg, []*errors.Error{}, []*errors.Error{}) } -func (c *Client) InsertHeaders(ctx context.Context, msg *btclctypes.MsgInsertHeaders) (*pv.RelayerTxResponse, error) { +func (c *Client) InsertHeaders(ctx context.Context, msg *btclctypes.MsgInsertHeaders) (*babylonclient.RelayerTxResponse, error) { return c.ReliablySendMsg(ctx, msg, []*errors.Error{}, []*errors.Error{}) } diff --git a/client/config/babylon_config.go b/client/config/babylon_config.go index 680e5fb1a..39597dde5 100644 --- a/client/config/babylon_config.go +++ b/client/config/babylon_config.go @@ -2,7 +2,7 @@ package config import ( "fmt" - "github.com/cosmos/relayer/v2/relayer/chains/cosmos" + "github.com/babylonlabs-io/babylon/client/babylonclient" "net/url" "os" "path/filepath" @@ -42,8 +42,8 @@ func (cfg *BabylonConfig) Validate() error { return nil } -func (cfg *BabylonConfig) ToCosmosProviderConfig() cosmos.CosmosProviderConfig { - return cosmos.CosmosProviderConfig{ +func (cfg *BabylonConfig) ToCosmosProviderConfig() babylonclient.CosmosProviderConfig { + return babylonclient.CosmosProviderConfig{ Key: cfg.Key, ChainID: cfg.ChainID, RPCAddr: cfg.RPCAddr, diff --git a/client/config/babylon_query_config.go b/client/config/babylon_query_config.go index 5a378f221..ff4026cb4 100644 --- a/client/config/babylon_query_config.go +++ b/client/config/babylon_query_config.go @@ -6,7 +6,7 @@ import ( "time" ) -// BabylonConfig defines configuration for the Babylon query client +// BabylonQueryConfig defines configuration for the Babylon query client type BabylonQueryConfig struct { RPCAddr string `mapstructure:"rpc-addr"` Timeout time.Duration `mapstructure:"timeout"` diff --git a/go.mod b/go.mod index 0621a60af..79b68d339 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/cometbft/cometbft-db v0.15.0 github.com/cosmos/cosmos-sdk v0.50.11 github.com/cosmos/ibc-go/modules/light-clients/08-wasm v0.0.0-20240429153234-e1e6da7e4ead - github.com/cosmos/relayer/v2 v2.5.3 github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/pkg/errors v0.9.1 @@ -159,13 +158,10 @@ require ( github.com/cockroachdb/pebble v1.1.2 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/ibc-go/modules/apps/callbacks v0.2.1-0.20231113120333-342c00b0f8bd // indirect github.com/cosmos/ics23/go v0.11.0 // indirect - github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/creachadair/atomicfile v0.3.1 // indirect github.com/creachadair/tomledit v0.0.24 // indirect github.com/danieljoos/wincred v1.1.2 // indirect @@ -177,14 +173,13 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/dot v1.6.2 // indirect - github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/ethereum/go-ethereum v1.13.14 // indirect github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gobwas/ws v1.1.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -205,7 +200,6 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/holiman/uint256 v1.2.4 // indirect github.com/huandu/skiplist v1.2.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -217,7 +211,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect @@ -234,10 +227,8 @@ require ( github.com/shamaton/msgpack/v2 v2.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/strangelove-ventures/cometbft-client v0.1.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tidwall/btree v1.7.0 // indirect - github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -260,7 +251,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect pgregory.net/rapid v1.1.0 // indirect - rsc.io/tmplfunc v0.0.3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 290802642..c25346d86 100644 --- a/go.sum +++ b/go.sum @@ -241,10 +241,6 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= -github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= 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.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= @@ -372,10 +368,6 @@ github.com/cometbft/cometbft v0.38.15 h1:5veFd8k1uXM27PBg9sMO3hAfRJ3vbh4OmmLf6cV github.com/cometbft/cometbft v0.38.15/go.mod h1:+wh6ap6xctVG+JOHwbl8pPKZ0GeqdPYqISu7F4b43cQ= github.com/cometbft/cometbft-db v0.15.0 h1:VLtsRt8udD4jHCyjvrsTBpgz83qne5hnL245AcPJVRk= github.com/cometbft/cometbft-db v0.15.0/go.mod h1:EBrFs1GDRiTqrWXYi4v90Awf/gcdD5ExzdPbg4X8+mk= -github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= -github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= 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/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -413,14 +405,8 @@ github.com/cosmos/keyring v1.2.0 h1:8C1lBP9xhImmIabyXW4c3vFjjLiBdGCmfLUfeZlV1Yo= github.com/cosmos/keyring v1.2.0/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8= -github.com/cosmos/relayer/v2 v2.5.3 h1:Cuhp4MZ8OwWtGf4/UU4HqpEdEDkCvd42uHvY5MK5MpE= -github.com/cosmos/relayer/v2 v2.5.3/go.mod h1:4N4Dd42G1sKd7zUqOonrGPC2xgdY59T8KevjKodoLUI= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= -github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= -github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= -github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creachadair/atomicfile v0.3.1 h1:yQORkHjSYySh/tv5th1dkKcn02NEW5JleB84sjt+W4Q= github.com/creachadair/atomicfile v0.3.1/go.mod h1:mwfrkRxFKwpNAflYZzytbSwxvbK6fdGRRlp0KEQc0qU= github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6QKQDhQ= @@ -484,10 +470,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= -github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.13.14 h1:EwiY3FZP94derMCIam1iW4HFVrSgIcpsu0HwTQtm6CQ= -github.com/ethereum/go-ethereum v1.13.14/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= @@ -504,8 +486,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= -github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -537,8 +517,6 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -567,8 +545,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= @@ -670,7 +646,6 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -767,10 +742,6 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU= github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= -github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= @@ -841,8 +812,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -872,8 +841,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= @@ -891,9 +858,6 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= -github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -929,8 +893,6 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -1027,8 +989,6 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1054,8 +1014,6 @@ github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6v github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shamaton/msgpack/v2 v2.2.0 h1:IP1m01pHwCrMa6ZccP9B3bqxEMKMSmMVAVKk54g3L/Y= github.com/shamaton/msgpack/v2 v2.2.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -1084,8 +1042,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/strangelove-ventures/cometbft-client v0.1.1 h1:chBZCTcTOGl3i+CjXWU+kIJZ7s6mY2uZ1gzwr7W2f0g= -github.com/strangelove-ventures/cometbft-client v0.1.1/go.mod h1:aVposiPW9FOUeAeJ7JjJRdE3g+L6i8YDxFn6Cv6+Az4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1118,14 +1074,8 @@ github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2l github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= -github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= @@ -1836,8 +1786,6 @@ pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=