Skip to content

Commit edfa817

Browse files
authored
chore(eth): refactor eth API module into separate pieces in new pkg (#12796)
1 parent 4e08e7c commit edfa817

32 files changed

+4137
-3481
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Add F3GetCertificate & F3GetLatestCertificate to the gateway. ([filecoin-project/lotus#12778](https://github.com/filecoin-project/lotus/pull/12778))
1616
- Add Magik's bootstrap node. ([filecoin-project/lotus#12792](https://github.com/filecoin-project/lotus/pull/12792))
1717
- 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))
18+
- 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))
1819

1920
# UNRELEASED v.1.32.0
2021

api/api_full.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,32 @@ type FullNode interface {
768768
// MethodGroup: Eth
769769
// These methods are used for Ethereum-compatible JSON-RPC calls
770770
//
771+
// ### Execution model reconciliation
772+
//
773+
// Ethereum relies on an immediate block-based execution model. The block that includes
774+
// a transaction is also the block that executes it. Each block specifies the state root
775+
// resulting from executing all transactions within it (output state).
776+
//
777+
// In Filecoin, at every epoch there is an unknown number of round winners all of whom are
778+
// entitled to publish a block. Blocks are collected into a tipset. A tipset is committed
779+
// only when the subsequent tipset is built on it (i.e. it becomes a parent). Block producers
780+
// execute the parent tipset and specify the resulting state root in the block being produced.
781+
// In other words, contrary to Ethereum, each block specifies the input state root.
782+
//
783+
// Ethereum clients expect transactions returned via eth_getBlock* to have a receipt
784+
// (due to immediate execution). For this reason:
785+
//
786+
// - eth_blockNumber returns the latest executed epoch (head - 1)
787+
// - The 'latest' block refers to the latest executed epoch (head - 1)
788+
// - The 'pending' block refers to the current speculative tipset (head)
789+
// - eth_getTransactionByHash returns the inclusion tipset of a message, but
790+
// only after it has executed.
791+
// - eth_getTransactionReceipt ditto.
792+
//
793+
// "Latest executed epoch" refers to the tipset that this node currently
794+
// accepts as the best parent tipset, based on the blocks it is accumulating
795+
// within the HEAD tipset.
796+
771797
// EthAccounts will always return [] since we don't expect Lotus to manage private keys
772798
EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) //perm:read
773799
// EthAddressToFilecoinAddress converts an EthAddress into an f410 Filecoin Address

