diff --git a/CHANGELOG.md b/CHANGELOG.md index 555fa5992..4aa34212e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2031](https://github.com/NibiruChain/nibiru/pull/2031) - fix(evm): debug calls with custom tracer and tracer options - [#2039](https://github.com/NibiruChain/nibiru/pull/2039) - refactor(rpc-backend): remove unnecessary interface code - [#2039](https://github.com/NibiruChain/nibiru/pull/2039) - refactor(rpc-backend): Remove mocks from eth/rpc/backend, partially completing [nibiru#2037](https://github.com/NibiruChain/nibiru/issue/2037). +- [#2045](https://github.com/NibiruChain/nibiru/pull/2045) - test(evm): backend tests with test network and real txs #### Dapp modules: perp, spot, oracle, etc diff --git a/eth/chain_id.go b/eth/chain_id.go index 3c5491594..541e8fa0b 100644 --- a/eth/chain_id.go +++ b/eth/chain_id.go @@ -38,14 +38,14 @@ func IsValidChainID(chainID string) bool { return nibiruEvmChainId.MatchString(chainID) } -// ParseEthChainID parses a string chain identifier's epoch to an -// Ethereum-compatible chain-id in *big.Int format. +// ParseEthChainID parses a string chain identifier's +// to an Ethereum-compatible chain-id in *big.Int format. // // This function uses Nibiru's map of chain IDs defined in Nibiru/app/appconst // rather than the regex of EIP155, which is implemented by // ParseEthChainIDStrict. -func ParseEthChainID(chainID string) (*big.Int, error) { - return appconst.GetEthChainID(chainID), nil +func ParseEthChainID(chainID string) *big.Int { + return appconst.GetEthChainID(chainID) } // ParseEthChainIDStrict parses a string chain identifier's epoch to an diff --git a/eth/eip712/encoding.go b/eth/eip712/encoding.go index 2c2b8ea0b..2950d68b1 100644 --- a/eth/eip712/encoding.go +++ b/eth/eip712/encoding.go @@ -103,10 +103,7 @@ func decodeAminoSignDoc(signDocBytes []byte) (apitypes.TypedData, error) { return apitypes.TypedData{}, err } - chainID, err := eth.ParseEthChainID(aminoDoc.ChainID) - if err != nil { - return apitypes.TypedData{}, errors.New("invalid chain ID passed as argument") - } + chainID := eth.ParseEthChainID(aminoDoc.ChainID) typedData, err := WrapTxToTypedData( chainID.Uint64(), @@ -167,10 +164,7 @@ func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) { signerInfo := authInfo.SignerInfos[0] - chainID, err := eth.ParseEthChainID(signDoc.ChainId) - if err != nil { - return apitypes.TypedData{}, fmt.Errorf("invalid chain ID passed as argument: %w", err) - } + chainID := eth.ParseEthChainID(signDoc.ChainId) stdFee := &legacytx.StdFee{ Amount: authInfo.Fee.Amount, diff --git a/eth/eip712/encoding_legacy.go b/eth/eip712/encoding_legacy.go index 751be9a4b..805eb48dd 100644 --- a/eth/eip712/encoding_legacy.go +++ b/eth/eip712/encoding_legacy.go @@ -94,11 +94,7 @@ func legacyDecodeAminoSignDoc(signDocBytes []byte) (apitypes.TypedData, error) { feeDelegation := &FeeDelegationOptions{ FeePayer: feePayer, } - - chainID, err := eth.ParseEthChainID(aminoDoc.ChainID) - if err != nil { - return apitypes.TypedData{}, errors.New("invalid chain ID passed as argument") - } + chainID := eth.ParseEthChainID(aminoDoc.ChainID) typedData, err := LegacyWrapTxToTypedData( protoCodec, @@ -165,10 +161,7 @@ func legacyDecodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error signerInfo := authInfo.SignerInfos[0] - chainID, err := eth.ParseEthChainID(signDoc.ChainId) - if err != nil { - return apitypes.TypedData{}, fmt.Errorf("invalid chain ID passed as argument: %w", err) - } + chainID := eth.ParseEthChainID(signDoc.ChainId) stdFee := &legacytx.StdFee{ Amount: authInfo.Fee.Amount, diff --git a/eth/rpc/backend/account_info.go b/eth/rpc/backend/account_info.go index 94478a259..53fcd222e 100644 --- a/eth/rpc/backend/account_info.go +++ b/eth/rpc/backend/account_info.go @@ -12,7 +12,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" @@ -22,7 +22,7 @@ import ( // GetCode returns the contract code at the given address and block number. func (b *Backend) GetCode( - address common.Address, blockNrOrHash rpc.BlockNumberOrHash, + address gethcommon.Address, blockNrOrHash rpc.BlockNumberOrHash, ) (hexutil.Bytes, error) { blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash) if err != nil { @@ -43,7 +43,7 @@ func (b *Backend) GetCode( // GetProof returns an account object with proof and any storage proofs func (b *Backend) GetProof( - address common.Address, + address gethcommon.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash, ) (*rpc.AccountResult, error) { @@ -81,7 +81,7 @@ func (b *Backend) GetProof( storageProofs := make([]rpc.StorageResult, len(storageKeys)) for i, key := range storageKeys { - hexKey := common.HexToHash(key) + hexKey := gethcommon.HexToHash(key) valueBz, proof, err := b.queryClient.GetProof(clientCtx, evm.StoreKey, evm.StateKey(address, hexKey.Bytes())) if err != nil { return nil, err @@ -105,7 +105,7 @@ func (b *Backend) GetProof( } // query account proofs - accountKey := authtypes.AddressStoreKey(sdk.AccAddress(address.Bytes())) + accountKey := authtypes.AddressStoreKey(address.Bytes()) _, proof, err := b.queryClient.GetProof(clientCtx, authtypes.StoreKey, accountKey) if err != nil { return nil, err @@ -120,18 +120,18 @@ func (b *Backend) GetProof( Address: address, AccountProof: GetHexProofs(proof), Balance: (*hexutil.Big)(balance.BigInt()), - CodeHash: common.HexToHash(res.CodeHash), + CodeHash: gethcommon.HexToHash(res.CodeHash), Nonce: hexutil.Uint64(res.Nonce), // NOTE: The StorageHash is blank. Consider whether this is useful in the // future. Currently, all storage is handles by persistent and transient // `sdk.KVStore` objects. - StorageHash: common.Hash{}, + StorageHash: gethcommon.Hash{}, StorageProof: storageProofs, }, nil } // GetStorageAt returns the contract storage at the given address, block number, and key. -func (b *Backend) GetStorageAt(address common.Address, key string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { +func (b *Backend) GetStorageAt(address gethcommon.Address, key string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash) if err != nil { return nil, err @@ -147,13 +147,13 @@ func (b *Backend) GetStorageAt(address common.Address, key string, blockNrOrHash return nil, err } - value := common.HexToHash(res.Value) + value := gethcommon.HexToHash(res.Value) return value.Bytes(), nil } // GetBalance returns the provided account's balance up to the provided block number. func (b *Backend) GetBalance( - address common.Address, + address gethcommon.Address, blockNrOrHash rpc.BlockNumberOrHash, ) (*hexutil.Big, error) { blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash) @@ -189,7 +189,7 @@ func (b *Backend) GetBalance( } // GetTransactionCount returns the number of transactions at the given address up to the given block number. -func (b *Backend) GetTransactionCount(address common.Address, blockNum rpc.BlockNumber) (*hexutil.Uint64, error) { +func (b *Backend) GetTransactionCount(address gethcommon.Address, blockNum rpc.BlockNumber) (*hexutil.Uint64, error) { n := hexutil.Uint64(0) bn, err := b.BlockNumber() if err != nil { diff --git a/eth/rpc/backend/account_info_test.go b/eth/rpc/backend/account_info_test.go index f0dcea536..109cc3d49 100644 --- a/eth/rpc/backend/account_info_test.go +++ b/eth/rpc/backend/account_info_test.go @@ -1 +1,202 @@ package backend_test + +import ( + "math/big" + + gethcommon "github.com/ethereum/go-ethereum/common" + "golang.org/x/crypto/sha3" + + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" + + rpc "github.com/NibiruChain/nibiru/v2/eth/rpc" +) + +func (s *BackendSuite) TestGetCode() { + testCases := []struct { + name string + contractAddr gethcommon.Address + blockNumber rpc.BlockNumber + codeFound bool + }{ + { + name: "happy: valid contract address", + contractAddr: testContractAddress, + blockNumber: deployContractBlockNumber, + codeFound: true, + }, + { + name: "sad: not a contract address", + contractAddr: s.fundedAccEthAddr, + blockNumber: deployContractBlockNumber, + codeFound: false, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + code, err := s.backend.GetCode( + tc.contractAddr, + rpc.BlockNumberOrHash{ + BlockNumber: &tc.blockNumber, + }, + ) + if !tc.codeFound { + s.Require().Nil(code) + return + } + s.Require().NoError(err) + s.Require().NotNil(code) + }) + } +} + +func (s *BackendSuite) TestGetProof() { + testCases := []struct { + name string + contractAddr gethcommon.Address + blockNumber rpc.BlockNumber + address gethcommon.Address + slot uint64 + wantValue string + }{ + { + name: "happy: balance of the contract deployer", + contractAddr: testContractAddress, + address: s.fundedAccEthAddr, + blockNumber: deployContractBlockNumber, + slot: 0, // _balances is the first slot in ERC20 + wantValue: "0xd3c21bcecceda1000000", // = 1000000 * (10**18), initial supply + }, + { + name: "sad: address which is not in contract storage", + contractAddr: s.fundedAccEthAddr, + address: recipient, + blockNumber: deployContractBlockNumber, + slot: 0, + wantValue: "0x0", + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + proof, err := s.backend.GetProof( + tc.contractAddr, + []string{generateStorageKey(tc.address, tc.slot)}, + rpc.BlockNumberOrHash{ + BlockNumber: &tc.blockNumber, + }, + ) + s.Require().NoError(err) + s.Require().NotNil(proof) + s.Require().Equal(tc.wantValue, proof.StorageProof[0].Value.String()) + }) + } +} + +func (s *BackendSuite) TestGetStorageAt() { + testCases := []struct { + name string + contractAddr gethcommon.Address + blockNumber rpc.BlockNumber + address gethcommon.Address + slot uint64 + wantValue string + }{ + { + name: "happy: balance of the contract deployer", + contractAddr: testContractAddress, + address: s.fundedAccEthAddr, + blockNumber: deployContractBlockNumber, + // _balances is the first slot in ERC20 + slot: 0, + // = 1000000 * (10**18), initial supply + wantValue: "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000", + }, + { + name: "sad: address which is not in contract storage", + contractAddr: s.fundedAccEthAddr, + address: recipient, + blockNumber: deployContractBlockNumber, + slot: 0, + wantValue: "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + value, err := s.backend.GetStorageAt( + tc.contractAddr, + generateStorageKey(tc.address, tc.slot), + rpc.BlockNumberOrHash{ + BlockNumber: &tc.blockNumber, + }, + ) + s.Require().NoError(err) + s.Require().NotNil(value) + s.Require().Equal(tc.wantValue, value.String()) + }) + } +} + +func (s *BackendSuite) TestGetBalance() { + testCases := []struct { + name string + blockNumber rpc.BlockNumber + address gethcommon.Address + wantPositiveBalance bool + }{ + { + name: "happy: funded account balance", + address: s.fundedAccEthAddr, + blockNumber: transferTxBlockNumber, + wantPositiveBalance: true, + }, + { + name: "happy: recipient balance at block 1", + address: recipient, + blockNumber: rpc.NewBlockNumber(big.NewInt(1)), + wantPositiveBalance: false, + }, + { + name: "happy: recipient balance after transfer", + address: recipient, + blockNumber: transferTxBlockNumber, + wantPositiveBalance: true, + }, + { + name: "sad: not existing account", + address: evmtest.NewEthPrivAcc().EthAddr, + blockNumber: transferTxBlockNumber, + wantPositiveBalance: false, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + balance, err := s.backend.GetBalance( + tc.address, + rpc.BlockNumberOrHash{ + BlockNumber: &tc.blockNumber, + }, + ) + s.Require().NoError(err) + s.Require().NotNil(balance) + if tc.wantPositiveBalance { + s.Require().Greater(balance.ToInt().Int64(), int64(0)) + } else { + s.Require().Equal(balance.ToInt().Int64(), int64(0)) + } + }) + } +} + +// generateStorageKey produces the storage key from address and slot (order of the variable in solidity declaration) +func generateStorageKey(key gethcommon.Address, slot uint64) string { + // Prepare the key and slot as 32-byte values + keyBytes := gethcommon.LeftPadBytes(key.Bytes(), 32) + slotBytes := gethcommon.LeftPadBytes(new(big.Int).SetUint64(slot).Bytes(), 32) + + // Concatenate key and slot + data := append(keyBytes, slotBytes...) + + // Hash the data using Keccak256 + hash := sha3.NewLegacyKeccak256() + hash.Write(data) + return gethcommon.BytesToHash(hash.Sum(nil)).Hex() +} diff --git a/eth/rpc/backend/backend.go b/eth/rpc/backend/backend.go index 2f569125f..7383fe600 100644 --- a/eth/rpc/backend/backend.go +++ b/eth/rpc/backend/backend.go @@ -37,11 +37,7 @@ func NewBackend( allowUnprotectedTxs bool, indexer eth.EVMTxIndexer, ) *Backend { - chainID, err := eth.ParseEthChainID(clientCtx.ChainID) - if err != nil { - panic(err) - } - + chainID := eth.ParseEthChainID(clientCtx.ChainID) appConf, err := config.GetConfig(ctx.Viper) if err != nil { panic(err) diff --git a/eth/rpc/backend/backend_suite_test.go b/eth/rpc/backend/backend_suite_test.go index 1914bf865..540bdddb4 100644 --- a/eth/rpc/backend/backend_suite_test.go +++ b/eth/rpc/backend/backend_suite_test.go @@ -1,8 +1,11 @@ package backend_test import ( + "context" + "fmt" "math/big" "testing" + "time" "crypto/ecdsa" @@ -13,6 +16,8 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/suite" + "github.com/NibiruChain/nibiru/v2/x/evm/embeds" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" "github.com/NibiruChain/nibiru/v2/eth/rpc" @@ -30,9 +35,14 @@ import ( var recipient = evmtest.NewEthPrivAcc().EthAddr var amountToSend = evm.NativeToWei(big.NewInt(1)) + var transferTxBlockNumber rpc.BlockNumber +var transferTxBlockHash gethcommon.Hash var transferTxHash gethcommon.Hash +var testContractAddress gethcommon.Address +var deployContractBlockNumber rpc.BlockNumber + type BackendSuite struct { suite.Suite cfg testnetwork.Config @@ -72,39 +82,101 @@ func (s *BackendSuite) SetupSuite() { txResp, err := testnetwork.FillWalletFromValidator( s.fundedAccNibiAddr, funds, s.node, eth.EthBaseDenom, ) - s.Require().NoError(err, txResp.TxHash) + s.Require().NoError(err) + s.Require().NotNil(txResp.TxHash) s.NoError(s.network.WaitForNextBlock()) - // Send 1 Transfer TX and use the results in the tests - transferTxBlockNumber, transferTxHash = s.sendNibiViaEthTransfer(recipient, amountToSend) + // Send Transfer TX and use the results in the tests + s.Require().NoError(err) + transferTxHash = s.SendNibiViaEthTransfer(recipient, amountToSend, true) + blockNumber, blockHash := WaitForReceipt(s, transferTxHash) + s.Require().NotNil(blockNumber) + s.Require().NotNil(blockHash) + transferTxBlockNumber = rpc.NewBlockNumber(blockNumber) + transferTxBlockHash = *blockHash + + // Deploy test erc20 contract + deployContractTxHash, contractAddress := s.DeployTestContract(true) + testContractAddress = contractAddress + blockNumber, blockHash = WaitForReceipt(s, deployContractTxHash) + s.Require().NotNil(blockNumber) + s.Require().NotNil(blockHash) + deployContractBlockNumber = rpc.NewBlockNumber(blockNumber) } // SendNibiViaEthTransfer sends nibi using the eth rpc backend -func (s *BackendSuite) sendNibiViaEthTransfer( +func (s *BackendSuite) SendNibiViaEthTransfer( to gethcommon.Address, amount *big.Int, -) (rpc.BlockNumber, gethcommon.Hash) { - block, err := s.backend.BlockNumber() + waitForNextBlock bool, +) gethcommon.Hash { + nonce, err := s.backend.GetTransactionCount(s.fundedAccEthAddr, rpc.EthPendingBlockNumber) s.Require().NoError(err) - s.NoError(err) - - signer := gethcore.LatestSignerForChainID(s.ethChainID) - gasPrice := evm.NativeToWei(big.NewInt(1)) - tx, err := gethcore.SignNewTx( - s.fundedAccPrivateKey, - signer, + return SendTransaction( + s, &gethcore.LegacyTx{ To: &to, + Nonce: uint64(*nonce), Value: amount, Gas: params.TxGas, - GasPrice: gasPrice, - }) + GasPrice: big.NewInt(1), + }, + waitForNextBlock, + ) +} + +// DeployTestContract deploys test erc20 contract +func (s *BackendSuite) DeployTestContract(waitForNextBlock bool) (gethcommon.Hash, gethcommon.Address) { + packedArgs, err := embeds.SmartContract_TestERC20.ABI.Pack("") s.Require().NoError(err) - txBz, err := tx.MarshalBinary() + bytecodeForCall := append(embeds.SmartContract_TestERC20.Bytecode, packedArgs...) + nonce, err := s.backend.GetTransactionCount(s.fundedAccEthAddr, rpc.EthPendingBlockNumber) + s.Require().NoError(err) + + txHash := SendTransaction( + s, + &gethcore.LegacyTx{ + Nonce: uint64(*nonce), + Data: bytecodeForCall, + Gas: 1500_000, + GasPrice: big.NewInt(1), + }, + waitForNextBlock, + ) + contractAddr := crypto.CreateAddress(s.fundedAccEthAddr, (uint64)(*nonce)) + return txHash, contractAddr +} + +// SendTransaction signs and sends raw ethereum transaction +func SendTransaction(s *BackendSuite, tx *gethcore.LegacyTx, waitForNextBlock bool) gethcommon.Hash { + signer := gethcore.LatestSignerForChainID(s.ethChainID) + signedTx, err := gethcore.SignNewTx(s.fundedAccPrivateKey, signer, tx) + s.Require().NoError(err) + txBz, err := signedTx.MarshalBinary() s.Require().NoError(err) txHash, err := s.backend.SendRawTransaction(txBz) s.Require().NoError(err) - s.Require().NoError(s.network.WaitForNextBlock()) + if waitForNextBlock { + s.Require().NoError(s.network.WaitForNextBlock()) + } + return txHash +} - return rpc.NewBlockNumber(big.NewInt(int64(block) + 1)), txHash +func WaitForReceipt(s *BackendSuite, txHash gethcommon.Hash) (*big.Int, *gethcommon.Hash) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + for { + receipt, err := s.backend.GetTransactionReceipt(txHash) + if err == nil { + return receipt.BlockNumber, &receipt.BlockHash + } + select { + case <-ctx.Done(): + fmt.Println("Timeout reached, transaction not included in a block yet.") + return nil, nil + default: + time.Sleep(1 * time.Second) + } + } } diff --git a/eth/rpc/backend/blocks.go b/eth/rpc/backend/blocks.go index ea3e38f95..2b90c55c6 100644 --- a/eth/rpc/backend/blocks.go +++ b/eth/rpc/backend/blocks.go @@ -13,7 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/cosmos/gogoproto/proto" - "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" @@ -29,8 +29,7 @@ import ( // BlockNumber returns the current block number in abci app state. Because abci // app state could lag behind from tendermint latest block, it's more stable for -// the client to use the latest block number in abci app state than tendermint -// rpc. +// the client to use the latest block number in abci app state than tendermint rpc. func (b *Backend) BlockNumber() (hexutil.Uint64, error) { // do any grpc query, ignore the response and use the returned block height var header metadata.MD @@ -87,7 +86,7 @@ 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 common.Hash, fullTx bool) (map[string]interface{}, error) { +func (b *Backend) GetBlockByHash(hash gethcommon.Hash, fullTx bool) (map[string]interface{}, error) { resBlock, err := b.TendermintBlockByHash(hash) if err != nil { return nil, err @@ -115,7 +114,7 @@ func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]inte // GetBlockTransactionCountByHash returns the number of Ethereum transactions in // the block identified by hash. -func (b *Backend) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint { +func (b *Backend) GetBlockTransactionCountByHash(hash gethcommon.Hash) *hexutil.Uint { sc, ok := b.clientCtx.Client.(tmrpcclient.SignClient) if !ok { b.logger.Error("invalid rpc client") @@ -202,7 +201,7 @@ func (b *Backend) TendermintBlockResultByNumber(height *int64) (*tmrpctypes.Resu } // TendermintBlockByHash returns a Tendermint-formatted block by block number -func (b *Backend) TendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) { +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") @@ -240,7 +239,7 @@ func (b *Backend) BlockNumberFromTendermint(blockNrOrHash rpc.BlockNumberOrHash) } // BlockNumberFromTendermintByHash returns the block height of given block hash -func (b *Backend) BlockNumberFromTendermintByHash(blockHash common.Hash) (*big.Int, error) { +func (b *Backend) BlockNumberFromTendermintByHash(blockHash gethcommon.Hash) (*big.Int, error) { resBlock, err := b.TendermintBlockByHash(blockHash) if err != nil { return nil, err @@ -294,7 +293,6 @@ func (b *Backend) EthMsgsFromTendermintBlock( result = append(result, ethMsg) } } - return result } @@ -329,36 +327,6 @@ func (b *Backend) HeaderByNumber(blockNum rpc.BlockNumber) (*gethcore.Header, er return ethHeader, nil } -// HeaderByHash returns the block header identified by hash. -func (b *Backend) HeaderByHash(blockHash common.Hash) (*gethcore.Header, error) { - resBlock, err := b.TendermintBlockByHash(blockHash) - if err != nil { - return nil, err - } - if resBlock == nil { - return nil, errors.Errorf("block not found for hash %s", blockHash.Hex()) - } - - blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height) - if err != nil { - return nil, errors.Errorf("block result not found for height %d", resBlock.Block.Height) - } - - bloom, err := b.BlockBloom(blockRes) - if err != nil { - b.logger.Debug("HeaderByHash BlockBloom failed", "height", resBlock.Block.Height) - } - - baseFee, err := b.BaseFee(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) - } - - ethHeader := rpc.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee) - return ethHeader, nil -} - // BlockBloom query block bloom filter from block results func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (gethcore.Bloom, error) { msgType := proto.MessageName((*evm.EventBlockBloom)(nil)) @@ -399,7 +367,7 @@ func (b *Backend) RPCBlockFromTendermintBlock( msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes) for txIndex, ethMsg := range msgs { if !fullTx { - hash := common.HexToHash(ethMsg.Hash) + hash := gethcommon.HexToHash(ethMsg.Hash) ethRPCTxs = append(ethRPCTxs, hash) continue } @@ -409,7 +377,7 @@ func (b *Backend) RPCBlockFromTendermintBlock( index := uint64(txIndex) //#nosec G701 -- checked for int overflow already rpcTx, err := rpc.NewRPCTxFromEthTx( tx, - common.BytesToHash(block.Hash()), + gethcommon.BytesToHash(block.Hash()), height, index, baseFee, @@ -443,7 +411,7 @@ func (b *Backend) RPCBlockFromTendermintBlock( "error", err.Error(), ) // use zero address as the validator operator address - validatorAccAddr = sdk.AccAddress(common.Address{}.Bytes()) + validatorAccAddr = sdk.AccAddress(gethcommon.Address{}.Bytes()) } else { validatorAccAddr, err = sdk.AccAddressFromBech32(res.AccountAddress) if err != nil { @@ -451,7 +419,7 @@ func (b *Backend) RPCBlockFromTendermintBlock( } } - validatorAddr := common.BytesToAddress(validatorAccAddr) + validatorAddr := gethcommon.BytesToAddress(validatorAccAddr) gasLimit, err := rpc.BlockMaxGasFromConsensusParams(ctx, b.clientCtx, block.Height) if err != nil { diff --git a/eth/rpc/backend/blocks_test.go b/eth/rpc/backend/blocks_test.go index 8d04c814f..4bb5d6511 100644 --- a/eth/rpc/backend/blocks_test.go +++ b/eth/rpc/backend/blocks_test.go @@ -1,9 +1,6 @@ package backend_test import ( - "context" - "math/big" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/NibiruChain/nibiru/v2/eth/rpc" @@ -17,23 +14,96 @@ func (s *BackendSuite) TestBlockNumber() { s.Greater(blockHeightU64, uint64(1)) latestHeight, _ := s.network.LatestHeight() - wantFullTx := true - resp, err := s.backend.GetBlockByNumber( - rpc.NewBlockNumber(big.NewInt(latestHeight)), - wantFullTx, - ) + resp, err := s.backend.BlockNumber() s.Require().NoError(err, resp) + s.Require().Equal(uint64(latestHeight), uint64(blockHeight)) +} + +func (s *BackendSuite) TestGetBlockByNumberr() { + block, err := s.backend.GetBlockByNumber(transferTxBlockNumber, true) + s.Require().NoError(err) + s.Require().NotNil(block) + s.Require().Greater(len(block["transactions"].([]interface{})), 0) + s.Require().NotNil(block["size"]) + s.Require().NotNil(block["nonce"]) + s.Require().Equal(int64(block["number"].(hexutil.Uint64)), transferTxBlockNumber.Int64()) +} + +func (s *BackendSuite) TestGetBlockByHash() { + blockMap, err := s.backend.GetBlockByHash(transferTxBlockHash, true) + s.Require().NoError(err) + AssertBlockContents(s, blockMap) +} + +func (s *BackendSuite) TestBlockNumberFromTendermint() { + testCases := []struct { + name string + blockNrOrHash rpc.BlockNumberOrHash + wantBlockNumber rpc.BlockNumber + wantErr string + }{ + { + name: "happy: block number specified", + blockNrOrHash: rpc.BlockNumberOrHash{ + BlockNumber: &transferTxBlockNumber, + }, + wantBlockNumber: transferTxBlockNumber, + wantErr: "", + }, + { + name: "happy: block hash specified", + blockNrOrHash: rpc.BlockNumberOrHash{ + BlockHash: &transferTxBlockHash, + }, + wantBlockNumber: transferTxBlockNumber, + wantErr: "", + }, + { + name: "sad: neither block number nor hash specified", + blockNrOrHash: rpc.BlockNumberOrHash{}, + wantBlockNumber: 0, + wantErr: "BlockHash and BlockNumber cannot be both nil", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + blockNumber, err := s.backend.BlockNumberFromTendermint(tc.blockNrOrHash) + + if tc.wantErr != "" { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.Require().Equal(tc.wantBlockNumber, blockNumber) + }) + } +} + +func (s *BackendSuite) TestEthBlockByNumber() { + block, err := s.backend.EthBlockByNumber(transferTxBlockNumber) + s.Require().NoError(err) + s.Require().NotNil(block) + s.Require().Equal(transferTxBlockNumber.Int64(), block.Number().Int64()) + s.Require().Greater(block.Transactions().Len(), 0) + s.Require().NotNil(block.ParentHash()) + s.Require().NotNil(block.UncleHash()) +} + +func (s *BackendSuite) TestGetBlockTransactionCountByHash() { + txCount := s.backend.GetBlockTransactionCountByHash(transferTxBlockHash) + s.Require().Greater((uint64)(*txCount), uint64(0)) +} + +func (s *BackendSuite) TestGetBlockTransactionCountByNumber() { + txCount := s.backend.GetBlockTransactionCountByNumber(transferTxBlockNumber) + s.Require().Greater((uint64)(*txCount), uint64(0)) +} - // TODO: test backend.GetBlockByHash - // s.backend.GetBlockByHash() - block, err := s.node.RPCClient.Block( - context.Background(), - &latestHeight, - ) - s.NoError(err, block) - blockResults, err := s.node.RPCClient.BlockResults( - context.Background(), - &latestHeight, - ) - s.NoError(err, blockResults) +func AssertBlockContents(s *BackendSuite, blockMap map[string]interface{}) { + s.Require().NotNil(blockMap) + s.Require().Greater(len(blockMap["transactions"].([]interface{})), 0) + s.Require().NotNil(blockMap["size"]) + s.Require().NotNil(blockMap["nonce"]) + s.Require().Equal(int64(blockMap["number"].(hexutil.Uint64)), transferTxBlockNumber.Int64()) } diff --git a/eth/rpc/backend/call_tx.go b/eth/rpc/backend/call_tx.go index 12f7ce378..4b64997e2 100644 --- a/eth/rpc/backend/call_tx.go +++ b/eth/rpc/backend/call_tx.go @@ -19,88 +19,10 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/eth/rpc" "github.com/NibiruChain/nibiru/v2/x/evm" ) -// Resend accepts an existing transaction and a new gas price and limit. It will remove -// the given transaction from the pool and reinsert it with the new gas price and limit. -func (b *Backend) Resend(args evm.JsonTxArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { - if args.Nonce == nil { - return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec") - } - - args, err := b.SetTxDefaults(args) - if err != nil { - return common.Hash{}, err - } - - // The signer used should always be the 'latest' known one because we expect - // signers to be backwards-compatible with old transactions. - eip155ChainID, err := eth.ParseEthChainID(b.clientCtx.ChainID) - if err != nil { - return common.Hash{}, err - } - - cfg := b.ChainConfig() - if cfg == nil { - cfg = evm.EthereumConfig(eip155ChainID) - } - - signer := gethcore.LatestSigner(cfg) - - matchTx := args.ToMsgEthTx().AsTransaction() - - // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. - price := matchTx.GasPrice() - if gasPrice != nil { - price = gasPrice.ToInt() - } - gas := matchTx.Gas() - if gasLimit != nil { - gas = uint64(*gasLimit) - } - if err := rpc.CheckTxFee(price, gas, b.RPCTxFeeCap()); err != nil { - return common.Hash{}, err - } - - pending, err := b.PendingTransactions() - if err != nil { - return common.Hash{}, err - } - - for _, tx := range pending { - p, err := evm.UnwrapEthereumMsg(tx, common.Hash{}) - if err != nil { - // not valid ethereum tx - continue - } - - pTx := p.AsTransaction() - - wantSigHash := signer.Hash(matchTx) - pFrom, err := gethcore.Sender(signer, pTx) - if err != nil { - continue - } - - if pFrom == *args.From && signer.Hash(pTx) == wantSigHash { - // Match. Re-sign and send the transaction. - if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 { - args.GasPrice = gasPrice - } - if gasLimit != nil && *gasLimit != 0 { - args.Gas = gasLimit - } - - return b.SendTransaction(args) // TODO: this calls SetTxDefaults again, refactor to avoid calling it twice - } - } - - return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) -} - // SendRawTransaction send a raw Ethereum transaction. func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { // RLP decode raw transaction bytes @@ -111,7 +33,7 @@ func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { } // check the local node config in case unprotected txs are disabled - if !b.UnprotectedAllowed() && !tx.Protected() { + if !b.allowUnprotectedTxs && !tx.Protected() { // Ensure only eip155 signed transactions are submitted if EIP155Required is set. return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") } @@ -233,9 +155,9 @@ func (b *Backend) SetTxDefaults(args evm.JsonTxArgs) (evm.JsonTxArgs, error) { if args.Value == nil { args.Value = new(hexutil.Big) } - if args.Nonce == nil { + if args.Nonce == nil && args.From != nil { // get the nonce from the account retriever - // ignore error in case tge account doesn't exist yet + // ignore error in case the account doesn't exist yet nonce, _ := b.getAccountNonce(*args.From, true, 0, b.logger) // #nosec G703s args.Nonce = (*hexutil.Uint64)(&nonce) } @@ -280,7 +202,7 @@ func (b *Backend) SetTxDefaults(args evm.JsonTxArgs) (evm.JsonTxArgs, error) { Nonce: args.Nonce, } - blockNr := rpc.NewBlockNumber(big.NewInt(0)) + blockNr := rpc.EthPendingBlockNumber estimated, err := b.EstimateGas(callArgs, &blockNr) if err != nil { return args, err @@ -416,7 +338,7 @@ func (b *Backend) GasPrice() (*hexutil.Big, error) { if err != nil { return nil, err } - minGasPriceInt := minGasPrice.TruncateInt().BigInt() + minGasPriceInt := minGasPrice if result.Cmp(minGasPriceInt) < 0 { result = minGasPriceInt } diff --git a/eth/rpc/backend/call_tx_test.go b/eth/rpc/backend/call_tx_test.go index f0dcea536..3267d8fd9 100644 --- a/eth/rpc/backend/call_tx_test.go +++ b/eth/rpc/backend/call_tx_test.go @@ -1 +1,90 @@ package backend_test + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/NibiruChain/nibiru/v2/app/appconst" + "github.com/NibiruChain/nibiru/v2/eth/rpc" + "github.com/NibiruChain/nibiru/v2/x/evm" +) + +func (s *BackendSuite) TestSetTxDefaults() { + testCases := []struct { + name string + jsonTxArgs evm.JsonTxArgs + wantErr string + }{ + { + name: "happy: minimal args set", + jsonTxArgs: evm.JsonTxArgs{ + From: &s.fundedAccEthAddr, + To: &recipient, + Value: (*hexutil.Big)(evm.NativeToWei(big.NewInt(1))), + }, + wantErr: "", + }, + { + name: "happy: gas price set", + jsonTxArgs: evm.JsonTxArgs{ + From: &s.fundedAccEthAddr, + To: &recipient, + GasPrice: (*hexutil.Big)(evm.NativeToWei(big.NewInt(1))), + Value: (*hexutil.Big)(evm.NativeToWei(big.NewInt(1))), + }, + wantErr: "", + }, + { + name: "sad: no to (contract creation) and no data", + jsonTxArgs: evm.JsonTxArgs{ + From: &s.fundedAccEthAddr, + }, + wantErr: "contract creation without any data provided", + }, + { + name: "sad: transfer without from specified generates new empty account", + jsonTxArgs: evm.JsonTxArgs{ + To: &recipient, + Value: (*hexutil.Big)(evm.NativeToWei(big.NewInt(1))), + }, + wantErr: "insufficient balance", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + jsonTxArgs, err := s.backend.SetTxDefaults(tc.jsonTxArgs) + + if tc.wantErr != "" { + s.Require().Error(err) + return + } + s.Require().NoError(err) + s.Require().NotNil(jsonTxArgs.Nonce) + s.Require().NotNil(jsonTxArgs.Gas) + s.Require().Greater(*jsonTxArgs.Nonce, hexutil.Uint64(0)) + s.Require().Greater(*jsonTxArgs.Gas, hexutil.Uint64(0)) + s.Require().Equal(jsonTxArgs.ChainID.ToInt().Int64(), appconst.ETH_CHAIN_ID_DEFAULT) + }) + } +} + +func (s *BackendSuite) TestDoCall() { + jsonTxArgs := evm.JsonTxArgs{ + From: &s.fundedAccEthAddr, + To: &recipient, + Value: (*hexutil.Big)(evm.NativeToWei(big.NewInt(1))), + } + txResponse, err := s.backend.DoCall(jsonTxArgs, rpc.EthPendingBlockNumber) + s.Require().NoError(err) + s.Require().NotNil(txResponse) + s.Require().Greater(txResponse.GasUsed, uint64(0)) +} + +func (s *BackendSuite) TestGasPrice() { + gasPrice, err := s.backend.GasPrice() + s.Require().NoError(err) + s.Require().NotNil(gasPrice) + s.Require().Greater(gasPrice.ToInt().Int64(), int64(0)) +} diff --git a/eth/rpc/backend/chain_info.go b/eth/rpc/backend/chain_info.go index 6b478a577..efeededb8 100644 --- a/eth/rpc/backend/chain_info.go +++ b/eth/rpc/backend/chain_info.go @@ -5,7 +5,6 @@ import ( "fmt" "math/big" - sdkmath "cosmossdk.io/math" tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -21,23 +20,8 @@ import ( ) // ChainID is the EIP-155 replay-protection chain id for the current ethereum chain config. -func (b *Backend) ChainID() (*hexutil.Big, error) { - eip155ChainID, err := eth.ParseEthChainID(b.clientCtx.ChainID) - if err != nil { - panic(err) - } - // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config - bn, err := b.BlockNumber() - if err != nil { - b.logger.Debug("failed to fetch latest block number", "error", err.Error()) - return (*hexutil.Big)(eip155ChainID), nil - } - - if config := b.ChainConfig(); config.IsEIP155(new(big.Int).SetUint64(uint64(bn))) { - return (*hexutil.Big)(config.ChainID), nil - } - - return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block") +func (b *Backend) ChainID() *hexutil.Big { + return (*hexutil.Big)(eth.ParseEthChainID(b.clientCtx.ChainID)) } // ChainConfig returns the latest ethereum chain configuration @@ -46,7 +30,6 @@ func (b *Backend) ChainConfig() *params.ChainConfig { if err != nil { return nil } - return evm.EthereumConfig(b.chainID) } @@ -100,7 +83,7 @@ func (b *Backend) PendingTransactions() ([]*sdk.Tx, error) { // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. func (b *Backend) FeeHistory( userBlockCount gethrpc.DecimalOrHex, // number blocks to fetch, maximum is 100 - lastBlock gethrpc.BlockNumber, // the block to start search , to oldest + lastBlock gethrpc.BlockNumber, // the block to start search, to oldest rewardPercentiles []float64, // percentiles to fetch reward ) (*rpc.FeeHistoryResult, error) { blockEnd := int64(lastBlock) //#nosec G701 -- checked for int overflow already @@ -162,7 +145,7 @@ func (b *Backend) FeeHistory( } oneFeeHistory := rpc.OneFeeHistory{} - err = b.processBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory) + err = b.retrieveEVMTxFeesFromBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory) if err != nil { return nil, err } @@ -194,18 +177,16 @@ func (b *Backend) FeeHistory( return &feeHistory, nil } -// SuggestGasTipCap: Not yet supported. Returns 0 as the suggested tip cap. After -// implementing tx prioritization, this function can come to life. +// SuggestGasTipCap Not yet supported. Returns 0 as the suggested tip cap. +// After implementing tx prioritization, this function can come to life. func (b *Backend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) { maxDelta := big.NewInt(0) return maxDelta, nil } -func DefaultMinGasPrice() sdkmath.LegacyDec { return sdkmath.LegacyZeroDec() } - -// GlobalMinGasPrice returns the minimum gas price for all nodes. This is -// distinct from the individual configuration set by the validator set. -func (b *Backend) GlobalMinGasPrice() (sdkmath.LegacyDec, error) { +// GlobalMinGasPrice returns the minimum gas price for all nodes. +// This is distinct from the individual configuration set by the validator set. +func (b *Backend) GlobalMinGasPrice() (*big.Int, error) { // TODO: feat(eth): dynamic fees - return DefaultMinGasPrice(), nil + return big.NewInt(0), nil } diff --git a/eth/rpc/backend/chain_info_test.go b/eth/rpc/backend/chain_info_test.go index f0dcea536..923764888 100644 --- a/eth/rpc/backend/chain_info_test.go +++ b/eth/rpc/backend/chain_info_test.go @@ -1 +1,93 @@ package backend_test + +import ( + "math/big" + + gethrpc "github.com/ethereum/go-ethereum/rpc" + + "github.com/NibiruChain/nibiru/v2/app/appconst" + "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" +) + +func (s *BackendSuite) TestChainID() { + s.Require().Equal(appconst.ETH_CHAIN_ID_DEFAULT, s.backend.ChainID().ToInt().Int64()) +} + +func (s *BackendSuite) TestChainConfig() { + config := s.backend.ChainConfig() + s.Require().Equal(appconst.ETH_CHAIN_ID_DEFAULT, config.ChainID.Int64()) + s.Require().Equal(int64(0), config.LondonBlock.Int64()) +} + +func (s *BackendSuite) TestBaseFee() { + resBlock, err := s.backend.TendermintBlockResultByNumber(transferTxBlockNumber.TmHeight()) + s.Require().NoError(err) + baseFee, err := s.backend.BaseFee(resBlock) + s.Require().NoError(err) + s.Require().Equal(evm.BASE_FEE_MICRONIBI, baseFee) +} + +func (s *BackendSuite) TestCurrentHeader() { + currentHeader, err := s.backend.CurrentHeader() + s.Require().NoError(err) + s.Require().NotNil(currentHeader) + s.Require().GreaterOrEqual(currentHeader.Number.Int64(), transferTxBlockNumber.Int64()) +} + +func (s *BackendSuite) TestPendingTransactions() { + // Create pending tx: don't wait for next block + randomEthAddr := evmtest.NewEthPrivAcc().EthAddr + txHash := s.SendNibiViaEthTransfer(randomEthAddr, big.NewInt(123), false) + txs, err := s.backend.PendingTransactions() + s.Require().NoError(err) + s.Require().NotNil(txs) + s.Require().NotNil(txHash) + s.Require().Greater(len(txs), 0) + txFound := false + for _, tx := range txs { + msg, err := evm.UnwrapEthereumMsg(tx, txHash) + if err != nil { + // not ethereum tx + continue + } + if msg.Hash == txHash.String() { + txFound = true + } + } + s.Require().True(txFound, "pending tx not found") +} + +func (s *BackendSuite) TestFeeHistory() { + currentBlock, err := s.backend.BlockNumber() + s.Require().NoError(err) + blockCount := 2 // blocks to search backwards from the current block + percentiles := []float64{50, 100} + + res, err := s.backend.FeeHistory( + (gethrpc.DecimalOrHex)(blockCount), + gethrpc.BlockNumber(int64(currentBlock)), + percentiles, + ) + s.Require().NoError(err) + s.Require().NotNil(res) + s.Require().Len(res.Reward, blockCount) + s.Require().Len(res.BaseFee, blockCount+1) + s.Require().Len(res.GasUsedRatio, len(percentiles)) + + for _, gasUsed := range res.GasUsedRatio { + s.Require().LessOrEqual(gasUsed, float64(1)) + } +} + +func (s *BackendSuite) TestSuggestGasTipCap() { + tipCap, err := s.backend.SuggestGasTipCap(big.NewInt(1)) + s.Require().NoError(err) + s.Require().Equal(big.NewInt(0), tipCap) +} + +func (s *BackendSuite) TestGlobalMinGasPrice() { + gasPrice, err := s.backend.GlobalMinGasPrice() + s.Require().NoError(err) + s.Require().Equal(big.NewInt(0), gasPrice) +} diff --git a/eth/rpc/backend/evm_query_client_test.go b/eth/rpc/backend/evm_query_client_test.go deleted file mode 100644 index f0dcea536..000000000 --- a/eth/rpc/backend/evm_query_client_test.go +++ /dev/null @@ -1 +0,0 @@ -package backend_test diff --git a/eth/rpc/backend/filters.go b/eth/rpc/backend/filters.go deleted file mode 100644 index 02fa6c11f..000000000 --- a/eth/rpc/backend/filters.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023-2024 Nibi, Inc. -package backend - -import ( - "github.com/ethereum/go-ethereum/common" - gethcore "github.com/ethereum/go-ethereum/core/types" - "github.com/pkg/errors" -) - -// GetLogs returns all the logs from all the ethereum transactions in a block. -func (b *Backend) GetLogs(hash common.Hash) ([][]*gethcore.Log, error) { - resBlock, err := b.TendermintBlockByHash(hash) - if err != nil { - return nil, err - } - if resBlock == nil { - return nil, errors.Errorf("block not found for hash %s", hash) - } - return b.GetLogsByHeight(&resBlock.Block.Header.Height) -} - -// GetLogsByHeight returns all the logs from all the ethereum transactions in a block. -func (b *Backend) GetLogsByHeight(height *int64) ([][]*gethcore.Log, error) { - // NOTE: we query the state in case the tx result logs are not persisted after an upgrade. - blockRes, err := b.TendermintBlockResultByNumber(height) - if err != nil { - return nil, err - } - - return GetLogsFromBlockResults(blockRes) -} - -// BloomStatus returns: -// - bloomBitsBlocks: The number of blocks a single bloom bit section vector -// contains on the server side. -// - bloomSections: The number of processed sections maintained by the indexer. -func (b *Backend) BloomStatus() ( - bloomBitBlocks, bloomSections uint64, -) { - return 4096, 0 -} diff --git a/eth/rpc/backend/filters_test.go b/eth/rpc/backend/filters_test.go deleted file mode 100644 index f0dcea536..000000000 --- a/eth/rpc/backend/filters_test.go +++ /dev/null @@ -1 +0,0 @@ -package backend_test diff --git a/eth/rpc/backend/node_info.go b/eth/rpc/backend/node_info.go index 93f3cc32b..66d56d76c 100644 --- a/eth/rpc/backend/node_info.go +++ b/eth/rpc/backend/node_info.go @@ -4,7 +4,7 @@ package backend import ( "time" - "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/NibiruChain/nibiru/v2/eth" @@ -12,8 +12,8 @@ import ( ) // Accounts returns the list of accounts available to this node. -func (b *Backend) Accounts() ([]common.Address, error) { - addresses := make([]common.Address, 0) // return [] instead of nil if empty +func (b *Backend) Accounts() ([]gethcommon.Address, error) { + addresses := make([]gethcommon.Address, 0) // return [] instead of nil if empty infos, err := b.clientCtx.Keyring.List() if err != nil { @@ -26,14 +26,14 @@ func (b *Backend) Accounts() ([]common.Address, error) { return nil, err } addressBytes := pubKey.Address().Bytes() - addresses = append(addresses, common.BytesToAddress(addressBytes)) + addresses = append(addresses, gethcommon.BytesToAddress(addressBytes)) } return addresses, nil } // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not -// yet received the latest block headers from its pears. In case it is synchronizing: +// yet received the latest block headers from its peers. In case it is synchronizing: // - startingBlock: block number this node started to synchronize from // - currentBlock: block number this node is currently importing // - highestBlock: block number of the highest block header this node has received from peers @@ -58,12 +58,6 @@ func (b *Backend) Syncing() (interface{}, error) { }, nil } -// UnprotectedAllowed returns the node configuration value for allowing -// unprotected transactions (i.e not replay-protected) -func (b Backend) UnprotectedAllowed() bool { - return b.allowUnprotectedTxs -} - // RPCGasCap is the global gas cap for eth-call variants. func (b *Backend) RPCGasCap() uint64 { return b.cfg.JSONRPC.GasCap @@ -74,21 +68,11 @@ func (b *Backend) RPCEVMTimeout() time.Duration { return b.cfg.JSONRPC.EVMTimeout } -// RPCGasCap is the global gas cap for eth-call variants. -func (b *Backend) RPCTxFeeCap() float64 { - return b.cfg.JSONRPC.TxFeeCap -} - // RPCFilterCap is the limit for total number of filters that can be created func (b *Backend) RPCFilterCap() int32 { return b.cfg.JSONRPC.FilterCap } -// RPCFeeHistoryCap is the limit for total number of blocks that can be fetched -func (b *Backend) RPCFeeHistoryCap() int32 { - return b.cfg.JSONRPC.FeeHistoryCap -} - // RPCLogsCap defines the max number of results can be returned from single `eth_getLogs` query. func (b *Backend) RPCLogsCap() int32 { return b.cfg.JSONRPC.LogsCap @@ -101,7 +85,6 @@ func (b *Backend) RPCBlockRangeCap() int32 { // RPCMinGasPrice returns the minimum gas price for a transaction obtained from // the node config. If set value is 0, it will default to 20. - func (b *Backend) RPCMinGasPrice() int64 { evmParams, err := b.queryClient.Params(b.ctx, &evm.QueryParamsRequest{}) if err != nil { diff --git a/eth/rpc/backend/node_info_test.go b/eth/rpc/backend/node_info_test.go index f0dcea536..a41d2f66d 100644 --- a/eth/rpc/backend/node_info_test.go +++ b/eth/rpc/backend/node_info_test.go @@ -1 +1,45 @@ package backend_test + +import ( + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/v2/app/server/config" + "github.com/NibiruChain/nibiru/v2/eth" +) + +func (s *BackendSuite) TestAccounts() { + accounts, err := s.backend.Accounts() + s.Require().NoError(err) + s.Require().Greater(len(accounts), 0) + s.Require().Contains(accounts, gethcommon.BytesToAddress(s.node.ValAddress.Bytes())) +} + +func (s *BackendSuite) TestSyncing() { + syncing, err := s.backend.Syncing() + s.Require().NoError(err) + s.Require().False(syncing.(bool)) +} + +func (s *BackendSuite) TestRPCGasCap() { + s.Require().Equal(config.DefaultConfig().JSONRPC.GasCap, s.backend.RPCGasCap()) +} + +func (s *BackendSuite) TestRPCEVMTimeout() { + s.Require().Equal(config.DefaultConfig().JSONRPC.EVMTimeout, s.backend.RPCEVMTimeout()) +} + +func (s *BackendSuite) TestRPCFilterCap() { + s.Require().Equal(config.DefaultConfig().JSONRPC.FilterCap, s.backend.RPCFilterCap()) +} + +func (s *BackendSuite) TestRPCLogsCap() { + s.Require().Equal(config.DefaultConfig().JSONRPC.LogsCap, s.backend.RPCLogsCap()) +} + +func (s *BackendSuite) TestRPCBlockRangeCap() { + s.Require().Equal(config.DefaultConfig().JSONRPC.BlockRangeCap, s.backend.RPCBlockRangeCap()) +} + +func (s *BackendSuite) TestRPCMinGasPrice() { + s.Require().Equal(int64(eth.DefaultGasPrice), s.backend.RPCMinGasPrice()) +} diff --git a/eth/rpc/backend/sign_tx.go b/eth/rpc/backend/sign_tx.go deleted file mode 100644 index 7d3032ea3..000000000 --- a/eth/rpc/backend/sign_tx.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2023-2024 Nibi, Inc. -package backend - -import ( - "errors" - "fmt" - "math/big" - - errorsmod "cosmossdk.io/errors" - "github.com/cosmos/cosmos-sdk/client/flags" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - gethcore "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/signer/core/apitypes" - - "github.com/NibiruChain/nibiru/v2/x/evm" -) - -// SendTransaction sends transaction based on received args using Node's key to sign it -func (b *Backend) SendTransaction(args evm.JsonTxArgs) (common.Hash, error) { - // Look up the wallet containing the requested signer - _, err := b.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.GetFrom().Bytes())) - if err != nil { - b.logger.Error("failed to find key in keyring", "address", args.GetFrom(), "error", err.Error()) - return common.Hash{}, fmt.Errorf("failed to find key in the node's keyring; %s; %s", keystore.ErrNoMatch, err.Error()) - } - - if args.ChainID != nil && (b.chainID).Cmp((*big.Int)(args.ChainID)) != 0 { - return common.Hash{}, fmt.Errorf("chainId does not match node's (have=%v, want=%v)", args.ChainID, (*hexutil.Big)(b.chainID)) - } - - args, err = b.SetTxDefaults(args) - if err != nil { - return common.Hash{}, err - } - - bn, err := b.BlockNumber() - if err != nil { - b.logger.Debug("failed to fetch latest block number", "error", err.Error()) - return common.Hash{}, err - } - - signer := gethcore.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(uint64(bn))) - - // LegacyTx derives chainID from the signature. To make sure the msg.ValidateBasic makes - // the corresponding chainID validation, we need to sign the transaction before calling it - - // Sign transaction - msg := args.ToMsgEthTx() - if err := msg.Sign(signer, b.clientCtx.Keyring); err != nil { - b.logger.Debug("failed to sign tx", "error", err.Error()) - return common.Hash{}, err - } - - if err := msg.ValidateBasic(); err != nil { - b.logger.Debug("tx failed basic validation", "error", err.Error()) - return common.Hash{}, err - } - - // Query params to use the EVM denomination - res, err := b.queryClient.QueryClient.Params(b.ctx, &evm.QueryParamsRequest{}) - if err != nil { - b.logger.Error("failed to query evm params", "error", err.Error()) - return common.Hash{}, err - } - - // Assemble transaction from fields - tx, err := msg.BuildTx(b.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom) - if err != nil { - b.logger.Error("build cosmos tx failed", "error", err.Error()) - return common.Hash{}, err - } - - // Encode transaction by default Tx encoder - txEncoder := b.clientCtx.TxConfig.TxEncoder() - txBytes, err := txEncoder(tx) - if err != nil { - b.logger.Error("failed to encode eth tx using default encoder", "error", err.Error()) - return common.Hash{}, err - } - - ethTx := msg.AsTransaction() - - // check the local node config in case unprotected txs are disabled - if !b.UnprotectedAllowed() && !ethTx.Protected() { - // Ensure only eip155 signed transactions are submitted if EIP155Required is set. - return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") - } - - txHash := ethTx.Hash() - - // Broadcast transaction in sync mode (default) - // NOTE: If error is encountered on the node, the broadcast will not return an 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) - } - if err != nil { - b.logger.Error("failed to broadcast tx", "error", err.Error()) - return txHash, err - } - - // Return transaction hash - return txHash, nil -} - -// Sign signs the provided data using the private key of address via Geth's signature standard. -func (b *Backend) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { - from := sdk.AccAddress(address.Bytes()) - - _, err := b.clientCtx.Keyring.KeyByAddress(from) - if err != nil { - b.logger.Error("failed to find key in keyring", "address", address.String()) - return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error()) - } - - // Sign the requested hash with the wallet - signature, _, err := b.clientCtx.Keyring.SignByAddress(from, data) - if err != nil { - b.logger.Error("keyring.SignByAddress failed", "address", address.Hex()) - return nil, err - } - - signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} - -// SignTypedData signs EIP-712 conformant typed data -func (b *Backend) SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error) { - from := sdk.AccAddress(address.Bytes()) - - _, err := b.clientCtx.Keyring.KeyByAddress(from) - if err != nil { - b.logger.Error("failed to find key in keyring", "address", address.String()) - return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error()) - } - - sigHash, _, err := apitypes.TypedDataAndHash(typedData) - if err != nil { - return nil, err - } - - // Sign the requested hash with the wallet - signature, _, err := b.clientCtx.Keyring.SignByAddress(from, sigHash) - if err != nil { - b.logger.Error("keyring.SignByAddress failed", "address", address.Hex()) - return nil, err - } - - signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} diff --git a/eth/rpc/backend/sign_tx_test.go b/eth/rpc/backend/sign_tx_test.go deleted file mode 100644 index f0dcea536..000000000 --- a/eth/rpc/backend/sign_tx_test.go +++ /dev/null @@ -1 +0,0 @@ -package backend_test diff --git a/eth/rpc/backend/tracing.go b/eth/rpc/backend/tracing.go index 425e3d00e..4a20557cd 100644 --- a/eth/rpc/backend/tracing.go +++ b/eth/rpc/backend/tracing.go @@ -9,7 +9,7 @@ import ( tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/NibiruChain/nibiru/v2/eth/rpc" @@ -18,7 +18,7 @@ import ( // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (b *Backend) TraceTransaction(hash common.Hash, config *evm.TraceConfig) (interface{}, error) { +func (b *Backend) TraceTransaction(hash gethcommon.Hash, config *evm.TraceConfig) (interface{}, error) { // Get transaction by hash transaction, err := b.GetTxByEthHash(hash) if err != nil { @@ -101,7 +101,7 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evm.TraceConfig) (i Predecessors: predecessors, BlockNumber: blk.Block.Height, BlockTime: blk.Block.Time, - BlockHash: common.Bytes2Hex(blk.BlockID.Hash), + BlockHash: gethcommon.Bytes2Hex(blk.BlockID.Hash), ProposerAddress: sdk.ConsAddress(blk.Block.ProposerAddress), ChainId: b.chainID.Int64(), BlockMaxGas: cp.ConsensusParams.Block.MaxGas, @@ -191,7 +191,7 @@ func (b *Backend) TraceBlock(height rpc.BlockNumber, TraceConfig: config, BlockNumber: block.Block.Height, BlockTime: block.Block.Time, - BlockHash: common.Bytes2Hex(block.BlockID.Hash), + BlockHash: gethcommon.Bytes2Hex(block.BlockID.Hash), ProposerAddress: sdk.ConsAddress(block.Block.ProposerAddress), ChainId: b.chainID.Int64(), BlockMaxGas: cp.ConsensusParams.Block.MaxGas, @@ -240,7 +240,7 @@ func (b *Backend) TraceCall( Predecessors: nil, BlockNumber: blk.Block.Height, BlockTime: blk.Block.Time, - BlockHash: common.Bytes2Hex(blk.BlockID.Hash), + BlockHash: gethcommon.Bytes2Hex(blk.BlockID.Hash), ProposerAddress: sdk.ConsAddress(blk.Block.ProposerAddress), ChainId: b.chainID.Int64(), BlockMaxGas: cp.ConsensusParams.Block.MaxGas, diff --git a/eth/rpc/backend/tx_info.go b/eth/rpc/backend/tx_info.go index 9783ca747..c4c7f0de7 100644 --- a/eth/rpc/backend/tx_info.go +++ b/eth/rpc/backend/tx_info.go @@ -127,17 +127,22 @@ func (b *Backend) getTransactionByHashPending(txHash gethcommon.Hash) (*rpc.EthT return nil, nil } -// GetGasUsed returns gasUsed from transaction, patching to -// price * gas in the event the tx is reverted. -func (b *Backend) GetGasUsed(res *eth.TxResult, price *big.Int, gas uint64) uint64 { - if res.Failed && res.Height < b.cfg.JSONRPC.FixRevertGasRefundHeight { - return new(big.Int).Mul(price, new(big.Int).SetUint64(gas)).Uint64() - } - return res.GasUsed +// TransactionReceipt represents the results of a transaction. TransactionReceipt +// is an extension of gethcore.Receipt, the response type for the +// "eth_getTransactionReceipt" JSON-RPC method. +// Reason being, the gethcore.Receipt struct has an incorrect JSON struct tag on one +// field and doesn't marshal JSON as expected, so we embed and extend it here. +type TransactionReceipt struct { + gethcore.Receipt + + ContractAddress *gethcommon.Address `json:"contractAddress,omitempty"` + From gethcommon.Address + To *gethcommon.Address + EffectiveGasPrice *big.Int } // GetTransactionReceipt returns the transaction receipt identified by hash. -func (b *Backend) GetTransactionReceipt(hash gethcommon.Hash) (map[string]interface{}, error) { +func (b *Backend) GetTransactionReceipt(hash gethcommon.Hash) (*TransactionReceipt, error) { hexTx := hash.Hex() b.logger.Debug("eth_getTransactionReceipt", "hash", hexTx) @@ -175,17 +180,13 @@ func (b *Backend) GetTransactionReceipt(hash gethcommon.Hash) (map[string]interf } cumulativeGasUsed += res.CumulativeGasUsed - var status hexutil.Uint + var status uint64 = gethcore.ReceiptStatusSuccessful if res.Failed { - status = hexutil.Uint(gethcore.ReceiptStatusFailed) - } else { - status = hexutil.Uint(gethcore.ReceiptStatusSuccessful) - } - chainID, err := b.ChainID() - if err != nil { - return nil, err + status = gethcore.ReceiptStatusFailed } + chainID := b.ChainID() + from, err := ethMsg.GetSender(chainID.ToInt()) if err != nil { return nil, err @@ -213,40 +214,38 @@ func (b *Backend) GetTransactionReceipt(hash gethcommon.Hash) (map[string]interf return nil, errors.New("can't find index of ethereum tx") } - // TODO: refactor(evm-rpc-backend): Replace interface with gethcore.Receipt - // in eth_getTransactionReceipt - receipt := map[string]interface{}{ - // Consensus fields: These fields are defined by the Yellow Paper - "status": status, - "cumulativeGasUsed": hexutil.Uint64(cumulativeGasUsed), - "logsBloom": gethcore.BytesToBloom(gethcore.LogsBloom(logs)), - "logs": logs, + receipt := TransactionReceipt{ + Receipt: gethcore.Receipt{ + Type: ethMsg.AsTransaction().Type(), - // Implementation fields: These fields are added by geth when processing a transaction. - // They are stored in the chain database. - "transactionHash": hash, - "contractAddress": nil, - "gasUsed": hexutil.Uint64(b.GetGasUsed(res, txData.GetGasPrice(), txData.GetGas())), + // Consensus fields: These fields are defined by the Etheruem Yellow Paper + Status: status, + CumulativeGasUsed: cumulativeGasUsed, + Bloom: gethcore.BytesToBloom(gethcore.LogsBloom(logs)), + Logs: logs, - // Inclusion information: These fields provide information about the inclusion of the - // transaction corresponding to this receipt. - "blockHash": gethcommon.BytesToHash(resBlock.Block.Header.Hash()).Hex(), - "blockNumber": hexutil.Uint64(res.Height), - "transactionIndex": hexutil.Uint64(res.EthTxIndex), + // Implementation fields: These fields are added by geth when processing a transaction. + // They are stored in the chain database. + TxHash: hash, + GasUsed: res.GasUsed, - // sender and receiver (contract or EOA) addreses - "from": from, - "to": txData.GetTo(), - "type": hexutil.Uint(ethMsg.AsTransaction().Type()), + BlockHash: gethcommon.BytesToHash(resBlock.Block.Header.Hash()), + BlockNumber: big.NewInt(res.Height), + TransactionIndex: uint(res.EthTxIndex), + }, + ContractAddress: nil, + From: from, + To: txData.GetTo(), } if logs == nil { - receipt["logs"] = [][]*gethcore.Log{} + receipt.Logs = []*gethcore.Log{} } // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation if txData.GetTo() == nil { - receipt["contractAddress"] = crypto.CreateAddress(from, txData.GetNonce()) + addr := crypto.CreateAddress(from, txData.GetNonce()) + receipt.ContractAddress = &addr } if dynamicTx, ok := txData.(*evm.DynamicFeeTx); ok { @@ -255,11 +254,10 @@ func (b *Backend) GetTransactionReceipt(hash gethcommon.Hash) (map[string]interf // 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.EffectiveGasPriceWei(baseFee)) + receipt.EffectiveGasPrice = dynamicTx.EffectiveGasPriceWei(baseFee) } } - - return receipt, nil + return &receipt, nil } // GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. diff --git a/eth/rpc/backend/tx_info_test.go b/eth/rpc/backend/tx_info_test.go index d203b2bc4..3d14d60bd 100644 --- a/eth/rpc/backend/tx_info_test.go +++ b/eth/rpc/backend/tx_info_test.go @@ -1,12 +1,16 @@ package backend_test import ( + "encoding/json" "math/big" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + gethcore "github.com/ethereum/go-ethereum/core/types" "github.com/NibiruChain/nibiru/v2/eth/rpc" + "github.com/NibiruChain/nibiru/v2/eth/rpc/backend" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" ) func (s *BackendSuite) TestGetTransactionByHash() { @@ -73,13 +77,13 @@ func (s *BackendSuite) TestGetTransactionReceipt() { s.Require().NotNil(receipt) // Check fields - // s.Equal(s.fundedAccEthAddr, receipt.From) - // s.Equal(&recipient, receipt.To) - // s.Greater(receipt.GasUsed, uint64(0)) - // s.Equal(receipt.GasUsed, receipt.CumulativeGasUsed) - // s.Equal(tc.txHash, receipt.TxHash) - // s.Nil(receipt.ContractAddress) - // s.Require().Equal(gethcore.ReceiptStatusSuccessful, receipt.Status) + s.Equal(s.fundedAccEthAddr, receipt.From) + s.Equal(&recipient, receipt.To) + s.Greater(receipt.GasUsed, uint64(0)) + s.Equal(receipt.GasUsed, receipt.CumulativeGasUsed) + s.Equal(tc.txHash, receipt.TxHash) + s.Nil(receipt.ContractAddress) + s.Require().Equal(gethcore.ReceiptStatusSuccessful, receipt.Status) }) } } @@ -177,3 +181,38 @@ func AssertTxResults(s *BackendSuite, tx *rpc.EthTxJsonRPC, expectedTxHash gethc s.Require().Equal(expectedTxHash, tx.Hash) s.Require().Equal(uint64(1), uint64(*tx.TransactionIndex)) } + +func (s *BackendSuite) TestReceiptMarshalJson() { + toAddr := evmtest.NewEthPrivAcc().EthAddr + tr := backend.TransactionReceipt{ + Receipt: gethcore.Receipt{ + Type: 0, + PostState: []byte{}, + Status: 0, + CumulativeGasUsed: 0, + Bloom: [256]byte{}, + Logs: []*gethcore.Log{}, + TxHash: [32]byte{}, + ContractAddress: [20]byte{}, + GasUsed: 0, + BlockHash: [32]byte{}, + BlockNumber: &big.Int{}, + TransactionIndex: 0, + }, + ContractAddress: nil, + From: evmtest.NewEthPrivAcc().EthAddr, + To: &toAddr, + EffectiveGasPrice: big.NewInt(1), + } + + jsonBz, err := json.Marshal(tr) + s.Require().NoError(err) + + gethReceipt := new(gethcore.Receipt) + err = json.Unmarshal(jsonBz, gethReceipt) + s.Require().NoError(err) + + receipt := new(backend.TransactionReceipt) + err = json.Unmarshal(jsonBz, receipt) + s.Require().NoError(err) +} diff --git a/eth/rpc/backend/utils.go b/eth/rpc/backend/utils.go index 499bf107e..7083354c7 100644 --- a/eth/rpc/backend/utils.go +++ b/eth/rpc/backend/utils.go @@ -71,8 +71,8 @@ func (b *Backend) getAccountNonce(accAddr common.Address, pending bool, height i return nonce, nil } - // the account retriever doesn't include the uncommitted transactions on the - // nonce so we need to to manually add them. + // the account retriever doesn't include the uncommitted transactions on the nonce, + // so we need to manually add them. pendingTxs, err := b.PendingTransactions() if err != nil { logger.Error("failed to fetch pending transactions", "error", err.Error()) @@ -102,8 +102,10 @@ func (b *Backend) getAccountNonce(accAddr common.Address, pending bool, height i return nonce, nil } -// output: targetOneFeeHistory -func (b *Backend) processBlock( +// retrieveEVMTxFeesFromBlock goes through evm txs of the block, +// retrieves the gas fees and puts them into an object `targetOneFeeHistory` +// See eth_feeHistory method for more details of the return format. +func (b *Backend) retrieveEVMTxFeesFromBlock( tendermintBlock *tmrpctypes.ResultBlock, ethBlock *map[string]interface{}, rewardPercentiles []float64, @@ -281,13 +283,13 @@ func GetLogsFromBlockResults(blockRes *tmrpctypes.ResultBlockResults) ([][]*geth } // GetHexProofs returns list of hex data of proof op -func GetHexProofs(proof *crypto.ProofOps) []string { - if proof == nil { +func GetHexProofs(proofOps *crypto.ProofOps) []string { + if proofOps == nil { return []string{""} } proofs := []string{} // check for proof - for _, p := range proof.Ops { + for _, p := range proofOps.Ops { proof := "" if len(p.Data) > 0 { proof = hexutil.Encode(p.Data) diff --git a/eth/rpc/backend/utils_test.go b/eth/rpc/backend/utils_test.go index f0dcea536..94fc90fe4 100644 --- a/eth/rpc/backend/utils_test.go +++ b/eth/rpc/backend/utils_test.go @@ -1 +1,68 @@ package backend_test + +import ( + "fmt" + + "github.com/cometbft/cometbft/proto/tendermint/crypto" + + "github.com/NibiruChain/nibiru/v2/eth/rpc/backend" +) + +func (s *BackendSuite) TestGetLogsFromBlockResults() { + blockWithTx := transferTxBlockNumber.Int64() + blockResults, err := s.backend.TendermintBlockResultByNumber(&blockWithTx) + s.Require().NoError(err) + s.Require().NotNil(blockResults) + + logs, err := backend.GetLogsFromBlockResults(blockResults) + s.Require().NoError(err) + s.Require().NotNil(logs) + + // TODO: ON: the structured event eth.evm.v1.EventTxLog is not emitted properly, so the logs are not retrieved + // Add proper checks after implementing +} + +func (s *BackendSuite) TestGetHexProofs() { + defaultRes := []string{""} + testCases := []struct { + name string + proof *crypto.ProofOps + exp []string + }{ + { + "no proof provided", + mockProofs(0, false), + defaultRes, + }, + { + "no proof data provided", + mockProofs(1, false), + defaultRes, + }, + { + "valid proof provided", + mockProofs(1, true), + []string{"0x0a190a034b4559120556414c55451a0b0801180120012a03000202"}, + }, + } + for _, tc := range testCases { + s.Run(fmt.Sprintf("Case %s", tc.name), func() { + s.Require().Equal(tc.exp, backend.GetHexProofs(tc.proof)) + }) + } +} + +func mockProofs(num int, withData bool) *crypto.ProofOps { + var proofOps *crypto.ProofOps + if num > 0 { + proofOps = new(crypto.ProofOps) + for i := 0; i < num; i++ { + proof := crypto.ProofOp{} + if withData { + proof.Data = []byte("\n\031\n\003KEY\022\005VALUE\032\013\010\001\030\001 \001*\003\000\002\002") + } + proofOps.Ops = append(proofOps.Ops, proof) + } + } + return proofOps +} diff --git a/eth/rpc/rpcapi/eth_api.go b/eth/rpc/rpcapi/eth_api.go index 9695832df..62590347f 100644 --- a/eth/rpc/rpcapi/eth_api.go +++ b/eth/rpc/rpcapi/eth_api.go @@ -4,8 +4,6 @@ package rpcapi import ( "context" - "github.com/ethereum/go-ethereum/signer/core/apitypes" - gethrpc "github.com/ethereum/go-ethereum/rpc" "github.com/cometbft/cometbft/libs/log" @@ -43,7 +41,7 @@ type IEthAPI interface { // it is a user or a smart contract. GetTransactionByHash(hash common.Hash) (*rpc.EthTxJsonRPC, error) GetTransactionCount(address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) - GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) + GetTransactionReceipt(hash common.Hash) (*backend.TransactionReceipt, error) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpc.EthTxJsonRPC, error) GetTransactionByBlockNumberAndIndex(blockNum rpc.BlockNumber, idx hexutil.Uint) (*rpc.EthTxJsonRPC, error) // eth_getBlockReceipts @@ -53,9 +51,6 @@ type IEthAPI interface { // 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) - SendTransaction(args evm.JsonTxArgs) (common.Hash, error) - // eth_sendPrivateTransaction - // eth_cancel PrivateTransaction // Account Information // @@ -112,16 +107,9 @@ type IEthAPI interface { // Other Syncing() (interface{}, error) GetTransactionLogs(txHash common.Hash) ([]*gethcore.Log, error) - SignTypedData( - address common.Address, typedData apitypes.TypedData, - ) (hexutil.Bytes, error) FillTransaction( args evm.JsonTxArgs, ) (*rpc.SignTransactionResult, error) - Resend( - ctx context.Context, args evm.JsonTxArgs, - gasPrice *hexutil.Big, gasLimit *hexutil.Uint64, - ) (common.Hash, error) GetPendingTransactions() ([]*rpc.EthTxJsonRPC, error) } @@ -192,7 +180,7 @@ func (e *EthAPI) GetTransactionCount( // GetTransactionReceipt returns the transaction receipt identified by hash. func (e *EthAPI) GetTransactionReceipt( hash common.Hash, -) (map[string]interface{}, error) { +) (*backend.TransactionReceipt, error) { hexTx := hash.Hex() e.logger.Debug("eth_getTransactionReceipt", "hash", hexTx) return e.backend.GetTransactionReceipt(hash) @@ -238,14 +226,6 @@ func (e *EthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { return e.backend.SendRawTransaction(data) } -// SendTransaction sends an Ethereum transaction. -func (e *EthAPI) SendTransaction( - txArgs evm.JsonTxArgs, -) (common.Hash, error) { - e.logger.Debug("eth_sendTransaction", "args", txArgs.String()) - return e.backend.SendTransaction(txArgs) -} - // -------------------------------------------------------------------------- // Account Information // -------------------------------------------------------------------------- @@ -368,7 +348,7 @@ func (e *EthAPI) MaxPriorityFeePerGas() (*hexutil.Big, error) { // chain config. func (e *EthAPI) ChainId() (*hexutil.Big, error) { //nolint e.logger.Debug("eth_chainId") - return e.backend.ChainID() + return e.backend.ChainID(), nil } // -------------------------------------------------------------------------- @@ -448,16 +428,6 @@ func (e *EthAPI) GetTransactionLogs(txHash common.Hash) ([]*gethcore.Log, error) return backend.TxLogsFromEvents(resBlockResult.TxsResults[res.TxIndex].Events, index) } -// SignTypedData signs EIP-712 conformant typed data -func (e *EthAPI) SignTypedData( - address common.Address, typedData apitypes.TypedData, -) (hexutil.Bytes, error) { - e.logger.Debug( - "eth_signTypedData", "address", address.Hex(), "data", typedData, - ) - return e.backend.SignTypedData(address, typedData) -} - // FillTransaction fills the defaults (nonce, gas, gasPrice or 1559 fields) // on a given unsigned transaction, and returns it to the caller for further // processing (signing + broadcast). @@ -484,18 +454,6 @@ func (e *EthAPI) FillTransaction( }, nil } -// Resend accepts an existing transaction and a new gas price and limit. It will -// remove the given transaction from the pool and reinsert it with the new gas -// price and limit. -func (e *EthAPI) Resend(_ context.Context, - args evm.JsonTxArgs, - gasPrice *hexutil.Big, - gasLimit *hexutil.Uint64, -) (common.Hash, error) { - e.logger.Debug("eth_resend", "args", args.String()) - return e.backend.Resend(args, gasPrice, gasLimit) -} - // GetPendingTransactions returns the transactions that are in the transaction // pool and have a from address that is one of the accounts this node manages. func (e *EthAPI) GetPendingTransactions() ([]*rpc.EthTxJsonRPC, error) { diff --git a/eth/rpc/rpcapi/net_api.go b/eth/rpc/rpcapi/net_api.go index cc63bd303..029aacc38 100644 --- a/eth/rpc/rpcapi/net_api.go +++ b/eth/rpc/rpcapi/net_api.go @@ -22,14 +22,10 @@ type NetAPI struct { // NewImplNetAPI creates an instance of the public Net Web3 API. func NewImplNetAPI(clientCtx client.Context) *NetAPI { - // parse the chainID from a integer string - chainIDEpoch, err := eth.ParseEthChainID(clientCtx.ChainID) - if err != nil { - panic(err) - } + chainID := eth.ParseEthChainID(clientCtx.ChainID) return &NetAPI{ - networkVersion: chainIDEpoch.Uint64(), + networkVersion: chainID.Uint64(), tmClient: clientCtx.Client.(rpcclient.Client), } }