From 57bf1c7a8900118bd5a74585a58330c313cbfc86 Mon Sep 17 00:00:00 2001 From: ychiao Date: Fri, 20 Jan 2023 12:06:33 -0500 Subject: [PATCH 1/9] Eth JSON-RPC: support passing uint64 in JSON-RPC arguments for EthUint64 --- chain/types/ethtypes/eth_types.go | 24 +++++++++++++++--------- chain/types/ethtypes/eth_types_test.go | 5 ++++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index eb0e12891..af27ec58c 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -39,18 +39,24 @@ func (e EthUint64) MarshalJSON() ([]byte, error) { return json.Marshal(e.Hex()) } +// UnmarshalJSON should be able to parse two types of input: +// 1. a JSON string containing a hex-encoded uint64 starting with 0x +// 2. a valid string containing a uint64 in decimal func (e *EthUint64) UnmarshalJSON(b []byte) error { var s string - if err := json.Unmarshal(b, &s); err != nil { - return err + if err := json.Unmarshal(b, &s); err == nil { + parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64) + if err != nil { + return err + } + eint := EthUint64(parsedInt) + *e = eint + return nil + } else if eint, err := strconv.ParseUint(string(b), 10, 64); err == nil { + *e = EthUint64(eint) + return nil } - parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64) - if err != nil { - return err - } - eint := EthUint64(parsedInt) - *e = eint - return nil + return fmt.Errorf("cannot parse %s into EthUint64", string(b)) } func EthUint64FromHex(s string) (EthUint64, error) { diff --git a/chain/types/ethtypes/eth_types_test.go b/chain/types/ethtypes/eth_types_test.go index 89c38ba29..b05557b07 100644 --- a/chain/types/ethtypes/eth_types_test.go +++ b/chain/types/ethtypes/eth_types_test.go @@ -36,13 +36,16 @@ func TestEthIntUnmarshalJSON(t *testing.T) { {[]byte("\"0x0\""), EthUint64(0)}, {[]byte("\"0x41\""), EthUint64(65)}, {[]byte("\"0x400\""), EthUint64(1024)}, + {[]byte("0"), EthUint64(0)}, + {[]byte("100"), EthUint64(100)}, + {[]byte("1024"), EthUint64(1024)}, } for _, tc := range testcases { var i EthUint64 err := i.UnmarshalJSON(tc.Input.([]byte)) require.Nil(t, err) - require.Equal(t, i, tc.Output) + require.Equal(t, tc.Output, i) } } From 37044ed3c3df15b41a84108d19e3468a7b79f724 Mon Sep 17 00:00:00 2001 From: ychiao Date: Fri, 10 Feb 2023 10:42:19 -0500 Subject: [PATCH 2/9] Apply suggestions from code review Co-authored-by: raulk --- chain/types/ethtypes/eth_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index 0c455c0f0..4cdb2abed 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -57,7 +57,7 @@ func (e *EthUint64) UnmarshalJSON(b []byte) error { *e = EthUint64(eint) return nil } - return fmt.Errorf("cannot parse %s into EthUint64", string(b)) + return fmt.Errorf("cannot interpret %s as a hex-encoded uint64, or a number", string(b)) } func EthUint64FromHex(s string) (EthUint64, error) { From 3c580403c1bd61e06acf442969730d6b14219166 Mon Sep 17 00:00:00 2001 From: ychiao Date: Fri, 10 Feb 2023 13:33:59 -0500 Subject: [PATCH 3/9] implement itest and handle optional params --- .circleci/config.yml | 5 ++ api/api_full.go | 18 ++-- api/api_gateway.go | 2 +- api/mocks/mock_full.go | 8 +- api/proxy_gen.go | 16 ++-- build/openrpc/full.json.gz | Bin 33147 -> 33136 bytes build/openrpc/gateway.json.gz | Bin 8510 -> 8492 bytes chain/types/ethtypes/eth_types.go | 48 ++++++++++- chain/types/ethtypes/eth_types_test.go | 3 + documentation/en/api-v1-unstable-methods.md | 6 +- gateway/node.go | 2 +- gateway/proxy_eth.go | 13 ++- itests/eth_fee_history_test.go | 87 ++++++++++++++++++++ node/impl/full/dummy.go | 2 +- node/impl/full/eth.go | 26 ++++-- 15 files changed, 193 insertions(+), 43 deletions(-) create mode 100644 itests/eth_fee_history_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 88635ab2c..289cbfa23 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -643,6 +643,11 @@ workflows: suite: itest-eth_deploy target: "./itests/eth_deploy_test.go" + - test: + name: test-itest-eth_fee_history + suite: itest-eth_fee_history + target: "./itests/eth_fee_history_test.go" + - test: name: test-itest-eth_filter suite: itest-eth_filter diff --git a/api/api_full.go b/api/api_full.go index 20870adce..3bd875dc7 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -786,15 +786,15 @@ type FullNode interface { EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) //perm:read EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) //perm:read - EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) //perm:read - 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 - 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 - EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) //perm:read - EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) //perm:read + EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) //perm:read + 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 + 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 + EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) //perm:read + EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) //perm:read EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) //perm:read EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) //perm:read diff --git a/api/api_gateway.go b/api/api_gateway.go index 1f7cce8e3..aea6fc3d1 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -93,7 +93,7 @@ type Gateway interface { NetListening(ctx context.Context) (bool, error) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) - EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) + EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index c944e2de1..6e4873715 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1014,18 +1014,18 @@ func (mr *MockFullNodeMockRecorder) EthEstimateGas(arg0, arg1 interface{}) *gomo } // EthFeeHistory mocks base method. -func (m *MockFullNode) EthFeeHistory(arg0 context.Context, arg1 ethtypes.EthUint64, arg2 string, arg3 []float64) (ethtypes.EthFeeHistory, error) { +func (m *MockFullNode) EthFeeHistory(arg0 context.Context, arg1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EthFeeHistory", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "EthFeeHistory", arg0, arg1) ret0, _ := ret[0].(ethtypes.EthFeeHistory) ret1, _ := ret[1].(error) return ret0, ret1 } // EthFeeHistory indicates an expected call of EthFeeHistory. -func (mr *MockFullNodeMockRecorder) EthFeeHistory(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockFullNodeMockRecorder) EthFeeHistory(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthFeeHistory", reflect.TypeOf((*MockFullNode)(nil).EthFeeHistory), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthFeeHistory", reflect.TypeOf((*MockFullNode)(nil).EthFeeHistory), arg0, arg1) } // EthGasPrice mocks base method. diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 794456eae..2cfaa099a 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -252,7 +252,7 @@ type FullNodeMethods struct { EthEstimateGas func(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) `perm:"read"` - EthFeeHistory func(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) `perm:"read"` + EthFeeHistory func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) `perm:"read"` EthGasPrice func(p0 context.Context) (ethtypes.EthBigInt, error) `perm:"read"` @@ -658,7 +658,7 @@ type GatewayMethods struct { EthEstimateGas func(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) `` - EthFeeHistory func(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) `` + EthFeeHistory func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) `` EthGasPrice func(p0 context.Context) (ethtypes.EthBigInt, error) `` @@ -2051,14 +2051,14 @@ func (s *FullNodeStub) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) ( return *new(ethtypes.EthUint64), ErrNotSupported } -func (s *FullNodeStruct) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { +func (s *FullNodeStruct) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { if s.Internal.EthFeeHistory == nil { return *new(ethtypes.EthFeeHistory), ErrNotSupported } - return s.Internal.EthFeeHistory(p0, p1, p2, p3) + return s.Internal.EthFeeHistory(p0, p1) } -func (s *FullNodeStub) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { +func (s *FullNodeStub) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { return *new(ethtypes.EthFeeHistory), ErrNotSupported } @@ -4218,14 +4218,14 @@ func (s *GatewayStub) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (e return *new(ethtypes.EthUint64), ErrNotSupported } -func (s *GatewayStruct) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { +func (s *GatewayStruct) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { if s.Internal.EthFeeHistory == nil { return *new(ethtypes.EthFeeHistory), ErrNotSupported } - return s.Internal.EthFeeHistory(p0, p1, p2, p3) + return s.Internal.EthFeeHistory(p0, p1) } -func (s *GatewayStub) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { +func (s *GatewayStub) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { return *new(ethtypes.EthFeeHistory), ErrNotSupported } diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index a33ab9ab39eade00899a9a39b1c17baed4603378..f5b21db04836a2e74f739e478a7032ec4bb090f2 100644 GIT binary patch delta 24353 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 T{6Pi)e0(ThiDU8>BLMyfpBg4_ delta 24458 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 diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz index f8dc516395a47f2550ea80fefb3433b2e14ba4be..f75e5a14e57a78ec6989d5b040d19d0853683d43 100644 GIT binary patch delta 8110 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 ethtypes.EthUint64(EthFeeHistoryMaxBlockCount) { + if params.BlkCount > ethtypes.EthUint64(EthFeeHistoryMaxBlockCount) { return ethtypes.EthFeeHistory{}, fmt.Errorf("block count too high") } - return gw.target.EthFeeHistory(ctx, blkCount, newestBlk, rewardPercentiles) + return gw.target.EthFeeHistory(ctx, p) } func (gw *Node) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) { diff --git a/itests/eth_fee_history_test.go b/itests/eth_fee_history_test.go new file mode 100644 index 000000000..9b256c527 --- /dev/null +++ b/itests/eth_fee_history_test.go @@ -0,0 +1,87 @@ +package itests + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-jsonrpc" + + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/lib/result" +) + +func TestEthFeeHistory(t *testing.T) { + require := require.New(t) + + kit.QuietAllLogsExcept() + + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // Wait for the network to create 20 blocks + <-time.After(20 * blockTime) + + history, err := client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( + json.Marshal([]interface{}{5, "0x10"}), + ).Assert(require.NoError)) + require.NoError(err) + require.Equal(6, len(history.BaseFeePerGas)) + require.Equal(5, len(history.GasUsedRatio)) + require.Equal(ethtypes.EthUint64(16-5+1), history.OldestBlock) + + history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( + json.Marshal([]interface{}{"5", "0x10"}), + ).Assert(require.NoError)) + require.NoError(err) + require.Equal(6, len(history.BaseFeePerGas)) + require.Equal(5, len(history.GasUsedRatio)) + require.Equal(ethtypes.EthUint64(16-5+1), history.OldestBlock) + + history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( + json.Marshal([]interface{}{"0x10", "0x12"}), + ).Assert(require.NoError)) + require.NoError(err) + require.Equal(17, len(history.BaseFeePerGas)) + require.Equal(16, len(history.GasUsedRatio)) + require.Equal(ethtypes.EthUint64(18-16+1), history.OldestBlock) + + history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( + json.Marshal([]interface{}{5, "0x10"}), + ).Assert(require.NoError)) + require.NoError(err) + require.Equal(6, len(history.BaseFeePerGas)) + require.Equal(5, len(history.GasUsedRatio)) + require.Equal(ethtypes.EthUint64(16-5+1), history.OldestBlock) + + history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( + json.Marshal([]interface{}{5, "10"}), + ).Assert(require.NoError)) + require.NoError(err) + require.Equal(6, len(history.BaseFeePerGas)) + require.Equal(5, len(history.GasUsedRatio)) + require.Equal(ethtypes.EthUint64(10-5+1), history.OldestBlock) + + history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( + json.Marshal([]interface{}{5, "10", &[]float64{0.25, 0.50, 0.75}}), + ).Assert(require.NoError)) + require.NoError(err) + require.Equal(6, len(history.BaseFeePerGas)) + require.Equal(5, len(history.GasUsedRatio)) + require.Equal(ethtypes.EthUint64(10-5+1), history.OldestBlock) + require.NotNil(history.Reward) + require.Equal(0, len(*history.Reward)) + + history, err = client.EthFeeHistory(ctx, result.Wrap[jsonrpc.RawParams]( + json.Marshal([]interface{}{1025, "10", &[]float64{0.25, 0.50, 0.75}}), + ).Assert(require.NoError)) + require.Error(err) +} diff --git a/node/impl/full/dummy.go b/node/impl/full/dummy.go index 0b4b0080c..9abe00321 100644 --- a/node/impl/full/dummy.go +++ b/node/impl/full/dummy.go @@ -80,7 +80,7 @@ func (e *EthModuleDummy) EthGetBalance(ctx context.Context, address ethtypes.Eth return ethtypes.EthBigIntZero, ErrModuleDisabled } -func (e *EthModuleDummy) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) { +func (e *EthModuleDummy) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { return ethtypes.EthFeeHistory{}, ErrModuleDisabled } diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index bfbd6dbda..bf1e26a43 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -57,7 +57,7 @@ type EthModuleAPI interface { EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) 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) - EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) + EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) NetVersion(ctx context.Context) (string, error) NetListening(ctx context.Context) (bool, error) @@ -581,8 +581,12 @@ func (a *EthModule) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) return ethtypes.EthUint64(build.Eip155ChainId), nil } -func (a *EthModule) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlkNum string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) { - if blkCount > 1024 { +func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { + params, err := jsonrpc.DecodeParams[ethtypes.EthFeeHistoryParams](p) + if err != nil { + return ethtypes.EthFeeHistory{}, xerrors.Errorf("decoding params: %w", err) + } + if params.BlkCount > 1024 { return ethtypes.EthFeeHistory{}, fmt.Errorf("block count should be smaller than 1024") } @@ -590,7 +594,7 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint // TODO https://github.com/filecoin-project/ref-fvm/issues/1016 var blkNum ethtypes.EthUint64 - err := blkNum.UnmarshalJSON([]byte(`"` + newestBlkNum + `"`)) + err = blkNum.UnmarshalJSON([]byte(`"` + params.NewestBlkNum + `"`)) if err == nil && uint64(blkNum) < newestBlkHeight { newestBlkHeight = uint64(blkNum) } @@ -598,8 +602,8 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint // Deal with the case that the chain is shorter than the number of // requested blocks. oldestBlkHeight := uint64(1) - if uint64(blkCount) <= newestBlkHeight { - oldestBlkHeight = newestBlkHeight - uint64(blkCount) + 1 + if uint64(params.BlkCount) <= newestBlkHeight { + oldestBlkHeight = newestBlkHeight - uint64(params.BlkCount) + 1 } ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(newestBlkHeight), nil, false) @@ -633,7 +637,6 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint } // Reverse the arrays; we collected them newest to oldest; the client expects oldest to newest. - for i, j := 0, len(baseFeeArray)-1; i < j; i, j = i+1, j-1 { baseFeeArray[i], baseFeeArray[j] = baseFeeArray[j], baseFeeArray[i] } @@ -641,11 +644,16 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint gasUsedRatioArray[i], gasUsedRatioArray[j] = gasUsedRatioArray[j], gasUsedRatioArray[i] } - return ethtypes.EthFeeHistory{ + ret := ethtypes.EthFeeHistory{ OldestBlock: ethtypes.EthUint64(oldestBlkHeight), BaseFeePerGas: baseFeeArray, GasUsedRatio: gasUsedRatioArray, - }, nil + } + if params.RewardPercentiles != nil { + reward := make([][]ethtypes.EthBigInt, 0) + ret.Reward = &reward + } + return ret, nil } func (a *EthModule) NetVersion(ctx context.Context) (string, error) { From 5e56af33df3027a5996ed1230adaa23df105b54e Mon Sep 17 00:00:00 2001 From: ychiao Date: Fri, 10 Feb 2023 13:52:25 -0500 Subject: [PATCH 4/9] fix typo --- chain/types/ethtypes/eth_types.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index c8307eed3..621331de1 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -40,9 +40,10 @@ func (e EthUint64) MarshalJSON() ([]byte, error) { return json.Marshal(e.Hex()) } -// UnmarshalJSON should be able to parse two types of input: +// UnmarshalJSON should be able to parse these types of input: // 1. a JSON string containing a hex-encoded uint64 starting with 0x -// 2. a valid string containing a uint64 in decimal +// 2. a JSON string containing an uint64 in decimal +// 3. a string containing an uint64 in decimal func (e *EthUint64) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err == nil { @@ -737,7 +738,7 @@ func GetContractEthAddressFromCode(sender EthAddress, salt [32]byte, initcode [] return ethAddr, nil } -// EthFeeHistoryParams handles raw jsonrpc params for eth_subscribe +// EthFeeHistoryParams handles raw jsonrpc params for eth_feeHistory type EthFeeHistoryParams struct { BlkCount EthUint64 NewestBlkNum string From 778768dbf7afb9dd58979ac0ecbcac131a1205d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 10 Feb 2023 22:29:44 +0000 Subject: [PATCH 5/9] eth_feeHistory: parse block param correctly. --- node/impl/full/eth.go | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index f3e2a3ba4..e84af461d 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -590,30 +590,21 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth return ethtypes.EthFeeHistory{}, fmt.Errorf("block count should be smaller than 1024") } - newestBlkHeight := uint64(a.Chain.GetHeaviestTipSet().Height()) - - // TODO https://github.com/filecoin-project/ref-fvm/issues/1016 - var blkNum ethtypes.EthUint64 - err = blkNum.UnmarshalJSON([]byte(`"` + params.NewestBlkNum + `"`)) - if err == nil && uint64(blkNum) < newestBlkHeight { - newestBlkHeight = uint64(blkNum) - } - - // Deal with the case that the chain is shorter than the number of - // requested blocks. - oldestBlkHeight := uint64(1) - if uint64(params.BlkCount) <= newestBlkHeight { - oldestBlkHeight = newestBlkHeight - uint64(params.BlkCount) + 1 - } - - ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(newestBlkHeight), nil, false) + ts, err := a.parseBlkParam(ctx, params.NewestBlkNum) if err != nil { - return ethtypes.EthFeeHistory{}, fmt.Errorf("cannot load find block height: %v", newestBlkHeight) + return ethtypes.EthFeeHistory{}, fmt.Errorf("bad block parameter %s: %s", params.NewestBlkNum, err) } - // FIXME: baseFeePerGas should include the next block after the newest of the returned range, because this - // can be inferred from the newest block. we use the newest block's baseFeePerGas for now but need to fix it - // In other words, due to deferred execution, we might not be returning the most useful value here for the client. + // Deal with the case that the chain is shorter than the number of requested blocks. + oldestBlkHeight := uint64(1) + if abi.ChainEpoch(params.BlkCount) <= ts.Height() { + oldestBlkHeight = uint64(ts.Height()) - uint64(params.BlkCount) + 1 + } + + // NOTE: baseFeePerGas should include the next block after the newest of the returned range, + // because the next base fee can be inferred from the messages in the newest block. + // However, this is NOT the case in Filecoin due to deferred execution, so the best + // we can do is duplicate the last value. baseFeeArray := []ethtypes.EthBigInt{ethtypes.EthBigInt(ts.Blocks()[0].ParentBaseFee)} gasUsedRatioArray := []float64{} From 8cded9f8d2240c1b3fd504ab2ec861174b8d998d Mon Sep 17 00:00:00 2001 From: ychiao Date: Fri, 10 Feb 2023 17:34:53 -0500 Subject: [PATCH 6/9] add todo --- node/impl/full/eth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index e84af461d..6fc25e2e3 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -641,6 +641,7 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth GasUsedRatio: gasUsedRatioArray, } if params.RewardPercentiles != nil { + // TODO: populate reward percentile reward := make([][]ethtypes.EthBigInt, 0) ret.Reward = &reward } From c61267cadcd466601573ad3ccf64650a34a75f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 10 Feb 2023 22:42:08 +0000 Subject: [PATCH 7/9] fix bad test. --- itests/eth_conformance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itests/eth_conformance_test.go b/itests/eth_conformance_test.go index c190e03fe..8a367d6b1 100644 --- a/itests/eth_conformance_test.go +++ b/itests/eth_conformance_test.go @@ -192,7 +192,7 @@ func TestEthOpenRPCConformance(t *testing.T) { { method: "eth_feeHistory", call: func(a *ethAPIRaw) (json.RawMessage, error) { - return ethapi.EthFeeHistory(context.Background(), ethtypes.EthUint64(2), "", nil) + return ethapi.EthFeeHistory(context.Background(), ethtypes.EthUint64(2), "latest", nil) }, }, From 6a76bbb6e3c38b487cf490dccfa1e6a892a8aa7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Fri, 10 Feb 2023 22:46:57 +0000 Subject: [PATCH 8/9] improve TODO. --- node/impl/full/eth.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 6fc25e2e3..2672894ba 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -641,7 +641,11 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth GasUsedRatio: gasUsedRatioArray, } if params.RewardPercentiles != nil { - // TODO: populate reward percentile + // TODO: Populate reward percentiles + // https://github.com/filecoin-project/lotus/issues/10236 + // We need to calculate the requested percentiles of effective gas premium + // based on the newest block (I presume it's the newest, we need to dig in + // as it's underspecified). Effective means we're clamped at the gas_fee_cap - base_fee. reward := make([][]ethtypes.EthBigInt, 0) ret.Reward = &reward } From 37e1ac5d93bf7d4bf6a7f74bac42c577eb668dab Mon Sep 17 00:00:00 2001 From: raulk Date: Sun, 12 Feb 2023 21:08:42 +0000 Subject: [PATCH 9/9] eth: FIP-0055: implement final version of transitory delegated signature. (#10239) --- chain/types/ethtypes/eth_transactions.go | 35 ++++----------- itests/eth_account_abstraction_test.go | 35 ++++++++------- itests/fevm_test.go | 57 ++++++++++++++++++++++++ itests/kit/evm.go | 3 +- node/impl/full/eth.go | 5 +-- 5 files changed, 89 insertions(+), 46 deletions(-) diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 5c2283d2b..7c065928e 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -117,41 +117,29 @@ func EthTxArgsFromUnsignedEthMessage(msg *types.Message) (EthTxArgs, error) { default: return EthTxArgs{}, fmt.Errorf("unsupported EAM method") } - } else { + } else if msg.Method == builtintypes.MethodsEVM.InvokeContract { addr, err := EthAddressFromFilecoinAddress(msg.To) if err != nil { return EthTxArgs{}, err } to = &addr - if len(msg.Params) == 0 { - if msg.Method != builtintypes.MethodSend { - return EthTxArgs{}, xerrors.Errorf("cannot invoke method %d on non-EAM actor without params", msg.Method) - } - } else { - if msg.Method != builtintypes.MethodsEVM.InvokeContract { - return EthTxArgs{}, - xerrors.Errorf("invalid methodnum %d: only allowed non-send method is InvokeContract(%d)", - msg.Method, - builtintypes.MethodsEVM.InvokeContract) - } - + if len(msg.Params) > 0 { params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) if err != nil { return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err) } } + } else { + return EthTxArgs{}, + xerrors.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)", + msg.Method, builtintypes.MethodsEVM.InvokeContract) } if paramsReader.Len() != 0 { return EthTxArgs{}, xerrors.Errorf("extra data found in params") } - if len(params) == 0 && msg.Method != builtintypes.MethodSend { - // Otherwise, we don't get a guaranteed round-trip. - return EthTxArgs{}, xerrors.Errorf("msgs with empty parameters from an eth-account must be Sends (MethodNum: %d)", msg.Method) - } - return EthTxArgs{ ChainID: build.Eip155ChainId, Nonce: int(msg.Nonce), @@ -170,9 +158,9 @@ func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, er } var err error - method := builtintypes.MethodSend var params []byte var to address.Address + method := builtintypes.MethodsEVM.InvokeContract // nil indicates the EAM, only CreateExternal is allowed if tx.To == nil { to = builtintypes.EthereumAddressManagerActorAddr @@ -192,18 +180,11 @@ func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, er if err != nil { return nil, xerrors.Errorf("failed to convert To into filecoin addr: %w", err) } - if len(tx.Input) == 0 { - // Yes, this is redundant, but let's be sure what we're doing - method = builtintypes.MethodSend - params = make([]byte, 0) - } else { - // must be InvokeContract - method = builtintypes.MethodsEVM.InvokeContract + if len(tx.Input) > 0 { buf := new(bytes.Buffer) if err = cbg.WriteByteArray(buf, tx.Input); err != nil { return nil, xerrors.Errorf("failed to write input args: %w", err) } - params = buf.Bytes() } } diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go index e6d9723bf..8d92d0a04 100644 --- a/itests/eth_account_abstraction_test.go +++ b/itests/eth_account_abstraction_test.go @@ -24,7 +24,7 @@ import ( "github.com/filecoin-project/lotus/itests/kit" ) -// TestEthAccountAbstraction goes over the account abstraction workflow: +// TestEthAccountAbstraction goes over the placeholder creation and promotion workflow: // - an placeholder is created when it receives a message // - the placeholder turns into an EOA when it sends a message func TestEthAccountAbstraction(t *testing.T) { @@ -67,7 +67,8 @@ func TestEthAccountAbstraction(t *testing.T) { msgFromPlaceholder := &types.Message{ From: placeholderAddress, // self-send because an "eth tx payload" can't be to a filecoin address? - To: placeholderAddress, + To: placeholderAddress, + Method: builtin2.MethodsEVM.InvokeContract, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) @@ -100,9 +101,10 @@ func TestEthAccountAbstraction(t *testing.T) { // Send another message, it should succeed without any code CID changes msgFromPlaceholder = &types.Message{ - From: placeholderAddress, - To: placeholderAddress, - Nonce: 1, + From: placeholderAddress, + To: placeholderAddress, + Method: builtin2.MethodsEVM.InvokeContract, + Nonce: 1, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) @@ -152,9 +154,10 @@ func TestEthAccountAbstractionFailure(t *testing.T) { // create a placeholder actor at the target address msgCreatePlaceholder := &types.Message{ - From: client.DefaultKey.Address, - To: placeholderAddress, - Value: abi.TokenAmount(types.MustParseFIL("100")), + From: client.DefaultKey.Address, + To: placeholderAddress, + Value: abi.TokenAmount(types.MustParseFIL("100")), + Method: builtin2.MethodsEVM.InvokeContract, } smCreatePlaceholder, err := client.MpoolPushMessage(ctx, msgCreatePlaceholder, nil) require.NoError(t, err) @@ -172,9 +175,10 @@ func TestEthAccountAbstractionFailure(t *testing.T) { // send a message from the placeholder address msgFromPlaceholder := &types.Message{ - From: placeholderAddress, - To: placeholderAddress, - Value: abi.TokenAmount(types.MustParseFIL("20")), + From: placeholderAddress, + To: placeholderAddress, + Value: abi.TokenAmount(types.MustParseFIL("20")), + Method: builtin2.MethodsEVM.InvokeContract, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) @@ -209,10 +213,11 @@ func TestEthAccountAbstractionFailure(t *testing.T) { // Send a valid message now, it should succeed without any code CID changes msgFromPlaceholder = &types.Message{ - From: placeholderAddress, - To: placeholderAddress, - Nonce: 1, - Value: abi.NewTokenAmount(1), + From: placeholderAddress, + To: placeholderAddress, + Nonce: 1, + Value: abi.NewTokenAmount(1), + Method: builtin2.MethodsEVM.InvokeContract, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) diff --git a/itests/fevm_test.go b/itests/fevm_test.go index 6cdd440dd..2b2a3a6d3 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "fmt" "testing" + "time" "github.com/stretchr/testify/require" @@ -17,6 +18,7 @@ import ( "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" @@ -786,3 +788,58 @@ func TestFEVMDestroyCreate2(t *testing.T) { require.Equal(t, ethFromAddr, senderSecondCall) } + +func TestFEVMBareTransferTriggersSmartContractLogic(t *testing.T) { + ctx, cancel, client := kit.SetupFEVMTest(t) + defer cancel() + + // This contract emits an event on receiving value. + filename := "contracts/ValueSender.hex" + _, contractAddr := client.EVM().DeployContractFromFilename(ctx, filename) + + accctKey, accntEth, accntFil := client.EVM().NewAccount() + kit.SendFunds(ctx, t, client, accntFil, types.FromFil(10)) + + contractEth, err := ethtypes.EthAddressFromFilecoinAddress(contractAddr) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: &accntEth, + To: &contractEth, + Value: ethtypes.EthBigInt(big.NewInt(100)), + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.NewInt(100), + Nonce: 0, + To: &contractEth, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, accctKey.PrivateKey) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + + var receipt *api.EthTxReceipt + for i := 0; i < 1000; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + require.NoError(t, err) + if receipt != nil { + break + } + time.Sleep(500 * time.Millisecond) + } + + // The receive() function emits one log, that's how we know we hit it. + require.Len(t, receipt.Logs, 1) +} diff --git a/itests/kit/evm.go b/itests/kit/evm.go index 8a764793c..216a7f1f3 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -354,7 +354,7 @@ func SetupFEVMTest(t *testing.T) (context.Context, context.CancelFunc, *TestFull return ctx, cancel, client } -func (e *EVM) TransferValueOrFail(ctx context.Context, fromAddr address.Address, toAddr address.Address, sendAmount big.Int) { +func (e *EVM) TransferValueOrFail(ctx context.Context, fromAddr address.Address, toAddr address.Address, sendAmount big.Int) *api.MsgLookup { sendMsg := &types.Message{ From: fromAddr, To: toAddr, @@ -365,6 +365,7 @@ func (e *EVM) TransferValueOrFail(ctx context.Context, fromAddr address.Address, mLookup, err := e.StateWaitMsg(ctx, signedMsg.Cid(), 3, api.LookbackNoLimit, true) require.NoError(e.t, err) require.Equal(e.t, exitcode.Ok, mLookup.Receipt.ExitCode) + return mLookup } func NewEthFilterBuilder() *EthFilterBuilder { diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 2672894ba..8401d7d51 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -765,10 +765,9 @@ func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.Et return nil, fmt.Errorf("failed to encode tx input into a cbor byte-string") } params = buf.Bytes() - method = builtintypes.MethodsEVM.InvokeContract - } else { - method = builtintypes.MethodSend } + + method = builtintypes.MethodsEVM.InvokeContract } return &types.Message{