diff --git a/api/api_full.go b/api/api_full.go index bd77539d3..af62c3b0c 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -812,6 +812,7 @@ type FullNode interface { EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) //perm:read EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) //perm:read EthChainId(ctx context.Context) (ethtypes.EthUint64, error) //perm:read + EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) //perm:read NetVersion(ctx context.Context) (string, error) //perm:read NetListening(ctx context.Context) (bool, error) //perm:read EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) //perm:read diff --git a/api/api_gateway.go b/api/api_gateway.go index f4d6c20a0..0fa329724 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -98,6 +98,7 @@ type Gateway interface { EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) + EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) NetVersion(ctx context.Context) (string, error) NetListening(ctx context.Context) (bool, error) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) diff --git a/api/eth_aliases.go b/api/eth_aliases.go index ccf0317d9..ca0f861ac 100644 --- a/api/eth_aliases.go +++ b/api/eth_aliases.go @@ -21,6 +21,7 @@ func CreateEthRPCAliases(as apitypes.Aliaser) { as.AliasMethod("eth_getStorageAt", "Filecoin.EthGetStorageAt") as.AliasMethod("eth_getBalance", "Filecoin.EthGetBalance") as.AliasMethod("eth_chainId", "Filecoin.EthChainId") + as.AliasMethod("eth_syncing", "Filecoin.EthSyncing") as.AliasMethod("eth_feeHistory", "Filecoin.EthFeeHistory") as.AliasMethod("eth_protocolVersion", "Filecoin.EthProtocolVersion") as.AliasMethod("eth_maxPriorityFeePerGas", "Filecoin.EthMaxPriorityFeePerGas") diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 12632bc2d..0b0e3ca4c 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1476,6 +1476,21 @@ func (mr *MockFullNodeMockRecorder) EthSubscribe(arg0, arg1 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSubscribe", reflect.TypeOf((*MockFullNode)(nil).EthSubscribe), arg0, arg1) } +// EthSyncing mocks base method. +func (m *MockFullNode) EthSyncing(arg0 context.Context) (ethtypes.EthSyncingResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthSyncing", arg0) + ret0, _ := ret[0].(ethtypes.EthSyncingResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthSyncing indicates an expected call of EthSyncing. +func (mr *MockFullNodeMockRecorder) EthSyncing(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSyncing", reflect.TypeOf((*MockFullNode)(nil).EthSyncing), arg0) +} + // EthUninstallFilter mocks base method. func (m *MockFullNode) EthUninstallFilter(arg0 context.Context, arg1 ethtypes.EthFilterID) (bool, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 3a9842eb5..459fd5864 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -314,6 +314,8 @@ type FullNodeMethods struct { EthSubscribe func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) `perm:"read"` + EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) `perm:"read"` + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"read"` EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `perm:"read"` @@ -722,6 +724,8 @@ type GatewayMethods struct { EthSubscribe func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) `` + EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) `` + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `` EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `` @@ -2422,6 +2426,17 @@ func (s *FullNodeStub) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (e return *new(ethtypes.EthSubscriptionID), ErrNotSupported } +func (s *FullNodeStruct) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + if s.Internal.EthSyncing == nil { + return *new(ethtypes.EthSyncingResult), ErrNotSupported + } + return s.Internal.EthSyncing(p0) +} + +func (s *FullNodeStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + return *new(ethtypes.EthSyncingResult), ErrNotSupported +} + func (s *FullNodeStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { if s.Internal.EthUninstallFilter == nil { return false, ErrNotSupported @@ -4600,6 +4615,17 @@ func (s *GatewayStub) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (et return *new(ethtypes.EthSubscriptionID), ErrNotSupported } +func (s *GatewayStruct) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + if s.Internal.EthSyncing == nil { + return *new(ethtypes.EthSyncingResult), ErrNotSupported + } + return s.Internal.EthSyncing(p0) +} + +func (s *GatewayStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + return *new(ethtypes.EthSyncingResult), ErrNotSupported +} + func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { if s.Internal.EthUninstallFilter == nil { return false, ErrNotSupported diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 74101567b..8e12e7ed3 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz index 6f5407def..2fa38e648 100644 Binary files a/build/openrpc/gateway.json.gz and b/build/openrpc/gateway.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 25aba1b8f..164434f3b 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 44c537b1c..f56329b05 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index 02b0012be..64f67f662 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -238,6 +238,30 @@ func (c *EthCall) UnmarshalJSON(b []byte) error { return nil } +type EthSyncingResult struct { + DoneSync bool + StartingBlock EthUint64 + CurrentBlock EthUint64 + HighestBlock EthUint64 +} + +func (sr EthSyncingResult) MarshalJSON() ([]byte, error) { + if sr.DoneSync { + // when done syncing, the json response should be '"result": false' + return []byte("false"), nil + } + + // need to do an anonymous struct to avoid infinite recursion + return json.Marshal(&struct { + StartingBlock EthUint64 `json:"startingblock"` + CurrentBlock EthUint64 `json:"currentblock"` + HighestBlock EthUint64 `json:"highestblock"` + }{ + StartingBlock: sr.StartingBlock, + CurrentBlock: sr.CurrentBlock, + HighestBlock: sr.HighestBlock}) +} + const ( EthAddressLength = 20 EthHashLength = 32 diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index f0a95d8dc..645df4f9d 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -103,6 +103,7 @@ * [EthProtocolVersion](#EthProtocolVersion) * [EthSendRawTransaction](#EthSendRawTransaction) * [EthSubscribe](#EthSubscribe) + * [EthSyncing](#EthSyncing) * [EthUninstallFilter](#EthUninstallFilter) * [EthUnsubscribe](#EthUnsubscribe) * [Filecoin](#Filecoin) @@ -3071,6 +3072,15 @@ Inputs: Response: `"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"` +### EthSyncing + + +Perms: read + +Inputs: `null` + +Response: `false` + ### EthUninstallFilter Uninstalls a filter with given id. diff --git a/gateway/node.go b/gateway/node.go index a88b22860..d0ff53402 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -115,6 +115,7 @@ type TargetAPI interface { EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) + EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) NetVersion(ctx context.Context) (string, error) NetListening(ctx context.Context) (bool, error) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) diff --git a/gateway/proxy_eth.go b/gateway/proxy_eth.go index 3342875c3..b992c9ea9 100644 --- a/gateway/proxy_eth.go +++ b/gateway/proxy_eth.go @@ -252,6 +252,14 @@ func (gw *Node) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) { return gw.target.EthChainId(ctx) } +func (gw *Node) EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) { + if err := gw.limit(ctx, basicRateLimitTokens); err != nil { + return ethtypes.EthSyncingResult{}, err + } + + return gw.target.EthSyncing(ctx) +} + func (gw *Node) NetVersion(ctx context.Context) (string, error) { if err := gw.limit(ctx, stateRateLimitTokens); err != nil { return "", err diff --git a/node/impl/full/dummy.go b/node/impl/full/dummy.go index a4a125d6f..918e84d10 100644 --- a/node/impl/full/dummy.go +++ b/node/impl/full/dummy.go @@ -102,6 +102,10 @@ func (e *EthModuleDummy) EthChainId(ctx context.Context) (ethtypes.EthUint64, er return 0, ErrModuleDisabled } +func (e *EthModuleDummy) EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) { + return ethtypes.EthSyncingResult{}, ErrModuleDisabled +} + func (e *EthModuleDummy) NetVersion(ctx context.Context) (string, error) { return "", ErrModuleDisabled } diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 9bc525737..a26241cd7 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -67,6 +67,7 @@ type EthModuleAPI interface { EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) + EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) NetVersion(ctx context.Context) (string, error) NetListening(ctx context.Context) (bool, error) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) @@ -133,6 +134,7 @@ type EthModule struct { ChainAPI MpoolAPI StateAPI + SyncAPI } var _ EthModuleAPI = (*EthModule)(nil) @@ -673,6 +675,42 @@ func (a *EthModule) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) return ethtypes.EthUint64(build.Eip155ChainId), nil } +func (a *EthModule) EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) { + state, err := a.SyncAPI.SyncState(ctx) + if err != nil { + return ethtypes.EthSyncingResult{}, fmt.Errorf("failed calling SyncState: %w", err) + } + + if len(state.ActiveSyncs) == 0 { + return ethtypes.EthSyncingResult{}, errors.New("no active syncs, try again") + } + + working := -1 + for i, ss := range state.ActiveSyncs { + if ss.Stage == api.StageIdle { + continue + } + working = i + } + if working == -1 { + working = len(state.ActiveSyncs) - 1 + } + + ss := state.ActiveSyncs[working] + if ss.Base == nil || ss.Target == nil { + return ethtypes.EthSyncingResult{}, errors.New("missing syncing information, try again") + } + + res := ethtypes.EthSyncingResult{ + DoneSync: ss.Stage == api.StageSyncComplete, + CurrentBlock: ethtypes.EthUint64(ss.Height), + StartingBlock: ethtypes.EthUint64(ss.Base.Height()), + HighestBlock: ethtypes.EthUint64(ss.Target.Height()), + } + + return res, nil +} + func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { params, err := jsonrpc.DecodeParams[ethtypes.EthFeeHistoryParams](p) if err != nil { diff --git a/node/modules/ethmodule.go b/node/modules/ethmodule.go index 074e911e2..f7f4508c4 100644 --- a/node/modules/ethmodule.go +++ b/node/modules/ethmodule.go @@ -18,8 +18,8 @@ import ( "github.com/filecoin-project/lotus/node/repo" ) -func EthModuleAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI, full.MpoolAPI) (*full.EthModule, error) { - return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI, mpoolapi full.MpoolAPI) (*full.EthModule, error) { +func EthModuleAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI, full.MpoolAPI, full.SyncAPI) (*full.EthModule, error) { + return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI, mpoolapi full.MpoolAPI, syncapi full.SyncAPI) (*full.EthModule, error) { sqlitePath, err := r.SqlitePath() if err != nil { return nil, err @@ -84,6 +84,7 @@ func EthModuleAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRep ChainAPI: chainapi, MpoolAPI: mpoolapi, StateAPI: stateapi, + SyncAPI: syncapi, EthTxHashManager: ðTxHashManager, }, nil