Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(eth): refactor eth API module into separate pieces in new pkg #12796

Merged
merged 3 commits into from
Dec 20, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Add F3GetCertificate & F3GetLatestCertificate to the gateway. ([filecoin-project/lotus#12778](https://github.com/filecoin-project/lotus/pull/12778))
- Add Magik's bootstrap node. ([filecoin-project/lotus#12792](https://github.com/filecoin-project/lotus/pull/12792))
- Lotus now reports the network name as a tag in most metrics. Some untagged metrics will be completed in a follow-up at a later date. ([filecoin-project/lotus#12733](https://github.com/filecoin-project/lotus/pull/12733))
- Refactored Ethereum API implementation into smaller, more manageable modules in a new `github.com/filecoin-project/lotus/node/impl/eth` package. ([filecoin-project/lotus#12796](https://github.com/filecoin-project/lotus/pull/12796))

# UNRELEASED v.1.32.0

Expand Down
26 changes: 26 additions & 0 deletions api/api_full.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,32 @@ type FullNode interface {
// MethodGroup: Eth
// These methods are used for Ethereum-compatible JSON-RPC calls
//
// ### Execution model reconciliation
//
// Ethereum relies on an immediate block-based execution model. The block that includes
// a transaction is also the block that executes it. Each block specifies the state root
// resulting from executing all transactions within it (output state).
//
// In Filecoin, at every epoch there is an unknown number of round winners all of whom are
// entitled to publish a block. Blocks are collected into a tipset. A tipset is committed
// only when the subsequent tipset is built on it (i.e. it becomes a parent). Block producers
// execute the parent tipset and specify the resulting state root in the block being produced.
// In other words, contrary to Ethereum, each block specifies the input state root.
//
// Ethereum clients expect transactions returned via eth_getBlock* to have a receipt
// (due to immediate execution). For this reason:
//
// - eth_blockNumber returns the latest executed epoch (head - 1)
// - The 'latest' block refers to the latest executed epoch (head - 1)
// - The 'pending' block refers to the current speculative tipset (head)
// - eth_getTransactionByHash returns the inclusion tipset of a message, but
// only after it has executed.
// - eth_getTransactionReceipt ditto.
//
// "Latest executed epoch" refers to the tipset that this node currently
// accepts as the best parent tipset, based on the blocks it is accumulating
// within the HEAD tipset.

// EthAccounts will always return [] since we don't expect Lotus to manage private keys
EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) //perm:read
// EthAddressToFilecoinAddress converts an EthAddress into an f410 Filecoin Address
Expand Down
2 changes: 1 addition & 1 deletion build/openrpc/full.json
Original file line number Diff line number Diff line change
Expand Up @@ -2155,7 +2155,7 @@
{
"name": "Filecoin.EthAccounts",
"description": "```go\nfunc (s *FullNodeStruct) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) {\n\tif s.Internal.EthAccounts == nil {\n\t\treturn *new([]ethtypes.EthAddress), ErrNotSupported\n\t}\n\treturn s.Internal.EthAccounts(p0)\n}\n```",
"summary": "There are not yet any comments for this method.",
"summary": "EthAccounts will always return [] since we don't expect Lotus to manage private keys\n",
"paramStructure": "by-position",
"params": [],
"result": {
Expand Down
29 changes: 27 additions & 2 deletions documentation/en/api-v1-unstable-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -1326,11 +1326,36 @@ Response: `{}`
## Eth
These methods are used for Ethereum-compatible JSON-RPC calls

EthAccounts will always return [] since we don't expect Lotus to manage private keys
### Execution model reconciliation

Ethereum relies on an immediate block-based execution model. The block that includes
a transaction is also the block that executes it. Each block specifies the state root
resulting from executing all transactions within it (output state).

In Filecoin, at every epoch there is an unknown number of round winners all of whom are
entitled to publish a block. Blocks are collected into a tipset. A tipset is committed
only when the subsequent tipset is built on it (i.e. it becomes a parent). Block producers
execute the parent tipset and specify the resulting state root in the block being produced.
In other words, contrary to Ethereum, each block specifies the input state root.

Ethereum clients expect transactions returned via eth_getBlock* to have a receipt
(due to immediate execution). For this reason:

- eth_blockNumber returns the latest executed epoch (head - 1)
- The 'latest' block refers to the latest executed epoch (head - 1)
- The 'pending' block refers to the current speculative tipset (head)
- eth_getTransactionByHash returns the inclusion tipset of a message, but
only after it has executed.
- eth_getTransactionReceipt ditto.

"Latest executed epoch" refers to the tipset that this node currently
accepts as the best parent tipset, based on the blocks it is accumulating
within the HEAD tipset.


### EthAccounts
There are not yet any comments for this method.
EthAccounts will always return [] since we don't expect Lotus to manage private keys


Perms: read

Expand Down
1 change: 1 addition & 0 deletions gateway/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ var (
_ full.MpoolModuleAPI = (*Node)(nil)
_ full.StateModuleAPI = (*Node)(nil)
_ full.EthModuleAPI = (*Node)(nil)
_ full.EthEventAPI = (*Node)(nil)
)

type options struct {
Expand Down
22 changes: 11 additions & 11 deletions itests/eth_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/itests/kit"
"github.com/filecoin-project/lotus/node/impl/full"
"github.com/filecoin-project/lotus/node/impl/eth"
)

func TestEthFilterAPIDisabledViaConfig(t *testing.T) {
Expand All @@ -21,41 +21,41 @@ func TestEthFilterAPIDisabledViaConfig(t *testing.T) {

_, err := client.EthNewPendingTransactionFilter(ctx)
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthGetLogs(ctx, &ethtypes.EthFilterSpec{})
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthGetFilterChanges(ctx, ethtypes.EthFilterID{})
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthGetFilterLogs(ctx, ethtypes.EthFilterID{})
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthNewFilter(ctx, &ethtypes.EthFilterSpec{})
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthNewBlockFilter(ctx)
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthNewPendingTransactionFilter(ctx)
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthUninstallFilter(ctx, ethtypes.EthFilterID{})
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthSubscribe(ctx, []byte("{}"))
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())

_, err = client.EthUnsubscribe(ctx, ethtypes.EthSubscriptionID{})
require.NotNil(t, err)
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
}
4 changes: 2 additions & 2 deletions itests/eth_fee_history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/itests/kit"
"github.com/filecoin-project/lotus/lib/result"
"github.com/filecoin-project/lotus/node/impl/full"
"github.com/filecoin-project/lotus/node/impl/gasutils"
)

// calculateExpectations calculates the expected number of items to be included in the response
Expand Down Expand Up @@ -171,7 +171,7 @@ func TestEthFeeHistory(t *testing.T) {
for _, arr := range *history.Reward {
require.Equal(3, len(arr))
for _, item := range arr {
require.Equal(ethtypes.EthBigInt(types.NewInt(full.MinGasPremium)), item)
require.Equal(ethtypes.EthBigInt(types.NewInt(gasutils.MinGasPremium)), item)
}
}

Expand Down
48 changes: 39 additions & 9 deletions node/builder_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ import (
"github.com/filecoin-project/lotus/node/hello"
"github.com/filecoin-project/lotus/node/impl"
"github.com/filecoin-project/lotus/node/impl/common"
"github.com/filecoin-project/lotus/node/impl/eth"
"github.com/filecoin-project/lotus/node/impl/full"
"github.com/filecoin-project/lotus/node/impl/gasutils"
"github.com/filecoin-project/lotus/node/impl/net"
"github.com/filecoin-project/lotus/node/modules"
"github.com/filecoin-project/lotus/node/modules/dtypes"
Expand Down Expand Up @@ -127,7 +129,7 @@ var ChainNode = Options(
// Markets (storage)
Override(new(*market.FundManager), market.NewFundManager),

Override(new(*full.GasPriceCache), full.NewGasPriceCache),
Override(new(*gasutils.GasPriceCache), gasutils.NewGasPriceCache),

// Lite node API
ApplyIf(isLiteNode,
Expand All @@ -138,9 +140,17 @@ var ChainNode = Options(
Override(new(full.MpoolModuleAPI), From(new(api.Gateway))),
Override(new(full.StateModuleAPI), From(new(api.Gateway))),
Override(new(stmgr.StateManagerAPI), rpcstmgr.NewRPCStateManager),
Override(new(full.EthModuleAPI), From(new(api.Gateway))),
Override(new(full.EthEventAPI), From(new(api.Gateway))),
Override(new(full.ActorEventAPI), From(new(api.Gateway))),
Override(new(eth.EthFilecoinAPI), From(new(api.Gateway))),
Override(new(eth.EthBasicAPI), From(new(api.Gateway))),
Override(new(eth.EthEventsAPI), From(new(api.Gateway))),
Override(new(eth.EthTransactionAPI), From(new(api.Gateway))),
Override(new(eth.EthLookupAPI), From(new(api.Gateway))),
Override(new(eth.EthTraceAPI), From(new(api.Gateway))),
Override(new(eth.EthGasAPI), From(new(api.Gateway))),
// EthSendAPI is a special case, we block the Untrusted method via GatewayEthSend even though it
// shouldn't be exposed on the Gateway API.
Override(new(eth.EthSendAPI), new(modules.GatewayEthSend)),

Override(new(index.Indexer), modules.ChainIndexer(config.ChainIndexerConfig{
EnableIndexer: false,
Expand Down Expand Up @@ -266,17 +276,37 @@ func ConfigFullNode(c interface{}) Option {
If(cfg.Fevm.EnableEthRPC || cfg.Events.EnableActorEventsAPI,
// Actor event filtering support, only needed for either Eth RPC and ActorEvents API
Override(new(events.EventHelperAPI), From(new(modules.EventHelperAPI))),
Override(new(*filter.EventFilterManager), modules.EventFilterManager(cfg.Events)),
Override(new(*filter.EventFilterManager), modules.MakeEventFilterManager(cfg.Events)),
),

Override(new(eth.ChainStore), From(new(*store.ChainStore))),
Override(new(eth.StateManager), From(new(*stmgr.StateManager))),
Override(new(eth.EthFilecoinAPI), eth.NewEthFilecoinAPI),

If(cfg.Fevm.EnableEthRPC,
Override(new(*full.EthEventHandler), modules.EthEventHandler(cfg.Events, cfg.Fevm.EnableEthRPC)),
Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm)),
Override(new(full.EthEventAPI), From(new(*full.EthEventHandler))),
Override(new(eth.StateAPI), From(new(full.StateAPI))),
Override(new(eth.SyncAPI), From(new(full.SyncAPI))),
Override(new(eth.MpoolAPI), From(new(full.MpoolAPI))),
Override(new(eth.MessagePool), From(new(*messagepool.MessagePool))),
Override(new(eth.GasAPI), From(new(full.GasModule))),

Override(new(eth.EthBasicAPI), eth.NewEthBasicAPI),
Override(new(eth.EthEventsInternal), modules.MakeEthEventsExtended(cfg.Events, cfg.Fevm.EnableEthRPC)),
Override(new(eth.EthEventsAPI), From(new(eth.EthEventsInternal))),
Override(new(eth.EthTransactionAPI), modules.MakeEthTransaction(cfg.Fevm)),
Override(new(eth.EthLookupAPI), eth.NewEthLookupAPI),
Override(new(eth.EthTraceAPI), modules.MakeEthTrace(cfg.Fevm)),
Override(new(eth.EthGasAPI), eth.NewEthGasAPI),
Override(new(eth.EthSendAPI), eth.NewEthSendAPI),
),
If(!cfg.Fevm.EnableEthRPC,
Override(new(full.EthModuleAPI), &full.EthModuleDummy{}),
Override(new(full.EthEventAPI), &full.EthModuleDummy{}),
Override(new(eth.EthBasicAPI), &eth.EthBasicDisabled{}),
Override(new(eth.EthTransactionAPI), &eth.EthTransactionDisabled{}),
Override(new(eth.EthLookupAPI), &eth.EthLookupDisabled{}),
Override(new(eth.EthTraceAPI), &eth.EthTraceDisabled{}),
Override(new(eth.EthGasAPI), &eth.EthGasDisabled{}),
Override(new(eth.EthEventsAPI), &eth.EthEventsDisabled{}),
Override(new(eth.EthSendAPI), &eth.EthSendDisabled{}),
),

If(cfg.Events.EnableActorEventsAPI,
Expand Down
97 changes: 97 additions & 0 deletions node/impl/eth/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package eth

import (
"context"

"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log/v2"
"golang.org/x/xerrors"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/network"

"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/adt"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
)

var (
ErrChainIndexerDisabled = xerrors.New("chain indexer is disabled; please enable the ChainIndexer to use the ETH RPC API")
ErrModuleDisabled = xerrors.New("module disabled, enable with Fevm.EnableEthRPC / LOTUS_FEVM_ENABLEETHRPC")
)

var log = logging.Logger("node/eth")

// SyncAPI is a minimal version of full.SyncAPI
type SyncAPI interface {
SyncState(ctx context.Context) (*api.SyncState, error)
}

// ChainStore is a minimal version of store.ChainStore just for tipsets
type ChainStore interface {
// TipSets
GetHeaviestTipSet() *types.TipSet
GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, ts *types.TipSet, prev bool) (*types.TipSet, error)
GetTipSetFromKey(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
GetTipSetByCid(ctx context.Context, c cid.Cid) (*types.TipSet, error)
LoadTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)

// Messages
GetSignedMessage(ctx context.Context, c cid.Cid) (*types.SignedMessage, error)
GetMessage(ctx context.Context, c cid.Cid) (*types.Message, error)
BlockMsgsForTipset(ctx context.Context, ts *types.TipSet) ([]store.BlockMessages, error)
MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error)
ReadReceipts(ctx context.Context, root cid.Cid) ([]types.MessageReceipt, error)

// Misc
ActorStore(ctx context.Context) adt.Store
}

// StateAPI is a minimal version of full.StateAPI
type StateAPI interface {
StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error)
}

// StateManager is a minimal version of stmgr.StateManager
type StateManager interface {
GetNetworkVersion(ctx context.Context, height abi.ChainEpoch) network.Version

TipSetState(ctx context.Context, ts *types.TipSet) (cid.Cid, cid.Cid, error)
ParentState(ts *types.TipSet) (*state.StateTree, error)
StateTree(st cid.Cid) (*state.StateTree, error)

LookupIDAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error)
LoadActor(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, error)
LoadActorRaw(ctx context.Context, addr address.Address, st cid.Cid) (*types.Actor, error)
ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error)

ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error)
Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error)
CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, applyTsMessages bool) (*api.InvocResult, error)
ApplyOnStateWithGas(ctx context.Context, stateCid cid.Cid, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error)

HasExpensiveForkBetween(parent, height abi.ChainEpoch) bool
}

// MpoolAPI is a minimal version of full.MpoolAPI
type MpoolAPI interface {
MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*types.SignedMessage, error)
MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error)
MpoolPushUntrusted(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error)
MpoolPush(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error)
}

// MessagePool is a minimal version of messagepool.MessagePool
type MessagePool interface {
PendingFor(ctx context.Context, a address.Address) ([]*types.SignedMessage, *types.TipSet)
GetConfig() *types.MpoolConfig
}

// GasAPI is a minimal version of full.GasAPI
type GasAPI interface {
GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, ts types.TipSetKey) (types.BigInt, error)
GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, ts types.TipSetKey) (*types.Message, error)
}
Loading
Loading