diff --git a/.circleci/config.yml b/.circleci/config.yml index a19703272..c264b8e5f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1016,29 +1016,28 @@ workflows: requires: - build suite: utest-unit-cli - target: "./cli/... ./cmd/... ./api/..." + executor: golang-2xl get-params: true - test: name: test-unit-node requires: - build suite: utest-unit-node - target: "./node/..." - + - test: name: test-unit-rest requires: - build suite: utest-unit-rest - target: "./blockstore/... ./build/... ./chain/... ./conformance/... ./gateway/... ./journal/... ./lib/... ./markets/... ./paychmgr/... ./tools/..." - + + executor: golang-2xl - test: name: test-unit-storage requires: - build suite: utest-unit-storage - target: "./storage/... ./extern/..." - + target: "./storage/... + get-params: true - test: go-test-flags: "-run=TestMulticoreSDR" requires: diff --git a/.circleci/template.yml b/.circleci/template.yml index 2bfba9564..25d0fe67b 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -567,6 +567,16 @@ workflows: suite: utest-[[ $suite ]] target: "[[ $pkgs ]]" [[if eq $suite "unit-cli"]]get-params: true[[end]] + [[- if eq $suite "unit-storage"]] + get-params: true + [[- end -]] + [[- if eq $suite "unit-cli"]] + executor: golang-2xl + get-params: true + [[- end -]] + [[- if eq $suite "unit-rest"]] + executor: golang-2xl + [[- end -]] [[- end]] - test: go-test-flags: "-run=TestMulticoreSDR" diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d336362..6fb092922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - chore: Auto remove local chain data when importing chain file or snapshot ([filecoin-project/lotus#11277](https://github.com/filecoin-project/lotus/pull/11277)) - feat: metric: export Mpool message count ([filecoin-project/lotus#11361](https://github.com/filecoin-project/lotus/pull/11361)) - feat: sealing: load SectorsSummary from sealing SectorStats instead of calling API each time ([filecoin-project/lotus#11353](https://github.com/filecoin-project/lotus/pull/11353)) +- fix: miner info: Show correct sector state counts ([filecoin-project/lotus#11456](https://github.com/filecoin-project/lotus/pull/11456)) +- feat: add support for specifying block number when calling `eth_estimateGas` ([filecoin-project/lotus#11462](https://github.com/filecoin-project/lotus/pull/11462)). ## Improvements - fix: Add time slicing to splitstore purging step during compaction to reduce lock congestion [filecoin-project/lotus#11269](https://github.com/filecoin-project/lotus/pull/11269) @@ -13,6 +15,7 @@ - feat: Unambiguously translate native messages to Ethereum transactions by: - Detecting native messages that "look" like Ethereum transactions (creating smart contracts, invoking a smart contract, etc.), and decoding them as such. - Otherwise, ABI-encoding the inputs as if they were calls to a `handle_filecoin_method` Solidity method. +- fix: ensure that the Ethereum API never returns "empty" addresses for native messages. When a "to" address cannot be resolved to a 0x-style address, it will be re-written to `0xff0000000000000000000000ffffffffffffffff`. This can only happen when the native transaction _reverted_ (failing to create an account at the specified "to" address). # v 1.25.0 / 2023-11-22 diff --git a/api/api_full.go b/api/api_full.go index f919bc13b..4ae2ea531 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -824,7 +824,7 @@ type FullNode interface { 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 + EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) //perm:read EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) //perm:read EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) //perm:read diff --git a/api/api_gateway.go b/api/api_gateway.go index 27e725457..238bf43ab 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -114,7 +114,7 @@ type Gateway interface { EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, 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) + EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 856d83813..92b719550 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1042,7 +1042,7 @@ func (mr *MockFullNodeMockRecorder) EthChainId(arg0 interface{}) *gomock.Call { } // EthEstimateGas mocks base method. -func (m *MockFullNode) EthEstimateGas(arg0 context.Context, arg1 ethtypes.EthCall) (ethtypes.EthUint64, error) { +func (m *MockFullNode) EthEstimateGas(arg0 context.Context, arg1 jsonrpc.RawParams) (ethtypes.EthUint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "EthEstimateGas", arg0, arg1) ret0, _ := ret[0].(ethtypes.EthUint64) diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 2d1333495..8adcbc189 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -255,7 +255,7 @@ type FullNodeMethods struct { EthChainId func(p0 context.Context) (ethtypes.EthUint64, error) `perm:"read"` - EthEstimateGas func(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) `perm:"read"` + EthEstimateGas func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthUint64, error) `perm:"read"` EthFeeHistory func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) `perm:"read"` @@ -679,7 +679,7 @@ type GatewayMethods struct { EthChainId func(p0 context.Context) (ethtypes.EthUint64, error) `` - EthEstimateGas func(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) `` + EthEstimateGas func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthUint64, error) `` EthFeeHistory func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) `` @@ -2134,14 +2134,14 @@ func (s *FullNodeStub) EthChainId(p0 context.Context) (ethtypes.EthUint64, error return *new(ethtypes.EthUint64), ErrNotSupported } -func (s *FullNodeStruct) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { +func (s *FullNodeStruct) EthEstimateGas(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthUint64, error) { if s.Internal.EthEstimateGas == nil { return *new(ethtypes.EthUint64), ErrNotSupported } return s.Internal.EthEstimateGas(p0, p1) } -func (s *FullNodeStub) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { +func (s *FullNodeStub) EthEstimateGas(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthUint64, error) { return *new(ethtypes.EthUint64), ErrNotSupported } @@ -4400,14 +4400,14 @@ func (s *GatewayStub) EthChainId(p0 context.Context) (ethtypes.EthUint64, error) return *new(ethtypes.EthUint64), ErrNotSupported } -func (s *GatewayStruct) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { +func (s *GatewayStruct) EthEstimateGas(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthUint64, error) { if s.Internal.EthEstimateGas == nil { return *new(ethtypes.EthUint64), ErrNotSupported } return s.Internal.EthEstimateGas(p0, p1) } -func (s *GatewayStub) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { +func (s *GatewayStub) EthEstimateGas(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthUint64, error) { return *new(ethtypes.EthUint64), ErrNotSupported } diff --git a/build/isnearupgrade.go b/build/isnearupgrade.go index 4273f0e9e..74975780f 100644 --- a/build/isnearupgrade.go +++ b/build/isnearupgrade.go @@ -5,5 +5,8 @@ import ( ) func IsNearUpgrade(epoch, upgradeEpoch abi.ChainEpoch) bool { + if upgradeEpoch < 0 { + return false + } return epoch > upgradeEpoch-Finality && epoch < upgradeEpoch+Finality } diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 29131b824..994266492 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 cf83f9390..2a3e5c939 100644 Binary files a/build/openrpc/gateway.json.gz and b/build/openrpc/gateway.json.gz differ diff --git a/chain/store/index_test.go b/chain/store/index_test.go index a3a4ad6ce..3cde40062 100644 --- a/chain/store/index_test.go +++ b/chain/store/index_test.go @@ -42,7 +42,7 @@ func TestIndexSeeks(t *testing.T) { cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), filcns.Weight, nil) defer cs.Close() //nolint:errcheck - _, err = cs.Import(ctx, bytes.NewReader(gencar)) + _, _, err = cs.Import(ctx, bytes.NewReader(gencar)) if err != nil { t.Fatal(err) } diff --git a/chain/store/snapshot.go b/chain/store/snapshot.go index 301a5f87b..de2190c5d 100644 --- a/chain/store/snapshot.go +++ b/chain/store/snapshot.go @@ -60,7 +60,7 @@ func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, inclRecentRo }) } -func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, error) { +func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (head *types.TipSet, genesis *types.BlockHeader, err error) { // TODO: writing only to the state blockstore is incorrect. // At this time, both the state and chain blockstores are backed by the // universal store. When we physically segregate the stores, we will need @@ -69,7 +69,7 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e br, err := carv2.NewBlockReader(r) if err != nil { - return nil, xerrors.Errorf("loadcar failed: %w", err) + return nil, nil, xerrors.Errorf("loadcar failed: %w", err) } s := cs.StateBlockstore() @@ -80,27 +80,51 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e putThrottle <- nil } + if len(br.Roots) == 0 { + return nil, nil, xerrors.Errorf("no roots in snapshot car file") + } + nextTailCid := br.Roots[0] + + var tailBlock types.BlockHeader + tailBlock.Height = abi.ChainEpoch(-1) + var buf []blocks.Block for { blk, err := br.Next() if err != nil { + + // we're at the end if err == io.EOF { if len(buf) > 0 { if err := s.PutMany(ctx, buf); err != nil { - return nil, err + return nil, nil, err } } break } - return nil, err + return nil, nil, err } + // check for header block, looking for genesis + if blk.Cid() == nextTailCid && tailBlock.Height != 0 { + if err := tailBlock.UnmarshalCBOR(bytes.NewReader(blk.RawData())); err != nil { + return nil, nil, xerrors.Errorf("failed to unmarshal genesis block: %w", err) + } + if len(tailBlock.Parents) > 0 { + nextTailCid = tailBlock.Parents[0] + } else { + // note: even the 0th block has a parent linking to the cbor genesis block + return nil, nil, xerrors.Errorf("current block (epoch %d cid %s) has no parents", tailBlock.Height, tailBlock.Cid()) + } + } + + // append to batch buf = append(buf, blk) if len(buf) > 1000 { if lastErr := <-putThrottle; lastErr != nil { // consume one error to have the right to add one - return nil, lastErr + return nil, nil, lastErr } go func(buf []blocks.Block) { @@ -113,13 +137,17 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e // check errors for i := 0; i < parallelPuts; i++ { if lastErr := <-putThrottle; lastErr != nil { - return nil, lastErr + return nil, nil, lastErr } } + if tailBlock.Height != 0 { + return nil, nil, xerrors.Errorf("expected genesis block to have height 0 (genesis), got %d: %s", tailBlock.Height, tailBlock.Cid()) + } + root, err := cs.LoadTipSet(ctx, types.NewTipSetKey(br.Roots...)) if err != nil { - return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) + return nil, nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) } ts := root @@ -135,10 +163,10 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e } if err := cs.PersistTipsets(ctx, tssToPersist); err != nil { - return nil, xerrors.Errorf("failed to persist tipsets: %w", err) + return nil, nil, xerrors.Errorf("failed to persist tipsets: %w", err) } - return root, nil + return root, &tailBlock, nil } type walkSchedTaskType int diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 9c717fdbe..1ecfc474a 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -118,7 +118,7 @@ func TestChainExportImport(t *testing.T) { cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) defer cs.Close() //nolint:errcheck - root, err := cs.Import(context.TODO(), buf) + root, _, err := cs.Import(context.TODO(), buf) if err != nil { t.Fatal(err) } @@ -153,7 +153,7 @@ func TestChainImportTipsetKeyCid(t *testing.T) { cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) defer cs.Close() //nolint:errcheck - root, err := cs.Import(ctx, buf) + root, _, err := cs.Import(ctx, buf) require.NoError(t, err) require.Truef(t, root.Equals(last), "imported chain differed from exported chain") @@ -202,7 +202,7 @@ func TestChainExportImportFull(t *testing.T) { cs := store.NewChainStore(nbs, nbs, ds, filcns.Weight, nil) defer cs.Close() //nolint:errcheck - root, err := cs.Import(context.TODO(), buf) + root, _, err := cs.Import(context.TODO(), buf) if err != nil { t.Fatal(err) } diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index b796e6f56..b933329f4 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -799,6 +799,45 @@ func GetContractEthAddressFromCode(sender EthAddress, salt [32]byte, initcode [] return ethAddr, nil } +// EthEstimateGasParams handles raw jsonrpc params for eth_estimateGas +type EthEstimateGasParams struct { + Tx EthCall + BlkParam *EthBlockNumberOrHash +} + +func (e *EthEstimateGasParams) UnmarshalJSON(b []byte) error { + var params []json.RawMessage + err := json.Unmarshal(b, ¶ms) + if err != nil { + return err + } + + switch len(params) { + case 2: + err = json.Unmarshal(params[1], &e.BlkParam) + if err != nil { + return err + } + fallthrough + case 1: + err = json.Unmarshal(params[0], &e.Tx) + if err != nil { + return err + } + default: + return xerrors.Errorf("expected 1 or 2 params, got %d", len(params)) + } + + return nil +} + +func (e EthEstimateGasParams) MarshalJSON() ([]byte, error) { + if e.BlkParam != nil { + return json.Marshal([]interface{}{e.Tx, e.BlkParam}) + } + return json.Marshal([]interface{}{e.Tx}) +} + // EthFeeHistoryParams handles raw jsonrpc params for eth_feeHistory type EthFeeHistoryParams struct { BlkCount EthUint64 diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 95b91054a..16adbad60 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -304,7 +304,7 @@ var importBenchCmd = &cli.Command{ return fmt.Errorf("no CAR file provided for import") } - head, err = cs.Import(cctx.Context, carFile) + head, _, err = cs.Import(cctx.Context, carFile) if err != nil { return err } diff --git a/cmd/lotus-bench/simple.go b/cmd/lotus-bench/simple.go index 8341c4b53..09df22078 100644 --- a/cmd/lotus-bench/simple.go +++ b/cmd/lotus-bench/simple.go @@ -658,6 +658,10 @@ var simpleWinningPost = &cli.Command{ Usage: "pass miner address (only necessary if using existing sectorbuilder)", Value: "t01000", }, + &cli.BoolFlag{ + Name: "show-inputs", + Usage: "output inputs for winning post generation", + }, }, ArgsUsage: "[sealed] [cache] [comm R] [sector num]", Action: func(cctx *cli.Context) error { @@ -728,6 +732,17 @@ var simpleWinningPost = &cli.Command{ fmt.Printf("Vanilla %s (%s)\n", challenge.Sub(start), bps(sectorSize, 1, challenge.Sub(start))) fmt.Printf("Proof %s (%s)\n", end.Sub(challenge), bps(sectorSize, 1, end.Sub(challenge))) fmt.Println(base64.StdEncoding.EncodeToString(proof[0].ProofBytes)) + + if cctx.Bool("show-inputs") { + fmt.Println("GenerateWinningPoStWithVanilla info:") + + fmt.Printf(" wpt: %d\n", wpt) + fmt.Printf(" mid: %d\n", mid) + fmt.Printf(" rand: %x\n", rand) + fmt.Printf(" vp: %x\n", vp) + fmt.Printf(" proof: %x\n", proof) + } + return nil }, } diff --git a/cmd/lotus-shed/genesis-verify.go b/cmd/lotus-shed/genesis-verify.go index 6795f1528..4ead8467e 100644 --- a/cmd/lotus-shed/genesis-verify.go +++ b/cmd/lotus-shed/genesis-verify.go @@ -62,7 +62,7 @@ var genesisVerifyCmd = &cli.Command{ return xerrors.Errorf("opening the car file: %w", err) } - ts, err := cs.Import(cctx.Context, f) + ts, _, err := cs.Import(cctx.Context, f) if err != nil { return err } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 042e89f21..5d8096d1f 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -585,7 +585,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) } bar.Start() - ts, err := cst.Import(ctx, ir) + ts, gen, err := cst.Import(ctx, ir) bar.Finish() if err != nil { @@ -596,18 +596,14 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) return xerrors.Errorf("flushing validation cache failed: %w", err) } - gb, err := cst.GetTipsetByHeight(ctx, 0, ts, true) - if err != nil { - return err - } - - err = cst.SetGenesis(ctx, gb.Blocks()[0]) + log.Infof("setting genesis") + err = cst.SetGenesis(ctx, gen) if err != nil { return err } if !snapshot { - shd, err := drand.BeaconScheduleFromDrandSchedule(build.DrandConfigSchedule(), gb.MinTimestamp(), nil) + shd, err := drand.BeaconScheduleFromDrandSchedule(build.DrandConfigSchedule(), gen.Timestamp, nil) if err != nil { return xerrors.Errorf("failed to construct beacon schedule: %w", err) } diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 1dae12101..c2929f0f5 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -2402,14 +2402,7 @@ Perms: read Inputs: ```json [ - { - "from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", - "to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", - "gas": "0x5", - "gasPrice": "0x0", - "value": "0x0", - "data": "0x07" - } + "Bw==" ] ``` diff --git a/gateway/node.go b/gateway/node.go index 367e645c1..f2464d274 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -131,7 +131,7 @@ type TargetAPI interface { EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, 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) + EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) diff --git a/gateway/proxy_eth.go b/gateway/proxy_eth.go index e6d433a17..13a12acfa 100644 --- a/gateway/proxy_eth.go +++ b/gateway/proxy_eth.go @@ -353,13 +353,19 @@ func (gw *Node) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt return gw.target.EthMaxPriorityFeePerGas(ctx) } -func (gw *Node) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { +func (gw *Node) EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) { + // validate params + _, err := jsonrpc.DecodeParams[ethtypes.EthEstimateGasParams](p) + if err != nil { + return ethtypes.EthUint64(0), xerrors.Errorf("decoding params: %w", err) + } + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { return 0, err } // todo limit gas? to what? - return gw.target.EthEstimateGas(ctx, tx) + return gw.target.EthEstimateGas(ctx, p) } func (gw *Node) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) { diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go index 8d92d0a04..5ca672674 100644 --- a/itests/eth_account_abstraction_test.go +++ b/itests/eth_account_abstraction_test.go @@ -3,6 +3,7 @@ package itests import ( "context" "encoding/hex" + "encoding/json" "os" "testing" "time" @@ -272,10 +273,13 @@ func TestEthAccountAbstractionFailsFromEvmActor(t *testing.T) { contract, err := hex.DecodeString(string(contractHex)) require.NoError(t, err) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) diff --git a/itests/eth_conformance_test.go b/itests/eth_conformance_test.go index 15b249977..9c1b2ae34 100644 --- a/itests/eth_conformance_test.go +++ b/itests/eth_conformance_test.go @@ -35,7 +35,7 @@ type ethAPIRaw struct { EthBlockNumber func(context.Context) (json.RawMessage, error) EthCall func(context.Context, ethtypes.EthCall, ethtypes.EthBlockNumberOrHash) (json.RawMessage, error) EthChainId func(context.Context) (json.RawMessage, error) - EthEstimateGas func(context.Context, ethtypes.EthCall) (json.RawMessage, error) + EthEstimateGas func(context.Context, jsonrpc.RawParams) (json.RawMessage, error) EthFeeHistory func(context.Context, ethtypes.EthUint64, string, []float64) (json.RawMessage, error) EthGasPrice func(context.Context) (json.RawMessage, error) EthGetBalance func(context.Context, ethtypes.EthAddress, ethtypes.EthBlockNumberOrHash) (json.RawMessage, error) @@ -182,10 +182,13 @@ func TestEthOpenRPCConformance(t *testing.T) { { method: "eth_estimateGas", call: func(a *ethAPIRaw) (json.RawMessage, error) { - return ethapi.EthEstimateGas(context.Background(), ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: &senderEthAddr, Data: contractBin, - }) + }}) + require.NoError(t, err) + + return ethapi.EthEstimateGas(ctx, gasParams) }, }, @@ -448,10 +451,13 @@ func TestEthOpenRPCConformance(t *testing.T) { } func createRawSignedEthTx(ctx context.Context, t *testing.T, client *kit.TestFullNode, senderEthAddr ethtypes.EthAddress, receiverEthAddr ethtypes.EthAddress, senderKey *key.Key, contractBin []byte) []byte { - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: &senderEthAddr, Data: contractBin, - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) diff --git a/itests/eth_deploy_test.go b/itests/eth_deploy_test.go index ce4c94a28..68861f98f 100644 --- a/itests/eth_deploy_test.go +++ b/itests/eth_deploy_test.go @@ -60,10 +60,13 @@ func TestDeployment(t *testing.T) { // verify the deployer address is an Placeholder. client.AssertActorType(ctx, deployer, manifest.PlaceholderKey) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) diff --git a/itests/eth_hash_lookup_test.go b/itests/eth_hash_lookup_test.go index e1474a90d..0a2c11d3c 100644 --- a/itests/eth_hash_lookup_test.go +++ b/itests/eth_hash_lookup_test.go @@ -3,6 +3,7 @@ package itests import ( "context" "encoding/hex" + "encoding/json" "os" "testing" "time" @@ -47,10 +48,13 @@ func TestTransactionHashLookup(t *testing.T) { // send some funds to the f410 address kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) @@ -350,10 +354,13 @@ func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) { // send some funds to the f410 address kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go index 026ac2387..9e9fb7b87 100644 --- a/itests/eth_transactions_test.go +++ b/itests/eth_transactions_test.go @@ -3,17 +3,21 @@ package itests import ( "context" "encoding/hex" + "encoding/json" "os" "testing" "time" "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" builtin2 "github.com/filecoin-project/go-state-types/builtin" + "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/actors" "github.com/filecoin-project/lotus/chain/store" @@ -44,12 +48,19 @@ func TestValueTransferValidSignature(t *testing.T) { kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ - From: ðAddr, - Data: contract, + blkParam := ethtypes.NewEthBlockNumberOrHashFromPredefined("latest") + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{ + Tx: ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }, + BlkParam: &blkParam, }) require.NoError(t, err) + gaslimit, err := client.EthEstimateGas(ctx, gasParams) + require.NoError(t, err) + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) require.NoError(t, err) @@ -231,11 +242,14 @@ func TestContractInvocation(t *testing.T) { params, err := hex.DecodeString("f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064") require.NoError(t, err) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, To: &contractAddr, Data: params, - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) @@ -350,10 +364,15 @@ func TestGetBlockByNumber(t *testing.T) { } func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthTxArgs, error) { - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, Data: contract, - }) + }}) + if err != nil { + return nil, err + } + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) if err != nil { return nil, err } @@ -378,7 +397,54 @@ func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr eth }, nil } -func TestEthTxFromNativeAccount(t *testing.T) { +// Invoke a contract with empty input. +func TestEthTxFromNativeAccount_EmptyInput(t *testing.T) { + blockTime := 10 * 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() + + secpAddr, err := address.NewSecp256k1Address([]byte("foobar")) + require.NoError(t, err) + + msg := &types.Message{ + From: client.DefaultKey.Address, + To: secpAddr, + Value: abi.TokenAmount(types.MustParseFIL("100")), + Method: builtin2.MethodsEVM.InvokeContract, + } + + sMsg, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + client.WaitMsg(ctx, sMsg.Cid()) + + hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid()) + require.NoError(t, err) + tx, err := client.EthGetTransactionByHash(ctx, hash) + require.NoError(t, err) + + // Expect empty input params given that we "invoked" the contract (well, invoked ourselves). + require.Equal(t, ethtypes.EthBytes{}, tx.Input) + + // Validate the to/from addresses. + toId, err := client.StateLookupID(ctx, msg.To, types.EmptyTSK) + require.NoError(t, err) + fromId, err := client.StateLookupID(ctx, msg.From, types.EmptyTSK) + require.NoError(t, err) + + expectedTo, err := ethtypes.EthAddressFromFilecoinAddress(toId) + require.NoError(t, err) + expectedFrom, err := ethtypes.EthAddressFromFilecoinAddress(fromId) + require.NoError(t, err) + require.Equal(t, &expectedTo, tx.To) + require.Equal(t, expectedFrom, tx.From) +} + +// Invoke a contract with non-empty input. +func TestEthTxFromNativeAccount_NonEmptyInput(t *testing.T) { blockTime := 10 * time.Millisecond client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) @@ -394,49 +460,48 @@ func TestEthTxFromNativeAccount(t *testing.T) { Method: builtin2.MethodsEVM.InvokeContract, } - // Send a message with no input. + var err error + input := abi.CborBytes([]byte{0x1, 0x2, 0x3, 0x4}) + msg.Params, err = actors.SerializeParams(&input) + require.NoError(t, err) sMsg, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) client.WaitMsg(ctx, sMsg.Cid()) - hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid()) require.NoError(t, err) tx, err := client.EthGetTransactionByHash(ctx, hash) require.NoError(t, err) - // Expect empty input params given that we "invoked" the contract (well, invoked ourselves). - require.Equal(t, ethtypes.EthBytes{}, tx.Input) - - // Send a message with some input. - - input := abi.CborBytes([]byte{0x1, 0x2, 0x3, 0x4}) - msg.Params, err = actors.SerializeParams(&input) - require.NoError(t, err) - - sMsg, err = client.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - client.WaitMsg(ctx, sMsg.Cid()) - hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid()) - require.NoError(t, err) - tx, err = client.EthGetTransactionByHash(ctx, hash) - require.NoError(t, err) - // Expect the decoded input. require.EqualValues(t, input, tx.Input) +} - // Invoke the contract, but with incorrectly encoded input. We expect this to be abi-encoded - // as if it were any other method call. +// Invoke a contract, but with incorrectly encoded input. We expect this to be abi-encoded as if it +// were any other method call. +func TestEthTxFromNativeAccount_BadInput(t *testing.T) { + blockTime := 10 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) - msg.Params = input - require.NoError(t, err) + ens.InterconnectAll().BeginMining(blockTime) - sMsg, err = client.MpoolPushMessage(ctx, msg, nil) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + msg := &types.Message{ + From: client.DefaultKey.Address, + To: client.DefaultKey.Address, + Value: abi.TokenAmount(types.MustParseFIL("100")), + Method: builtin2.MethodsEVM.InvokeContract, + Params: []byte{0x1, 0x2, 0x3, 0x4}, + } + + sMsg, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) client.WaitMsg(ctx, sMsg.Cid()) - hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid()) + hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid()) require.NoError(t, err) - tx, err = client.EthGetTransactionByHash(ctx, hash) + tx, err := client.EthGetTransactionByHash(ctx, hash) require.NoError(t, err) const expectedHex1 = "868e10c4" + // "handle filecoin method" function selector @@ -451,24 +516,39 @@ func TestEthTxFromNativeAccount(t *testing.T) { // Input: 1, 2, 3, 4 "0102030400000000000000000000000000000000000000000000000000000000" - input, err = hex.DecodeString(expectedHex1) + input, err := hex.DecodeString(expectedHex1) require.NoError(t, err) require.EqualValues(t, input, tx.Input) - // Invoke a random method with the same input. We expect the same result as above, but with - // a different method number. +} - msg.Method++ +// Invoke a native method. +func TestEthTxFromNativeAccount_NativeMethod(t *testing.T) { + blockTime := 10 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) - sMsg, err = client.MpoolPushMessage(ctx, msg, nil) + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + msg := &types.Message{ + From: client.DefaultKey.Address, + To: client.DefaultKey.Address, + Value: abi.TokenAmount(types.MustParseFIL("100")), + Method: builtin2.MethodsEVM.InvokeContract + 1, + Params: []byte{0x1, 0x2, 0x3, 0x4}, + } + + sMsg, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) client.WaitMsg(ctx, sMsg.Cid()) - hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid()) + hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid()) require.NoError(t, err) - tx, err = client.EthGetTransactionByHash(ctx, hash) + tx, err := client.EthGetTransactionByHash(ctx, hash) require.NoError(t, err) - const expectedHex2 = "868e10c4" + // "handle filecoin method" function selector + const expectedHex = "868e10c4" + // "handle filecoin method" function selector // InvokeEVM+1 "00000000000000000000000000000000000000000000000000000000e525aa16" + // CBOR multicodec (0x51) @@ -479,7 +559,51 @@ func TestEthTxFromNativeAccount(t *testing.T) { "0000000000000000000000000000000000000000000000000000000000000004" + // Input: 1, 2, 3, 4 "0102030400000000000000000000000000000000000000000000000000000000" - input, err = hex.DecodeString(expectedHex2) + input, err := hex.DecodeString(expectedHex) require.NoError(t, err) require.EqualValues(t, input, tx.Input) } + +// Send to an invalid receiver. We're checking to make sure we correctly set `txn.To` to the special +// "reverted" eth addr. +func TestEthTxFromNativeAccount_InvalidReceiver(t *testing.T) { + blockTime := 10 * 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() + + to, err := address.NewActorAddress([]byte("foobar")) + require.NoError(t, err) + + msg := &types.Message{ + From: client.DefaultKey.Address, + To: to, + Value: abi.TokenAmount(types.MustParseFIL("100")), + Method: builtin2.MethodsEVM.InvokeContract + 1, + Params: []byte{0x1, 0x2, 0x3, 0x4}, + // We can't estimate gas for a failed message, so we hard-code these values. + GasLimit: 10_000_000, + GasFeeCap: abi.NewTokenAmount(10000), + } + + // We expect the "to" address to be the special "reverted" eth address. + expectedTo, err := ethtypes.ParseEthAddress("ff0000000000000000000000ffffffffffffffff") + require.NoError(t, err) + + sMsg, err := client.WalletSignMessage(ctx, client.DefaultKey.Address, msg) + require.NoError(t, err) + k, err := client.MpoolPush(ctx, sMsg) + require.NoError(t, err) + res, err := client.StateWaitMsg(ctx, k, 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Equal(t, res.Receipt.ExitCode, exitcode.SysErrInvalidReceiver) + + hash, err := client.EthGetTransactionHashByCid(ctx, k) + require.NoError(t, err) + tx, err := client.EthGetTransactionByHash(ctx, hash) + require.NoError(t, err) + require.EqualValues(t, &expectedTo, tx.To) +} diff --git a/itests/fevm_test.go b/itests/fevm_test.go index cb69c036c..2dcb8ef1d 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "encoding/binary" "encoding/hex" + "encoding/json" "fmt" "testing" "time" @@ -657,11 +658,15 @@ func TestFEVMRecursiveActorCallEstimate(t *testing.T) { t.Logf("running with %d recursive calls", r) params := makeParams(r) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: ðAddr, To: &contractAddr, Data: params, - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) require.LessOrEqual(t, int64(gaslimit), build.BlockGasLimit) @@ -816,11 +821,14 @@ func TestFEVMBareTransferTriggersSmartContractLogic(t *testing.T) { contractEth, err := ethtypes.EthAddressFromFilecoinAddress(contractAddr) require.NoError(t, err) - gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ From: &accntEth, To: &contractEth, Value: ethtypes.EthBigInt(big.NewInt(100)), - }) + }}) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, gasParams) require.NoError(t, err) maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) @@ -1034,10 +1042,13 @@ func TestFEVMErrorParsing(t *testing.T) { require.ErrorContains(t, err, expected) }) t.Run("EthEstimateGas", func(t *testing.T) { - _, err := e.EthEstimateGas(ctx, ethtypes.EthCall{ + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ To: &contractAddrEth, Data: entryPoint, - }) + }}) + require.NoError(t, err) + + _, err = e.EthEstimateGas(ctx, gasParams) require.ErrorContains(t, err, expected) }) }) diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go index 1e045c79d..b4e5c1133 100644 --- a/itests/sector_pledge_test.go +++ b/itests/sector_pledge_test.go @@ -223,3 +223,29 @@ func TestPledgeSynth(t *testing.T) { runTest(t, 3) }) } + +func TestSectorsSummary(t *testing.T) { + kit.QuietMiningLogs() + + blockTime := 50 * time.Millisecond + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + nPreseal := 2 + + _, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.PresealSectors(nPreseal)) + ens.InterconnectAll().BeginMining(blockTime) + + miner.PledgeSectors(ctx, 1, 0, nil) + + ms, err := miner.SectorsSummary(ctx) + require.NoError(t, err) + + require.Len(t, ms, 1) // all proving + + for st, n := range ms { + require.Equal(t, api.SectorState(sealing.Proving), st) + require.Equal(t, 1+nPreseal, n) + } +} diff --git a/node/impl/full/dummy.go b/node/impl/full/dummy.go index 743eadf34..11ff95a63 100644 --- a/node/impl/full/dummy.go +++ b/node/impl/full/dummy.go @@ -122,7 +122,7 @@ func (e *EthModuleDummy) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, e return ethtypes.EthBigIntZero, ErrModuleDisabled } -func (e *EthModuleDummy) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { +func (e *EthModuleDummy) EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) { return 0, ErrModuleDisabled } diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 22f78e723..d4298492f 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -66,7 +66,7 @@ type EthModuleAPI interface { NetListening(ctx context.Context) (bool, error) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) - EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) + EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) @@ -1007,8 +1007,13 @@ func (a *EthModule) applyMessage(ctx context.Context, msg *types.Message, tsk ty return res, nil } -func (a *EthModule) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { - msg, err := ethCallToFilecoinMessage(ctx, tx) +func (a *EthModule) EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) { + params, err := jsonrpc.DecodeParams[ethtypes.EthEstimateGasParams](p) + if err != nil { + return ethtypes.EthUint64(0), xerrors.Errorf("decoding params: %w", err) + } + + msg, err := ethCallToFilecoinMessage(ctx, params.Tx) if err != nil { return ethtypes.EthUint64(0), err } @@ -1017,7 +1022,16 @@ func (a *EthModule) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (et // gas estimation actually run. msg.GasLimit = 0 - ts := a.Chain.GetHeaviestTipSet() + var ts *types.TipSet + if params.BlkParam == nil { + ts = a.Chain.GetHeaviestTipSet() + } else { + ts, err = getTipsetByEthBlockNumberOrHash(ctx, a.Chain, *params.BlkParam) + if err != nil { + return ethtypes.EthUint64(0), xerrors.Errorf("failed to process block param: %v; %w", params.BlkParam, err) + } + } + gassedMsg, err := a.GasAPI.GasEstimateMessageGas(ctx, msg, nil, ts.Key()) if err != nil { // On failure, GasEstimateMessageGas doesn't actually return the invocation result, diff --git a/node/impl/full/eth_utils.go b/node/impl/full/eth_utils.go index 7b5a35b40..1e407a321 100644 --- a/node/impl/full/eth_utils.go +++ b/node/impl/full/eth_utils.go @@ -29,6 +29,18 @@ import ( "github.com/filecoin-project/lotus/chain/vm" ) +// The address used in messages to actors that have since been deleted. +// +// 0xff0000000000000000000000ffffffffffffffff +var revertedEthAddress ethtypes.EthAddress + +func init() { + revertedEthAddress[0] = 0xff + for i := 20 - 8; i < 20; i++ { + revertedEthAddress[i] = 0xff + } +} + func getTipsetByBlockNumber(ctx context.Context, chain *store.ChainStore, blkParam string, strict bool) (*types.TipSet, error) { if blkParam == "earliest" { return nil, fmt.Errorf("block param \"earliest\" is not supported") @@ -463,13 +475,19 @@ func newEthTxFromSignedMessage(smsg *types.SignedMessage, st *state.StateTree) ( return ethtypes.EthTx{}, xerrors.Errorf("failed to calculate hash for ethTx: %w", err) } } else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message - tx = ethTxFromNativeMessage(smsg.VMMessage(), st) + tx, err = ethTxFromNativeMessage(smsg.VMMessage(), st) + if err != nil { + return ethtypes.EthTx{}, err + } tx.Hash, err = ethtypes.EthHashFromCid(smsg.Cid()) if err != nil { return ethtypes.EthTx{}, err } } else { // BLS Filecoin message - tx = ethTxFromNativeMessage(smsg.VMMessage(), st) + tx, err = ethTxFromNativeMessage(smsg.VMMessage(), st) + if err != nil { + return ethtypes.EthTx{}, err + } tx.Hash, err = ethtypes.EthHashFromCid(smsg.Message.Cid()) if err != nil { return ethtypes.EthTx{}, err @@ -482,19 +500,33 @@ func newEthTxFromSignedMessage(smsg *types.SignedMessage, st *state.StateTree) ( // Convert a native message to an eth transaction. // // - The state-tree must be from after the message was applied (ideally the following tipset). +// - In some cases, the "to" address may be `0xff0000000000000000000000ffffffffffffffff`. This +// means that the "to" address has not been assigned in the passed state-tree and can only +// happen if the transaction reverted. // // ethTxFromNativeMessage does NOT populate: // - BlockHash // - BlockNumber // - TransactionIndex // - Hash -func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) ethtypes.EthTx { - // We don't care if we error here, conversion is best effort for non-eth transactions - from, _ := lookupEthAddress(msg.From, st) - to, _ := lookupEthAddress(msg.To, st) +func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) (ethtypes.EthTx, error) { + // Lookup the from address. This must succeed. + from, err := lookupEthAddress(msg.From, st) + if err != nil { + return ethtypes.EthTx{}, xerrors.Errorf("failed to lookup sender address %s when converting a native message to an eth txn: %w", msg.From, err) + } + // Lookup the to address. If the recipient doesn't exist, we replace the address with a + // known sentinel address. + to, err := lookupEthAddress(msg.To, st) + if err != nil { + if !errors.Is(err, types.ErrActorNotFound) { + return ethtypes.EthTx{}, xerrors.Errorf("failed to lookup receiver address %s when converting a native message to an eth txn: %w", msg.To, err) + } + to = revertedEthAddress + } toPtr := &to - // Convert the input parameters to "solidity ABI". + // Finally, convert the input parameters to "solidity ABI". // For empty, we use "0" as the codec. Otherwise, we use CBOR for message // parameters. @@ -536,7 +568,7 @@ func ethTxFromNativeMessage(msg *types.Message, st *state.StateTree) ethtypes.Et MaxFeePerGas: ethtypes.EthBigInt(msg.GasFeeCap), MaxPriorityFeePerGas: ethtypes.EthBigInt(msg.GasPremium), AccessList: []ethtypes.EthHash{}, - } + }, nil } func getSignedMessage(ctx context.Context, cs *store.ChainStore, msgCid cid.Cid) (*types.SignedMessage, error) { diff --git a/node/modules/chain.go b/node/modules/chain.go index d08b7dbec..70cb9b585 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -82,12 +82,12 @@ func ChainStore(lc fx.Lifecycle, basebs dtypes.BaseBlockstore, weight store.WeightFunc, us stmgr.UpgradeSchedule, - j journal.Journal) *store.ChainStore { + j journal.Journal) (*store.ChainStore, error) { chain := store.NewChainStore(cbs, sbs, ds, weight, j) if err := chain.Load(helpers.LifecycleCtx(mctx, lc)); err != nil { - log.Warnf("loading chain state from disk: %s", err) + return nil, xerrors.Errorf("loading chain state from disk: %w", err) } var startHook func(context.Context) error @@ -108,7 +108,7 @@ func ChainStore(lc fx.Lifecycle, }, }) - return chain + return chain, nil } func NetworkName(mctx helpers.MetricsCtx, diff --git a/storage/pipeline/stats.go b/storage/pipeline/stats.go index 696cd56a5..a558421ae 100644 --- a/storage/pipeline/stats.go +++ b/storage/pipeline/stats.go @@ -44,6 +44,10 @@ func (ss *SectorStats) updateSector(ctx context.Context, cfg sealiface.Config, i ss.totals[toStatState(oldst, cfg.FinalizeEarly)]-- ss.byState[oldst]-- + if ss.byState[oldst] <= 0 { + delete(ss.byState, oldst) + } + mctx, _ := tag.New(ctx, tag.Upsert(metrics.SectorState, string(oldst))) stats.Record(mctx, metrics.SectorStates.M(ss.byState[oldst])) }