diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b6ed5d5f7..cbe903220d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ to geth v1.14 with tracing updates and new StateDB methods. setup. Namely, blobs (Cancun) and Verkle additions for zkEVM. - The jump to v1.14 was necessary to use an up-to-date "cockroach/pebble" DB dependency and leverage new generics features added in Go 1.23+. +- [#2289](https://github.com/NibiruChain/nibiru/pull/2289) - fix(eth-rpc): error propagation fixes and tests for the methods exposed by Nibiru's EVM JSON-RPC - [#2288](https://github.com/NibiruChain/nibiru/pull/2288) - chore(ci): add workflow to check for missing upgrade handler - [#2278](https://github.com/NibiruChain/nibiru/pull/2278) - chore: migrate to cosmossdk.io/mathLegacyDec and cosmossdk.io/math.Int diff --git a/eth/assert.go b/eth/assert.go index 09e5bd3284..57c47ae9e2 100644 --- a/eth/assert.go +++ b/eth/assert.go @@ -4,8 +4,8 @@ package eth import ( "bytes" - errorsmod "cosmossdk.io/errors" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkioerrors "cosmossdk.io/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" ) @@ -22,8 +22,8 @@ func IsZeroAddress(address string) bool { // ValidateAddress returns an error if the provided string is either not a hex formatted string address func ValidateAddress(address string) error { if !common.IsHexAddress(address) { - return errorsmod.Wrapf( - errortypes.ErrInvalidAddress, "address '%s' is not a valid ethereum hex address", + return sdkioerrors.Wrapf( + sdkerrors.ErrInvalidAddress, "address '%s' is not a valid ethereum hex address", address, ) } @@ -34,8 +34,8 @@ func ValidateAddress(address string) error { // formatted string address or is equal to zero func ValidateNonZeroAddress(address string) error { if IsZeroAddress(address) { - return errorsmod.Wrapf( - errortypes.ErrInvalidAddress, "address '%s' must not be zero", + return sdkioerrors.Wrapf( + sdkerrors.ErrInvalidAddress, "address '%s' must not be zero", address, ) } diff --git a/eth/crypto/ethsecp256k1/ethsecp256k1.go b/eth/crypto/ethsecp256k1/ethsecp256k1.go index 7a341931e2..5b2b2a56b5 100644 --- a/eth/crypto/ethsecp256k1/ethsecp256k1.go +++ b/eth/crypto/ethsecp256k1/ethsecp256k1.go @@ -8,11 +8,11 @@ import ( "crypto/subtle" "fmt" - errorsmod "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" tmcrypto "github.com/cometbft/cometbft/crypto" "github.com/cosmos/cosmos-sdk/codec" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/crypto" "github.com/NibiruChain/nibiru/v2/eth/eip712" @@ -186,7 +186,7 @@ func (pubKey PubKey) MarshalAmino() ([]byte, error) { // UnmarshalAmino overrides Amino binary marshaling. func (pubKey *PubKey) UnmarshalAmino(bz []byte) error { if len(bz) != PubKeySize { - return errorsmod.Wrapf(errortypes.ErrInvalidPubKey, "invalid pubkey size, expected %d, got %d", PubKeySize, len(bz)) + return sdkioerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "invalid pubkey size, expected %d, got %d", PubKeySize, len(bz)) } pubKey.Key = bz diff --git a/eth/eip712/eip712_legacy.go b/eth/eip712/eip712_legacy.go index b50f10e5fd..75647c7466 100644 --- a/eth/eip712/eip712_legacy.go +++ b/eth/eip712/eip712_legacy.go @@ -9,12 +9,12 @@ import ( "strings" "time" - errorsmod "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" gethmath "github.com/ethereum/go-ethereum/common/math" @@ -41,7 +41,7 @@ func LegacyWrapTxToTypedData( txData := make(map[string]any) if err := json.Unmarshal(data, &txData); err != nil { - return apitypes.TypedData{}, errorsmod.Wrap(errortypes.ErrJSONUnmarshal, "failed to JSON unmarshal data") + return apitypes.TypedData{}, sdkioerrors.Wrap(sdkerrors.ErrJSONUnmarshal, "failed to JSON unmarshal data") } domain := apitypes.TypedDataDomain{ @@ -60,7 +60,7 @@ func LegacyWrapTxToTypedData( if feeDelegation != nil { feeInfo, ok := txData["fee"].(map[string]any) if !ok { - return apitypes.TypedData{}, errorsmod.Wrap(errortypes.ErrInvalidType, "cannot parse fee from tx data") + return apitypes.TypedData{}, sdkioerrors.Wrap(sdkerrors.ErrInvalidType, "cannot parse fee from tx data") } feeInfo["feePayer"] = feeDelegation.FeePayer.String() @@ -369,7 +369,7 @@ func jsonNameFromTag(tag reflect.StructTag) string { func UnpackAny(cdc codectypes.AnyUnpacker, field reflect.Value) (reflect.Type, reflect.Value, error) { anyData, ok := field.Interface().(*codectypes.Any) if !ok { - return nil, reflect.Value{}, errorsmod.Wrapf(errortypes.ErrPackAny, "%T", field.Interface()) + return nil, reflect.Value{}, sdkioerrors.Wrapf(sdkerrors.ErrPackAny, "%T", field.Interface()) } anyWrapper := &CosmosAnyWrapper{ @@ -377,7 +377,7 @@ func UnpackAny(cdc codectypes.AnyUnpacker, field reflect.Value) (reflect.Type, r } if err := cdc.UnpackAny(anyData, &anyWrapper.Value); err != nil { - return nil, reflect.Value{}, errorsmod.Wrap(err, "failed to unpack Any in msg struct") + return nil, reflect.Value{}, sdkioerrors.Wrap(err, "failed to unpack Any in msg struct") } fieldType := reflect.TypeOf(anyWrapper) diff --git a/eth/eip712/message.go b/eth/eip712/message.go index 176d90b564..cdef601b1e 100644 --- a/eth/eip712/message.go +++ b/eth/eip712/message.go @@ -4,8 +4,8 @@ package eip712 import ( "fmt" - errorsmod "cosmossdk.io/errors" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkioerrors "cosmossdk.io/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -31,12 +31,12 @@ func createEIP712MessagePayload(data []byte) (eip712MessagePayload, error) { payload, numPayloadMsgs, err := FlattenPayloadMessages(basicPayload) if err != nil { - return eip712MessagePayload{}, errorsmod.Wrap(err, "failed to flatten payload JSON messages") + return eip712MessagePayload{}, sdkioerrors.Wrap(err, "failed to flatten payload JSON messages") } message, ok := payload.Value().(map[string]any) if !ok { - return eip712MessagePayload{}, errorsmod.Wrap(errortypes.ErrInvalidType, "failed to parse JSON as map") + return eip712MessagePayload{}, sdkioerrors.Wrap(sdkerrors.ErrInvalidType, "failed to parse JSON as map") } messagePayload := eip712MessagePayload{ @@ -52,13 +52,13 @@ func createEIP712MessagePayload(data []byte) (eip712MessagePayload, error) { // a JSON object, then makes sure the JSON is an object. func unmarshalBytesToJSONObject(data []byte) (gjson.Result, error) { if !gjson.ValidBytes(data) { - return gjson.Result{}, errorsmod.Wrap(errortypes.ErrJSONUnmarshal, "invalid JSON received") + return gjson.Result{}, sdkioerrors.Wrap(sdkerrors.ErrJSONUnmarshal, "invalid JSON received") } payload := gjson.ParseBytes(data) if !payload.IsObject() { - return gjson.Result{}, errorsmod.Wrap(errortypes.ErrJSONUnmarshal, "failed to JSON unmarshal data as object") + return gjson.Result{}, sdkioerrors.Wrap(sdkerrors.ErrJSONUnmarshal, "failed to JSON unmarshal data as object") } return payload, nil @@ -96,11 +96,11 @@ func getPayloadMessages(payload gjson.Result) ([]gjson.Result, error) { rawMsgs := payload.Get(payloadMsgsField) if !rawMsgs.Exists() { - return nil, errorsmod.Wrap(errortypes.ErrInvalidRequest, "no messages found in payload, unable to parse") + return nil, sdkioerrors.Wrap(sdkerrors.ErrInvalidRequest, "no messages found in payload, unable to parse") } if !rawMsgs.IsArray() { - return nil, errorsmod.Wrap(errortypes.ErrInvalidRequest, "expected type array of messages, cannot parse") + return nil, sdkioerrors.Wrap(sdkerrors.ErrInvalidRequest, "expected type array of messages, cannot parse") } return rawMsgs.Array(), nil @@ -112,14 +112,14 @@ func payloadWithNewMessage(payload gjson.Result, msg gjson.Result, index int) (g field := msgFieldForIndex(index) if payload.Get(field).Exists() { - return gjson.Result{}, errorsmod.Wrapf( - errortypes.ErrInvalidRequest, + return gjson.Result{}, sdkioerrors.Wrapf( + sdkerrors.ErrInvalidRequest, "malformed payload received, did not expect to find key at field %v", field, ) } if !msg.IsObject() { - return gjson.Result{}, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "msg at index %d is not valid JSON: %v", index, msg) + return gjson.Result{}, sdkioerrors.Wrapf(sdkerrors.ErrInvalidRequest, "msg at index %d is not valid JSON: %v", index, msg) } newRaw, err := sjson.SetRaw(payload.Raw, field, msg.Raw) diff --git a/eth/eip712/types.go b/eth/eip712/types.go index 82c4dee9ff..86127022a2 100644 --- a/eth/eip712/types.go +++ b/eth/eip712/types.go @@ -10,8 +10,8 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" - errorsmod "cosmossdk.io/errors" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkioerrors "cosmossdk.io/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/tidwall/gjson" @@ -93,7 +93,7 @@ func addMsgTypesToRoot(eip712Types apitypes.Types, msgField string, msg gjson.Re defer doRecover(&err) if !msg.IsObject() { - return errorsmod.Wrapf(errortypes.ErrInvalidRequest, "message is not valid JSON, cannot parse types") + return sdkioerrors.Wrapf(sdkerrors.ErrInvalidRequest, "message is not valid JSON, cannot parse types") } msgRootType, err := msgRootType(msg) @@ -117,7 +117,7 @@ func msgRootType(msg gjson.Result) (string, error) { msgType := msg.Get(msgTypeField).Str if msgType == "" { // .Str is empty for arrays and objects - return "", errorsmod.Wrap(errortypes.ErrInvalidType, "malformed message type value, expected type string") + return "", sdkioerrors.Wrap(sdkerrors.ErrInvalidType, "malformed message type value, expected type string") } // Convert e.g. cosmos-sdk/MsgSend to TypeMsgSend @@ -152,7 +152,7 @@ func recursivelyAddTypesToRoot( // Must sort the JSON keys for deterministic type generation. sortedFieldNames, err := sortedJSONKeys(payload) if err != nil { - return "", errorsmod.Wrap(err, "unable to sort object keys") + return "", sdkioerrors.Wrap(err, "unable to sort object keys") } typeDef := typeDefForPrefix(prefix, rootType) @@ -224,7 +224,7 @@ func recursivelyAddTypesToRoot( // to be used for deterministic iteration. func sortedJSONKeys(json gjson.Result) ([]string, error) { if !json.IsObject() { - return nil, errorsmod.Wrap(errortypes.ErrInvalidType, "expected JSON map to parse") + return nil, sdkioerrors.Wrap(sdkerrors.ErrInvalidType, "expected JSON map to parse") } jsonMap := json.Map() @@ -299,7 +299,7 @@ func addTypesToRoot(typeMap apitypes.Types, typeDef string, types []apitypes.Typ indexAsDuplicate++ if indexAsDuplicate == maxDuplicateTypeDefs { - return "", errorsmod.Wrap(errortypes.ErrInvalidRequest, "exceeded maximum number of duplicates for a single type definition") + return "", sdkioerrors.Wrap(sdkerrors.ErrInvalidRequest, "exceeded maximum number of duplicates for a single type definition") } } @@ -380,7 +380,7 @@ func getEthTypeForJSON(json gjson.Result) string { func doRecover(err *error) { if r := recover(); r != nil { if e, ok := r.(error); ok { - e = errorsmod.Wrap(e, "panicked with error") + e = sdkioerrors.Wrap(e, "panicked with error") *err = e return } diff --git a/eth/errors.go b/eth/errors.go index 8f2ea84f46..26ec807fa1 100644 --- a/eth/errors.go +++ b/eth/errors.go @@ -1,14 +1,14 @@ package eth import ( - sdkerrors "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" ) var moduleErrorCodeIdx uint32 = 1 -func registerError(msg string) *sdkerrors.Error { +func registerError(msg string) *sdkioerrors.Error { moduleErrorCodeIdx += 1 - return sdkerrors.Register("eth", moduleErrorCodeIdx, msg) + return sdkioerrors.Register("eth", moduleErrorCodeIdx, msg) } // Module "sentinel" errors diff --git a/eth/indexer/evm_tx_indexer.go b/eth/indexer/evm_tx_indexer.go index 0b1ba8966f..947426aa3f 100644 --- a/eth/indexer/evm_tx_indexer.go +++ b/eth/indexer/evm_tx_indexer.go @@ -4,7 +4,7 @@ package indexer import ( "fmt" - errorsmod "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" dbm "github.com/cometbft/cometbft-db" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/libs/log" @@ -122,12 +122,12 @@ func (indexer *EVMTxIndexer) IndexBlock(block *tmtypes.Block, txResults []*abci. ethTxIndex++ if err := saveTxResult(indexer.clientCtx.Codec, batch, txHash, &txResult); err != nil { - return errorsmod.Wrapf(err, "IndexBlock %d", height) + return sdkioerrors.Wrapf(err, "IndexBlock %d", height) } } } if err := batch.Write(); err != nil { - return errorsmod.Wrapf(err, "IndexBlock %d, write batch", block.Height) + return sdkioerrors.Wrapf(err, "IndexBlock %d, write batch", block.Height) } return nil } @@ -146,14 +146,14 @@ func (indexer *EVMTxIndexer) FirstIndexedBlock() (int64, error) { func (indexer *EVMTxIndexer) GetByTxHash(hash common.Hash) (*eth.TxResult, error) { bz, err := indexer.db.Get(TxHashKey(hash)) if err != nil { - return nil, errorsmod.Wrapf(err, "GetByTxHash %s", hash.Hex()) + return nil, sdkioerrors.Wrapf(err, "GetByTxHash %s", hash.Hex()) } if len(bz) == 0 { return nil, fmt.Errorf("tx not found, hash: %s", hash.Hex()) } var txKey eth.TxResult if err := indexer.clientCtx.Codec.Unmarshal(bz, &txKey); err != nil { - return nil, errorsmod.Wrapf(err, "GetByTxHash %s", hash.Hex()) + return nil, sdkioerrors.Wrapf(err, "GetByTxHash %s", hash.Hex()) } return &txKey, nil } @@ -162,7 +162,7 @@ func (indexer *EVMTxIndexer) GetByTxHash(hash common.Hash) (*eth.TxResult, error func (indexer *EVMTxIndexer) GetByBlockAndIndex(blockNumber int64, txIndex int32) (*eth.TxResult, error) { bz, err := indexer.db.Get(TxIndexKey(blockNumber, txIndex)) if err != nil { - return nil, errorsmod.Wrapf(err, "GetByBlockAndIndex %d %d", blockNumber, txIndex) + return nil, sdkioerrors.Wrapf(err, "GetByBlockAndIndex %d %d", blockNumber, txIndex) } if len(bz) == 0 { return nil, fmt.Errorf("tx not found, block: %d, eth-index: %d", blockNumber, txIndex) @@ -186,7 +186,7 @@ func TxIndexKey(blockNumber int64, txIndex int32) []byte { func LoadLastBlock(db dbm.DB) (int64, error) { it, err := db.ReverseIterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) if err != nil { - return 0, errorsmod.Wrap(err, "LoadLastBlock") + return 0, sdkioerrors.Wrap(err, "LoadLastBlock") } defer it.Close() if !it.Valid() { @@ -199,7 +199,7 @@ func LoadLastBlock(db dbm.DB) (int64, error) { func LoadFirstBlock(db dbm.DB) (int64, error) { it, err := db.Iterator([]byte{KeyPrefixTxIndex}, []byte{KeyPrefixTxIndex + 1}) if err != nil { - return 0, errorsmod.Wrap(err, "LoadFirstBlock") + return 0, sdkioerrors.Wrap(err, "LoadFirstBlock") } defer it.Close() if !it.Valid() { @@ -213,7 +213,7 @@ func (indexer *EVMTxIndexer) CloseDBAndExit() error { indexer.logger.Info("Closing EVMTxIndexer DB") err := indexer.db.Close() if err != nil { - return errorsmod.Wrap(err, "CloseDBAndExit") + return sdkioerrors.Wrap(err, "CloseDBAndExit") } return nil } @@ -235,10 +235,10 @@ func isEthTx(tx sdk.Tx) bool { func saveTxResult(codec codec.Codec, batch dbm.Batch, txHash common.Hash, txResult *eth.TxResult) error { bz := codec.MustMarshal(txResult) if err := batch.Set(TxHashKey(txHash), bz); err != nil { - return errorsmod.Wrap(err, "set tx-hash key") + return sdkioerrors.Wrap(err, "set tx-hash key") } if err := batch.Set(TxIndexKey(txResult.Height, txResult.EthTxIndex), txHash.Bytes()); err != nil { - return errorsmod.Wrap(err, "set tx-index key") + return sdkioerrors.Wrap(err, "set tx-index key") } return nil } diff --git a/eth/rpc/backend/account_info.go b/eth/rpc/backend/account_info.go index a0c0cacebd..cee7ff990a 100644 --- a/eth/rpc/backend/account_info.go +++ b/eth/rpc/backend/account_info.go @@ -6,8 +6,7 @@ import ( "math" "math/big" - errorsmod "cosmossdk.io/errors" - + sdkioerrors "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -204,7 +203,7 @@ func (b *Backend) GetTransactionCount(address gethcommon.Address, blockNum rpc.B currentHeight := int64(bn) //#nosec G701 -- checked for int overflow already if height > currentHeight { - return &n, errorsmod.Wrapf( + return &n, sdkioerrors.Wrapf( sdkerrors.ErrInvalidHeight, "cannot query with height in the future (current: %d, queried: %d); please provide a valid height", currentHeight, height, diff --git a/eth/rpc/backend/backend_suite_test.go b/eth/rpc/backend/backend_suite_test.go index 1b70a1c081..5ed630d9a0 100644 --- a/eth/rpc/backend/backend_suite_test.go +++ b/eth/rpc/backend/backend_suite_test.go @@ -132,7 +132,10 @@ func (s *BackendSuite) SetupSuite() { } for _, tx := range s.SuccessfulTxs { - s.T().Logf("SuccessfulTx{ BlockNumber: %s, BlockHash: %s, TxHash: %s }", tx.BlockNumber, tx.BlockHash.Hex(), tx.Receipt.TxHash.Hex()) + s.T().Logf( + "SuccessfulTx{ BlockNumber: %s, BlockHash: %s, TxHash: %s }", + tx.BlockNumber, tx.BlockHash.Hex(), tx.Receipt.TxHash.Hex(), + ) } } diff --git a/eth/rpc/backend/blocks.go b/eth/rpc/backend/blocks.go index 80a8c469b1..1b62a83a3a 100644 --- a/eth/rpc/backend/blocks.go +++ b/eth/rpc/backend/blocks.go @@ -2,6 +2,7 @@ package backend import ( + "errors" "fmt" "math" "math/big" @@ -16,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" - "github.com/pkg/errors" "github.com/status-im/keycard-go/hexutils" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -34,50 +34,72 @@ func (b *Backend) BlockNumber() (hexutil.Uint64, error) { var header metadata.MD _, err := b.queryClient.Params(b.ctx, &evm.QueryParamsRequest{}, grpc.Header(&header)) if err != nil { - return hexutil.Uint64(0), err + return 0, fmt.Errorf("BlockNumberError: failed to query the EVM module params: %w", err) } blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader) if headerLen := len(blockHeightHeader); headerLen != 1 { - return 0, fmt.Errorf("unexpected '%s' gRPC header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, headerLen, 1) + return 0, fmt.Errorf( + "BlockNumberError: unexpected '%s' gRPC header length; got %d, expected: %d", + grpctypes.GRPCBlockHeightHeader, headerLen, 1) } height, err := strconv.ParseUint(blockHeightHeader[0], 10, 64) if err != nil { - return 0, fmt.Errorf("failed to parse block height: %w", err) + return 0, fmt.Errorf("BlockNumberError: failed to parse block height header: %w", err) } if height > math.MaxInt64 { - return 0, fmt.Errorf("block height %d is greater than max uint64", height) + return 0, fmt.Errorf("BlockNumberError: block height %d is greater than max int64", height) } return hexutil.Uint64(height), nil } +var ErrNilBlockSuccess = errors.New("block query succeeded, but the block was nil") + // GetBlockByNumber returns the JSON-RPC compatible Ethereum block identified by // block number. Depending on fullTx it either returns the full transaction // objects or if false only the hashes of the transactions. -func (b *Backend) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]any, error) { +func (b *Backend) GetBlockByNumber( + blockNum rpc.BlockNumber, + fullTx bool, +) (block map[string]any, err error) { + defer func() { + if err != nil { + b.logger.Debug("eth_getBlockByNumber failed", "error", err.Error()) + } + }() resBlock, err := b.TendermintBlockByNumber(blockNum) if err != nil { - return nil, nil + return nil, err } // return if requested block height is greater than the current one if resBlock == nil || resBlock.Block == nil { - return nil, nil + currentBlockNum, err := b.BlockNumber() + if err != nil { + return nil, err + //#nosec G701 -- checked for int overflow already + } else if blockNumI64 := blockNum.Int64(); blockNumI64 >= int64(currentBlockNum) { + return nil, fmt.Errorf("requested block number is too high: current block %d, requested block %d", + currentBlockNum, blockNumI64, + ) + } + return nil, fmt.Errorf("requested block %d: %w", blockNum, ErrNilBlockSuccess) } blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height) if err != nil { - b.logger.Debug("failed to fetch block result from Tendermint", "height", blockNum, "error", err.Error()) - return nil, nil + return nil, fmt.Errorf( + "failed to fetch block result from Tendermint: blockNumber %d: %w", blockNum, err, + ) } res, err := b.RPCBlockFromTendermintBlock(resBlock, blockRes, fullTx) if err != nil { - b.logger.Debug("GetEthBlockFromTendermint failed", "height", blockNum, "error", err.Error()) - return nil, err + return nil, fmt.Errorf( + "RPCBlockFromTendermintBlock error: blockNumber %d: %w", blockNum, err) } return res, nil @@ -85,27 +107,28 @@ func (b *Backend) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[s // GetBlockByHash returns the JSON-RPC compatible Ethereum block identified by // hash. -func (b *Backend) GetBlockByHash(hash gethcommon.Hash, fullTx bool) (map[string]any, error) { - resBlock, err := b.TendermintBlockByHash(hash) +func (b *Backend) GetBlockByHash( + blockHash gethcommon.Hash, + fullTx bool, +) (block map[string]any, err error) { + resBlock, err := b.TendermintBlockByHash(blockHash) if err != nil { return nil, err } if resBlock == nil { - // block not found - return nil, nil + return nil, fmt.Errorf("block not found: blockHash %s", blockHash.Hex()) } blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height) if err != nil { - b.logger.Debug("failed to fetch block result from Tendermint", "block-hash", hash.String(), "error", err.Error()) - return nil, nil + return nil, fmt.Errorf( + "failed to fetch block result from Tendermint: blockHash %s: %w", blockHash, err) } res, err := b.RPCBlockFromTendermintBlock(resBlock, blockRes, fullTx) if err != nil { - b.logger.Debug("GetEthBlockFromTendermint failed", "hash", hash, "error", err.Error()) - return nil, err + return nil, fmt.Errorf("RPCBlockFromTendermintBlock error: blockHash %s: %w", blockHash, err) } return res, nil @@ -113,21 +136,18 @@ func (b *Backend) GetBlockByHash(hash gethcommon.Hash, fullTx bool) (map[string] // GetBlockTransactionCountByHash returns the number of Ethereum transactions in // the block identified by hash. -func (b *Backend) GetBlockTransactionCountByHash(hash gethcommon.Hash) *hexutil.Uint { +func (b *Backend) GetBlockTransactionCountByHash(blockHash gethcommon.Hash) (*hexutil.Uint, error) { sc, ok := b.clientCtx.Client.(tmrpcclient.SignClient) if !ok { - b.logger.Error("invalid rpc client") + return nil, fmt.Errorf("invalid rpc client of type %T", b.clientCtx.Client) } - block, err := sc.BlockByHash(b.ctx, hash.Bytes()) + block, err := sc.BlockByHash(b.ctx, blockHash.Bytes()) if err != nil { - b.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error()) - return nil + return nil, fmt.Errorf("block not found: hash %s: %w", blockHash, err) } - if block.Block == nil { - b.logger.Debug("block not found", "hash", hash.Hex()) - return nil + return nil, fmt.Errorf("block not found: hash %s: %w", blockHash, ErrNilBlockSuccess) } return b.GetBlockTransactionCount(block) @@ -135,16 +155,14 @@ func (b *Backend) GetBlockTransactionCountByHash(hash gethcommon.Hash) *hexutil. // GetBlockTransactionCountByNumber returns the number of Ethereum transactions // in the block identified by number. -func (b *Backend) GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) *hexutil.Uint { +func (b *Backend) GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) (*hexutil.Uint, error) { block, err := b.TendermintBlockByNumber(blockNum) if err != nil { - b.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error()) - return nil + return nil, fmt.Errorf("block not found: height %d: %w", blockNum.Int64(), err) } if block.Block == nil { - b.logger.Debug("block not found", "height", blockNum.Int64()) - return nil + return nil, fmt.Errorf("block not found: height %d: TendermintBlockByNumber query succeeded but returned a nil block", blockNum.Int64()) } return b.GetBlockTransactionCount(block) @@ -152,15 +170,15 @@ func (b *Backend) GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) *he // GetBlockTransactionCount returns the number of Ethereum transactions in a // given block. -func (b *Backend) GetBlockTransactionCount(block *tmrpctypes.ResultBlock) *hexutil.Uint { +func (b *Backend) GetBlockTransactionCount(block *tmrpctypes.ResultBlock) (*hexutil.Uint, error) { blockRes, err := b.TendermintBlockResultByNumber(&block.Block.Height) if err != nil { - return nil + return nil, err } ethMsgs := b.EthMsgsFromTendermintBlock(block, blockRes) n := hexutil.Uint(len(ethMsgs)) - return &n + return &n, nil } // TendermintBlockByNumber returns a Tendermint-formatted block for a given @@ -177,13 +195,10 @@ func (b *Backend) TendermintBlockByNumber(blockNum rpc.BlockNumber) (*tmrpctypes } resBlock, err := b.clientCtx.Client.Block(b.ctx, &height) if err != nil { - b.logger.Debug("tendermint client failed to get block", "height", height, "error", err.Error()) - return nil, err + return nil, fmt.Errorf("block not found: tendermint client failed to get block %d: %w", height, err) } - if resBlock.Block == nil { - b.logger.Debug("TendermintBlockByNumber block not found", "height", height) - return nil, nil + return nil, fmt.Errorf("block not found: block number %d: %w", height, ErrNilBlockSuccess) } return resBlock, nil @@ -194,7 +209,7 @@ func (b *Backend) TendermintBlockByNumber(blockNum rpc.BlockNumber) (*tmrpctypes func (b *Backend) TendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error) { sc, ok := b.clientCtx.Client.(tmrpcclient.SignClient) if !ok { - return nil, errors.New("invalid rpc client") + return nil, fmt.Errorf("invalid rpc client: type %T", b.clientCtx.Client) } return sc.BlockResults(b.ctx, height) } @@ -203,17 +218,21 @@ func (b *Backend) TendermintBlockResultByNumber(height *int64) (*tmrpctypes.Resu func (b *Backend) TendermintBlockByHash(blockHash gethcommon.Hash) (*tmrpctypes.ResultBlock, error) { sc, ok := b.clientCtx.Client.(tmrpcclient.SignClient) if !ok { - return nil, errors.New("invalid rpc client") + return nil, fmt.Errorf("TendermintBlockByHash: invalid RPC client: type %T", b.clientCtx.Client) } resBlock, err := sc.BlockByHash(b.ctx, blockHash.Bytes()) if err != nil { - b.logger.Debug("tendermint client failed to get block", "blockHash", blockHash.Hex(), "error", err.Error()) - return nil, err + return nil, fmt.Errorf( + "TendermintBlockByHash: RPC BlockByHash(%s) failed: %w", + blockHash.Hex(), err, + ) } if resBlock == nil || resBlock.Block == nil { - b.logger.Debug("TendermintBlockByHash block not found", "blockHash", blockHash.Hex()) - return nil, nil + return nil, fmt.Errorf( + "TendermintBlockByHash: block not found: blockHash %s: %w", + blockHash.Hex(), ErrNilBlockSuccess, + ) } return resBlock, nil @@ -244,7 +263,7 @@ func (b *Backend) BlockNumberFromTendermintByHash(blockHash gethcommon.Hash) (*b return nil, err } if resBlock == nil { - return nil, errors.Errorf("block not found for hash %s", blockHash.Hex()) + return nil, fmt.Errorf("block not found for hash %s", blockHash.Hex()) } return big.NewInt(resBlock.Block.Height), nil } @@ -259,14 +278,12 @@ func (b *Backend) EthMsgsFromTendermintBlock( var result []*evm.MsgEthereumTx block := resBlock.Block - txResults := blockRes.TxsResults - for i, tx := range block.Txs { // Check if tx exists on EVM by cross checking with blockResults: // - Include unsuccessful tx that exceeds block gas limit // - Include unsuccessful tx that failed when committing changes to stateDB // - Exclude unsuccessful tx with any other error but ExceedBlockGasLimit - isValidEnough, reason := rpc.TxIsValidEnough(txResults[i]) + isValidEnough, reason := rpc.TxIsValidEnough(blockRes.TxsResults[i]) if !isValidEnough { b.logger.Debug( "invalid tx result code", @@ -278,7 +295,9 @@ func (b *Backend) EthMsgsFromTendermintBlock( tx, err := b.clientCtx.TxConfig.TxDecoder()(tx) if err != nil { - b.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error()) + b.logger.Debug( + "failed to decode transaction in block", "height", + block.Height, "error", err.Error()) continue } @@ -302,32 +321,23 @@ func (b *Backend) HeaderByNumber(blockNum rpc.BlockNumber) (*gethcore.Header, er return nil, err } - if resBlock == nil { - return nil, errors.Errorf("block not found for height %d", blockNum) - } - blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height) if err != nil { return nil, fmt.Errorf("block result not found for height %d. %w", resBlock.Block.Height, err) } - bloom, err := b.BlockBloom(blockRes) - if err != nil { - b.logger.Debug("HeaderByNumber BlockBloom failed", "height", resBlock.Block.Height) - } - - baseFeeWei, err := b.BaseFeeWei(blockRes) - if err != nil { - // handle the error for pruned node. - b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", resBlock.Block.Height, "error", err) - } + bloom := b.BlockBloom(blockRes) + baseFeeWei := evm.BASE_FEE_WEI ethHeader := rpc.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFeeWei) return ethHeader, nil } // BlockBloom query block bloom filter from block results -func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (gethcore.Bloom, error) { +func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (bloom gethcore.Bloom) { + if blockRes == nil || len(blockRes.EndBlockEvents) == 0 { + return bloom + } msgType := proto.MessageName((*evm.EventBlockBloom)(nil)) for _, event := range blockRes.EndBlockEvents { if event.Type != msgType { @@ -337,11 +347,11 @@ func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (gethcore. if err != nil { continue } - return gethcore.BytesToBloom(hexutils.HexToBytes(blockBloomEvent.Bloom)), nil + return gethcore.BytesToBloom(hexutils.HexToBytes(blockBloomEvent.Bloom)) } // Suppressing error as it is expected to be missing for pruned node or for blocks before evm - return gethcore.Bloom{}, nil + return gethcore.Bloom{} } // RPCBlockFromTendermintBlock returns a JSON-RPC compatible Ethereum block from a @@ -353,12 +363,7 @@ func (b *Backend) RPCBlockFromTendermintBlock( ) (map[string]any, error) { ethRPCTxs := []any{} block := resBlock.Block - - baseFeeWei, err := b.BaseFeeWei(blockRes) - if err != nil { - // handle the error for pruned node. - b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Height, "error", err) - } + baseFeeWei := evm.BASE_FEE_WEI msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes) for txIndex, ethMsg := range msgs { @@ -368,11 +373,10 @@ func (b *Backend) RPCBlockFromTendermintBlock( continue } - tx := ethMsg.AsTransaction() height := uint64(block.Height) //#nosec G701 -- checked for int overflow already index := uint64(txIndex) //#nosec G701 -- checked for int overflow already - rpcTx, err := rpc.NewRPCTxFromEthTx( - tx, + rpcTx, err := rpc.NewRPCTxFromMsgEthTx( + ethMsg, gethcommon.BytesToHash(block.Hash()), height, index, @@ -380,16 +384,13 @@ func (b *Backend) RPCBlockFromTendermintBlock( b.chainID, ) if err != nil { - b.logger.Debug("NewTransactionFromData for receipt failed", "hash", tx.Hash().Hex(), "error", err.Error()) + b.logger.Debug("NewTransactionFromData for receipt failed", "hash", ethMsg.Hash, "error", err.Error()) continue } ethRPCTxs = append(ethRPCTxs, rpcTx) } - bloom, err := b.BlockBloom(blockRes) - if err != nil { - b.logger.Debug("failed to query BlockBloom", "height", block.Height, "error", err.Error()) - } + bloom := b.BlockBloom(blockRes) req := &evm.QueryValidatorAccountRequest{ ConsAddress: sdk.ConsAddress(block.Header.ProposerAddress).String(), @@ -467,17 +468,8 @@ func (b *Backend) EthBlockFromTendermintBlock( blockRes *tmrpctypes.ResultBlockResults, ) (*gethcore.Block, error) { block := resBlock.Block - height := block.Height - bloom, err := b.BlockBloom(blockRes) - if err != nil { - b.logger.Debug("HeaderByNumber BlockBloom failed", "height", height) - } - - baseFeeWei, err := b.BaseFeeWei(blockRes) - if err != nil { - // handle error for pruned node and log - b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", height, "error", err) - } + bloom := b.BlockBloom(blockRes) + baseFeeWei := evm.BASE_FEE_WEI ethHeader := rpc.EthHeaderFromTendermint(block.Header, bloom, baseFeeWei) msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes) diff --git a/eth/rpc/backend/blocks_test.go b/eth/rpc/backend/blocks_test.go index 4bbd084f4f..dc08ebdd9e 100644 --- a/eth/rpc/backend/blocks_test.go +++ b/eth/rpc/backend/blocks_test.go @@ -95,13 +95,15 @@ func (s *BackendSuite) TestEthBlockByNumber() { } func (s *BackendSuite) TestGetBlockTransactionCountByHash() { - txCount := s.backend.GetBlockTransactionCountByHash(*s.SuccessfulTxTransfer().BlockHash) + txCount, err := s.backend.GetBlockTransactionCountByHash(*s.SuccessfulTxTransfer().BlockHash) + s.NoError(err) s.Require().Greater((uint64)(*txCount), uint64(0)) } func (s *BackendSuite) TestGetBlockTransactionCountByNumber() { - txCount := s.backend.GetBlockTransactionCountByNumber( + txCount, err := s.backend.GetBlockTransactionCountByNumber( *s.SuccessfulTxTransfer().BlockNumberRpc) + s.NoError(err) s.Require().Greater((uint64)(*txCount), uint64(0)) } diff --git a/eth/rpc/backend/call_tx.go b/eth/rpc/backend/call_tx.go index 19b9ee4fc7..7299deb661 100644 --- a/eth/rpc/backend/call_tx.go +++ b/eth/rpc/backend/call_tx.go @@ -8,7 +8,7 @@ import ( "fmt" "math/big" - errorsmod "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" @@ -71,7 +71,7 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync) rsp, err := syncCtx.BroadcastTx(txBytes) if rsp != nil && rsp.Code != 0 { - err = errorsmod.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) + err = sdkioerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog) } if err != nil { b.logger.Error("failed to broadcast tx", "error", err.Error()) diff --git a/eth/rpc/backend/chain_info_test.go b/eth/rpc/backend/chain_info_test.go index 43c28b33e0..80dd7bfdcd 100644 --- a/eth/rpc/backend/chain_info_test.go +++ b/eth/rpc/backend/chain_info_test.go @@ -21,15 +21,6 @@ func (s *BackendSuite) TestChainConfig() { s.Require().Equal(int64(0), config.LondonBlock.Int64()) } -func (s *BackendSuite) TestBaseFeeWei() { - resBlock, err := s.backend.TendermintBlockResultByNumber( - s.SuccessfulTxTransfer().BlockNumberRpc.TmHeight()) - s.Require().NoError(err) - baseFeeWei, err := s.backend.BaseFeeWei(resBlock) - s.Require().NoError(err) - s.Require().Equal(evm.BASE_FEE_WEI, baseFeeWei) -} - func (s *BackendSuite) TestCurrentHeader() { currentHeader, err := s.backend.CurrentHeader() s.Require().NoError(err) diff --git a/eth/rpc/backend/tx_info.go b/eth/rpc/backend/tx_info.go index ec7ad89e5b..febec1ff77 100644 --- a/eth/rpc/backend/tx_info.go +++ b/eth/rpc/backend/tx_info.go @@ -7,7 +7,7 @@ import ( "math" "math/big" - errorsmod "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -71,15 +71,10 @@ func (b *Backend) GetTransactionByHash(txHash gethcommon.Hash) (*rpc.EthTxJsonRP return nil, errors.New("can't find index of ethereum tx") } - baseFeeWei, err := b.BaseFeeWei(blockRes) - if err != nil { - // handle the error for pruned node. - b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", blockRes.Height, "error", err) - } - + baseFeeWei := evm.BASE_FEE_WEI height := uint64(res.Height) //#nosec G701 -- checked for int overflow already index := uint64(res.EthTxIndex) //#nosec G701 -- checked for int overflow already - return rpc.NewRPCTxFromMsg( + return rpc.NewRPCTxFromMsgEthTx( msg, gethcommon.BytesToHash(block.BlockID.Hash.Bytes()), height, @@ -108,7 +103,7 @@ func (b *Backend) getTransactionByHashPending(txHash gethcommon.Hash) (*rpc.EthT if msg.Hash == hexTx { // use zero block values since it's not included in a block yet - rpctx, err := rpc.NewRPCTxFromMsg( + rpctx, err := rpc.NewRPCTxFromMsgEthTx( msg, gethcommon.Hash{}, uint64(0), @@ -283,13 +278,8 @@ func (b *Backend) GetTransactionReceipt(hash gethcommon.Hash) (*TransactionRecei } if dynamicTx, ok := txData.(*evm.DynamicFeeTx); ok { - baseFeeWei, err := b.BaseFeeWei(blockRes) - if err != nil { - // tolerate the error for pruned node. - b.logger.Error("fetch basefee failed, node is pruned?", "height", res.Height, "error", err) - } else { - receipt.EffectiveGasPrice = (*hexutil.Big)(dynamicTx.EffectiveGasPriceWeiPerGas(baseFeeWei)) - } + baseFeeWei := evm.BASE_FEE_WEI + receipt.EffectiveGasPrice = (*hexutil.Big)(dynamicTx.EffectiveGasPriceWeiPerGas(baseFeeWei)) } else { receipt.EffectiveGasPrice = (*hexutil.Big)(txData.GetGasPrice()) } @@ -349,7 +339,7 @@ func (b *Backend) GetTxByEthHash(hash gethcommon.Hash) (*eth.TxResult, error) { return txs.GetTxByHash(hash) }) if err != nil { - return nil, errorsmod.Wrapf(err, "GetTxByEthHash(%s)", hash.Hex()) + return nil, sdkioerrors.Wrapf(err, "GetTxByEthHash(%s)", hash.Hex()) } return txResult, nil } @@ -372,7 +362,7 @@ func (b *Backend) GetTxByTxIndex(height int64, index uint) (*eth.TxResult, error return txs.GetTxByTxIndex(int(index)) // #nosec G701 -- checked for int overflow already }) if err != nil { - return nil, errorsmod.Wrapf(err, "GetTxByTxIndex %d %d", height, index) + return nil, sdkioerrors.Wrapf(err, "GetTxByTxIndex %d %d", height, index) } return txResult, nil } @@ -439,15 +429,10 @@ func (b *Backend) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, i msg = ethMsgs[i] } - baseFeeWei, err := b.BaseFeeWei(blockRes) - if err != nil { - // handle the error for pruned node. - b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Block.Height, "error", err) - } - + baseFeeWei := evm.BASE_FEE_WEI height := uint64(block.Block.Height) // #nosec G701 -- checked for int overflow already index := uint64(idx) // #nosec G701 -- checked for int overflow already - return rpc.NewRPCTxFromMsg( + return rpc.NewRPCTxFromMsgEthTx( msg, gethcommon.BytesToHash(block.Block.Hash()), height, diff --git a/eth/rpc/backend/utils.go b/eth/rpc/backend/utils.go index 3f47b5754a..4d9e2347c4 100644 --- a/eth/rpc/backend/utils.go +++ b/eth/rpc/backend/utils.go @@ -113,10 +113,7 @@ func (b *Backend) retrieveEVMTxFeesFromBlock( targetOneFeeHistory *rpc.OneFeeHistory, ) error { blockHeight := tendermintBlock.Block.Height - blockBaseFee, err := b.BaseFeeWei(tendermintBlockResult) - if err != nil { - return err - } + blockBaseFee := evm.BASE_FEE_WEI // set basefee targetOneFeeHistory.BaseFee = blockBaseFee diff --git a/eth/rpc/rpc.go b/eth/rpc/rpc.go index c35a6c896c..9dc9985578 100644 --- a/eth/rpc/rpc.go +++ b/eth/rpc/rpc.go @@ -10,18 +10,16 @@ import ( abci "github.com/cometbft/cometbft/abci/types" tmtypes "github.com/cometbft/cometbft/types" - errorsmod "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" tmrpcclient "github.com/cometbft/cometbft/rpc/client" "github.com/cosmos/cosmos-sdk/client" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/NibiruChain/nibiru/v2/x/common/nmath" "github.com/NibiruChain/nibiru/v2/x/evm" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" gethcore "github.com/ethereum/go-ethereum/core/types" - gethparams "github.com/ethereum/go-ethereum/params" ) // ErrExceedBlockGasLimit defines the error message when tx execution exceeds the @@ -39,7 +37,7 @@ const ErrStateDBCommit = "failed to commit stateDB" func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evm.MsgEthereumTx, error) { tx, err := clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { - return nil, errorsmod.Wrap(errortypes.ErrJSONUnmarshal, err.Error()) + return nil, sdkioerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } ethTxs := make([]*evm.MsgEthereumTx, len(tx.GetMsgs())) @@ -155,42 +153,31 @@ func FormatBlock( return result } -// NewRPCTxFromMsg returns a transaction that will serialize to the RPC +// NewRPCTxFromMsgEthTx returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). -func NewRPCTxFromMsg( - msg *evm.MsgEthereumTx, +func NewRPCTxFromMsgEthTx( + msgEthTx *evm.MsgEthereumTx, blockHash gethcommon.Hash, - blockNumber, index uint64, + blockNumber uint64, + index uint64, baseFeeWei *big.Int, chainID *big.Int, ) (*EthTxJsonRPC, error) { - tx := msg.AsTransaction() - return NewRPCTxFromEthTx(tx, blockHash, blockNumber, index, baseFeeWei, chainID) -} + var ( + tx = msgEthTx.AsTransaction() + // Determine the signer. For replay-protected transactions, use the most + // permissive signer, because we assume that signers are backwards-compatible + // with old transactions. For non-protected transactions, the homestead + // signer is used because the return value of ChainId is zero for unprotected + // transactions. + signer gethcore.Signer = gethcore.HomesteadSigner{} + v, r, s = tx.RawSignatureValues() + ) -// NewRPCTxFromEthTx returns a transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -func NewRPCTxFromEthTx( - tx *gethcore.Transaction, - blockHash gethcommon.Hash, - blockNumber, - index uint64, - baseFee *big.Int, - chainID *big.Int, -) (*EthTxJsonRPC, error) { - // Determine the signer. For replay-protected transactions, use the most - // permissive signer, because we assume that signers are backwards-compatible - // with old transactions. For non-protected transactions, the homestead - // signer is used because the return value of ChainId is zero for unprotected - // transactions. - var signer gethcore.Signer if tx.Protected() { signer = gethcore.LatestSignerForChainID(tx.ChainId()) - } else { - signer = gethcore.HomesteadSigner{} } from, _ := gethcore.Sender(signer, tx) // #nosec G703 - v, r, s := tx.RawSignatureValues() result := &EthTxJsonRPC{ Type: hexutil.Uint64(tx.Type()), From: from, @@ -211,22 +198,23 @@ func NewRPCTxFromEthTx( result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } - switch tx.Type() { + + switch txType := tx.Type(); txType { case gethcore.AccessListTxType: al := tx.AccessList() result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) - case gethcore.DynamicFeeTxType: + case gethcore.DynamicFeeTxType, gethcore.BlobTxType: al := tx.AccessList() result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price - if baseFee != nil && blockHash != (gethcommon.Hash{}) { + if baseFeeWei != nil && blockHash != (gethcommon.Hash{}) { // price = min(tip, gasFeeCap - baseFee) + baseFee - price := nmath.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap()) - result.GasPrice = (*hexutil.Big)(price) + result.GasPrice = (*hexutil.Big)(msgEthTx.EffectiveGasPriceWeiPerGas(baseFeeWei)) } else { result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) } @@ -234,43 +222,22 @@ func NewRPCTxFromEthTx( return result, nil } -// CheckTxFee is an internal function used to check whether the fee of -// the given transaction is _reasonable_(under the cap). -func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error { - // Short circuit if there is no cap for transaction fee at all. - if cap == 0 { - return nil - } - totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))) - oneEther := new(big.Float).SetInt(big.NewInt(gethparams.Ether)) - // quo = rounded(x/y) - feeEth := new(big.Float).Quo(totalfee, oneEther) - // no need to check error from parsing - feeFloat, _ := feeEth.Float64() - if feeFloat > cap { - return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap) - } - return nil -} - -// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit. -func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool { - return strings.Contains(res.Log, ErrExceedBlockGasLimit) -} - -// TxStateDBCommitError returns true if the evm tx commit error. -func TxStateDBCommitError(res *abci.ResponseDeliverTx) bool { - return strings.Contains(res.Log, ErrStateDBCommit) -} - // TxIsValidEnough returns true if the transaction was successful // or if it failed with an ExceedBlockGasLimit error or TxStateDBCommitError error +// +// Include in Block: +// - Include successful tx +// - Include unsuccessful tx that exceeds block gas limit +// - Include unsuccessful tx that failed when committing changes to stateDB +// +// Exclude from Block (Not Valid Enough): +// - Exclude unsuccessful tx with any other error but ExceedBlockGasLimit func TxIsValidEnough(res *abci.ResponseDeliverTx) (condition bool, reason string) { if res.Code == 0 { return true, "tx succeeded" - } else if TxExceedBlockGasLimit(res) { + } else if strings.Contains(res.Log, ErrExceedBlockGasLimit) { return true, "tx exceeded block gas limit" - } else if TxStateDBCommitError(res) { + } else if strings.Contains(res.Log, ErrStateDBCommit) { return true, "tx state db commit error" } return false, "unexpected failure" diff --git a/eth/rpc/rpcapi/apis.go b/eth/rpc/rpcapi/apis.go index 6607f92d22..3d43cafe85 100644 --- a/eth/rpc/rpcapi/apis.go +++ b/eth/rpc/rpcapi/apis.go @@ -2,6 +2,11 @@ package rpcapi import ( + "context" + "fmt" + "reflect" + "unicode" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server" @@ -134,3 +139,87 @@ func GetRPCAPIs(ctx *server.Context, return apis } + +var ( + ctxType = reflect.TypeOf((*context.Context)(nil)).Elem() + errType = reflect.TypeOf((*error)(nil)).Elem() + subscriptionType = reflect.TypeOf(Subscription{}) +) + +type MethodInfo struct { + TrueName string + GoFunc reflect.Method + IsSubscription bool +} + +// ParseAPIMethods returns all method names exposed by api, e.g. "eth_gasPrice" +func ParseAPIMethods(api rpc.API) map[string]MethodInfo { + svcType := reflect.TypeOf(api.Service) + methods := make(map[string]MethodInfo) + + for i := range svcType.NumMethod() { + m := svcType.Method(i) + if m.PkgPath != "" { + continue // unexported + } + sig := m.Type + + // 1) Detect optional context.Context arg + // Args must be: Optional([context.Context]) + zero or more args + hasCtx := sig.NumIn() > 1 && sig.In(1) == ctxType + + // 2) Validate outputs: either (error), or (T, error) + nOut := sig.NumOut() + switch nOut { + case 1: + if !sig.Out(0).Implements(errType) { + continue + } + case 2: + if !sig.Out(1).Implements(errType) { + continue + } + default: + continue + } + + // 3) detect subscriptions: ctx + (Subscription, error) + isSub := false + if hasCtx && nOut == 2 { + t0 := sig.Out(0) + // strip pointer + for t0.Kind() == reflect.Ptr { + t0 = t0.Elem() + } + if t0 == subscriptionType { + isSub = true + } + } + + // 3) name the RPC method by lower‑casing the first rune + trueName := rpcMethodName(api.Namespace, m.Name) + methods[trueName] = MethodInfo{ + TrueName: trueName, + GoFunc: m, + IsSubscription: isSub, + } + } + + return methods +} + +// Example: "TransactionByHash" -> "transactionByHash" +func rpcMethodName(namespace, funcName string) string { + return fmt.Sprintf("%v_%v", namespace, lowerFirst(funcName)) +} + +// lowerFirst returns lowercases the first letter of the input. For context, +// service methods are in the form: +// fmt.Sprintf("%v_%v", namespace, lowerFirst(methodName)) +func lowerFirst(s string) string { + r := []rune(s) + if len(r) > 0 { + r[0] = unicode.ToLower(r[0]) + } + return string(r) +} diff --git a/eth/rpc/rpcapi/debugapi/api.go b/eth/rpc/rpcapi/debugapi/api.go index 354096757f..e67dd9cd7e 100644 --- a/eth/rpc/rpcapi/debugapi/api.go +++ b/eth/rpc/rpcapi/debugapi/api.go @@ -3,7 +3,10 @@ package debugapi import ( "bytes" + "context" + "encoding/json" "errors" + "fmt" "io" "os" "runtime" // #nosec G702 @@ -16,13 +19,13 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm" - stderrors "github.com/pkg/errors" - "github.com/cosmos/cosmos-sdk/server" "github.com/cometbft/cometbft/libs/log" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + getheth "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/rlp" "github.com/NibiruChain/nibiru/v2/eth/rpc" @@ -221,7 +224,7 @@ func (a *DebugAPI) StartCPUProfile(file string) error { a.logger.Debug("cpu profiling already in use", "error", err.Error()) if err := f.Close(); err != nil { a.logger.Debug("failed to close cpu profile file") - return stderrors.Wrap(err, "failed to close cpu profile file") + return fmt.Errorf("failed to close cpu profile file: %w", err) } return err } @@ -248,7 +251,7 @@ func (a *DebugAPI) StopCPUProfile() error { pprof.StopCPUProfile() if err := a.handler.cpuFile.Close(); err != nil { a.logger.Debug("failed to close cpu file") - return stderrors.Wrap(err, "failed to close cpu file") + return fmt.Errorf("failed to close cpu file: %w", err) } a.handler.cpuFile = nil a.handler.cpuFilename = "" @@ -345,3 +348,126 @@ func (a *DebugAPI) IntermediateRoots(hash common.Hash, _ *evm.TraceConfig) ([]co a.logger.Debug("debug_intermediateRoots", "hash", hash) return ([]common.Hash)(nil), nil } + +// GetBadBlocks returns a list of the last 'bad blocks' that the client has seen +// on the network and returns them as a JSON list of block hashes. +func (a *DebugAPI) GetBadBlocks(ctx context.Context) ([]*getheth.BadBlockArgs, error) { + a.logger.Debug("debug_getBadBlocks") + return []*getheth.BadBlockArgs{}, nil +} + +func ErrNotImplemented(method string) error { + return fmt.Errorf("method is not implemented: %v", method) +} + +// GetRawBlock returns an RLP-encoded block +func (a *DebugAPI) GetRawBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { + fnName := "debug_getRawBlock" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// GetRawReceipts returns an array of EIP-2718 binary-encoded receipts +func (a *DebugAPI) GetRawReceipts( + ctx context.Context, + blockNrOrHash rpc.BlockNumberOrHash, +) ([]hexutil.Bytes, error) { + fnName := "debug_getRawReceipts" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// GetRawHeader returns an RLP-encoded block header +func (a *DebugAPI) GetRawHeader( + ctx context.Context, + blockNrOrHash rpc.BlockNumberOrHash, +) (hexutil.Bytes, error) { + fnName := "debug_getRawHeader" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// GetRawTransaction returns the bytes of the transaction for the given hash. +func (a *DebugAPI) GetRawTransaction( + ctx context.Context, + hash common.Hash, +) (hexutil.Bytes, error) { + fnName := "debug_getRawTransaction" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// StandardTraceBadBlockToFile dumps the structured logs created during the +// execution of EVM against a block pulled from the pool of bad ones to the +// local file system and returns a list of files to the caller. +func (a *DebugAPI) StandardTraceBadBlockToFile( + ctx context.Context, + hash common.Hash, + config *tracers.StdTraceConfig, +) ([]string, error) { + fnName := "debug_standardTraceBadBlockToFile" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// StandardTraceBlockToFile dumps the structured logs created during the +// execution of EVM to the local file system and returns a list of files +// to the caller. +func (a *DebugAPI) StandardTraceBlockToFile( + ctx context.Context, + hash common.Hash, + config *tracers.StdTraceConfig, +) ([]string, error) { + fnName := "debug_standardTraceBlockToFile" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// TraceBadBlock returns the structured logs created during the execution of +// EVM against a block pulled from the pool of bad ones and returns them as a JSON +// object. +func (a *DebugAPI) TraceBadBlock( + ctx context.Context, + hash common.Hash, + config *tracers.TraceConfig, +) ([]json.RawMessage, error) { + fnName := "debug_traceBadBlock" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// TraceBlock returns the structured logs created during the execution of EVM +// and returns them as a JSON object. +func (a *DebugAPI) TraceBlock( + ctx context.Context, + blob hexutil.Bytes, + config *tracers.TraceConfig, +) ([]json.RawMessage, error) { + fnName := "debug_traceBlock" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// TraceBlockFromFile returns the structured logs created during the execution of +// EVM and returns them as a JSON object. +func (a *DebugAPI) TraceBlockFromFile( + ctx context.Context, + file string, + config *tracers.TraceConfig, +) ([]json.RawMessage, error) { + fnName := "debug_traceBlockFromFile" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} + +// TraceChain returns the structured logs created during the execution of EVM +// between two blocks (excluding start) and returns them as a JSON object. +func (a *DebugAPI) TraceChain( + ctx context.Context, + start, end rpc.BlockNumber, + config *tracers.TraceConfig, +) (subscription any, err error) { // Fetch the block interval that we want to trace + fnName := "debug_traceChain" + a.logger.Debug(fnName) + return nil, ErrNotImplemented(fnName) +} diff --git a/eth/rpc/rpcapi/debugapi/trace.go b/eth/rpc/rpcapi/debugapi/trace.go index 8dce68952d..f73d425c89 100644 --- a/eth/rpc/rpcapi/debugapi/trace.go +++ b/eth/rpc/rpcapi/debugapi/trace.go @@ -24,7 +24,7 @@ import ( "os" "runtime/trace" - stderrors "github.com/pkg/errors" + pkgerrors "github.com/pkg/errors" ) // StartGoTrace turns on tracing, writing to the given file. @@ -51,7 +51,7 @@ func (a *DebugAPI) StartGoTrace(file string) error { a.logger.Debug("Go tracing already started", "error", err.Error()) if err := f.Close(); err != nil { a.logger.Debug("failed to close trace file") - return stderrors.Wrap(err, "failed to close trace file") + return pkgerrors.Wrap(err, "failed to close trace file") } return err @@ -76,7 +76,7 @@ func (a *DebugAPI) StopGoTrace() error { a.logger.Info("Done writing Go trace", "dump", a.handler.traceFilename) if err := a.handler.traceFile.Close(); err != nil { a.logger.Debug("failed to close trace file") - return stderrors.Wrap(err, "failed to close trace file") + return pkgerrors.Wrap(err, "failed to close trace file") } a.handler.traceFile = nil a.handler.traceFilename = "" diff --git a/eth/rpc/rpcapi/eth_api.go b/eth/rpc/rpcapi/eth_api.go index 883dad86f1..dcc88aaae2 100644 --- a/eth/rpc/rpcapi/eth_api.go +++ b/eth/rpc/rpcapi/eth_api.go @@ -20,12 +20,6 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm" ) -// Ethereum API: Allows connection to a full node of the Nibiru blockchain -// network via Nibiru EVM. Developers can interact with on-chain EVM data and -// send different types of transactions to the network by utilizing the endpoints -// provided by the API. The API follows a JSON-RPC standard. If not otherwise -// specified, the interface is derived from the Alchemy Ethereum API: -// https://docs.alchemy.com/alchemy/apis/ethereum type IEthAPI interface { // Getting Blocks // @@ -33,8 +27,8 @@ type IEthAPI interface { BlockNumber() (hexutil.Uint64, error) GetBlockByNumber(ethBlockNum rpc.BlockNumber, fullTx bool) (map[string]any, error) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]any, error) - GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint - GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) *hexutil.Uint + GetBlockTransactionCountByHash(hash common.Hash) (*hexutil.Uint, error) + GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) (*hexutil.Uint, error) // Reading Transactions // @@ -47,12 +41,6 @@ type IEthAPI interface { GetTransactionByBlockNumberAndIndex(blockNum rpc.BlockNumber, idx hexutil.Uint) (*rpc.EthTxJsonRPC, error) // eth_getBlockReceipts - // Writing Transactions - // - // Allows developers to both send ETH from one address to another, write data - // on-chain, and interact with smart contracts. - SendRawTransaction(data hexutil.Bytes) (common.Hash, error) - // Account Information // // Returns information regarding an address's stored on-chain data. @@ -70,14 +58,6 @@ type IEthAPI interface { address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash, ) (*rpc.AccountResult, error) - // EVM/Smart Contract Execution - // - // Allows developers to read data from the blockchain which includes executing - // smart contracts. However, no data is published to the Ethereum network. - Call( - args evm.JsonTxArgs, blockNrOrHash rpc.BlockNumberOrHash, _ *rpc.StateOverride, - ) (hexutil.Bytes, error) - // Chain Information // // Returns information on the Ethereum network and internal settings. @@ -118,7 +98,16 @@ type IEthAPI interface { var _ IEthAPI = (*EthAPI)(nil) -// EthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +// EthAPI: Allows connection to a full node of the Nibiru blockchain +// network via Nibiru EVM. Developers can interact with on-chain EVM data and +// send different types of transactions to the network by utilizing the endpoints +// provided by the API. +// +// [EthAPI] contains much of the "eth_" prefixed methods in the Web3 JSON-RPC spec. +// +// The API follows a JSON-RPC standard. If not otherwise +// specified, the interface is derived from the Alchemy Ethereum API: +// https://docs.alchemy.com/alchemy/apis/ethereum type EthAPI struct { ctx context.Context logger log.Logger @@ -148,14 +137,24 @@ func (e *EthAPI) BlockNumber() (hexutil.Uint64, error) { // GetBlockByNumber returns the block identified by number. func (e *EthAPI) GetBlockByNumber(ethBlockNum rpc.BlockNumber, fullTx bool) (map[string]any, error) { - e.logger.Debug("eth_getBlockByNumber", "number", ethBlockNum, "full", fullTx) + e.logger.Debug("eth_getBlockByNumber", "blockNumber", ethBlockNum, "fullTx", fullTx) return e.backend.GetBlockByNumber(ethBlockNum, fullTx) } // GetBlockByHash returns the block identified by hash. func (e *EthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]any, error) { - e.logger.Debug("eth_getBlockByHash", "hash", hash.Hex(), "full", fullTx) - return e.backend.GetBlockByHash(hash, fullTx) + methodName := "eth_getBlockByHash" + e.logger.Debug(methodName, "hash", hash.Hex(), "fullTx", fullTx) + block, err := e.backend.GetBlockByHash(hash, fullTx) + logError(e.logger, err, methodName) + return block, err +} + +// logError logs a backend error if one is present +func logError(logger log.Logger, err error, methodName string) { + if err != nil { + logger.Debug(methodName+" failed", "error", err.Error()) + } } // -------------------------------------------------------------------------- @@ -164,8 +163,11 @@ func (e *EthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]any, // GetTransactionByHash returns the transaction identified by hash. func (e *EthAPI) GetTransactionByHash(hash common.Hash) (*rpc.EthTxJsonRPC, error) { - e.logger.Debug("eth_getTransactionByHash", "hash", hash.Hex()) - return e.backend.GetTransactionByHash(hash) + methodName := "eth_getTransactionByHash" + e.logger.Debug(methodName, "hash", hash.Hex()) + tx, err := e.backend.GetTransactionByHash(hash) + logError(e.logger, err, methodName) + return tx, err } // GetTransactionCount returns the number of transactions at the given address up to the given block number. @@ -190,17 +192,23 @@ func (e *EthAPI) GetTransactionReceipt( } // GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash. -func (e *EthAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint { - e.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash.Hex()) - return e.backend.GetBlockTransactionCountByHash(hash) +func (e *EthAPI) GetBlockTransactionCountByHash(hash common.Hash) (*hexutil.Uint, error) { + methodName := "eth_getBlockTransactionCountByHash" + e.logger.Debug(methodName, "hash", hash.Hex()) + txCount, err := e.backend.GetBlockTransactionCountByHash(hash) + logError(e.logger, err, methodName) + return txCount, err } // GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number. func (e *EthAPI) GetBlockTransactionCountByNumber( blockNum rpc.BlockNumber, -) *hexutil.Uint { - e.logger.Debug("eth_getBlockTransactionCountByNumber", "height", blockNum.Int64()) - return e.backend.GetBlockTransactionCountByNumber(blockNum) +) (*hexutil.Uint, error) { + methodName := "eth_getBlockTransactionCountByNumber" + e.logger.Debug(methodName, "height", blockNum.Int64()) + txCount, err := e.backend.GetBlockTransactionCountByNumber(blockNum) + logError(e.logger, err, methodName) + return txCount, err } // GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. @@ -224,6 +232,8 @@ func (e *EthAPI) GetTransactionByBlockNumberAndIndex( // -------------------------------------------------------------------------- // SendRawTransaction send a raw Ethereum transaction. +// Allows developers to both send ETH from one address to another, write data +// on-chain, and interact with smart contracts. func (e *EthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { e.logger.Debug("eth_sendRawTransaction", "length", len(data)) return e.backend.SendRawTransaction(data) @@ -277,6 +287,9 @@ func (e *EthAPI) GetProof(address common.Address, // -------------------------------------------------------------------------- // Call performs a raw contract call. +// +// Allows developers to read data from the blockchain which includes executing +// smart contracts. However, no data is published to the blockchain network. func (e *EthAPI) Call(args evm.JsonTxArgs, blockNrOrHash rpc.BlockNumberOrHash, _ *rpc.StateOverride, @@ -437,6 +450,7 @@ func (e *EthAPI) GetTransactionLogs(txHash common.Hash) ([]*gethcore.Log, error) func (e *EthAPI) FillTransaction( args evm.JsonTxArgs, ) (*rpc.SignTransactionResult, error) { + e.logger.Debug("eth_fillTransaction") // Set some sanity defaults and terminate on failure args, err := e.backend.SetTxDefaults(args) if err != nil { @@ -476,7 +490,7 @@ func (e *EthAPI) GetPendingTransactions() ([]*rpc.EthTxJsonRPC, error) { break } - rpctx, err := rpc.NewRPCTxFromMsg( + rpctx, err := rpc.NewRPCTxFromMsgEthTx( ethMsg, common.Hash{}, uint64(0), diff --git a/eth/rpc/rpcapi/eth_api_test.go b/eth/rpc/rpcapi/eth_api_test.go index 8339d20969..24f3581d0e 100644 --- a/eth/rpc/rpcapi/eth_api_test.go +++ b/eth/rpc/rpcapi/eth_api_test.go @@ -3,14 +3,21 @@ package rpcapi_test import ( "context" "crypto/ecdsa" + "encoding/json" "fmt" "math/big" + "sort" "strings" "testing" "cosmossdk.io/math" + cmtlog "github.com/cometbft/cometbft/libs/log" + cmtrpcclient "github.com/cometbft/cometbft/rpc/jsonrpc/client" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" bank "github.com/cosmos/cosmos-sdk/x/bank/types" + geth "github.com/ethereum/go-ethereum" gethcommon "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -55,7 +62,7 @@ type NodeSuite struct { contractData embeds.CompiledEvmContract } -func TestSuite_RunAll(t *testing.T) { +func Test(t *testing.T) { suite.Run(t, new(Suite)) testutil.RetrySuiteRunIfDbClosed(t, func() { @@ -63,6 +70,118 @@ func TestSuite_RunAll(t *testing.T) { }, 2) } +type Suite struct { + suite.Suite +} + +func (s *Suite) TestExpectedMethods() { + serverCtx := server.NewDefaultContext() + serverCtx.Logger = cmtlog.TestingLogger() + apis := rpcapi.GetRPCAPIs( + serverCtx, client.Context{}, + &cmtrpcclient.WSClient{}, + true, nil, + []string{ + rpcapi.NamespaceEth, // eth and filters services + rpcapi.NamespaceDebug, + }, + ) + s.Require().Len(apis, 3) + type WantMethod struct { + ServiceName string + Methods []string + } + testCases := []WantMethod{ + { + ServiceName: "rpcapi.EthAPI", + Methods: []string{ + "eth_accounts", + "eth_blockNumber", + "eth_call", + "eth_chainId", + "eth_estimateGas", + "eth_feeHistory", + "eth_fillTransaction", + "eth_gasPrice", + "eth_getBalance", + "eth_getBlockByHash", + "eth_getBlockByNumber", + "eth_getCode", + "eth_getPendingTransactions", + "eth_getProof", + "eth_getStorageAt", + "eth_getTransactionByBlockHashAndIndex", + "eth_getTransactionByBlockNumberAndIndex", + "eth_getTransactionByHash", + "eth_getTransactionCount", + "eth_getTransactionLogs", + "eth_getTransactionReceipt", + "eth_maxPriorityFeePerGas", + "eth_sendRawTransaction", + "eth_syncing", + }, + }, + { + ServiceName: "rpcapi.FiltersAPI", + Methods: []string{ + "eth_getFilterChanges", + "eth_getFilterLogs", + "eth_getLogs", + }, + }, + { + ServiceName: "rpcapi.DebugAPI", + // See https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug + Methods: []string{ + "debug_getBadBlocks", + "debug_getRawBlock", + "debug_getRawHeader", + "debug_getRawReceipts", + "debug_getRawTransaction", + "debug_intermediateRoots", + "debug_standardTraceBadBlockToFile", + "debug_standardTraceBlockToFile", + "debug_traceBadBlock", + "debug_traceBlock", + "debug_traceBlockByHash", + "debug_traceBlockByNumber", + "debug_traceBlockFromFile", + "debug_traceCall", + "debug_traceChain", + "debug_traceTransaction", + }, + }, + } + + for idx, api := range apis { + tc := testCases[idx] + testName := fmt.Sprintf("%v-%v", api.Namespace, tc.ServiceName) + s.Run(testName, func() { + gotMethods := rpcapi.ParseAPIMethods(api) + for _, wantMethod := range tc.Methods { + _, ok := gotMethods[wantMethod] + if !ok { + errMsg := fmt.Sprintf( + "Missing RPC implementation for \"%s\" : service: %s, namespace: %s", + wantMethod, tc.ServiceName, api.Namespace, + ) + s.Fail(errMsg) + } + } + + if s.T().Failed() { + gotNames := []string{} + for name := range gotMethods { + gotNames = append(gotNames, name) + } + sort.Strings(gotNames) + bz, _ := json.MarshalIndent(gotNames, "", " ") + s.T().Logf("gotMethods: %s", bz) + } + }) + } +} + // SetupSuite runs before every test in the suite. Implements the // "suite.SetupAllSuite" interface. func (s *NodeSuite) SetupSuite() { diff --git a/eth/rpc/rpcapi/event_subscriber_test.go b/eth/rpc/rpcapi/event_subscriber_test.go index dc14b092e1..ff8cf29ad8 100644 --- a/eth/rpc/rpcapi/event_subscriber_test.go +++ b/eth/rpc/rpcapi/event_subscriber_test.go @@ -8,7 +8,6 @@ import ( abci "github.com/cometbft/cometbft/abci/types" gogoproto "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" "github.com/cometbft/cometbft/libs/log" coretypes "github.com/cometbft/cometbft/rpc/core/types" @@ -24,10 +23,6 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" ) -type Suite struct { - suite.Suite -} - func TestEventSubscriber(t *testing.T) { index := make(rpcapi.FilterIndex) for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ { @@ -52,7 +47,7 @@ func TestEventSubscriber(t *testing.T) { <-sub.Installed ch, ok := es.TopicChans[sub.Event] if !ok { - t.Error("expect topic channel exist") + t.Errorf("expect topic channel exist: event %v", sub.Event) } sub = rpcapi.MakeSubscription("2", event) @@ -60,11 +55,11 @@ func TestEventSubscriber(t *testing.T) { <-sub.Installed newCh, ok := es.TopicChans[sub.Event] if !ok { - t.Error("expect topic channel exist") + t.Errorf("expect topic channel exist: event %v", sub.Event) } if newCh != ch { - t.Error("expect topic channel unchanged") + t.Errorf("expect topic channel unchanged: event %v", sub.Event) } } diff --git a/eth/rpc/rpcapi/filters.go b/eth/rpc/rpcapi/filters.go index 850e7fdfd6..f3d4c54d34 100644 --- a/eth/rpc/rpcapi/filters.go +++ b/eth/rpc/rpcapi/filters.go @@ -115,11 +115,7 @@ func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*get return nil, nil } - bloom, err := f.backend.BlockBloom(blockRes) - if err != nil { - return nil, err - } - + bloom := f.backend.BlockBloom(blockRes) return f.blockLogs(blockRes, bloom) } @@ -167,10 +163,7 @@ func (f *Filter) Logs(_ context.Context, logLimit int, blockLimit int64) ([]*get return nil, nil } - bloom, err := f.backend.BlockBloom(blockRes) - if err != nil { - return nil, err - } + bloom := f.backend.BlockBloom(blockRes) filtered, err := f.blockLogs(blockRes, bloom) if err != nil { diff --git a/eth/safe_math.go b/eth/safe_math.go index d1ca9544de..4d57c15370 100644 --- a/eth/safe_math.go +++ b/eth/safe_math.go @@ -5,9 +5,9 @@ import ( math "math" "math/big" - errorsmod "cosmossdk.io/errors" + sdkioerrors "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) const maxBitLen = 256 @@ -30,7 +30,7 @@ func IsValidInt256(i *big.Int) bool { // SafeInt64 checks for overflows while casting a uint64 to int64 value. func SafeInt64(value uint64) (int64, error) { if value > uint64(math.MaxInt64) { - return 0, errorsmod.Wrapf(errortypes.ErrInvalidHeight, "uint64 value %v cannot exceed %v", value, int64(math.MaxInt64)) + return 0, sdkioerrors.Wrapf(sdkerrors.ErrInvalidHeight, "uint64 value %v cannot exceed %v", value, int64(math.MaxInt64)) } return int64(value), nil // #nosec G701 -- checked for int overflow already diff --git a/evm-e2e/test/debug_queries.test.ts b/evm-e2e/test/debug_queries.test.ts index 6877adcb57..33a8bcbf93 100644 --- a/evm-e2e/test/debug_queries.test.ts +++ b/evm-e2e/test/debug_queries.test.ts @@ -90,7 +90,7 @@ describe("debug queries", () => { // ticket: https://github.com/NibiruChain/nibiru/issues/2279 it("debug_getBadBlocks", async () => { try { - const traceResult = await provider.send("debug_getBadBlocks", [txHash]) + const traceResult = await provider.send("debug_getBadBlocks", []) expect(traceResult).toBeDefined() } catch (err) { expect(err.message).toContain( diff --git a/go.mod b/go.mod index 204f30944b..6f9d100d4b 100644 --- a/go.mod +++ b/go.mod @@ -115,6 +115,7 @@ require ( github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect github.com/creachadair/taskgroup v0.4.2 // indirect @@ -144,6 +145,7 @@ require ( github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/googleapis v1.4.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/glog v1.2.4 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect @@ -159,6 +161,7 @@ require ( github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect + github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.5 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -167,6 +170,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect + github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huandu/skiplist v1.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect @@ -192,6 +196,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect @@ -205,6 +210,7 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect @@ -222,6 +228,8 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/ulikunitz/xz v0.5.11 // indirect + github.com/urfave/cli/v2 v2.25.7 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.8 // indirect @@ -243,6 +251,7 @@ require ( google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.6 // indirect pgregory.net/rapid v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1cc6a1f281..e125288602 100644 --- a/go.sum +++ b/go.sum @@ -927,7 +927,6 @@ github.com/cosmos/ledger-cosmos-go v0.12.4 h1:drvWt+GJP7Aiw550yeb3ON/zsrgW0jgh5s github.com/cosmos/ledger-cosmos-go v0.12.4/go.mod h1:fjfVWRf++Xkygt9wzCsjEBdjcf7wiiY35fv3ctT+k4M= github.com/cosmos/rosetta-sdk-go v0.10.0 h1:E5RhTruuoA7KTIXUcMicL76cffyeoyvNybzUGSKFTcM= github.com/cosmos/rosetta-sdk-go v0.10.0/go.mod h1:SImAZkb96YbwvoRkzSMQB6noNJXFgWl/ENIznEoYQI4= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -1148,7 +1147,6 @@ github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0 github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -1800,7 +1798,6 @@ github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -1947,7 +1944,6 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= diff --git a/x/common/error.go b/x/common/error.go index de3e4acac5..d817554d8e 100644 --- a/x/common/error.go +++ b/x/common/error.go @@ -185,11 +185,3 @@ func CombineErrorsFromStrings(strs ...string) (err error) { } var ErrNilGrpcMsg = grpcstatus.Errorf(grpccodes.InvalidArgument, "nil msg") - -// ErrNotImplemented: Represents an function error value. -func ErrNotImplemented() error { return fmt.Errorf("fn not implemented yet") } - -// ErrNotImplementedGprc: Represents an unimplemented gRPC method. -func ErrNotImplementedGprc() error { - return grpcstatus.Error(grpccodes.Unimplemented, ErrNotImplemented().Error()) -}