From 640870901826fea359c32a3897bc2b17eebcca67 Mon Sep 17 00:00:00 2001 From: Mikers Date: Thu, 20 Jun 2024 15:38:21 -1000 Subject: [PATCH] feat: eth: support "safe" and "finalized" for eth_getBlockByNumber (#12110) * add support for eth_getBlockByNumber to accept the term safe which we are using as 30 blocks * fix lint catch of unnecessary cast * add finalized to get block by number * Update chain/types/ethtypes/eth_types.go Co-authored-by: Rod Vagg * add test for eth get block by number to accept latest and safe and finalized as arguments --------- Co-authored-by: Rod Vagg --- chain/types/ethtypes/eth_types.go | 6 ++++++ gateway/proxy_eth.go | 5 +++++ itests/eth_api_test.go | 36 +++++++++++++++++++++++++++++++ node/impl/full/eth_utils.go | 16 ++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index 3c2b9bec0..251d8d501 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -28,6 +28,12 @@ import ( var ErrInvalidAddress = errors.New("invalid Filecoin Eth address") +// Research into Filecoin chain behaviour suggests that probabilistic finality +// generally approaches the intended stability guarantee at, or near, 30 epochs. +// Although a strictly "finalized" safe recommendation remains 900 epochs. +// See https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0089.md +const SafeEpochDelay = abi.ChainEpoch(30) + type EthUint64 uint64 func (e EthUint64) MarshalJSON() ([]byte, error) { diff --git a/gateway/proxy_eth.go b/gateway/proxy_eth.go index eca6ae2bf..55780c53a 100644 --- a/gateway/proxy_eth.go +++ b/gateway/proxy_eth.go @@ -17,6 +17,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/events/filter" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" @@ -142,6 +143,10 @@ func (gw *Node) checkBlkParam(ctx context.Context, blkParam string, lookback eth break } num = ethtypes.EthUint64(head.Height()) - lookback + case "safe": + num = ethtypes.EthUint64(head.Height()) - lookback - ethtypes.EthUint64(ethtypes.SafeEpochDelay) + case "finalized": + num = ethtypes.EthUint64(head.Height()) - lookback - ethtypes.EthUint64(build.Finality) default: if err := num.UnmarshalJSON([]byte(`"` + blkParam + `"`)); err != nil { return fmt.Errorf("cannot parse block number: %v", err) diff --git a/itests/eth_api_test.go b/itests/eth_api_test.go index 43b4b5266..7b9f61662 100644 --- a/itests/eth_api_test.go +++ b/itests/eth_api_test.go @@ -124,3 +124,39 @@ func TestNetVersion(t *testing.T) { require.NoError(t, err) require.Equal(t, strconv.Itoa(build.Eip155ChainId), version) } + +func TestEthBlockNumberAliases(t *testing.T) { + blockTime := 2 * time.Millisecond + kit.QuietMiningLogs() + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + ens.Start() + + build.Clock.Sleep(time.Second) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + head := client.WaitTillChain(ctx, kit.HeightAtLeast(build.Finality+100)) + + // latest should be head-1 (parents) + latestEthBlk, err := client.EVM().EthGetBlockByNumber(ctx, "latest", true) + require.NoError(t, err) + diff := int64(latestEthBlk.Number) - int64(head.Height()-1) + require.GreaterOrEqual(t, diff, int64(0)) + require.LessOrEqual(t, diff, int64(2)) + + // safe should be latest-30 + safeEthBlk, err := client.EVM().EthGetBlockByNumber(ctx, "safe", true) + require.NoError(t, err) + diff = int64(latestEthBlk.Number-30) - int64(safeEthBlk.Number) + require.GreaterOrEqual(t, diff, int64(0)) + require.LessOrEqual(t, diff, int64(2)) + + // finalized should be Finality blocks behind latest + finalityEthBlk, err := client.EVM().EthGetBlockByNumber(ctx, "finalized", true) + require.NoError(t, err) + diff = int64(latestEthBlk.Number) - int64(build.Finality) - int64(finalityEthBlk.Number) + require.GreaterOrEqual(t, diff, int64(0)) + require.LessOrEqual(t, diff, int64(2)) +} diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index 56cc1e094..a5799b0dd 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -57,6 +57,22 @@ func getTipsetByBlockNumber(ctx context.Context, chain *store.ChainStore, blkPar return nil, fmt.Errorf("cannot get parent tipset") } return parent, nil + case "safe": + latestHeight := head.Height() - 1 + safeHeight := latestHeight - ethtypes.SafeEpochDelay + ts, err := chain.GetTipsetByHeight(ctx, safeHeight, head, true) + if err != nil { + return nil, fmt.Errorf("cannot get tipset at height: %v", safeHeight) + } + return ts, nil + case "finalized": + latestHeight := head.Height() - 1 + safeHeight := latestHeight - build.Finality + ts, err := chain.GetTipsetByHeight(ctx, safeHeight, head, true) + if err != nil { + return nil, fmt.Errorf("cannot get tipset at height: %v", safeHeight) + } + return ts, nil default: var num ethtypes.EthUint64 err := num.UnmarshalJSON([]byte(`"` + blkParam + `"`))