Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions giga/evmonly/rpc/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ func (api *callAPI) Call(ctx context.Context, args export.TransactionArgs, block
if err := requireCurrentState(block); err != nil {
return nil, err
}
// Must match the base fee EvmCall executes under (evmOnlyBaseFee).
baseFee := new(big.Int)
baseFee, err := api.backend.EvmBaseFee()
if err != nil {
return nil, err
}
chainID := new(big.Int).SetUint64(api.backend.EvmChainID())
if err := args.CallDefaults(defaultCallGasCap, baseFee, chainID); err != nil {
return nil, err
Expand Down
7 changes: 7 additions & 0 deletions giga/evmonly/rpc/call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rpc
import (
"context"
"errors"
"math/big"
"net/http/httptest"
"testing"

Expand Down Expand Up @@ -40,6 +41,7 @@ func TestCallHappyPath(t *testing.T) {
var gotMsg *core.Message
backend := &testBackend{
chainID: func() uint64 { return 713715 },
baseFee: func() (*big.Int, error) { return new(big.Int), nil },
call: func(_ context.Context, msg *core.Message) (*core.ExecutionResult, error) {
gotMsg = msg
return &core.ExecutionResult{ReturnData: []byte{0x2a}}, nil
Expand All @@ -65,6 +67,7 @@ func TestCallCapsExplicitGasAboveDefault(t *testing.T) {
var gotMsg *core.Message
backend := &testBackend{
chainID: func() uint64 { return 713715 },
baseFee: func() (*big.Int, error) { return new(big.Int), nil },
call: func(_ context.Context, msg *core.Message) (*core.ExecutionResult, error) {
gotMsg = msg
return &core.ExecutionResult{}, nil
Expand All @@ -85,6 +88,7 @@ func TestCallPreservesExplicitGasBelowDefault(t *testing.T) {
var gotMsg *core.Message
backend := &testBackend{
chainID: func() uint64 { return 713715 },
baseFee: func() (*big.Int, error) { return new(big.Int), nil },
call: func(_ context.Context, msg *core.Message) (*core.ExecutionResult, error) {
gotMsg = msg
return &core.ExecutionResult{}, nil
Expand All @@ -105,6 +109,7 @@ func TestCallSurfacesRevertReason(t *testing.T) {
revert := revertData("insufficient balance")
backend := &testBackend{
chainID: func() uint64 { return 713715 },
baseFee: func() (*big.Int, error) { return new(big.Int), nil },
call: func(context.Context, *core.Message) (*core.ExecutionResult, error) {
return &core.ExecutionResult{Err: vm.ErrExecutionReverted, ReturnData: revert}, nil
},
Expand All @@ -127,6 +132,7 @@ func TestCallPassesThroughNonRevertExecutionError(t *testing.T) {
wantErr := errors.New("out of gas")
backend := &testBackend{
chainID: func() uint64 { return 713715 },
baseFee: func() (*big.Int, error) { return new(big.Int), nil },
call: func(context.Context, *core.Message) (*core.ExecutionResult, error) {
return &core.ExecutionResult{Err: wantErr}, nil
},
Expand Down Expand Up @@ -164,6 +170,7 @@ func TestHandlerServesCall(t *testing.T) {
to := common.HexToAddress("0x1000000000000000000000000000000000000001")
backend := &testBackend{
chainID: func() uint64 { return 713715 },
baseFee: func() (*big.Int, error) { return new(big.Int), nil },
call: func(context.Context, *core.Message) (*core.ExecutionResult, error) {
return &core.ExecutionResult{ReturnData: []byte{0x01, 0x02}}, nil
},
Expand Down
4 changes: 4 additions & 0 deletions giga/evmonly/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"context"
"errors"
"fmt"
"math/big"
"net"
"net/http"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/params"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"

Expand All @@ -35,8 +37,10 @@ type Backend interface {
Block(context.Context, *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error)
BroadcastTx(context.Context, *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
EvmBalance(common.Address) uint256.Int
EvmBaseFee() (*big.Int, error)
EvmBlockNumber() uint64
EvmCall(context.Context, *core.Message) (*core.ExecutionResult, error)
EvmChainConfig() (*params.ChainConfig, error)
EvmChainID() uint64
EvmProxy(common.Address) utils.Option[*ethrpc.Client]
EvmProxyEnabled() bool
Expand Down
12 changes: 12 additions & 0 deletions giga/evmonly/rpc/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package rpc

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/params"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"

Expand All @@ -14,10 +16,12 @@ import (

type testBackend struct {
balance func(common.Address) uint256.Int
baseFee func() (*big.Int, error)
block func(context.Context, *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error)
blockNumber func() uint64
broadcast func(context.Context, *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
call func(context.Context, *core.Message) (*core.ExecutionResult, error)
chainConfig func() (*params.ChainConfig, error)
chainID func() uint64
proxy utils.Option[*ethrpc.Client]
proxyCalls int
Expand All @@ -41,6 +45,10 @@ func (b *testBackend) EvmBalance(address common.Address) uint256.Int {
return b.balance(address)
}

func (b *testBackend) EvmBaseFee() (*big.Int, error) {
return b.baseFee()
}

func (b *testBackend) EvmProxyEnabled() bool {
return b.proxy.IsPresent()
}
Expand All @@ -53,6 +61,10 @@ func (b *testBackend) EvmCall(ctx context.Context, msg *core.Message) (*core.Exe
return b.call(ctx, msg)
}

func (b *testBackend) EvmChainConfig() (*params.ChainConfig, error) {
return b.chainConfig()
}

func (b *testBackend) EvmChainID() uint64 {
return b.chainID()
}
Expand Down
76 changes: 68 additions & 8 deletions giga/evmonly/rpc/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/export"
ethrpc "github.com/ethereum/go-ethereum/rpc"

sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
receiptpkg "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
evmtypes "github.com/sei-protocol/sei-chain/x/evm/types"
)
Expand All @@ -34,32 +36,90 @@ func (api *txAPI) GetTransactionCount(_ context.Context, address common.Address,

// GetTransactionReceipt returns the finalized Ethereum receipt for hash.
func (api *txAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]any, error) {
stored, block, err := api.lookupFinalizedTx(ctx, hash)
if err != nil || stored == nil {
return nil, err
}
return encodeReceipt(hash, stored, common.BytesToHash(block.BlockID.Hash)), nil
}

// GetTransactionByHash returns hash's transaction as committed in a finalized
// block, decoded from the block's raw transaction bytes, or nil if hash is
// unknown or its block is not yet finalized. This server tracks no local
// mempool, so unlike a full Ethereum node it never returns a pending result.
func (api *txAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*export.RPCTransaction, error) {
stored, block, err := api.lookupFinalizedTx(ctx, hash)
if err != nil || stored == nil {
return nil, err
}
if int(stored.TransactionIndex) >= len(block.Block.Txs) {
return nil, fmt.Errorf("receipt transaction index %d exceeds block %d transaction count %d",
stored.TransactionIndex, stored.BlockNumber, len(block.Block.Txs))
}
ethtx := new(ethtypes.Transaction)
if err := ethtx.UnmarshalBinary(block.Block.Txs[stored.TransactionIndex]); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The response is assembled entirely from stored.TransactionIndex without ever confirming the decoded transaction is the one that was asked for. Today the index is trustworthy — evmOnlyApplication.FinalizeBlock passes req.Txs straight through and executeBlockSequential increments txIndexUint 1:1 with that slice, so receipt index == Data.Txs index — but that invariant lives two packages away and nothing here asserts it. If the receipt store and block store ever disagree at a height (stale receipts surviving a rollback and re-execution with different ordering, a future filtering step in the prepare path), this returns a different transaction's full field set under the caller's hash rather than an error. A one-line guard after the decode makes the invariant local and turns silent wrong data into a diagnosable failure:

if ethtx.Hash() != hash {
	return nil, fmt.Errorf("block %d index %d holds transaction %s, not %s", stored.BlockNumber, stored.TransactionIndex, ethtx.Hash(), hash)
}

ethtx.Hash() is memoized, so the cost is one keccak on a path that already does a store read and a block fetch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave open design space where TxId may actually be an ID and not a hash of the body. Thus don't verify but trust.

return nil, fmt.Errorf("decode transaction at block %d index %d: %w", stored.BlockNumber, stored.TransactionIndex, err)
}
chainConfig, err := api.backend.EvmChainConfig()
if err != nil {
return nil, err
}
blockUnix, ok := utils.SafeCast[uint64](block.Block.Time.Unix())
if !ok {
return nil, fmt.Errorf("block %d time is negative: %s", stored.BlockNumber, block.Block.Time)
}
baseFee, err := api.backend.EvmBaseFee()
if err != nil {
return nil, err
}
// TODO: If the EVM-only base fee becomes dynamic, read the fee for
// stored.BlockNumber here or persist it with the receipt. Using the current
// fee would misreport a historical transaction's effective gas price.
result := export.NewRPCTransaction(ethtx, common.BytesToHash(block.BlockID.Hash), stored.BlockNumber, blockUnix,
uint64(stored.TransactionIndex), baseFee, chainConfig)
replaceFrom(result, stored)
return result, nil
}

// lookupFinalizedTx resolves hash's stored receipt and finalized block. It
// returns a nil receipt with a nil error when hash is unknown or its block is
// not yet finalized.
func (api *txAPI) lookupFinalizedTx(ctx context.Context, hash common.Hash) (*evmtypes.Receipt, *coretypes.ResultBlock, error) {
stored, err := api.store.GetReceipt(receiptContext(ctx), hash)
if errors.Is(err, receiptpkg.ErrNotFound) {
return nil, nil
return nil, nil, nil
}
if err != nil {
return nil, fmt.Errorf("read transaction receipt: %w", err)
return nil, nil, fmt.Errorf("read transaction receipt: %w", err)
}
if stored == nil {
return nil, errors.New("receipt store returned a nil receipt")
return nil, nil, errors.New("receipt store returned a nil receipt")
}
if stored.BlockNumber > math.MaxInt64 {
return nil, fmt.Errorf("receipt block number %d exceeds int64", stored.BlockNumber)
return nil, nil, fmt.Errorf("receipt block number %d exceeds int64", stored.BlockNumber)
}

height := coretypes.Int64(stored.BlockNumber)
block, err := api.backend.Block(ctx, &coretypes.RequestBlockInfo{Height: &height})
if errors.Is(err, coretypes.ErrHeightExceedsChainHead) {
return nil, nil
return nil, nil, nil
}
if err != nil {
return nil, fmt.Errorf("read receipt block %d: %w", stored.BlockNumber, err)
return nil, nil, fmt.Errorf("read receipt block %d: %w", stored.BlockNumber, err)
}
if block == nil || block.Block == nil {
return nil, nil
return nil, nil, nil
}
return stored, block, nil
}

// replaceFrom patches a decoded transaction's From field from its stored
// receipt when the tx's own signature did not resolve a sender, an edge case
// for some legacy transaction shapes.
func replaceFrom(tx *export.RPCTransaction, stored *evmtypes.Receipt) {
if tx.From == (common.Address{}) {
tx.From = common.HexToAddress(stored.From)
}
return encodeReceipt(hash, stored, common.BytesToHash(block.BlockID.Hash)), nil
}

func encodeReceipt(hash common.Hash, stored *evmtypes.Receipt, blockHash common.Hash) map[string]any {
Expand Down
Loading
Loading