From e958c804016095ccb61e4ed53ae4dc4522402ca7 Mon Sep 17 00:00:00 2001 From: Unique Divine Date: Sat, 19 Apr 2025 17:18:45 -0500 Subject: [PATCH 1/4] test(rpcapi): prevent more regressions with runtime service inspection --- eth/rpc/rpcapi/apis.go | 89 ++++++++++++++++ eth/rpc/rpcapi/debugapi/api.go | 128 ++++++++++++++++++++++++ eth/rpc/rpcapi/eth_api.go | 7 +- eth/rpc/rpcapi/eth_api_test.go | 124 ++++++++++++++++++++++- eth/rpc/rpcapi/event_subscriber_test.go | 11 +- x/common/error.go | 12 +-- 6 files changed, 350 insertions(+), 21 deletions(-) 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..bdfcbb3a72 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 @@ -23,6 +26,8 @@ import ( "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" @@ -345,3 +350,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/eth_api.go b/eth/rpc/rpcapi/eth_api.go index 883dad86f1..6e4abd432f 100644 --- a/eth/rpc/rpcapi/eth_api.go +++ b/eth/rpc/rpcapi/eth_api.go @@ -148,13 +148,13 @@ 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) + e.logger.Debug("eth_getBlockByHash", "hash", hash.Hex(), "fullTx", fullTx) return e.backend.GetBlockByHash(hash, fullTx) } @@ -437,6 +437,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 +477,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..c881fa4fad 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,121 @@ 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/x/common/error.go b/x/common/error.go index de3e4acac5..2b1bbdcc59 100644 --- a/x/common/error.go +++ b/x/common/error.go @@ -184,12 +184,6 @@ func CombineErrorsFromStrings(strs ...string) (err error) { return CombineErrors(errs...) } -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()) -} +var ( + ErrNilGrpcMsg = grpcstatus.Errorf(grpccodes.InvalidArgument, "nil msg") +) From fcb3846c9fc7a4b22b24dc1b9022f7a83dd66142 Mon Sep 17 00:00:00 2001 From: Unique Divine Date: Sat, 19 Apr 2025 17:20:10 -0500 Subject: [PATCH 2/4] fix(backend): fix error propagation --- eth/rpc/backend/backend_suite_test.go | 5 +- eth/rpc/backend/blocks.go | 96 +++++++++++++++++---------- eth/rpc/backend/tx_info.go | 10 +-- eth/rpc/rpc.go | 95 +++++++++----------------- go.mod | 9 +++ go.sum | 4 -- 6 files changed, 111 insertions(+), 108 deletions(-) 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..558de75db6 100644 --- a/eth/rpc/backend/blocks.go +++ b/eth/rpc/backend/blocks.go @@ -34,21 +34,23 @@ 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 uint64", height) } return hexutil.Uint64(height), nil @@ -57,27 +59,47 @@ func (b *Backend) BlockNumber() (hexutil.Uint64, error) { // 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 b := blockNum.Int64(); b >= int64(currentBlockNum) { + return nil, fmt.Errorf("requested block number is too high: current block %d, requested block %d", + currentBlockNum, b, + ) + } + return nil, fmt.Errorf( + "block query succeeded, but the block was nil: requested block %d", + blockNum) } 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,33 @@ 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) { + defer func() { + if err != nil { + b.logger.Debug("eth_getBlockByHash failed", "error", 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,20 +141,21 @@ 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 { sc, ok := b.clientCtx.Client.(tmrpcclient.SignClient) if !ok { b.logger.Error("invalid rpc client") + return nil } - 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()) + b.logger.Debug("block not found", "hash", blockHash.Hex(), "error", err.Error()) return nil } if block.Block == nil { - b.logger.Debug("block not found", "hash", hash.Hex()) + b.logger.Debug("block not found", "hash", blockHash.Hex()) return nil } @@ -259,14 +288,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 +305,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 } @@ -319,7 +348,7 @@ func (b *Backend) HeaderByNumber(blockNum rpc.BlockNumber) (*gethcore.Header, er 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) + b.logger.Error("failed to fetch Base Fee from pruned block. Check node pruning configuration", "height", resBlock.Block.Height, "error", err) } ethHeader := rpc.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFeeWei) @@ -357,7 +386,7 @@ func (b *Backend) RPCBlockFromTendermintBlock( 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) + b.logger.Error("failed to fetch Base Fee from pruned block. Check node prunning configuration", "height", block.Height, "error", err) } msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes) @@ -368,11 +397,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,7 +408,7 @@ 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) @@ -476,7 +504,7 @@ func (b *Backend) EthBlockFromTendermintBlock( 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) + b.logger.Error("failed to fetch Base Fee from pruned block. Check node prunning configuration", "height", height, "error", err) } ethHeader := rpc.EthHeaderFromTendermint(block.Header, bloom, baseFeeWei) diff --git a/eth/rpc/backend/tx_info.go b/eth/rpc/backend/tx_info.go index ec7ad89e5b..1fdeeef7f8 100644 --- a/eth/rpc/backend/tx_info.go +++ b/eth/rpc/backend/tx_info.go @@ -74,12 +74,12 @@ func (b *Backend) GetTransactionByHash(txHash gethcommon.Hash) (*rpc.EthTxJsonRP 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) + b.logger.Error("failed to fetch Base Fee from pruned block. Check node prunning configuration", "height", blockRes.Height, "error", err) } 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 +108,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), @@ -442,12 +442,12 @@ func (b *Backend) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, 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) + b.logger.Error("failed to fetch Base Fee from pruned block. Check node prunning configuration", "height", block.Block.Height, "error", err) } 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/rpc.go b/eth/rpc/rpc.go index c35a6c896c..43db458dc5 100644 --- a/eth/rpc/rpc.go +++ b/eth/rpc/rpc.go @@ -15,13 +15,11 @@ import ( "github.com/cosmos/cosmos-sdk/client" errortypes "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 @@ -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/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= From 5e4d987360468f1fa88106133aaa66e85e8def2d Mon Sep 17 00:00:00 2001 From: Unique Divine Date: Mon, 21 Apr 2025 10:46:13 -0500 Subject: [PATCH 3/4] fix(eth): error propagation fixes and tests for the methods exposed by Nibiru's EVM JSON-RPC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace legacy error aliases throughout the eth module with sdkioerrors.Wrapf and sdkerrors.Err* to ensure consistent wrapping of Cosmos SDK errors. Simplify EVM backend error reporting by using fmt.Errorf with clear method prefixes (e.g. “BlockNumberError”, “RPCBlockFromTendermintBlock error”) and defaulting the base fee to evm.BASE_FEE_WEI. Suppress pruning‑related bloom errors by returning empty blooms when data is missing. Unify RPC transaction conversion logic by renaming NewRPCTxFromMsg to NewRPCTxFromMsgEthTx, retiring the older NewRPCTxFromEthTx, and centralizing signer and signature‑value extraction. Refactor BackendSuite tests to use a SuccessfulTx map for structured receipt capture—eliminating global variables and improving setup clarity. Stub out unimplemented debugapi methods with context‑aware signatures and adjust filters, tracing, and utils to inline BlockBloom usage and align imports (e.g. eip1559, tracers) with modern patterns. --- CHANGELOG.md | 1 + eth/assert.go | 12 +-- eth/crypto/ethsecp256k1/ethsecp256k1.go | 6 +- eth/eip712/eip712_legacy.go | 12 +-- eth/eip712/message.go | 22 ++--- eth/eip712/types.go | 16 ++-- eth/errors.go | 6 +- eth/indexer/evm_tx_indexer.go | 22 ++--- eth/rpc/backend/account_info.go | 5 +- eth/rpc/backend/blocks.go | 120 +++++++++--------------- eth/rpc/backend/blocks_test.go | 6 +- eth/rpc/backend/call_tx.go | 4 +- eth/rpc/backend/chain_info_test.go | 9 -- eth/rpc/backend/tx_info.go | 29 ++---- eth/rpc/backend/utils.go | 5 +- eth/rpc/rpc.go | 6 +- eth/rpc/rpcapi/debugapi/api.go | 6 +- eth/rpc/rpcapi/debugapi/trace.go | 6 +- eth/rpc/rpcapi/eth_api.go | 79 +++++++++------- eth/rpc/rpcapi/eth_api_test.go | 3 - eth/rpc/rpcapi/filters.go | 11 +-- eth/safe_math.go | 6 +- x/common/error.go | 4 +- 23 files changed, 167 insertions(+), 229 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 053b689d3f..fe3cff4de8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,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+. +- [#2xxx](https://github.com/NibiruChain/nibiru/pull/2xxx) - fix(eth-rpc): error propagation fixes and tests for the methods exposed by Nibiru's EVM JSON-RPC ## v2.3.0 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/blocks.go b/eth/rpc/backend/blocks.go index 558de75db6..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" @@ -50,12 +50,14 @@ func (b *Backend) BlockNumber() (hexutil.Uint64, error) { } if height > math.MaxInt64 { - return 0, fmt.Errorf("BlockNumberError: 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. @@ -79,14 +81,12 @@ func (b *Backend) GetBlockByNumber( if err != nil { return nil, err //#nosec G701 -- checked for int overflow already - } else if b := blockNum.Int64(); b >= int64(currentBlockNum) { + } 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, b, + currentBlockNum, blockNumI64, ) } - return nil, fmt.Errorf( - "block query succeeded, but the block was nil: requested block %d", - blockNum) + return nil, fmt.Errorf("requested block %d: %w", blockNum, ErrNilBlockSuccess) } blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height) @@ -111,11 +111,6 @@ func (b *Backend) GetBlockByHash( blockHash gethcommon.Hash, fullTx bool, ) (block map[string]any, err error) { - defer func() { - if err != nil { - b.logger.Debug("eth_getBlockByHash failed", "error", err.Error()) - } - }() resBlock, err := b.TendermintBlockByHash(blockHash) if err != nil { return nil, err @@ -141,22 +136,18 @@ func (b *Backend) GetBlockByHash( // GetBlockTransactionCountByHash returns the number of Ethereum transactions in // the block identified by hash. -func (b *Backend) GetBlockTransactionCountByHash(blockHash 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 + return nil, fmt.Errorf("invalid rpc client of type %T", b.clientCtx.Client) } block, err := sc.BlockByHash(b.ctx, blockHash.Bytes()) if err != nil { - b.logger.Debug("block not found", "hash", blockHash.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", blockHash.Hex()) - return nil + return nil, fmt.Errorf("block not found: hash %s: %w", blockHash, ErrNilBlockSuccess) } return b.GetBlockTransactionCount(block) @@ -164,16 +155,14 @@ func (b *Backend) GetBlockTransactionCountByHash(blockHash gethcommon.Hash) *hex // 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) @@ -181,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 @@ -206,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 @@ -223,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) } @@ -232,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 @@ -273,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 } @@ -331,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 pruned block. Check node pruning 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 { @@ -366,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 @@ -382,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 pruned 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 { @@ -414,10 +390,7 @@ func (b *Backend) RPCBlockFromTendermintBlock( 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(), @@ -495,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 pruned 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 1fdeeef7f8..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,12 +71,7 @@ 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 pruned 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.NewRPCTxFromMsgEthTx( @@ -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,12 +429,7 @@ 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 pruned 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.NewRPCTxFromMsgEthTx( 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 43db458dc5..9dc9985578 100644 --- a/eth/rpc/rpc.go +++ b/eth/rpc/rpc.go @@ -10,10 +10,10 @@ 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/evm" @@ -37,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())) diff --git a/eth/rpc/rpcapi/debugapi/api.go b/eth/rpc/rpcapi/debugapi/api.go index bdfcbb3a72..e67dd9cd7e 100644 --- a/eth/rpc/rpcapi/debugapi/api.go +++ b/eth/rpc/rpcapi/debugapi/api.go @@ -19,8 +19,6 @@ 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" @@ -226,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 } @@ -253,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 = "" 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 6e4abd432f..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 @@ -154,8 +143,18 @@ func (e *EthAPI) GetBlockByNumber(ethBlockNum rpc.BlockNumber, fullTx bool) (map // 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(), "fullTx", 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, diff --git a/eth/rpc/rpcapi/eth_api_test.go b/eth/rpc/rpcapi/eth_api_test.go index c881fa4fad..24f3581d0e 100644 --- a/eth/rpc/rpcapi/eth_api_test.go +++ b/eth/rpc/rpcapi/eth_api_test.go @@ -167,7 +167,6 @@ func (s *Suite) TestExpectedMethods() { ) s.Fail(errMsg) } - } if s.T().Failed() { @@ -179,10 +178,8 @@ func (s *Suite) TestExpectedMethods() { bz, _ := json.MarshalIndent(gotNames, "", " ") s.T().Logf("gotMethods: %s", bz) } - }) } - } // SetupSuite runs before every test in the suite. Implements the 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/x/common/error.go b/x/common/error.go index 2b1bbdcc59..d817554d8e 100644 --- a/x/common/error.go +++ b/x/common/error.go @@ -184,6 +184,4 @@ func CombineErrorsFromStrings(strs ...string) (err error) { return CombineErrors(errs...) } -var ( - ErrNilGrpcMsg = grpcstatus.Errorf(grpccodes.InvalidArgument, "nil msg") -) +var ErrNilGrpcMsg = grpcstatus.Errorf(grpccodes.InvalidArgument, "nil msg") From 9a4d400f219d789502b0ccd31d2e90cfd55d4cf3 Mon Sep 17 00:00:00 2001 From: Unique Divine Date: Mon, 21 Apr 2025 11:12:47 -0500 Subject: [PATCH 4/4] chore: changelog PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3cff4de8..8ccb377190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,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+. -- [#2xxx](https://github.com/NibiruChain/nibiru/pull/2xxx) - fix(eth-rpc): error propagation fixes and tests for the methods exposed by Nibiru's EVM JSON-RPC +- [#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 ## v2.3.0