build/openrpc/full.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2155,7 +2155,7 @@
21552155
{
21562156
"name": "Filecoin.EthAccounts",
21572157
"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```",
2158-
"summary": "There are not yet any comments for this method.",
2158+
"summary": "EthAccounts will always return [] since we don't expect Lotus to manage private keys\n",
21592159
"paramStructure": "by-position",
21602160
"params": [],
21612161
"result": {

documentation/en/api-v1-unstable-methods.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,11 +1326,36 @@ Response: `{}`
13261326
## Eth
13271327
These methods are used for Ethereum-compatible JSON-RPC calls
13281328

1329-
EthAccounts will always return [] since we don't expect Lotus to manage private keys
1329+
### Execution model reconciliation
1330+
1331+
Ethereum relies on an immediate block-based execution model. The block that includes
1332+
a transaction is also the block that executes it. Each block specifies the state root
1333+
resulting from executing all transactions within it (output state).
1334+
1335+
In Filecoin, at every epoch there is an unknown number of round winners all of whom are
1336+
entitled to publish a block. Blocks are collected into a tipset. A tipset is committed
1337+
only when the subsequent tipset is built on it (i.e. it becomes a parent). Block producers
1338+
execute the parent tipset and specify the resulting state root in the block being produced.
1339+
In other words, contrary to Ethereum, each block specifies the input state root.
1340+
1341+
Ethereum clients expect transactions returned via eth_getBlock* to have a receipt
1342+
(due to immediate execution). For this reason:
1343+
1344+
- eth_blockNumber returns the latest executed epoch (head - 1)
1345+
- The 'latest' block refers to the latest executed epoch (head - 1)
1346+
- The 'pending' block refers to the current speculative tipset (head)
1347+
- eth_getTransactionByHash returns the inclusion tipset of a message, but
1348+
only after it has executed.
1349+
- eth_getTransactionReceipt ditto.
1350+
1351+
"Latest executed epoch" refers to the tipset that this node currently
1352+
accepts as the best parent tipset, based on the blocks it is accumulating
1353+
within the HEAD tipset.
13301354

13311355

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

13351360
Perms: read
13361361

gateway/node.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ var (
185185
_ full.MpoolModuleAPI = (*Node)(nil)
186186
_ full.StateModuleAPI = (*Node)(nil)
187187
_ full.EthModuleAPI = (*Node)(nil)
188+
_ full.EthEventAPI = (*Node)(nil)
188189
)
189190

190191
type options struct {

itests/eth_config_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
"github.com/filecoin-project/lotus/chain/types/ethtypes"
1010
"github.com/filecoin-project/lotus/itests/kit"
11-
"github.com/filecoin-project/lotus/node/impl/full"
11+
"github.com/filecoin-project/lotus/node/impl/eth"
1212
)
1313

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

2222
_, err := client.EthNewPendingTransactionFilter(ctx)
2323
require.NotNil(t, err)
24-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
24+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
2525

2626
_, err = client.EthGetLogs(ctx, &ethtypes.EthFilterSpec{})
2727
require.NotNil(t, err)
28-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
28+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
2929

3030
_, err = client.EthGetFilterChanges(ctx, ethtypes.EthFilterID{})
3131
require.NotNil(t, err)
32-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
32+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
3333

3434
_, err = client.EthGetFilterLogs(ctx, ethtypes.EthFilterID{})
3535
require.NotNil(t, err)
36-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
36+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
3737

3838
_, err = client.EthNewFilter(ctx, &ethtypes.EthFilterSpec{})
3939
require.NotNil(t, err)
40-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
40+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
4141

4242
_, err = client.EthNewBlockFilter(ctx)
4343
require.NotNil(t, err)
44-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
44+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
4545

4646
_, err = client.EthNewPendingTransactionFilter(ctx)
4747
require.NotNil(t, err)
48-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
48+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
4949

5050
_, err = client.EthUninstallFilter(ctx, ethtypes.EthFilterID{})
5151
require.NotNil(t, err)
52-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
52+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
5353

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

5858
_, err = client.EthUnsubscribe(ctx, ethtypes.EthSubscriptionID{})
5959
require.NotNil(t, err)
60-
require.Equal(t, err.Error(), full.ErrModuleDisabled.Error())
60+
require.Equal(t, err.Error(), eth.ErrModuleDisabled.Error())
6161
}

itests/eth_fee_history_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/filecoin-project/lotus/chain/types/ethtypes"
1717
"github.com/filecoin-project/lotus/itests/kit"
1818
"github.com/filecoin-project/lotus/lib/result"
19-
"github.com/filecoin-project/lotus/node/impl/full"
19+
"github.com/filecoin-project/lotus/node/impl/gasutils"
2020
)
2121

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

