Skip to content

Commit

Permalink
chore(eth): refactor eth API module into separate pieces in new pkg (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg authored Dec 20, 2024
1 parent 4e08e7c commit edfa817
Show file tree
Hide file tree
Showing 32 changed files with 4,137 additions and 3,481 deletions.
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

0 comments on commit edfa817

Please sign in to comment.