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
5 changes: 5 additions & 0 deletions app/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ func (app *App) EvmBalance(evmAddr common.Address, seiAddrBz []byte) uint256.Int
return bigIntToUint256(mempoolBalanceFloor(balance))
}

// EvmChainID returns the EVM chain ID configured for this network.
func (app *App) EvmChainID() uint64 {
return app.EvmKeeper.ChainID(app.GetCheckCtx()).Uint64()
}

func bigIntToUint256(x *big.Int) uint256.Int {
if x == nil {
return uint256.Int{}
Expand Down
24 changes: 24 additions & 0 deletions giga/evmonly/rpc/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package rpc

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/common/hexutil"
)

type infoAPI struct {
backend Backend
}

// BlockNumber returns the height of the most recently committed block.
func (api *infoAPI) BlockNumber(_ context.Context) hexutil.Uint64 {
return hexutil.Uint64(api.backend.EvmBlockNumber())
}

// ChainId returns the EVM chain ID this Autobahn shard is configured for.
//
//nolint:revive // matches the go-ethereum RPC method name eth_chainId.
func (api *infoAPI) ChainId(_ context.Context) *hexutil.Big {
return (*hexutil.Big)(new(big.Int).SetUint64(api.backend.EvmChainID()))
}
57 changes: 57 additions & 0 deletions giga/evmonly/rpc/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package rpc

import (
"math/big"
"net/http/httptest"
"testing"

"github.com/ethereum/go-ethereum/common/hexutil"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"

"github.com/sei-protocol/sei-chain/giga/evmonly"
)

func TestBlockNumber(t *testing.T) {
backend := &testBackend{blockNumber: func() uint64 { return 42 }}
api := &infoAPI{backend: backend}
require.Equal(t, hexutil.Uint64(42), api.BlockNumber(t.Context()))
}

func TestBlockNumberEndToEnd(t *testing.T) {
backend := &testBackend{blockNumber: func() uint64 { return 42 }}
handler, err := newHandler(backend, evmonly.NewMemoryReceiptStore())
require.NoError(t, err)
t.Cleanup(handler.Stop)
server := httptest.NewServer(handler)
t.Cleanup(server.Close)
client, err := ethrpc.DialHTTP(server.URL)
require.NoError(t, err)
t.Cleanup(client.Close)

var got hexutil.Uint64
require.NoError(t, client.CallContext(t.Context(), &got, "eth_blockNumber"))
require.Equal(t, hexutil.Uint64(42), got)
}

func TestChainId(t *testing.T) {
backend := &testBackend{chainID: func() uint64 { return 713715 }}
api := &infoAPI{backend: backend}
require.Equal(t, (*hexutil.Big)(big.NewInt(713715)), api.ChainId(t.Context()))
}

func TestChainIdEndToEnd(t *testing.T) {
backend := &testBackend{chainID: func() uint64 { return 713715 }}
handler, err := newHandler(backend, evmonly.NewMemoryReceiptStore())
require.NoError(t, err)
t.Cleanup(handler.Stop)
server := httptest.NewServer(handler)
t.Cleanup(server.Close)
client, err := ethrpc.DialHTTP(server.URL)
require.NoError(t, err)
t.Cleanup(client.Close)

var got hexutil.Big
require.NoError(t, client.CallContext(t.Context(), &got, "eth_chainId"))
require.Equal(t, *big.NewInt(713715), big.Int(got))
}
56 changes: 56 additions & 0 deletions giga/evmonly/rpc/send.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package rpc

import (
"context"
"errors"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"

abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types"
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types"
)

type sendAPI struct {
backend Backend
}

// SendRawTransaction submits a signed raw Ethereum transaction to Autobahn and
// returns its Ethereum transaction hash.
func (api *sendAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
tx := new(ethtypes.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}
hash := tx.Hash()

if sender, err := ethtypes.Sender(ethtypes.LatestSignerForChainID(tx.ChainId()), tx); err == nil {
if client, ok := api.backend.EvmProxy(sender).Get(); ok {
if err := client.CallContext(ctx, &hash, "eth_sendRawTransaction", input); err != nil {
return hash, err
}
return hash, nil
}
}

result, err := api.backend.BroadcastTx(ctx, &coretypes.RequestBroadcastTx{
Tx: append(tmtypes.Tx(nil), input...),
})
if err != nil {
return hash, err
}
if result == nil {
return hash, errors.New("missing broadcast response")
}
if result.Code != abci.CodeTypeOK {
message := result.Log
if message == "" {
message = fmt.Sprintf("transaction rejected with code %d", result.Code)
}
return hash, errors.New(message)
}
return hash, nil
}
24 changes: 3 additions & 21 deletions giga/evmonly/rpc/server_test.go → giga/evmonly/rpc/send_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,6 @@ import (
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
)

type testBackend struct {
broadcast func(context.Context, *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
block func(context.Context, *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error)
proxy utils.Option[*ethrpc.Client]
}

func (b *testBackend) BroadcastTx(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
return b.broadcast(ctx, req)
}

func (b *testBackend) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) {
return b.block(ctx, req)
}

func (b *testBackend) EvmProxy(common.Address) utils.Option[*ethrpc.Client] {
return b.proxy
}

func TestSendRawTransaction(t *testing.T) {
tx, raw := testSignedTransaction(t)
var broadcastRaw []byte
Expand All @@ -60,9 +42,9 @@ func TestSendRawTransaction(t *testing.T) {
require.Equal(t, tx.Hash(), got)
require.Equal(t, raw, broadcastRaw)

var chainID hexutil.Big
err = client.CallContext(t.Context(), &chainID, "eth_chainId")
require.ErrorContains(t, err, "method eth_chainId does not exist")
var callResult hexutil.Bytes
err = client.CallContext(t.Context(), &callResult, "eth_call")
require.ErrorContains(t, err, "method eth_call does not exist")
err = client.CallContext(t.Context(), nil, "status")
require.ErrorContains(t, err, "method status does not exist")
}
Expand Down
68 changes: 17 additions & 51 deletions giga/evmonly/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ import (
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"

"github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt"
abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types"
"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types"
"github.com/sei-protocol/seilog"
)

Expand All @@ -29,53 +26,16 @@ const (

var logger = seilog.NewLogger("giga", "evmonly", "rpc")

// Backend submits transactions, reads finalized blocks, and returns the RPC
// client for an Autobahn shard owner.
// Backend submits transactions, reads committed EVM state and finalized
// blocks, and returns the RPC client for an Autobahn shard owner.
type Backend interface {
BroadcastTx(context.Context, *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
Block(context.Context, *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error)
BroadcastTx(context.Context, *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
EvmBalance(common.Address) uint256.Int
EvmBlockNumber() uint64
EvmChainID() uint64
EvmProxy(common.Address) utils.Option[*ethrpc.Client]
}

type sendAPI struct {
backend Backend
}

// SendRawTransaction submits a signed raw Ethereum transaction to Autobahn and
// returns its Ethereum transaction hash.
func (api *sendAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
tx := new(ethtypes.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}
hash := tx.Hash()

if sender, err := ethtypes.Sender(ethtypes.LatestSignerForChainID(tx.ChainId()), tx); err == nil {
if client, ok := api.backend.EvmProxy(sender).Get(); ok {
if err := client.CallContext(ctx, &hash, "eth_sendRawTransaction", input); err != nil {
return hash, err
}
return hash, nil
}
}

result, err := api.backend.BroadcastTx(ctx, &coretypes.RequestBroadcastTx{
Tx: append(tmtypes.Tx(nil), input...),
})
if err != nil {
return hash, err
}
if result == nil {
return hash, errors.New("missing broadcast response")
}
if result.Code != abci.CodeTypeOK {
message := result.Log
if message == "" {
message = fmt.Sprintf("transaction rejected with code %d", result.Code)
}
return hash, errors.New(message)
}
return hash, nil
EvmTransactionCount(common.Address) uint64
}

// Server serves the EVM-only JSON-RPC API on port 8545.
Expand Down Expand Up @@ -112,10 +72,16 @@ func newHandler(backend Backend, receiptStore receipt.ReceiptStore) (*ethrpc.Ser
}
rpcServer := ethrpc.NewServer()
if err := rpcServer.RegisterName("eth", &sendAPI{backend: backend}); err != nil {
return nil, fmt.Errorf("register EVM-only RPC: %w", err)
return nil, fmt.Errorf("register EVM-only send RPC: %w", err)
}
if err := rpcServer.RegisterName("eth", &txAPI{backend: backend, store: receiptStore}); err != nil {
return nil, fmt.Errorf("register EVM-only transaction RPC: %w", err)
}
if err := rpcServer.RegisterName("eth", &stateAPI{backend: backend}); err != nil {
return nil, fmt.Errorf("register EVM-only state RPC: %w", err)
}
if err := rpcServer.RegisterName("eth", &receiptAPI{backend: backend, store: receiptStore}); err != nil {
return nil, fmt.Errorf("register EVM-only receipt RPC: %w", err)
if err := rpcServer.RegisterName("eth", &infoAPI{backend: backend}); err != nil {
return nil, fmt.Errorf("register EVM-only info RPC: %w", err)
}
return rpcServer, nil
}
Expand Down
50 changes: 50 additions & 0 deletions giga/evmonly/rpc/setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package rpc

import (
"context"

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

"github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils"
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
)

type testBackend struct {
broadcast func(context.Context, *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
block func(context.Context, *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error)
balance func(common.Address) uint256.Int
proxy utils.Option[*ethrpc.Client]
transactionCount func(common.Address) uint64
blockNumber func() uint64
chainID func() uint64
}

func (b *testBackend) BroadcastTx(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
return b.broadcast(ctx, req)
}

func (b *testBackend) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) {
return b.block(ctx, req)
}

func (b *testBackend) EvmBalance(address common.Address) uint256.Int {
return b.balance(address)
}

func (b *testBackend) EvmProxy(common.Address) utils.Option[*ethrpc.Client] {
return b.proxy
}

func (b *testBackend) EvmTransactionCount(address common.Address) uint64 {
return b.transactionCount(address)
}

func (b *testBackend) EvmBlockNumber() uint64 {
return b.blockNumber()
}

func (b *testBackend) EvmChainID() uint64 {
return b.chainID()
}
38 changes: 38 additions & 0 deletions giga/evmonly/rpc/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package rpc

import (
"context"
"errors"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethrpc "github.com/ethereum/go-ethereum/rpc"
)

var errHistoricalStateUnsupported = errors.New("historical state is not supported by EVM-only RPC")

type stateAPI struct {
backend Backend
}

// GetBalance returns the address balance from the current committed EVM state.
func (api *stateAPI) GetBalance(_ context.Context, address common.Address, block ethrpc.BlockNumberOrHash) (*hexutil.Big, error) {
if err := requireCurrentState(block); err != nil {
return nil, err
}
balance := api.backend.EvmBalance(address)
return (*hexutil.Big)(balance.ToBig()), nil
}

func requireCurrentState(block ethrpc.BlockNumberOrHash) error {
number, ok := block.Number()
if !ok {
return errHistoricalStateUnsupported
}
switch number {
case ethrpc.LatestBlockNumber, ethrpc.SafeBlockNumber, ethrpc.FinalizedBlockNumber, ethrpc.PendingBlockNumber:
return nil
default:
return errHistoricalStateUnsupported
}
}
Loading
Loading