node/builder_chain.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ import (
4040
"github.com/filecoin-project/lotus/node/hello"
4141
"github.com/filecoin-project/lotus/node/impl"
4242
"github.com/filecoin-project/lotus/node/impl/common"
43+
"github.com/filecoin-project/lotus/node/impl/eth"
4344
"github.com/filecoin-project/lotus/node/impl/full"
45+
"github.com/filecoin-project/lotus/node/impl/gasutils"
4446
"github.com/filecoin-project/lotus/node/impl/net"
4547
"github.com/filecoin-project/lotus/node/modules"
4648
"github.com/filecoin-project/lotus/node/modules/dtypes"
@@ -127,7 +129,7 @@ var ChainNode = Options(
127129
// Markets (storage)
128130
Override(new(*market.FundManager), market.NewFundManager),
129131

130-
Override(new(*full.GasPriceCache), full.NewGasPriceCache),
132+
Override(new(*gasutils.GasPriceCache), gasutils.NewGasPriceCache),
131133

132134
// Lite node API
133135
ApplyIf(isLiteNode,
@@ -138,9 +140,17 @@ var ChainNode = Options(
138140
Override(new(full.MpoolModuleAPI), From(new(api.Gateway))),
139141
Override(new(full.StateModuleAPI), From(new(api.Gateway))),
140142
Override(new(stmgr.StateManagerAPI), rpcstmgr.NewRPCStateManager),
141-
Override(new(full.EthModuleAPI), From(new(api.Gateway))),
142-
Override(new(full.EthEventAPI), From(new(api.Gateway))),
143143
Override(new(full.ActorEventAPI), From(new(api.Gateway))),
144+
Override(new(eth.EthFilecoinAPI), From(new(api.Gateway))),
145+
Override(new(eth.EthBasicAPI), From(new(api.Gateway))),
146+
Override(new(eth.EthEventsAPI), From(new(api.Gateway))),
147+
Override(new(eth.EthTransactionAPI), From(new(api.Gateway))),
148+
Override(new(eth.EthLookupAPI), From(new(api.Gateway))),
149+
Override(new(eth.EthTraceAPI), From(new(api.Gateway))),
150+
Override(new(eth.EthGasAPI), From(new(api.Gateway))),
151+
// EthSendAPI is a special case, we block the Untrusted method via GatewayEthSend even though it
152+
// shouldn't be exposed on the Gateway API.
153+
Override(new(eth.EthSendAPI), new(modules.GatewayEthSend)),
144154

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

282+
Override(new(eth.ChainStore), From(new(*store.ChainStore))),
283+
Override(new(eth.StateManager), From(new(*stmgr.StateManager))),
284+
Override(new(eth.EthFilecoinAPI), eth.NewEthFilecoinAPI),
285+
272286
If(cfg.Fevm.EnableEthRPC,
273-
Override(new(*full.EthEventHandler), modules.EthEventHandler(cfg.Events, cfg.Fevm.EnableEthRPC)),
274-
Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm)),
275-
Override(new(full.EthEventAPI), From(new(*full.EthEventHandler))),
287+
Override(new(eth.StateAPI), From(new(full.StateAPI))),
288+
Override(new(eth.SyncAPI), From(new(full.SyncAPI))),
289+
Override(new(eth.MpoolAPI), From(new(full.MpoolAPI))),
290+
Override(new(eth.MessagePool), From(new(*messagepool.MessagePool))),
291+
Override(new(eth.GasAPI), From(new(full.GasModule))),
292+
293+
Override(new(eth.EthBasicAPI), eth.NewEthBasicAPI),
294+
Override(new(eth.EthEventsInternal), modules.MakeEthEventsExtended(cfg.Events, cfg.Fevm.EnableEthRPC)),
295+
Override(new(eth.EthEventsAPI), From(new(eth.EthEventsInternal))),
296+
Override(new(eth.EthTransactionAPI), modules.MakeEthTransaction(cfg.Fevm)),
297+
Override(new(eth.EthLookupAPI), eth.NewEthLookupAPI),
298+
Override(new(eth.EthTraceAPI), modules.MakeEthTrace(cfg.Fevm)),
299+
Override(new(eth.EthGasAPI), eth.NewEthGasAPI),
300+
Override(new(eth.EthSendAPI), eth.NewEthSendAPI),
276301
),
277302
If(!cfg.Fevm.EnableEthRPC,
278-
Override(new(full.EthModuleAPI), &full.EthModuleDummy{}),
279-
Override(new(full.EthEventAPI), &full.EthModuleDummy{}),
303+
Override(new(eth.EthBasicAPI), &eth.EthBasicDisabled{}),
304+
Override(new(eth.EthTransactionAPI), &eth.EthTransactionDisabled{}),
305+
Override(new(eth.EthLookupAPI), &eth.EthLookupDisabled{}),
306+
Override(new(eth.EthTraceAPI), &eth.EthTraceDisabled{}),
307+
Override(new(eth.EthGasAPI), &eth.EthGasDisabled{}),
308+
Override(new(eth.EthEventsAPI), &eth.EthEventsDisabled{}),
309+
Override(new(eth.EthSendAPI), &eth.EthSendDisabled{}),
280310
),
281311

282312
If(cfg.Events.EnableActorEventsAPI,

node/impl/eth/api.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package eth
2+
3+
import (
4+
"context"
5+
6+
"github.com/ipfs/go-cid"
7+
logging "github.com/ipfs/go-log/v2"
8+
"golang.org/x/xerrors"
9+
10+
"github.com/filecoin-project/go-address"
11+
"github.com/filecoin-project/go-state-types/abi"
12+
"github.com/filecoin-project/go-state-types/network"
13+
14+
"github.com/filecoin-project/lotus/api"
15+
"github.com/filecoin-project/lotus/chain/actors/adt"
16+
"github.com/filecoin-project/lotus/chain/state"
17+
"github.com/filecoin-project/lotus/chain/store"
18+
"github.com/filecoin-project/lotus/chain/types"
19+
)
20+
21+
var (
22+
ErrChainIndexerDisabled = xerrors.New("chain indexer is disabled; please enable the ChainIndexer to use the ETH RPC API")
23+
ErrModuleDisabled = xerrors.New("module disabled, enable with Fevm.EnableEthRPC / LOTUS_FEVM_ENABLEETHRPC")
24+
)
25+
26+
var log = logging.Logger("node/eth")
27+
28+
// SyncAPI is a minimal version of full.SyncAPI
29+
type SyncAPI interface {
30+
SyncState(ctx context.Context) (*api.SyncState, error)
31+
}
32+
33+
// ChainStore is a minimal version of store.ChainStore just for tipsets
34+
type ChainStore interface {
35+
// TipSets
36+
GetHeaviestTipSet() *types.TipSet
37+
GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, ts *types.TipSet, prev bool) (*types.TipSet, error)
38+
GetTipSetFromKey(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
39+
GetTipSetByCid(ctx context.Context, c cid.Cid) (*types.TipSet, error)
40+
LoadTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
41+
42+
// Messages
43+
GetSignedMessage(ctx context.Context, c cid.Cid) (*types.SignedMessage, error)
44+
GetMessage(ctx context.Context, c cid.Cid) (*types.Message, error)
45+
BlockMsgsForTipset(ctx context.Context, ts *types.TipSet) ([]store.BlockMessages, error)
46+
MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error)
47+
ReadReceipts(ctx context.Context, root cid.Cid) ([]types.MessageReceipt, error)
48+
49+
// Misc
50+
ActorStore(ctx context.Context) adt.Store
51+
}
52+
53+
// StateAPI is a minimal version of full.StateAPI
54+
type StateAPI interface {
55+
StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error)
56+
}
57+
58+
// StateManager is a minimal version of stmgr.StateManager
59+
type StateManager interface {
60+
GetNetworkVersion(ctx context.Context, height abi.ChainEpoch) network.Version
61+
62+
TipSetState(ctx context.Context, ts *types.TipSet) (cid.Cid, cid.Cid, error)
63+
ParentState(ts *types.TipSet) (*state.StateTree, error)
64+
StateTree(st cid.Cid) (*state.StateTree, error)
65+
66+
LookupIDAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error)
67+
LoadActor(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, error)
68+
LoadActorRaw(ctx context.Context, addr address.Address, st cid.Cid) (*types.Actor, error)
69+
ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error)
70+
71+
ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error)
72+
Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error)
73+
CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, applyTsMessages bool) (*api.InvocResult, error)
74+
ApplyOnStateWithGas(ctx context.Context, stateCid cid.Cid, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error)
75+
76+
HasExpensiveForkBetween(parent, height abi.ChainEpoch) bool
77+
}
78+
79+
// MpoolAPI is a minimal version of full.MpoolAPI
80+
type MpoolAPI interface {
81+
MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*types.SignedMessage, error)
82+
MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error)
83+
MpoolPushUntrusted(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error)
84+
MpoolPush(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error)
85+
}
86+
87+
// MessagePool is a minimal version of messagepool.MessagePool
88+
type MessagePool interface {
89+
PendingFor(ctx context.Context, a address.Address) ([]*types.SignedMessage, *types.TipSet)
90+
GetConfig() *types.MpoolConfig
91+
}
92+
93+
// GasAPI is a minimal version of full.GasAPI
94+
type GasAPI interface {
95+
GasEstimateGasPremium(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, ts types.TipSetKey) (types.BigInt, error)
96+
GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, ts types.TipSetKey) (*types.Message, error)
97+
}

0 commit comments

Comments
 (0)