diff --git a/.circleci/config.yml b/.circleci/config.yml index c7d0a5aa2..9a687f036 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -658,6 +658,66 @@ workflows: - build suite: itest-dup_mpool_messages target: "./itests/dup_mpool_messages_test.go" + - test: + name: test-itest-eth_account_abstraction + requires: + - build + suite: itest-eth_account_abstraction + target: "./itests/eth_account_abstraction_test.go" + - test: + name: test-itest-eth_balance + requires: + - build + suite: itest-eth_balance + target: "./itests/eth_balance_test.go" + - test: + name: test-itest-eth_block_hash + requires: + - build + suite: itest-eth_block_hash + target: "./itests/eth_block_hash_test.go" + - test: + name: test-itest-eth_deploy + requires: + - build + suite: itest-eth_deploy + target: "./itests/eth_deploy_test.go" + - test: + name: test-itest-eth_filter + requires: + - build + suite: itest-eth_filter + target: "./itests/eth_filter_test.go" + - test: + name: test-itest-eth_hash_lookup + requires: + - build + suite: itest-eth_hash_lookup + target: "./itests/eth_hash_lookup_test.go" + - test: + name: test-itest-eth_transactions + requires: + - build + suite: itest-eth_transactions + target: "./itests/eth_transactions_test.go" + - test: + name: test-itest-fevm_address + requires: + - build + suite: itest-fevm_address + target: "./itests/fevm_address_test.go" + - test: + name: test-itest-fevm_events + requires: + - build + suite: itest-fevm_events + target: "./itests/fevm_events_test.go" + - test: + name: test-itest-fevm + requires: + - build + suite: itest-fevm + target: "./itests/fevm_test.go" - test: name: test-itest-gas_estimation requires: @@ -700,6 +760,12 @@ workflows: - build suite: itest-migration_nv17 target: "./itests/migration_nv17_test.go" + - test: + name: test-itest-migration_nv18 + requires: + - build + suite: itest-migration_nv18 + target: "./itests/migration_nv18_test.go" - test: name: test-itest-mpool_msg_uuid requires: diff --git a/api/api_full.go b/api/api_full.go index ae7609168..b17fad3b5 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -31,6 +31,7 @@ import ( lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo/imports" ) @@ -182,6 +183,9 @@ type FullNode interface { // ChainBlockstoreInfo returns some basic information about the blockstore ChainBlockstoreInfo(context.Context) (map[string]interface{}, error) //perm:read + // ChainGetEvents returns the events under an event AMT root CID. + ChainGetEvents(context.Context, cid.Cid) ([]types.Event, error) //perm:read + // GasEstimateFeeCap estimates gas fee cap GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) //perm:read @@ -759,6 +763,79 @@ type FullNode interface { NodeStatus(ctx context.Context, inclChainStatus bool) (NodeStatus, error) //perm:read + // MethodGroup: Eth + // These methods are used for Ethereum-compatible JSON-RPC calls + // + // EthAccounts will always return [] since we don't expect Lotus to manage private keys + EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) //perm:read + // EthBlockNumber returns the height of the latest (heaviest) TipSet + EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) //perm:read + // EthGetBlockTransactionCountByNumber returns the number of messages in the TipSet + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) //perm:read + // EthGetBlockTransactionCountByHash returns the number of messages in the TipSet + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) //perm:read + + EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read + EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read + EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) //perm:read + EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) //perm:read + EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) //perm:read + EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) //perm:read + EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*EthTxReceipt, error) //perm:read + 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 + + EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) //perm:read + EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) //perm:read + EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) //perm:read + + EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) //perm:read + + // Returns event logs matching given filter spec. + EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) //perm:read + + // Polling method for a filter, returns event logs which occurred since last poll. + // (requires write perm since timestamp of last filter execution will be written) + EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) //perm:write + + // Returns event logs matching filter with given id. + // (requires write perm since timestamp of last filter execution will be written) + EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) //perm:write + + // Installs a persistent filter based on given filter spec. + EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) //perm:write + + // Installs a persistent filter to notify when a new block arrives. + EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) //perm:write + + // Installs a persistent filter to notify when new messages arrive in the message pool. + EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) //perm:write + + // Uninstalls a filter with given id. + EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) //perm:write + + // Subscribe to different event types using websockets + // eventTypes is one or more of: + // - newHeads: notify when new blocks arrive. + // - pendingTransactions: notify when new messages arrive in the message pool. + // - logs: notify new event logs that match a criteria + // params contains additional parameters used with the log event type + // The client will receive a stream of EthSubscriptionResponse values until EthUnsubscribe is called. + EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) //perm:write + + // Unsubscribe from a websocket subscription + EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) //perm:write + // CreateBackup creates node backup onder the specified file name. The // method requires that the lotus daemon is running with the // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that @@ -1252,3 +1329,21 @@ type PruneOpts struct { MovingGC bool RetainState int64 } + +type EthTxReceipt struct { + TransactionHash ethtypes.EthHash `json:"transactionHash"` + TransactionIndex ethtypes.EthUint64 `json:"transactionIndex"` + BlockHash ethtypes.EthHash `json:"blockHash"` + BlockNumber ethtypes.EthUint64 `json:"blockNumber"` + From ethtypes.EthAddress `json:"from"` + To *ethtypes.EthAddress `json:"to"` + StateRoot ethtypes.EthHash `json:"root"` + Status ethtypes.EthUint64 `json:"status"` + ContractAddress *ethtypes.EthAddress `json:"contractAddress"` + CumulativeGasUsed ethtypes.EthUint64 `json:"cumulativeGasUsed"` + GasUsed ethtypes.EthUint64 `json:"gasUsed"` + EffectiveGasPrice ethtypes.EthBigInt `json:"effectiveGasPrice"` + LogsBloom ethtypes.EthBytes `json:"logsBloom"` + Logs []ethtypes.EthLog `json:"logs"` + Type ethtypes.EthUint64 `json:"type"` +} diff --git a/api/api_gateway.go b/api/api_gateway.go index b95299493..c78710026 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -13,6 +13,7 @@ import ( apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" ) // MODIFYING THE API INTERFACE @@ -69,4 +70,38 @@ type Gateway interface { WalletBalance(context.Context, address.Address) (types.BigInt, error) Version(context.Context) (APIVersion, error) Discover(context.Context) (apitypes.OpenRPCDocument, error) + + EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) + EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) + EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) + EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) + EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*EthTxReceipt, error) + EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) + EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) + 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) + EthChainId(ctx context.Context) (ethtypes.EthUint64, error) + NetVersion(ctx context.Context) (string, error) + 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) + 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) + EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) + EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) + EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) + EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) + EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) + EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) } diff --git a/api/api_storage.go b/api/api_storage.go index 051206787..0c00b9b93 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -22,7 +22,7 @@ import ( "github.com/filecoin-project/go-state-types/builtin/v9/miner" abinetwork "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/chain/actors/builtin" + builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/storage/pipeline/sealiface" "github.com/filecoin-project/lotus/storage/sealer/fsutil" @@ -152,7 +152,7 @@ type StorageMiner interface { WorkerStats(context.Context) (map[uuid.UUID]storiface.WorkerStats, error) //perm:admin WorkerJobs(context.Context) (map[uuid.UUID][]storiface.WorkerJob, error) //perm:admin - //storiface.WorkerReturn + // storiface.WorkerReturn ReturnDataCid(ctx context.Context, callID storiface.CallID, pi abi.PieceInfo, err *storiface.CallError) error //perm:admin retry:true ReturnAddPiece(ctx context.Context, callID storiface.CallID, pi abi.PieceInfo, err *storiface.CallError) error //perm:admin retry:true ReturnSealPreCommit1(ctx context.Context, callID storiface.CallID, p1o storiface.PreCommit1Out, err *storiface.CallError) error //perm:admin retry:true @@ -175,7 +175,7 @@ type StorageMiner interface { // SealingSchedDiag dumps internal sealing scheduler state SealingSchedDiag(ctx context.Context, doSched bool) (interface{}, error) //perm:admin SealingAbort(ctx context.Context, call storiface.CallID) error //perm:admin - //SealingSchedRemove removes a request from sealing pipeline + // SealingSchedRemove removes a request from sealing pipeline SealingRemoveRequest(ctx context.Context, schedId uuid.UUID) error //perm:admin // paths.SectorIndex @@ -322,7 +322,7 @@ type StorageMiner interface { CheckProvable(ctx context.Context, pp abi.RegisteredPoStProof, sectors []storiface.SectorRef) (map[abi.SectorNumber]string, error) //perm:admin - ComputeProof(ctx context.Context, ssi []builtin.ExtendedSectorInfo, rand abi.PoStRandomness, poStEpoch abi.ChainEpoch, nv abinetwork.Version) ([]builtin.PoStProof, error) //perm:read + ComputeProof(ctx context.Context, ssi []builtinactors.ExtendedSectorInfo, rand abi.PoStRandomness, poStEpoch abi.ChainEpoch, nv abinetwork.Version) ([]builtinactors.PoStProof, error) //perm:read // RecoverFault can be used to declare recoveries manually. It sends messages // to the miner actor with details of recovered sectors and returns the CID of messages. It honors the diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 9478c25ff..606ae9d8e 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -41,6 +41,7 @@ import ( "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo/imports" sealing "github.com/filecoin-project/lotus/storage/pipeline" @@ -68,6 +69,7 @@ func init() { } ExampleValues[reflect.TypeOf(c)] = c + ExampleValues[reflect.TypeOf(&c)] = &c c2, err := cid.Decode("bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve") if err != nil { @@ -298,7 +300,8 @@ func init() { "title": "Lotus RPC API", "version": "1.2.1/generated=2020-11-22T08:22:42-06:00", }, - "methods": []interface{}{}}, + "methods": []interface{}{}, + }, ) addExample(api.CheckStatusCode(0)) @@ -335,7 +338,8 @@ func init() { NumConnsInbound: 3, NumConnsOutbound: 4, NumFD: 5, - }}) + }, + }) addExample(api.NetLimit{ Memory: 123, StreamsInbound: 1, @@ -346,6 +350,7 @@ func init() { Conns: 4, FD: 5, }) + addExample(map[string]bitfield.BitField{ "": bitfield.NewFromSet([]uint64{5, 6, 7, 10}), }) @@ -365,11 +370,40 @@ func init() { Headers: nil, }, }) + + ethint := ethtypes.EthUint64(5) + addExample(ethint) + addExample(ðint) + + ethaddr, _ := ethtypes.ParseEthAddress("0x5CbEeCF99d3fDB3f25E309Cc264f240bb0664031") + addExample(ethaddr) + addExample(ðaddr) + + ethhash, _ := ethtypes.EthHashFromCid(c) + addExample(ethhash) + addExample(ðhash) + + ethFeeHistoryReward := [][]ethtypes.EthBigInt{} + addExample(ðFeeHistoryReward) + addExample(&uuid.UUID{}) + + filterid := ethtypes.EthFilterID(ethhash) + addExample(filterid) + addExample(&filterid) + + subid := ethtypes.EthSubscriptionID(ethhash) + addExample(subid) + addExample(&subid) + + pstring := func(s string) *string { return &s } + addExample(ðtypes.EthFilterSpec{ + FromBlock: pstring("2301220"), + Address: []ethtypes.EthAddress{ethaddr}, + }) } func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { - switch pkg { case "api": // latest switch name { @@ -439,7 +473,7 @@ func ExampleValue(method string, t, parent reflect.Type) interface{} { case reflect.Ptr: if t.Elem().Kind() == reflect.Struct { es := exampleStruct(method, t.Elem(), t) - // ExampleValues[t] = es + ExampleValues[t] = es return es } case reflect.Interface: diff --git a/api/eth_aliases.go b/api/eth_aliases.go new file mode 100644 index 000000000..cf69bfff7 --- /dev/null +++ b/api/eth_aliases.go @@ -0,0 +1,44 @@ +package api + +import apitypes "github.com/filecoin-project/lotus/api/types" + +func CreateEthRPCAliases(as apitypes.Aliaser) { + // TODO: maybe use reflect to automatically register all the eth aliases + as.AliasMethod("eth_accounts", "Filecoin.EthAccounts") + as.AliasMethod("eth_blockNumber", "Filecoin.EthBlockNumber") + as.AliasMethod("eth_getBlockTransactionCountByNumber", "Filecoin.EthGetBlockTransactionCountByNumber") + as.AliasMethod("eth_getBlockTransactionCountByHash", "Filecoin.EthGetBlockTransactionCountByHash") + + as.AliasMethod("eth_getBlockByHash", "Filecoin.EthGetBlockByHash") + as.AliasMethod("eth_getBlockByNumber", "Filecoin.EthGetBlockByNumber") + as.AliasMethod("eth_getTransactionByHash", "Filecoin.EthGetTransactionByHash") + as.AliasMethod("eth_getTransactionCount", "Filecoin.EthGetTransactionCount") + as.AliasMethod("eth_getTransactionReceipt", "Filecoin.EthGetTransactionReceipt") + as.AliasMethod("eth_getTransactionByBlockHashAndIndex", "Filecoin.EthGetTransactionByBlockHashAndIndex") + as.AliasMethod("eth_getTransactionByBlockNumberAndIndex", "Filecoin.EthGetTransactionByBlockNumberAndIndex") + + as.AliasMethod("eth_getCode", "Filecoin.EthGetCode") + as.AliasMethod("eth_getStorageAt", "Filecoin.EthGetStorageAt") + as.AliasMethod("eth_getBalance", "Filecoin.EthGetBalance") + as.AliasMethod("eth_chainId", "Filecoin.EthChainId") + as.AliasMethod("eth_feeHistory", "Filecoin.EthFeeHistory") + as.AliasMethod("eth_protocolVersion", "Filecoin.EthProtocolVersion") + as.AliasMethod("eth_maxPriorityFeePerGas", "Filecoin.EthMaxPriorityFeePerGas") + as.AliasMethod("eth_gasPrice", "Filecoin.EthGasPrice") + as.AliasMethod("eth_sendRawTransaction", "Filecoin.EthSendRawTransaction") + as.AliasMethod("eth_estimateGas", "Filecoin.EthEstimateGas") + as.AliasMethod("eth_call", "Filecoin.EthCall") + + as.AliasMethod("eth_getLogs", "Filecoin.EthGetLogs") + as.AliasMethod("eth_getFilterChanges", "Filecoin.EthGetFilterChanges") + as.AliasMethod("eth_getFilterLogs", "Filecoin.EthGetFilterLogs") + as.AliasMethod("eth_newFilter", "Filecoin.EthNewFilter") + as.AliasMethod("eth_newBlockFilter", "Filecoin.EthNewBlockFilter") + as.AliasMethod("eth_newPendingTransactionFilter", "Filecoin.EthNewPendingTransactionFilter") + as.AliasMethod("eth_uninstallFilter", "Filecoin.EthUninstallFilter") + as.AliasMethod("eth_subscribe", "Filecoin.EthSubscribe") + as.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe") + + as.AliasMethod("net_version", "Filecoin.NetVersion") + as.AliasMethod("net_listening", "Filecoin.NetListening") +} diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index b5bf2bfea..b32fc7d8b 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -37,6 +37,7 @@ import ( apitypes "github.com/filecoin-project/lotus/api/types" miner0 "github.com/filecoin-project/lotus/chain/actors/builtin/miner" types "github.com/filecoin-project/lotus/chain/types" + ethtypes "github.com/filecoin-project/lotus/chain/types/ethtypes" alerting "github.com/filecoin-project/lotus/journal/alerting" dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" imports "github.com/filecoin-project/lotus/node/repo/imports" @@ -183,6 +184,21 @@ func (mr *MockFullNodeMockRecorder) ChainGetBlockMessages(arg0, arg1 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetBlockMessages", reflect.TypeOf((*MockFullNode)(nil).ChainGetBlockMessages), arg0, arg1) } +// ChainGetEvents mocks base method. +func (m *MockFullNode) ChainGetEvents(arg0 context.Context, arg1 cid.Cid) ([]types.Event, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetEvents", arg0, arg1) + ret0, _ := ret[0].([]types.Event) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetEvents indicates an expected call of ChainGetEvents. +func (mr *MockFullNodeMockRecorder) ChainGetEvents(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetEvents", reflect.TypeOf((*MockFullNode)(nil).ChainGetEvents), arg0, arg1) +} + // ChainGetGenesis mocks base method. func (m *MockFullNode) ChainGetGenesis(arg0 context.Context) (*types.TipSet, error) { m.ctrl.T.Helper() @@ -921,6 +937,501 @@ func (mr *MockFullNodeMockRecorder) Discover(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discover", reflect.TypeOf((*MockFullNode)(nil).Discover), arg0) } +// EthAccounts mocks base method. +func (m *MockFullNode) EthAccounts(arg0 context.Context) ([]ethtypes.EthAddress, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthAccounts", arg0) + ret0, _ := ret[0].([]ethtypes.EthAddress) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthAccounts indicates an expected call of EthAccounts. +func (mr *MockFullNodeMockRecorder) EthAccounts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthAccounts", reflect.TypeOf((*MockFullNode)(nil).EthAccounts), arg0) +} + +// EthBlockNumber mocks base method. +func (m *MockFullNode) EthBlockNumber(arg0 context.Context) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthBlockNumber", arg0) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthBlockNumber indicates an expected call of EthBlockNumber. +func (mr *MockFullNodeMockRecorder) EthBlockNumber(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthBlockNumber", reflect.TypeOf((*MockFullNode)(nil).EthBlockNumber), arg0) +} + +// EthCall mocks base method. +func (m *MockFullNode) EthCall(arg0 context.Context, arg1 ethtypes.EthCall, arg2 string) (ethtypes.EthBytes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthCall", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBytes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthCall indicates an expected call of EthCall. +func (mr *MockFullNodeMockRecorder) EthCall(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthCall", reflect.TypeOf((*MockFullNode)(nil).EthCall), arg0, arg1, arg2) +} + +// EthChainId mocks base method. +func (m *MockFullNode) EthChainId(arg0 context.Context) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthChainId", arg0) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthChainId indicates an expected call of EthChainId. +func (mr *MockFullNodeMockRecorder) EthChainId(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthChainId", reflect.TypeOf((*MockFullNode)(nil).EthChainId), arg0) +} + +// EthEstimateGas mocks base method. +func (m *MockFullNode) EthEstimateGas(arg0 context.Context, arg1 ethtypes.EthCall) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthEstimateGas", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthEstimateGas indicates an expected call of EthEstimateGas. +func (mr *MockFullNodeMockRecorder) EthEstimateGas(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthEstimateGas", reflect.TypeOf((*MockFullNode)(nil).EthEstimateGas), arg0, arg1) +} + +// EthFeeHistory mocks base method. +func (m *MockFullNode) EthFeeHistory(arg0 context.Context, arg1 ethtypes.EthUint64, arg2 string, arg3 []float64) (ethtypes.EthFeeHistory, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthFeeHistory", arg0, arg1, arg2, arg3) + 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 { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthFeeHistory", reflect.TypeOf((*MockFullNode)(nil).EthFeeHistory), arg0, arg1, arg2, arg3) +} + +// EthGasPrice mocks base method. +func (m *MockFullNode) EthGasPrice(arg0 context.Context) (ethtypes.EthBigInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGasPrice", arg0) + ret0, _ := ret[0].(ethtypes.EthBigInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGasPrice indicates an expected call of EthGasPrice. +func (mr *MockFullNodeMockRecorder) EthGasPrice(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGasPrice", reflect.TypeOf((*MockFullNode)(nil).EthGasPrice), arg0) +} + +// EthGetBalance mocks base method. +func (m *MockFullNode) EthGetBalance(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 string) (ethtypes.EthBigInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBigInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBalance indicates an expected call of EthGetBalance. +func (mr *MockFullNodeMockRecorder) EthGetBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBalance", reflect.TypeOf((*MockFullNode)(nil).EthGetBalance), arg0, arg1, arg2) +} + +// EthGetBlockByHash mocks base method. +func (m *MockFullNode) EthGetBlockByHash(arg0 context.Context, arg1 ethtypes.EthHash, arg2 bool) (ethtypes.EthBlock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockByHash", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockByHash indicates an expected call of EthGetBlockByHash. +func (mr *MockFullNodeMockRecorder) EthGetBlockByHash(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockByHash), arg0, arg1, arg2) +} + +// EthGetBlockByNumber mocks base method. +func (m *MockFullNode) EthGetBlockByNumber(arg0 context.Context, arg1 string, arg2 bool) (ethtypes.EthBlock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockByNumber", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockByNumber indicates an expected call of EthGetBlockByNumber. +func (mr *MockFullNodeMockRecorder) EthGetBlockByNumber(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockByNumber", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockByNumber), arg0, arg1, arg2) +} + +// EthGetBlockTransactionCountByHash mocks base method. +func (m *MockFullNode) EthGetBlockTransactionCountByHash(arg0 context.Context, arg1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockTransactionCountByHash", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockTransactionCountByHash indicates an expected call of EthGetBlockTransactionCountByHash. +func (mr *MockFullNodeMockRecorder) EthGetBlockTransactionCountByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockTransactionCountByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockTransactionCountByHash), arg0, arg1) +} + +// EthGetBlockTransactionCountByNumber mocks base method. +func (m *MockFullNode) EthGetBlockTransactionCountByNumber(arg0 context.Context, arg1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockTransactionCountByNumber", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockTransactionCountByNumber indicates an expected call of EthGetBlockTransactionCountByNumber. +func (mr *MockFullNodeMockRecorder) EthGetBlockTransactionCountByNumber(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockTransactionCountByNumber", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockTransactionCountByNumber), arg0, arg1) +} + +// EthGetCode mocks base method. +func (m *MockFullNode) EthGetCode(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 string) (ethtypes.EthBytes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetCode", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBytes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetCode indicates an expected call of EthGetCode. +func (mr *MockFullNodeMockRecorder) EthGetCode(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetCode", reflect.TypeOf((*MockFullNode)(nil).EthGetCode), arg0, arg1, arg2) +} + +// EthGetFilterChanges mocks base method. +func (m *MockFullNode) EthGetFilterChanges(arg0 context.Context, arg1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetFilterChanges", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthFilterResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetFilterChanges indicates an expected call of EthGetFilterChanges. +func (mr *MockFullNodeMockRecorder) EthGetFilterChanges(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetFilterChanges", reflect.TypeOf((*MockFullNode)(nil).EthGetFilterChanges), arg0, arg1) +} + +// EthGetFilterLogs mocks base method. +func (m *MockFullNode) EthGetFilterLogs(arg0 context.Context, arg1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetFilterLogs", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthFilterResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetFilterLogs indicates an expected call of EthGetFilterLogs. +func (mr *MockFullNodeMockRecorder) EthGetFilterLogs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetFilterLogs", reflect.TypeOf((*MockFullNode)(nil).EthGetFilterLogs), arg0, arg1) +} + +// EthGetLogs mocks base method. +func (m *MockFullNode) EthGetLogs(arg0 context.Context, arg1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetLogs", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthFilterResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetLogs indicates an expected call of EthGetLogs. +func (mr *MockFullNodeMockRecorder) EthGetLogs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetLogs", reflect.TypeOf((*MockFullNode)(nil).EthGetLogs), arg0, arg1) +} + +// EthGetMessageCidByTransactionHash mocks base method. +func (m *MockFullNode) EthGetMessageCidByTransactionHash(arg0 context.Context, arg1 *ethtypes.EthHash) (*cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetMessageCidByTransactionHash", arg0, arg1) + ret0, _ := ret[0].(*cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetMessageCidByTransactionHash indicates an expected call of EthGetMessageCidByTransactionHash. +func (mr *MockFullNodeMockRecorder) EthGetMessageCidByTransactionHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetMessageCidByTransactionHash", reflect.TypeOf((*MockFullNode)(nil).EthGetMessageCidByTransactionHash), arg0, arg1) +} + +// EthGetStorageAt mocks base method. +func (m *MockFullNode) EthGetStorageAt(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 ethtypes.EthBytes, arg3 string) (ethtypes.EthBytes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetStorageAt", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(ethtypes.EthBytes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetStorageAt indicates an expected call of EthGetStorageAt. +func (mr *MockFullNodeMockRecorder) EthGetStorageAt(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetStorageAt", reflect.TypeOf((*MockFullNode)(nil).EthGetStorageAt), arg0, arg1, arg2, arg3) +} + +// EthGetTransactionByBlockHashAndIndex mocks base method. +func (m *MockFullNode) EthGetTransactionByBlockHashAndIndex(arg0 context.Context, arg1 ethtypes.EthHash, arg2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByBlockHashAndIndex", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByBlockHashAndIndex indicates an expected call of EthGetTransactionByBlockHashAndIndex. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByBlockHashAndIndex(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByBlockHashAndIndex", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByBlockHashAndIndex), arg0, arg1, arg2) +} + +// EthGetTransactionByBlockNumberAndIndex mocks base method. +func (m *MockFullNode) EthGetTransactionByBlockNumberAndIndex(arg0 context.Context, arg1, arg2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByBlockNumberAndIndex", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByBlockNumberAndIndex indicates an expected call of EthGetTransactionByBlockNumberAndIndex. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByBlockNumberAndIndex(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByBlockNumberAndIndex", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByBlockNumberAndIndex), arg0, arg1, arg2) +} + +// EthGetTransactionByHash mocks base method. +func (m *MockFullNode) EthGetTransactionByHash(arg0 context.Context, arg1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByHash", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByHash indicates an expected call of EthGetTransactionByHash. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByHash), arg0, arg1) +} + +// EthGetTransactionCount mocks base method. +func (m *MockFullNode) EthGetTransactionCount(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 string) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionCount", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionCount indicates an expected call of EthGetTransactionCount. +func (mr *MockFullNodeMockRecorder) EthGetTransactionCount(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionCount", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionCount), arg0, arg1, arg2) +} + +// EthGetTransactionHashByCid mocks base method. +func (m *MockFullNode) EthGetTransactionHashByCid(arg0 context.Context, arg1 cid.Cid) (*ethtypes.EthHash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionHashByCid", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthHash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionHashByCid indicates an expected call of EthGetTransactionHashByCid. +func (mr *MockFullNodeMockRecorder) EthGetTransactionHashByCid(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionHashByCid", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionHashByCid), arg0, arg1) +} + +// EthGetTransactionReceipt mocks base method. +func (m *MockFullNode) EthGetTransactionReceipt(arg0 context.Context, arg1 ethtypes.EthHash) (*api.EthTxReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionReceipt", arg0, arg1) + ret0, _ := ret[0].(*api.EthTxReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionReceipt indicates an expected call of EthGetTransactionReceipt. +func (mr *MockFullNodeMockRecorder) EthGetTransactionReceipt(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionReceipt", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionReceipt), arg0, arg1) +} + +// EthMaxPriorityFeePerGas mocks base method. +func (m *MockFullNode) EthMaxPriorityFeePerGas(arg0 context.Context) (ethtypes.EthBigInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthMaxPriorityFeePerGas", arg0) + ret0, _ := ret[0].(ethtypes.EthBigInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthMaxPriorityFeePerGas indicates an expected call of EthMaxPriorityFeePerGas. +func (mr *MockFullNodeMockRecorder) EthMaxPriorityFeePerGas(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthMaxPriorityFeePerGas", reflect.TypeOf((*MockFullNode)(nil).EthMaxPriorityFeePerGas), arg0) +} + +// EthNewBlockFilter mocks base method. +func (m *MockFullNode) EthNewBlockFilter(arg0 context.Context) (ethtypes.EthFilterID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthNewBlockFilter", arg0) + ret0, _ := ret[0].(ethtypes.EthFilterID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthNewBlockFilter indicates an expected call of EthNewBlockFilter. +func (mr *MockFullNodeMockRecorder) EthNewBlockFilter(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthNewBlockFilter", reflect.TypeOf((*MockFullNode)(nil).EthNewBlockFilter), arg0) +} + +// EthNewFilter mocks base method. +func (m *MockFullNode) EthNewFilter(arg0 context.Context, arg1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthNewFilter", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthFilterID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthNewFilter indicates an expected call of EthNewFilter. +func (mr *MockFullNodeMockRecorder) EthNewFilter(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthNewFilter", reflect.TypeOf((*MockFullNode)(nil).EthNewFilter), arg0, arg1) +} + +// EthNewPendingTransactionFilter mocks base method. +func (m *MockFullNode) EthNewPendingTransactionFilter(arg0 context.Context) (ethtypes.EthFilterID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthNewPendingTransactionFilter", arg0) + ret0, _ := ret[0].(ethtypes.EthFilterID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthNewPendingTransactionFilter indicates an expected call of EthNewPendingTransactionFilter. +func (mr *MockFullNodeMockRecorder) EthNewPendingTransactionFilter(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthNewPendingTransactionFilter", reflect.TypeOf((*MockFullNode)(nil).EthNewPendingTransactionFilter), arg0) +} + +// EthProtocolVersion mocks base method. +func (m *MockFullNode) EthProtocolVersion(arg0 context.Context) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthProtocolVersion", arg0) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthProtocolVersion indicates an expected call of EthProtocolVersion. +func (mr *MockFullNodeMockRecorder) EthProtocolVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthProtocolVersion", reflect.TypeOf((*MockFullNode)(nil).EthProtocolVersion), arg0) +} + +// EthSendRawTransaction mocks base method. +func (m *MockFullNode) EthSendRawTransaction(arg0 context.Context, arg1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthSendRawTransaction", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthHash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthSendRawTransaction indicates an expected call of EthSendRawTransaction. +func (mr *MockFullNodeMockRecorder) EthSendRawTransaction(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSendRawTransaction", reflect.TypeOf((*MockFullNode)(nil).EthSendRawTransaction), arg0, arg1) +} + +// EthSubscribe mocks base method. +func (m *MockFullNode) EthSubscribe(arg0 context.Context, arg1 string, arg2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthSubscribe", arg0, arg1, arg2) + ret0, _ := ret[0].(<-chan ethtypes.EthSubscriptionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthSubscribe indicates an expected call of EthSubscribe. +func (mr *MockFullNodeMockRecorder) EthSubscribe(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSubscribe", reflect.TypeOf((*MockFullNode)(nil).EthSubscribe), arg0, arg1, arg2) +} + +// EthUninstallFilter mocks base method. +func (m *MockFullNode) EthUninstallFilter(arg0 context.Context, arg1 ethtypes.EthFilterID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthUninstallFilter", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthUninstallFilter indicates an expected call of EthUninstallFilter. +func (mr *MockFullNodeMockRecorder) EthUninstallFilter(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthUninstallFilter", reflect.TypeOf((*MockFullNode)(nil).EthUninstallFilter), arg0, arg1) +} + +// EthUnsubscribe mocks base method. +func (m *MockFullNode) EthUnsubscribe(arg0 context.Context, arg1 ethtypes.EthSubscriptionID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthUnsubscribe", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthUnsubscribe indicates an expected call of EthUnsubscribe. +func (mr *MockFullNodeMockRecorder) EthUnsubscribe(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthUnsubscribe", reflect.TypeOf((*MockFullNode)(nil).EthUnsubscribe), arg0, arg1) +} + // GasEstimateFeeCap mocks base method. func (m *MockFullNode) GasEstimateFeeCap(arg0 context.Context, arg1 *types.Message, arg2 int64, arg3 types.TipSetKey) (big.Int, error) { m.ctrl.T.Helper() @@ -1843,6 +2354,21 @@ func (mr *MockFullNodeMockRecorder) NetLimit(arg0, arg1 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetLimit", reflect.TypeOf((*MockFullNode)(nil).NetLimit), arg0, arg1) } +// NetListening mocks base method. +func (m *MockFullNode) NetListening(arg0 context.Context) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetListening", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetListening indicates an expected call of NetListening. +func (mr *MockFullNodeMockRecorder) NetListening(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetListening", reflect.TypeOf((*MockFullNode)(nil).NetListening), arg0) +} + // NetPeerInfo mocks base method. func (m *MockFullNode) NetPeerInfo(arg0 context.Context, arg1 peer.ID) (*api.ExtendedPeerInfo, error) { m.ctrl.T.Helper() @@ -1975,6 +2501,21 @@ func (mr *MockFullNodeMockRecorder) NetStat(arg0, arg1 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetStat", reflect.TypeOf((*MockFullNode)(nil).NetStat), arg0, arg1) } +// NetVersion mocks base method. +func (m *MockFullNode) NetVersion(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetVersion", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetVersion indicates an expected call of NetVersion. +func (mr *MockFullNodeMockRecorder) NetVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetVersion", reflect.TypeOf((*MockFullNode)(nil).NetVersion), arg0) +} + // NodeStatus mocks base method. func (m *MockFullNode) NodeStatus(arg0 context.Context, arg1 bool) (api.NodeStatus, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 14d5c999d..aaa1d87c7 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -33,9 +33,10 @@ import ( "github.com/filecoin-project/go-state-types/proof" apitypes "github.com/filecoin-project/lotus/api/types" - "github.com/filecoin-project/lotus/chain/actors/builtin" + builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/journal/alerting" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo/imports" @@ -122,6 +123,8 @@ type FullNodeStruct struct { ChainGetBlockMessages func(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) `perm:"read"` + ChainGetEvents func(p0 context.Context, p1 cid.Cid) ([]types.Event, error) `perm:"read"` + ChainGetGenesis func(p0 context.Context) (*types.TipSet, error) `perm:"read"` ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, error) `perm:"read"` @@ -218,6 +221,72 @@ type FullNodeStruct struct { CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` + EthAccounts func(p0 context.Context) ([]ethtypes.EthAddress, error) `perm:"read"` + + EthBlockNumber func(p0 context.Context) (ethtypes.EthUint64, error) `perm:"read"` + + EthCall func(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) `perm:"read"` + + EthChainId func(p0 context.Context) (ethtypes.EthUint64, error) `perm:"read"` + + 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"` + + EthGasPrice func(p0 context.Context) (ethtypes.EthBigInt, error) `perm:"read"` + + EthGetBalance func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) `perm:"read"` + + EthGetBlockByHash func(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) `perm:"read"` + + EthGetBlockByNumber func(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) `perm:"read"` + + EthGetBlockTransactionCountByHash func(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) `perm:"read"` + + EthGetBlockTransactionCountByNumber func(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) `perm:"read"` + + EthGetCode func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) `perm:"read"` + + EthGetFilterChanges func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `perm:"write"` + + EthGetFilterLogs func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `perm:"write"` + + EthGetLogs func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) `perm:"read"` + + EthGetMessageCidByTransactionHash func(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) `perm:"read"` + + EthGetStorageAt func(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) `perm:"read"` + + EthGetTransactionByBlockHashAndIndex func(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) `perm:"read"` + + EthGetTransactionByBlockNumberAndIndex func(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) `perm:"read"` + + EthGetTransactionByHash func(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) `perm:"read"` + + EthGetTransactionCount func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) `perm:"read"` + + EthGetTransactionHashByCid func(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) `perm:"read"` + + EthGetTransactionReceipt func(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) `perm:"read"` + + EthMaxPriorityFeePerGas func(p0 context.Context) (ethtypes.EthBigInt, error) `perm:"read"` + + EthNewBlockFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `perm:"write"` + + EthNewFilter func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) `perm:"write"` + + EthNewPendingTransactionFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `perm:"write"` + + EthProtocolVersion func(p0 context.Context) (ethtypes.EthUint64, error) `perm:"read"` + + EthSendRawTransaction func(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) `perm:"read"` + + EthSubscribe func(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) `perm:"write"` + + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"write"` + + EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `perm:"write"` + GasEstimateFeeCap func(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` GasEstimateGasLimit func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) `perm:"read"` @@ -306,6 +375,10 @@ type FullNodeStruct struct { MsigSwapPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) `perm:"sign"` + NetListening func(p0 context.Context) (bool, error) `perm:"read"` + + NetVersion func(p0 context.Context) (string, error) `perm:"read"` + NodeStatus func(p0 context.Context, p1 bool) (NodeStatus, error) `perm:"read"` PaychAllocateLane func(p0 context.Context, p1 address.Address) (uint64, error) `perm:"sign"` @@ -550,6 +623,68 @@ type GatewayStruct struct { Discover func(p0 context.Context) (apitypes.OpenRPCDocument, error) `` + EthAccounts func(p0 context.Context) ([]ethtypes.EthAddress, error) `` + + EthBlockNumber func(p0 context.Context) (ethtypes.EthUint64, error) `` + + EthCall func(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) `` + + EthChainId func(p0 context.Context) (ethtypes.EthUint64, error) `` + + 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) `` + + EthGasPrice func(p0 context.Context) (ethtypes.EthBigInt, error) `` + + EthGetBalance func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) `` + + EthGetBlockByHash func(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) `` + + EthGetBlockByNumber func(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) `` + + EthGetBlockTransactionCountByHash func(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) `` + + EthGetBlockTransactionCountByNumber func(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) `` + + EthGetCode func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) `` + + EthGetFilterChanges func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `` + + EthGetFilterLogs func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `` + + EthGetLogs func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) `` + + EthGetStorageAt func(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) `` + + EthGetTransactionByBlockHashAndIndex func(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) `` + + EthGetTransactionByBlockNumberAndIndex func(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) `` + + EthGetTransactionByHash func(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) `` + + EthGetTransactionCount func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) `` + + EthGetTransactionReceipt func(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) `` + + EthMaxPriorityFeePerGas func(p0 context.Context) (ethtypes.EthBigInt, error) `` + + EthNewBlockFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `` + + EthNewFilter func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) `` + + EthNewPendingTransactionFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `` + + EthProtocolVersion func(p0 context.Context) (ethtypes.EthUint64, error) `` + + EthSendRawTransaction func(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) `` + + EthSubscribe func(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) `` + + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `` + + EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `` + GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) `` MpoolPush func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) `` @@ -562,6 +697,10 @@ type GatewayStruct struct { MsigGetVestingSchedule func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MsigVesting, error) `` + NetListening func(p0 context.Context) (bool, error) `` + + NetVersion func(p0 context.Context) (string, error) `` + StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) `` @@ -687,7 +826,7 @@ type StorageMinerStruct struct { ComputeDataCid func(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (abi.PieceInfo, error) `perm:"admin"` - ComputeProof func(p0 context.Context, p1 []builtin.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtin.PoStProof, error) `perm:"read"` + ComputeProof func(p0 context.Context, p1 []builtinactors.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtinactors.PoStProof, error) `perm:"read"` ComputeWindowPoSt func(p0 context.Context, p1 uint64, p2 types.TipSetKey) ([]miner.SubmitWindowedPoStParams, error) `perm:"admin"` @@ -1267,6 +1406,17 @@ func (s *FullNodeStub) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*B return nil, ErrNotSupported } +func (s *FullNodeStruct) ChainGetEvents(p0 context.Context, p1 cid.Cid) ([]types.Event, error) { + if s.Internal.ChainGetEvents == nil { + return *new([]types.Event), ErrNotSupported + } + return s.Internal.ChainGetEvents(p0, p1) +} + +func (s *FullNodeStub) ChainGetEvents(p0 context.Context, p1 cid.Cid) ([]types.Event, error) { + return *new([]types.Event), ErrNotSupported +} + func (s *FullNodeStruct) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) { if s.Internal.ChainGetGenesis == nil { return nil, ErrNotSupported @@ -1795,6 +1945,369 @@ func (s *FullNodeStub) CreateBackup(p0 context.Context, p1 string) error { return ErrNotSupported } +func (s *FullNodeStruct) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + if s.Internal.EthAccounts == nil { + return *new([]ethtypes.EthAddress), ErrNotSupported + } + return s.Internal.EthAccounts(p0) +} + +func (s *FullNodeStub) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + return *new([]ethtypes.EthAddress), ErrNotSupported +} + +func (s *FullNodeStruct) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthBlockNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthBlockNumber(p0) +} + +func (s *FullNodeStub) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthCall == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthCall(p0, p1, p2) +} + +func (s *FullNodeStub) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *FullNodeStruct) EthChainId(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthChainId == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthChainId(p0) +} + +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) { + 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) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { + if s.Internal.EthFeeHistory == nil { + return *new(ethtypes.EthFeeHistory), ErrNotSupported + } + return s.Internal.EthFeeHistory(p0, p1, p2, p3) +} + +func (s *FullNodeStub) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { + return *new(ethtypes.EthFeeHistory), ErrNotSupported +} + +func (s *FullNodeStruct) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthGasPrice == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGasPrice(p0) +} + +func (s *FullNodeStub) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + if s.Internal.EthGetBalance == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGetBalance(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByHash == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByHash(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByNumber == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByNumber(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByHash == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByHash(p0, p1) +} + +func (s *FullNodeStub) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByNumber(p0, p1) +} + +func (s *FullNodeStub) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetCode == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetCode(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterChanges == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterChanges(p0, p1) +} + +func (s *FullNodeStub) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterLogs(p0, p1) +} + +func (s *FullNodeStub) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetLogs(p0, p1) +} + +func (s *FullNodeStub) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + if s.Internal.EthGetMessageCidByTransactionHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetMessageCidByTransactionHash(p0, p1) +} + +func (s *FullNodeStub) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetStorageAt == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetStorageAt(p0, p1, p2, p3) +} + +func (s *FullNodeStub) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByBlockHashAndIndex == nil { + return *new(ethtypes.EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockHashAndIndex(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + return *new(ethtypes.EthTx), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByBlockNumberAndIndex == nil { + return *new(ethtypes.EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockNumberAndIndex(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + return *new(ethtypes.EthTx), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionByHash(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + if s.Internal.EthGetTransactionCount == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetTransactionCount(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + if s.Internal.EthGetTransactionHashByCid == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionHashByCid(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + if s.Internal.EthGetTransactionReceipt == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionReceipt(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthMaxPriorityFeePerGas == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthMaxPriorityFeePerGas(p0) +} + +func (s *FullNodeStub) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewBlockFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewBlockFilter(p0) +} + +func (s *FullNodeStub) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *FullNodeStruct) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewFilter(p0, p1) +} + +func (s *FullNodeStub) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *FullNodeStruct) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewPendingTransactionFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewPendingTransactionFilter(p0) +} + +func (s *FullNodeStub) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *FullNodeStruct) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthProtocolVersion == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthProtocolVersion(p0) +} + +func (s *FullNodeStub) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + if s.Internal.EthSendRawTransaction == nil { + return *new(ethtypes.EthHash), ErrNotSupported + } + return s.Internal.EthSendRawTransaction(p0, p1) +} + +func (s *FullNodeStub) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + return *new(ethtypes.EthHash), ErrNotSupported +} + +func (s *FullNodeStruct) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { + if s.Internal.EthSubscribe == nil { + return nil, ErrNotSupported + } + return s.Internal.EthSubscribe(p0, p1, p2) +} + +func (s *FullNodeStub) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + if s.Internal.EthUninstallFilter == nil { + return false, ErrNotSupported + } + return s.Internal.EthUninstallFilter(p0, p1) +} + +func (s *FullNodeStub) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + if s.Internal.EthUnsubscribe == nil { + return false, ErrNotSupported + } + return s.Internal.EthUnsubscribe(p0, p1) +} + +func (s *FullNodeStub) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + return false, ErrNotSupported +} + func (s *FullNodeStruct) GasEstimateFeeCap(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) { if s.Internal.GasEstimateFeeCap == nil { return *new(types.BigInt), ErrNotSupported @@ -2279,6 +2792,28 @@ func (s *FullNodeStub) MsigSwapPropose(p0 context.Context, p1 address.Address, p return nil, ErrNotSupported } +func (s *FullNodeStruct) NetListening(p0 context.Context) (bool, error) { + if s.Internal.NetListening == nil { + return false, ErrNotSupported + } + return s.Internal.NetListening(p0) +} + +func (s *FullNodeStub) NetListening(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) NetVersion(p0 context.Context) (string, error) { + if s.Internal.NetVersion == nil { + return "", ErrNotSupported + } + return s.Internal.NetVersion(p0) +} + +func (s *FullNodeStub) NetVersion(p0 context.Context) (string, error) { + return "", ErrNotSupported +} + func (s *FullNodeStruct) NodeStatus(p0 context.Context, p1 bool) (NodeStatus, error) { if s.Internal.NodeStatus == nil { return *new(NodeStatus), ErrNotSupported @@ -3566,6 +4101,347 @@ func (s *GatewayStub) Discover(p0 context.Context) (apitypes.OpenRPCDocument, er return *new(apitypes.OpenRPCDocument), ErrNotSupported } +func (s *GatewayStruct) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + if s.Internal.EthAccounts == nil { + return *new([]ethtypes.EthAddress), ErrNotSupported + } + return s.Internal.EthAccounts(p0) +} + +func (s *GatewayStub) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + return *new([]ethtypes.EthAddress), ErrNotSupported +} + +func (s *GatewayStruct) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthBlockNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthBlockNumber(p0) +} + +func (s *GatewayStub) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthCall == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthCall(p0, p1, p2) +} + +func (s *GatewayStub) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *GatewayStruct) EthChainId(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthChainId == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthChainId(p0) +} + +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) { + 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) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { + if s.Internal.EthFeeHistory == nil { + return *new(ethtypes.EthFeeHistory), ErrNotSupported + } + return s.Internal.EthFeeHistory(p0, p1, p2, p3) +} + +func (s *GatewayStub) EthFeeHistory(p0 context.Context, p1 ethtypes.EthUint64, p2 string, p3 []float64) (ethtypes.EthFeeHistory, error) { + return *new(ethtypes.EthFeeHistory), ErrNotSupported +} + +func (s *GatewayStruct) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthGasPrice == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGasPrice(p0) +} + +func (s *GatewayStub) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + if s.Internal.EthGetBalance == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGetBalance(p0, p1, p2) +} + +func (s *GatewayStub) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByHash == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByHash(p0, p1, p2) +} + +func (s *GatewayStub) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByNumber == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByNumber(p0, p1, p2) +} + +func (s *GatewayStub) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByHash == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByHash(p0, p1) +} + +func (s *GatewayStub) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByNumber(p0, p1) +} + +func (s *GatewayStub) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetCode == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetCode(p0, p1, p2) +} + +func (s *GatewayStub) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *GatewayStruct) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterChanges == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterChanges(p0, p1) +} + +func (s *GatewayStub) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterLogs(p0, p1) +} + +func (s *GatewayStub) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetLogs(p0, p1) +} + +func (s *GatewayStub) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetStorageAt == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetStorageAt(p0, p1, p2, p3) +} + +func (s *GatewayStub) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByBlockHashAndIndex == nil { + return *new(ethtypes.EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockHashAndIndex(p0, p1, p2) +} + +func (s *GatewayStub) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + return *new(ethtypes.EthTx), ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByBlockNumberAndIndex == nil { + return *new(ethtypes.EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockNumberAndIndex(p0, p1, p2) +} + +func (s *GatewayStub) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + return *new(ethtypes.EthTx), ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionByHash(p0, p1) +} + +func (s *GatewayStub) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + if s.Internal.EthGetTransactionCount == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetTransactionCount(p0, p1, p2) +} + +func (s *GatewayStub) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + if s.Internal.EthGetTransactionReceipt == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionReceipt(p0, p1) +} + +func (s *GatewayStub) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthMaxPriorityFeePerGas == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthMaxPriorityFeePerGas(p0) +} + +func (s *GatewayStub) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *GatewayStruct) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewBlockFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewBlockFilter(p0) +} + +func (s *GatewayStub) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *GatewayStruct) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewFilter(p0, p1) +} + +func (s *GatewayStub) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *GatewayStruct) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewPendingTransactionFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewPendingTransactionFilter(p0) +} + +func (s *GatewayStub) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *GatewayStruct) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthProtocolVersion == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthProtocolVersion(p0) +} + +func (s *GatewayStub) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + if s.Internal.EthSendRawTransaction == nil { + return *new(ethtypes.EthHash), ErrNotSupported + } + return s.Internal.EthSendRawTransaction(p0, p1) +} + +func (s *GatewayStub) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + return *new(ethtypes.EthHash), ErrNotSupported +} + +func (s *GatewayStruct) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { + if s.Internal.EthSubscribe == nil { + return nil, ErrNotSupported + } + return s.Internal.EthSubscribe(p0, p1, p2) +} + +func (s *GatewayStub) EthSubscribe(p0 context.Context, p1 string, p2 *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + if s.Internal.EthUninstallFilter == nil { + return false, ErrNotSupported + } + return s.Internal.EthUninstallFilter(p0, p1) +} + +func (s *GatewayStub) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + if s.Internal.EthUnsubscribe == nil { + return false, ErrNotSupported + } + return s.Internal.EthUnsubscribe(p0, p1) +} + +func (s *GatewayStub) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + return false, ErrNotSupported +} + func (s *GatewayStruct) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { if s.Internal.GasEstimateMessageGas == nil { return nil, ErrNotSupported @@ -3632,6 +4508,28 @@ func (s *GatewayStub) MsigGetVestingSchedule(p0 context.Context, p1 address.Addr return *new(MsigVesting), ErrNotSupported } +func (s *GatewayStruct) NetListening(p0 context.Context) (bool, error) { + if s.Internal.NetListening == nil { + return false, ErrNotSupported + } + return s.Internal.NetListening(p0) +} + +func (s *GatewayStub) NetListening(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) NetVersion(p0 context.Context) (string, error) { + if s.Internal.NetVersion == nil { + return "", ErrNotSupported + } + return s.Internal.NetVersion(p0) +} + +func (s *GatewayStub) NetVersion(p0 context.Context) (string, error) { + return "", ErrNotSupported +} + func (s *GatewayStruct) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { if s.Internal.StateAccountKey == nil { return *new(address.Address), ErrNotSupported @@ -4182,15 +5080,15 @@ func (s *StorageMinerStub) ComputeDataCid(p0 context.Context, p1 abi.UnpaddedPie return *new(abi.PieceInfo), ErrNotSupported } -func (s *StorageMinerStruct) ComputeProof(p0 context.Context, p1 []builtin.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtin.PoStProof, error) { +func (s *StorageMinerStruct) ComputeProof(p0 context.Context, p1 []builtinactors.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtinactors.PoStProof, error) { if s.Internal.ComputeProof == nil { - return *new([]builtin.PoStProof), ErrNotSupported + return *new([]builtinactors.PoStProof), ErrNotSupported } return s.Internal.ComputeProof(p0, p1, p2, p3, p4) } -func (s *StorageMinerStub) ComputeProof(p0 context.Context, p1 []builtin.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtin.PoStProof, error) { - return *new([]builtin.PoStProof), ErrNotSupported +func (s *StorageMinerStub) ComputeProof(p0 context.Context, p1 []builtinactors.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtinactors.PoStProof, error) { + return *new([]builtinactors.PoStProof), ErrNotSupported } func (s *StorageMinerStruct) ComputeWindowPoSt(p0 context.Context, p1 uint64, p2 types.TipSetKey) ([]miner.SubmitWindowedPoStParams, error) { diff --git a/api/types.go b/api/types.go index 5cbe0edef..e67903436 100644 --- a/api/types.go +++ b/api/types.go @@ -338,6 +338,7 @@ type ForkUpgradeParams struct { UpgradeOhSnapHeight abi.ChainEpoch UpgradeSkyrHeight abi.ChainEpoch UpgradeSharkHeight abi.ChainEpoch + UpgradeHyggeHeight abi.ChainEpoch } type NonceMapType map[address.Address]uint64 diff --git a/api/types/rpc.go b/api/types/rpc.go new file mode 100644 index 000000000..4bde6dfbc --- /dev/null +++ b/api/types/rpc.go @@ -0,0 +1,5 @@ +package apitypes + +type Aliaser interface { + AliasMethod(alias, original string) +} diff --git a/blockstore/splitstore/splitstore_compact.go b/blockstore/splitstore/splitstore_compact.go index 1c4c903ff..6ffb35726 100644 --- a/blockstore/splitstore/splitstore_compact.go +++ b/blockstore/splitstore/splitstore_compact.go @@ -905,6 +905,10 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp walkCnt := new(int64) scanCnt := new(int64) + tsRef := func(blkCids []cid.Cid) (cid.Cid, error) { + return types.NewTipSetKey(blkCids...).Cid() + } + stopWalk := func(_ cid.Cid) error { return errStopWalk } walkBlock := func(c cid.Cid) error { @@ -926,11 +930,19 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp err = s.view(c, func(data []byte) error { return hdr.UnmarshalCBOR(bytes.NewBuffer(data)) }) - if err != nil { return xerrors.Errorf("error unmarshaling block header (cid: %s): %w", c, err) } + // tipset CID references are retained + pRef, err := tsRef(hdr.Parents) + if err != nil { + return xerrors.Errorf("error computing cid reference to parent tipset") + } + if err := s.walkObjectIncomplete(pRef, visitor, fHot, stopWalk); err != nil { + return xerrors.Errorf("error walking parent tipset cid reference") + } + // message are retained if within the inclMsgs boundary if hdr.Height >= inclMsgs && hdr.Height > 0 { if inclMsgs < inclState { @@ -981,6 +993,15 @@ func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEp return nil } + // retain ref to chain head + hRef, err := tsRef(ts.Cids()) + if err != nil { + return xerrors.Errorf("error computing cid reference to parent tipset") + } + if err := s.walkObjectIncomplete(hRef, visitor, fHot, stopWalk); err != nil { + return xerrors.Errorf("error walking parent tipset cid reference") + } + for len(toWalk) > 0 { // walking can take a while, so check this with every opportunity if err := s.checkClosing(); err != nil { diff --git a/build/actors/v10.tar.zst b/build/actors/v10.tar.zst index f5644f474..b0c9e5ce8 100644 Binary files a/build/actors/v10.tar.zst and b/build/actors/v10.tar.zst differ diff --git a/build/actors/v8.tar.zst b/build/actors/v8.tar.zst index c4eb857b9..88e0ac800 100644 Binary files a/build/actors/v8.tar.zst and b/build/actors/v8.tar.zst differ diff --git a/build/actors/v9.tar.zst b/build/actors/v9.tar.zst index 95b887312..55d0eba0b 100644 Binary files a/build/actors/v9.tar.zst and b/build/actors/v9.tar.zst differ diff --git a/build/builtin_actors_gen.go b/build/builtin_actors_gen.go index fa10798ef..38d4c49ed 100644 --- a/build/builtin_actors_gen.go +++ b/build/builtin_actors_gen.go @@ -9,58 +9,68 @@ import ( var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMetadata{{ Network: "butterflynet", Version: 8, - ManifestCid: MustParseCid("bafy2bzacedvaarfyh6q3bk4dyzux46ednlace2ckxp5nbyn6mb3da2apqn6sk"), + ManifestCid: MustParseCid("bafy2bzaceba5qgs4z3imhlxwds5vamahngatvuuglbv5yl3ftfiosj6ud5chs"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzaceavzeu4gqte7o33vr4htiaapiwpfq6p26tgdkqla2baqhmiqswfso"), - "cron": MustParseCid("bafk2bzacech35onpqxep4yox36k7sr4mj4bch54s3i4b3yyaustrbo5xwfbfs"), - "init": MustParseCid("bafk2bzaceahxin3sf5f6ude5j6we4yeqlg66s5qe4tu7lwp26jcg7yp2ns6hi"), - "multisig": MustParseCid("bafk2bzacectfmzjtniypgl4whm42sws5aupihqgfikwsr7p5yoq3bmqaogldi"), - "paymentchannel": MustParseCid("bafk2bzacecbwu54ce5mjgp2pqxyj6kpn2vlgiu5wv2lj2byjiegxnn3infd5i"), - "reward": MustParseCid("bafk2bzacecskkbhe6c4ud5jt62wg4w7j7shj6xdwoyic74s5y6pgywxxvnw72"), - "storagemarket": MustParseCid("bafk2bzacebycxcwwm7hwhuhpasaskil2kxaqb7tins7azdvvm72rorlciuysi"), - "storageminer": MustParseCid("bafk2bzacecgx3etor5m6lahpmjdwqnryutqe6naiurfhgsju72rd4nqssutbg"), - "storagepower": MustParseCid("bafk2bzaceayvy6xyp5cwtngm457c5hssvihidppgq3o7gy3dlmhgor3yzujoc"), - "system": MustParseCid("bafk2bzacec6xctjxybp7r3kkhase56o6jsaiua7ure5ttu2xfuojt4jhlsoa6"), - "verifiedregistry": MustParseCid("bafk2bzacec2hcqlqcfacylfcrhhliwkisvh4y3adwt47xkf2gdvodwu6ccepc"), + "account": MustParseCid("bafk2bzacebd5zetyjtragjwrv2nqktct6u2pmsi4eifbanovxohx3a7lszjxi"), + "cron": MustParseCid("bafk2bzacecrszortqkc7har77ssgajglymv6ftrqvmdko5h2yqqh5k2qospl2"), + "datacap": MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e"), + "eam": MustParseCid("bafk2bzacecflry2dyjqj6fhpovkbcbei377zabectznuxsf6bxggsve7bsxga"), + "ethaccount": MustParseCid("bafk2bzacedl4pmkfxkzoqajs6im3ranmopozsmxjcxsnk3kwvd3vv7mfwwrf4"), + "evm": MustParseCid("bafk2bzacebgzvmvwv7rsnnhp3zhqbiqkumvyrc7pazfovpptgpgtqkalrli74"), + "init": MustParseCid("bafk2bzacecbxp66q3ytjkg37nyv4rmzezbfaigvx4i5yhvqbm5gg4amjeaias"), + "multisig": MustParseCid("bafk2bzacecjltag3mn75dsnmrmopjow27buxqhabissowayqlmavrcfetqswc"), + "paymentchannel": MustParseCid("bafk2bzacednzxg263eqbl2imwz3uhujov63tjkffieyl4hl3dhrgxyhwep6hc"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacectp23cxsbbdrr3uggnw7f263qll5wkkfzqhn5yq37ae2ehdjdzri"), + "storagemarket": MustParseCid("bafk2bzacea45ko3ezkpeujsniovncwnizc4wsxd7kyckskhs7gvzwthzb2mqe"), + "storageminer": MustParseCid("bafk2bzaced74qthwrl3gahcf7o3vrdrodbcqhlplh6fykbgy5sd2iyouhq44c"), + "storagepower": MustParseCid("bafk2bzaceduksv6wqthr5fgp7mx5prv6gzul2oozf3svrjbuggc4bgokdxgfy"), + "system": MustParseCid("bafk2bzacebe6j2ius6clbbr7dypsg54jzmn5xablzunph7ebedw6yhwla4cj2"), + "verifiedregistry": MustParseCid("bafk2bzacebu4joy25gneu2qv3qfm3ktakzalndjrbhekeqrqk3zhotv6nyy2g"), }, }, { Network: "butterflynet", Version: 9, - ManifestCid: MustParseCid("bafy2bzacec35by4erhcdgcsgzp7yb3j57utydlxxfc73m3k5pep67ehvvyv6i"), + ManifestCid: MustParseCid("bafy2bzaceba5qgs4z3imhlxwds5vamahngatvuuglbv5yl3ftfiosj6ud5chs"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzaceajsdln7v4chxqoukiw7lxw6aexg5qdsaex2hgelz2sbu24iblhzg"), - "cron": MustParseCid("bafk2bzacecgrwmgnqhybn3l23uvwf2n2vrcfjrprfzgd44uxers2pgr5mhsue"), - "datacap": MustParseCid("bafk2bzacebyier2ceh27acbrq2ccv4efvzotl6qntnlrxdsrik6i4tembz6qw"), - "init": MustParseCid("bafk2bzaceberhto43wnf4pklkd4c7d36kzslngyzyms4op7shxuswv3dtvfxu"), - "multisig": MustParseCid("bafk2bzaceaclpbrhoqdruvsuqqgknvy2k5dywzmjoehk4uarce3uvt3w2rewu"), - "paymentchannel": MustParseCid("bafk2bzacedzp56g5cg73oilloak3kf7u667rdkd5pgnhe2cljmr3o7ykcrzuk"), - "reward": MustParseCid("bafk2bzacebczbwfbbi6mvppbjcozatasjiaohvjjiqcy65ccuuyyw3xiixhk2"), - "storagemarket": MustParseCid("bafk2bzaceawqexy6t2ybzh3jjwhbs7icbg5vqnedbbge4e4r4pfp7spkcadsu"), - "storageminer": MustParseCid("bafk2bzacearemd7pn2jj26fdtqd4di27lfhpng3vp5chepm7qnmdzgiqr6wfi"), - "storagepower": MustParseCid("bafk2bzaceddc7fiaxfobfegqaobf5xinjgmhsa5iu4yi6klvc3jmjimcdvgyg"), - "system": MustParseCid("bafk2bzacedylltr57b2n6zpadh4i2c2kis4fzzvhao3kgvfaggrrbqyacew7q"), - "verifiedregistry": MustParseCid("bafk2bzacecjkesz766626ab4svnzpq3jfs26a75vfktlfaku5fjdao2eyiqyq"), + "account": MustParseCid("bafk2bzacebd5zetyjtragjwrv2nqktct6u2pmsi4eifbanovxohx3a7lszjxi"), + "cron": MustParseCid("bafk2bzacecrszortqkc7har77ssgajglymv6ftrqvmdko5h2yqqh5k2qospl2"), + "datacap": MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e"), + "eam": MustParseCid("bafk2bzacecflry2dyjqj6fhpovkbcbei377zabectznuxsf6bxggsve7bsxga"), + "ethaccount": MustParseCid("bafk2bzacedl4pmkfxkzoqajs6im3ranmopozsmxjcxsnk3kwvd3vv7mfwwrf4"), + "evm": MustParseCid("bafk2bzacebgzvmvwv7rsnnhp3zhqbiqkumvyrc7pazfovpptgpgtqkalrli74"), + "init": MustParseCid("bafk2bzacecbxp66q3ytjkg37nyv4rmzezbfaigvx4i5yhvqbm5gg4amjeaias"), + "multisig": MustParseCid("bafk2bzacecjltag3mn75dsnmrmopjow27buxqhabissowayqlmavrcfetqswc"), + "paymentchannel": MustParseCid("bafk2bzacednzxg263eqbl2imwz3uhujov63tjkffieyl4hl3dhrgxyhwep6hc"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacectp23cxsbbdrr3uggnw7f263qll5wkkfzqhn5yq37ae2ehdjdzri"), + "storagemarket": MustParseCid("bafk2bzacea45ko3ezkpeujsniovncwnizc4wsxd7kyckskhs7gvzwthzb2mqe"), + "storageminer": MustParseCid("bafk2bzaced74qthwrl3gahcf7o3vrdrodbcqhlplh6fykbgy5sd2iyouhq44c"), + "storagepower": MustParseCid("bafk2bzaceduksv6wqthr5fgp7mx5prv6gzul2oozf3svrjbuggc4bgokdxgfy"), + "system": MustParseCid("bafk2bzacebe6j2ius6clbbr7dypsg54jzmn5xablzunph7ebedw6yhwla4cj2"), + "verifiedregistry": MustParseCid("bafk2bzacebu4joy25gneu2qv3qfm3ktakzalndjrbhekeqrqk3zhotv6nyy2g"), }, }, { Network: "butterflynet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaceciz4ytt5gnn6gc4epez7v6xeg6efkgbvwfxkoa34o2gj3hp5f7zc"), + ManifestCid: MustParseCid("bafy2bzaced2wq4k4i2deknam6ehbynaoo37bhysud7eze7su3ftlaggwwjuje"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzacedavorwsriewoddjlaganjpsk3o7zfts2wyid3clv5xnctacg37j2"), - "cron": MustParseCid("bafk2bzacebtauucwaewxuzgxfpjtmn6xt3kya4om4ugyprlkhhkde76h7fkqg"), - "datacap": MustParseCid("bafk2bzacebzdjapqwasq6woxkgq2nm2nre3v7cl2754xwiuo2cfhvsceq4cba"), - "eam": MustParseCid("bafk2bzacecmr4zdbpfnemvgo446qby7x4y4v5cbfespt3f6ousv2hxnflyrlk"), - "embryo": MustParseCid("bafk2bzacebj2mj5zlcs3yjlgpbznzistfjkdlwaoncjziliqrxqavvz4dcvnk"), - "evm": MustParseCid("bafk2bzacebuewexvig54cuvsvwn4k4zr36tm2q5fel4ezq4v7363n2lmn362k"), - "init": MustParseCid("bafk2bzacebww5gsctsk5hack2alkt4kh55bmpb4ywzbyyhoaskryymjj3snj6"), - "multisig": MustParseCid("bafk2bzacec5k4wxvou34pyjd5kcsrbsfnlk4k753kkscg3ron2r7tsxollfsq"), - "paymentchannel": MustParseCid("bafk2bzacebzdeaxglaqpmegalakmxr6secjd24mu5llo4ctoy7pvom5upyuvs"), - "reward": MustParseCid("bafk2bzaceb4hyabxnyrrsno5erqqwk5ynnjibblzfcaq3aotlz3ek4uu6dyla"), - "storagemarket": MustParseCid("bafk2bzacedpocbf2lg2x2jg6arw2argnwmvo2hyjqvpkrgfu4khz5mtlzxz2o"), - "storageminer": MustParseCid("bafk2bzaceacrumah7jdfc62bmvemob4lsh5yiohwodest2cgxakgnn24cenlk"), - "storagepower": MustParseCid("bafk2bzaceaxz6n5nywermfptnz6dc53vqsa42lic4rf66l4irm3mqfj4ak5ps"), - "system": MustParseCid("bafk2bzaceb4w5bblgyu25ylytpmfrixjsk2ra6emd44j4mv42xfxbwnqloyzi"), - "verifiedregistry": MustParseCid("bafk2bzacedbz2koeb6teewobcjdpgfv7qdae7utgoka6wzlkf6gronnis2nn2"), + "account": MustParseCid("bafk2bzacebd5zetyjtragjwrv2nqktct6u2pmsi4eifbanovxohx3a7lszjxi"), + "cron": MustParseCid("bafk2bzacecrszortqkc7har77ssgajglymv6ftrqvmdko5h2yqqh5k2qospl2"), + "datacap": MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e"), + "eam": MustParseCid("bafk2bzacebsvtqzp7g7vpufbyqrwwcpuo2yu3y7kenm7auidyiwzcv6jdw724"), + "ethaccount": MustParseCid("bafk2bzacedl4pmkfxkzoqajs6im3ranmopozsmxjcxsnk3kwvd3vv7mfwwrf4"), + "evm": MustParseCid("bafk2bzacedx5wdyaihi22pwqqqtfxmuwh5acem46mzaep3znmhh5bsuqmxogq"), + "init": MustParseCid("bafk2bzacecbxp66q3ytjkg37nyv4rmzezbfaigvx4i5yhvqbm5gg4amjeaias"), + "multisig": MustParseCid("bafk2bzacecjltag3mn75dsnmrmopjow27buxqhabissowayqlmavrcfetqswc"), + "paymentchannel": MustParseCid("bafk2bzacednzxg263eqbl2imwz3uhujov63tjkffieyl4hl3dhrgxyhwep6hc"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacectp23cxsbbdrr3uggnw7f263qll5wkkfzqhn5yq37ae2ehdjdzri"), + "storagemarket": MustParseCid("bafk2bzacea45ko3ezkpeujsniovncwnizc4wsxd7kyckskhs7gvzwthzb2mqe"), + "storageminer": MustParseCid("bafk2bzaced74qthwrl3gahcf7o3vrdrodbcqhlplh6fykbgy5sd2iyouhq44c"), + "storagepower": MustParseCid("bafk2bzaceduksv6wqthr5fgp7mx5prv6gzul2oozf3svrjbuggc4bgokdxgfy"), + "system": MustParseCid("bafk2bzacebe6j2ius6clbbr7dypsg54jzmn5xablzunph7ebedw6yhwla4cj2"), + "verifiedregistry": MustParseCid("bafk2bzacebu4joy25gneu2qv3qfm3ktakzalndjrbhekeqrqk3zhotv6nyy2g"), }, }, { Network: "calibrationnet", @@ -100,79 +110,90 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "calibrationnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaced7wbd43lvgc55xb37mkoo4ppev6ig4jj4j7dtswtjfjq4u5qmpck"), + ManifestCid: MustParseCid("bafy2bzacearpwvmcqlailxyq2d2wtzmtudxqhvfot77tbdqotek5qiq5hyhzg"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzacecq4owv5begvryvpsy4atfb2jnf7g7o4hxovtdb5a4jfkzacownli"), - "cron": MustParseCid("bafk2bzaced4uz5w5h5wksx4end27lphd4qc4kh7q336uyt46lba5ddynwftya"), - "datacap": MustParseCid("bafk2bzacedoc7y4s5n3p2zo4bcmafcrellkakn2e3uyf5wb3mtbuqhvwqn2l4"), - "eam": MustParseCid("bafk2bzacealpqjgz5qmucm3v6z6hn36igx7zijixhqrxwoj3g4bdgvyml3adi"), - "embryo": MustParseCid("bafk2bzacebj2mj5zlcs3yjlgpbznzistfjkdlwaoncjziliqrxqavvz4dcvnk"), - "evm": MustParseCid("bafk2bzacedmlmyy2efbt4qk5ighawiychklhzc6pzyiwvpijwvxoq3xyxlgxw"), - "init": MustParseCid("bafk2bzaceaqcfmfylwdemq5bdcelydpf6iqfct4p7b2zwtmqyhuxn522yvic2"), - "multisig": MustParseCid("bafk2bzacebuh55hkbkobmmoaoduruss5nsh6e2gtqtdbqsmw6e7k5vg6heyrm"), - "paymentchannel": MustParseCid("bafk2bzacedcpzw7prdoxnaclcvmtwr6yf54zi4bzzwe5w3xknh72ji6p3qfc6"), - "reward": MustParseCid("bafk2bzaced74ym6j424zzbr6millasfcyl3r4zm5fnauasrwn3ti6fdarbkym"), - "storagemarket": MustParseCid("bafk2bzacec7delr2q42yj4wu3daa5xjz4zezeivphtx3xwyvpgwpdnfoevhh2"), - "storageminer": MustParseCid("bafk2bzaced7isnew5lhu237pdtwaqmbv65qqvfmmnve2c5yfobtfqw2fptuvc"), - "storagepower": MustParseCid("bafk2bzacebe5frk6gcgzcvzkxavhhbs3id3iyacybn7y7gxwzgl5t6zawzswg"), - "system": MustParseCid("bafk2bzacectivaezqijucle5s2f7xeui5uxig7bnk7fe4vsvz3xu7agjtb2ge"), - "verifiedregistry": MustParseCid("bafk2bzaceczgwckte4exultjxyzgzoo6m6r5coyphnlappi4clethhhybslxc"), + "account": MustParseCid("bafk2bzacea7zmrdz2rjbzlbmrmx3ko6pm3cbyqxxgogiqldsccbqffuok7m6s"), + "cron": MustParseCid("bafk2bzacec7bxugi7ouh75nglycy7qwdq7e2hnku3w6yafq4fwdwvvq2mtrl2"), + "datacap": MustParseCid("bafk2bzacedii4stmlo3ccdff7eevcolmgnuxy5ftkzbzwtkqa4iinlfzq4mei"), + "eam": MustParseCid("bafk2bzacedykxiyewqijj5nksr7qi6o4wu5yz4rezb747ntql4rpidyfdpes4"), + "ethaccount": MustParseCid("bafk2bzacecgbcbh3uk7olcfdqo44no5nxxayeqnycdznrlekqigbifor2revm"), + "evm": MustParseCid("bafk2bzaceau5n66rabegik55kymni6uyk7n7jb5eymfywybs543yifpl7du2m"), + "init": MustParseCid("bafk2bzacea7lxnvgxupwwgoxlmwtrca75w73qabe324wnwx43qranbgf5zdqo"), + "multisig": MustParseCid("bafk2bzacear5eu5gpbjlroqkmsgpqerzc4aemp2uqcaeq7s2h4ur4ucgpzesg"), + "paymentchannel": MustParseCid("bafk2bzacecwxuruxawcru7xfcx3rmt4hmhlfh4hi6jvfumerazz6jpvfmxxcw"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebk4syfvyk7kbxelk7ajo4vuxcc24k5ry52mvi3qtadlucy2vqlay"), + "storagemarket": MustParseCid("bafk2bzaced2rfzwup3jlwovblx2y7q64w6mshbtn2nmampi4zfd3b4oplkp5c"), + "storageminer": MustParseCid("bafk2bzacecden66gfmmgylmr47myn4murqmbt3ycyxqayn54yzhcsda32rp3m"), + "storagepower": MustParseCid("bafk2bzacebxvco3shuhdnzjmmme3olbffdgpab7j3onfncksi762k3agjhzaa"), + "system": MustParseCid("bafk2bzacednnhpk5kno67bkomiohweglryqvgnqz4cbks6eomidai677fat5w"), + "verifiedregistry": MustParseCid("bafk2bzaceawecz24xbz7robn7ck7k2mprkewvup6q346whbfiybcrvy63qcsa"), }, }, { Network: "caterpillarnet", Version: 8, - ManifestCid: MustParseCid("bafy2bzacecsmunz6fzhg53276cixadn6ybhcnzkgbw3la5hf342tfxsdoet26"), + ManifestCid: MustParseCid("bafy2bzacebsdvrxmdajiyxq2mxxxppvg2zwvqjzz3pgbsxwh6pvdcjofpmnxw"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzaced6yatl4y2nmqmx2h3btk3np6oelyw2yt57elsb2nfmm33fadzt2g"), - "cron": MustParseCid("bafk2bzacebrujytq4u7g62jbz52gio5k2s6rhruty7nt4eqq7ygitzxuee5zi"), - "init": MustParseCid("bafk2bzacedajw5ptnwfdidv6m4rvd4c2m7dve4lhfbawygl5idkalcxbiiudu"), - "multisig": MustParseCid("bafk2bzaceb3kh5hjh6eebb5236xp7crn2owyyo7irap6sy4ns76uc7om6pxuy"), - "paymentchannel": MustParseCid("bafk2bzacedl5am53e4mtxpzligcycxvmkolfkhfiuavww2dq3ukgaqwowj7vw"), - "reward": MustParseCid("bafk2bzacecbswf242j43cymj3wh7nszawwlofv6z6z4qipb5d32hpxdhxywng"), - "storagemarket": MustParseCid("bafk2bzaceca5ersmg3zxf2cztgktq33bmfjuiqjcjlktwj52xyrpujbdsqvek"), - "storageminer": MustParseCid("bafk2bzacedg2fqaq5udfp3h6cxhywm27dgagxtselfgkyyyunqq362eaxpdm4"), - "storagepower": MustParseCid("bafk2bzaceb3dm2i2q323e6iozo3r6pyded645vvlpf537kga2a3hu5x7abgl4"), - "system": MustParseCid("bafk2bzacebu47th3xerlngqavlipb6cfu2utljkxxzgadc3totogto2tmx2jc"), - "verifiedregistry": MustParseCid("bafk2bzaceci3niq3rmbcmepgn27zvlgci6d5t4dvthx3pbmmx3wcu5elova6i"), + "account": MustParseCid("bafk2bzacedfms6w3ghqtljpgsfuiqa6ztjx7kcuin6myjezj6rypj3zjbqms6"), + "cron": MustParseCid("bafk2bzaceaganmlpozvy4jywigs46pfrtdmhjjey6uyhpurplqbasojsislba"), + "datacap": MustParseCid("bafk2bzacebafqqe3wv5ytkfwmqzbmchgem66pw6yq6rl7w6vlhqsbkxnisswq"), + "eam": MustParseCid("bafk2bzaceaeayeksiivw4y3gdqtigbgfntyvwc3q7v2ivb5kx7u55pn4q5lt6"), + "ethaccount": MustParseCid("bafk2bzaceburkmtd63nmzxpux5rcxsbqr6x5didl2ce7al32g4tqrvo4pjz2i"), + "evm": MustParseCid("bafk2bzacea7tp4lop7ivhay3ozitkmxxurk74v4zse42ant47rh2uw5z3tq5e"), + "init": MustParseCid("bafk2bzaced23r54kwuebl7t6mdantbby5qpfduxwxfryeliof2enyqzhokix6"), + "multisig": MustParseCid("bafk2bzacebcn3rib6j6jvclys7dkf62hco45ssgamczkrtzt6xyewd6gt3mtu"), + "paymentchannel": MustParseCid("bafk2bzacecvas4leo44pqdguj22nnwqoqdgwajzrpm5d6ltkehc37ni6p6doq"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebiizh4ohvv6p4uxjusoygex4wxcgvudqmdl2fsh6ft6s2zt4tz6q"), + "storagemarket": MustParseCid("bafk2bzacedhkidshm7w2sqlw7izvaieyhkvmyhfsem6t6qfnkh7dnwqe56po2"), + "storageminer": MustParseCid("bafk2bzacedcmsibwfwhkp3sabmbyjmhqibyhjf3wwst7u5bkb2k6xpun3xevg"), + "storagepower": MustParseCid("bafk2bzacecrgnpypxnxzgglhlitaallfee3dl4ejy3y63knl7llnwba4ycf7i"), + "system": MustParseCid("bafk2bzacecl7gizbe52xj6sfm5glubkhrdblmzuwlid6lxrwr5zhcmv4dl2ew"), + "verifiedregistry": MustParseCid("bafk2bzacebzndvdqtdck2y35smcxezldgh6nm6rbkj3g3fmiknsgg2uah235y"), }, }, { Network: "caterpillarnet", Version: 9, - ManifestCid: MustParseCid("bafy2bzacedo6tmei6rzjaaddh2yffe5xgr6w4smnadofjhomc3saiv3ubplqe"), + ManifestCid: MustParseCid("bafy2bzacebsdvrxmdajiyxq2mxxxppvg2zwvqjzz3pgbsxwh6pvdcjofpmnxw"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzacebb32htqlwcwiotyvtbeehfmluu2ubjnepo57gelelwitudrstwba"), - "cron": MustParseCid("bafk2bzaceatvkww7soy4a6onu6xhe7pzkdzkqw46ywuu56yv3ncl76xpotzqu"), - "datacap": MustParseCid("bafk2bzaced57nk7i7w6qmbosy4gd6atme6yppesdgjllou6nppbti5yw6glcg"), - "init": MustParseCid("bafk2bzacedtoputbtz573ytg4yo5wbbg7fbhrzplux4uknxrb2jarifcuxxou"), - "multisig": MustParseCid("bafk2bzacec22z3xz45mbwgtliwkj7ngc43bervnt557c6dqsg6aesatpd5isy"), - "paymentchannel": MustParseCid("bafk2bzacedym7xnaxr2igfq72rttj2adqyqqfxk3j4qovp2bcwqk5paoe4t7e"), - "reward": MustParseCid("bafk2bzacedemsmbmbtk5toprmm6jivjq3wkxumavc65vpvm6ngspgjfkth7z6"), - "storagemarket": MustParseCid("bafk2bzacecb53mmklf4rbv263dvufqj3nsf7mi6zk2tjlgwmzbr633kw3ds3w"), - "storageminer": MustParseCid("bafk2bzacea3wljpn2ixgnd4lovr6yckiwd652ytcrz5amgj47lg6drjhgggqa"), - "storagepower": MustParseCid("bafk2bzaceakvohgvovpeldb6hjfg7readxo37a5h4qauis4nz6pte7mcll6c2"), - "system": MustParseCid("bafk2bzacecisuqj2ln7ep72xaejvs2lrgh2logc7retxxpd3qvobymwyz7bxo"), - "verifiedregistry": MustParseCid("bafk2bzacebyjosiripwqyf56yhjfs5hg26mch7totsqth4rgpt5j32hqg6ric"), + "account": MustParseCid("bafk2bzacedfms6w3ghqtljpgsfuiqa6ztjx7kcuin6myjezj6rypj3zjbqms6"), + "cron": MustParseCid("bafk2bzaceaganmlpozvy4jywigs46pfrtdmhjjey6uyhpurplqbasojsislba"), + "datacap": MustParseCid("bafk2bzacebafqqe3wv5ytkfwmqzbmchgem66pw6yq6rl7w6vlhqsbkxnisswq"), + "eam": MustParseCid("bafk2bzaceaeayeksiivw4y3gdqtigbgfntyvwc3q7v2ivb5kx7u55pn4q5lt6"), + "ethaccount": MustParseCid("bafk2bzaceburkmtd63nmzxpux5rcxsbqr6x5didl2ce7al32g4tqrvo4pjz2i"), + "evm": MustParseCid("bafk2bzacea7tp4lop7ivhay3ozitkmxxurk74v4zse42ant47rh2uw5z3tq5e"), + "init": MustParseCid("bafk2bzaced23r54kwuebl7t6mdantbby5qpfduxwxfryeliof2enyqzhokix6"), + "multisig": MustParseCid("bafk2bzacebcn3rib6j6jvclys7dkf62hco45ssgamczkrtzt6xyewd6gt3mtu"), + "paymentchannel": MustParseCid("bafk2bzacecvas4leo44pqdguj22nnwqoqdgwajzrpm5d6ltkehc37ni6p6doq"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebiizh4ohvv6p4uxjusoygex4wxcgvudqmdl2fsh6ft6s2zt4tz6q"), + "storagemarket": MustParseCid("bafk2bzacedhkidshm7w2sqlw7izvaieyhkvmyhfsem6t6qfnkh7dnwqe56po2"), + "storageminer": MustParseCid("bafk2bzacedcmsibwfwhkp3sabmbyjmhqibyhjf3wwst7u5bkb2k6xpun3xevg"), + "storagepower": MustParseCid("bafk2bzacecrgnpypxnxzgglhlitaallfee3dl4ejy3y63knl7llnwba4ycf7i"), + "system": MustParseCid("bafk2bzacecl7gizbe52xj6sfm5glubkhrdblmzuwlid6lxrwr5zhcmv4dl2ew"), + "verifiedregistry": MustParseCid("bafk2bzacebzndvdqtdck2y35smcxezldgh6nm6rbkj3g3fmiknsgg2uah235y"), }, }, { Network: "caterpillarnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzacea5csj2os7h76a6yvf6shgpwkysawijxemk5uvvzejxrwjo6ir4yg"), + ManifestCid: MustParseCid("bafy2bzacebxr4uvnf5g3373shjzbaca6pf4th6nnfubytjfbrlxcpvbjw4ane"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzacea7tpruyxdgyz4xa7curiphwdw4abmspft3ee24puruazdcl3tq5c"), - "cron": MustParseCid("bafk2bzacebc6kkj7kzsicm5baszjgd37b4b3kijsffqmmkhhjlyd7zhkwfcqm"), - "datacap": MustParseCid("bafk2bzaceddcmwl6po2jd3tfkkgv4zvub7i47gsx33pkqdspqhgvhe4npc4as"), - "eam": MustParseCid("bafk2bzaceccsvcww2rmqnh4plkq6oapqaeqbhydrtup54z4dwunolz5tpgtb4"), - "embryo": MustParseCid("bafk2bzacebj2mj5zlcs3yjlgpbznzistfjkdlwaoncjziliqrxqavvz4dcvnk"), - "evm": MustParseCid("bafk2bzacea5sig3zpxfkqppoj3t344cvuhzvkx6ge2isgdzc34rfpng2ogdje"), - "init": MustParseCid("bafk2bzacedtby353aho7itoyoj7w6moydmigjm3sgy6djgnfxqehlpae4vcc2"), - "multisig": MustParseCid("bafk2bzacedyguvwz5zfveqoqicn3j6lkdzipf247nhvdi6dvmahulr7nzgox6"), - "paymentchannel": MustParseCid("bafk2bzaceavaatmmnsz3v3ksopcbu6jx4iq7u7nnmqbclsiabsfkfu3zfpmka"), - "reward": MustParseCid("bafk2bzacecrphs4avteik4yejsqwkpy5bcqramdhnzykbfq3uu2qalj2p26ti"), - "storagemarket": MustParseCid("bafk2bzaceajby2jb5m3fenzarum374zxdzuyrpkspfljwovu7c3hvyceqd5sa"), - "storageminer": MustParseCid("bafk2bzacebqtn7jdvk756ighri5ajro6gjepnef3c6rxupbbgkth62zytiy5s"), - "storagepower": MustParseCid("bafk2bzacedwlo32brlalpovfkkk7qwo3ou2kpgv2bf7fioy5srn7uejmn7n46"), - "system": MustParseCid("bafk2bzacebbt63h26x5vw5fdo2pmdb4q65u3t6lilkugvmjar6zfsc7ethxsi"), - "verifiedregistry": MustParseCid("bafk2bzacecr5kbyypdxnxlepzk5sji2k72t454vto5ok4owfcuwfpeyivjtu4"), + "account": MustParseCid("bafk2bzacedfms6w3ghqtljpgsfuiqa6ztjx7kcuin6myjezj6rypj3zjbqms6"), + "cron": MustParseCid("bafk2bzaceaganmlpozvy4jywigs46pfrtdmhjjey6uyhpurplqbasojsislba"), + "datacap": MustParseCid("bafk2bzacebafqqe3wv5ytkfwmqzbmchgem66pw6yq6rl7w6vlhqsbkxnisswq"), + "eam": MustParseCid("bafk2bzacedwk5eqczflcsuisqsyeomgkpg54olojjq2ieb2ozu5s45wfwluti"), + "ethaccount": MustParseCid("bafk2bzaceburkmtd63nmzxpux5rcxsbqr6x5didl2ce7al32g4tqrvo4pjz2i"), + "evm": MustParseCid("bafk2bzacedbroioygjnbjtc7ykcjjs4wfbwnaa6gkzubi7c5enifoqqqu66s6"), + "init": MustParseCid("bafk2bzaced23r54kwuebl7t6mdantbby5qpfduxwxfryeliof2enyqzhokix6"), + "multisig": MustParseCid("bafk2bzacebcn3rib6j6jvclys7dkf62hco45ssgamczkrtzt6xyewd6gt3mtu"), + "paymentchannel": MustParseCid("bafk2bzacecvas4leo44pqdguj22nnwqoqdgwajzrpm5d6ltkehc37ni6p6doq"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebiizh4ohvv6p4uxjusoygex4wxcgvudqmdl2fsh6ft6s2zt4tz6q"), + "storagemarket": MustParseCid("bafk2bzacedhkidshm7w2sqlw7izvaieyhkvmyhfsem6t6qfnkh7dnwqe56po2"), + "storageminer": MustParseCid("bafk2bzacedcmsibwfwhkp3sabmbyjmhqibyhjf3wwst7u5bkb2k6xpun3xevg"), + "storagepower": MustParseCid("bafk2bzacecrgnpypxnxzgglhlitaallfee3dl4ejy3y63knl7llnwba4ycf7i"), + "system": MustParseCid("bafk2bzacecl7gizbe52xj6sfm5glubkhrdblmzuwlid6lxrwr5zhcmv4dl2ew"), + "verifiedregistry": MustParseCid("bafk2bzacebzndvdqtdck2y35smcxezldgh6nm6rbkj3g3fmiknsgg2uah235y"), }, }, { Network: "devnet", @@ -212,23 +233,90 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "devnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzacea73thrlpfejrswlcu5uhe7rcgdewvmrcwoef6jzngsba3i4v5ibi"), + ManifestCid: MustParseCid("bafy2bzacebixrjysarwxdadewlllfp4rwfoejxstwdutghghei54uvuuxlsbq"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzaceau2o55aripm7kqrbzzog72zcduv5psnxzpohx5rdkykepc4z7aag"), - "cron": MustParseCid("bafk2bzacec5qc5xluwikf4lolfa4oe356iwep25tiezbxfdyg5jib54rhlh6q"), - "datacap": MustParseCid("bafk2bzacebo47u6q3xou5exsecjpa4rpfqjfm7vyhz4qlr3nk7p46trsk4occ"), - "eam": MustParseCid("bafk2bzacea6yeptevserd7ayf4ahokor4sdpizpxpbqwkuvvhzdkon672shsm"), - "embryo": MustParseCid("bafk2bzacebj2mj5zlcs3yjlgpbznzistfjkdlwaoncjziliqrxqavvz4dcvnk"), - "evm": MustParseCid("bafk2bzacebi46zgjili4luu3nqy6mno5k4skvo4cvs7genhkdfaukhtw7xirw"), - "init": MustParseCid("bafk2bzacedvf2bij6jovem2dfzkz347yvmydxj7vlgaiagosz5t3c5jyy43zu"), - "multisig": MustParseCid("bafk2bzacecukolwx6y5pcajnxg2aawiubgxo5zyj24a23zg5t4qu3k4qbofh4"), - "paymentchannel": MustParseCid("bafk2bzacecwyih7nodrwsw5vyl5zk7fapklje76jpowqjr6x6br2bm55smqqy"), - "reward": MustParseCid("bafk2bzacea6vfrcprxg2i4l5qnigf4c6pyvnjxpzfqr4pmph3elif7sfidrei"), - "storagemarket": MustParseCid("bafk2bzaceahradb3od4ahs46x6yriwvm36iabgtohhoiolubsumto5eravzbu"), - "storageminer": MustParseCid("bafk2bzacedekivqgvqapbepvzn6jte3xyymyg5yjuwy42xvboa6rcqnzgo74u"), - "storagepower": MustParseCid("bafk2bzacedkmiosllqqqarmr53twspyswdvsm7givwczgo3qqsxzpad4hzjma"), - "system": MustParseCid("bafk2bzaceagdymtxb4lxqqjgmnphbgdtdgveuuqaouswpzagj4bpbon3ptop4"), - "verifiedregistry": MustParseCid("bafk2bzacec556wsqldm22k2abshvvnsrawlm3bbqkwzht6ubcj76m2jsy3azi"), + "account": MustParseCid("bafk2bzacebb5txxkfexeaxa2th3rckxsxchzyss3ijgqbicf265h7rre2rvhm"), + "cron": MustParseCid("bafk2bzacecotn4gwluhamoqwnzgbg7ogehv26o5xnhjzltnzfv6utrlyanzek"), + "datacap": MustParseCid("bafk2bzacea4hket2srrtbewkf3tip6ellwpxdfbrzt5u47y57i2k6iojqqgba"), + "eam": MustParseCid("bafk2bzacecxm2gr6tevzzan6oqp6aiqydjm5b7eo34mlzo5jdm7mnlbbueikq"), + "ethaccount": MustParseCid("bafk2bzacedh4y3zvtgft3i6ift4rpptgr2dx67pvenowvq7yaspuf25gqgcdc"), + "evm": MustParseCid("bafk2bzacec26myls7vg6anr5yjbb2r75dryhdzwlwnrhjcyuhahlaoxdrua6i"), + "init": MustParseCid("bafk2bzacedof2ckc6w2qboxzxv4w67njcug4ut4cq3nnlrfybzsvlgnp4kt24"), + "multisig": MustParseCid("bafk2bzacec4eqajjqhl53tnkbs7glu7njlbtlditi7lxhvw33ezmxk6jae46y"), + "paymentchannel": MustParseCid("bafk2bzacec6nvdprqja7dy3qp5islebbbh2ifiyg2p7arbe6pocjhfe6xwkfy"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacecqaoqksjotl4wwsqt2wf6kqv6s372afi3r5on4bqj3u3a44px2rm"), + "storagemarket": MustParseCid("bafk2bzaceb7yefqlzyoxkgoug5k4kizy63izrg5udartw5l4d6j53xjwdxbg4"), + "storageminer": MustParseCid("bafk2bzaceagmuxcgdj65yuvtfrcup5viwkhhhlzslpdd4j6v6qxmhxtcssc6u"), + "storagepower": MustParseCid("bafk2bzacedt2qu6ykj3bjsfhchg2gxvc6asfb7c4tmranl76n4ojut5d6sgqm"), + "system": MustParseCid("bafk2bzacebp4ysxqv4cy633pgdxjlbwkwqkokc2fgez77y73abpt5hkthczn6"), + "verifiedregistry": MustParseCid("bafk2bzaceb7odugx7meltvt2gra4vogn2g6avbgysivvdccldylusjcfsnfhy"), + }, +}, { + Network: "hyperspace", + Version: 8, + ManifestCid: MustParseCid("bafy2bzacedvffumcvf72f2btjqvece3kpcdorxq5tq76iwcmqbzvsiu526cqm"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecim7uybic2qprbkjhowg7qkniv4zywj5h5g4u4ss72urco2akzuo"), + "cron": MustParseCid("bafk2bzaceahgq64awp4f7li3hdgimc4upqvdvltpmeywckvens33umcxt424a"), + "datacap": MustParseCid("bafk2bzacebkxn52ttooaslkwncijk3bgd3tm2zw7vijdhwvg2cxnxbrzmmq5e"), + "eam": MustParseCid("bafk2bzaceczhgub5anrnaf7ol65mu54gsgwcj6c6m3yhet7rhxm2l6kz4s4ru"), + "ethaccount": MustParseCid("bafk2bzacealn5enbxyxbfs7gbsjbyma2zk3bcr7okvflxhpr753d4eh6ixooa"), + "evm": MustParseCid("bafk2bzacedljkrmazyewawpnddrkzrt55556374dw2pm2hokgkompgzw4vx5y"), + "init": MustParseCid("bafk2bzacec55gyyaqjrw7zughywocgwcjvv6k5fijjpjw4xgckuqz6pjtff5a"), + "multisig": MustParseCid("bafk2bzaceblozbdzybdivvjdiid4jwm2jc6x5a66sunh2vvwsqba6wzqmr7i6"), + "paymentchannel": MustParseCid("bafk2bzacealcyke5a6n24efs6qe4iikynpk2twqssyugy7jcyf6p6shgw2iwa"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebafzaqhwsm3nmsfwcd6ngvx6ev6zlcpyfljqh4kb77vok6opban6"), + "storagemarket": MustParseCid("bafk2bzacecrjfg4p7fxznsdkoobs4po2ve3ywixrirrk6netgxh63qqaefamg"), + "storageminer": MustParseCid("bafk2bzaceb3ctd4atxwhdkmlg4i63zxo5aopknlj7l5kaiqr22xpcmico6vg4"), + "storagepower": MustParseCid("bafk2bzacecvcix3ugopvby2vah5wwiu5cqjedwzwkanmr34kdoc4f3o6p7nsq"), + "system": MustParseCid("bafk2bzacedo2hfopt6gy52goj7fot5qwzhtnysmgo7h25crq4clpugkerjabk"), + "verifiedregistry": MustParseCid("bafk2bzacea7rfkjrixaidksnmjehglmavyt56nyeu3sfxu2e3dcpf62oab6tw"), + }, +}, { + Network: "hyperspace", + Version: 9, + ManifestCid: MustParseCid("bafy2bzacedvffumcvf72f2btjqvece3kpcdorxq5tq76iwcmqbzvsiu526cqm"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecim7uybic2qprbkjhowg7qkniv4zywj5h5g4u4ss72urco2akzuo"), + "cron": MustParseCid("bafk2bzaceahgq64awp4f7li3hdgimc4upqvdvltpmeywckvens33umcxt424a"), + "datacap": MustParseCid("bafk2bzacebkxn52ttooaslkwncijk3bgd3tm2zw7vijdhwvg2cxnxbrzmmq5e"), + "eam": MustParseCid("bafk2bzaceczhgub5anrnaf7ol65mu54gsgwcj6c6m3yhet7rhxm2l6kz4s4ru"), + "ethaccount": MustParseCid("bafk2bzacealn5enbxyxbfs7gbsjbyma2zk3bcr7okvflxhpr753d4eh6ixooa"), + "evm": MustParseCid("bafk2bzacedljkrmazyewawpnddrkzrt55556374dw2pm2hokgkompgzw4vx5y"), + "init": MustParseCid("bafk2bzacec55gyyaqjrw7zughywocgwcjvv6k5fijjpjw4xgckuqz6pjtff5a"), + "multisig": MustParseCid("bafk2bzaceblozbdzybdivvjdiid4jwm2jc6x5a66sunh2vvwsqba6wzqmr7i6"), + "paymentchannel": MustParseCid("bafk2bzacealcyke5a6n24efs6qe4iikynpk2twqssyugy7jcyf6p6shgw2iwa"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebafzaqhwsm3nmsfwcd6ngvx6ev6zlcpyfljqh4kb77vok6opban6"), + "storagemarket": MustParseCid("bafk2bzacecrjfg4p7fxznsdkoobs4po2ve3ywixrirrk6netgxh63qqaefamg"), + "storageminer": MustParseCid("bafk2bzaceb3ctd4atxwhdkmlg4i63zxo5aopknlj7l5kaiqr22xpcmico6vg4"), + "storagepower": MustParseCid("bafk2bzacecvcix3ugopvby2vah5wwiu5cqjedwzwkanmr34kdoc4f3o6p7nsq"), + "system": MustParseCid("bafk2bzacedo2hfopt6gy52goj7fot5qwzhtnysmgo7h25crq4clpugkerjabk"), + "verifiedregistry": MustParseCid("bafk2bzacea7rfkjrixaidksnmjehglmavyt56nyeu3sfxu2e3dcpf62oab6tw"), + }, +}, { + Network: "hyperspace", + Version: 10, + ManifestCid: MustParseCid("bafy2bzaced6hc7ujjmypg6mkrxdmf32oh2udhmhpmwkqyxusdkxoi2uoodyxg"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecim7uybic2qprbkjhowg7qkniv4zywj5h5g4u4ss72urco2akzuo"), + "cron": MustParseCid("bafk2bzaceahgq64awp4f7li3hdgimc4upqvdvltpmeywckvens33umcxt424a"), + "datacap": MustParseCid("bafk2bzacebkxn52ttooaslkwncijk3bgd3tm2zw7vijdhwvg2cxnxbrzmmq5e"), + "eam": MustParseCid("bafk2bzaceaftiqwpx6dcjfqxyq7pazn2p55diukf32pz74755vj7pgg5joexw"), + "ethaccount": MustParseCid("bafk2bzacealn5enbxyxbfs7gbsjbyma2zk3bcr7okvflxhpr753d4eh6ixooa"), + "evm": MustParseCid("bafk2bzacea6etsvrqejjl7uej5dxlswja5gxzqyggsjjvg27timvtiedf7nsg"), + "init": MustParseCid("bafk2bzacec55gyyaqjrw7zughywocgwcjvv6k5fijjpjw4xgckuqz6pjtff5a"), + "multisig": MustParseCid("bafk2bzaceblozbdzybdivvjdiid4jwm2jc6x5a66sunh2vvwsqba6wzqmr7i6"), + "paymentchannel": MustParseCid("bafk2bzacealcyke5a6n24efs6qe4iikynpk2twqssyugy7jcyf6p6shgw2iwa"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebafzaqhwsm3nmsfwcd6ngvx6ev6zlcpyfljqh4kb77vok6opban6"), + "storagemarket": MustParseCid("bafk2bzacecrjfg4p7fxznsdkoobs4po2ve3ywixrirrk6netgxh63qqaefamg"), + "storageminer": MustParseCid("bafk2bzaceb3ctd4atxwhdkmlg4i63zxo5aopknlj7l5kaiqr22xpcmico6vg4"), + "storagepower": MustParseCid("bafk2bzacecvcix3ugopvby2vah5wwiu5cqjedwzwkanmr34kdoc4f3o6p7nsq"), + "system": MustParseCid("bafk2bzacedo2hfopt6gy52goj7fot5qwzhtnysmgo7h25crq4clpugkerjabk"), + "verifiedregistry": MustParseCid("bafk2bzacea7rfkjrixaidksnmjehglmavyt56nyeu3sfxu2e3dcpf62oab6tw"), }, }, { Network: "mainnet", @@ -268,23 +356,24 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "mainnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaceduyggnyqhlr346hfw32tbobzrvhzhill33zhe7jw64pmwjci2xoc"), + ManifestCid: MustParseCid("bafy2bzacea5vylkbby7rb42fknkk4g4byhj7hkqlxp4z4urydi3vlpwsgllik"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzacedmr3wxl7qmhquageorrt3aavbzqfpm7eymxidakwuhaobu7dseqs"), - "cron": MustParseCid("bafk2bzaceblekxapm5nnqnxmw3mk27236iyutvbhhpsc3fyde7zi7guccn7cc"), - "datacap": MustParseCid("bafk2bzacedu4jevyvqsilq7bq4uhegbkm75muwebc5ifqpfaojwhexf2j4i6a"), - "eam": MustParseCid("bafk2bzacedc7224twbolvdq6iwc7ybdpah2ywe3ueo33jv67ecimndinle374"), - "embryo": MustParseCid("bafk2bzacebj2mj5zlcs3yjlgpbznzistfjkdlwaoncjziliqrxqavvz4dcvnk"), - "evm": MustParseCid("bafk2bzaceaggldo6wmkvp5innv4pnjv4xnpedspzofvma3dhu7vk45hh5djoq"), - "init": MustParseCid("bafk2bzacedutlaebaczkdi4vqvt3xim24u3whleqk2r4lufjd5jnmxcosea6q"), - "multisig": MustParseCid("bafk2bzaceatiqxjwtugpzus3s52zoggnrftxqn7kiw3obvjgkjvtd6zr3636q"), - "paymentchannel": MustParseCid("bafk2bzacebyviac6i43gtsvmjfg6mzcp6rwgz44axidc7m432btbmvt7i2m2g"), - "reward": MustParseCid("bafk2bzacecbcnlvk2izojpfoaksitqenhzaofn6ynxx5pegl4y45wjlouexdi"), - "storagemarket": MustParseCid("bafk2bzacebobteeoz2jycplgtydfyltzughegz2sopn6pzy2udjfvuo77joyk"), - "storageminer": MustParseCid("bafk2bzacecwcypas3y6u4rya7qolfwmou437xgrjxh7mnnim7bo3nhk4dscxw"), - "storagepower": MustParseCid("bafk2bzacec62kids6rcrdmdeqhwiz3s5rs35s5gn25ilwemgmm6jqnr2rnaaq"), - "system": MustParseCid("bafk2bzacecj3c4bjbs2xfttn7zqle7yocqh47u2s7hwuxrsn7fi5h74tcyxoc"), - "verifiedregistry": MustParseCid("bafk2bzacedgf7zbnlste5ukzueduemkimiit64scz7lvebztufx5jxtx6gkz2"), + "account": MustParseCid("bafk2bzacedsn6i2flkpk6sb4iuejo7gfl5n6fhsdawggtbsihlrrjtvs7oepu"), + "cron": MustParseCid("bafk2bzacecw4guere7ba2canyi2622lw52b5qbn7iubckcp5cwlmx2kw7qqwy"), + "datacap": MustParseCid("bafk2bzaceat2ncckd2jjjqcovd3ib4sylwff7jk7rlk6gr5d2gmrrc7isrmu2"), + "eam": MustParseCid("bafk2bzacebbpu5smjrjqpkrvvlhcpk23yvlovlndqmwzhfz5kuuph54tdw732"), + "ethaccount": MustParseCid("bafk2bzacedmwzkbytxfn7exmxxosomvix4mpyxrmupeqw45aofqmdq5q7mgqe"), + "evm": MustParseCid("bafk2bzacechkf43lmddynxmc35hvz5kwr3fdxrbg6fxbcvysfsihgiopbrb7o"), + "init": MustParseCid("bafk2bzacec6276d7ls3hhuqibqorn3yp45mv7hroczf3bgb6jkhmbb2zqt3bw"), + "multisig": MustParseCid("bafk2bzaceahggxrnjj3w3cgtko54srssqyhcs4x6y55ytego6jf2owg5piw3y"), + "paymentchannel": MustParseCid("bafk2bzaceaobaqjamso57bkjv3n4ilv7lfropgrncnnej666w3tegmr4cfgve"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacecqet4s7abe4owznq2wtdefe2z2w5isbde2gj7u3hwgf54di4r7hy"), + "storagemarket": MustParseCid("bafk2bzacebgk2q2ktrqauzop6ha4pcq5gpf6g24hprxnp6wdmlzf724e5sx7i"), + "storageminer": MustParseCid("bafk2bzacecqrm4tlmzci7vilmcchr4lq2e6yyrlhy6ofbuecjna2phmbq4h2a"), + "storagepower": MustParseCid("bafk2bzaceco674a5e5lpv5leui65bljxzgyc2ypdquaow55iuckmq5rvsghr6"), + "system": MustParseCid("bafk2bzacedlt3zcsbw2vucbydptbcfudw5y5pkhhxe26m7pjod6rkxkuzn52w"), + "verifiedregistry": MustParseCid("bafk2bzacea2eehyf7h3m6ydh46piu2gtr4fawpqzh3brtmybgi2tyxf5nwj6m"), }, }, { Network: "testing", @@ -324,23 +413,24 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "testing", Version: 10, - ManifestCid: MustParseCid("bafy2bzacearlgbespxi2zdrybtp2rrbwscmtbyou5qa2egbdvcz6v2yjjqvjo"), + ManifestCid: MustParseCid("bafy2bzacea7tbn4p232ecrjvlp2uvpci5pexqjqq2vpv4t5ihktpja2zsj3ek"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzaceba6me5ipkcijuhyypnzjydhv3ebi2ctailar7mtzlk4vk3rbxfee"), - "cron": MustParseCid("bafk2bzacea6k2mai2xnakygqvbigivfrvv5q7d34qrzjv2crkqtwbjxnxmkbe"), - "datacap": MustParseCid("bafk2bzaceah4oxcgck6bcfkzctm2klpvmltyidq7uxnlkcap6ypi3lnkcvrqk"), - "eam": MustParseCid("bafk2bzacedjtkvocrnkrot2oztsfrxtpwl32wwbmbkrjfbbm4xipwzrhhxn5c"), - "embryo": MustParseCid("bafk2bzacebj2mj5zlcs3yjlgpbznzistfjkdlwaoncjziliqrxqavvz4dcvnk"), - "evm": MustParseCid("bafk2bzaced6vhabkr2ojpjzsybrq5yvksjzpjk6yei6fwobkwwydlj5f473pw"), - "init": MustParseCid("bafk2bzaceaib3o5e7wop7kwjirgpferqarmngrgjkur2yhdnwplctidpxsgme"), - "multisig": MustParseCid("bafk2bzaced4z3awacxumq6yr33a3adu2legb7colahgvqpmigs3fvvjxs3byc"), - "paymentchannel": MustParseCid("bafk2bzaceb6mfi24mpzt7qlkratj2tdtqo7aia67zcztuslrxcjaycz6fnai6"), - "reward": MustParseCid("bafk2bzacebngh5kwtem4ncarpjtxhs4rwyoficttkgxlsjtiz5ucdi4p3czoc"), - "storagemarket": MustParseCid("bafk2bzacecnsibyil62jfq2gbkoe6c2epehfcrxzjmqjnwz7kxab2hkbu3lks"), - "storageminer": MustParseCid("bafk2bzacedzw4vkrt3sdkhagpvn62pknyyjkcrzewncvtvae5qgwe6ulzx4a4"), - "storagepower": MustParseCid("bafk2bzacedxgadibot6nzvripqt3z5shvjsoscupinejnsvswq4cbeskblwyy"), - "system": MustParseCid("bafk2bzacedm24avrmp5o5odhpad43qeglooflygwh4ah7qnzbij2h4c3v6cge"), - "verifiedregistry": MustParseCid("bafk2bzaceapq3j6ww3ofytwq3pz3obumaqsyg3wrm6tksdh7op23a72co3rya"), + "account": MustParseCid("bafk2bzaceds3iy5qjgr3stoywxt4uxvhybca23q7d2kxhitedgudrkhxaxa6o"), + "cron": MustParseCid("bafk2bzacebxp4whb4ocqxnbvqlz3kckarabtyvhjbhqvrdwhejuffwactyiss"), + "datacap": MustParseCid("bafk2bzacedepm3zas6vqryruwiz7d3axkneo7v66q65gf2dlpfd53pjlycrg4"), + "eam": MustParseCid("bafk2bzacea2uascrtv6xnsqlxyf3tcf4onpgrs7frh55p6dnrdeum2uup7wx4"), + "ethaccount": MustParseCid("bafk2bzacecbhz4ipg773lsovgpjysm6fxl2i7y2wuxadqnt4s4vm3nd2qodb4"), + "evm": MustParseCid("bafk2bzaceabwn4i62od3i4qkuj5zx4vn5w5cbcl53tqnszk6kl43bfl55hl6c"), + "init": MustParseCid("bafk2bzacebqym5i5eciyyyzsimu73z6bkffpm5hzjpx3gwcm64pm2fh7okrja"), + "multisig": MustParseCid("bafk2bzacecmlyngek7qvj5ezaaitadrycapup3mbty4ijlzun6g23tcoysxle"), + "paymentchannel": MustParseCid("bafk2bzacedspin4hxpgnxkjen3hsxpcc52oc5q4ypukl4qq6vaytcgmmi7hl4"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacecmumnnqkqnoa23hhsfgwccwvmksr2q65tznbves6x2a6fhwvtm7a"), + "storagemarket": MustParseCid("bafk2bzacea2re4nxba7mtlrwdxabu2i3l2fwbuw2veb4p7qbvrsaocgablqvi"), + "storageminer": MustParseCid("bafk2bzacecixm7d7d5ltsp6mubzw5s3fv335cjuwwy7oqovujn3xlyk6twivs"), + "storagepower": MustParseCid("bafk2bzaced5lqpftacjsflcgfwlm32gzckpi3ndj3kd3prtqqi2lfj3uhl2je"), + "system": MustParseCid("bafk2bzaceaafqf7lwaiqx5po6b3l4dfg4xsr5qhfk3bjgoi7qke2mfy3shla4"), + "verifiedregistry": MustParseCid("bafk2bzacec2ouguts4z335vetmdeifpk5fkqthcmrwshk7yxbw2uohddfu5lo"), }, }, { Network: "testing-fake-proofs", @@ -380,22 +470,23 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "testing-fake-proofs", Version: 10, - ManifestCid: MustParseCid("bafy2bzacea4irr2oxhclwt4mvtrevbzb7mbqddcebjz7bkqjq6eoflpfhencc"), + ManifestCid: MustParseCid("bafy2bzacecyqfyzmw72234rvbk6vzq2omnmt3cbfezkq2h3ewnn33w42b2s62"), Actors: map[string]cid.Cid{ - "account": MustParseCid("bafk2bzaceba6me5ipkcijuhyypnzjydhv3ebi2ctailar7mtzlk4vk3rbxfee"), - "cron": MustParseCid("bafk2bzacea6k2mai2xnakygqvbigivfrvv5q7d34qrzjv2crkqtwbjxnxmkbe"), - "datacap": MustParseCid("bafk2bzaceah4oxcgck6bcfkzctm2klpvmltyidq7uxnlkcap6ypi3lnkcvrqk"), - "eam": MustParseCid("bafk2bzacedjtkvocrnkrot2oztsfrxtpwl32wwbmbkrjfbbm4xipwzrhhxn5c"), - "embryo": MustParseCid("bafk2bzacebj2mj5zlcs3yjlgpbznzistfjkdlwaoncjziliqrxqavvz4dcvnk"), - "evm": MustParseCid("bafk2bzaced6vhabkr2ojpjzsybrq5yvksjzpjk6yei6fwobkwwydlj5f473pw"), - "init": MustParseCid("bafk2bzaceaib3o5e7wop7kwjirgpferqarmngrgjkur2yhdnwplctidpxsgme"), - "multisig": MustParseCid("bafk2bzaced4z3awacxumq6yr33a3adu2legb7colahgvqpmigs3fvvjxs3byc"), - "paymentchannel": MustParseCid("bafk2bzaceb6mfi24mpzt7qlkratj2tdtqo7aia67zcztuslrxcjaycz6fnai6"), - "reward": MustParseCid("bafk2bzacebngh5kwtem4ncarpjtxhs4rwyoficttkgxlsjtiz5ucdi4p3czoc"), - "storagemarket": MustParseCid("bafk2bzacecnsibyil62jfq2gbkoe6c2epehfcrxzjmqjnwz7kxab2hkbu3lks"), - "storageminer": MustParseCid("bafk2bzaceb4grddnw54gczgcdak5a2gqvwed66mhibbug6qu4jy35bf45jltg"), - "storagepower": MustParseCid("bafk2bzacedp2dnbk4bg3hhaeztre4q3jv7eqs267rlafszpggb2njjn3x5eru"), - "system": MustParseCid("bafk2bzacedm24avrmp5o5odhpad43qeglooflygwh4ah7qnzbij2h4c3v6cge"), - "verifiedregistry": MustParseCid("bafk2bzaceapq3j6ww3ofytwq3pz3obumaqsyg3wrm6tksdh7op23a72co3rya"), + "account": MustParseCid("bafk2bzaceds3iy5qjgr3stoywxt4uxvhybca23q7d2kxhitedgudrkhxaxa6o"), + "cron": MustParseCid("bafk2bzacebxp4whb4ocqxnbvqlz3kckarabtyvhjbhqvrdwhejuffwactyiss"), + "datacap": MustParseCid("bafk2bzacedepm3zas6vqryruwiz7d3axkneo7v66q65gf2dlpfd53pjlycrg4"), + "eam": MustParseCid("bafk2bzacea2uascrtv6xnsqlxyf3tcf4onpgrs7frh55p6dnrdeum2uup7wx4"), + "ethaccount": MustParseCid("bafk2bzacecbhz4ipg773lsovgpjysm6fxl2i7y2wuxadqnt4s4vm3nd2qodb4"), + "evm": MustParseCid("bafk2bzaceabwn4i62od3i4qkuj5zx4vn5w5cbcl53tqnszk6kl43bfl55hl6c"), + "init": MustParseCid("bafk2bzacebqym5i5eciyyyzsimu73z6bkffpm5hzjpx3gwcm64pm2fh7okrja"), + "multisig": MustParseCid("bafk2bzacecmlyngek7qvj5ezaaitadrycapup3mbty4ijlzun6g23tcoysxle"), + "paymentchannel": MustParseCid("bafk2bzacedspin4hxpgnxkjen3hsxpcc52oc5q4ypukl4qq6vaytcgmmi7hl4"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacecmumnnqkqnoa23hhsfgwccwvmksr2q65tznbves6x2a6fhwvtm7a"), + "storagemarket": MustParseCid("bafk2bzacea2re4nxba7mtlrwdxabu2i3l2fwbuw2veb4p7qbvrsaocgablqvi"), + "storageminer": MustParseCid("bafk2bzacedz4mmupganqbwe6mz4636zepooh5ipxb36tybsrf6ynewrfdihl6"), + "storagepower": MustParseCid("bafk2bzacedcqv6k2fszpfb7zpw6q6c6fe2u7g2zefabcntp46xgv3owosgymy"), + "system": MustParseCid("bafk2bzaceaafqf7lwaiqx5po6b3l4dfg4xsr5qhfk3bjgoi7qke2mfy3shla4"), + "verifiedregistry": MustParseCid("bafk2bzacec2ouguts4z335vetmdeifpk5fkqthcmrwshk7yxbw2uohddfu5lo"), }, }} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 114fb584a..d7a354461 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 8b3fc78f2..74a9f3221 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 0424b7cad..d3750150c 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 72a40797a..e84f7f5d1 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/build/params_2k.go b/build/params_2k.go index f822d701e..081007dd1 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -21,6 +21,7 @@ const GenesisFile = "" var NetworkBundle = "devnet" var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = true const GenesisNetworkVersion = network.Version18 @@ -58,6 +59,8 @@ var UpgradeSkyrHeight = abi.ChainEpoch(-19) var UpgradeSharkHeight = abi.ChainEpoch(-20) +var UpgradeHyggeHeight = abi.ChainEpoch(-21) + var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, } @@ -110,6 +113,7 @@ func init() { UpgradeOhSnapHeight = getUpgradeHeight("LOTUS_OHSNAP_HEIGHT", UpgradeOhSnapHeight) UpgradeSkyrHeight = getUpgradeHeight("LOTUS_SKYR_HEIGHT", UpgradeSkyrHeight) UpgradeSharkHeight = getUpgradeHeight("LOTUS_SHARK_HEIGHT", UpgradeSharkHeight) + UpgradeHyggeHeight = getUpgradeHeight("LOTUS_HYGGE_HEIGHT", UpgradeHyggeHeight) BuildType |= Build2k @@ -130,4 +134,8 @@ const InteractivePoRepConfidence = 6 const BootstrapPeerThreshold = 1 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 31415926 + var WhitelistedBlock = cid.Undef diff --git a/build/params_butterfly.go b/build/params_butterfly.go index 6f0a64598..7cd02bce9 100644 --- a/build/params_butterfly.go +++ b/build/params_butterfly.go @@ -23,6 +23,7 @@ const GenesisNetworkVersion = network.Version16 var NetworkBundle = "butterflynet" var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = false const BootstrappersFile = "butterflynet.pi" const GenesisFile = "butterflynet.car" @@ -49,7 +50,8 @@ const UpgradeHyperdriveHeight = -16 const UpgradeChocolateHeight = -17 const UpgradeOhSnapHeight = -18 const UpgradeSkyrHeight = -19 -const UpgradeSharkHeight = abi.ChainEpoch(600) +const UpgradeSharkHeight = abi.ChainEpoch(-20) +const UpgradeHyggeHeight = abi.ChainEpoch(600) var SupportedProofTypes = []abi.RegisteredSealProof{ abi.RegisteredSealProof_StackedDrg512MiBV1, @@ -80,4 +82,8 @@ const PropagationDelaySecs = uint64(6) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 2 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 3141592 + var WhitelistedBlock = cid.Undef diff --git a/build/params_calibnet.go b/build/params_calibnet.go index f1aacc506..0661bb839 100644 --- a/build/params_calibnet.go +++ b/build/params_calibnet.go @@ -4,6 +4,7 @@ package build import ( + "math" "os" "strconv" @@ -26,6 +27,7 @@ const GenesisNetworkVersion = network.Version0 var NetworkBundle = "calibrationnet" var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = false const BootstrappersFile = "calibnet.pi" const GenesisFile = "calibnet.car" @@ -69,6 +71,8 @@ const UpgradeSkyrHeight = 510 const UpgradeSharkHeight = 16800 // 6 days after genesis +const UpgradeHyggeHeight = math.MaxInt64 + var SupportedProofTypes = []abi.RegisteredSealProof{ abi.RegisteredSealProof_StackedDrg32GiBV1, abi.RegisteredSealProof_StackedDrg64GiBV1, @@ -113,4 +117,8 @@ var PropagationDelaySecs = uint64(10) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 4 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 314159 + var WhitelistedBlock = cid.Undef diff --git a/build/params_interop.go b/build/params_interop.go index dbc619e1b..4d94de049 100644 --- a/build/params_interop.go +++ b/build/params_interop.go @@ -20,6 +20,7 @@ import ( var NetworkBundle = "caterpillarnet" var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = false const BootstrappersFile = "interopnet.pi" const GenesisFile = "interopnet.car" @@ -49,7 +50,9 @@ var UpgradeChocolateHeight = abi.ChainEpoch(-17) var UpgradeOhSnapHeight = abi.ChainEpoch(-18) var UpgradeSkyrHeight = abi.ChainEpoch(-19) -const UpgradeSharkHeight = abi.ChainEpoch(99999999999999) +const UpgradeSharkHeight = abi.ChainEpoch(-20) + +const UpgradeHyggeHeight = abi.ChainEpoch(99999999999999) var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, @@ -104,6 +107,7 @@ func init() { UpgradeOhSnapHeight = getUpgradeHeight("LOTUS_OHSNAP_HEIGHT", UpgradeOhSnapHeight) UpgradeSkyrHeight = getUpgradeHeight("LOTUS_SKYR_HEIGHT", UpgradeSkyrHeight) UpgradeSharkHeight = getUpgradeHeight("LOTUS_SHARK_HEIGHT", UpgradeSharkHeight) + UpgradeHyggeHeight = getUpgradeHeight("LOTUS_HYGGE_HEIGHT", UpgradeHyggeHeight) BuildType |= BuildInteropnet SetAddressNetwork(address.Testnet) @@ -118,4 +122,9 @@ const PropagationDelaySecs = uint64(6) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 2 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +// TODO same as butterfly for now, as we didn't submit an assignment for interopnet. +const Eip155ChainId = 3141592 + var WhitelistedBlock = cid.Undef diff --git a/build/params_mainnet.go b/build/params_mainnet.go index 296793131..e29fa4567 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -25,6 +25,9 @@ var NetworkBundle = "mainnet" // NOTE: DO NOT change this unless you REALLY know what you're doing. This is consensus critical. var BundleOverrides map[actorstypes.Version]string +// NOTE: DO NOT change this unless you REALLY know what you're doing. This is consensus critical. +const ActorDebugging = false + const GenesisNetworkVersion = network.Version0 const BootstrappersFile = "mainnet.pi" @@ -56,6 +59,7 @@ const UpgradePersianHeight = UpgradeCalicoHeight + (builtin2.EpochsInHour * 60) const UpgradeOrangeHeight = 336458 // 2020-12-22T02:00:00Z +// var because of wdpost_test.go var UpgradeClausHeight = abi.ChainEpoch(343200) // 2021-03-04T00:00:30Z @@ -80,7 +84,10 @@ const UpgradeOhSnapHeight = 1594680 const UpgradeSkyrHeight = 1960320 // 2022-11-30T14:00:00Z -var UpgradeSharkHeight = abi.ChainEpoch(2383680) +const UpgradeSharkHeight = 2383680 + +// ?????????????? +var UpgradeHyggeHeight = abi.ChainEpoch(math.MaxInt64) var SupportedProofTypes = []abi.RegisteredSealProof{ abi.RegisteredSealProof_StackedDrg32GiBV1, @@ -95,8 +102,8 @@ func init() { SetAddressNetwork(address.Mainnet) } - if os.Getenv("LOTUS_DISABLE_SHARK") == "1" { - UpgradeSharkHeight = math.MaxInt64 + if os.Getenv("LOTUS_DISABLE_HYGGE") == "1" { + UpgradeHyggeHeight = math.MaxInt64 } // NOTE: DO NOT change this unless you REALLY know what you're doing. This is not consensus critical, however, @@ -124,5 +131,9 @@ const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 4 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 314 + // we skip checks on message validity in this block to sidestep the zero-bls signature var WhitelistedBlock = MustParseCid("bafy2bzaceapyg2uyzk7vueh3xccxkuwbz3nxewjyguoxvhx77malc2lzn2ybi") diff --git a/build/params_testground.go b/build/params_testground.go index dcdee888d..17ea5a59b 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -42,9 +42,6 @@ var ( AllowableClockDriftSecs = uint64(1) - Finality = policy.ChainFinality - ForkLengthThreshold = Finality - SlashablePowerDelay = 20 InteractivePoRepConfidence = 6 @@ -108,6 +105,7 @@ var ( UpgradeOhSnapHeight abi.ChainEpoch = -17 UpgradeSkyrHeight abi.ChainEpoch = -18 UpgradeSharkHeight abi.ChainEpoch = -19 + UpgradeHyggeHeight abi.ChainEpoch = -20 DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, @@ -116,6 +114,7 @@ var ( GenesisNetworkVersion = network.Version0 NetworkBundle = "devnet" BundleOverrides map[actorstypes.Version]string + ActorDebugging = true NewestNetworkVersion = network.Version16 ActorUpgradeNetworkVersion = network.Version16 @@ -128,4 +127,11 @@ var ( GenesisFile = "" ) +const Finality = policy.ChainFinality +const ForkLengthThreshold = Finality + const BootstrapPeerThreshold = 1 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 31415926 diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go index 7811aab6c..116ed35d0 100644 --- a/chain/actors/builtin/builtin.go +++ b/chain/actors/builtin/builtin.go @@ -274,6 +274,33 @@ func IsPaymentChannelActor(c cid.Cid) bool { return false } +func IsPlaceholderActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.PlaceholderKey + } + + return false +} + +func IsEvmActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EvmKey + } + + return false +} + +func IsEthAccountActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EthAccountKey + } + + return false +} + func makeAddress(addr string) address.Address { ret, err := address.NewFromString(addr) if err != nil { diff --git a/chain/actors/builtin/builtin.go.template b/chain/actors/builtin/builtin.go.template index 766279451..977217b1a 100644 --- a/chain/actors/builtin/builtin.go.template +++ b/chain/actors/builtin/builtin.go.template @@ -153,6 +153,33 @@ func IsPaymentChannelActor(c cid.Cid) bool { return false } +func IsPlaceholderActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.PlaceholderKey + } + + return false +} + +func IsEvmActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EvmKey + } + + return false +} + +func IsEthAccountActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EthAccountKey + } + + return false +} + func makeAddress(addr string) address.Address { ret, err := address.NewFromString(addr) if err != nil { diff --git a/chain/actors/builtin/registry.go b/chain/actors/builtin/registry.go index ec487275b..4ce48098d 100644 --- a/chain/actors/builtin/registry.go +++ b/chain/actors/builtin/registry.go @@ -13,11 +13,15 @@ import ( account10 "github.com/filecoin-project/go-state-types/builtin/v10/account" cron10 "github.com/filecoin-project/go-state-types/builtin/v10/cron" datacap10 "github.com/filecoin-project/go-state-types/builtin/v10/datacap" + eam10 "github.com/filecoin-project/go-state-types/builtin/v10/eam" + ethaccount10 "github.com/filecoin-project/go-state-types/builtin/v10/ethaccount" + evm10 "github.com/filecoin-project/go-state-types/builtin/v10/evm" _init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" market10 "github.com/filecoin-project/go-state-types/builtin/v10/market" miner10 "github.com/filecoin-project/go-state-types/builtin/v10/miner" multisig10 "github.com/filecoin-project/go-state-types/builtin/v10/multisig" paych10 "github.com/filecoin-project/go-state-types/builtin/v10/paych" + placeholder10 "github.com/filecoin-project/go-state-types/builtin/v10/placeholder" power10 "github.com/filecoin-project/go-state-types/builtin/v10/power" reward10 "github.com/filecoin-project/go-state-types/builtin/v10/reward" system10 "github.com/filecoin-project/go-state-types/builtin/v10/system" @@ -265,6 +269,7 @@ func MakeRegistry(av actorstypes.Version) []RegistryEntry { methods: datacap9.Methods, state: new(datacap9.State), }) + } } @@ -343,6 +348,32 @@ func MakeRegistry(av actorstypes.Version) []RegistryEntry { methods: datacap10.Methods, state: new(datacap10.State), }) + + case manifest.EvmKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: evm10.Methods, + state: new(evm10.State), + }) + case manifest.EamKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: eam10.Methods, + state: nil, + }) + case manifest.PlaceholderKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: placeholder10.Methods, + state: nil, + }) + case manifest.EthAccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: ethaccount10.Methods, + state: nil, + }) + } } diff --git a/chain/actors/builtin/registry.go.template b/chain/actors/builtin/registry.go.template index b38cd41c6..a63b00917 100644 --- a/chain/actors/builtin/registry.go.template +++ b/chain/actors/builtin/registry.go.template @@ -25,6 +25,12 @@ import ( {{if (ge . 9)}} datacap{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/datacap" {{end}} + {{if (ge . 10)}} + evm{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/evm" + eam{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/eam" + placeholder{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/placeholder" + ethaccount{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/ethaccount" + {{end}} {{end}} "github.com/filecoin-project/go-state-types/cbor" rtt "github.com/filecoin-project/go-state-types/rt" @@ -174,6 +180,32 @@ func MakeRegistry(av actorstypes.Version) []RegistryEntry { methods: datacap{{.}}.Methods, state: new(datacap{{.}}.State), }){{end}} + {{if (ge . 10)}} + case manifest.EvmKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: evm{{.}}.Methods, + state: new(evm{{.}}.State), + }) + case manifest.EamKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: eam{{.}}.Methods, + state: nil, + }) + case manifest.PlaceholderKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: placeholder{{.}}.Methods, + state: nil, + }) + case manifest.EthAccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: ethaccount{{.}}.Methods, + state: nil, + }) + {{end}} } } {{end}} diff --git a/chain/consensus/common.go b/chain/consensus/common.go index 4dc70f0d4..1d9fb3646 100644 --- a/chain/consensus/common.go +++ b/chain/consensus/common.go @@ -10,11 +10,13 @@ import ( cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/multiformats/go-varint" cbg "github.com/whyrusleeping/cbor-gen" "go.opencensus.io/stats" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + builtintypes "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/network" blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -29,7 +31,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/lib/async" - "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/lotus/metrics" ) @@ -130,7 +131,39 @@ func CommonBlkChecks(ctx context.Context, sm *stmgr.StateManager, cs *store.Chai } } -// Check the validity of the messages included in a block. +func IsValidForSending(nv network.Version, act *types.Actor) bool { + // Before nv18 (Hygge), we only supported built-in account actors as senders. + // + // Note: this gate is probably superfluous, since: + // 1. Placeholder actors cannot be created before nv18. + // 2. EthAccount actors cannot be created before nv18. + // 3. Delegated addresses cannot be created before nv18. + // + // But it's a safeguard. + // + // Note 2: ad-hoc checks for network versions like this across the codebase + // will be problematic with networks with diverging version lineages + // (e.g. Hyperspace). We need to revisit this strategy entirely. + if nv < network.Version18 { + return builtin.IsAccountActor(act.Code) + } + + // After nv18, we also support other kinds of senders. + if builtin.IsAccountActor(act.Code) || builtin.IsEthAccountActor(act.Code) { + return true + } + + // Allow placeholder actors with a delegated address and nonce 0 to send a message. + // These will be converted to an EthAccount actor on first send. + if !builtin.IsPlaceholderActor(act.Code) || act.Nonce != 0 || act.Address == nil || act.Address.Protocol() != address.Delegated { + return false + } + + // Only allow such actors to send if their delegated address is in the EAM's namespace. + id, _, err := varint.FromUvarint(act.Address.Payload()) + return err == nil && id == builtintypes.EthereumAddressManagerActorID +} + func checkBlockMessages(ctx context.Context, sm *stmgr.StateManager, cs *store.ChainStore, b *types.FullBlock, baseTs *types.TipSet) error { { var sigCids []cid.Cid // this is what we get for people not wanting the marshalcbor method on the cid type @@ -202,7 +235,7 @@ func checkBlockMessages(ctx context.Context, sm *stmgr.StateManager, cs *store.C return xerrors.Errorf("failed to get actor: %w", err) } - if !builtin.IsAccountActor(act.Code) { + if !IsValidForSending(nv, act) { return xerrors.New("Sender must be an account actor") } nonces[sender] = act.Nonce @@ -239,25 +272,23 @@ func checkBlockMessages(ctx context.Context, sm *stmgr.StateManager, cs *store.C smArr := blockadt.MakeEmptyArray(tmpstore) for i, m := range b.SecpkMessages { - if sm.GetNetworkVersion(ctx, b.Header.Height) >= network.Version14 { - if m.Signature.Type != crypto.SigTypeSecp256k1 { - return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err) - } + if nv >= network.Version14 && !IsValidSecpkSigType(nv, m.Signature.Type) { + return xerrors.Errorf("block had invalid signed message at index %d: %w", i, err) } if err := checkMsg(m); err != nil { return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err) } - // `From` being an account actor is only validated inside the `vm.ResolveToKeyAddr` call - // in `StateManager.ResolveToKeyAddress` here (and not in `checkMsg`). - kaddr, err := sm.ResolveToKeyAddress(ctx, m.Message.From, baseTs) + // `From` being an account actor is only validated inside the `vm.ResolveToDeterministicAddr` call + // in `StateManager.ResolveToDeterministicAddress` here (and not in `checkMsg`). + kaddr, err := sm.ResolveToDeterministicAddress(ctx, m.Message.From, baseTs) if err != nil { return xerrors.Errorf("failed to resolve key addr: %w", err) } - if err := sigs.Verify(&m.Signature, kaddr, m.Message.Cid().Bytes()); err != nil { - return xerrors.Errorf("secpk message %s has invalid signature: %w", m.Cid(), err) + if err := AuthenticateMessage(m, kaddr); err != nil { + return xerrors.Errorf("failed to validate signature: %w", err) } c, err := store.PutMessage(ctx, tmpbs, m) @@ -330,6 +361,7 @@ func CreateBlockHeader(ctx context.Context, sm *stmgr.StateManager, pts *types.T var blsMsgCids, secpkMsgCids []cid.Cid var blsSigs []crypto.Signature + nv := sm.GetNetworkVersion(ctx, bt.Epoch) for _, msg := range bt.Messages { if msg.Signature.Type == crypto.SigTypeBLS { blsSigs = append(blsSigs, msg.Signature) @@ -341,7 +373,7 @@ func CreateBlockHeader(ctx context.Context, sm *stmgr.StateManager, pts *types.T } blsMsgCids = append(blsMsgCids, c) - } else if msg.Signature.Type == crypto.SigTypeSecp256k1 { + } else if IsValidSecpkSigType(nv, msg.Signature.Type) { c, err := sm.ChainStore().PutMessage(ctx, msg) if err != nil { return nil, nil, nil, err diff --git a/chain/consensus/compute_state.go b/chain/consensus/compute_state.go index 0785f93f0..d3d9e382d 100644 --- a/chain/consensus/compute_state.go +++ b/chain/consensus/compute_state.go @@ -94,6 +94,7 @@ func (t *TipSetExecutor) ApplyBlocks(ctx context.Context, vmopt := &vm.VMOpts{ StateBase: base, Epoch: e, + Timestamp: ts.MinTimestamp(), Rand: r, Bstore: sm.ChainStore().StateBlockstore(), Actors: NewActorRegistry(), diff --git a/chain/consensus/filcns/upgrades.go b/chain/consensus/filcns/upgrades.go index 54e507277..5a268c8a6 100644 --- a/chain/consensus/filcns/upgrades.go +++ b/chain/consensus/filcns/upgrades.go @@ -18,8 +18,10 @@ import ( "github.com/filecoin-project/go-state-types/abi" actorstypes "github.com/filecoin-project/go-state-types/actors" "github.com/filecoin-project/go-state-types/big" + nv18 "github.com/filecoin-project/go-state-types/builtin/v10/migration" nv17 "github.com/filecoin-project/go-state-types/builtin/v9/migration" "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/migration" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/go-state-types/rt" gstStore "github.com/filecoin-project/go-state-types/store" @@ -232,8 +234,18 @@ func DefaultUpgradeSchedule() stmgr.UpgradeSchedule { StopWithin: 5, }}, Expensive: true, + }, { + Height: build.UpgradeHyggeHeight, + Network: network.Version18, + Migration: UpgradeActorsV10, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV10, + StartWithin: 180, + DontStartWithin: 60, + StopWithin: 5, + }}, + Expensive: true, }, - // TODO v10 upgrade } for _, u := range updates { @@ -1580,11 +1592,110 @@ func upgradeActorsV9Common( func UpgradeActorsV10(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } - // TODO migration - // - the init actor state to include the (empty) installed actors field - // - state tree migration to v5 - return cid.Undef, fmt.Errorf("IMPLEMENTME: v10 migration") + config := migration.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV10Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v10 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV10(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + + lbts, lbRoot, err := stmgr.GetLookbackTipSetForRound(ctx, sm, ts, epoch) + if err != nil { + return xerrors.Errorf("error getting lookback ts for premigration: %w", err) + } + + config := migration.Config{ + MaxWorkers: uint(workerCount), + ProgressLogPeriod: time.Minute * 5, + } + + _, err = upgradeActorsV10Common(ctx, sm, cache, lbRoot, epoch, lbts, config) + return err +} + +func upgradeActorsV10Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config migration.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // ensure that the manifest is loaded in the blockstore + if err := bundle.LoadBundles(ctx, sm.ChainStore().StateBlockstore(), actorstypes.Version10); err != nil { + return cid.Undef, xerrors.Errorf("failed to load manifest bundle: %w", err) + } + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion4 { + return cid.Undef, xerrors.Errorf( + "expected state root version 4 for actors v9 upgrade, got %d", + stateRoot.Version, + ) + } + + manifest, ok := actors.GetManifest(actorstypes.Version10) + if !ok { + return cid.Undef, xerrors.Errorf("no manifest CID for v9 upgrade") + } + + // Perform the migration + newHamtRoot, err := nv18.MigrateStateTree(ctx, store, manifest, stateRoot.Actors, epoch, config, + migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v10: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion5, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil } // Example upgrade function if upgrade requires only code changes diff --git a/chain/consensus/signatures.go b/chain/consensus/signatures.go new file mode 100644 index 000000000..d89ae8337 --- /dev/null +++ b/chain/consensus/signatures.go @@ -0,0 +1,62 @@ +package consensus + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/lib/sigs" +) + +// AuthenticateMessage authenticates the message by verifying that the supplied +// SignedMessage was signed by the indicated Address, computing the correct +// signature payload depending on the signature type. The supplied Address type +// must be recognized by the registered verifier for the signature type. +func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error { + var digest []byte + + typ := msg.Signature.Type + switch typ { + case crypto.SigTypeDelegated: + txArgs, err := ethtypes.EthTxArgsFromMessage(&msg.Message) + if err != nil { + return xerrors.Errorf("failed to reconstruct eth transaction: %w", err) + } + roundTripMsg, err := txArgs.ToUnsignedMessage(msg.Message.From) + if err != nil { + return xerrors.Errorf("failed to reconstruct filecoin msg: %w", err) + } + + if !msg.Message.Equals(roundTripMsg) { + return xerrors.New("ethereum tx failed to roundtrip") + } + + rlpEncodedMsg, err := txArgs.ToRlpUnsignedMsg() + if err != nil { + return xerrors.Errorf("failed to repack eth rlp message: %w", err) + } + digest = rlpEncodedMsg + default: + digest = msg.Message.Cid().Bytes() + } + + if err := sigs.Verify(&msg.Signature, signer, digest); err != nil { + return xerrors.Errorf("message %s has invalid signature (type %d): %w", msg.Cid(), typ, err) + } + return nil +} + +// IsValidSecpkSigType checks that a signature type is valid for the network +// version, for a "secpk" message. +func IsValidSecpkSigType(nv network.Version, typ crypto.SigType) bool { + switch { + case nv < network.Version18: + return typ == crypto.SigTypeSecp256k1 + default: + return typ == crypto.SigTypeSecp256k1 || typ == crypto.SigTypeDelegated + } +} diff --git a/chain/ethhashlookup/eth_transaction_hash_lookup.go b/chain/ethhashlookup/eth_transaction_hash_lookup.go new file mode 100644 index 000000000..85cb84db1 --- /dev/null +++ b/chain/ethhashlookup/eth_transaction_hash_lookup.go @@ -0,0 +1,163 @@ +package ethhashlookup + +import ( + "database/sql" + "errors" + "strconv" + + "github.com/ipfs/go-cid" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +var ErrNotFound = errors.New("not found") + +var pragmas = []string{ + "PRAGMA synchronous = normal", + "PRAGMA temp_store = memory", + "PRAGMA mmap_size = 30000000000", + "PRAGMA page_size = 32768", + "PRAGMA auto_vacuum = NONE", + "PRAGMA automatic_index = OFF", + "PRAGMA journal_mode = WAL", + "PRAGMA read_uncommitted = ON", +} + +var ddls = []string{ + `CREATE TABLE IF NOT EXISTS eth_tx_hashes ( + hash TEXT PRIMARY KEY NOT NULL, + cid TEXT NOT NULL UNIQUE, + insertion_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + )`, + + `CREATE INDEX IF NOT EXISTS insertion_time_index ON eth_tx_hashes (insertion_time)`, + + // metadata containing version of schema + `CREATE TABLE IF NOT EXISTS _meta ( + version UINT64 NOT NULL UNIQUE + )`, + + // version 1. + `INSERT OR IGNORE INTO _meta (version) VALUES (1)`, +} + +const schemaVersion = 1 + +const ( + insertTxHash = `INSERT INTO eth_tx_hashes + (hash, cid) + VALUES(?, ?) + ON CONFLICT (hash) DO UPDATE SET insertion_time = CURRENT_TIMESTAMP` +) + +type EthTxHashLookup struct { + db *sql.DB +} + +func (ei *EthTxHashLookup) UpsertHash(txHash ethtypes.EthHash, c cid.Cid) error { + hashEntry, err := ei.db.Prepare(insertTxHash) + if err != nil { + return xerrors.Errorf("prepare insert event: %w", err) + } + + _, err = hashEntry.Exec(txHash.String(), c.String()) + return err +} + +func (ei *EthTxHashLookup) GetCidFromHash(txHash ethtypes.EthHash) (cid.Cid, error) { + q, err := ei.db.Query("SELECT cid FROM eth_tx_hashes WHERE hash = :hash;", sql.Named("hash", txHash.String())) + if err != nil { + return cid.Undef, err + } + + var c string + if !q.Next() { + return cid.Undef, ErrNotFound + } + err = q.Scan(&c) + if err != nil { + return cid.Undef, err + } + return cid.Decode(c) +} + +func (ei *EthTxHashLookup) GetHashFromCid(c cid.Cid) (ethtypes.EthHash, error) { + q, err := ei.db.Query("SELECT hash FROM eth_tx_hashes WHERE cid = :cid;", sql.Named("cid", c.String())) + if err != nil { + return ethtypes.EmptyEthHash, err + } + + var hashString string + if !q.Next() { + return ethtypes.EmptyEthHash, ErrNotFound + } + err = q.Scan(&hashString) + if err != nil { + return ethtypes.EmptyEthHash, err + } + return ethtypes.ParseEthHash(hashString) +} + +func (ei *EthTxHashLookup) DeleteEntriesOlderThan(days int) (int64, error) { + res, err := ei.db.Exec("DELETE FROM eth_tx_hashes WHERE insertion_time < datetime('now', ?);", "-"+strconv.Itoa(days)+" day") + if err != nil { + return 0, err + } + + return res.RowsAffected() +} + +func NewTransactionHashLookup(path string) (*EthTxHashLookup, error) { + db, err := sql.Open("sqlite3", path+"?mode=rwc") + if err != nil { + return nil, xerrors.Errorf("open sqlite3 database: %w", err) + } + + for _, pragma := range pragmas { + if _, err := db.Exec(pragma); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec pragma %q: %w", pragma, err) + } + } + + q, err := db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name='_meta';") + if err == sql.ErrNoRows || !q.Next() { + // empty database, create the schema + for _, ddl := range ddls { + if _, err := db.Exec(ddl); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec ddl %q: %w", ddl, err) + } + } + } else if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("looking for _meta table: %w", err) + } else { + // Ensure we don't open a database from a different schema version + + row := db.QueryRow("SELECT max(version) FROM _meta") + var version int + err := row.Scan(&version) + if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: no version found") + } + if version != schemaVersion { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: got %d, expected %d", version, schemaVersion) + } + } + + return &EthTxHashLookup{ + db: db, + }, nil +} + +func (ei *EthTxHashLookup) Close() error { + if ei.db == nil { + return nil + } + return ei.db.Close() +} diff --git a/chain/events/filter/event.go b/chain/events/filter/event.go new file mode 100644 index 000000000..7d59ad8bd --- /dev/null +++ b/chain/events/filter/event.go @@ -0,0 +1,483 @@ +package filter + +import ( + "bytes" + "context" + "math" + "sync" + "time" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + amt4 "github.com/filecoin-project/go-amt-ipld/v4" + "github.com/filecoin-project/go-state-types/abi" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + cstore "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +const indexed uint8 = 0x01 + +type EventFilter struct { + id types.FilterID + minHeight abi.ChainEpoch // minimum epoch to apply filter or -1 if no minimum + maxHeight abi.ChainEpoch // maximum epoch to apply filter or -1 if no maximum + tipsetCid cid.Cid + addresses []address.Address // list of f4 actor addresses that are extpected to emit the event + keys map[string][][]byte // map of key names to a list of alternate values that may match + maxResults int // maximum number of results to collect, 0 is unlimited + + mu sync.Mutex + collected []*CollectedEvent + lastTaken time.Time + ch chan<- interface{} +} + +var _ Filter = (*EventFilter)(nil) + +type CollectedEvent struct { + Entries []types.EventEntry + EmitterAddr address.Address // f4 address of emitter + EventIdx int // index of the event within the list of emitted events + Reverted bool + Height abi.ChainEpoch + TipSetKey types.TipSetKey // tipset that contained the message + MsgIdx int // index of the message in the tipset + MsgCid cid.Cid // cid of message that produced event +} + +func (f *EventFilter) ID() types.FilterID { + return f.id +} + +func (f *EventFilter) SetSubChannel(ch chan<- interface{}) { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = ch + f.collected = nil +} + +func (f *EventFilter) ClearSubChannel() { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = nil +} + +func (f *EventFilter) CollectEvents(ctx context.Context, te *TipSetEvents, revert bool, resolver func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool)) error { + if !f.matchTipset(te) { + return nil + } + + // cache of lookups between actor id and f4 address + addressLookups := make(map[abi.ActorID]address.Address) + + ems, err := te.messages(ctx) + if err != nil { + return xerrors.Errorf("load executed messages: %w", err) + } + for msgIdx, em := range ems { + for evIdx, ev := range em.Events() { + // lookup address corresponding to the actor id + addr, found := addressLookups[ev.Emitter] + if !found { + var ok bool + addr, ok = resolver(ctx, ev.Emitter, te.rctTs) + if !ok { + // not an address we will be able to match against + continue + } + addressLookups[ev.Emitter] = addr + } + + if !f.matchAddress(addr) { + continue + } + if !f.matchKeys(ev.Entries) { + continue + } + + decodedEntries := make([]types.EventEntry, len(ev.Entries)) + for i, entry := range ev.Entries { + decodedEntries[i] = types.EventEntry{ + Flags: entry.Flags, + Key: entry.Key, + Value: decodeLogBytes(entry.Value), + } + } + + // event matches filter, so record it + cev := &CollectedEvent{ + Entries: decodedEntries, + EmitterAddr: addr, + EventIdx: evIdx, + Reverted: revert, + Height: te.msgTs.Height(), + TipSetKey: te.msgTs.Key(), + MsgCid: em.Message().Cid(), + MsgIdx: msgIdx, + } + + f.mu.Lock() + // if we have a subscription channel then push event to it + if f.ch != nil { + f.ch <- cev + f.mu.Unlock() + continue + } + + if f.maxResults > 0 && len(f.collected) == f.maxResults { + copy(f.collected, f.collected[1:]) + f.collected = f.collected[:len(f.collected)-1] + } + f.collected = append(f.collected, cev) + f.mu.Unlock() + } + } + + return nil +} + +func (f *EventFilter) setCollectedEvents(ces []*CollectedEvent) { + f.mu.Lock() + f.collected = ces + f.mu.Unlock() +} + +func (f *EventFilter) TakeCollectedEvents(ctx context.Context) []*CollectedEvent { + f.mu.Lock() + collected := f.collected + f.collected = nil + f.lastTaken = time.Now().UTC() + f.mu.Unlock() + + return collected +} + +func (f *EventFilter) LastTaken() time.Time { + f.mu.Lock() + defer f.mu.Unlock() + return f.lastTaken +} + +// matchTipset reports whether this filter matches the given tipset +func (f *EventFilter) matchTipset(te *TipSetEvents) bool { + if f.tipsetCid != cid.Undef { + tsCid, err := te.Cid() + if err != nil { + return false + } + return f.tipsetCid.Equals(tsCid) + } + + if f.minHeight >= 0 && f.minHeight > te.Height() { + return false + } + if f.maxHeight >= 0 && f.maxHeight < te.Height() { + return false + } + return true +} + +func (f *EventFilter) matchAddress(o address.Address) bool { + if len(f.addresses) == 0 { + return true + } + + // Assume short lists of addresses + // TODO: binary search for longer lists or restrict list length + for _, a := range f.addresses { + if a == o { + return true + } + } + return false +} + +func (f *EventFilter) matchKeys(ees []types.EventEntry) bool { + if len(f.keys) == 0 { + return true + } + // TODO: optimize this naive algorithm + // tracked in https://github.com/filecoin-project/lotus/issues/9987 + + // Note keys names may be repeated so we may have multiple opportunities to match + + matched := map[string]bool{} + for _, ee := range ees { + // Skip an entry that is not indexable + if ee.Flags&indexed != indexed { + continue + } + + keyname := ee.Key + + // skip if we have already matched this key + if matched[keyname] { + continue + } + + wantlist, ok := f.keys[keyname] + if !ok { + continue + } + + for _, w := range wantlist { + if bytes.Equal(w, ee.Value) { + matched[keyname] = true + break + } + } + + if len(matched) == len(f.keys) { + // all keys have been matched + return true + } + + } + + return false +} + +type TipSetEvents struct { + rctTs *types.TipSet // rctTs is the tipset containing the receipts of executed messages + msgTs *types.TipSet // msgTs is the tipset containing the messages that have been executed + + load func(ctx context.Context, msgTs, rctTs *types.TipSet) ([]executedMessage, error) + + once sync.Once // for lazy population of ems + ems []executedMessage + err error +} + +func (te *TipSetEvents) Height() abi.ChainEpoch { + return te.msgTs.Height() +} + +func (te *TipSetEvents) Cid() (cid.Cid, error) { + return te.msgTs.Key().Cid() +} + +func (te *TipSetEvents) messages(ctx context.Context) ([]executedMessage, error) { + te.once.Do(func() { + // populate executed message list + ems, err := te.load(ctx, te.msgTs, te.rctTs) + if err != nil { + te.err = err + return + } + te.ems = ems + }) + return te.ems, te.err +} + +type executedMessage struct { + msg types.ChainMsg + rct *types.MessageReceipt + // events extracted from receipt + evs []*types.Event +} + +func (e *executedMessage) Message() types.ChainMsg { + return e.msg +} + +func (e *executedMessage) Receipt() *types.MessageReceipt { + return e.rct +} + +func (e *executedMessage) Events() []*types.Event { + return e.evs +} + +type EventFilterManager struct { + ChainStore *cstore.ChainStore + AddressResolver func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) + MaxFilterResults int + EventIndex *EventIndex + + mu sync.Mutex // guards mutations to filters + filters map[types.FilterID]*EventFilter + currentHeight abi.ChainEpoch +} + +func (m *EventFilterManager) Apply(ctx context.Context, from, to *types.TipSet) error { + m.mu.Lock() + defer m.mu.Unlock() + m.currentHeight = to.Height() + + if len(m.filters) == 0 && m.EventIndex == nil { + return nil + } + + tse := &TipSetEvents{ + msgTs: from, + rctTs: to, + load: m.loadExecutedMessages, + } + + if m.EventIndex != nil { + if err := m.EventIndex.CollectEvents(ctx, tse, false, m.AddressResolver); err != nil { + return err + } + } + + // TODO: could run this loop in parallel with errgroup if there are many filters + for _, f := range m.filters { + if err := f.CollectEvents(ctx, tse, false, m.AddressResolver); err != nil { + return err + } + } + + return nil +} + +func (m *EventFilterManager) Revert(ctx context.Context, from, to *types.TipSet) error { + m.mu.Lock() + defer m.mu.Unlock() + m.currentHeight = to.Height() + + if len(m.filters) == 0 && m.EventIndex == nil { + return nil + } + + tse := &TipSetEvents{ + msgTs: to, + rctTs: from, + load: m.loadExecutedMessages, + } + + if m.EventIndex != nil { + if err := m.EventIndex.CollectEvents(ctx, tse, true, m.AddressResolver); err != nil { + return err + } + } + + // TODO: could run this loop in parallel with errgroup if there are many filters + for _, f := range m.filters { + if err := f.CollectEvents(ctx, tse, true, m.AddressResolver); err != nil { + return err + } + } + + return nil +} + +func (m *EventFilterManager) Install(ctx context.Context, minHeight, maxHeight abi.ChainEpoch, tipsetCid cid.Cid, addresses []address.Address, keys map[string][][]byte) (*EventFilter, error) { + m.mu.Lock() + currentHeight := m.currentHeight + m.mu.Unlock() + + if m.EventIndex == nil && minHeight != -1 && minHeight < currentHeight { + return nil, xerrors.Errorf("historic event index disabled") + } + + id, err := newFilterID() + if err != nil { + return nil, xerrors.Errorf("new filter id: %w", err) + } + + f := &EventFilter{ + id: id, + minHeight: minHeight, + maxHeight: maxHeight, + tipsetCid: tipsetCid, + addresses: addresses, + keys: keys, + maxResults: m.MaxFilterResults, + } + + if m.EventIndex != nil && minHeight != -1 && minHeight < currentHeight { + // Filter needs historic events + if err := m.EventIndex.PrefillFilter(ctx, f); err != nil { + return nil, err + } + } + + m.mu.Lock() + if m.filters == nil { + m.filters = make(map[types.FilterID]*EventFilter) + } + m.filters[id] = f + m.mu.Unlock() + + return f, nil +} + +func (m *EventFilterManager) Remove(ctx context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, found := m.filters[id]; !found { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} + +func (m *EventFilterManager) loadExecutedMessages(ctx context.Context, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + msgs, err := m.ChainStore.MessagesForTipset(ctx, msgTs) + if err != nil { + return nil, xerrors.Errorf("read messages: %w", err) + } + + st := m.ChainStore.ActorStore(ctx) + + arr, err := blockadt.AsArray(st, rctTs.Blocks()[0].ParentMessageReceipts) + if err != nil { + return nil, xerrors.Errorf("load receipts amt: %w", err) + } + + if uint64(len(msgs)) != arr.Length() { + return nil, xerrors.Errorf("mismatching message and receipt counts (%d msgs, %d rcts)", len(msgs), arr.Length()) + } + + ems := make([]executedMessage, len(msgs)) + + for i := 0; i < len(msgs); i++ { + ems[i].msg = msgs[i] + + var rct types.MessageReceipt + found, err := arr.Get(uint64(i), &rct) + if err != nil { + return nil, xerrors.Errorf("load receipt: %w", err) + } + if !found { + return nil, xerrors.Errorf("receipt %d not found", i) + } + ems[i].rct = &rct + + if rct.EventsRoot == nil { + continue + } + + evtArr, err := amt4.LoadAMT(ctx, st, *rct.EventsRoot, amt4.UseTreeBitWidth(types.EventAMTBitwidth)) + if err != nil { + return nil, xerrors.Errorf("load events amt: %w", err) + } + + ems[i].evs = make([]*types.Event, evtArr.Len()) + var evt types.Event + err = evtArr.ForEach(ctx, func(u uint64, deferred *cbg.Deferred) error { + if u > math.MaxInt { + return xerrors.Errorf("too many events") + } + if err := evt.UnmarshalCBOR(bytes.NewReader(deferred.Raw)); err != nil { + return err + } + + cpy := evt + ems[i].evs[int(u)] = &cpy //nolint:scopelint + return nil + }) + + if err != nil { + return nil, xerrors.Errorf("read events: %w", err) + } + + } + + return ems, nil +} diff --git a/chain/events/filter/event_test.go b/chain/events/filter/event_test.go new file mode 100644 index 000000000..80c4c7fc0 --- /dev/null +++ b/chain/events/filter/event_test.go @@ -0,0 +1,431 @@ +package filter + +import ( + "context" + pseudo "math/rand" + "testing" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/exitcode" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +func TestEventFilterCollectEvents(t *testing.T) { + rng := pseudo.New(pseudo.NewSource(299792458)) + a1 := randomF4Addr(t, rng) + a2 := randomF4Addr(t, rng) + + a1ID := abi.ActorID(1) + a2ID := abi.ActorID(2) + + addrMap := addressMap{} + addrMap.add(a1ID, a1) + addrMap.add(a2ID, a2) + + ev1 := fakeEvent( + a1ID, + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr1")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + st := newStore() + events := []*types.Event{ev1} + em := executedMessage{ + msg: fakeMessage(randomF4Addr(t, rng), randomF4Addr(t, rng)), + rct: fakeReceipt(t, rng, st, events), + evs: events, + } + + events14000 := buildTipSetEvents(t, rng, 14000, em) + cid14000, err := events14000.msgTs.Key().Cid() + require.NoError(t, err, "tipset cid") + + noCollectedEvents := []*CollectedEvent{} + oneCollectedEvent := []*CollectedEvent{ + { + Entries: ev1.Entries, + EmitterAddr: a1, + EventIdx: 0, + Reverted: false, + Height: 14000, + TipSetKey: events14000.msgTs.Key(), + MsgIdx: 0, + MsgCid: em.msg.Cid(), + }, + } + + testCases := []struct { + name string + filter *EventFilter + te *TipSetEvents + want []*CollectedEvent + }{ + { + name: "nomatch tipset min height", + filter: &EventFilter{ + minHeight: 14001, + maxHeight: -1, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch tipset max height", + filter: &EventFilter{ + minHeight: -1, + maxHeight: 13999, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match tipset min height", + filter: &EventFilter{ + minHeight: 14000, + maxHeight: -1, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match tipset cid", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + tipsetCid: cid14000, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a2}, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a1}, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry with alternate values", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry by missing value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry by missing key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "method": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match one entry with multiple keys", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry with one mismatching key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "approver": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one mismatching value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr2"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one unindexed key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "amount": { + []byte("2988181"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + } + + for _, tc := range testCases { + tc := tc // appease lint + t.Run(tc.name, func(t *testing.T) { + if err := tc.filter.CollectEvents(context.Background(), tc.te, false, addrMap.ResolveAddress); err != nil { + require.NoError(t, err, "collect events") + } + + coll := tc.filter.TakeCollectedEvents(context.Background()) + require.ElementsMatch(t, coll, tc.want) + }) + } +} + +type kv struct { + k string + v []byte +} + +func fakeEvent(emitter abi.ActorID, indexed []kv, unindexed []kv) *types.Event { + ev := &types.Event{ + Emitter: emitter, + } + + for _, in := range indexed { + ev.Entries = append(ev.Entries, types.EventEntry{ + Flags: 0x01, + Key: in.k, + Value: in.v, + }) + } + + for _, in := range unindexed { + ev.Entries = append(ev.Entries, types.EventEntry{ + Flags: 0x00, + Key: in.k, + Value: in.v, + }) + } + + return ev +} + +func randomF4Addr(tb testing.TB, rng *pseudo.Rand) address.Address { + tb.Helper() + addr, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, randomBytes(32, rng)) + require.NoError(tb, err) + + return addr +} + +func randomIDAddr(tb testing.TB, rng *pseudo.Rand) address.Address { + tb.Helper() + addr, err := address.NewIDAddress(uint64(rng.Int63())) + require.NoError(tb, err) + return addr +} + +func randomCid(tb testing.TB, rng *pseudo.Rand) cid.Cid { + tb.Helper() + cb := cid.V1Builder{Codec: cid.Raw, MhType: mh.IDENTITY} + c, err := cb.Sum(randomBytes(10, rng)) + require.NoError(tb, err) + return c +} + +func randomBytes(n int, rng *pseudo.Rand) []byte { + buf := make([]byte, n) + rng.Read(buf) + return buf +} + +func fakeMessage(to, from address.Address) *types.Message { + return &types.Message{ + To: to, + From: from, + Nonce: 197, + Method: 1, + Params: []byte("some random bytes"), + GasLimit: 126723, + GasPremium: types.NewInt(4), + GasFeeCap: types.NewInt(120), + } +} + +func fakeReceipt(tb testing.TB, rng *pseudo.Rand, st adt.Store, events []*types.Event) *types.MessageReceipt { + arr := blockadt.MakeEmptyArray(st) + for _, ev := range events { + err := arr.AppendContinuous(ev) + require.NoError(tb, err, "append event") + } + eventsRoot, err := arr.Root() + require.NoError(tb, err, "flush events amt") + + rec := types.NewMessageReceiptV1(exitcode.Ok, randomBytes(32, rng), rng.Int63(), &eventsRoot) + return &rec +} + +func fakeTipSet(tb testing.TB, rng *pseudo.Rand, h abi.ChainEpoch, parents []cid.Cid) *types.TipSet { + tb.Helper() + ts, err := types.NewTipSet([]*types.BlockHeader{ + { + Height: h, + Miner: randomIDAddr(tb, rng), + + Parents: parents, + + Ticket: &types.Ticket{VRFProof: []byte{byte(h % 2)}}, + + ParentStateRoot: randomCid(tb, rng), + Messages: randomCid(tb, rng), + ParentMessageReceipts: randomCid(tb, rng), + + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + }, + { + Height: h, + Miner: randomIDAddr(tb, rng), + + Parents: parents, + + Ticket: &types.Ticket{VRFProof: []byte{byte((h + 1) % 2)}}, + + ParentStateRoot: randomCid(tb, rng), + Messages: randomCid(tb, rng), + ParentMessageReceipts: randomCid(tb, rng), + + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + }, + }) + + require.NoError(tb, err) + + return ts +} + +func newStore() adt.Store { + ctx := context.Background() + bs := blockstore.NewMemorySync() + store := cbor.NewCborStore(bs) + return adt.WrapStore(ctx, store) +} + +func buildTipSetEvents(tb testing.TB, rng *pseudo.Rand, h abi.ChainEpoch, em executedMessage) *TipSetEvents { + tb.Helper() + + msgTs := fakeTipSet(tb, rng, h, []cid.Cid{}) + rctTs := fakeTipSet(tb, rng, h+1, msgTs.Cids()) + + return &TipSetEvents{ + msgTs: msgTs, + rctTs: rctTs, + load: func(ctx context.Context, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return []executedMessage{em}, nil + }, + } +} + +type addressMap map[abi.ActorID]address.Address + +func (a addressMap) add(actorID abi.ActorID, addr address.Address) { + a[actorID] = addr +} + +func (a addressMap) ResolveAddress(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { + ra, ok := a[emitter] + return ra, ok +} diff --git a/chain/events/filter/index.go b/chain/events/filter/index.go new file mode 100644 index 000000000..1920a91fe --- /dev/null +++ b/chain/events/filter/index.go @@ -0,0 +1,434 @@ +package filter + +import ( + "bytes" + "context" + "database/sql" + "errors" + "fmt" + "sort" + "strings" + + "github.com/ipfs/go-cid" + _ "github.com/mattn/go-sqlite3" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" +) + +var pragmas = []string{ + "PRAGMA synchronous = normal", + "PRAGMA temp_store = memory", + "PRAGMA mmap_size = 30000000000", + "PRAGMA page_size = 32768", + "PRAGMA auto_vacuum = NONE", + "PRAGMA automatic_index = OFF", + "PRAGMA journal_mode = WAL", + "PRAGMA read_uncommitted = ON", +} + +var ddls = []string{ + `CREATE TABLE IF NOT EXISTS event ( + id INTEGER PRIMARY KEY, + height INTEGER NOT NULL, + tipset_key BLOB NOT NULL, + tipset_key_cid BLOB NOT NULL, + emitter_addr BLOB NOT NULL, + event_index INTEGER NOT NULL, + message_cid BLOB NOT NULL, + message_index INTEGER NOT NULL, + reverted INTEGER NOT NULL + )`, + + `CREATE TABLE IF NOT EXISTS event_entry ( + event_id INTEGER, + indexed INTEGER NOT NULL, + flags BLOB NOT NULL, + key TEXT NOT NULL, + value BLOB NOT NULL + )`, + + // metadata containing version of schema + `CREATE TABLE IF NOT EXISTS _meta ( + version UINT64 NOT NULL UNIQUE + )`, + + // version 1. + `INSERT OR IGNORE INTO _meta (version) VALUES (1)`, +} + +const schemaVersion = 1 + +const ( + insertEvent = `INSERT OR IGNORE INTO event + (height, tipset_key, tipset_key_cid, emitter_addr, event_index, message_cid, message_index, reverted) + VALUES(?, ?, ?, ?, ?, ?, ?, ?)` + + insertEntry = `INSERT OR IGNORE INTO event_entry + (event_id, indexed, flags, key, value) + VALUES(?, ?, ?, ?, ?)` +) + +type EventIndex struct { + db *sql.DB +} + +func NewEventIndex(path string) (*EventIndex, error) { + db, err := sql.Open("sqlite3", path+"?mode=rwc") + if err != nil { + return nil, xerrors.Errorf("open sqlite3 database: %w", err) + } + + for _, pragma := range pragmas { + if _, err := db.Exec(pragma); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec pragma %q: %w", pragma, err) + } + } + + q, err := db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name='_meta';") + if err == sql.ErrNoRows || !q.Next() { + // empty database, create the schema + for _, ddl := range ddls { + if _, err := db.Exec(ddl); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec ddl %q: %w", ddl, err) + } + } + } else if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("looking for _meta table: %w", err) + } else { + // Ensure we don't open a database from a different schema version + + row := db.QueryRow("SELECT max(version) FROM _meta") + var version int + err := row.Scan(&version) + if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: no version found") + } + if version != schemaVersion { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: got %d, expected %d", version, schemaVersion) + } + } + + return &EventIndex{ + db: db, + }, nil +} + +func (ei *EventIndex) Close() error { + if ei.db == nil { + return nil + } + return ei.db.Close() +} + +func (ei *EventIndex) CollectEvents(ctx context.Context, te *TipSetEvents, revert bool, resolver func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool)) error { + // cache of lookups between actor id and f4 address + + addressLookups := make(map[abi.ActorID]address.Address) + + ems, err := te.messages(ctx) + if err != nil { + return xerrors.Errorf("load executed messages: %w", err) + } + + tx, err := ei.db.Begin() + if err != nil { + return xerrors.Errorf("begin transaction: %w", err) + } + stmtEvent, err := tx.Prepare(insertEvent) + if err != nil { + return xerrors.Errorf("prepare insert event: %w", err) + } + stmtEntry, err := tx.Prepare(insertEntry) + if err != nil { + return xerrors.Errorf("prepare insert entry: %w", err) + } + + isIndexedValue := func(b uint8) bool { + // currently we mark the full entry as indexed if either the key + // or the value are indexed; in the future we will need finer-grained + // management of indices + return b&(types.EventFlagIndexedKey|types.EventFlagIndexedValue) > 0 + } + + for msgIdx, em := range ems { + for evIdx, ev := range em.Events() { + addr, found := addressLookups[ev.Emitter] + if !found { + var ok bool + addr, ok = resolver(ctx, ev.Emitter, te.rctTs) + if !ok { + // not an address we will be able to match against + continue + } + addressLookups[ev.Emitter] = addr + } + + tsKeyCid, err := te.msgTs.Key().Cid() + if err != nil { + return xerrors.Errorf("tipset key cid: %w", err) + } + + res, err := stmtEvent.Exec( + te.msgTs.Height(), // height + te.msgTs.Key().Bytes(), // tipset_key + tsKeyCid.Bytes(), // tipset_key_cid + addr.Bytes(), // emitter_addr + evIdx, // event_index + em.Message().Cid().Bytes(), // message_cid + msgIdx, // message_index + revert, // reverted + ) + if err != nil { + return xerrors.Errorf("exec insert event: %w", err) + } + + lastID, err := res.LastInsertId() + if err != nil { + return xerrors.Errorf("get last row id: %w", err) + } + + for _, entry := range ev.Entries { + value := decodeLogBytes(entry.Value) + _, err := stmtEntry.Exec( + lastID, // event_id + isIndexedValue(entry.Flags), // indexed + []byte{entry.Flags}, // flags + entry.Key, // key + value, // value + ) + if err != nil { + return xerrors.Errorf("exec insert entry: %w", err) + } + } + } + } + + if err := tx.Commit(); err != nil { + return xerrors.Errorf("commit transaction: %w", err) + } + + return nil +} + +// decodeLogBytes decodes a CBOR-serialized array into its original form. +// +// This function swallows errors and returns the original array if it failed +// to decode. +func decodeLogBytes(orig []byte) []byte { + if len(orig) == 0 { + return orig + } + decoded, err := cbg.ReadByteArray(bytes.NewReader(orig), uint64(len(orig))) + if err != nil { + return orig + } + return decoded +} + +// PrefillFilter fills a filter's collection of events from the historic index +func (ei *EventIndex) PrefillFilter(ctx context.Context, f *EventFilter) error { + clauses := []string{} + values := []any{} + joins := []string{} + + if f.tipsetCid != cid.Undef { + clauses = append(clauses, "event.tipset_key_cid=?") + values = append(values, f.tipsetCid.Bytes()) + } else { + if f.minHeight >= 0 { + clauses = append(clauses, "event.height>=?") + values = append(values, f.minHeight) + } + if f.maxHeight >= 0 { + clauses = append(clauses, "event.height<=?") + values = append(values, f.maxHeight) + } + } + + if len(f.addresses) > 0 { + subclauses := []string{} + for _, addr := range f.addresses { + subclauses = append(subclauses, "emitter_addr=?") + values = append(values, addr.Bytes()) + } + clauses = append(clauses, "("+strings.Join(subclauses, " OR ")+")") + } + + if len(f.keys) > 0 { + join := 0 + for key, vals := range f.keys { + if len(vals) > 0 { + join++ + joinAlias := fmt.Sprintf("ee%d", join) + joins = append(joins, fmt.Sprintf("event_entry %s on event.id=%[1]s.event_id", joinAlias)) + clauses = append(clauses, fmt.Sprintf("%s.indexed=1 AND %[1]s.key=?", joinAlias)) + values = append(values, key) + subclauses := []string{} + for _, val := range vals { + subclauses = append(subclauses, fmt.Sprintf("%s.value=?", joinAlias)) + values = append(values, trimLeadingZeros(val)) + } + clauses = append(clauses, "("+strings.Join(subclauses, " OR ")+")") + } + } + } + + s := `SELECT + event.id, + event.height, + event.tipset_key, + event.tipset_key_cid, + event.emitter_addr, + event.event_index, + event.message_cid, + event.message_index, + event.reverted, + event_entry.flags, + event_entry.key, + event_entry.value + FROM event JOIN event_entry ON event.id=event_entry.event_id` + + if len(joins) > 0 { + s = s + ", " + strings.Join(joins, ", ") + } + + if len(clauses) > 0 { + s = s + " WHERE " + strings.Join(clauses, " AND ") + } + + s += " ORDER BY event.height DESC" + + stmt, err := ei.db.Prepare(s) + if err != nil { + return xerrors.Errorf("prepare prefill query: %w", err) + } + + q, err := stmt.QueryContext(ctx, values...) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil + } + return xerrors.Errorf("exec prefill query: %w", err) + } + + var ces []*CollectedEvent + var currentID int64 = -1 + var ce *CollectedEvent + + for q.Next() { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + var row struct { + id int64 + height uint64 + tipsetKey []byte + tipsetKeyCid []byte + emitterAddr []byte + eventIndex int + messageCid []byte + messageIndex int + reverted bool + flags []byte + key string + value []byte + } + + if err := q.Scan( + &row.id, + &row.height, + &row.tipsetKey, + &row.tipsetKeyCid, + &row.emitterAddr, + &row.eventIndex, + &row.messageCid, + &row.messageIndex, + &row.reverted, + &row.flags, + &row.key, + &row.value, + ); err != nil { + return xerrors.Errorf("read prefill row: %w", err) + } + + if row.id != currentID { + if ce != nil { + ces = append(ces, ce) + ce = nil + // Unfortunately we can't easily incorporate the max results limit into the query due to the + // unpredictable number of rows caused by joins + // Break here to stop collecting rows + if f.maxResults > 0 && len(ces) >= f.maxResults { + break + } + } + + currentID = row.id + ce = &CollectedEvent{ + EventIdx: row.eventIndex, + Reverted: row.reverted, + Height: abi.ChainEpoch(row.height), + MsgIdx: row.messageIndex, + } + + ce.EmitterAddr, err = address.NewFromBytes(row.emitterAddr) + if err != nil { + return xerrors.Errorf("parse emitter addr: %w", err) + } + + ce.TipSetKey, err = types.TipSetKeyFromBytes(row.tipsetKey) + if err != nil { + return xerrors.Errorf("parse tipsetkey: %w", err) + } + + ce.MsgCid, err = cid.Cast(row.messageCid) + if err != nil { + return xerrors.Errorf("parse message cid: %w", err) + } + } + + ce.Entries = append(ce.Entries, types.EventEntry{ + Flags: row.flags[0], + Key: row.key, + Value: row.value, + }) + + } + + if ce != nil { + ces = append(ces, ce) + } + + if len(ces) == 0 { + return nil + } + + // collected event list is in inverted order since we selected only the most recent events + // sort it into height order + sort.Slice(ces, func(i, j int) bool { return ces[i].Height < ces[j].Height }) + f.setCollectedEvents(ces) + + return nil +} + +func trimLeadingZeros(b []byte) []byte { + for i := range b { + if b[i] != 0 { + return b[i:] + } + } + return []byte{} +} diff --git a/chain/events/filter/index_test.go b/chain/events/filter/index_test.go new file mode 100644 index 000000000..ee2ae8611 --- /dev/null +++ b/chain/events/filter/index_test.go @@ -0,0 +1,283 @@ +package filter + +import ( + "context" + pseudo "math/rand" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" +) + +func TestEventIndexPrefillFilter(t *testing.T) { + rng := pseudo.New(pseudo.NewSource(299792458)) + a1 := randomF4Addr(t, rng) + a2 := randomF4Addr(t, rng) + + a1ID := abi.ActorID(1) + a2ID := abi.ActorID(2) + + addrMap := addressMap{} + addrMap.add(a1ID, a1) + addrMap.add(a2ID, a2) + + ev1 := fakeEvent( + a1ID, + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr1")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + st := newStore() + events := []*types.Event{ev1} + em := executedMessage{ + msg: fakeMessage(randomF4Addr(t, rng), randomF4Addr(t, rng)), + rct: fakeReceipt(t, rng, st, events), + evs: events, + } + + events14000 := buildTipSetEvents(t, rng, 14000, em) + cid14000, err := events14000.msgTs.Key().Cid() + require.NoError(t, err, "tipset cid") + + noCollectedEvents := []*CollectedEvent{} + oneCollectedEvent := []*CollectedEvent{ + { + Entries: ev1.Entries, + EmitterAddr: a1, + EventIdx: 0, + Reverted: false, + Height: 14000, + TipSetKey: events14000.msgTs.Key(), + MsgIdx: 0, + MsgCid: em.msg.Cid(), + }, + } + + workDir, err := os.MkdirTemp("", "lotusevents") + require.NoError(t, err, "create temporary work directory") + + defer func() { + _ = os.RemoveAll(workDir) + }() + t.Logf("using work dir %q", workDir) + + dbPath := filepath.Join(workDir, "actorevents.db") + + ei, err := NewEventIndex(dbPath) + require.NoError(t, err, "create event index") + if err := ei.CollectEvents(context.Background(), events14000, false, addrMap.ResolveAddress); err != nil { + require.NoError(t, err, "collect events") + } + + testCases := []struct { + name string + filter *EventFilter + te *TipSetEvents + want []*CollectedEvent + }{ + { + name: "nomatch tipset min height", + filter: &EventFilter{ + minHeight: 14001, + maxHeight: -1, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch tipset max height", + filter: &EventFilter{ + minHeight: -1, + maxHeight: 13999, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match tipset min height", + filter: &EventFilter{ + minHeight: 14000, + maxHeight: -1, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match tipset cid", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + tipsetCid: cid14000, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a2}, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a1}, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry with alternate values", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry by missing value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry by missing key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "method": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match one entry with multiple keys", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry with one mismatching key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "approver": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one mismatching value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr2"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one unindexed key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "amount": { + []byte("2988181"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + } + + for _, tc := range testCases { + tc := tc // appease lint + t.Run(tc.name, func(t *testing.T) { + if err := ei.PrefillFilter(context.Background(), tc.filter); err != nil { + require.NoError(t, err, "prefill filter events") + } + + coll := tc.filter.TakeCollectedEvents(context.Background()) + require.ElementsMatch(t, coll, tc.want) + }) + } +} diff --git a/chain/events/filter/mempool.go b/chain/events/filter/mempool.go new file mode 100644 index 000000000..39ccf12c2 --- /dev/null +++ b/chain/events/filter/mempool.go @@ -0,0 +1,142 @@ +package filter + +import ( + "context" + "sync" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +type MemPoolFilter struct { + id types.FilterID + maxResults int // maximum number of results to collect, 0 is unlimited + ch chan<- interface{} + + mu sync.Mutex + collected []*types.SignedMessage + lastTaken time.Time +} + +var _ Filter = (*MemPoolFilter)(nil) + +func (f *MemPoolFilter) ID() types.FilterID { + return f.id +} + +func (f *MemPoolFilter) SetSubChannel(ch chan<- interface{}) { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = ch + f.collected = nil +} + +func (f *MemPoolFilter) ClearSubChannel() { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = nil +} + +func (f *MemPoolFilter) CollectMessage(ctx context.Context, msg *types.SignedMessage) { + f.mu.Lock() + defer f.mu.Unlock() + + // if we have a subscription channel then push message to it + if f.ch != nil { + f.ch <- msg + return + } + + if f.maxResults > 0 && len(f.collected) == f.maxResults { + copy(f.collected, f.collected[1:]) + f.collected = f.collected[:len(f.collected)-1] + } + f.collected = append(f.collected, msg) +} + +func (f *MemPoolFilter) TakeCollectedMessages(context.Context) []*types.SignedMessage { + f.mu.Lock() + collected := f.collected + f.collected = nil + f.lastTaken = time.Now().UTC() + f.mu.Unlock() + + return collected +} + +func (f *MemPoolFilter) LastTaken() time.Time { + f.mu.Lock() + defer f.mu.Unlock() + return f.lastTaken +} + +type MemPoolFilterManager struct { + MaxFilterResults int + + mu sync.Mutex // guards mutations to filters + filters map[types.FilterID]*MemPoolFilter +} + +func (m *MemPoolFilterManager) WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate) { + for { + select { + case <-ctx.Done(): + return + case u := <-ch: + m.processUpdate(ctx, u) + } + } +} + +func (m *MemPoolFilterManager) processUpdate(ctx context.Context, u api.MpoolUpdate) { + // only process added messages + if u.Type == api.MpoolRemove { + return + } + + m.mu.Lock() + defer m.mu.Unlock() + + if len(m.filters) == 0 { + return + } + + // TODO: could run this loop in parallel with errgroup if we expect large numbers of filters + for _, f := range m.filters { + f.CollectMessage(ctx, u.Message) + } +} + +func (m *MemPoolFilterManager) Install(ctx context.Context) (*MemPoolFilter, error) { + id, err := newFilterID() + if err != nil { + return nil, xerrors.Errorf("new filter id: %w", err) + } + + f := &MemPoolFilter{ + id: id, + maxResults: m.MaxFilterResults, + } + + m.mu.Lock() + if m.filters == nil { + m.filters = make(map[types.FilterID]*MemPoolFilter) + } + m.filters[id] = f + m.mu.Unlock() + + return f, nil +} + +func (m *MemPoolFilterManager) Remove(ctx context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, found := m.filters[id]; !found { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} diff --git a/chain/events/filter/store.go b/chain/events/filter/store.go new file mode 100644 index 000000000..d3c173ec0 --- /dev/null +++ b/chain/events/filter/store.go @@ -0,0 +1,108 @@ +package filter + +import ( + "context" + "errors" + "sync" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types" +) + +type Filter interface { + ID() types.FilterID + LastTaken() time.Time + SetSubChannel(chan<- interface{}) + ClearSubChannel() +} + +type FilterStore interface { + Add(context.Context, Filter) error + Get(context.Context, types.FilterID) (Filter, error) + Remove(context.Context, types.FilterID) error + NotTakenSince(when time.Time) []Filter // returns a list of filters that have not had their collected results taken +} + +var ( + ErrFilterAlreadyRegistered = errors.New("filter already registered") + ErrFilterNotFound = errors.New("filter not found") + ErrMaximumNumberOfFilters = errors.New("maximum number of filters registered") +) + +func newFilterID() (types.FilterID, error) { + rawid, err := uuid.NewRandom() + if err != nil { + return types.FilterID{}, xerrors.Errorf("new uuid: %w", err) + } + id := types.FilterID{} + copy(id[:], rawid[:]) // uuid is 16 bytes, the last 16 bytes are zeroed + return id, nil +} + +type memFilterStore struct { + max int + mu sync.Mutex + filters map[types.FilterID]Filter +} + +var _ FilterStore = (*memFilterStore)(nil) + +func NewMemFilterStore(maxFilters int) FilterStore { + return &memFilterStore{ + max: maxFilters, + filters: make(map[types.FilterID]Filter), + } +} + +func (m *memFilterStore) Add(_ context.Context, f Filter) error { + m.mu.Lock() + defer m.mu.Unlock() + + if len(m.filters) >= m.max { + return ErrMaximumNumberOfFilters + } + + if _, exists := m.filters[f.ID()]; exists { + return ErrFilterAlreadyRegistered + } + m.filters[f.ID()] = f + return nil +} + +func (m *memFilterStore) Get(_ context.Context, id types.FilterID) (Filter, error) { + m.mu.Lock() + f, found := m.filters[id] + m.mu.Unlock() + if !found { + return nil, ErrFilterNotFound + } + return f, nil +} + +func (m *memFilterStore) Remove(_ context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + + if _, exists := m.filters[id]; !exists { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} + +func (m *memFilterStore) NotTakenSince(when time.Time) []Filter { + m.mu.Lock() + defer m.mu.Unlock() + + var res []Filter + for _, f := range m.filters { + if f.LastTaken().Before(when) { + res = append(res, f) + } + } + + return res +} diff --git a/chain/events/filter/tipset.go b/chain/events/filter/tipset.go new file mode 100644 index 000000000..be734c6f7 --- /dev/null +++ b/chain/events/filter/tipset.go @@ -0,0 +1,130 @@ +package filter + +import ( + "context" + "sync" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types" +) + +type TipSetFilter struct { + id types.FilterID + maxResults int // maximum number of results to collect, 0 is unlimited + ch chan<- interface{} + + mu sync.Mutex + collected []types.TipSetKey + lastTaken time.Time +} + +var _ Filter = (*TipSetFilter)(nil) + +func (f *TipSetFilter) ID() types.FilterID { + return f.id +} + +func (f *TipSetFilter) SetSubChannel(ch chan<- interface{}) { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = ch + f.collected = nil +} + +func (f *TipSetFilter) ClearSubChannel() { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = nil +} + +func (f *TipSetFilter) CollectTipSet(ctx context.Context, ts *types.TipSet) { + f.mu.Lock() + defer f.mu.Unlock() + + // if we have a subscription channel then push tipset to it + if f.ch != nil { + f.ch <- ts + return + } + + if f.maxResults > 0 && len(f.collected) == f.maxResults { + copy(f.collected, f.collected[1:]) + f.collected = f.collected[:len(f.collected)-1] + } + f.collected = append(f.collected, ts.Key()) +} + +func (f *TipSetFilter) TakeCollectedTipSets(context.Context) []types.TipSetKey { + f.mu.Lock() + collected := f.collected + f.collected = nil + f.lastTaken = time.Now().UTC() + f.mu.Unlock() + + return collected +} + +func (f *TipSetFilter) LastTaken() time.Time { + f.mu.Lock() + defer f.mu.Unlock() + return f.lastTaken +} + +type TipSetFilterManager struct { + MaxFilterResults int + + mu sync.Mutex // guards mutations to filters + filters map[types.FilterID]*TipSetFilter +} + +func (m *TipSetFilterManager) Apply(ctx context.Context, from, to *types.TipSet) error { + m.mu.Lock() + defer m.mu.Unlock() + if len(m.filters) == 0 { + return nil + } + + // TODO: could run this loop in parallel with errgroup + for _, f := range m.filters { + f.CollectTipSet(ctx, to) + } + + return nil +} + +func (m *TipSetFilterManager) Revert(ctx context.Context, from, to *types.TipSet) error { + return nil +} + +func (m *TipSetFilterManager) Install(ctx context.Context) (*TipSetFilter, error) { + id, err := newFilterID() + if err != nil { + return nil, xerrors.Errorf("new filter id: %w", err) + } + + f := &TipSetFilter{ + id: id, + maxResults: m.MaxFilterResults, + } + + m.mu.Lock() + if m.filters == nil { + m.filters = make(map[types.FilterID]*TipSetFilter) + } + m.filters[id] = f + m.mu.Unlock() + + return f, nil +} + +func (m *TipSetFilterManager) Remove(ctx context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, found := m.filters[id]; !found { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 674befaef..673a299e3 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -469,10 +469,6 @@ func (cg *ChainGen) NextTipSetFromMinersWithMessagesAndNulls(base *types.TipSet, return nil, xerrors.Errorf("making a block for next tipset failed: %w", err) } - if err := cg.cs.PersistBlockHeaders(context.TODO(), fblk.Header); err != nil { - return nil, xerrors.Errorf("chainstore AddBlock: %w", err) - } - blks = append(blks, fblk) } } diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index 241c3501c..3e8848021 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -123,12 +123,7 @@ Genesis: { func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template genesis.Template) (*state.StateTree, map[address.Address]address.Address, error) { // Create empty state tree - cst := cbor.NewCborStore(bs) - _, err := cst.Put(context.TODO(), []struct{}{}) - if err != nil { - return nil, nil, xerrors.Errorf("putting empty object: %w", err) - } sv, err := state.VersionForNetwork(template.NetworkVersion) if err != nil { @@ -238,15 +233,12 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge // Create accounts for _, info := range template.Accounts { - switch info.Type { case genesis.TAccount: if err := CreateAccountActor(ctx, cst, state, info, keyIDs, av); err != nil { return nil, nil, xerrors.Errorf("failed to create account actor: %w", err) } - case genesis.TMultisig: - ida, err := address.NewIDAddress(uint64(idStart)) if err != nil { return nil, nil, err @@ -566,6 +558,11 @@ func MakeGenesisBlock(ctx context.Context, j journal.Journal, bs bstore.Blocksto return nil, xerrors.Errorf("make initial state tree failed: %w", err) } + // Set up the Ethereum Address Manager + if err = SetupEAM(ctx, st, template.NetworkVersion); err != nil { + return nil, xerrors.Errorf("failed to setup EAM: %w", err) + } + stateroot, err := st.Flush(ctx) if err != nil { return nil, xerrors.Errorf("flush state tree failed: %w", err) @@ -580,11 +577,27 @@ func MakeGenesisBlock(ctx context.Context, j journal.Journal, bs bstore.Blocksto return nil, xerrors.Errorf("failed to verify presealed data: %w", err) } + // setup Storage Miners stateroot, err = SetupStorageMiners(ctx, cs, sys, stateroot, template.Miners, template.NetworkVersion) if err != nil { return nil, xerrors.Errorf("setup miners failed: %w", err) } + st, err = state.LoadStateTree(st.Store, stateroot) + if err != nil { + return nil, xerrors.Errorf("failed to load updated state tree: %w", err) + } + + // Set up Eth null addresses. + if _, err := SetupEthNullAddresses(ctx, st, template.NetworkVersion); err != nil { + return nil, xerrors.Errorf("failed to set up Eth null addresses: %w", err) + } + + stateroot, err = st.Flush(ctx) + if err != nil { + return nil, xerrors.Errorf("failed to flush state tree: %w", err) + } + store := adt.WrapStore(ctx, cbor.NewCborStore(bs)) emptyroot, err := adt0.MakeEmptyArray(store).Root() if err != nil { diff --git a/chain/gen/genesis/genesis_eth.go b/chain/gen/genesis/genesis_eth.go new file mode 100644 index 000000000..d5aa2f0b5 --- /dev/null +++ b/chain/gen/genesis/genesis_eth.go @@ -0,0 +1,139 @@ +package genesis + +import ( + "context" + "fmt" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/chain/vm" +) + +// EthNullAddresses are the Ethereum addresses we want to create zero-balanced EthAccounts in. +// We may want to add null addresses for precompiles going forward. +var EthNullAddresses = []string{ + "0x0000000000000000000000000000000000000000", +} + +func SetupEAM(_ context.Context, nst *state.StateTree, nv network.Version) error { + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + return fmt.Errorf("failed to get actors version for network version %d: %w", nv, err) + } + + if av < actorstypes.Version10 { + // Not defined before version 10; migration has to create. + return nil + } + + codecid, ok := actors.GetActorCodeID(av, manifest.EamKey) + if !ok { + return fmt.Errorf("failed to get CodeCID for EAM during genesis") + } + + header := &types.Actor{ + Code: codecid, + Head: vm.EmptyObjectCid, + Balance: big.Zero(), + Address: &builtin.EthereumAddressManagerActorAddr, // so that it can create ETH0 + } + return nst.SetActor(builtin.EthereumAddressManagerActorAddr, header) +} + +// MakeEthNullAddressActor creates a null address actor at the specified Ethereum address. +func MakeEthNullAddressActor(av actorstypes.Version, addr address.Address) (*types.Actor, error) { + actcid, ok := actors.GetActorCodeID(av, manifest.EthAccountKey) + if !ok { + return nil, xerrors.Errorf("failed to get EthAccount actor code ID for actors version %d", av) + } + + act := &types.Actor{ + Code: actcid, + Head: vm.EmptyObjectCid, + Nonce: 0, + Balance: big.Zero(), + Address: &addr, + } + + return act, nil +} + +func SetupEthNullAddresses(ctx context.Context, st *state.StateTree, nv network.Version) ([]address.Address, error) { + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + return nil, xerrors.Errorf("failed to resolve actors version for network version %d: %w", av, err) + } + + if av < actorstypes.Version10 { + // Not defined before version 10. + return nil, nil + } + + var ethAddresses []ethtypes.EthAddress + for _, addr := range EthNullAddresses { + a, err := ethtypes.ParseEthAddress(addr) + if err != nil { + return nil, xerrors.Errorf("failed to represent the 0x0 as an EthAddress: %w", err) + } + ethAddresses = append(ethAddresses, a) + } + + initAct, err := st.GetActor(builtin.InitActorAddr) + if err != nil { + return nil, xerrors.Errorf("failed to load init actor: %w", err) + } + + initState, err := init_.Load(adt.WrapStore(ctx, st.Store), initAct) + if err != nil { + return nil, xerrors.Errorf("failed to load init actor state: %w", err) + } + + var ret []address.Address + for _, ethAddr := range ethAddresses { + // Place an EthAccount at the 0x0 Eth Null Address. + f4Addr, err := ethAddr.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("failed to compute Filecoin address for Eth addr 0x0: %w", err) + } + + idAddr, err := initState.MapAddressToNewID(f4Addr) + if err != nil { + return nil, xerrors.Errorf("failed to map addr in init actor: %w", err) + } + + actState, err := MakeEthNullAddressActor(av, f4Addr) + if err != nil { + return nil, xerrors.Errorf("failed to create EthAccount actor for null address: %w", err) + } + + if err := st.SetActor(idAddr, actState); err != nil { + return nil, xerrors.Errorf("failed to set Eth Null Address EthAccount actor state: %w", err) + } + + ret = append(ret, idAddr) + } + + initAct.Head, err = st.Store.Put(ctx, initState) + if err != nil { + return nil, xerrors.Errorf("failed to add init actor state to store: %w", err) + } + + if err := st.SetActor(builtin.InitActorAddr, initAct); err != nil { + return nil, xerrors.Errorf("failed to set updated state for init actor: %w", err) + } + + return ret, nil +} diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 94ae62673..9abe38648 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -33,12 +33,12 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/journal" - "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/node/modules/dtypes" ) @@ -282,7 +282,7 @@ func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, strict, untrusted } ms.requiredFunds.Sub(ms.requiredFunds, exms.Message.RequiredFunds().Int) - //ms.requiredFunds.Sub(ms.requiredFunds, exms.Message.Value.Int) + // ms.requiredFunds.Sub(ms.requiredFunds, exms.Message.Value.Int) } if !has && strict && len(ms.msgs) >= maxActorPendingMessages { @@ -298,7 +298,7 @@ func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, strict, untrusted ms.nextNonce = nextNonce ms.msgs[m.Message.Nonce] = m ms.requiredFunds.Add(ms.requiredFunds, m.Message.RequiredFunds().Int) - //ms.requiredFunds.Add(ms.requiredFunds, m.Message.Value.Int) + // ms.requiredFunds.Add(ms.requiredFunds, m.Message.Value.Int) return !has, nil } @@ -318,7 +318,7 @@ func (ms *msgSet) rm(nonce uint64, applied bool) { } ms.requiredFunds.Sub(ms.requiredFunds, m.Message.RequiredFunds().Int) - //ms.requiredFunds.Sub(ms.requiredFunds, m.Message.Value.Int) + // ms.requiredFunds.Sub(ms.requiredFunds, m.Message.Value.Int) delete(ms.msgs, nonce) // adjust next nonce @@ -344,7 +344,7 @@ func (ms *msgSet) getRequiredFunds(nonce uint64) types.BigInt { m, has := ms.msgs[nonce] if has { requiredFunds.Sub(requiredFunds, m.Message.RequiredFunds().Int) - //requiredFunds.Sub(requiredFunds, m.Message.Value.Int) + // requiredFunds.Sub(requiredFunds, m.Message.Value.Int) } return types.BigInt{Int: requiredFunds} @@ -476,7 +476,7 @@ func (mp *MessagePool) resolveToKey(ctx context.Context, addr address.Address) ( } // resolve the address - ka, err := mp.api.StateAccountKeyAtFinality(ctx, addr, mp.curTs) + ka, err := mp.api.StateDeterministicAddressAtFinality(ctx, addr, mp.curTs) if err != nil { return address.Undef, err } @@ -772,11 +772,9 @@ func sigCacheKey(m *types.SignedMessage) (string, error) { if len(m.Signature.Data) != ffi.SignatureBytes { return "", fmt.Errorf("bls signature incorrectly sized") } - hashCache := blake2b.Sum256(append(m.Cid().Bytes(), m.Signature.Data...)) - return string(hashCache[:]), nil - case crypto.SigTypeSecp256k1: + case crypto.SigTypeSecp256k1, crypto.SigTypeDelegated: return string(m.Cid().Bytes()), nil default: return "", xerrors.Errorf("unrecognized signature type: %d", m.Signature.Type) @@ -795,8 +793,8 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error { return nil } - if err := sigs.Verify(&m.Signature, m.Message.From, m.Message.Cid().Bytes()); err != nil { - return err + if err := consensus.AuthenticateMessage(m, m.Message.From); err != nil { + return xerrors.Errorf("failed to validate signature: %w", err) } mp.sigValCache.Add(sck, struct{}{}) @@ -816,7 +814,7 @@ func (mp *MessagePool) checkBalance(ctx context.Context, m *types.SignedMessage, } // add Value for soft failure check - //requiredFunds = types.BigAdd(requiredFunds, m.Message.Value) + // requiredFunds = types.BigAdd(requiredFunds, m.Message.Value) mset, ok, err := mp.getPendingMset(ctx, m.Message.From) if err != nil { @@ -853,18 +851,32 @@ func (mp *MessagePool) addTs(ctx context.Context, m *types.SignedMessage, curTs mp.lk.Lock() defer mp.lk.Unlock() + senderAct, err := mp.api.GetActorAfter(m.Message.From, curTs) + if err != nil { + return false, xerrors.Errorf("failed to get sender actor: %w", err) + } + + // This message can only be included in the _next_ epoch and beyond, hence the +1. + epoch := curTs.Height() + 1 + nv := mp.api.StateNetworkVersion(ctx, epoch) + + // TODO: I'm not thrilled about depending on filcns here, but I prefer this to duplicating logic + if !consensus.IsValidForSending(nv, senderAct) { + return false, xerrors.Errorf("sender actor %s is not a valid top-level sender", m.Message.From) + } + publish, err := mp.verifyMsgBeforeAdd(ctx, m, curTs, local) if err != nil { - return false, err + return false, xerrors.Errorf("verify msg failed: %w", err) } if err := mp.checkBalance(ctx, m, curTs); err != nil { - return false, err + return false, xerrors.Errorf("failed to check balance: %w", err) } err = mp.addLocked(ctx, m, !local, untrusted) if err != nil { - return false, err + return false, xerrors.Errorf("failed to add locked: %w", err) } if local { diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index 9495400c6..20da2317e 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -155,14 +155,14 @@ func (tma *testMpoolAPI) GetActorAfter(addr address.Address, ts *types.TipSet) ( } return &types.Actor{ - Code: builtin2.StorageMarketActorCodeID, + Code: builtin2.AccountActorCodeID, Nonce: nonce, Balance: balance, }, nil } -func (tma *testMpoolAPI) StateAccountKeyAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { - if addr.Protocol() != address.BLS && addr.Protocol() != address.SECP256K1 { +func (tma *testMpoolAPI) StateDeterministicAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + if addr.Protocol() != address.BLS && addr.Protocol() != address.SECP256K1 && addr.Protocol() != address.Delegated { return address.Undef, fmt.Errorf("given address was not a key addr") } return addr, nil @@ -214,7 +214,7 @@ func (tma *testMpoolAPI) ChainComputeBaseFee(ctx context.Context, ts *types.TipS func assertNonce(t *testing.T, mp *MessagePool, addr address.Address, val uint64) { t.Helper() - //stm: @CHAIN_MEMPOOL_GET_NONCE_001 + // stm: @CHAIN_MEMPOOL_GET_NONCE_001 n, err := mp.GetNonce(context.TODO(), addr, types.EmptyTSK) if err != nil { t.Fatal(err) @@ -233,7 +233,7 @@ func mustAdd(t *testing.T, mp *MessagePool, msg *types.SignedMessage) { } func TestMessagePool(t *testing.T) { - //stm: @CHAIN_MEMPOOL_GET_NONCE_001 + // stm: @CHAIN_MEMPOOL_GET_NONCE_001 tma := newTestMpoolAPI() @@ -336,7 +336,7 @@ func TestCheckMessageBig(t *testing.T) { Message: *msg, Signature: *sig, } - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 err = mp.Add(context.TODO(), sm) assert.ErrorIs(t, err, ErrMessageTooBig) } @@ -378,10 +378,10 @@ func TestMessagePoolMessagesInEachBlock(t *testing.T) { tma.applyBlock(t, a) tsa := mock.TipSet(a) - //stm: @CHAIN_MEMPOOL_PENDING_001 + // stm: @CHAIN_MEMPOOL_PENDING_001 _, _ = mp.Pending(context.TODO()) - //stm: @CHAIN_MEMPOOL_SELECT_001 + // stm: @CHAIN_MEMPOOL_SELECT_001 selm, _ := mp.SelectMessages(context.Background(), tsa, 1) if len(selm) == 0 { t.Fatal("should have returned the rest of the messages") @@ -442,7 +442,7 @@ func TestRevertMessages(t *testing.T) { assertNonce(t, mp, sender, 4) - //stm: @CHAIN_MEMPOOL_PENDING_001 + // stm: @CHAIN_MEMPOOL_PENDING_001 p, _ := mp.Pending(context.TODO()) fmt.Printf("%+v\n", p) if len(p) != 3 { @@ -501,7 +501,7 @@ func TestPruningSimple(t *testing.T) { mp.Prune() - //stm: @CHAIN_MEMPOOL_PENDING_001 + // stm: @CHAIN_MEMPOOL_PENDING_001 msgs, _ := mp.Pending(context.TODO()) if len(msgs) != 5 { t.Fatal("expected only 5 messages in pool, got: ", len(msgs)) @@ -544,7 +544,7 @@ func TestLoadLocal(t *testing.T) { msgs := make(map[cid.Cid]struct{}) for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 cid, err := mp.Push(context.TODO(), m, true) if err != nil { t.Fatal(err) @@ -561,7 +561,7 @@ func TestLoadLocal(t *testing.T) { t.Fatal(err) } - //stm: @CHAIN_MEMPOOL_PENDING_001 + // stm: @CHAIN_MEMPOOL_PENDING_001 pmsgs, _ := mp.Pending(context.TODO()) if len(msgs) != len(pmsgs) { t.Fatalf("expected %d messages, but got %d", len(msgs), len(pmsgs)) @@ -617,7 +617,7 @@ func TestClearAll(t *testing.T) { gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 _, err := mp.Push(context.TODO(), m, true) if err != nil { t.Fatal(err) @@ -629,10 +629,10 @@ func TestClearAll(t *testing.T) { mustAdd(t, mp, m) } - //stm: @CHAIN_MEMPOOL_CLEAR_001 + // stm: @CHAIN_MEMPOOL_CLEAR_001 mp.Clear(context.Background(), true) - //stm: @CHAIN_MEMPOOL_PENDING_001 + // stm: @CHAIN_MEMPOOL_PENDING_001 pending, _ := mp.Pending(context.TODO()) if len(pending) > 0 { t.Fatalf("cleared the mpool, but got %d pending messages", len(pending)) @@ -675,7 +675,7 @@ func TestClearNonLocal(t *testing.T) { gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 _, err := mp.Push(context.TODO(), m, true) if err != nil { t.Fatal(err) @@ -687,10 +687,10 @@ func TestClearNonLocal(t *testing.T) { mustAdd(t, mp, m) } - //stm: @CHAIN_MEMPOOL_CLEAR_001 + // stm: @CHAIN_MEMPOOL_CLEAR_001 mp.Clear(context.Background(), false) - //stm: @CHAIN_MEMPOOL_PENDING_001 + // stm: @CHAIN_MEMPOOL_PENDING_001 pending, _ := mp.Pending(context.TODO()) if len(pending) != 10 { t.Fatalf("expected 10 pending messages, but got %d instead", len(pending)) @@ -748,7 +748,7 @@ func TestUpdates(t *testing.T) { for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 _, err := mp.Push(context.TODO(), m, true) if err != nil { t.Fatal(err) @@ -772,7 +772,7 @@ func TestUpdates(t *testing.T) { } func TestMessageBelowMinGasFee(t *testing.T) { - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) @@ -818,7 +818,7 @@ func TestMessageBelowMinGasFee(t *testing.T) { } func TestMessageValueTooHigh(t *testing.T) { - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) @@ -866,7 +866,7 @@ func TestMessageValueTooHigh(t *testing.T) { } func TestMessageSignatureInvalid(t *testing.T) { - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) @@ -911,7 +911,7 @@ func TestMessageSignatureInvalid(t *testing.T) { } func TestAddMessageTwice(t *testing.T) { - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) @@ -957,7 +957,7 @@ func TestAddMessageTwice(t *testing.T) { } func TestAddMessageTwiceNonceGap(t *testing.T) { - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) @@ -1011,7 +1011,7 @@ func TestAddMessageTwiceCidDiff(t *testing.T) { // Create message with different data, so CID is different sm2 := makeTestMessage(w, from, to, 0, 50_000_001, minimumBaseFee.Uint64()) - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 // then try to add message again err = mp.Add(context.TODO(), sm2) // assert.Contains(t, err.Error(), "replace by fee has too low GasPremium") @@ -1020,7 +1020,7 @@ func TestAddMessageTwiceCidDiff(t *testing.T) { } func TestAddMessageTwiceCidDiffReplaced(t *testing.T) { - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) @@ -1049,7 +1049,7 @@ func TestAddMessageTwiceCidDiffReplaced(t *testing.T) { } func TestRemoveMessage(t *testing.T) { - //stm: @CHAIN_MEMPOOL_PUSH_001 + // stm: @CHAIN_MEMPOOL_PUSH_001 tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) @@ -1071,11 +1071,11 @@ func TestRemoveMessage(t *testing.T) { sm := makeTestMessage(w, from, to, 0, 50_000_000, minimumBaseFee.Uint64()) mustAdd(t, mp, sm) - //stm: @CHAIN_MEMPOOL_REMOVE_001 + // stm: @CHAIN_MEMPOOL_REMOVE_001 // remove message for sender mp.Remove(context.TODO(), from, sm.Message.Nonce, true) - //stm: @CHAIN_MEMPOOL_PENDING_FOR_001 + // stm: @CHAIN_MEMPOOL_PENDING_FOR_001 // check messages in pool: should be none present msgs := mp.pendingFor(context.TODO(), from) assert.Len(t, msgs, 0) diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index f8bbbc01e..123a2607e 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -28,7 +28,7 @@ type Provider interface { PutMessage(ctx context.Context, m types.ChainMsg) (cid.Cid, error) PubSubPublish(string, []byte) error GetActorAfter(address.Address, *types.TipSet) (*types.Actor, error) - StateAccountKeyAtFinality(context.Context, address.Address, *types.TipSet) (address.Address, error) + StateDeterministicAddressAtFinality(context.Context, address.Address, *types.TipSet) (address.Address, error) StateNetworkVersion(context.Context, abi.ChainEpoch) network.Version MessagesForBlock(context.Context, *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) MessagesForTipset(context.Context, *types.TipSet) ([]types.ChainMsg, error) @@ -74,7 +74,7 @@ func (mpp *mpoolProvider) PutMessage(ctx context.Context, m types.ChainMsg) (cid } func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error { - return mpp.ps.Publish(k, v) //nolint + return mpp.ps.Publish(k, v) // nolint } func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) { @@ -102,8 +102,8 @@ func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) return st.GetActor(addr) } -func (mpp *mpoolProvider) StateAccountKeyAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { - return mpp.sm.ResolveToKeyAddressAtFinality(ctx, addr, ts) +func (mpp *mpoolProvider) StateDeterministicAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + return mpp.sm.ResolveToDeterministicAddressAtFinality(ctx, addr, ts) } func (mpp *mpoolProvider) StateNetworkVersion(ctx context.Context, height abi.ChainEpoch) network.Version { diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index e84962869..bd5044128 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -97,7 +97,7 @@ func (sm *selectedMessages) tryToAdd(mc *msgChain) bool { sm.msgs = append(sm.msgs, mc.msgs...) sm.blsLimit -= l sm.gasLimit -= mc.gasLimit - } else if mc.sigType == crypto.SigTypeSecp256k1 { + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { if sm.secpLimit < l { return false } @@ -123,7 +123,7 @@ func (sm *selectedMessages) tryToAddWithDeps(mc *msgChain, mp *MessagePool, base if mc.sigType == crypto.SigTypeBLS { smMsgLimit = sm.blsLimit - } else if mc.sigType == crypto.SigTypeSecp256k1 { + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { smMsgLimit = sm.secpLimit } else { return false @@ -174,7 +174,7 @@ func (sm *selectedMessages) tryToAddWithDeps(mc *msgChain, mp *MessagePool, base if mc.sigType == crypto.SigTypeBLS { sm.blsLimit -= chainMsgLimit - } else if mc.sigType == crypto.SigTypeSecp256k1 { + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { sm.secpLimit -= chainMsgLimit } @@ -187,7 +187,7 @@ func (sm *selectedMessages) trimChain(mc *msgChain, mp *MessagePool, baseFee typ if msgLimit > sm.blsLimit { msgLimit = sm.blsLimit } - } else if mc.sigType == crypto.SigTypeSecp256k1 { + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { if msgLimit > sm.secpLimit { msgLimit = sm.secpLimit } diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index a5b2f3266..c3a5c6d6f 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -31,6 +31,7 @@ import ( "github.com/filecoin-project/lotus/chain/types/mock" "github.com/filecoin-project/lotus/chain/wallet" _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" ) diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index 96457e9f8..6a622dd57 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -13,10 +13,13 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/chain/wallet/key" "github.com/filecoin-project/lotus/node/modules/dtypes" ) @@ -66,15 +69,24 @@ func (ms *MessageSigner) SignMessage(ctx context.Context, msg *types.Message, sp // Sign the message with the nonce msg.Nonce = nonce + keyInfo, err := ms.wallet.WalletExport(ctx, msg.From) + if err != nil { + return nil, err + } + sb, err := SigningBytes(msg, key.ActSigType(keyInfo.Type)) + if err != nil { + return nil, err + } mb, err := msg.ToStorageBlock() if err != nil { return nil, xerrors.Errorf("serializing message: %w", err) } - sig, err := ms.wallet.WalletSign(ctx, msg.From, mb.Cid().Bytes(), api.MsgMeta{ + sig, err := ms.wallet.WalletSign(ctx, msg.From, sb, api.MsgMeta{ Type: api.MTChainMsg, Extra: mb.RawData(), }) + if err != nil { return nil, xerrors.Errorf("failed to sign message: %w, addr=%s", err, msg.From) } @@ -187,3 +199,19 @@ func (ms *MessageSigner) SaveNonce(ctx context.Context, addr address.Address, no func (ms *MessageSigner) dstoreKey(addr address.Address) datastore.Key { return datastore.KeyWithNamespaces([]string{dsKeyActorNonce, addr.String()}) } + +func SigningBytes(msg *types.Message, sigType crypto.SigType) ([]byte, error) { + if sigType == crypto.SigTypeDelegated { + txArgs, err := ethtypes.EthTxArgsFromMessage(msg) + if err != nil { + return nil, xerrors.Errorf("failed to reconstruct eth transaction: %w", err) + } + rlpEncodedMsg, err := txArgs.ToRlpUnsignedMsg() + if err != nil { + return nil, xerrors.Errorf("failed to repack eth rlp message: %w", err) + } + return rlpEncodedMsg, nil + } + + return msg.Cid().Bytes(), nil +} diff --git a/chain/stmgr/actors.go b/chain/stmgr/actors.go index 0c2524b7b..4de39c7f1 100644 --- a/chain/stmgr/actors.go +++ b/chain/stmgr/actors.go @@ -48,7 +48,7 @@ func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr return address.Undef, xerrors.Errorf("failed to load actor info: %w", err) } - return vm.ResolveToKeyAddr(state, sm.cs.ActorStore(ctx), info.Worker) + return vm.ResolveToDeterministicAddr(state, sm.cs.ActorStore(ctx), info.Worker) } func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (power.Claim, power.Claim, bool, error) { @@ -381,7 +381,7 @@ func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcs beacon.Schedule return nil, err } - worker, err := sm.ResolveToKeyAddress(ctx, info.Worker, ts) + worker, err := sm.ResolveToDeterministicAddress(ctx, info.Worker, ts) if err != nil { return nil, xerrors.Errorf("resolving worker address: %w", err) } diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index c110dd4bd..61be26e56 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -143,6 +143,7 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr vmopt := &vm.VMOpts{ StateBase: stateCid, Epoch: vmHeight, + Timestamp: ts.MinTimestamp(), Rand: rand.NewStateRand(sm.cs, ts.Cids(), sm.beacon, nvGetter), Bstore: buffStore, Actors: sm.tsExec.NewActorRegistry(), @@ -199,7 +200,7 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr var ret *vm.ApplyRet var gasInfo api.MsgGasCost if checkGas { - fromKey, err := sm.ResolveToKeyAddress(ctx, msg.From, ts) + fromKey, err := sm.ResolveToDeterministicAddress(ctx, msg.From, ts) if err != nil { return nil, xerrors.Errorf("could not resolve key: %w", err) } @@ -217,6 +218,14 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr Data: make([]byte, 65), }, } + case address.Delegated: + msgApply = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: make([]byte, 65), + }, + } } ret, err = vmi.ApplyMessage(ctx, msgApply) diff --git a/chain/stmgr/rpc/rpcstatemanager.go b/chain/stmgr/rpc/rpcstatemanager.go index 2c9893cc0..9186501ea 100644 --- a/chain/stmgr/rpc/rpcstatemanager.go +++ b/chain/stmgr/rpc/rpcstatemanager.go @@ -48,7 +48,7 @@ func (s *RPCStateManager) LookupID(ctx context.Context, addr address.Address, ts return s.gapi.StateLookupID(ctx, addr, ts.Key()) } -func (s *RPCStateManager) ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { +func (s *RPCStateManager) ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { return s.gapi.StateAccountKey(ctx, addr, ts.Key()) } diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 926646a4c..ee9338e63 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -42,7 +42,7 @@ type StateManagerAPI interface { GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) LoadActorTsk(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*types.Actor, error) LookupID(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) - ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) + ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) } type versionSpec struct { @@ -207,11 +207,11 @@ func (sm *StateManager) Beacon() beacon.Schedule { return sm.beacon } -// ResolveToKeyAddress is similar to `vm.ResolveToKeyAddr` but does not allow `Actor` type of addresses. +// ResolveToDeterministicAddress is similar to `vm.ResolveToDeterministicAddr` but does not allow `Actor` type of addresses. // Uses the `TipSet` `ts` to generate the VM state. -func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { +func (sm *StateManager) ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { switch addr.Protocol() { - case address.BLS, address.SECP256K1: + case address.BLS, address.SECP256K1, address.Delegated: return addr, nil case address.Actor: return address.Undef, xerrors.New("cannot resolve actor address to key address") @@ -230,7 +230,7 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad return address.Undef, xerrors.Errorf("failed to load parent state tree at tipset %s: %w", ts.Parents(), err) } - resolved, err := vm.ResolveToKeyAddr(tree, cst, addr) + resolved, err := vm.ResolveToDeterministicAddr(tree, cst, addr) if err == nil { return resolved, nil } @@ -246,14 +246,14 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad return address.Undef, xerrors.Errorf("failed to load state tree at tipset %s: %w", ts, err) } - return vm.ResolveToKeyAddr(tree, cst, addr) + return vm.ResolveToDeterministicAddr(tree, cst, addr) } -// ResolveToKeyAddressAtFinality is similar to stmgr.ResolveToKeyAddress but fails if the ID address being resolved isn't reorg-stable yet. +// ResolveToDeterministicAddressAtFinality is similar to stmgr.ResolveToDeterministicAddress but fails if the ID address being resolved isn't reorg-stable yet. // It should not be used for consensus-critical subsystems. -func (sm *StateManager) ResolveToKeyAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { +func (sm *StateManager) ResolveToDeterministicAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { switch addr.Protocol() { - case address.BLS, address.SECP256K1: + case address.BLS, address.SECP256K1, address.Delegated: return addr, nil case address.Actor: return address.Undef, xerrors.New("cannot resolve actor address to key address") @@ -287,7 +287,7 @@ func (sm *StateManager) ResolveToKeyAddressAtFinality(ctx context.Context, addr } } - resolved, err := vm.ResolveToKeyAddr(tree, cst, addr) + resolved, err := vm.ResolveToDeterministicAddr(tree, cst, addr) if err == nil { return resolved, nil } @@ -296,7 +296,7 @@ func (sm *StateManager) ResolveToKeyAddressAtFinality(ctx context.Context, addr } func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Address, ts *types.TipSet) (pubk []byte, err error) { - kaddr, err := sm.ResolveToKeyAddress(ctx, addr, ts) + kaddr, err := sm.ResolveToDeterministicAddress(ctx, addr, ts) if err != nil { return pubk, xerrors.Errorf("failed to resolve address to key address: %w", err) } diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index af4a0cd9e..c93267d50 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -2,6 +2,7 @@ package stmgr import ( "context" + "errors" "fmt" "reflect" @@ -27,6 +28,8 @@ import ( "github.com/filecoin-project/lotus/node/modules/dtypes" ) +var ErrMetadataNotFound = errors.New("actor metadata not found") + func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, method abi.MethodNum, ts *types.TipSet) (cbg.CBORUnmarshaler, error) { act, err := sm.LoadActor(ctx, to, ts) if err != nil { @@ -35,7 +38,7 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me m, found := sm.tsExec.NewActorRegistry().Methods[act.Code][method] if !found { - return nil, fmt.Errorf("unknown method %d for actor %s", method, act.Code) + return nil, fmt.Errorf("unknown method %d for actor %s: %w", method, act.Code, ErrMetadataNotFound) } return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil @@ -44,7 +47,7 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me func GetParamType(ar *vm.ActorRegistry, actCode cid.Cid, method abi.MethodNum) (cbg.CBORUnmarshaler, error) { m, found := ar.Methods[actCode][method] if !found { - return nil, fmt.Errorf("unknown method %d for actor %s", method, actCode) + return nil, fmt.Errorf("unknown method %d for actor %s: %w", method, actCode, ErrMetadataNotFound) } return reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler), nil } @@ -87,6 +90,7 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, vmopt := &vm.VMOpts{ StateBase: base, Epoch: height, + Timestamp: ts.MinTimestamp(), Rand: r, Bstore: sm.cs.StateBlockstore(), Actors: sm.tsExec.NewActorRegistry(), diff --git a/chain/store/snapshot.go b/chain/store/snapshot.go index f9e65f4bf..e90a60611 100644 --- a/chain/store/snapshot.go +++ b/chain/store/snapshot.go @@ -22,6 +22,8 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) +const TipsetkeyBackfillRange = 2 * build.Finality + func (cs *ChainStore) UnionStore() bstore.Blockstore { return bstore.Union(cs.stateBlockstore, cs.chainBlockstore) } @@ -113,6 +115,20 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) } + ts := root + for i := 0; i < int(TipsetkeyBackfillRange); i++ { + err = cs.PersistTipset(ctx, ts) + if err != nil { + return nil, err + } + parentTsKey := ts.Parents() + ts, err = cs.LoadTipSet(ctx, parentTsKey) + if ts == nil || err != nil { + log.Warnf("Only able to load the last %d tipsets", i) + break + } + } + return root, nil } diff --git a/chain/store/store.go b/chain/store/store.go index 6313492a7..6dc17d766 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -1,6 +1,7 @@ package store import ( + "bytes" "context" "encoding/json" "errors" @@ -375,17 +376,27 @@ func (cs *ChainStore) SetGenesis(ctx context.Context, b *types.BlockHeader) erro } func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error { - for _, b := range ts.Blocks() { - if err := cs.PersistBlockHeaders(ctx, b); err != nil { - return err - } + if err := cs.PersistTipset(ctx, ts); err != nil { + return xerrors.Errorf("failed to persist tipset: %w", err) } expanded, err := cs.expandTipset(ctx, ts.Blocks()[0]) if err != nil { return xerrors.Errorf("errored while expanding tipset: %w", err) } - log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids()) + + if expanded.Key() != ts.Key() { + log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids()) + + tsBlk, err := expanded.Key().ToStorageBlock() + if err != nil { + return xerrors.Errorf("failed to get tipset key block: %w", err) + } + + if err = cs.chainLocalBlockstore.Put(ctx, tsBlk); err != nil { + return xerrors.Errorf("failed to put tipset key block: %w", err) + } + } if err := cs.MaybeTakeHeavierTipSet(ctx, expanded); err != nil { return xerrors.Errorf("MaybeTakeHeavierTipSet failed in PutTipSet: %w", err) @@ -646,7 +657,7 @@ func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) if err := cs.writeHead(ctx, ts); err != nil { log.Errorf("failed to write chain head: %s", err) - return nil + return err } return nil @@ -958,7 +969,24 @@ func (cs *ChainStore) AddToTipSetTracker(ctx context.Context, b *types.BlockHead return nil } -func (cs *ChainStore) PersistBlockHeaders(ctx context.Context, b ...*types.BlockHeader) error { +func (cs *ChainStore) PersistTipset(ctx context.Context, ts *types.TipSet) error { + if err := cs.persistBlockHeaders(ctx, ts.Blocks()...); err != nil { + return xerrors.Errorf("failed to persist block headers: %w", err) + } + + tsBlk, err := ts.Key().ToStorageBlock() + if err != nil { + return xerrors.Errorf("failed to get tipset key block: %w", err) + } + + if err = cs.chainLocalBlockstore.Put(ctx, tsBlk); err != nil { + return xerrors.Errorf("failed to put tipset key block: %w", err) + } + + return nil +} + +func (cs *ChainStore) persistBlockHeaders(ctx context.Context, b ...*types.BlockHeader) error { sbs := make([]block.Block, len(b)) for i, header := range b { @@ -1026,23 +1054,6 @@ func (cs *ChainStore) expandTipset(ctx context.Context, b *types.BlockHeader) (* return types.NewTipSet(all) } -func (cs *ChainStore) AddBlock(ctx context.Context, b *types.BlockHeader) error { - if err := cs.PersistBlockHeaders(ctx, b); err != nil { - return err - } - - ts, err := cs.expandTipset(ctx, b) - if err != nil { - return err - } - - if err := cs.MaybeTakeHeavierTipSet(ctx, ts); err != nil { - return xerrors.Errorf("MaybeTakeHeavierTipSet failed: %w", err) - } - - return nil -} - func (cs *ChainStore) GetGenesis(ctx context.Context) (*types.BlockHeader, error) { data, err := cs.metadataDs.Get(ctx, dstore.NewKey("0")) if err != nil { @@ -1098,6 +1109,10 @@ func (cs *ChainStore) StateBlockstore() bstore.Blockstore { return cs.stateBlockstore } +func (cs *ChainStore) ChainLocalBlockstore() bstore.Blockstore { + return cs.chainLocalBlockstore +} + func ActorStore(ctx context.Context, bs bstore.Blockstore) adt.Store { return adt.WrapStore(ctx, cbor.NewCborStore(bs)) } @@ -1165,6 +1180,24 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t return cs.LoadTipSet(ctx, lbts.Parents()) } +func (cs *ChainStore) GetTipSetByCid(ctx context.Context, c cid.Cid) (*types.TipSet, error) { + blk, err := cs.chainBlockstore.Get(ctx, c) + if err != nil { + return nil, xerrors.Errorf("cannot find tipset with cid %s: %w", c, err) + } + + tsk := new(types.TipSetKey) + if err := tsk.UnmarshalCBOR(bytes.NewReader(blk.RawData())); err != nil { + return nil, xerrors.Errorf("cannot unmarshal block into tipset key: %w", err) + } + + ts, err := cs.GetTipSetFromKey(ctx, *tsk) + if err != nil { + return nil, xerrors.Errorf("cannot get tipset from key: %w", err) + } + return ts, nil +} + func (cs *ChainStore) Weight(ctx context.Context, hts *types.TipSet) (types.BigInt, error) { // todo remove return cs.weight(ctx, cs.StateBlockstore(), hts) } diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 42a57ab7c..f5765fddc 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/ipfs/go-datastore" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" @@ -125,6 +126,51 @@ func TestChainExportImport(t *testing.T) { } } +// Test to check if tipset key cids are being stored on snapshot +func TestChainImportTipsetKeyCid(t *testing.T) { + + ctx := context.Background() + cg, err := gen.NewGenerator() + require.NoError(t, err) + + buf := new(bytes.Buffer) + var last *types.TipSet + var tsKeys []types.TipSetKey + for i := 0; i < 10; i++ { + ts, err := cg.NextTipSet() + require.NoError(t, err) + last = ts.TipSet.TipSet() + tsKeys = append(tsKeys, last.Key()) + } + + if err := cg.ChainStore().Export(ctx, last, last.Height(), false, buf); err != nil { + t.Fatal(err) + } + + nbs := blockstore.NewMemorySync() + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + root, err := cs.Import(ctx, buf) + require.NoError(t, err) + + require.Truef(t, root.Equals(last), "imported chain differed from exported chain") + + err = cs.SetHead(ctx, last) + require.NoError(t, err) + + for _, tsKey := range tsKeys { + _, err := cs.LoadTipSet(ctx, tsKey) + require.NoError(t, err) + + tsCid, err := tsKey.Cid() + require.NoError(t, err) + _, err = cs.ChainLocalBlockstore().Get(ctx, tsCid) + require.NoError(t, err) + + } +} + func TestChainExportImportFull(t *testing.T) { //stm: @CHAIN_GEN_NEXT_TIPSET_001 //stm: @CHAIN_STORE_IMPORT_001, @CHAIN_STORE_EXPORT_001, @CHAIN_STORE_SET_HEAD_001 diff --git a/chain/sync.go b/chain/sync.go index 634313855..b0c8b50ff 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -228,7 +228,7 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { // TODO: IMPORTANT(GARBAGE) this needs to be put in the 'temporary' side of // the blockstore - if err := syncer.store.PersistBlockHeaders(ctx, fts.TipSet().Blocks()...); err != nil { + if err := syncer.store.PersistTipset(ctx, fts.TipSet()); err != nil { log.Warn("failed to persist incoming block header: ", err) return false } @@ -1145,7 +1145,7 @@ func persistMessages(ctx context.Context, bs bstore.Blockstore, bst *exchange.Co } } for _, m := range bst.Secpk { - if m.Signature.Type != crypto.SigTypeSecp256k1 { + if m.Signature.Type != crypto.SigTypeSecp256k1 && m.Signature.Type != crypto.SigTypeDelegated { return xerrors.Errorf("unknown signature type on message %s: %q", m.Cid(), m.Signature.Type) } //log.Infof("putting secp256k1 message: %s", m.Cid()) @@ -1198,16 +1198,13 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet, hts *t ss.SetStage(api.StagePersistHeaders) - toPersist := make([]*types.BlockHeader, 0, len(headers)*int(build.BlocksPerEpoch)) for _, ts := range headers { - toPersist = append(toPersist, ts.Blocks()...) + if err := syncer.store.PersistTipset(ctx, ts); err != nil { + err = xerrors.Errorf("failed to persist synced tipset to the chainstore: %w", err) + ss.Error(err) + return err + } } - if err := syncer.store.PersistBlockHeaders(ctx, toPersist...); err != nil { - err = xerrors.Errorf("failed to persist synced blocks to the chainstore: %w", err) - ss.Error(err) - return err - } - toPersist = nil ss.SetStage(api.StageMessages) diff --git a/chain/sync_test.go b/chain/sync_test.go index 18520a07f..a86d42f17 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -306,13 +306,13 @@ func (tu *syncTestUtil) addSourceNode(gen int) { require.NoError(tu.t, err) tu.t.Cleanup(func() { _ = stop(context.Background()) }) - lastTs := blocks[len(blocks)-1].Blocks - for _, lastB := range lastTs { - cs := out.(*impl.FullNodeAPI).ChainAPI.Chain + lastTs := blocks[len(blocks)-1] + cs := out.(*impl.FullNodeAPI).ChainAPI.Chain + for _, lastB := range lastTs.Blocks { require.NoError(tu.t, cs.AddToTipSetTracker(context.Background(), lastB.Header)) - err = cs.AddBlock(tu.ctx, lastB.Header) - require.NoError(tu.t, err) } + err = cs.PutTipSet(tu.ctx, lastTs.TipSet()) + require.NoError(tu.t, err) tu.genesis = genesis tu.blocks = blocks diff --git a/chain/types/actor.go b/chain/types/actor.go index 29a6865eb..8ba611617 100644 --- a/chain/types/actor.go +++ b/chain/types/actor.go @@ -26,7 +26,7 @@ type ActorV5 struct { Head cid.Cid Nonce uint64 Balance BigInt - // Predictable Address + // Deterministic Address. Address *address.Address } diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index da42d7603..709f7dd88 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -15,7 +15,6 @@ import ( address "github.com/filecoin-project/go-address" abi "github.com/filecoin-project/go-state-types/abi" crypto "github.com/filecoin-project/go-state-types/crypto" - exitcode "github.com/filecoin-project/go-state-types/exitcode" proof "github.com/filecoin-project/go-state-types/proof" ) @@ -1289,154 +1288,6 @@ func (t *ActorV5) UnmarshalCBOR(r io.Reader) (err error) { return nil } -var lengthBufMessageReceipt = []byte{131} - -func (t *MessageReceipt) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - - cw := cbg.NewCborWriter(w) - - if _, err := cw.Write(lengthBufMessageReceipt); err != nil { - return err - } - - // t.ExitCode (exitcode.ExitCode) (int64) - if t.ExitCode >= 0 { - if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil { - return err - } - } else { - if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil { - return err - } - } - - // t.Return ([]uint8) (slice) - if len(t.Return) > cbg.ByteArrayMaxLen { - return xerrors.Errorf("Byte array in field t.Return was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil { - return err - } - - if _, err := cw.Write(t.Return[:]); err != nil { - return err - } - - // t.GasUsed (int64) (int64) - if t.GasUsed >= 0 { - if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil { - return err - } - } else { - if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil { - return err - } - } - return nil -} - -func (t *MessageReceipt) UnmarshalCBOR(r io.Reader) (err error) { - *t = MessageReceipt{} - - cr := cbg.NewCborReader(r) - - maj, extra, err := cr.ReadHeader() - if err != nil { - return err - } - defer func() { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - }() - - if maj != cbg.MajArray { - return fmt.Errorf("cbor input should be of type array") - } - - if extra != 3 { - return fmt.Errorf("cbor input had wrong number of fields") - } - - // t.ExitCode (exitcode.ExitCode) (int64) - { - maj, extra, err := cr.ReadHeader() - var extraI int64 - if err != nil { - return err - } - switch maj { - case cbg.MajUnsignedInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 positive overflow") - } - case cbg.MajNegativeInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") - } - extraI = -1 - extraI - default: - return fmt.Errorf("wrong type for int64 field: %d", maj) - } - - t.ExitCode = exitcode.ExitCode(extraI) - } - // t.Return ([]uint8) (slice) - - maj, extra, err = cr.ReadHeader() - if err != nil { - return err - } - - if extra > cbg.ByteArrayMaxLen { - return fmt.Errorf("t.Return: byte array too large (%d)", extra) - } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - - if extra > 0 { - t.Return = make([]uint8, extra) - } - - if _, err := io.ReadFull(cr, t.Return[:]); err != nil { - return err - } - // t.GasUsed (int64) (int64) - { - maj, extra, err := cr.ReadHeader() - var extraI int64 - if err != nil { - return err - } - switch maj { - case cbg.MajUnsignedInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 positive overflow") - } - case cbg.MajNegativeInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") - } - extraI = -1 - extraI - default: - return fmt.Errorf("wrong type for int64 field: %d", maj) - } - - t.GasUsed = int64(extraI) - } - return nil -} - var lengthBufBlockMsg = []byte{131} func (t *BlockMsg) MarshalCBOR(w io.Writer) error { @@ -1986,3 +1837,224 @@ func (t *StateInfo0) UnmarshalCBOR(r io.Reader) (err error) { return nil } + +var lengthBufEvent = []byte{130} + +func (t *Event) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufEvent); err != nil { + return err + } + + // t.Emitter (abi.ActorID) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Emitter)); err != nil { + return err + } + + // t.Entries ([]types.EventEntry) (slice) + if len(t.Entries) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Entries was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Entries))); err != nil { + return err + } + for _, v := range t.Entries { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + return nil +} + +func (t *Event) UnmarshalCBOR(r io.Reader) (err error) { + *t = Event{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 2 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Emitter (abi.ActorID) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Emitter = abi.ActorID(extra) + + } + // t.Entries ([]types.EventEntry) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Entries: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Entries = make([]EventEntry, extra) + } + + for i := 0; i < int(extra); i++ { + + var v EventEntry + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Entries[i] = v + } + + return nil +} + +var lengthBufEventEntry = []byte{131} + +func (t *EventEntry) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufEventEntry); err != nil { + return err + } + + // t.Flags (uint8) (uint8) + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Flags)); err != nil { + return err + } + + // t.Key (string) (string) + if len(t.Key) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Key was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Key))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Key)); err != nil { + return err + } + + // t.Value ([]uint8) (slice) + if len(t.Value) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Value was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Value))); err != nil { + return err + } + + if _, err := cw.Write(t.Value[:]); err != nil { + return err + } + return nil +} + +func (t *EventEntry) UnmarshalCBOR(r io.Reader) (err error) { + *t = EventEntry{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Flags (uint8) (uint8) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint8 field") + } + if extra > math.MaxUint8 { + return fmt.Errorf("integer in input was too large for uint8 field") + } + t.Flags = uint8(extra) + // t.Key (string) (string) + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.Key = string(sval) + } + // t.Value ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Value: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Value = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Value[:]); err != nil { + return err + } + return nil +} diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go new file mode 100644 index 000000000..18b922234 --- /dev/null +++ b/chain/types/ethtypes/eth_transactions.go @@ -0,0 +1,647 @@ +package ethtypes + +import ( + "bytes" + "encoding/binary" + "fmt" + mathbig "math/big" + + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + typescrypto "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +const Eip1559TxType = 2 + +type EthTx struct { + ChainID EthUint64 `json:"chainId"` + Nonce EthUint64 `json:"nonce"` + Hash EthHash `json:"hash"` + BlockHash *EthHash `json:"blockHash"` + BlockNumber *EthUint64 `json:"blockNumber"` + TransactionIndex *EthUint64 `json:"transactionIndex"` + From EthAddress `json:"from"` + To *EthAddress `json:"to"` + Value EthBigInt `json:"value"` + Type EthUint64 `json:"type"` + Input EthBytes `json:"input"` + Gas EthUint64 `json:"gas"` + MaxFeePerGas EthBigInt `json:"maxFeePerGas"` + MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"` + V EthBigInt `json:"v"` + R EthBigInt `json:"r"` + S EthBigInt `json:"s"` +} + +type EthTxArgs struct { + ChainID int `json:"chainId"` + Nonce int `json:"nonce"` + To *EthAddress `json:"to"` + Value big.Int `json:"value"` + MaxFeePerGas big.Int `json:"maxFeePerGas"` + MaxPriorityFeePerGas big.Int `json:"maxPriorityFeePerGas"` + GasLimit int `json:"gasLimit"` + Input []byte `json:"input"` + V big.Int `json:"v"` + R big.Int `json:"r"` + S big.Int `json:"s"` +} + +func EthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) { + var ( + to *EthAddress + params []byte + paramsReader = bytes.NewReader(msg.Params) + err error + ) + + if msg.Version != 0 { + return EthTxArgs{}, xerrors.Errorf("unsupported msg version: %d", msg.Version) + } + + if msg.To == builtintypes.EthereumAddressManagerActorAddr { + switch msg.Method { + case builtintypes.MethodsEAM.CreateExternal: + params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) + if err != nil { + return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err) + } + default: + return EthTxArgs{}, fmt.Errorf("unsupported EAM method") + } + } else { + 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) + } + + params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) + if err != nil { + return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err) + } + } + } + + 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), + To: to, + Value: msg.Value, + Input: params, + MaxFeePerGas: msg.GasFeeCap, + MaxPriorityFeePerGas: msg.GasPremium, + GasLimit: int(msg.GasLimit), + }, nil +} + +func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, error) { + if tx.ChainID != build.Eip155ChainId { + return nil, xerrors.Errorf("unsupported chain id: %d", tx.ChainID) + } + + var err error + method := builtintypes.MethodSend + var params []byte + var to address.Address + // nil indicates the EAM, only CreateExternal is allowed + if tx.To == nil { + to = builtintypes.EthereumAddressManagerActorAddr + method = builtintypes.MethodsEAM.CreateExternal + if len(tx.Input) == 0 { + return nil, xerrors.New("cannot call CreateExternal without params") + } + + buf := new(bytes.Buffer) + if err = cbg.WriteByteArray(buf, tx.Input); err != nil { + return nil, xerrors.Errorf("failed to serialize Create params: %w", err) + } + + params = buf.Bytes() + } else { + to, err = tx.To.ToFilecoinAddress() + 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 + 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() + } + } + + return &types.Message{ + Version: 0, + To: to, + From: from, + Nonce: uint64(tx.Nonce), + Value: tx.Value, + GasLimit: int64(tx.GasLimit), + GasFeeCap: tx.MaxFeePerGas, + GasPremium: tx.MaxPriorityFeePerGas, + Method: method, + Params: params, + }, nil +} + +func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) { + from, err := tx.Sender() + if err != nil { + return nil, xerrors.Errorf("failed to calculate sender: %w", err) + } + + unsignedMsg, err := tx.ToUnsignedMessage(from) + if err != nil { + return nil, xerrors.Errorf("failed to convert to unsigned msg: %w", err) + } + + siggy, err := tx.Signature() + if err != nil { + return nil, xerrors.Errorf("failed to calculate signature: %w", err) + } + + return &types.SignedMessage{ + Message: *unsignedMsg, + Signature: *siggy, + }, nil +} + +func (tx *EthTxArgs) HashedOriginalRlpMsg() ([]byte, error) { + msg, err := tx.ToRlpUnsignedMsg() + if err != nil { + return nil, err + } + + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hash := hasher.Sum(nil) + return hash, nil +} + +func (tx *EthTxArgs) ToRlpUnsignedMsg() ([]byte, error) { + packed, err := tx.packTxFields() + if err != nil { + return nil, err + } + + encoded, err := EncodeRLP(packed) + if err != nil { + return nil, err + } + return append([]byte{0x02}, encoded...), nil +} + +func (tx *EthTx) ToEthTxArgs() EthTxArgs { + return EthTxArgs{ + ChainID: int(tx.ChainID), + Nonce: int(tx.Nonce), + To: tx.To, + Value: big.Int(tx.Value), + MaxFeePerGas: big.Int(tx.MaxFeePerGas), + MaxPriorityFeePerGas: big.Int(tx.MaxPriorityFeePerGas), + GasLimit: int(tx.Gas), + Input: tx.Input, + V: big.Int(tx.V), + R: big.Int(tx.R), + S: big.Int(tx.S), + } +} + +func (tx *EthTx) TxHash() (EthHash, error) { + ethTxArgs := tx.ToEthTxArgs() + return (ðTxArgs).TxHash() +} + +func (tx *EthTxArgs) TxHash() (EthHash, error) { + rlp, err := tx.ToRlpSignedMsg() + if err != nil { + return EmptyEthHash, err + } + + return EthHashFromTxBytes(rlp), nil +} + +func (tx *EthTxArgs) ToRlpSignedMsg() ([]byte, error) { + packed1, err := tx.packTxFields() + if err != nil { + return nil, err + } + + packed2, err := tx.packSigFields() + if err != nil { + return nil, err + } + + encoded, err := EncodeRLP(append(packed1, packed2...)) + if err != nil { + return nil, err + } + return append([]byte{0x02}, encoded...), nil +} + +func (tx *EthTxArgs) packTxFields() ([]interface{}, error) { + chainId, err := formatInt(tx.ChainID) + if err != nil { + return nil, err + } + + nonce, err := formatInt(tx.Nonce) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := formatBigInt(tx.MaxPriorityFeePerGas) + if err != nil { + return nil, err + } + + maxFeePerGas, err := formatBigInt(tx.MaxFeePerGas) + if err != nil { + return nil, err + } + + gasLimit, err := formatInt(tx.GasLimit) + if err != nil { + return nil, err + } + + value, err := formatBigInt(tx.Value) + if err != nil { + return nil, err + } + + res := []interface{}{ + chainId, + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + formatEthAddr(tx.To), + value, + tx.Input, + []interface{}{}, // access list + } + return res, nil +} + +func (tx *EthTxArgs) packSigFields() ([]interface{}, error) { + r, err := formatBigInt(tx.R) + if err != nil { + return nil, err + } + + s, err := formatBigInt(tx.S) + if err != nil { + return nil, err + } + + v, err := formatBigInt(tx.V) + if err != nil { + return nil, err + } + + res := []interface{}{v, r, s} + return res, nil +} + +func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) { + r := tx.R.Int.Bytes() + s := tx.S.Int.Bytes() + v := tx.V.Int.Bytes() + + sig := append([]byte{}, padLeadingZeros(r, 32)...) + sig = append(sig, padLeadingZeros(s, 32)...) + if len(v) == 0 { + sig = append(sig, 0) + } else { + sig = append(sig, v[0]) + } + + if len(sig) != 65 { + return nil, fmt.Errorf("signature is not 65 bytes") + } + return &typescrypto.Signature{ + Type: typescrypto.SigTypeDelegated, Data: sig, + }, nil +} + +func (tx *EthTxArgs) Sender() (address.Address, error) { + msg, err := tx.ToRlpUnsignedMsg() + if err != nil { + return address.Undef, err + } + + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hash := hasher.Sum(nil) + + sig, err := tx.Signature() + if err != nil { + return address.Undef, err + } + + pubk, err := gocrypto.EcRecover(hash, sig.Data) + if err != nil { + return address.Undef, err + } + + ethAddr, err := EthAddressFromPubKey(pubk) + if err != nil { + return address.Undef, err + } + + ea, err := CastEthAddress(ethAddr) + if err != nil { + return address.Undef, err + } + + return ea.ToFilecoinAddress() +} + +func RecoverSignature(sig typescrypto.Signature) (r, s, v EthBigInt, err error) { + if sig.Type != typescrypto.SigTypeDelegated { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("RecoverSignature only supports Delegated signature") + } + + if len(sig.Data) != 65 { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data)) + } + + r_, err := parseBigInt(sig.Data[0:32]) + if err != nil { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse r into EthBigInt") + } + + s_, err := parseBigInt(sig.Data[32:64]) + if err != nil { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse s into EthBigInt") + } + + v_, err := parseBigInt([]byte{sig.Data[64]}) + if err != nil { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse v into EthBigInt") + } + + return EthBigInt(r_), EthBigInt(s_), EthBigInt(v_), nil +} + +func parseEip1559Tx(data []byte) (*EthTxArgs, error) { + if data[0] != 2 { + return nil, fmt.Errorf("not an EIP-1559 transaction: first byte is not 2") + } + + d, err := DecodeRLP(data[1:]) + if err != nil { + return nil, err + } + decoded, ok := d.([]interface{}) + if !ok { + return nil, fmt.Errorf("not an EIP-1559 transaction: decoded data is not a list") + } + + if len(decoded) != 12 { + return nil, fmt.Errorf("not an EIP-1559 transaction: should have 12 elements in the rlp list") + } + + chainId, err := parseInt(decoded[0]) + if err != nil { + return nil, err + } + + nonce, err := parseInt(decoded[1]) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := parseBigInt(decoded[2]) + if err != nil { + return nil, err + } + + maxFeePerGas, err := parseBigInt(decoded[3]) + if err != nil { + return nil, err + } + + gasLimit, err := parseInt(decoded[4]) + if err != nil { + return nil, err + } + + to, err := parseEthAddr(decoded[5]) + if err != nil { + return nil, err + } + + value, err := parseBigInt(decoded[6]) + if err != nil { + return nil, err + } + + input, err := parseBytes(decoded[7]) + if err != nil { + return nil, err + } + + accessList, ok := decoded[8].([]interface{}) + if !ok || (ok && len(accessList) != 0) { + return nil, fmt.Errorf("access list should be an empty list") + } + + r, err := parseBigInt(decoded[10]) + if err != nil { + return nil, err + } + + s, err := parseBigInt(decoded[11]) + if err != nil { + return nil, err + } + + v, err := parseBigInt(decoded[9]) + if err != nil { + return nil, err + } + + // EIP-1559 and EIP-2930 transactions only support 0 or 1 for v + // Legacy and EIP-155 transactions support other values + // https://github.com/ethers-io/ethers.js/blob/56fabe987bb8c1e4891fdf1e5d3fe8a4c0471751/packages/transactions/src.ts/index.ts#L333 + if !v.Equals(big.NewInt(0)) && !v.Equals(big.NewInt(1)) { + return nil, fmt.Errorf("EIP-1559 transactions only support 0 or 1 for v") + } + + args := EthTxArgs{ + ChainID: chainId, + Nonce: nonce, + To: to, + MaxPriorityFeePerGas: maxPriorityFeePerGas, + MaxFeePerGas: maxFeePerGas, + GasLimit: gasLimit, + Value: value, + Input: input, + V: v, + R: r, + S: s, + } + return &args, nil +} + +func ParseEthTxArgs(data []byte) (*EthTxArgs, error) { + if len(data) == 0 { + return nil, fmt.Errorf("empty data") + } + + if data[0] > 0x7f { + // legacy transaction + return nil, fmt.Errorf("legacy transaction is not supported") + } + + if data[0] == 1 { + // EIP-2930 + return nil, fmt.Errorf("EIP-2930 transaction is not supported") + } + + if data[0] == Eip1559TxType { + // EIP-1559 + return parseEip1559Tx(data) + } + + return nil, fmt.Errorf("unsupported transaction type") +} + +func padLeadingZeros(data []byte, length int) []byte { + if len(data) >= length { + return data + } + zeros := make([]byte, length-len(data)) + return append(zeros, data...) +} + +func removeLeadingZeros(data []byte) []byte { + firstNonZeroIndex := len(data) + for i, b := range data { + if b > 0 { + firstNonZeroIndex = i + break + } + } + return data[firstNonZeroIndex:] +} + +func formatInt(val int) ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, int64(val)) + if err != nil { + return nil, err + } + return removeLeadingZeros(buf.Bytes()), nil +} + +func formatEthAddr(addr *EthAddress) []byte { + if addr == nil { + return nil + } + return addr[:] +} + +func formatBigInt(val big.Int) ([]byte, error) { + b, err := val.Bytes() + if err != nil { + return nil, err + } + return removeLeadingZeros(b), nil +} + +func parseInt(v interface{}) (int, error) { + data, ok := v.([]byte) + if !ok { + return 0, fmt.Errorf("cannot parse interface to int: input is not a byte array") + } + if len(data) == 0 { + return 0, nil + } + if len(data) > 8 { + return 0, fmt.Errorf("cannot parse interface to int: length is more than 8 bytes") + } + var value int64 + r := bytes.NewReader(append(make([]byte, 8-len(data)), data...)) + if err := binary.Read(r, binary.BigEndian, &value); err != nil { + return 0, fmt.Errorf("cannot parse interface to EthUint64: %w", err) + } + return int(value), nil +} + +func parseBigInt(v interface{}) (big.Int, error) { + data, ok := v.([]byte) + if !ok { + return big.Zero(), fmt.Errorf("cannot parse interface to big.Int: input is not a byte array") + } + if len(data) == 0 { + return big.Zero(), nil + } + var b mathbig.Int + b.SetBytes(data) + return big.NewFromGo(&b), nil +} + +func parseBytes(v interface{}) ([]byte, error) { + val, ok := v.([]byte) + if !ok { + return nil, fmt.Errorf("cannot parse interface into bytes: input is not a byte array") + } + return val, nil +} + +func parseEthAddr(v interface{}) (*EthAddress, error) { + b, err := parseBytes(v) + if err != nil { + return nil, err + } + if len(b) == 0 { + return nil, nil + } + addr, err := CastEthAddress(b) + if err != nil { + return nil, err + } + return &addr, nil +} diff --git a/chain/types/ethtypes/eth_transactions_test.go b/chain/types/ethtypes/eth_transactions_test.go new file mode 100644 index 000000000..68abc55dd --- /dev/null +++ b/chain/types/ethtypes/eth_transactions_test.go @@ -0,0 +1,251 @@ +package ethtypes + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/builtin/v10/evm" + init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" + crypto1 "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/lib/sigs" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" +) + +type TxTestcase struct { + TxJSON string + NosigTx string + Input EthBytes + Output EthTxArgs +} + +func TestTxArgs(t *testing.T) { + testcases, err := prepareTxTestcases() + require.Nil(t, err) + require.NotEmpty(t, testcases) + + for i, tc := range testcases { + comment := fmt.Sprintf("case %d: \n%s\n%s", i, tc.TxJSON, hex.EncodeToString(tc.Input)) + + // parse txargs + txArgs, err := ParseEthTxArgs(tc.Input) + require.NoError(t, err, comment) + + msgRecovered, err := txArgs.ToRlpUnsignedMsg() + require.NoError(t, err, comment) + require.Equal(t, tc.NosigTx, "0x"+hex.EncodeToString(msgRecovered), comment) + + // verify signatures + from, err := txArgs.Sender() + require.NoError(t, err, comment) + + smsg, err := txArgs.ToSignedMessage() + require.NoError(t, err, comment) + + err = sigs.Verify(&smsg.Signature, from, msgRecovered) + require.NoError(t, err, comment) + + // verify data + require.Equal(t, tc.Output.ChainID, txArgs.ChainID, comment) + require.Equal(t, tc.Output.Nonce, txArgs.Nonce, comment) + require.Equal(t, tc.Output.To, txArgs.To, comment) + } +} + +func TestSignatures(t *testing.T) { + testcases := []struct { + RawTx string + ExpectedR string + ExpectedS string + ExpectedV string + ExpectErr bool + }{ + { + "0x02f8598401df5e76028301d69083086a5e835532dd808080c080a0457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595a02d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc", + `"0x457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595"`, + `"0x2d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc"`, + `"0x0"`, + false, + }, + { + "0x02f8598401df5e76038301d69083086a5e835532dd808080c001a012a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddfa052a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1", + `"0x12a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddf"`, + `"0x52a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1"`, + `"0x1"`, + false, + }, + { + "0x00", + `""`, + `""`, + `""`, + true, + }, + } + + for _, tc := range testcases { + tx, err := ParseEthTxArgs(mustDecodeHex(tc.RawTx)) + if tc.ExpectErr { + require.Error(t, err) + continue + } + require.Nil(t, err) + + sig, err := tx.Signature() + require.Nil(t, err) + + r, s, v, err := RecoverSignature(*sig) + require.Nil(t, err) + + marshaledR, err := r.MarshalJSON() + require.Nil(t, err) + + marshaledS, err := s.MarshalJSON() + require.Nil(t, err) + + marshaledV, err := v.MarshalJSON() + require.Nil(t, err) + + require.Equal(t, tc.ExpectedR, string(marshaledR)) + require.Equal(t, tc.ExpectedS, string(marshaledS)) + require.Equal(t, tc.ExpectedV, string(marshaledV)) + } +} + +func TestTransformParams(t *testing.T) { + constructorParams, err := actors.SerializeParams(&evm.ConstructorParams{ + Initcode: mustDecodeHex("0x1122334455"), + }) + require.Nil(t, err) + + evmActorCid, ok := actors.GetActorCodeID(actorstypes.Version10, "reward") + require.True(t, ok) + + params, err := actors.SerializeParams(&init10.ExecParams{ + CodeCID: evmActorCid, + ConstructorParams: constructorParams, + }) + require.Nil(t, err) + + var exec init10.ExecParams + reader := bytes.NewReader(params) + err1 := exec.UnmarshalCBOR(reader) + require.Nil(t, err1) + + var evmParams evm.ConstructorParams + reader1 := bytes.NewReader(exec.ConstructorParams) + err1 = evmParams.UnmarshalCBOR(reader1) + require.Nil(t, err1) + + require.Equal(t, mustDecodeHex("0x1122334455"), evmParams.Initcode) +} + +func TestEcRecover(t *testing.T) { + rHex := "0x479ff7fa64cf8bf641eb81635d1e8a698530d2f219951d234539e6d074819529" + sHex := "0x4b6146d27be50cdbb2853ba9a42f207af8d730272f1ebe9c9a78aeef1d6aa924" + fromHex := "0x3947D223fc5415f43ea099866AB62B1d4D33814D" + v := byte(0) + + msgHex := "0x02f1030185012a05f2008504a817c800825208942b87d1cb599bc2a606db9a0169fcec96af04ad3a880de0b6b3a764000080c0" + pubKeyHex := "0x048362749392a0e192eff600d21155236c5a0648d300a8e0e44d8617712c7c96384c75825dc5c7595df2a5005fd8a0f7c809119fb9ab36403ed712244fc329348e" + + msg := mustDecodeHex(msgHex) + pubKey := mustDecodeHex(pubKeyHex) + r := mustDecodeHex(rHex) + s := mustDecodeHex(sHex) + from := mustDecodeHex(fromHex) + + sig := append(r, s...) + sig = append(sig, v) + require.Equal(t, 65, len(sig)) + + sha := sha3.NewLegacyKeccak256() + sha.Write(msg) + h := sha.Sum(nil) + + pubk, err := gocrypto.EcRecover(h, sig) + require.Nil(t, err) + require.Equal(t, pubKey, pubk) + + sha.Reset() + sha.Write(pubk[1:]) + h = sha.Sum(nil) + h = h[len(h)-20:] + + require.Equal(t, from, h) +} + +func TestDelegatedSigner(t *testing.T) { + rHex := "0xcf1fa52fae9154ba21d67aeca9b42adfe186eb9e426c441051a8473efd190848" + sHex := "0x0e6c8c79ffaf35fb8f136c8cf6c5656f1f3befad21f2644321aa6dba58d68737" + v := byte(0) + + msgHex := "0x02f08401df5e76038502540be400843b9aca008398968094ff000000000000000000000000000000000003f2832dc6c080c0" + pubKeyHex := "0x04cfecc0520d906cbfea387759246e89d85e2998843e56ad1c41de247ce10b3e4c453aa73c8de13c178d94461b6fa3f8b6f74406ce43d2fbab6992d0b283394242" + + msg := mustDecodeHex(msgHex) + pubk := mustDecodeHex(pubKeyHex) + r := mustDecodeHex(rHex) + s := mustDecodeHex(sHex) + + addrHash, err := EthAddressFromPubKey(pubk) + require.NoError(t, err) + + from, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, addrHash) + require.NoError(t, err) + + sig := append(r, s...) + sig = append(sig, v) + require.Equal(t, 65, len(sig)) + + signature := &crypto1.Signature{ + Type: crypto1.SigTypeDelegated, + Data: sig, + } + + err = sigs.Verify(signature, from, msg) + require.NoError(t, err) +} + +func prepareTxTestcases() ([]TxTestcase, error) { + tcstr := `[{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0706d871013403cf8b965dfa7f2be5a4d185d746da45b21d5a67c667c26d255d6a02e68a14f386aa325ce8e82d30405107d53103d038cf20e40af961ef3a3963608","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0df137d0a6733354b2f2419a4ea5fe77d333deca28b2fe091d76190b51c2bae73a0232cbf9c29b8840cbf104ff77360fbf3ca4acda29b5e230636e19ac253ad92de","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b9ebc36653a4800816f71ceacf93a1ee601a136916a3476ea9073a9a55ff026aa0647665249b12e8d1d1773b91844588ed70f65c91bc088ccb259ec0f0a24330d5","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0122dd8468dbd34e111e1a5ea1997199be633aa3bc9c1a7ee27dc3a8eda39c29da07cb99cd28ac67f55e507a8b8ef5b931c56cacf79273a4a2969a004a4b4a2864a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a016e3f30a612fc802bb64b765325ecf78f2769b879a9acf62f07669f9723335d6a0781bb3444a73819f28233f1eebf8c3a4de288842fd73c2e05a7a7b0c288d5b25","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0b652a447bdcdd1906ed86406ee543ee06023e4f762784c1d3aaf4c3bd85c6a17a0368ae9995e15258f14b74f937e97140a659d052d341674be0c24452257b56b30","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02d8215d8408d2f4b83a2e68f4aad6fe5dee97d7ef6a43b02ec413ead2215ac80a0641a43cebd6905e3e324c0dd06585d5ffc9b971b519045999c48e31db7aa7f9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88a82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0da68784e191ce0806527d389f84b5d15bed3908e1c2cc0d8f0cea7a29eb0dba39f231a0b438b7d0f0f57292c68dc174d4ee6df7add933ab4e0b3789f597a7d3b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a059aecc1d365ee0dc56a577d162f04c0912a5c5b62f889cff1acc706ac17a4489a017209b3ec43a10a40c5863a2b7a1ee823380ad42697a5f7d5f537c230583a4c7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0dc1eb40f93e311f3f9a94d8a695db2bbb38973ce097121875885e4bc54f18152a0075da0bd405bb4f5c69034daaf8f40052b941fae5b9f3b8df218d80fb4d7ea99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a083cf6701aee00872946b6550c059f028f72e3052acb8cc9c25b830ace860e046a03fd969d73e995d43896659f94d3956a17da18451050349e7db6f7881f8c057d3","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c5a545f2d94e719068d9a43b01879bcb46b56e236dd378dd26ef3b8e4ec8314aa04024b9936960b9b156405e4f3e0b6562518df8778324a927381e380b23f47fb8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09d9a8ee802486b826348a76346987b3e7331d70ef0c0257ff976ceebef1141a2a07d97d14ed877c16bd932f08a67c374e773ee3337d512ff8241c8d78566a04d46","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a024ad1ec1578f51beb2b574507bda7691a486cdbc9c22add01ad4c1f686beb567a048445e0fe8945b8052e5e87139690c0615a11c52503b226cf23610c999eada40","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f36ff02ab3e90d2de77cdb24423dc39ca5c959429db62cb5c9ed4f0c9e04703aa0476bf841b0602af44039801d4e68648971f63fc2152002b127be6d914d4fc5ca","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a08267ae8838a8a5d9c2a761c182b5759184b7672b761278d499c1514fb6e8a495a023aa268f67da7728767e114fdec4d141bf649e0ad931117b5b325834dbf72803","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a06ab9d5988105d28dd090e509c8caabaa7773fc08ec5ef3dfeae532e01938ff69a078bca296df26dd2497a49110e138a49a67a6e232a35524b041d04a10fc583651","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a031d51b866a02a9966250d312ed6cb4e083f9131ad8f6bb5814074375093d7536a03f8f819c4011dd54348930b6f98f365de8060b487ada38a62a5617aab6cc6e09","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0def168136c0532ec148a9e200e3cc1b22f90c7bbc5d9ef25ac0c5d342e8f3784a022f94642dfc81ba321b3e09879888332fa7c25b623bead7686e3e493c0911b55","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0626f43b80260f84cde2c67538c5cfbd328ce85b0f934e8568769e51709b100a7a0283fff5dbfde72b72e2b74c464b1add985d72750be3f4e16ae8ffb4747a40ff2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d3bfebc6597304c6a06491f68d2ac149fc233d28e81af48dd5b1f83e6ff951d2a06668da06d86aba341971dabb58016ca7764cd4b4c1634e3f829dcc8ef8bca4f6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0d45b9fd9a2a3fdf79805cf73b70348037cc69927209a5e3728fe62cbe9543f03a02f5f8477666487ee5148a65ce59f400beac7c208369162b2d555411314d358fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a08c13c10490bc20cb1e55dc54ececb37a6c9cc8d013dbe513feacbb0416f09feba045c4e038759a0901820091e043db326b1bf9a8a1cd046ac72629969497c6a86f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b904edf8eb9b6beb9cde9e1fae538e12f8d40e9124ace0cba2eee8cbbe77aa10a0788a0bd9a6fb98e7230f5db89be2f5067d1a227ba277b9cb155fb5859c57aae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a04c43dab94dd746973a1f7f051cc520cc01e93e9c6c55147cef34e5fdc0b182a2a06d148cc6ec017f9aeb6442a17d72e388ffc835950e19abd0c06057520f893542","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a025b50c1db31c0ae7aaa73374659201b54b71488efecbb6985dc50015abde7e36a04dd8cf68920de7232ab8d1fb28ab94ac05466c1f9d9a3a658f2054fce7868e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0a43aba5078d2da3ecc1ec0c67191f8cf58f29f5b4db7f8d4765ea691ddbd4195a0110e568c803db5ea587b406f452cf49ddf6b6f24d41207973d6c785ffaed1454","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a00caeadf2fcba95f0deab5ee4899348ecac4a18eeb09317d6f8156b891626d219a0549c5376aba320889c2f7b61fd4a51aec5f9a1d9ed9b26cef0a3bee52fac4989","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0621255015626b35acf19629ce318999336441537920f9f3ff1bfd44e54d8abd3a03b3426f8fa963debdfa6b44561772bdebc9524c7f63abd0d947b678f5e966502","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b73c3ba53fc5a0f7fab636cc2b826c3873cda5d0be9dd2100fdceae7899f3310a0491905f676063924cf847fdf2e488be4606ce351748e5c88d49ed50c8d595c94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d67e28d31489af5129c4832af814a01e0baa5e5ba6245fe2d3304693ceea48e0a03bc06f1c6dd01a14826c67aa35258c0bbf7c516a9bb21e9190eaa8d3768f49bb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0a5368984aca4bc1e3d7ebc7ae4ead5e09ffd3b4b4712d039c19fdac948e5952ea065953ace0a29210440d6a0f05d6b43f482950b463b3be6b23fc63452c94b9446","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f88e82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a03dd64e48a1ae228665b3f180367997ee96bc60ee226615c900e3d86634044328a00f6cdb24633e75fa65f6b93fce9b084c1f30dd03dde97d01f25c6f10f34d5d9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84b82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88f82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a07475efeb8dd5bf4ba7efb31ab67a9077401ed71f4e8dd13e7058ce5cfeb5a0f2a01046e93a5258bf320bc392173a49b6fef15976be4c1210f2e367af223ad8c026","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84c82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02080212bb64a798e1e138e4991ab830cf04d37ffeedf6fde7eba0eb7d972b350a02aff43f9e5ca8d6cea6e918391188fa37bdb91b864eadec705f7c69c4a61bc5a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0e41c052d72950a563b8ed7fb15855beabea43ff5b038bd6a3ccc6416e3498619a0568bbd7cbff31a47e1d0b9712f382c52e74b7b28cbcb8458974d82a8d54ddc57","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0fa33b63666310ca1c72fc5d82639c5b8e2a7638910be7bee23ada9f139c6b891a02012cad8e991beea7dcf0b6e9346b0228699698e183e2fadfc5b9b880601af9b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0bc6ae4e92e7a20d5ff61258653dffda636cee0fd97dd156eac7a1f231f1f2785a0323055e0e0bed496b3fec30be292338d0956ecf8baeeb34458230821589aa7fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a055f63a6bef8e23dc437ff4ac9349a59fcde2f72d1879de50b0d3686ff648749da04cf8034df06cf6f15f31bb55979b40eeacbd28fb1d745e608acdc088e22beb66","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c4a0253448dad999692c1bf3cfb5de9e95a2e96da4e1f64133ada452a825fe9aa0757b576ceb7a2c494819960ac59e9d3a4e3da384f23c0e88ada758dc265eae94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0ed0db75f41b2b8b89768ce5ad08716aff149dc1d5a2e593140d8964eb2da3229a02e5248cca9b5af340d73271cad4d690f7efa11c9278824aca528eb15d28aec4d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a07108fbbabc45826dbdc8e4cf831240fb39ead7bd4b8ec5d8de64d04e2885e554a04dae4fb4bdbabb9d8f923d579e75ee980da1b4fac5773ec68f395af240f037f0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09c9d3b0d7b58bfe81a6881b9db184e0ade03c1ad11aa8f1566e2f24f50f85525a06c10cf91f4dbc24d0f78ef09a8e2310d349a034cec7e86e807d7a48ea26161e1","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f8423b51e513618c6a4bdd2696479d91c760e11ea24657dd27fa6eb9b7da8c0ea07e9456113fb034718d1b4f4e09ade1ce78251a8c86f298b152850bc5925156cb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c286f4ee350eab70273cf9a952537534446a0f39e9bfea7340eabc04396a0e3da01e1302ae987a69836ec2c9266e6fe623db5fcdc566e37084c0c57630c4de8ee6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a09dee3fa88e365133a18035618af718a045e1a957f10f50c632f23923fd337b9ba06bbbd59489849803f8c61138932ac1a8361edb4c80789d030542829c0a2b5b7f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89482013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a039357ad40087d17551ca2b94723f0394185a993671db02172a7de70c24054852a046c84070dfadd244b358690e5b89c75f3988b21b6614e6e3af2f8ca302d6c42a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85182013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89582013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c991c81705a4c53a9255e72beb8243638c68f10c63b082755972bbbe15245d12a014f6852ae34c92882559e6810d4372109930a23b522368fdef2c85ce04e27839","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85282013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"}]` + + testcases := []struct { + Input EthBytes `json:"input"` + Output string `json:"output"` + NosigTx string `json:"nosigTx"` + }{} + + err := json.Unmarshal([]byte(tcstr), &testcases) + if err != nil { + return nil, err + } + + res := []TxTestcase{} + for _, tc := range testcases { + tx := EthTxArgs{} + err := json.Unmarshal([]byte(tc.Output), &tx) + if err != nil { + return nil, err + } + res = append(res, TxTestcase{ + Input: tc.Input, + Output: tx, + TxJSON: tc.Output, + NosigTx: tc.NosigTx, + }) + } + + return res, err +} diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go new file mode 100644 index 000000000..235cc7c79 --- /dev/null +++ b/chain/types/ethtypes/eth_types.go @@ -0,0 +1,629 @@ +package ethtypes + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + mathbig "math/big" + "strconv" + "strings" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" + "github.com/multiformats/go-varint" + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + + "github.com/filecoin-project/lotus/build" +) + +var ( + EthTopic1 = "topic1" + EthTopic2 = "topic2" + EthTopic3 = "topic3" + EthTopic4 = "topic4" +) + +var ErrInvalidAddress = errors.New("invalid Filecoin Eth address") + +type EthUint64 uint64 + +func (e EthUint64) MarshalJSON() ([]byte, error) { + return json.Marshal(e.Hex()) +} + +func (e *EthUint64) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64) + if err != nil { + return err + } + eint := EthUint64(parsedInt) + *e = eint + return nil +} + +func EthUint64FromHex(s string) (EthUint64, error) { + parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64) + if err != nil { + return EthUint64(0), err + } + return EthUint64(parsedInt), nil +} + +func (e EthUint64) Hex() string { + if e == 0 { + return "0x0" + } + return fmt.Sprintf("0x%x", e) +} + +// EthBigInt represents a large integer whose zero value serializes to "0x0". +type EthBigInt big.Int + +var EthBigIntZero = EthBigInt{Int: big.Zero().Int} + +func (e EthBigInt) MarshalJSON() ([]byte, error) { + if e.Int == nil || e.Int.BitLen() == 0 { + return json.Marshal("0x0") + } + return json.Marshal(fmt.Sprintf("0x%x", e.Int)) +} + +func (e *EthBigInt) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + replaced := strings.Replace(s, "0x", "", -1) + if len(replaced)%2 == 1 { + replaced = "0" + replaced + } + + i := new(mathbig.Int) + i.SetString(replaced, 16) + + *e = EthBigInt(big.NewFromGo(i)) + return nil +} + +// EthBytes represent arbitrary bytes. A nil or empty slice serializes to "0x". +type EthBytes []byte + +func (e EthBytes) MarshalJSON() ([]byte, error) { + if len(e) == 0 { + return json.Marshal("0x") + } + s := hex.EncodeToString(e) + return json.Marshal("0x" + s) +} + +func (e *EthBytes) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + s = strings.Replace(s, "0x", "", -1) + if len(s)%2 == 1 { + s = "0" + s + } + + decoded, err := hex.DecodeString(s) + if err != nil { + return err + } + + *e = decoded + return nil +} + +type EthBlock struct { + Hash EthHash `json:"hash"` + ParentHash EthHash `json:"parentHash"` + Sha3Uncles EthHash `json:"sha3Uncles"` + Miner EthAddress `json:"miner"` + StateRoot EthHash `json:"stateRoot"` + TransactionsRoot EthHash `json:"transactionsRoot"` + ReceiptsRoot EthHash `json:"receiptsRoot"` + LogsBloom EthBytes `json:"logsBloom"` + Difficulty EthUint64 `json:"difficulty"` + TotalDifficulty EthUint64 `json:"totalDifficulty"` + Number EthUint64 `json:"number"` + GasLimit EthUint64 `json:"gasLimit"` + GasUsed EthUint64 `json:"gasUsed"` + Timestamp EthUint64 `json:"timestamp"` + Extradata []byte `json:"extraData"` + MixHash EthHash `json:"mixHash"` + Nonce EthNonce `json:"nonce"` + BaseFeePerGas EthBigInt `json:"baseFeePerGas"` + Size EthUint64 `json:"size"` + // can be []EthTx or []string depending on query params + Transactions []interface{} `json:"transactions"` + Uncles []EthHash `json:"uncles"` +} + +var ( + EmptyEthBloom = [256]byte{} + EmptyEthHash = EthHash{} + EmptyEthInt = EthUint64(0) + EmptyEthNonce = [8]byte{0, 0, 0, 0, 0, 0, 0, 0} +) + +func NewEthBlock() EthBlock { + return EthBlock{ + Sha3Uncles: EmptyEthHash, + StateRoot: EmptyEthHash, + TransactionsRoot: EmptyEthHash, + ReceiptsRoot: EmptyEthHash, + Difficulty: EmptyEthInt, + LogsBloom: EmptyEthBloom[:], + Extradata: []byte{}, + MixHash: EmptyEthHash, + Nonce: EmptyEthNonce, + GasLimit: EthUint64(build.BlockGasLimit), // TODO we map Ethereum blocks to Filecoin tipsets; this is inconsistent. + Uncles: []EthHash{}, + Transactions: []interface{}{}, + } +} + +type EthCall struct { + From *EthAddress `json:"from"` + To *EthAddress `json:"to"` + Gas EthUint64 `json:"gas"` + GasPrice EthBigInt `json:"gasPrice"` + Value EthBigInt `json:"value"` + Data EthBytes `json:"data"` +} + +func (c *EthCall) UnmarshalJSON(b []byte) error { + type TempEthCall EthCall + var params TempEthCall + + if err := json.Unmarshal(b, ¶ms); err != nil { + return err + } + *c = EthCall(params) + return nil +} + +const ( + EthAddressLength = 20 + EthHashLength = 32 +) + +type EthNonce [8]byte + +func (n EthNonce) String() string { + return "0x" + hex.EncodeToString(n[:]) +} + +func (n EthNonce) MarshalJSON() ([]byte, error) { + return json.Marshal(n.String()) +} + +func (n *EthNonce) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + s = strings.Replace(s, "0x", "", -1) + if len(s)%2 == 1 { + s = "0" + s + } + + decoded, err := hex.DecodeString(s) + if err != nil { + return err + } + copy(n[:], decoded[:8]) + return nil +} + +type EthAddress [EthAddressLength]byte + +// EthAddressFromPubKey returns the Ethereum address corresponding to an +// uncompressed secp256k1 public key. +func EthAddressFromPubKey(pubk []byte) ([]byte, error) { + // if we get an uncompressed public key (that's what we get from the library, + // but putting this check here for defensiveness), strip the prefix + const pubKeyLen = 65 + if len(pubk) != pubKeyLen { + return nil, fmt.Errorf("public key should have %d in length, but got %d", pubKeyLen, len(pubk)) + } + if pubk[0] != 0x04 { + return nil, fmt.Errorf("expected first byte of secp256k1 to be 0x04 (uncompressed)") + } + pubk = pubk[1:] + + // Calculate the Ethereum address based on the keccak hash of the pubkey. + hasher := sha3.NewLegacyKeccak256() + hasher.Write(pubk) + ethAddr := hasher.Sum(nil)[12:] + return ethAddr, nil +} + +func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) { + switch addr.Protocol() { + case address.ID: + id, err := address.IDFromAddress(addr) + if err != nil { + return EthAddress{}, err + } + var ethaddr EthAddress + ethaddr[0] = 0xff + binary.BigEndian.PutUint64(ethaddr[12:], id) + return ethaddr, nil + case address.Delegated: + payload := addr.Payload() + namespace, n, err := varint.FromUvarint(payload) + if err != nil { + return EthAddress{}, xerrors.Errorf("invalid delegated address namespace in: %s", addr) + } + payload = payload[n:] + if namespace == builtintypes.EthereumAddressManagerActorID { + return CastEthAddress(payload) + } + } + return EthAddress{}, ErrInvalidAddress +} + +// ParseEthAddress parses an Ethereum address from a hex string. +func ParseEthAddress(s string) (EthAddress, error) { + b, err := decodeHexString(s, EthAddressLength) + if err != nil { + return EthAddress{}, err + } + var h EthAddress + copy(h[EthAddressLength-len(b):], b) + return h, nil +} + +// CastEthAddress interprets bytes as an EthAddress, performing some basic checks. +func CastEthAddress(b []byte) (EthAddress, error) { + var a EthAddress + if len(b) != EthAddressLength { + return EthAddress{}, xerrors.Errorf("cannot parse bytes into an EthAddress: incorrect input length") + } + copy(a[:], b[:]) + return a, nil +} + +func (ea EthAddress) String() string { + return "0x" + hex.EncodeToString(ea[:]) +} + +func (ea EthAddress) MarshalJSON() ([]byte, error) { + return json.Marshal(ea.String()) +} + +func (ea *EthAddress) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + addr, err := ParseEthAddress(s) + if err != nil { + return err + } + copy(ea[:], addr[:]) + return nil +} + +func (ea EthAddress) IsMaskedID() bool { + idmask := [12]byte{0xff} + return bytes.Equal(ea[:12], idmask[:]) +} + +func (ea EthAddress) ToFilecoinAddress() (address.Address, error) { + if ea.IsMaskedID() { + // This is a masked ID address. + id := binary.BigEndian.Uint64(ea[12:]) + return address.NewIDAddress(id) + } + + // Otherwise, translate the address into an address controlled by the + // Ethereum Address Manager. + addr, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, ea[:]) + if err != nil { + return address.Undef, fmt.Errorf("failed to translate supplied address (%s) into a "+ + "Filecoin f4 address: %w", hex.EncodeToString(ea[:]), err) + } + return addr, nil +} + +type EthHash [EthHashLength]byte + +func (h EthHash) MarshalJSON() ([]byte, error) { + return json.Marshal(h.String()) +} + +func (h *EthHash) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + hash, err := ParseEthHash(s) + if err != nil { + return err + } + copy(h[:], hash[:]) + return nil +} + +func decodeHexString(s string, expectedLen int) ([]byte, error) { + s = handleHexStringPrefix(s) + if len(s) != expectedLen*2 { + return nil, xerrors.Errorf("expected hex string length sans prefix %d, got %d", expectedLen*2, len(s)) + } + b, err := hex.DecodeString(s) + if err != nil { + return nil, xerrors.Errorf("cannot parse hex value: %w", err) + } + return b, nil +} + +func DecodeHexString(s string) ([]byte, error) { + s = handleHexStringPrefix(s) + b, err := hex.DecodeString(s) + if err != nil { + return nil, xerrors.Errorf("cannot parse hex value: %w", err) + } + return b, nil +} + +func handleHexStringPrefix(s string) string { + // Strip the leading 0x or 0X prefix since hex.DecodeString does not support it. + if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { + s = s[2:] + } + // Sometimes clients will omit a leading zero in a byte; pad so we can decode correctly. + if len(s)%2 == 1 { + s = "0" + s + } + return s +} + +func EthHashFromCid(c cid.Cid) (EthHash, error) { + return ParseEthHash(c.Hash().HexString()[8:]) +} + +func ParseEthHash(s string) (EthHash, error) { + b, err := decodeHexString(s, EthHashLength) + if err != nil { + return EthHash{}, err + } + var h EthHash + copy(h[EthHashLength-len(b):], b) + return h, nil +} + +func EthHashFromTxBytes(b []byte) EthHash { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(b) + hash := hasher.Sum(nil) + + var ethHash EthHash + copy(ethHash[:], hash) + return ethHash +} + +func (h EthHash) String() string { + return "0x" + hex.EncodeToString(h[:]) +} + +// Should ONLY be used for blocks and Filecoin messages. Eth transactions expect a different hashing scheme. +func (h EthHash) ToCid() cid.Cid { + // err is always nil + mh, _ := multihash.EncodeName(h[:], "blake2b-256") + + return cid.NewCidV1(cid.DagCBOR, mh) +} + +type EthFeeHistory struct { + OldestBlock uint64 `json:"oldestBlock"` + BaseFeePerGas []EthBigInt `json:"baseFeePerGas"` + GasUsedRatio []float64 `json:"gasUsedRatio"` + Reward *[][]EthBigInt `json:"reward,omitempty"` +} + +type EthFilterID EthHash + +// An opaque identifier generated by the Lotus node to refer to an active subscription. +type EthSubscriptionID EthHash + +type EthFilterSpec struct { + // Interpreted as an epoch or one of "latest" for last mined block, "earliest" for first, + // "pending" for not yet committed messages. + // Optional, default: "latest". + FromBlock *string `json:"fromBlock,omitempty"` + + // Interpreted as an epoch or one of "latest" for last mined block, "earliest" for first, + // "pending" for not yet committed messages. + // Optional, default: "latest". + ToBlock *string `json:"toBlock,omitempty"` + + // Actor address or a list of addresses from which event logs should originate. + // Optional, default nil. + // The JSON decoding must treat a string as equivalent to an array with one value, for example + // "0x8888f1f195afa192cfee86069858" must be decoded as [ "0x8888f1f195afa192cfee86069858" ] + Address EthAddressList `json:"address"` + + // List of topics to be matched. + // Optional, default: empty list + Topics EthTopicSpec `json:"topics"` + + // Restricts event logs returned to those emitted from messages contained in this tipset. + // If BlockHash is present in in the filter criteria, then neither FromBlock nor ToBlock are allowed. + // Added in EIP-234 + BlockHash *EthHash `json:"blockHash,omitempty"` +} + +// EthAddressSpec represents a list of addresses. +// The JSON decoding must treat a string as equivalent to an array with one value, for example +// "0x8888f1f195afa192cfee86069858" must be decoded as [ "0x8888f1f195afa192cfee86069858" ] +type EthAddressList []EthAddress + +func (e *EthAddressList) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte{'n', 'u', 'l', 'l'}) { + return nil + } + if len(b) > 0 && b[0] == '[' { + var addrs []EthAddress + err := json.Unmarshal(b, &addrs) + if err != nil { + return err + } + *e = addrs + return nil + } + var addr EthAddress + err := json.Unmarshal(b, &addr) + if err != nil { + return err + } + *e = []EthAddress{addr} + return nil +} + +// TopicSpec represents a specification for matching by topic. An empty spec means all topics +// will be matched. Otherwise topics are matched conjunctively in the first dimension of the +// slice and disjunctively in the second dimension. Topics are matched in order. +// An event log with topics [A, B] will be matched by the following topic specs: +// [] "all" +// [[A]] "A in first position (and anything after)" +// [nil, [B] ] "anything in first position AND B in second position (and anything after)" +// [[A], [B]] "A in first position AND B in second position (and anything after)" +// [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)" +// +// The JSON decoding must treat string values as equivalent to arrays with one value, for example +// { "A", [ "B", "C" ] } must be decoded as [ [ A ], [ B, C ] ] +type EthTopicSpec []EthHashList + +// EthHashList represents a list of EthHashes. +// The JSON decoding treats string values as equivalent to arrays with one value. +type EthHashList []EthHash + +func (e *EthHashList) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte{'n', 'u', 'l', 'l'}) { + return nil + } + if len(b) > 0 && b[0] == '[' { + var hashes []EthHash + err := json.Unmarshal(b, &hashes) + if err != nil { + return err + } + *e = hashes + return nil + } + var hash EthHash + err := json.Unmarshal(b, &hash) + if err != nil { + return err + } + *e = []EthHash{hash} + return nil +} + +// FilterResult represents the response from executing a filter: a list of block hashes, a list of transaction hashes +// or a list of logs +// This is a union type. Only one field will be populated. +// The JSON encoding must produce an array of the populated field. +type EthFilterResult struct { + Results []interface{} +} + +func (h EthFilterResult) MarshalJSON() ([]byte, error) { + if h.Results != nil { + return json.Marshal(h.Results) + } + return []byte{'[', ']'}, nil +} + +func (h *EthFilterResult) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte{'n', 'u', 'l', 'l'}) { + return nil + } + err := json.Unmarshal(b, &h.Results) + return err +} + +// EthLog represents the results of an event filter execution. +type EthLog struct { + // Address is the address of the actor that produced the event log. + Address EthAddress `json:"address"` + + // Data is the value of the event log, excluding topics + Data EthBytes `json:"data"` + + // List of topics associated with the event log. + Topics []EthBytes `json:"topics"` + + // Following fields are derived from the transaction containing the log + + // Indicates whether the log was removed due to a chain reorganization. + Removed bool `json:"removed"` + + // LogIndex is the index of the event log in the sequence of events produced by the message execution. + // (this is the index in the events AMT on the message receipt) + LogIndex EthUint64 `json:"logIndex"` + + // TransactionIndex is the index in the tipset of the transaction that produced the event log. + // The index corresponds to the sequence of messages produced by ChainGetParentMessages + TransactionIndex EthUint64 `json:"transactionIndex"` + + // TransactionHash is the hash of the RLP message that produced the event log. + TransactionHash EthHash `json:"transactionHash"` + + // BlockHash is the hash of the tipset containing the message that produced the log. + BlockHash EthHash `json:"blockHash"` + + // BlockNumber is the epoch of the tipset containing the message. + BlockNumber EthUint64 `json:"blockNumber"` +} + +type EthSubscriptionParams struct { + // List of topics to be matched. + // Optional, default: empty list + Topics EthTopicSpec `json:"topics,omitempty"` +} + +type EthSubscriptionResponse struct { + // The persistent identifier for the subscription which can be used to unsubscribe. + SubscriptionID EthSubscriptionID `json:"subscription"` + + // The object matching the subscription. This may be a Block (tipset), a Transaction (message) or an EthLog + Result interface{} `json:"result"` +} + +func GetContractEthAddressFromCode(sender EthAddress, salt [32]byte, initcode []byte) (EthAddress, error) { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(initcode) + inithash := hasher.Sum(nil) + + hasher.Reset() + hasher.Write([]byte{0xff}) + hasher.Write(sender[:]) + hasher.Write(salt[:]) + hasher.Write(inithash) + + ethAddr, err := CastEthAddress(hasher.Sum(nil)[12:]) + if err != nil { + return [20]byte{}, err + } + + return ethAddr, nil +} diff --git a/chain/types/ethtypes/eth_types_test.go b/chain/types/ethtypes/eth_types_test.go new file mode 100644 index 000000000..89c38ba29 --- /dev/null +++ b/chain/types/ethtypes/eth_types_test.go @@ -0,0 +1,384 @@ +package ethtypes + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" +) + +type TestCase struct { + Input interface{} + Output interface{} +} + +func TestEthIntMarshalJSON(t *testing.T) { + // https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding + testcases := []TestCase{ + {EthUint64(0), []byte("\"0x0\"")}, + {EthUint64(65), []byte("\"0x41\"")}, + {EthUint64(1024), []byte("\"0x400\"")}, + } + + for _, tc := range testcases { + j, err := tc.Input.(EthUint64).MarshalJSON() + require.Nil(t, err) + require.Equal(t, j, tc.Output) + } +} + +func TestEthIntUnmarshalJSON(t *testing.T) { + testcases := []TestCase{ + {[]byte("\"0x0\""), EthUint64(0)}, + {[]byte("\"0x41\""), EthUint64(65)}, + {[]byte("\"0x400\""), 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) + } +} + +func TestEthBigIntMarshalJSON(t *testing.T) { + testcases := []TestCase{ + {EthBigInt(big.NewInt(0)), []byte("\"0x0\"")}, + {EthBigInt(big.NewInt(65)), []byte("\"0x41\"")}, + {EthBigInt(big.NewInt(1024)), []byte("\"0x400\"")}, + {EthBigInt(big.Int{}), []byte("\"0x0\"")}, + } + for _, tc := range testcases { + j, err := tc.Input.(EthBigInt).MarshalJSON() + require.Nil(t, err) + require.Equal(t, j, tc.Output) + } +} + +func TestEthBigIntUnmarshalJSON(t *testing.T) { + testcases := []TestCase{ + {[]byte("\"0x0\""), EthBigInt(big.MustFromString("0"))}, + {[]byte("\"0x41\""), EthBigInt(big.MustFromString("65"))}, + {[]byte("\"0x400\""), EthBigInt(big.MustFromString("1024"))}, + {[]byte("\"0xff1000000000000000000000000\""), EthBigInt(big.MustFromString("323330131220712761719252861321216"))}, + } + + for _, tc := range testcases { + var i EthBigInt + err := i.UnmarshalJSON(tc.Input.([]byte)) + require.Nil(t, err) + require.Equal(t, i, tc.Output) + } +} + +func TestEthHash(t *testing.T) { + testcases := []string{ + `"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"`, + `"0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"`, + } + + for _, hash := range testcases { + var h EthHash + err := h.UnmarshalJSON([]byte(hash)) + + require.Nil(t, err) + require.Equal(t, h.String(), strings.Replace(hash, `"`, "", -1)) + + c := h.ToCid() + h1, err := EthHashFromCid(c) + require.Nil(t, err) + require.Equal(t, h, h1) + } +} + +func TestEthAddr(t *testing.T) { + testcases := []string{ + strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`), + strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`), + strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`), + } + + for _, addr := range testcases { + var a EthAddress + err := a.UnmarshalJSON([]byte(addr)) + + require.Nil(t, err) + require.Equal(t, a.String(), strings.Replace(addr, `"`, "", -1)) + } +} + +func TestParseEthAddr(t *testing.T) { + testcases := []uint64{ + 1, 2, 3, 100, 101, + } + for _, id := range testcases { + addr, err := address.NewIDAddress(id) + require.Nil(t, err) + + eaddr, err := EthAddressFromFilecoinAddress(addr) + require.Nil(t, err) + + faddr, err := eaddr.ToFilecoinAddress() + require.Nil(t, err) + + require.Equal(t, addr, faddr) + } +} + +func TestUnmarshalEthCall(t *testing.T) { + data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","data":""}` + + var c EthCall + err := c.UnmarshalJSON([]byte(data)) + require.Nil(t, err) +} + +func TestUnmarshalEthBytes(t *testing.T) { + testcases := []string{ + `"0x00"`, + strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`), + strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`), + strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`), + } + + for _, tc := range testcases { + var s EthBytes + err := s.UnmarshalJSON([]byte(tc)) + require.Nil(t, err) + + data, err := s.MarshalJSON() + require.Nil(t, err) + require.Equal(t, string(data), tc) + } +} + +func TestEthFilterResultMarshalJSON(t *testing.T) { + hash1, err := ParseEthHash("013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184") + require.NoError(t, err, "eth hash") + + hash2, err := ParseEthHash("ab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738") + require.NoError(t, err, "eth hash") + + addr, err := ParseEthAddress("d4c5fb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + log := EthLog{ + Removed: true, + LogIndex: 5, + TransactionIndex: 45, + TransactionHash: hash1, + BlockHash: hash2, + BlockNumber: 53, + Topics: []EthBytes{hash1[:]}, + Data: EthBytes(hash1[:]), + Address: addr, + } + logjson, err := json.Marshal(log) + require.NoError(t, err, "log json") + + testcases := []struct { + res EthFilterResult + want string + }{ + { + res: EthFilterResult{}, + want: "[]", + }, + + { + res: EthFilterResult{ + Results: []any{hash1, hash2}, + }, + want: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]`, + }, + + { + res: EthFilterResult{ + Results: []any{hash1, hash2}, + }, + want: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]`, + }, + + { + res: EthFilterResult{ + Results: []any{log}, + }, + want: `[` + string(logjson) + `]`, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run("", func(t *testing.T) { + data, err := json.Marshal(tc.res) + require.NoError(t, err) + require.Equal(t, tc.want, string(data)) + }) + } +} + +func TestEthFilterSpecUnmarshalJSON(t *testing.T) { + hash1, err := ParseEthHash("013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184") + require.NoError(t, err, "eth hash") + + hash2, err := ParseEthHash("ab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738") + require.NoError(t, err, "eth hash") + + addr, err := ParseEthAddress("d4c5fb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + pstring := func(s string) *string { return &s } + phash := func(h EthHash) *EthHash { return &h } + + testcases := []struct { + input string + want EthFilterSpec + }{ + { + input: `{"fromBlock":"latest"}`, + want: EthFilterSpec{FromBlock: pstring("latest")}, + }, + { + input: `{"toBlock":"pending"}`, + want: EthFilterSpec{ToBlock: pstring("pending")}, + }, + { + input: `{"address":["0xd4c5fb16488Aa48081296299d54b0c648C9333dA"]}`, + want: EthFilterSpec{Address: EthAddressList{addr}}, + }, + { + input: `{"address":"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"}`, + want: EthFilterSpec{Address: EthAddressList{addr}}, + }, + { + input: `{"blockHash":"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"}`, + want: EthFilterSpec{BlockHash: phash(hash1)}, + }, + { + input: `{"topics":["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + {hash1}, + }, + }, + }, + { + input: `{"topics":["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + {hash1}, + {hash2}, + }, + }, + }, + { + input: `{"topics":[null, ["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + nil, + {hash1, hash2}, + }, + }, + }, + { + input: `{"topics":[null, "0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + nil, + {hash1}, + }, + }, + }, + } + + for _, tc := range testcases { + var got EthFilterSpec + err := json.Unmarshal([]byte(tc.input), &got) + require.NoError(t, err) + require.Equal(t, tc.want, got) + } +} + +func TestEthAddressListUnmarshalJSON(t *testing.T) { + addr1, err := ParseEthAddress("d4c5fb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + addr2, err := ParseEthAddress("abbbfb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + testcases := []struct { + input string + want EthAddressList + }{ + { + input: `["0xd4c5fb16488Aa48081296299d54b0c648C9333dA"]`, + want: EthAddressList{addr1}, + }, + { + input: `["0xd4c5fb16488Aa48081296299d54b0c648C9333dA","abbbfb16488Aa48081296299d54b0c648C9333dA"]`, + want: EthAddressList{addr1, addr2}, + }, + { + input: `"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`, + want: EthAddressList{addr1}, + }, + { + input: `[]`, + want: EthAddressList{}, + }, + { + input: `null`, + want: EthAddressList(nil), + }, + } + for _, tc := range testcases { + tc := tc + t.Run("", func(t *testing.T) { + var got EthAddressList + err := json.Unmarshal([]byte(tc.input), &got) + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} + +func TestEthHashListUnmarshalJSON(t *testing.T) { + hash1, err := ParseEthHash("013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184") + require.NoError(t, err, "eth hash") + + hash2, err := ParseEthHash("ab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738") + require.NoError(t, err, "eth hash") + + testcases := []struct { + input string + want *EthHashList + }{ + { + input: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"]`, + want: &EthHashList{hash1}, + }, + { + input: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]`, + want: &EthHashList{hash1, hash2}, + }, + { + input: `"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"`, + want: &EthHashList{hash1}, + }, + { + input: `null`, + want: nil, + }, + } + for _, tc := range testcases { + var got *EthHashList + err := json.Unmarshal([]byte(tc.input), &got) + require.NoError(t, err) + require.Equal(t, tc.want, got) + } +} diff --git a/chain/types/ethtypes/rlp.go b/chain/types/ethtypes/rlp.go new file mode 100644 index 000000000..049ea6fc4 --- /dev/null +++ b/chain/types/ethtypes/rlp.go @@ -0,0 +1,182 @@ +package ethtypes + +import ( + "bytes" + "encoding/binary" + "fmt" + + "golang.org/x/xerrors" +) + +// maxListElements restricts the amount of RLP list elements we'll read. +// The ETH API only ever reads EIP-1559 transactions, which are bounded by +// 12 elements exactly, so we play it safe and set exactly that limit here. +const maxListElements = 12 + +func EncodeRLP(val interface{}) ([]byte, error) { + return encodeRLP(val) +} + +func encodeRLPListItems(list []interface{}) (result []byte, err error) { + res := []byte{} + for _, elem := range list { + encoded, err := encodeRLP(elem) + if err != nil { + return nil, err + } + res = append(res, encoded...) + } + return res, nil +} + +func encodeLength(length int) (lenInBytes []byte, err error) { + if length == 0 { + return nil, fmt.Errorf("cannot encode length: length should be larger than 0") + } + + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, int64(length)) + if err != nil { + return nil, err + } + + firstNonZeroIndex := len(buf.Bytes()) - 1 + for i, b := range buf.Bytes() { + if b != 0 { + firstNonZeroIndex = i + break + } + } + + res := buf.Bytes()[firstNonZeroIndex:] + return res, nil +} + +func encodeRLP(val interface{}) ([]byte, error) { + switch data := val.(type) { + case []byte: + if len(data) == 1 && data[0] <= 0x7f { + return data, nil + } else if len(data) <= 55 { + prefix := byte(0x80 + len(data)) + return append([]byte{prefix}, data...), nil + } else { + lenInBytes, err := encodeLength(len(data)) + if err != nil { + return nil, err + } + prefix := byte(0xb7 + len(lenInBytes)) + return append( + []byte{prefix}, + append(lenInBytes, data...)..., + ), nil + } + case []interface{}: + encodedList, err := encodeRLPListItems(data) + if err != nil { + return nil, err + } + if len(encodedList) <= 55 { + prefix := byte(0xc0 + len(encodedList)) + return append( + []byte{prefix}, + encodedList..., + ), nil + } + lenInBytes, err := encodeLength(len(encodedList)) + if err != nil { + return nil, err + } + prefix := byte(0xf7 + len(lenInBytes)) + return append( + []byte{prefix}, + append(lenInBytes, encodedList...)..., + ), nil + default: + return nil, fmt.Errorf("input data should either be a list or a byte array") + } +} + +func DecodeRLP(data []byte) (interface{}, error) { + res, consumed, err := decodeRLP(data) + if err != nil { + return nil, err + } + if consumed != len(data) { + return nil, xerrors.Errorf("invalid rlp data: length %d, consumed %d", len(data), consumed) + } + return res, nil +} + +func decodeRLP(data []byte) (res interface{}, consumed int, err error) { + if len(data) == 0 { + return data, 0, xerrors.Errorf("invalid rlp data: data cannot be empty") + } + if data[0] >= 0xf8 { + listLenInBytes := int(data[0]) - 0xf7 + listLen, err := decodeLength(data[1:], listLenInBytes) + if err != nil { + return nil, 0, err + } + if 1+listLenInBytes+listLen > len(data) { + return nil, 0, xerrors.Errorf("invalid rlp data: out of bound while parsing list") + } + result, err := decodeListElems(data[1+listLenInBytes:], listLen) + return result, 1 + listLenInBytes + listLen, err + } else if data[0] >= 0xc0 { + length := int(data[0]) - 0xc0 + result, err := decodeListElems(data[1:], length) + return result, 1 + length, err + } else if data[0] >= 0xb8 { + strLenInBytes := int(data[0]) - 0xb7 + strLen, err := decodeLength(data[1:], strLenInBytes) + if err != nil { + return nil, 0, err + } + totalLen := 1 + strLenInBytes + strLen + if totalLen > len(data) { + return nil, 0, xerrors.Errorf("invalid rlp data: out of bound while parsing string") + } + return data[1+strLenInBytes : totalLen], totalLen, nil + } else if data[0] >= 0x80 { + length := int(data[0]) - 0x80 + if 1+length > len(data) { + return nil, 0, xerrors.Errorf("invalid rlp data: out of bound while parsing string") + } + return data[1 : 1+length], 1 + length, nil + } + return []byte{data[0]}, 1, nil +} + +func decodeLength(data []byte, lenInBytes int) (length int, err error) { + if lenInBytes > len(data) || lenInBytes > 8 { + return 0, xerrors.Errorf("invalid rlp data: out of bound while parsing list length") + } + var decodedLength int64 + r := bytes.NewReader(append(make([]byte, 8-lenInBytes), data[:lenInBytes]...)) + if err := binary.Read(r, binary.BigEndian, &decodedLength); err != nil { + return 0, xerrors.Errorf("invalid rlp data: cannot parse string length: %w", err) + } + if lenInBytes+int(decodedLength) > len(data) { + return 0, xerrors.Errorf("invalid rlp data: out of bound while parsing list") + } + return int(decodedLength), nil +} + +func decodeListElems(data []byte, length int) (res []interface{}, err error) { + totalConsumed := 0 + result := []interface{}{} + + for i := 0; totalConsumed < length && i < maxListElements; i++ { + elem, consumed, err := decodeRLP(data[totalConsumed:]) + if err != nil { + return nil, xerrors.Errorf("invalid rlp data: cannot decode list element: %w", err) + } + totalConsumed += consumed + result = append(result, elem) + } + if totalConsumed != length { + return nil, xerrors.Errorf("invalid rlp data: incorrect list length") + } + return result, nil +} diff --git a/chain/types/ethtypes/rlp_test.go b/chain/types/ethtypes/rlp_test.go new file mode 100644 index 000000000..bdbedff00 --- /dev/null +++ b/chain/types/ethtypes/rlp_test.go @@ -0,0 +1,190 @@ +package ethtypes + +import ( + "encoding/hex" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" +) + +func TestEncode(t *testing.T) { + testcases := []TestCase{ + {[]byte(""), mustDecodeHex("0x80")}, + {mustDecodeHex("0x01"), mustDecodeHex("0x01")}, + {mustDecodeHex("0xaa"), mustDecodeHex("0x81aa")}, + {mustDecodeHex("0x0402"), mustDecodeHex("0x820402")}, + { + []interface{}{}, + mustDecodeHex("0xc0"), + }, + { + mustDecodeHex("0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + mustDecodeHex("0xb83cabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + }, + { + mustDecodeHex("0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + mustDecodeHex("0xb8aaabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + }, + { + []interface{}{ + mustDecodeHex("0xaaaa"), + mustDecodeHex("0xbbbb"), + mustDecodeHex("0xcccc"), + mustDecodeHex("0xdddd"), + }, + mustDecodeHex("0xcc82aaaa82bbbb82cccc82dddd"), + }, + { + []interface{}{ + mustDecodeHex("0xaaaaaaaaaaaaaaaaaaaa"), + mustDecodeHex("0xbbbbbbbbbbbbbbbbbbbb"), + []interface{}{ + mustDecodeHex("0xc1c1c1c1c1c1c1c1c1c1"), + mustDecodeHex("0xc2c2c2c2c2c2c2c2c2c2"), + mustDecodeHex("0xc3c3c3c3c3c3c3c3c3c3"), + }, + mustDecodeHex("0xdddddddddddddddddddd"), + mustDecodeHex("0xeeeeeeeeeeeeeeeeeeee"), + mustDecodeHex("0xffffffffffffffffffff"), + }, + mustDecodeHex("0xf8598aaaaaaaaaaaaaaaaaaaaa8abbbbbbbbbbbbbbbbbbbbe18ac1c1c1c1c1c1c1c1c1c18ac2c2c2c2c2c2c2c2c2c28ac3c3c3c3c3c3c3c3c3c38adddddddddddddddddddd8aeeeeeeeeeeeeeeeeeeee8affffffffffffffffffff"), + }, + } + + for _, tc := range testcases { + result, err := EncodeRLP(tc.Input) + require.Nil(t, err) + + require.Equal(t, tc.Output.([]byte), result) + } +} + +func TestDecodeString(t *testing.T) { + testcases := []TestCase{ + {"0x00", "0x00"}, + {"0x80", "0x"}, + {"0x0f", "0x0f"}, + {"0x81aa", "0xaa"}, + {"0x820400", "0x0400"}, + {"0xb83cabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", + "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"}, + } + + for _, tc := range testcases { + input, err := hex.DecodeString(strings.Replace(tc.Input.(string), "0x", "", -1)) + require.Nil(t, err) + + output, err := hex.DecodeString(strings.Replace(tc.Output.(string), "0x", "", -1)) + require.Nil(t, err) + + result, err := DecodeRLP(input) + require.Nil(t, err) + require.Equal(t, output, result.([]byte)) + } +} + +func mustDecodeHex(s string) []byte { + d, err := hex.DecodeString(strings.Replace(s, "0x", "", -1)) + if err != nil { + panic(fmt.Errorf("err must be nil: %w", err)) + } + return d +} + +func TestDecodeList(t *testing.T) { + testcases := []TestCase{ + {"0xc0", []interface{}{}}, + {"0xc100", []interface{}{[]byte{0}}}, + {"0xc3000102", []interface{}{[]byte{0}, []byte{1}, []byte{2}}}, + {"0xc4000181aa", []interface{}{[]byte{0}, []byte{1}, []byte{0xaa}}}, + {"0xc6000181aa81ff", []interface{}{[]byte{0}, []byte{1}, []byte{0xaa}, []byte{0xff}}}, + {"0xf8428aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd", + []interface{}{ + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + }, + }, + {"0xf1030185012a05f2008504a817c800825208942b87d1cb599bc2a606db9a0169fcec96af04ad3a880de0b6b3a764000080c0", + []interface{}{ + []byte{3}, + []byte{1}, + mustDecodeHex("0x012a05f200"), + mustDecodeHex("0x04a817c800"), + mustDecodeHex("0x5208"), + mustDecodeHex("0x2b87d1CB599Bc2a606Db9A0169fcEc96Af04ad3a"), + mustDecodeHex("0x0de0b6b3a7640000"), + []byte{}, + []interface{}{}, + }}, + } + + for _, tc := range testcases { + input, err := hex.DecodeString(strings.Replace(tc.Input.(string), "0x", "", -1)) + require.Nil(t, err) + + result, err := DecodeRLP(input) + require.Nil(t, err) + + fmt.Println(result) + r := result.([]interface{}) + require.Equal(t, len(tc.Output.([]interface{})), len(r)) + + for i, v := range r { + require.Equal(t, tc.Output.([]interface{})[i], v) + } + } +} + +func TestDecodeEncodeTx(t *testing.T) { + testcases := [][]byte{ + mustDecodeHex("0xdc82013a0185012a05f2008504a817c8008080872386f26fc1000000c0"), + mustDecodeHex("0xf85f82013a0185012a05f2008504a817c8008080872386f26fc1000000c001a027fa36fb9623e4d71fcdd7f7dce71eb814c9560dcf3908c1719386e2efd122fba05fb4e4227174eeb0ba84747a4fb883c8d4e0fdb129c4b1f42e90282c41480234"), + mustDecodeHex("0xf9061c82013a0185012a05f2008504a817c8008080872386f26fc10000b905bb608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610556806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101939190610496565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104ca565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561048b5761048a61040d565b5b828202905092915050565b60006104a182610337565b91506104ac83610337565b9250828210156104bf576104be61040d565b5b828203905092915050565b60006104d582610337565b91506104e083610337565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156105155761051461040d565b5b82820190509291505056fea26469706673582212208e5b4b874c839967f88008ed2fa42d6c2d9c9b0ae05d1d2c61faa7d229c134e664736f6c634300080d0033c080a0c4e9477f57c6848b2f1ea73a14809c1f44529d20763c947f3ac8ffd3d1629d93a011485a215457579bb13ac7b53bb9d6804763ae6fe5ce8ddd41642cea55c9a09a"), + mustDecodeHex("0xf9063082013a0185012a05f2008504a817c8008094025b594a4f1c4888cafcfaf2bb24ed95507749e0872386f26fc10000b905bb608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610556806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101939190610496565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104ca565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561048b5761048a61040d565b5b828202905092915050565b60006104a182610337565b91506104ac83610337565b9250828210156104bf576104be61040d565b5b828203905092915050565b60006104d582610337565b91506104e083610337565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156105155761051461040d565b5b82820190509291505056fea26469706673582212208e5b4b874c839967f88008ed2fa42d6c2d9c9b0ae05d1d2c61faa7d229c134e664736f6c634300080d0033c080a0fe38720928596f9e9dfbf891d00311638efce3713f03cdd67b212ecbbcf18f29a05993e656c0b35b8a580da6aff7c89b3d3e8b1c6f83a7ce09473c0699a8500b9c"), + } + + for _, tc := range testcases { + decoded, err := DecodeRLP(tc) + require.Nil(t, err) + + encoded, err := EncodeRLP(decoded) + require.Nil(t, err) + require.Equal(t, tc, encoded) + } +} + +func TestDecodeError(t *testing.T) { + testcases := [][]byte{ + mustDecodeHex("0xdc82013a0185012a05f2008504a817c8008080872386f26fc1000000"), + mustDecodeHex("0xdc013a01012a05f2008504a817c8008080872386f26fc1000000"), + mustDecodeHex("0xdc82013a0185012a05f28504a817c08080872386f26fc1000000"), + mustDecodeHex("0xdc82013a0185012a05f504a817c080872386ffc1000000"), + mustDecodeHex("0x013a018505f2008504a817c8008080872386f26fc1000000"), + } + + for _, tc := range testcases { + _, err := DecodeRLP(tc) + require.NotNil(t, err, hex.EncodeToString(tc)) + } +} + +func TestDecode1(t *testing.T) { + b := mustDecodeHex("0x02f8758401df5e7680832c8411832c8411830767f89452963ef50e27e06d72d59fcb4f3c2a687be3cfef880de0b6b3a764000080c080a094b11866f453ad85a980e0e8a2fc98cbaeb4409618c7734a7e12ae2f66fd405da042dbfb1b37af102023830ceeee0e703ffba0b8b3afeb8fe59f405eca9ed61072") + decoded, err := ParseEthTxArgs(b) + require.NoError(t, err) + + sender, err := decoded.Sender() + require.NoError(t, err) + + addr, err := address.NewFromString("f410fkkld55ioe7qg24wvt7fu6pbknb56ht7pt4zamxa") + require.NoError(t, err) + require.Equal(t, sender, addr) +} diff --git a/chain/types/event.go b/chain/types/event.go new file mode 100644 index 000000000..00c25ca4c --- /dev/null +++ b/chain/types/event.go @@ -0,0 +1,32 @@ +package types + +import ( + "github.com/filecoin-project/go-state-types/abi" +) + +type Event struct { + // The ID of the actor that emitted this event. + Emitter abi.ActorID + + // Key values making up this event. + Entries []EventEntry +} + +type EventEntry struct { + // A bitmap conveying metadata or hints about this entry. + Flags uint8 + + // The key of this event entry + Key string + + // Any DAG-CBOR encodeable type. + Value []byte +} + +type FilterID [32]byte // compatible with EthHash + +// EventEntry flags defined in fvm_shared +const ( + EventFlagIndexedKey = 0b00000001 + EventFlagIndexedValue = 0b00000010 +) diff --git a/chain/types/keystore.go b/chain/types/keystore.go index 107c1fbe3..8e8d9192b 100644 --- a/chain/types/keystore.go +++ b/chain/types/keystore.go @@ -39,6 +39,8 @@ func (kt *KeyType) UnmarshalJSON(bb []byte) error { *kt = KTBLS case crypto.SigTypeSecp256k1: *kt = KTSecp256k1 + case crypto.SigTypeDelegated: + *kt = KTDelegated default: return fmt.Errorf("unknown sigtype: %d", bst) } @@ -51,6 +53,7 @@ const ( KTBLS KeyType = "bls" KTSecp256k1 KeyType = "secp256k1" KTSecp256k1Ledger KeyType = "secp256k1-ledger" + KTDelegated KeyType = "delegated" ) // KeyInfo is used for storing keys in KeyStore diff --git a/chain/types/message_receipt.go b/chain/types/message_receipt.go index 57761680d..b0db3b74d 100644 --- a/chain/types/message_receipt.go +++ b/chain/types/message_receipt.go @@ -3,15 +3,59 @@ package types import ( "bytes" + "github.com/ipfs/go-cid" + "github.com/filecoin-project/go-state-types/exitcode" ) +type MessageReceiptVersion byte + +const ( + // MessageReceiptV0 refers to pre FIP-0049 receipts. + MessageReceiptV0 MessageReceiptVersion = 0 + // MessageReceiptV1 refers to post FIP-0049 receipts. + MessageReceiptV1 MessageReceiptVersion = 1 +) + +const EventAMTBitwidth = 5 + type MessageReceipt struct { - ExitCode exitcode.ExitCode - Return []byte - GasUsed int64 + version MessageReceiptVersion + + ExitCode exitcode.ExitCode + Return []byte + GasUsed int64 + EventsRoot *cid.Cid // Root of Event AMT with bitwidth = EventAMTBitwidth +} + +// NewMessageReceiptV0 creates a new pre FIP-0049 receipt with no capability to +// convey events. +func NewMessageReceiptV0(exitcode exitcode.ExitCode, ret []byte, gasUsed int64) MessageReceipt { + return MessageReceipt{ + version: MessageReceiptV0, + ExitCode: exitcode, + Return: ret, + GasUsed: gasUsed, + } +} + +// NewMessageReceiptV1 creates a new pre FIP-0049 receipt with the ability to +// convey events. +func NewMessageReceiptV1(exitcode exitcode.ExitCode, ret []byte, gasUsed int64, eventsRoot *cid.Cid) MessageReceipt { + return MessageReceipt{ + version: MessageReceiptV1, + ExitCode: exitcode, + Return: ret, + GasUsed: gasUsed, + EventsRoot: eventsRoot, + } +} + +func (mr *MessageReceipt) Version() MessageReceiptVersion { + return mr.version } func (mr *MessageReceipt) Equals(o *MessageReceipt) bool { - return mr.ExitCode == o.ExitCode && bytes.Equal(mr.Return, o.Return) && mr.GasUsed == o.GasUsed + return mr.version == o.version && mr.ExitCode == o.ExitCode && bytes.Equal(mr.Return, o.Return) && mr.GasUsed == o.GasUsed && + (mr.EventsRoot == o.EventsRoot || (mr.EventsRoot != nil && o.EventsRoot != nil && *mr.EventsRoot == *o.EventsRoot)) } diff --git a/chain/types/message_receipt_cbor.go b/chain/types/message_receipt_cbor.go new file mode 100644 index 000000000..e1364e654 --- /dev/null +++ b/chain/types/message_receipt_cbor.go @@ -0,0 +1,359 @@ +package types + +import ( + "fmt" + "io" + + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/exitcode" +) + +// This file contains custom CBOR serde logic to deal with the new versioned +// MessageReceipt resulting from the introduction of actor events (FIP-0049). + +type messageReceiptV0 struct{ *MessageReceipt } + +type messageReceiptV1 struct{ *MessageReceipt } + +func (mr *MessageReceipt) MarshalCBOR(w io.Writer) error { + if mr == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + var m cbor.Marshaler + switch mr.version { + case MessageReceiptV0: + m = &messageReceiptV0{mr} + case MessageReceiptV1: + m = &messageReceiptV1{mr} + default: + return xerrors.Errorf("invalid message receipt version: %d", mr.version) + } + + return m.MarshalCBOR(w) +} + +func (mr *MessageReceipt) UnmarshalCBOR(r io.Reader) (err error) { + *mr = MessageReceipt{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + var u cbor.Unmarshaler + switch extra { + case 3: + mr.version = MessageReceiptV0 + u = &messageReceiptV0{mr} + case 4: + mr.version = MessageReceiptV1 + u = &messageReceiptV1{mr} + default: + return fmt.Errorf("cbor input had wrong number of fields") + } + + // Ok to pass a CBOR reader since cbg.NewCborReader will return itself when + // already a CBOR reader. + return u.UnmarshalCBOR(cr) +} + +var lengthBufAMessageReceiptV0 = []byte{131} + +func (t *messageReceiptV0) MarshalCBOR(w io.Writer) error { + // eliding null check since nulls were already handled in the dispatcher + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufAMessageReceiptV0); err != nil { + return err + } + + // t.ExitCode (exitcode.ExitCode) (int64) + if t.ExitCode >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil { + return err + } + } + + // t.Return ([]uint8) (slice) + if len(t.Return) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Return was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil { + return err + } + + if _, err := cw.Write(t.Return[:]); err != nil { + return err + } + + // t.GasUsed (int64) (int64) + if t.GasUsed >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil { + return err + } + } + return nil +} + +func (t *messageReceiptV0) UnmarshalCBOR(r io.Reader) (err error) { + cr := cbg.NewCborReader(r) + + // t.ExitCode (exitcode.ExitCode) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ExitCode = exitcode.ExitCode(extraI) + } + // t.Return ([]uint8) (slice) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Return: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Return = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Return[:]); err != nil { + return err + } + // t.GasUsed (int64) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.GasUsed = extraI + } + return nil +} + +var lengthBufBMessageReceiptV1 = []byte{132} + +func (t *messageReceiptV1) MarshalCBOR(w io.Writer) error { + // eliding null check since nulls were already handled in the dispatcher + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufBMessageReceiptV1); err != nil { + return err + } + + // t.ExitCode (exitcode.ExitCode) (int64) + if t.ExitCode >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil { + return err + } + } + + // t.Return ([]uint8) (slice) + if len(t.Return) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Return was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil { + return err + } + + if _, err := cw.Write(t.Return[:]); err != nil { + return err + } + + // t.GasUsed (int64) (int64) + if t.GasUsed >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil { + return err + } + } + + // t.EventsRoot (cid.Cid) (struct) + + if t.EventsRoot == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCid(cw, *t.EventsRoot); err != nil { + return xerrors.Errorf("failed to write cid field t.EventsRoot: %w", err) + } + } + + return nil +} + +func (t *messageReceiptV1) UnmarshalCBOR(r io.Reader) (err error) { + cr := cbg.NewCborReader(r) + + // t.ExitCode (exitcode.ExitCode) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ExitCode = exitcode.ExitCode(extraI) + } + // t.Return ([]uint8) (slice) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Return: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Return = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Return[:]); err != nil { + return err + } + // t.GasUsed (int64) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.GasUsed = extraI + } + // t.EventsRoot (cid.Cid) (struct) + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.EventsRoot: %w", err) + } + + t.EventsRoot = &c + } + + } + return nil +} diff --git a/chain/types/message_receipt_test.go b/chain/types/message_receipt_test.go new file mode 100644 index 000000000..f0b341f55 --- /dev/null +++ b/chain/types/message_receipt_test.go @@ -0,0 +1,75 @@ +package types + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/assert" +) + +func TestMessageReceiptSerdeRoundrip(t *testing.T) { + var ( + assert = assert.New(t) + buf = new(bytes.Buffer) + err error + ) + + randomCid, err := cid.Decode("bafy2bzacecu7n7wbtogznrtuuvf73dsz7wasgyneqasksdblxupnyovmtwxxu") + assert.NoError(err) + + // + // Version 0 + // + mr := NewMessageReceiptV0(0, []byte{0x00, 0x01, 0x02, 0x04}, 42) + + // marshal + err = mr.MarshalCBOR(buf) + assert.NoError(err) + + t.Logf("version 0: %s\n", hex.EncodeToString(buf.Bytes())) + + // unmarshal + var mr2 MessageReceipt + err = mr2.UnmarshalCBOR(buf) + assert.NoError(err) + assert.Equal(mr, mr2) + + // version 0 with an events root -- should not serialize the events root! + mr.EventsRoot = &randomCid + + buf.Reset() + + // marshal + err = mr.MarshalCBOR(buf) + assert.NoError(err) + + t.Logf("version 0 (with root): %s\n", hex.EncodeToString(buf.Bytes())) + + // unmarshal + mr2 = MessageReceipt{} + err = mr2.UnmarshalCBOR(buf) + assert.NoError(err) + assert.NotEqual(mr, mr2) + assert.Nil(mr2.EventsRoot) + + // + // Version 1 + // + buf.Reset() + mr = NewMessageReceiptV1(0, []byte{0x00, 0x01, 0x02, 0x04}, 42, &randomCid) + + // marshal + err = mr.MarshalCBOR(buf) + assert.NoError(err) + + t.Logf("version 1: %s\n", hex.EncodeToString(buf.Bytes())) + + // unmarshal + mr2 = MessageReceipt{} + err = mr2.UnmarshalCBOR(buf) + assert.NoError(err) + assert.Equal(mr, mr2) + assert.NotNil(mr2.EventsRoot) +} diff --git a/chain/types/tipset.go b/chain/types/tipset.go index cb981e0f0..c1aa90fc9 100644 --- a/chain/types/tipset.go +++ b/chain/types/tipset.go @@ -196,8 +196,23 @@ func (ts *TipSet) MinTicket() *Ticket { } func (ts *TipSet) MinTimestamp() uint64 { - minTs := ts.Blocks()[0].Timestamp - for _, bh := range ts.Blocks()[1:] { + if ts == nil { + return 0 + } + + blks := ts.Blocks() + + // TODO::FVM @vyzo @magik Null rounds shouldn't ever be represented as + // tipsets with no blocks; Null-round generally means that the tipset at + // that epoch doesn't exist - and the next tipset that does exist links + // straight to first epoch with blocks (@raulk agrees -- this is odd) + if len(blks) == 0 { + // null rounds make things crash -- it is threaded in every fvm instantiation + return 0 + } + + minTs := blks[0].Timestamp + for _, bh := range blks[1:] { if bh.Timestamp < minTs { minTs = bh.Timestamp } diff --git a/chain/types/vmcontext.go b/chain/types/vmcontext.go index 2702153b6..83ad81315 100644 --- a/chain/types/vmcontext.go +++ b/chain/types/vmcontext.go @@ -24,6 +24,8 @@ type StateTree interface { SetActor(addr address.Address, act *Actor) error // GetActor returns the actor from any type of `addr` provided. GetActor(addr address.Address) (*Actor, error) + + Version() StateTreeVersion } type storageWrapper struct { diff --git a/chain/vectors/gen/main.go b/chain/vectors/gen/main.go index fbc96d2c3..ce9f1baf8 100644 --- a/chain/vectors/gen/main.go +++ b/chain/vectors/gen/main.go @@ -19,6 +19,7 @@ import ( "github.com/filecoin-project/lotus/chain/vectors" "github.com/filecoin-project/lotus/chain/wallet" _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" ) diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index d3c4d7a46..a81bc10d6 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -24,6 +24,7 @@ import ( actorstypes "github.com/filecoin-project/go-state-types/actors" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" @@ -275,7 +276,7 @@ func (x *FvmExtern) workerKeyAtLookback(ctx context.Context, minerId address.Add return address.Undef, gasUsed, err } - raddr, err := ResolveToKeyAddr(stateTree, cstWithGas, info.Worker) + raddr, err := ResolveToDeterministicAddr(stateTree, cstWithGas, info.Worker) if err != nil { return address.Undef, gasUsed, err } @@ -285,6 +286,7 @@ func (x *FvmExtern) workerKeyAtLookback(ctx context.Context, minerId address.Add type FVM struct { fvm *ffi.FVM + nv network.Version } func defaultFVMOpts(ctx context.Context, opts *VMOpts) (*ffi.FVMOpts, error) { @@ -309,11 +311,14 @@ func defaultFVMOpts(ctx context.Context, opts *VMOpts) (*ffi.FVMOpts, error) { epoch: opts.Epoch, }, Epoch: opts.Epoch, + Timestamp: opts.Timestamp, + ChainID: build.Eip155ChainId, BaseFee: opts.BaseFee, BaseCircSupply: circToReport, NetworkVersion: opts.NetworkVersion, StateBase: opts.StateBase, Tracing: opts.Tracing || EnableDetailedTracing, + Debug: build.ActorDebugging, }, nil } @@ -324,20 +329,6 @@ func NewFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { return nil, xerrors.Errorf("creating fvm opts: %w", err) } - if os.Getenv("LOTUS_USE_FVM_CUSTOM_BUNDLE") == "1" { - av, err := actorstypes.VersionForNetwork(opts.NetworkVersion) - if err != nil { - return nil, xerrors.Errorf("mapping network version to actors version: %w", err) - } - - c, ok := actors.GetManifest(av) - if !ok { - return nil, xerrors.Errorf("no manifest for custom bundle (actors version %d)", av) - } - - fvmOpts.Manifest = c - } - fvm, err := ffi.CreateFVM(fvmOpts) if err != nil { @@ -346,6 +337,7 @@ func NewFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { return &FVM{ fvm: fvm, + nv: opts.NetworkVersion, }, nil } @@ -448,6 +440,7 @@ func NewDebugFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { return &FVM{ fvm: fvm, + nv: opts.NetworkVersion, }, nil } @@ -466,10 +459,12 @@ func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet } duration := time.Since(start) - receipt := types.MessageReceipt{ - Return: ret.Return, - ExitCode: exitcode.ExitCode(ret.ExitCode), - GasUsed: ret.GasUsed, + + var receipt types.MessageReceipt + if vm.nv >= network.Version18 { + receipt = types.NewMessageReceiptV1(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed, ret.EventsRoot) + } else { + receipt = types.NewMessageReceiptV0(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed) } var aerr aerrors.ActorError @@ -530,10 +525,12 @@ func (vm *FVM) ApplyImplicitMessage(ctx context.Context, cmsg *types.Message) (* } duration := time.Since(start) - receipt := types.MessageReceipt{ - Return: ret.Return, - ExitCode: exitcode.ExitCode(ret.ExitCode), - GasUsed: ret.GasUsed, + + var receipt types.MessageReceipt + if vm.nv >= network.Version18 { + receipt = types.NewMessageReceiptV1(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed, ret.EventsRoot) + } else { + receipt = types.NewMessageReceiptV0(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed) } var aerr aerrors.ActorError diff --git a/chain/vm/gas.go b/chain/vm/gas.go index ca6e5571a..c9007d3f1 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -111,6 +111,7 @@ var Prices = map[abi.ChainEpoch]Pricelist{ verifySignature: map[crypto.SigType]int64{ crypto.SigTypeBLS: 16598605, crypto.SigTypeSecp256k1: 1637292, + crypto.SigTypeDelegated: 1637292, }, hashingBase: 31355, diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index a97a454bf..47bd2e326 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -284,16 +284,16 @@ func DecodeParams(b []byte, out interface{}) error { } func DumpActorState(i *ActorRegistry, act *types.Actor, b []byte) (interface{}, error) { - if builtin.IsAccountActor(act.Code) { // Account code special case - return nil, nil - } - actInfo, ok := i.actors[act.Code] if !ok { return nil, xerrors.Errorf("state type for actor %s not found", act.Code) } um := actInfo.vmActor.State() + if um == nil { + // TODO::FVM @arajasek I would like to assert that we have the empty object here + return nil, nil + } if err := um.UnmarshalCBOR(bytes.NewReader(b)); err != nil { return nil, xerrors.Errorf("unmarshaling actor state: %w", err) } diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 05f8de2f0..daa55e4f4 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -168,8 +168,8 @@ func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.Act aerr = ar return } - //log.Desugar().WithOptions(zap.AddStacktrace(zapcore.ErrorLevel)). - //Sugar().Errorf("spec actors failure: %s", r) + // log.Desugar().WithOptions(zap.AddStacktrace(zapcore.ErrorLevel)). + // Sugar().Errorf("spec actors failure: %s", r) log.Errorf("spec actors failure: %s", r) if rt.NetworkVersion() <= network.Version3 { aerr = aerrors.Newf(1, "spec actors failure: %s", r) @@ -249,7 +249,7 @@ func (rt *Runtime) GetRandomnessFromBeacon(personalization crypto.DomainSeparati func (rt *Runtime) NewActorAddress() address.Address { var b bytes.Buffer - oa, _ := ResolveToKeyAddr(rt.vm.cstate, rt.vm.cst, rt.origin) + oa, _ := ResolveToDeterministicAddr(rt.vm.cstate, rt.vm.cst, rt.origin) if err := oa.MarshalCBOR(&b); err != nil { // todo: spec says cbor; why not just bytes? panic(aerrors.Fatalf("writing caller address into a buffer: %v", err)) } diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index f6adc8940..68dbbb2df 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -255,7 +255,7 @@ func (ss *syscallShim) workerKeyAtLookback(height abi.ChainEpoch) (address.Addre return address.Undef, err } - return ResolveToKeyAddr(ss.cstate, ss.cst, info.Worker) + return ResolveToDeterministicAddr(ss.cstate, ss.cst, info.Worker) } func (ss *syscallShim) VerifyPoSt(info proof7.WindowPoStVerifyInfo) error { @@ -270,8 +270,8 @@ func (ss *syscallShim) VerifyPoSt(info proof7.WindowPoStVerifyInfo) error { } func (ss *syscallShim) VerifySeal(info proof7.SealVerifyInfo) error { - //_, span := trace.StartSpan(ctx, "ValidatePoRep") - //defer span.End() + // _, span := trace.StartSpan(ctx, "ValidatePoRep") + // defer span.End() miner, err := address.NewIDAddress(uint64(info.Miner)) if err != nil { @@ -284,7 +284,7 @@ func (ss *syscallShim) VerifySeal(info proof7.SealVerifyInfo) error { log.Debugf("Verif r:%s; d:%s; m:%s; t:%x; s:%x; N:%d; p:%x", info.SealedCID, info.UnsealedCID, miner, ticket, seed, info.SectorID.Number, proof) - //func(ctx context.Context, maddr address.Address, ssize abi.SectorSize, commD, commR, ticket, proof, seed []byte, sectorID abi.SectorNumber) + // func(ctx context.Context, maddr address.Address, ssize abi.SectorSize, commD, commR, ticket, proof, seed []byte, sectorID abi.SectorNumber) ok, err := ss.verifier.VerifySeal(info) if err != nil { return xerrors.Errorf("failed to validate PoRep: %w", err) @@ -325,7 +325,7 @@ func (ss *syscallShim) VerifyReplicaUpdate(update proof7.ReplicaUpdateInfo) erro func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Address, input []byte) error { // TODO: in genesis setup, we are currently faking signatures - kaddr, err := ResolveToKeyAddr(ss.cstate, ss.cst, addr) + kaddr, err := ResolveToDeterministicAddr(ss.cstate, ss.cst, addr) if err != nil { return err } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index ecd87caf0..e18c4aea9 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -45,9 +45,11 @@ var ( gasOnActorExec = newGasCharge("OnActorExec", 0, 0) ) -// ResolveToKeyAddr returns the public key type of address (`BLS`/`SECP256K1`) of an account actor identified by `addr`. -func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, error) { - if addr.Protocol() == address.BLS || addr.Protocol() == address.SECP256K1 { +// ResolveToDeterministicAddr returns the public key type of address +// (`BLS`/`SECP256K1`) of an actor identified by `addr`, or its +// delegated address. +func ResolveToDeterministicAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, error) { + if addr.Protocol() == address.BLS || addr.Protocol() == address.SECP256K1 || addr.Protocol() == address.Delegated { return addr, nil } @@ -56,12 +58,19 @@ func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Ad return address.Undef, xerrors.Errorf("failed to find actor: %s", addr) } + if state.Version() >= types.StateTreeVersion5 { + if act.Address != nil { + // If there _is_ an f4 address, return it as "key" address + return *act.Address, nil + } + } + aast, err := account.Load(adt.WrapStore(context.TODO(), cst), act) if err != nil { return address.Undef, xerrors.Errorf("failed to get account actor state for %s: %w", addr, err) } - return aast.PubkeyAddress() + } var ( @@ -216,6 +225,7 @@ type LegacyVM struct { type VMOpts struct { StateBase cid.Cid Epoch abi.ChainEpoch + Timestamp uint64 Rand Rand Bstore blockstore.Blockstore Actors *ActorRegistry diff --git a/chain/wallet/key/key.go b/chain/wallet/key/key.go index 66053525b..422066610 100644 --- a/chain/wallet/key/key.go +++ b/chain/wallet/key/key.go @@ -7,6 +7,7 @@ import ( "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/lib/sigs" ) @@ -50,6 +51,22 @@ func NewKey(keyinfo types.KeyInfo) (*Key, error) { if err != nil { return nil, xerrors.Errorf("converting Secp256k1 to address: %w", err) } + case types.KTDelegated: + // Transitory Delegated signature verification as per FIP-0055 + ethAddr, err := ethtypes.EthAddressFromPubKey(k.PublicKey) + if err != nil { + return nil, xerrors.Errorf("failed to calculate Eth address from public key: %w", err) + } + + ea, err := ethtypes.CastEthAddress(ethAddr) + if err != nil { + return nil, xerrors.Errorf("failed to create ethereum address from bytes: %w", err) + } + + k.Address, err = ea.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("converting Delegated to address: %w", err) + } case types.KTBLS: k.Address, err = address.NewBLSAddress(k.PublicKey) if err != nil { @@ -58,6 +75,7 @@ func NewKey(keyinfo types.KeyInfo) (*Key, error) { default: return nil, xerrors.Errorf("unsupported key type: %s", k.Type) } + return k, nil } @@ -68,6 +86,8 @@ func ActSigType(typ types.KeyType) crypto.SigType { return crypto.SigTypeBLS case types.KTSecp256k1: return crypto.SigTypeSecp256k1 + case types.KTDelegated: + return crypto.SigTypeDelegated default: return crypto.SigTypeUnknown } diff --git a/chain/wallet/wallet.go b/chain/wallet/wallet.go index 2f382d5f8..76af663c7 100644 --- a/chain/wallet/wallet.go +++ b/chain/wallet/wallet.go @@ -16,7 +16,8 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet/key" "github.com/filecoin-project/lotus/lib/sigs" - _ "github.com/filecoin-project/lotus/lib/sigs/bls" // enable bls signatures + _ "github.com/filecoin-project/lotus/lib/sigs/bls" // enable bls signatures + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" // enable secp signatures ) diff --git a/cli/chain.go b/cli/chain.go index de48afb2b..dadef8492 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -149,7 +149,7 @@ var ChainGetBlock = &cli.Command{ recpts, err := api.ChainGetParentReceipts(ctx, bcid) if err != nil { log.Warn(err) - //return xerrors.Errorf("failed to get receipts: %w", err) + // return xerrors.Errorf("failed to get receipts: %w", err) } cblock := struct { diff --git a/cli/client.go b/cli/client.go index 1c41262a5..a8355f9a1 100644 --- a/cli/client.go +++ b/cli/client.go @@ -1517,6 +1517,8 @@ func GetAsks(ctx context.Context, api lapi.FullNode) ([]QueriedAsk, error) { } }(miner) } + + wg.Wait() }() loop: @@ -1590,6 +1592,8 @@ loop: lk.Unlock() }(miner) } + + wg.Wait() }() loop2: diff --git a/cli/cmd.go b/cli/cmd.go index 79023917b..802df0c99 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -81,6 +81,7 @@ var Commands = []*cli.Command{ WithCategory("developer", LogCmd), WithCategory("developer", WaitApiCmd), WithCategory("developer", FetchParamCmd), + WithCategory("developer", EvmCmd), WithCategory("network", NetCmd), WithCategory("network", SyncCmd), WithCategory("status", StatusCmd), diff --git a/cli/evm.go b/cli/evm.go new file mode 100644 index 000000000..1c02f92b4 --- /dev/null +++ b/cli/evm.go @@ -0,0 +1,482 @@ +package cli + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "os" + + "github.com/urfave/cli/v2" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + amt4 "github.com/filecoin-project/go-amt-ipld/v4" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/builtin/v10/eam" + + "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +var EvmCmd = &cli.Command{ + Name: "evm", + Usage: "Commands related to the Filecoin EVM runtime", + Subcommands: []*cli.Command{ + EvmDeployCmd, + EvmInvokeCmd, + EvmGetInfoCmd, + EvmCallSimulateCmd, + EvmGetContractAddress, + }, +} + +var EvmGetInfoCmd = &cli.Command{ + Name: "stat", + Usage: "Print eth/filecoin addrs and code cid", + ArgsUsage: "address", + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + addrString := cctx.Args().Get(0) + + var faddr address.Address + var eaddr ethtypes.EthAddress + addr, err := address.NewFromString(addrString) + if err != nil { // This isn't a filecoin address + eaddr, err = ethtypes.ParseEthAddress(addrString) + if err != nil { // This isn't an Eth address either + return xerrors.Errorf("address is not a filecoin or eth address") + } + faddr, err = eaddr.ToFilecoinAddress() + if err != nil { + return err + } + } else { + eaddr, faddr, err = ethAddrFromFilecoinAddress(ctx, addr, api) + if err != nil { + return err + } + } + + actor, err := api.StateGetActor(ctx, faddr, types.EmptyTSK) + if err != nil { + return err + } + + fmt.Println("Filecoin address: ", faddr) + fmt.Println("Eth address: ", eaddr) + fmt.Println("Code cid: ", actor.Code.String()) + + return nil + }, +} + +var EvmCallSimulateCmd = &cli.Command{ + Name: "call", + Usage: "Simulate an eth contract call", + ArgsUsage: "[from] [to] [params]", + Action: func(cctx *cli.Context) error { + + if cctx.NArg() != 3 { + return IncorrectNumArgs(cctx) + } + + fromEthAddr, err := ethtypes.ParseEthAddress(cctx.Args().Get(0)) + if err != nil { + return err + } + + toEthAddr, err := ethtypes.ParseEthAddress(cctx.Args().Get(1)) + if err != nil { + return err + } + + params, err := ethtypes.DecodeHexString(cctx.Args().Get(2)) + if err != nil { + return err + } + + api, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + res, err := api.EthCall(ctx, ethtypes.EthCall{ + From: &fromEthAddr, + To: &toEthAddr, + Data: params, + }, "") + if err != nil { + fmt.Println("Eth call fails, return val: ", res) + return err + } + + fmt.Println("Result: ", res) + + return nil + + }, +} + +var EvmGetContractAddress = &cli.Command{ + Name: "contract-address", + Usage: "Generate contract address from smart contract code", + ArgsUsage: "[senderEthAddr] [salt] [contractHexPath]", + Action: func(cctx *cli.Context) error { + + if cctx.NArg() != 3 { + return IncorrectNumArgs(cctx) + } + + sender, err := ethtypes.ParseEthAddress(cctx.Args().Get(0)) + if err != nil { + return err + } + + salt, err := ethtypes.DecodeHexString(cctx.Args().Get(1)) + if err != nil { + return xerrors.Errorf("Could not decode salt: %w", err) + } + if len(salt) > 32 { + return xerrors.Errorf("Len of salt bytes greater than 32") + } + var fsalt [32]byte + copy(fsalt[:], salt[:]) + + contractBin := cctx.Args().Get(2) + if err != nil { + return err + } + contractHex, err := os.ReadFile(contractBin) + if err != nil { + + return err + } + contract, err := ethtypes.DecodeHexString(string(contractHex)) + if err != nil { + return xerrors.Errorf("Could not decode contract file: %w", err) + } + + contractAddr, err := ethtypes.GetContractEthAddressFromCode(sender, fsalt, contract) + if err != nil { + return err + } + + fmt.Println("Contract Eth address: ", contractAddr) + + return nil + }, +} + +var EvmDeployCmd = &cli.Command{ + Name: "deploy", + Usage: "Deploy an EVM smart contract and return its address", + ArgsUsage: "contract", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "from", + Usage: "optionally specify the account to use for sending the creation message", + }, + &cli.BoolFlag{ + Name: "hex", + Usage: "use when input contract is in hex", + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if argc := cctx.Args().Len(); argc != 1 { + return xerrors.Errorf("must pass the contract init code") + } + + contract, err := os.ReadFile(cctx.Args().First()) + if err != nil { + return xerrors.Errorf("failed to read contract: %w", err) + } + if cctx.Bool("hex") { + contract, err = ethtypes.DecodeHexString(string(contract)) + if err != nil { + return xerrors.Errorf("failed to decode contract: %w", err) + } + } + + var fromAddr address.Address + if from := cctx.String("from"); from == "" { + fromAddr, err = api.WalletDefaultAddress(ctx) + } else { + fromAddr, err = address.NewFromString(from) + } + if err != nil { + return err + } + + initcode := abi.CborBytes(contract) + params, err := actors.SerializeParams(&initcode) + if err != nil { + return fmt.Errorf("failed to serialize Create params: %w", err) + } + + msg := &types.Message{ + To: builtintypes.EthereumAddressManagerActorAddr, + From: fromAddr, + Value: big.Zero(), + Method: builtintypes.MethodsEAM.CreateExternal, + Params: params, + } + + // TODO: On Jan 11th, we decided to add an `EAM#create_external` method + // that uses the nonce of the caller instead of taking a user-supplied nonce. + // Track: https://github.com/filecoin-project/ref-fvm/issues/1255 + // When that's implemented, we should migrate the CLI to use that, + // as `EAM#create` will be reserved for the EVM runtime actor. + // TODO: this is very racy. It may assign a _different_ nonce than the expected one. + afmt.Println("sending message...") + smsg, err := api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + return xerrors.Errorf("failed to push message: %w", err) + } + + afmt.Println("waiting for message to execute...") + wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 0) + if err != nil { + return xerrors.Errorf("error waiting for message: %w", err) + } + + // check it executed successfully + if wait.Receipt.ExitCode != 0 { + return xerrors.Errorf("actor execution failed") + } + + var result eam.CreateReturn + r := bytes.NewReader(wait.Receipt.Return) + if err := result.UnmarshalCBOR(r); err != nil { + return xerrors.Errorf("error unmarshaling return value: %w", err) + } + + addr, err := address.NewIDAddress(result.ActorID) + if err != nil { + return err + } + afmt.Printf("Actor ID: %d\n", result.ActorID) + afmt.Printf("ID Address: %s\n", addr) + afmt.Printf("Robust Address: %s\n", result.RobustAddress) + afmt.Printf("Eth Address: %s\n", "0x"+hex.EncodeToString(result.EthAddress[:])) + + ea, err := ethtypes.CastEthAddress(result.EthAddress[:]) + if err != nil { + return fmt.Errorf("failed to create ethereum address: %w", err) + } + + delegated, err := ea.ToFilecoinAddress() + if err != nil { + return fmt.Errorf("failed to calculate f4 address: %w", err) + } + + afmt.Printf("f4 Address: %s\n", delegated) + + if len(wait.Receipt.Return) > 0 { + result := base64.StdEncoding.EncodeToString(wait.Receipt.Return) + afmt.Printf("Return: %s\n", result) + } + + return nil + }, +} + +var EvmInvokeCmd = &cli.Command{ + Name: "invoke", + Usage: "Invoke an EVM smart contract using the specified CALLDATA", + ArgsUsage: "address calldata", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "from", + Usage: "optionally specify the account to use for sending the exec message", + }, &cli.IntFlag{ + Name: "value", + Usage: "optionally specify the value to be sent with the invokation message", + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if argc := cctx.Args().Len(); argc != 2 { + return xerrors.Errorf("must pass the address and calldata") + } + + addr, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return xerrors.Errorf("failed to decode address: %w", err) + } + + var calldata []byte + calldata, err = ethtypes.DecodeHexString(cctx.Args().Get(1)) + if err != nil { + return xerrors.Errorf("decoding hex input data: %w", err) + } + + var buffer bytes.Buffer + if err := cbg.WriteByteArray(&buffer, calldata); err != nil { + return xerrors.Errorf("failed to encode evm params as cbor: %w", err) + } + calldata = buffer.Bytes() + + var fromAddr address.Address + if from := cctx.String("from"); from == "" { + defaddr, err := api.WalletDefaultAddress(ctx) + if err != nil { + return err + } + + fromAddr = defaddr + } else { + addr, err := address.NewFromString(from) + if err != nil { + return err + } + + fromAddr = addr + } + + val := abi.NewTokenAmount(cctx.Int64("value")) + msg := &types.Message{ + To: addr, + From: fromAddr, + Value: val, + Method: builtintypes.MethodsEVM.InvokeContract, + Params: calldata, + } + + afmt.Println("sending message...") + smsg, err := api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + return xerrors.Errorf("failed to push message: %w", err) + } + + afmt.Println("waiting for message to execute...") + wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 0) + if err != nil { + return xerrors.Errorf("error waiting for message: %w", err) + } + + // check it executed successfully + if wait.Receipt.ExitCode != 0 { + return xerrors.Errorf("actor execution failed") + } + + afmt.Println("Gas used: ", wait.Receipt.GasUsed) + result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return))) + if err != nil { + return xerrors.Errorf("evm result not correctly encoded: %w", err) + } + + if len(result) > 0 { + afmt.Println(hex.EncodeToString(result)) + } else { + afmt.Println("OK") + } + + if eventsRoot := wait.Receipt.EventsRoot; eventsRoot != nil { + afmt.Println("Events emitted:") + + s := &apiIpldStore{ctx, api} + amt, err := amt4.LoadAMT(ctx, s, *eventsRoot, amt4.UseTreeBitWidth(types.EventAMTBitwidth)) + if err != nil { + return err + } + + var evt types.Event + err = amt.ForEach(ctx, func(u uint64, deferred *cbg.Deferred) error { + fmt.Printf("%x\n", deferred.Raw) + if err := evt.UnmarshalCBOR(bytes.NewReader(deferred.Raw)); err != nil { + return err + } + if err != nil { + return err + } + fmt.Printf("\tEmitter ID: %s\n", evt.Emitter) + for _, e := range evt.Entries { + value, err := cbg.ReadByteArray(bytes.NewBuffer(e.Value), uint64(len(e.Value))) + if err != nil { + return err + } + fmt.Printf("\t\tKey: %s, Value: 0x%x, Flags: b%b\n", e.Key, value, e.Flags) + } + return nil + + }) + } + if err != nil { + return err + } + + return nil + }, +} + +func ethAddrFromFilecoinAddress(ctx context.Context, addr address.Address, fnapi v0api.FullNode) (ethtypes.EthAddress, address.Address, error) { + var faddr address.Address + var err error + + switch addr.Protocol() { + case address.BLS, address.SECP256K1: + faddr, err = fnapi.StateLookupID(ctx, addr, types.EmptyTSK) + if err != nil { + return ethtypes.EthAddress{}, addr, err + } + case address.Actor, address.ID: + faddr, err = fnapi.StateLookupID(ctx, addr, types.EmptyTSK) + if err != nil { + return ethtypes.EthAddress{}, addr, err + } + fAct, err := fnapi.StateGetActor(ctx, faddr, types.EmptyTSK) + if err != nil { + return ethtypes.EthAddress{}, addr, err + } + if fAct.Address != nil && (*fAct.Address).Protocol() == address.Delegated { + faddr = *fAct.Address + } + case address.Delegated: + faddr = addr + default: + return ethtypes.EthAddress{}, addr, xerrors.Errorf("Filecoin address doesn't match known protocols") + } + + ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(faddr) + if err != nil { + return ethtypes.EthAddress{}, addr, err + } + + return ethAddr, faddr, nil +} diff --git a/cli/send.go b/cli/send.go index 4268f8eb2..3e390584d 100644 --- a/cli/send.go +++ b/cli/send.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" ) var sendCmd = &cli.Command{ @@ -24,6 +25,10 @@ var sendCmd = &cli.Command{ Name: "from", Usage: "optionally specify the account to send funds from", }, + &cli.StringFlag{ + Name: "from-eth-addr", + Usage: "optionally specify the eth addr to send funds from", + }, &cli.StringFlag{ Name: "gas-premium", Usage: "specify gas price to use in AttoFIL", @@ -98,6 +103,18 @@ var sendCmd = &cli.Command{ } params.From = addr + } else if from := cctx.String("from-eth-addr"); from != "" { + eaddr, err := ethtypes.ParseEthAddress(from) + if err != nil { + return err + } + faddr, err := eaddr.ToFilecoinAddress() + if err != nil { + fmt.Println("error on conversion to faddr") + return err + } + fmt.Println("f4 addr: ", faddr) + params.From = faddr } if cctx.IsSet("gas-premium") { diff --git a/cli/state.go b/cli/state.go index a29253dfc..7b8162aad 100644 --- a/cli/state.go +++ b/cli/state.go @@ -782,6 +782,9 @@ var StateGetActorCmd = &cli.Command{ fmt.Printf("Nonce:\t\t%d\n", a.Nonce) fmt.Printf("Code:\t\t%s (%s)\n", a.Code, strtype) fmt.Printf("Head:\t\t%s\n", a.Head) + if a.Address != nil { + fmt.Printf("Delegated address:\t\t%s\n", a.Address) + } return nil }, diff --git a/cmd/lotus-bench/import.go b/cmd/lotus-bench/import.go index 537109104..bb7dc6907 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -42,6 +42,7 @@ import ( "github.com/filecoin-project/lotus/chain/vm" lcli "github.com/filecoin-project/lotus/cli" _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" "github.com/filecoin-project/lotus/node/repo" "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" diff --git a/cmd/lotus-keygen/main.go b/cmd/lotus-keygen/main.go index 1970d5074..41993a169 100644 --- a/cmd/lotus-keygen/main.go +++ b/cmd/lotus-keygen/main.go @@ -10,6 +10,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" ) diff --git a/cmd/lotus-shed/keyinfo.go b/cmd/lotus-shed/keyinfo.go index 373964dc6..38f5ee6fe 100644 --- a/cmd/lotus-shed/keyinfo.go +++ b/cmd/lotus-shed/keyinfo.go @@ -23,6 +23,7 @@ import ( "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/chain/wallet/key" _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" "github.com/filecoin-project/lotus/node/modules" "github.com/filecoin-project/lotus/node/modules/lp2p" diff --git a/cmd/lotus-shed/migrations.go b/cmd/lotus-shed/migrations.go index 9cadb50ab..e305ba7e1 100644 --- a/cmd/lotus-shed/migrations.go +++ b/cmd/lotus-shed/migrations.go @@ -4,10 +4,10 @@ import ( "context" "fmt" "io" + "strconv" "time" "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" "github.com/urfave/cli/v2" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" @@ -25,6 +25,8 @@ import ( adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" "github.com/filecoin-project/go-state-types/manifest" + mutil "github.com/filecoin-project/go-state-types/migration" + "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/specs-actors/v7/actors/migration/nv15" "github.com/filecoin-project/lotus/blockstore" @@ -48,9 +50,9 @@ import ( ) var migrationsCmd = &cli.Command{ - Name: "migrate-nv17", - Description: "Run the nv17 migration", - ArgsUsage: "[block to look back from]", + Name: "migrate-state", + Description: "Run a network upgrade migration", + ArgsUsage: "[new network version, block to look back from]", Flags: []cli.Flag{ &cli.StringFlag{ Name: "repo", @@ -66,16 +68,21 @@ var migrationsCmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := context.TODO() - err := logging.SetLogLevelRegex("badger*", "ERROR") + if cctx.NArg() != 2 { + return lcli.IncorrectNumArgs(cctx) + } + + nv, err := strconv.ParseUint(cctx.Args().Get(0), 10, 32) + if err != nil { + return fmt.Errorf("failed to parse network version: %w", err) + } + + upgradeActorsFunc, preUpgradeActorsFunc, checkInvariantsFunc, err := getMigrationFuncsForNetwork(network.Version(nv)) if err != nil { return err } - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - blkCid, err := cid.Decode(cctx.Args().First()) + blkCid, err := cid.Decode(cctx.Args().Get(1)) if err != nil { return fmt.Errorf("failed to parse input: %w", err) } @@ -130,7 +137,7 @@ var migrationsCmd = &cli.Command{ startTime := time.Now() - newCid2, err := filcns.UpgradeActorsV9(ctx, sm, nv15.NewMemMigrationCache(), nil, blk.ParentStateRoot, blk.Height-1, migrationTs) + newCid2, err := upgradeActorsFunc(ctx, sm, nv15.NewMemMigrationCache(), nil, blk.ParentStateRoot, blk.Height-1, migrationTs) if err != nil { return err } @@ -143,7 +150,7 @@ var migrationsCmd = &cli.Command{ fmt.Println("completed round actual (without cache), took ", uncachedMigrationTime) if !cctx.IsSet("skip-pre-migration") { - cache := nv15.NewMemMigrationCache() + cache := mutil.NewMemMigrationCache() ts1, err := cs.GetTipsetByHeight(ctx, blk.Height-240, migrationTs, false) if err != nil { @@ -152,7 +159,7 @@ var migrationsCmd = &cli.Command{ startTime = time.Now() - err = filcns.PreUpgradeActorsV9(ctx, sm, cache, ts1.ParentState(), ts1.Height()-1, ts1) + err = preUpgradeActorsFunc(ctx, sm, cache, ts1.ParentState(), ts1.Height()-1, ts1) if err != nil { return err } @@ -166,7 +173,7 @@ var migrationsCmd = &cli.Command{ startTime = time.Now() - err = filcns.PreUpgradeActorsV9(ctx, sm, cache, ts2.ParentState(), ts2.Height()-1, ts2) + err = preUpgradeActorsFunc(ctx, sm, cache, ts2.ParentState(), ts2.Height()-1, ts2) if err != nil { return err } @@ -175,7 +182,7 @@ var migrationsCmd = &cli.Command{ startTime = time.Now() - newCid1, err := filcns.UpgradeActorsV9(ctx, sm, cache, nil, blk.ParentStateRoot, blk.Height-1, migrationTs) + newCid1, err := upgradeActorsFunc(ctx, sm, cache, nil, blk.ParentStateRoot, blk.Height-1, migrationTs) if err != nil { return err } @@ -192,7 +199,10 @@ var migrationsCmd = &cli.Command{ } if cctx.Bool("check-invariants") { - err = checkMigrationInvariants(ctx, blk.ParentStateRoot, newCid2, bs, blk.Height-1) + if checkInvariantsFunc == nil { + return xerrors.Errorf("check invariants not implemented for nv%d", nv) + } + err = checkInvariantsFunc(ctx, blk.ParentStateRoot, newCid2, bs, blk.Height-1) if err != nil { return err } @@ -202,7 +212,22 @@ var migrationsCmd = &cli.Command{ }, } -func checkMigrationInvariants(ctx context.Context, v8StateRootCid cid.Cid, v9StateRootCid cid.Cid, bs blockstore.Blockstore, epoch abi.ChainEpoch) error { +func getMigrationFuncsForNetwork(nv network.Version) (UpgradeActorsFunc, PreUpgradeActorsFunc, CheckInvariantsFunc, error) { + switch nv { + case network.Version17: + return filcns.UpgradeActorsV9, filcns.PreUpgradeActorsV9, checkNv17Invariants, nil + case network.Version18: + return filcns.UpgradeActorsV10, filcns.PreUpgradeActorsV10, nil, nil + default: + return nil, nil, nil, xerrors.Errorf("migration not implemented for nv%d", nv) + } +} + +type UpgradeActorsFunc = func(context.Context, *stmgr.StateManager, stmgr.MigrationCache, stmgr.ExecMonitor, cid.Cid, abi.ChainEpoch, *types.TipSet) (cid.Cid, error) +type PreUpgradeActorsFunc = func(context.Context, *stmgr.StateManager, stmgr.MigrationCache, cid.Cid, abi.ChainEpoch, *types.TipSet) error +type CheckInvariantsFunc = func(context.Context, cid.Cid, cid.Cid, blockstore.Blockstore, abi.ChainEpoch) error + +func checkNv17Invariants(ctx context.Context, v8StateRootCid cid.Cid, v9StateRootCid cid.Cid, bs blockstore.Blockstore, epoch abi.ChainEpoch) error { actorStore := store.ActorStore(ctx, bs) startTime := time.Now() diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go index 341877a88..4dddba921 100644 --- a/cmd/lotus-sim/simulation/block.go +++ b/cmd/lotus-sim/simulation/block.go @@ -74,14 +74,17 @@ func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message Timestamp: uts, ElectionProof: &types.ElectionProof{WinCount: 1}, }} - err = sim.Node.Chainstore.PersistBlockHeaders(ctx, blks...) - if err != nil { - return nil, xerrors.Errorf("failed to persist block headers: %w", err) - } + newTipSet, err := types.NewTipSet(blks) if err != nil { return nil, xerrors.Errorf("failed to create new tipset: %w", err) } + + err = sim.Node.Chainstore.PersistTipset(ctx, newTipSet) + if err != nil { + return nil, xerrors.Errorf("failed to persist block headers: %w", err) + } + now := time.Now() _, _, err = sim.StateManager.TipSetState(ctx, newTipSet) if err != nil { diff --git a/conformance/driver.go b/conformance/driver.go index ef9165e32..3a32c7604 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -28,7 +28,8 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/conformance/chaos" - _ "github.com/filecoin-project/lotus/lib/sigs/bls" // enable bls signatures + _ "github.com/filecoin-project/lotus/lib/sigs/bls" // enable bls signatures + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" // enable secp signatures "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" ) diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index cb94cf3f7..d18be03f5 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -464,7 +464,9 @@ Inputs: { "SealProof": 8, "SectorNumber": 9, - "SectorKey": null, + "SectorKey": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SealedCID": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" } @@ -1279,8 +1281,12 @@ Response: "ProposalCid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "AddFundsCid": null, - "PublishCid": null, + "AddFundsCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "PublishCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Miner": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", "Client": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", "State": 42, @@ -1295,7 +1301,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1457,8 +1465,12 @@ Response: "ProposalCid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "AddFundsCid": null, - "PublishCid": null, + "AddFundsCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "PublishCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Miner": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", "Client": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", "State": 42, @@ -1473,7 +1485,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1509,7 +1523,9 @@ Response: "Selector": { "Raw": "Ynl0ZSBhcnJheQ==" }, - "PieceCID": null, + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PricePerByte": "0", "PaymentInterval": 42, "PaymentIntervalIncrease": 42, @@ -2908,7 +2924,9 @@ Inputs: 1024, {}, { - "PublishCid": null, + "PublishCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "DealID": 5432, "DealProposal": { "PieceCID": { @@ -2962,7 +2980,9 @@ Response: "FailedSectors": { "123": "can't acquire read lock" }, - "Msg": null, + "Msg": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Error": "string value" } ] @@ -3156,7 +3176,9 @@ Response: 123, 124 ], - "Msg": null, + "Msg": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Error": "string value" } ] @@ -3204,7 +3226,9 @@ Inputs: } }, "DealInfo": { - "PublishCid": null, + "PublishCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "DealID": 5432, "DealProposal": { "PieceCID": { @@ -3232,8 +3256,12 @@ Inputs: "TicketValue": "Bw==", "TicketEpoch": 10101, "PreCommit1Out": "Bw==", - "CommD": null, - "CommR": null, + "CommD": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "CommR": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PreCommitInfo": { "SealProof": 8, "SectorNumber": 9, @@ -3245,10 +3273,14 @@ Inputs: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "PreCommitDeposit": "0", - "PreCommitMessage": null, + "PreCommitMessage": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PreCommitTipSet": [ { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -3260,7 +3292,9 @@ Inputs: "SeedValue": "Bw==", "SeedEpoch": 10101, "CommitProof": "Ynl0ZSBhcnJheQ==", - "CommitMessage": null, + "CommitMessage": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Log": [ { "Kind": "string value", @@ -3396,7 +3430,12 @@ Perms: admin Inputs: `null` -Response: `null` +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` ### SectorTerminatePending SectorTerminatePending returns a list of pending sector terminations to be sent in the next batch message @@ -3497,8 +3536,12 @@ Response: { "SectorID": 9, "State": "Proving", - "CommD": null, - "CommR": null, + "CommD": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "CommR": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Proof": "Ynl0ZSBhcnJheQ==", "Deals": [ 5432 @@ -3512,7 +3555,9 @@ Response: } }, "DealInfo": { - "PublishCid": null, + "PublishCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "DealID": 5432, "DealProposal": { "PieceCID": { @@ -3545,11 +3590,17 @@ Response: "Value": "Bw==", "Epoch": 10101 }, - "PreCommitMsg": null, - "CommitMsg": null, + "PreCommitMsg": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "CommitMsg": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Retries": 42, "ToUpgrade": true, - "ReplicaUpdateMessage": null, + "ReplicaUpdateMessage": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "LastErr": "string value", "Log": [ { @@ -3603,7 +3654,9 @@ Inputs: 1040384, 1024, "Bw==", - null + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } ] ``` diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 89b42b53e..fe639b2f3 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -754,7 +754,10 @@ Response: { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } ] ``` @@ -1247,7 +1250,9 @@ Inputs: { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - null + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } ] ``` @@ -1259,7 +1264,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Size": 42, "MinPrice": "0", "UnsealPrice": "0", @@ -1270,7 +1277,9 @@ Response: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } } ] @@ -1341,7 +1350,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1445,7 +1456,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1510,7 +1523,9 @@ Response: "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, "ID": 5, - "PieceCID": null, + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PricePerByte": "0", "UnsealPrice": "0", "Status": 0, @@ -1683,7 +1698,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1748,7 +1765,9 @@ Response: { "Key": 50, "Err": "string value", - "Root": null, + "Root": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Source": "string value", "FilePath": "string value", "CARPath": "string value" @@ -1773,7 +1792,9 @@ Response: "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, "ID": 5, - "PieceCID": null, + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PricePerByte": "0", "UnsealPrice": "0", "Status": 0, @@ -1834,7 +1855,9 @@ Inputs: { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - null + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } ] ``` @@ -1845,7 +1868,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Size": 42, "MinPrice": "0", "UnsealPrice": "0", @@ -1856,7 +1881,9 @@ Response: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } } ``` @@ -1933,7 +1960,9 @@ Inputs: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "DatamodelPathSelector": "Links/21/Hash/Links/42/Hash", "Size": 42, "FromLocalCAR": "string value", @@ -1946,7 +1975,9 @@ Inputs: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } }, { @@ -1988,7 +2019,9 @@ Inputs: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "DatamodelPathSelector": "Links/21/Hash/Links/42/Hash", "Size": 42, "FromLocalCAR": "string value", @@ -2001,7 +2034,9 @@ Inputs: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } }, { @@ -2037,7 +2072,9 @@ Inputs: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -2053,7 +2090,12 @@ Inputs: ] ``` -Response: `null` +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` ### ClientStatelessDeal ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. @@ -2070,7 +2112,9 @@ Inputs: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -2086,7 +2130,12 @@ Inputs: ] ``` -Response: `null` +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` ## Create @@ -2613,7 +2662,9 @@ Response: { "SealProof": 8, "SectorNumber": 9, - "SectorKey": null, + "SectorKey": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SealedCID": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" } @@ -4156,7 +4207,9 @@ Response: "PendingAmt": "0", "NonReservedAmt": "0", "PendingAvailableAmt": "0", - "PendingWaitSentinel": null, + "PendingWaitSentinel": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" } @@ -4185,7 +4238,9 @@ Response: "PendingAmt": "0", "NonReservedAmt": "0", "PendingAvailableAmt": "0", - "PendingWaitSentinel": null, + "PendingWaitSentinel": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" } @@ -4792,7 +4847,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -4825,7 +4883,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -4869,7 +4930,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5059,7 +5123,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -5092,7 +5159,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5136,7 +5206,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5460,7 +5533,8 @@ Response: "UpgradeChocolateHeight": 10101, "UpgradeOhSnapHeight": 10101, "UpgradeSkyrHeight": 10101, - "UpgradeSharkHeight": 10101 + "UpgradeSharkHeight": 10101, + "UpgradeHyggeHeight": 10101 } } ``` @@ -5551,7 +5625,10 @@ Response: { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } ``` @@ -5864,7 +5941,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ] @@ -6032,7 +6111,9 @@ Inputs: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, [ { @@ -6154,7 +6235,9 @@ Inputs: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, [ { @@ -6337,7 +6420,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ] @@ -6470,7 +6555,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -6503,7 +6591,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -6547,7 +6638,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -6618,7 +6712,10 @@ Response: "Receipt": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "ReturnDec": {}, "TipSet": [ @@ -6672,7 +6769,10 @@ Response: "Receipt": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "ReturnDec": {}, "TipSet": [ @@ -6761,7 +6861,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ``` @@ -6832,7 +6934,9 @@ Response: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "PreCommitDeposit": "0", "PreCommitEpoch": 10101 @@ -6984,7 +7088,10 @@ Response: "Receipt": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "ReturnDec": {}, "TipSet": [ @@ -7041,7 +7148,10 @@ Response: "Receipt": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "ReturnDec": {}, "TipSet": [ diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 24fd88c95..2c853754b 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -15,6 +15,7 @@ * [ChainExport](#ChainExport) * [ChainGetBlock](#ChainGetBlock) * [ChainGetBlockMessages](#ChainGetBlockMessages) + * [ChainGetEvents](#ChainGetEvents) * [ChainGetGenesis](#ChainGetGenesis) * [ChainGetMessage](#ChainGetMessage) * [ChainGetMessagesInTipset](#ChainGetMessagesInTipset) @@ -65,6 +66,40 @@ * [ClientStatelessDeal](#ClientStatelessDeal) * [Create](#Create) * [CreateBackup](#CreateBackup) +* [Eth](#Eth) + * [EthAccounts](#EthAccounts) + * [EthBlockNumber](#EthBlockNumber) + * [EthCall](#EthCall) + * [EthChainId](#EthChainId) + * [EthEstimateGas](#EthEstimateGas) + * [EthFeeHistory](#EthFeeHistory) + * [EthGasPrice](#EthGasPrice) + * [EthGetBalance](#EthGetBalance) + * [EthGetBlockByHash](#EthGetBlockByHash) + * [EthGetBlockByNumber](#EthGetBlockByNumber) + * [EthGetBlockTransactionCountByHash](#EthGetBlockTransactionCountByHash) + * [EthGetBlockTransactionCountByNumber](#EthGetBlockTransactionCountByNumber) + * [EthGetCode](#EthGetCode) + * [EthGetFilterChanges](#EthGetFilterChanges) + * [EthGetFilterLogs](#EthGetFilterLogs) + * [EthGetLogs](#EthGetLogs) + * [EthGetMessageCidByTransactionHash](#EthGetMessageCidByTransactionHash) + * [EthGetStorageAt](#EthGetStorageAt) + * [EthGetTransactionByBlockHashAndIndex](#EthGetTransactionByBlockHashAndIndex) + * [EthGetTransactionByBlockNumberAndIndex](#EthGetTransactionByBlockNumberAndIndex) + * [EthGetTransactionByHash](#EthGetTransactionByHash) + * [EthGetTransactionCount](#EthGetTransactionCount) + * [EthGetTransactionHashByCid](#EthGetTransactionHashByCid) + * [EthGetTransactionReceipt](#EthGetTransactionReceipt) + * [EthMaxPriorityFeePerGas](#EthMaxPriorityFeePerGas) + * [EthNewBlockFilter](#EthNewBlockFilter) + * [EthNewFilter](#EthNewFilter) + * [EthNewPendingTransactionFilter](#EthNewPendingTransactionFilter) + * [EthProtocolVersion](#EthProtocolVersion) + * [EthSendRawTransaction](#EthSendRawTransaction) + * [EthSubscribe](#EthSubscribe) + * [EthUninstallFilter](#EthUninstallFilter) + * [EthUnsubscribe](#EthUnsubscribe) * [Gas](#Gas) * [GasEstimateFeeCap](#GasEstimateFeeCap) * [GasEstimateGasLimit](#GasEstimateGasLimit) @@ -135,6 +170,7 @@ * [NetDisconnect](#NetDisconnect) * [NetFindPeer](#NetFindPeer) * [NetLimit](#NetLimit) + * [NetListening](#NetListening) * [NetPeerInfo](#NetPeerInfo) * [NetPeers](#NetPeers) * [NetPing](#NetPing) @@ -144,6 +180,7 @@ * [NetPubsubScores](#NetPubsubScores) * [NetSetLimit](#NetSetLimit) * [NetStat](#NetStat) + * [NetVersion](#NetVersion) * [Node](#Node) * [NodeStatus](#NodeStatus) * [Paych](#Paych) @@ -581,6 +618,37 @@ Response: } ``` +### ChainGetEvents +ChainGetEvents returns the events under an event AMT root CID. + + +Perms: read + +Inputs: +```json +[ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } +] +``` + +Response: +```json +[ + { + "Emitter": 1000, + "Entries": [ + { + "Flags": 7, + "Key": "string value", + "Value": "Ynl0ZSBhcnJheQ==" + } + ] + } +] +``` + ### ChainGetGenesis ChainGetGenesis returns the genesis tipset. @@ -766,7 +834,10 @@ Response: { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } ] ``` @@ -1291,7 +1362,9 @@ Inputs: { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - null + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } ] ``` @@ -1303,7 +1376,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Size": 42, "MinPrice": "0", "UnsealPrice": "0", @@ -1314,7 +1389,9 @@ Response: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } } ] @@ -1385,7 +1462,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1489,7 +1568,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1554,7 +1635,9 @@ Response: "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, "ID": 5, - "PieceCID": null, + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PricePerByte": "0", "UnsealPrice": "0", "Status": 0, @@ -1727,7 +1810,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1792,7 +1877,9 @@ Response: { "Key": 50, "Err": "string value", - "Root": null, + "Root": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Source": "string value", "FilePath": "string value", "CARPath": "string value" @@ -1816,7 +1903,9 @@ Response: "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, "ID": 5, - "PieceCID": null, + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PricePerByte": "0", "UnsealPrice": "0", "Status": 0, @@ -1877,7 +1966,9 @@ Inputs: { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - null + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } ] ``` @@ -1888,7 +1979,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Size": 42, "MinPrice": "0", "UnsealPrice": "0", @@ -1899,7 +1992,9 @@ Response: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } } ``` @@ -1982,7 +2077,9 @@ Inputs: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "DataSelector": "Links/21/Hash/Links/42/Hash", "Size": 42, "Total": "0", @@ -1994,7 +2091,9 @@ Inputs: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "RemoteStore": "00000000-0000-0000-0000-000000000000" } @@ -2054,7 +2153,9 @@ Inputs: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -2070,7 +2171,12 @@ Inputs: ] ``` -Response: `null` +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` ### ClientStatelessDeal ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. @@ -2087,7 +2193,9 @@ Inputs: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -2103,7 +2211,12 @@ Inputs: ] ``` -Response: `null` +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` ## Create @@ -2126,6 +2239,973 @@ Inputs: Response: `{}` +## Eth +These methods are used for Ethereum-compatible JSON-RPC calls + +EthAccounts will always return [] since we don't expect Lotus to manage private keys + + +### EthAccounts +There are not yet any comments for this method. + +Perms: read + +Inputs: `null` + +Response: +```json +[ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031" +] +``` + +### EthBlockNumber +EthBlockNumber returns the height of the latest (heaviest) TipSet + + +Perms: read + +Inputs: `null` + +Response: `"0x5"` + +### EthCall + + +Perms: read + +Inputs: +```json +[ + { + "from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "gas": "0x5", + "gasPrice": "0x0", + "value": "0x0", + "data": "0x07" + }, + "string value" +] +``` + +Response: `"0x07"` + +### EthChainId + + +Perms: read + +Inputs: `null` + +Response: `"0x5"` + +### EthEstimateGas + + +Perms: read + +Inputs: +```json +[ + { + "from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "gas": "0x5", + "gasPrice": "0x0", + "value": "0x0", + "data": "0x07" + } +] +``` + +Response: `"0x5"` + +### EthFeeHistory + + +Perms: read + +Inputs: +```json +[ + "0x5", + "string value", + [ + 12.3 + ] +] +``` + +Response: +```json +{ + "oldestBlock": 42, + "baseFeePerGas": [ + "0x0" + ], + "gasUsedRatio": [ + 12.3 + ], + "reward": [] +} +``` + +### EthGasPrice + + +Perms: read + +Inputs: `null` + +Response: `"0x0"` + +### EthGetBalance + + +Perms: read + +Inputs: +```json +[ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "string value" +] +``` + +Response: `"0x0"` + +### EthGetBlockByHash + + +Perms: read + +Inputs: +```json +[ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + true +] +``` + +Response: +```json +{ + "hash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "parentHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "sha3Uncles": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "miner": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "stateRoot": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "transactionsRoot": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "receiptsRoot": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "logsBloom": "0x07", + "difficulty": "0x5", + "totalDifficulty": "0x5", + "number": "0x5", + "gasLimit": "0x5", + "gasUsed": "0x5", + "timestamp": "0x5", + "extraData": "Ynl0ZSBhcnJheQ==", + "mixHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "nonce": "0x0707070707070707", + "baseFeePerGas": "0x0", + "size": "0x5", + "transactions": [ + {} + ], + "uncles": [ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" + ] +} +``` + +### EthGetBlockByNumber + + +Perms: read + +Inputs: +```json +[ + "string value", + true +] +``` + +Response: +```json +{ + "hash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "parentHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "sha3Uncles": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "miner": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "stateRoot": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "transactionsRoot": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "receiptsRoot": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "logsBloom": "0x07", + "difficulty": "0x5", + "totalDifficulty": "0x5", + "number": "0x5", + "gasLimit": "0x5", + "gasUsed": "0x5", + "timestamp": "0x5", + "extraData": "Ynl0ZSBhcnJheQ==", + "mixHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "nonce": "0x0707070707070707", + "baseFeePerGas": "0x0", + "size": "0x5", + "transactions": [ + {} + ], + "uncles": [ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" + ] +} +``` + +### EthGetBlockTransactionCountByHash +EthGetBlockTransactionCountByHash returns the number of messages in the TipSet + + +Perms: read + +Inputs: +```json +[ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" +] +``` + +Response: `"0x5"` + +### EthGetBlockTransactionCountByNumber +EthGetBlockTransactionCountByNumber returns the number of messages in the TipSet + + +Perms: read + +Inputs: +```json +[ + "0x5" +] +``` + +Response: `"0x5"` + +### EthGetCode + + +Perms: read + +Inputs: +```json +[ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "string value" +] +``` + +Response: `"0x07"` + +### EthGetFilterChanges +Polling method for a filter, returns event logs which occurred since last poll. +(requires write perm since timestamp of last filter execution will be written) + + +Perms: write + +Inputs: +```json +[ + [ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 + ] +] +``` + +Response: +```json +[ + {} +] +``` + +### EthGetFilterLogs +Returns event logs matching filter with given id. +(requires write perm since timestamp of last filter execution will be written) + + +Perms: write + +Inputs: +```json +[ + [ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 + ] +] +``` + +Response: +```json +[ + {} +] +``` + +### EthGetLogs +Returns event logs matching given filter spec. + + +Perms: read + +Inputs: +```json +[ + { + "fromBlock": "2301220", + "address": [ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031" + ], + "topics": null + } +] +``` + +Response: +```json +[ + {} +] +``` + +### EthGetMessageCidByTransactionHash + + +Perms: read + +Inputs: +```json +[ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" +] +``` + +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` + +### EthGetStorageAt + + +Perms: read + +Inputs: +```json +[ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "0x07", + "string value" +] +``` + +Response: `"0x07"` + +### EthGetTransactionByBlockHashAndIndex + + +Perms: read + +Inputs: +```json +[ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "0x5" +] +``` + +Response: +```json +{ + "chainId": "0x5", + "nonce": "0x5", + "hash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockNumber": "0x5", + "transactionIndex": "0x5", + "from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "value": "0x0", + "type": "0x5", + "input": "0x07", + "gas": "0x5", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "v": "0x0", + "r": "0x0", + "s": "0x0" +} +``` + +### EthGetTransactionByBlockNumberAndIndex + + +Perms: read + +Inputs: +```json +[ + "0x5", + "0x5" +] +``` + +Response: +```json +{ + "chainId": "0x5", + "nonce": "0x5", + "hash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockNumber": "0x5", + "transactionIndex": "0x5", + "from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "value": "0x0", + "type": "0x5", + "input": "0x07", + "gas": "0x5", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "v": "0x0", + "r": "0x0", + "s": "0x0" +} +``` + +### EthGetTransactionByHash + + +Perms: read + +Inputs: +```json +[ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" +] +``` + +Response: +```json +{ + "chainId": "0x5", + "nonce": "0x5", + "hash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockNumber": "0x5", + "transactionIndex": "0x5", + "from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "value": "0x0", + "type": "0x5", + "input": "0x07", + "gas": "0x5", + "maxFeePerGas": "0x0", + "maxPriorityFeePerGas": "0x0", + "v": "0x0", + "r": "0x0", + "s": "0x0" +} +``` + +### EthGetTransactionCount + + +Perms: read + +Inputs: +```json +[ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "string value" +] +``` + +Response: `"0x5"` + +### EthGetTransactionHashByCid + + +Perms: read + +Inputs: +```json +[ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } +] +``` + +Response: `"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"` + +### EthGetTransactionReceipt + + +Perms: read + +Inputs: +```json +[ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" +] +``` + +Response: +```json +{ + "transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "transactionIndex": "0x5", + "blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockNumber": "0x5", + "from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "root": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "status": "0x5", + "contractAddress": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "cumulativeGasUsed": "0x5", + "gasUsed": "0x5", + "effectiveGasPrice": "0x0", + "logsBloom": "0x07", + "logs": [ + { + "address": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "data": "0x07", + "topics": [ + "0x07" + ], + "removed": true, + "logIndex": "0x5", + "transactionIndex": "0x5", + "transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e", + "blockNumber": "0x5" + } + ], + "type": "0x5" +} +``` + +### EthMaxPriorityFeePerGas + + +Perms: read + +Inputs: `null` + +Response: `"0x0"` + +### EthNewBlockFilter +Installs a persistent filter to notify when a new block arrives. + + +Perms: write + +Inputs: `null` + +Response: +```json +[ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 +] +``` + +### EthNewFilter +Installs a persistent filter based on given filter spec. + + +Perms: write + +Inputs: +```json +[ + { + "fromBlock": "2301220", + "address": [ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031" + ], + "topics": null + } +] +``` + +Response: +```json +[ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 +] +``` + +### EthNewPendingTransactionFilter +Installs a persistent filter to notify when new messages arrive in the message pool. + + +Perms: write + +Inputs: `null` + +Response: +```json +[ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 +] +``` + +### EthProtocolVersion + + +Perms: read + +Inputs: `null` + +Response: `"0x5"` + +### EthSendRawTransaction + + +Perms: read + +Inputs: +```json +[ + "0x07" +] +``` + +Response: `"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"` + +### EthSubscribe +Subscribe to different event types using websockets +eventTypes is one or more of: + - newHeads: notify when new blocks arrive. + - pendingTransactions: notify when new messages arrive in the message pool. + - logs: notify new event logs that match a criteria +params contains additional parameters used with the log event type +The client will receive a stream of EthSubscriptionResponse values until EthUnsubscribe is called. + + +Perms: write + +Inputs: +```json +[ + "string value", + { + "topics": [ + [ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" + ] + ] + } +] +``` + +Response: +```json +{ + "subscription": [ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 + ], + "result": {} +} +``` + +### EthUninstallFilter +Uninstalls a filter with given id. + + +Perms: write + +Inputs: +```json +[ + [ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 + ] +] +``` + +Response: `true` + +### EthUnsubscribe +Unsubscribe from a websocket subscription + + +Perms: write + +Inputs: +```json +[ + [ + 55, + 105, + 12, + 254, + 198, + 193, + 191, + 76, + 59, + 146, + 136, + 199, + 165, + 215, + 131, + 233, + 135, + 49, + 233, + 11, + 10, + 76, + 23, + 124, + 42, + 55, + 76, + 122, + 148, + 39, + 53, + 94 + ] +] +``` + +Response: `true` + ## Gas @@ -2630,7 +3710,9 @@ Response: { "SealProof": 8, "SectorNumber": 9, - "SectorKey": null, + "SectorKey": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SealedCID": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" } @@ -4249,6 +5331,15 @@ Response: } ``` +### NetListening + + +Perms: read + +Inputs: `null` + +Response: `true` + ### NetPeerInfo @@ -4482,6 +5573,15 @@ Response: } ``` +### NetVersion + + +Perms: read + +Inputs: `null` + +Response: `"string value"` + ## Node These methods are general node management and status commands @@ -4556,7 +5656,9 @@ Response: "PendingAmt": "0", "NonReservedAmt": "0", "PendingAvailableAmt": "0", - "PendingWaitSentinel": null, + "PendingWaitSentinel": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" } @@ -4585,7 +5687,9 @@ Response: "PendingAmt": "0", "NonReservedAmt": "0", "PendingAvailableAmt": "0", - "PendingWaitSentinel": null, + "PendingWaitSentinel": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" } @@ -5255,7 +6359,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -5288,7 +6395,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5332,7 +6442,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5522,7 +6635,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -5555,7 +6671,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5599,7 +6718,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5998,7 +7120,8 @@ Response: "UpgradeChocolateHeight": 10101, "UpgradeOhSnapHeight": 10101, "UpgradeSkyrHeight": 10101, - "UpgradeSharkHeight": 10101 + "UpgradeSharkHeight": 10101, + "UpgradeHyggeHeight": 10101 } } ``` @@ -6385,7 +7508,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ] @@ -6581,7 +7706,9 @@ Inputs: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, [ { @@ -6703,7 +7830,9 @@ Inputs: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, [ { @@ -6886,7 +8015,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ] @@ -7019,7 +8150,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -7052,7 +8186,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -7096,7 +8233,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -7178,7 +8318,10 @@ Response: "Receipt": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "ReturnDec": {}, "TipSet": [ @@ -7267,7 +8410,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ``` @@ -7343,7 +8488,9 @@ Response: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "PreCommitDeposit": "0", "PreCommitEpoch": 10101 @@ -7499,7 +8646,10 @@ Response: "Receipt": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "ReturnDec": {}, "TipSet": [ diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 53f55e61d..fbb020b0e 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -31,6 +31,7 @@ COMMANDS: log Manage logging wait-api Wait for lotus api to come online fetch-params Fetch proving parameters + evm Commands related to the Filecoin EVM runtime NETWORK: net Manage P2P Network sync Inspect or interact with the chain syncer @@ -181,15 +182,16 @@ CATEGORY: BASIC OPTIONS: - --force Deprecated: use global 'force-send' (default: false) - --from value optionally specify the account to send funds from - --gas-feecap value specify gas fee cap to use in AttoFIL (default: "0") - --gas-limit value specify gas limit (default: 0) - --gas-premium value specify gas price to use in AttoFIL (default: "0") - --method value specify method to invoke (default: 0) - --nonce value specify the nonce to use (default: 0) - --params-hex value specify invocation parameters in hex - --params-json value specify invocation parameters in json + --force Deprecated: use global 'force-send' (default: false) + --from value optionally specify the account to send funds from + --from-eth-addr value optionally specify the eth addr to send funds from + --gas-feecap value specify gas fee cap to use in AttoFIL (default: "0") + --gas-limit value specify gas limit (default: 0) + --gas-premium value specify gas price to use in AttoFIL (default: "0") + --method value specify method to invoke (default: 0) + --nonce value specify the nonce to use (default: 0) + --params-hex value specify invocation parameters in hex + --params-json value specify invocation parameters in json ``` @@ -2568,6 +2570,94 @@ OPTIONS: ``` +## lotus evm +``` +NAME: + lotus evm - Commands related to the Filecoin EVM runtime + +USAGE: + lotus evm command [command options] [arguments...] + +COMMANDS: + deploy Deploy an EVM smart contract and return its address + invoke Invoke an EVM smart contract using the specified CALLDATA + stat Print eth/filecoin addrs and code cid + call Simulate an eth contract call + contract-address Generate contract address from smart contract code + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus evm deploy +``` +NAME: + lotus evm deploy - Deploy an EVM smart contract and return its address + +USAGE: + lotus evm deploy [command options] contract + +OPTIONS: + --from value optionally specify the account to use for sending the creation message + --hex use when input contract is in hex (default: false) + +``` + +### lotus evm invoke +``` +NAME: + lotus evm invoke - Invoke an EVM smart contract using the specified CALLDATA + +USAGE: + lotus evm invoke [command options] address calldata + +OPTIONS: + --from value optionally specify the account to use for sending the exec message + --value value optionally specify the value to be sent with the invokation message (default: 0) + +``` + +### lotus evm stat +``` +NAME: + lotus evm stat - Print eth/filecoin addrs and code cid + +USAGE: + lotus evm stat [command options] address + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus evm call +``` +NAME: + lotus evm call - Simulate an eth contract call + +USAGE: + lotus evm call [command options] [from] [to] [params] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus evm contract-address +``` +NAME: + lotus evm contract-address - Generate contract address from smart contract code + +USAGE: + lotus evm contract-address [command options] [senderEthAddr] [salt] [contractHexPath] + +OPTIONS: + --help, -h show help (default: false) + +``` + ## lotus net ``` NAME: diff --git a/documentation/en/default-lotus-config.toml b/documentation/en/default-lotus-config.toml index 7f4ff591b..41d7e6aca 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -293,3 +293,71 @@ #Tracing = false +[Fevm] + # EnableEthRPC enables eth_ rpc, and enables storing a mapping of eth transaction hashes to filecoin message Cids. + # This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above. + # + # type: bool + # env var: LOTUS_FEVM_ENABLEETHRPC + #EnableEthRPC = false + + # EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days + # Set to 0 to keep all mappings + # + # type: int + # env var: LOTUS_FEVM_ETHTXHASHMAPPINGLIFETIMEDAYS + #EthTxHashMappingLifetimeDays = 0 + + [Fevm.Events] + # EnableEthRPC enables APIs that + # DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. + # The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + # + # type: bool + # env var: LOTUS_FEVM_EVENTS_DISABLEREALTIMEFILTERAPI + #DisableRealTimeFilterAPI = false + + # DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events + # that occurred in the past. HistoricFilterAPI maintains a queryable index of events. + # The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + # + # type: bool + # env var: LOTUS_FEVM_EVENTS_DISABLEHISTORICFILTERAPI + #DisableHistoricFilterAPI = false + + # FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than + # this time become eligible for automatic deletion. + # + # type: Duration + # env var: LOTUS_FEVM_EVENTS_FILTERTTL + #FilterTTL = "24h0m0s" + + # MaxFilters specifies the maximum number of filters that may exist at any one time. + # + # type: int + # env var: LOTUS_FEVM_EVENTS_MAXFILTERS + #MaxFilters = 100 + + # MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter. + # + # type: int + # env var: LOTUS_FEVM_EVENTS_MAXFILTERRESULTS + #MaxFilterResults = 10000 + + # MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying + # the entire chain) + # + # type: uint64 + # env var: LOTUS_FEVM_EVENTS_MAXFILTERHEIGHTRANGE + #MaxFilterHeightRange = 2880 + + # DatabasePath is the full path to a sqlite database that will be used to index actor events to + # support the historic filter APIs. If the database does not exist it will be created. The directory containing + # the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as + # relative to the CWD (current working directory). + # + # type: string + # env var: LOTUS_FEVM_EVENTS_DATABASEPATH + #DatabasePath = "" + + diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 02ebb2d61..86eac2161 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 02ebb2d6169131cfe489e1063e896f14982c463d +Subproject commit 86eac2161f442945bffee3fbfe7d094c20b48dd3 diff --git a/gateway/handler.go b/gateway/handler.go index be824b430..e9bee6d52 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -25,6 +25,10 @@ type perConnLimiterKeyType string const perConnLimiterKey perConnLimiterKeyType = "limiter" +type filterTrackerKeyType string + +const filterTrackerKey filterTrackerKeyType = "filterTracker" + // Handler returns a gateway http.Handler, to be mounted as-is on the server. func Handler(gwapi lapi.Gateway, api lapi.FullNode, rateLimit int64, connPerMinute int64, opts ...jsonrpc.ServerOption) (http.Handler, error) { m := mux.NewRouter() @@ -34,6 +38,8 @@ func Handler(gwapi lapi.Gateway, api lapi.FullNode, rateLimit int64, connPerMinu rpcServer.Register("Filecoin", hnd) rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") + lapi.CreateEthRPCAliases(rpcServer) + m.Handle(path, rpcServer) } @@ -81,8 +87,12 @@ type RateLimiterHandler struct { } func (h RateLimiterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - r2 := r.WithContext(context.WithValue(r.Context(), perConnLimiterKey, h.limiter)) - h.handler.ServeHTTP(w, r2) + r = r.WithContext(context.WithValue(r.Context(), perConnLimiterKey, h.limiter)) + + // also add a filter tracker to the context + r = r.WithContext(context.WithValue(r.Context(), filterTrackerKey, newFilterTracker())) + + h.handler.ServeHTTP(w, r) } // this blocks new connections if there have already been too many. diff --git a/gateway/node.go b/gateway/node.go index 7e84092e3..f18189ae2 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -9,22 +9,20 @@ import ( "github.com/ipfs/go-cid" "go.opencensus.io/stats" "golang.org/x/time/rate" - "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/dline" "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/api" - apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/sigs" + "github.com/filecoin-project/lotus/chain/types/ethtypes" _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/node/impl/full" @@ -87,7 +85,40 @@ type TargetAPI interface { StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) StateVMCirculatingSupplyInternal(context.Context, types.TipSetKey) (api.CirculatingSupply, error) - WalletBalance(context.Context, address.Address) (types.BigInt, error) //perm:read + WalletBalance(context.Context, address.Address) (types.BigInt, error) + + EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) + EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) + EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) + EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) + EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) + EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) + 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) + EthChainId(ctx context.Context) (ethtypes.EthUint64, error) + NetVersion(ctx context.Context) (string, error) + 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) + 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) + EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) + EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) + EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) + EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) + EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) + EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) } var _ TargetAPI = *new(api.FullNode) // gateway depends on latest @@ -183,462 +214,3 @@ func (gw *Node) limit(ctx context.Context, tokens int) error { } return nil } - -func (gw *Node) Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) { - return build.OpenRPCDiscoverJSON_Gateway(), nil -} - -func (gw *Node) Version(ctx context.Context) (api.APIVersion, error) { - if err := gw.limit(ctx, basicRateLimitTokens); err != nil { - return api.APIVersion{}, err - } - return gw.target.Version(ctx) -} - -func (gw *Node) ChainGetParentMessages(ctx context.Context, c cid.Cid) ([]api.Message, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainGetParentMessages(ctx, c) -} - -func (gw *Node) ChainGetParentReceipts(ctx context.Context, c cid.Cid) ([]*types.MessageReceipt, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainGetParentReceipts(ctx, c) -} - -func (gw *Node) ChainGetBlockMessages(ctx context.Context, c cid.Cid) (*api.BlockMessages, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainGetBlockMessages(ctx, c) -} - -func (gw *Node) ChainHasObj(ctx context.Context, c cid.Cid) (bool, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return false, err - } - return gw.target.ChainHasObj(ctx, c) -} - -func (gw *Node) ChainHead(ctx context.Context) (*types.TipSet, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - - return gw.target.ChainHead(ctx) -} - -func (gw *Node) ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainGetMessage(ctx, mc) -} - -func (gw *Node) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainGetTipSet(ctx, tsk) -} - -func (gw *Node) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { - return nil, err - } - return gw.target.ChainGetTipSetByHeight(ctx, h, tsk) -} - -func (gw *Node) ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { - return nil, err - } - return gw.target.ChainGetTipSetAfterHeight(ctx, h, tsk) -} - -func (gw *Node) checkTipSetHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) error { - var ts *types.TipSet - if tsk.IsEmpty() { - head, err := gw.target.ChainHead(ctx) - if err != nil { - return err - } - ts = head - } else { - gts, err := gw.target.ChainGetTipSet(ctx, tsk) - if err != nil { - return err - } - ts = gts - } - - // Check if the tipset key refers to gw tipset that's too far in the past - if err := gw.checkTipset(ts); err != nil { - return err - } - - // Check if the height is too far in the past - if err := gw.checkTipsetHeight(ts, h); err != nil { - return err - } - - return nil -} - -func (gw *Node) ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainGetNode(ctx, p) -} - -func (gw *Node) ChainNotify(ctx context.Context) (<-chan []*api.HeadChange, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainNotify(ctx) -} - -func (gw *Node) ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, from); err != nil { - return nil, xerrors.Errorf("gateway: checking 'from' tipset: %w", err) - } - if err := gw.checkTipsetKey(ctx, to); err != nil { - return nil, xerrors.Errorf("gateway: checking 'to' tipset: %w", err) - } - return gw.target.ChainGetPath(ctx, from, to) -} - -func (gw *Node) ChainGetGenesis(ctx context.Context) (*types.TipSet, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainGetGenesis(ctx) -} - -func (gw *Node) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) { - if err := gw.limit(ctx, chainRateLimitTokens); err != nil { - return nil, err - } - return gw.target.ChainReadObj(ctx, c) -} - -func (gw *Node) ChainPutObj(context.Context, blocks.Block) error { - return xerrors.New("not supported") -} - -func (gw *Node) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.GasEstimateMessageGas(ctx, msg, spec, tsk) -} - -func (gw *Node) MpoolPush(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return cid.Cid{}, err - } - // TODO: additional anti-spam checks - return gw.target.MpoolPushUntrusted(ctx, sm) -} - -func (gw *Node) MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error) { - if err := gw.limit(ctx, walletRateLimitTokens); err != nil { - return types.BigInt{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return types.NewInt(0), err - } - return gw.target.MsigGetAvailableBalance(ctx, addr, tsk) -} - -func (gw *Node) MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error) { - if err := gw.limit(ctx, walletRateLimitTokens); err != nil { - return types.BigInt{}, err - } - if err := gw.checkTipsetKey(ctx, start); err != nil { - return types.NewInt(0), err - } - if err := gw.checkTipsetKey(ctx, end); err != nil { - return types.NewInt(0), err - } - return gw.target.MsigGetVested(ctx, addr, start, end) -} - -func (gw *Node) MsigGetVestingSchedule(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MsigVesting, error) { - if err := gw.limit(ctx, walletRateLimitTokens); err != nil { - return api.MsigVesting{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return api.MsigVesting{}, err - } - return gw.target.MsigGetVestingSchedule(ctx, addr, tsk) -} - -func (gw *Node) MsigGetPending(ctx context.Context, addr address.Address, tsk types.TipSetKey) ([]*api.MsigTransaction, error) { - if err := gw.limit(ctx, walletRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.MsigGetPending(ctx, addr, tsk) -} - -func (gw *Node) StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return address.Address{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return address.Undef, err - } - return gw.target.StateAccountKey(ctx, addr, tsk) -} - -func (gw *Node) StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return api.DealCollateralBounds{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return api.DealCollateralBounds{}, err - } - return gw.target.StateDealProviderCollateralBounds(ctx, size, verified, tsk) -} - -func (gw *Node) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateGetActor(ctx, actor, tsk) -} - -func (gw *Node) StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateListMiners(ctx, tsk) -} - -func (gw *Node) StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return address.Address{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return address.Undef, err - } - return gw.target.StateLookupID(ctx, addr, tsk) -} - -func (gw *Node) StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return api.MarketBalance{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return api.MarketBalance{}, err - } - return gw.target.StateMarketBalance(ctx, addr, tsk) -} - -func (gw *Node) StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateMarketStorageDeal(ctx, dealId, tsk) -} - -func (gw *Node) StateNetworkVersion(ctx context.Context, tsk types.TipSetKey) (network.Version, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return network.VersionMax, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return network.VersionMax, err - } - return gw.target.StateNetworkVersion(ctx, tsk) -} - -func (gw *Node) StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if limit == api.LookbackNoLimit { - limit = gw.stateWaitLookbackLimit - } - if gw.stateWaitLookbackLimit != api.LookbackNoLimit && limit > gw.stateWaitLookbackLimit { - limit = gw.stateWaitLookbackLimit - } - if err := gw.checkTipsetKey(ctx, from); err != nil { - return nil, err - } - return gw.target.StateSearchMsg(ctx, from, msg, limit, allowReplaced) -} - -func (gw *Node) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if limit == api.LookbackNoLimit { - limit = gw.stateWaitLookbackLimit - } - if gw.stateWaitLookbackLimit != api.LookbackNoLimit && limit > gw.stateWaitLookbackLimit { - limit = gw.stateWaitLookbackLimit - } - return gw.target.StateWaitMsg(ctx, msg, confidence, limit, allowReplaced) -} - -func (gw *Node) StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateReadState(ctx, actor, tsk) -} - -func (gw *Node) StateMinerPower(ctx context.Context, m address.Address, tsk types.TipSetKey) (*api.MinerPower, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateMinerPower(ctx, m, tsk) -} - -func (gw *Node) StateMinerFaults(ctx context.Context, m address.Address, tsk types.TipSetKey) (bitfield.BitField, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return bitfield.BitField{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return bitfield.BitField{}, err - } - return gw.target.StateMinerFaults(ctx, m, tsk) -} - -func (gw *Node) StateMinerRecoveries(ctx context.Context, m address.Address, tsk types.TipSetKey) (bitfield.BitField, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return bitfield.BitField{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return bitfield.BitField{}, err - } - return gw.target.StateMinerRecoveries(ctx, m, tsk) -} - -func (gw *Node) StateMinerInfo(ctx context.Context, m address.Address, tsk types.TipSetKey) (api.MinerInfo, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return api.MinerInfo{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return api.MinerInfo{}, err - } - return gw.target.StateMinerInfo(ctx, m, tsk) -} - -func (gw *Node) StateMinerDeadlines(ctx context.Context, m address.Address, tsk types.TipSetKey) ([]api.Deadline, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateMinerDeadlines(ctx, m, tsk) -} - -func (gw *Node) StateMinerAvailableBalance(ctx context.Context, m address.Address, tsk types.TipSetKey) (types.BigInt, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return types.BigInt{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return types.BigInt{}, err - } - return gw.target.StateMinerAvailableBalance(ctx, m, tsk) -} - -func (gw *Node) StateMinerProvingDeadline(ctx context.Context, m address.Address, tsk types.TipSetKey) (*dline.Info, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateMinerProvingDeadline(ctx, m, tsk) -} - -func (gw *Node) StateCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (abi.TokenAmount, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return abi.TokenAmount{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return abi.TokenAmount{}, err - } - return gw.target.StateCirculatingSupply(ctx, tsk) -} - -func (gw *Node) StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateSectorGetInfo(ctx, maddr, n, tsk) -} - -func (gw *Node) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return nil, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return nil, err - } - return gw.target.StateVerifiedClientStatus(ctx, addr, tsk) -} - -func (gw *Node) StateVMCirculatingSupplyInternal(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return api.CirculatingSupply{}, err - } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { - return api.CirculatingSupply{}, err - } - return gw.target.StateVMCirculatingSupplyInternal(ctx, tsk) -} - -func (gw *Node) WalletVerify(ctx context.Context, k address.Address, msg []byte, sig *crypto.Signature) (bool, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return false, err - } - return sigs.Verify(sig, k, msg) == nil, nil -} - -func (gw *Node) WalletBalance(ctx context.Context, k address.Address) (types.BigInt, error) { - if err := gw.limit(ctx, stateRateLimitTokens); err != nil { - return types.BigInt{}, err - } - return gw.target.WalletBalance(ctx, k) -} diff --git a/gateway/proxy_eth.go b/gateway/proxy_eth.go new file mode 100644 index 000000000..fcad213d9 --- /dev/null +++ b/gateway/proxy_eth.go @@ -0,0 +1,481 @@ +package gateway + +import ( + "bytes" + "context" + "fmt" + "sync" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +func (gw *Node) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) { + // gateway provides public API, so it can't hold user accounts + return []ethtypes.EthAddress{}, nil +} + +func (gw *Node) EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return 0, err + } + + return gw.target.EthBlockNumber(ctx) +} + +func (gw *Node) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return 0, err + } + + head, err := gw.target.ChainHead(ctx) + if err != nil { + return 0, err + } + if err := gw.checkTipsetHeight(head, abi.ChainEpoch(blkNum)); err != nil { + return 0, err + } + + return gw.target.EthGetBlockTransactionCountByNumber(ctx, blkNum) +} + +func (gw *Node) tskByEthHash(ctx context.Context, blkHash ethtypes.EthHash) (types.TipSetKey, error) { + tskCid := blkHash.ToCid() + tskBlk, err := gw.target.ChainReadObj(ctx, tskCid) + if err != nil { + return types.EmptyTSK, err + } + tsk := new(types.TipSetKey) + if err := tsk.UnmarshalCBOR(bytes.NewReader(tskBlk)); err != nil { + return types.EmptyTSK, xerrors.Errorf("cannot unmarshal block into tipset key: %w", err) + } + + return *tsk, nil +} + +func (gw *Node) checkBlkHash(ctx context.Context, blkHash ethtypes.EthHash) error { + tsk, err := gw.tskByEthHash(ctx, blkHash) + if err != nil { + return err + } + + return gw.checkTipsetKey(ctx, tsk) +} + +func (gw *Node) checkBlkParam(ctx context.Context, blkParam string) error { + if blkParam == "earliest" { + // also not supported in node impl + return fmt.Errorf("block param \"earliest\" is not supported") + } + + switch blkParam { + case "pending", "latest": + // those will be recent enough, so we don't need to check + return nil + default: + var num ethtypes.EthUint64 + err := num.UnmarshalJSON([]byte(`"` + blkParam + `"`)) + if err != nil { + return fmt.Errorf("cannot parse block number: %v", err) + } + head, err := gw.target.ChainHead(ctx) + if err != nil { + return err + } + if err := gw.checkTipsetHeight(head, abi.ChainEpoch(num)); err != nil { + return err + } + } + + return nil +} + +func (gw *Node) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return 0, err + } + + if err := gw.checkBlkHash(ctx, blkHash); err != nil { + return 0, err + } + + return gw.target.EthGetBlockTransactionCountByHash(ctx, blkHash) +} + +func (gw *Node) EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthBlock{}, err + } + + if err := gw.checkBlkHash(ctx, blkHash); err != nil { + return ethtypes.EthBlock{}, err + } + + return gw.target.EthGetBlockByHash(ctx, blkHash, fullTxInfo) +} + +func (gw *Node) EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthBlock{}, err + } + + if err := gw.checkBlkParam(ctx, blkNum); err != nil { + return ethtypes.EthBlock{}, err + } + + return gw.target.EthGetBlockByNumber(ctx, blkNum, fullTxInfo) +} + +func (gw *Node) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + return gw.target.EthGetTransactionByHash(ctx, txHash) +} + +func (gw *Node) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return 0, err + } + + if err := gw.checkBlkParam(ctx, blkOpt); err != nil { + return 0, err + } + + return gw.target.EthGetTransactionCount(ctx, sender, blkOpt) +} + +func (gw *Node) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + return gw.target.EthGetTransactionReceipt(ctx, txHash) +} + +func (gw *Node) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthTx{}, err + } + + if err := gw.checkBlkHash(ctx, blkHash); err != nil { + return ethtypes.EthTx{}, err + } + + return gw.target.EthGetTransactionByBlockHashAndIndex(ctx, blkHash, txIndex) +} + +func (gw *Node) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthTx{}, err + } + + head, err := gw.target.ChainHead(ctx) + if err != nil { + return ethtypes.EthTx{}, err + } + if err := gw.checkTipsetHeight(head, abi.ChainEpoch(blkNum)); err != nil { + return ethtypes.EthTx{}, err + } + + return gw.target.EthGetTransactionByBlockNumberAndIndex(ctx, blkNum, txIndex) +} + +func (gw *Node) EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + if err := gw.checkBlkParam(ctx, blkOpt); err != nil { + return nil, err + } + + return gw.target.EthGetCode(ctx, address, blkOpt) +} + +func (gw *Node) EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + if err := gw.checkBlkParam(ctx, blkParam); err != nil { + return nil, err + } + + return gw.target.EthGetStorageAt(ctx, address, position, blkParam) +} + +func (gw *Node) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthBigInt(big.Zero()), err + } + + if err := gw.checkBlkParam(ctx, blkParam); err != nil { + return ethtypes.EthBigInt(big.Zero()), err + } + + return gw.target.EthGetBalance(ctx, address, blkParam) +} + +func (gw *Node) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return 0, err + } + + return gw.target.EthChainId(ctx) +} + +func (gw *Node) NetVersion(ctx context.Context) (string, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return "", err + } + + return gw.target.NetVersion(ctx) +} + +func (gw *Node) NetListening(ctx context.Context) (bool, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return false, err + } + + return gw.target.NetListening(ctx) +} + +func (gw *Node) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return 0, err + } + + return gw.target.EthProtocolVersion(ctx) +} + +func (gw *Node) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthBigInt(big.Zero()), err + } + + return gw.target.EthGasPrice(ctx) +} + +var EthFeeHistoryMaxBlockCount = 128 // this seems to be expensive; todo: figure out what is a good number that works with everything + +func (gw *Node) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthFeeHistory{}, err + } + + if err := gw.checkBlkParam(ctx, newestBlk); err != nil { + return ethtypes.EthFeeHistory{}, err + } + + if blkCount > ethtypes.EthUint64(EthFeeHistoryMaxBlockCount) { + return ethtypes.EthFeeHistory{}, fmt.Errorf("block count too high") + } + + return gw.target.EthFeeHistory(ctx, blkCount, newestBlk, rewardPercentiles) +} + +func (gw *Node) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthBigInt(big.Zero()), err + } + + return gw.target.EthMaxPriorityFeePerGas(ctx) +} + +func (gw *Node) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return 0, err + } + + // todo limit gas? to what? + return gw.target.EthEstimateGas(ctx, tx) +} + +func (gw *Node) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + if err := gw.checkBlkParam(ctx, blkParam); err != nil { + return nil, err + } + + // todo limit gas? to what? + return gw.target.EthCall(ctx, tx, blkParam) +} + +func (gw *Node) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthHash{}, err + } + + return gw.target.EthSendRawTransaction(ctx, rawTx) +} + +func (gw *Node) EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + if filter.FromBlock != nil { + if err := gw.checkBlkParam(ctx, *filter.FromBlock); err != nil { + return nil, err + } + } + if filter.ToBlock != nil { + if err := gw.checkBlkParam(ctx, *filter.ToBlock); err != nil { + return nil, err + } + } + if filter.BlockHash != nil { + if err := gw.checkBlkHash(ctx, *filter.BlockHash); err != nil { + return nil, err + } + } + + return gw.target.EthGetLogs(ctx, filter) +} + +/* FILTERS: Those are stateful.. figure out how to properly either bind them to users, or time out? */ + +func (gw *Node) EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + ft := filterTrackerFromContext(ctx) + ft.lk.Lock() + _, ok := ft.userFilters[id] + ft.lk.Unlock() + + if !ok { + return nil, nil + } + + return gw.target.EthGetFilterChanges(ctx, id) +} + +func (gw *Node) EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + ft := filterTrackerFromContext(ctx) + ft.lk.Lock() + _, ok := ft.userFilters[id] + ft.lk.Unlock() + + if !ok { + return nil, nil + } + + return gw.target.EthGetFilterLogs(ctx, id) +} + +func (gw *Node) EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthFilterID{}, err + } + + return addUserFilterLimited(ctx, func() (ethtypes.EthFilterID, error) { + return gw.target.EthNewFilter(ctx, filter) + }) +} + +func (gw *Node) EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthFilterID{}, err + } + + return addUserFilterLimited(ctx, func() (ethtypes.EthFilterID, error) { + return gw.target.EthNewBlockFilter(ctx) + }) +} + +func (gw *Node) EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return ethtypes.EthFilterID{}, err + } + + return addUserFilterLimited(ctx, func() (ethtypes.EthFilterID, error) { + return gw.target.EthNewPendingTransactionFilter(ctx) + }) +} + +func (gw *Node) EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return false, err + } + + // check if the filter belongs to this connection + ft := filterTrackerFromContext(ctx) + ft.lk.Lock() + defer ft.lk.Unlock() + + if _, ok := ft.userFilters[id]; !ok { + return false, nil + } + + ok, err := gw.target.EthUninstallFilter(ctx, id) + if err != nil { + return false, err + } + + delete(ft.userFilters, id) + return ok, nil +} + +func (gw *Node) EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { + return nil, xerrors.Errorf("not implemented") +} + +func (gw *Node) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) { + return false, xerrors.Errorf("not implemented") +} + +var EthMaxFiltersPerConn = 16 // todo make this configurable + +func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) { + ft := filterTrackerFromContext(ctx) + ft.lk.Lock() + defer ft.lk.Unlock() + + if len(ft.userFilters) >= EthMaxFiltersPerConn { + return ethtypes.EthFilterID{}, fmt.Errorf("too many filters") + } + + id, err := cb() + if err != nil { + return id, err + } + + ft.userFilters[id] = time.Now() + + return id, nil +} + +func filterTrackerFromContext(ctx context.Context) *filterTracker { + return ctx.Value(filterTrackerKey).(*filterTracker) +} + +type filterTracker struct { + lk sync.Mutex + + userFilters map[ethtypes.EthFilterID]time.Time +} + +// called per request (ws connection) +func newFilterTracker() *filterTracker { + return &filterTracker{ + userFilters: make(map[ethtypes.EthFilterID]time.Time), + } +} diff --git a/gateway/proxy_fil.go b/gateway/proxy_fil.go new file mode 100644 index 000000000..0e53130fb --- /dev/null +++ b/gateway/proxy_fil.go @@ -0,0 +1,482 @@ +package gateway + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/sigs" +) + +func (gw *Node) Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) { + return build.OpenRPCDiscoverJSON_Gateway(), nil +} + +func (gw *Node) Version(ctx context.Context) (api.APIVersion, error) { + if err := gw.limit(ctx, basicRateLimitTokens); err != nil { + return api.APIVersion{}, err + } + return gw.target.Version(ctx) +} + +func (gw *Node) ChainGetParentMessages(ctx context.Context, c cid.Cid) ([]api.Message, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainGetParentMessages(ctx, c) +} + +func (gw *Node) ChainGetParentReceipts(ctx context.Context, c cid.Cid) ([]*types.MessageReceipt, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainGetParentReceipts(ctx, c) +} + +func (gw *Node) ChainGetBlockMessages(ctx context.Context, c cid.Cid) (*api.BlockMessages, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainGetBlockMessages(ctx, c) +} + +func (gw *Node) ChainHasObj(ctx context.Context, c cid.Cid) (bool, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return false, err + } + return gw.target.ChainHasObj(ctx, c) +} + +func (gw *Node) ChainHead(ctx context.Context) (*types.TipSet, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + + return gw.target.ChainHead(ctx) +} + +func (gw *Node) ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainGetMessage(ctx, mc) +} + +func (gw *Node) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainGetTipSet(ctx, tsk) +} + +func (gw *Node) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { + return nil, err + } + return gw.target.ChainGetTipSetByHeight(ctx, h, tsk) +} + +func (gw *Node) ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { + return nil, err + } + return gw.target.ChainGetTipSetAfterHeight(ctx, h, tsk) +} + +func (gw *Node) checkTipSetHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) error { + var ts *types.TipSet + if tsk.IsEmpty() { + head, err := gw.target.ChainHead(ctx) + if err != nil { + return err + } + ts = head + } else { + gts, err := gw.target.ChainGetTipSet(ctx, tsk) + if err != nil { + return err + } + ts = gts + } + + // Check if the tipset key refers to gw tipset that's too far in the past + if err := gw.checkTipset(ts); err != nil { + return err + } + + // Check if the height is too far in the past + if err := gw.checkTipsetHeight(ts, h); err != nil { + return err + } + + return nil +} + +func (gw *Node) ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainGetNode(ctx, p) +} + +func (gw *Node) ChainNotify(ctx context.Context) (<-chan []*api.HeadChange, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainNotify(ctx) +} + +func (gw *Node) ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, from); err != nil { + return nil, xerrors.Errorf("gateway: checking 'from' tipset: %w", err) + } + if err := gw.checkTipsetKey(ctx, to); err != nil { + return nil, xerrors.Errorf("gateway: checking 'to' tipset: %w", err) + } + return gw.target.ChainGetPath(ctx, from, to) +} + +func (gw *Node) ChainGetGenesis(ctx context.Context) (*types.TipSet, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainGetGenesis(ctx) +} + +func (gw *Node) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) { + if err := gw.limit(ctx, chainRateLimitTokens); err != nil { + return nil, err + } + return gw.target.ChainReadObj(ctx, c) +} + +func (gw *Node) ChainPutObj(context.Context, blocks.Block) error { + return xerrors.New("not supported") +} + +func (gw *Node) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.GasEstimateMessageGas(ctx, msg, spec, tsk) +} + +func (gw *Node) MpoolPush(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return cid.Cid{}, err + } + // TODO: additional anti-spam checks + return gw.target.MpoolPushUntrusted(ctx, sm) +} + +func (gw *Node) MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error) { + if err := gw.limit(ctx, walletRateLimitTokens); err != nil { + return types.BigInt{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return types.NewInt(0), err + } + return gw.target.MsigGetAvailableBalance(ctx, addr, tsk) +} + +func (gw *Node) MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error) { + if err := gw.limit(ctx, walletRateLimitTokens); err != nil { + return types.BigInt{}, err + } + if err := gw.checkTipsetKey(ctx, start); err != nil { + return types.NewInt(0), err + } + if err := gw.checkTipsetKey(ctx, end); err != nil { + return types.NewInt(0), err + } + return gw.target.MsigGetVested(ctx, addr, start, end) +} + +func (gw *Node) MsigGetVestingSchedule(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MsigVesting, error) { + if err := gw.limit(ctx, walletRateLimitTokens); err != nil { + return api.MsigVesting{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return api.MsigVesting{}, err + } + return gw.target.MsigGetVestingSchedule(ctx, addr, tsk) +} + +func (gw *Node) MsigGetPending(ctx context.Context, addr address.Address, tsk types.TipSetKey) ([]*api.MsigTransaction, error) { + if err := gw.limit(ctx, walletRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.MsigGetPending(ctx, addr, tsk) +} + +func (gw *Node) StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return address.Address{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return address.Undef, err + } + return gw.target.StateAccountKey(ctx, addr, tsk) +} + +func (gw *Node) StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return api.DealCollateralBounds{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return api.DealCollateralBounds{}, err + } + return gw.target.StateDealProviderCollateralBounds(ctx, size, verified, tsk) +} + +func (gw *Node) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateGetActor(ctx, actor, tsk) +} + +func (gw *Node) StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateListMiners(ctx, tsk) +} + +func (gw *Node) StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return address.Address{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return address.Undef, err + } + return gw.target.StateLookupID(ctx, addr, tsk) +} + +func (gw *Node) StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return api.MarketBalance{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return api.MarketBalance{}, err + } + return gw.target.StateMarketBalance(ctx, addr, tsk) +} + +func (gw *Node) StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateMarketStorageDeal(ctx, dealId, tsk) +} + +func (gw *Node) StateNetworkVersion(ctx context.Context, tsk types.TipSetKey) (network.Version, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return network.VersionMax, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return network.VersionMax, err + } + return gw.target.StateNetworkVersion(ctx, tsk) +} + +func (gw *Node) StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if limit == api.LookbackNoLimit { + limit = gw.stateWaitLookbackLimit + } + if gw.stateWaitLookbackLimit != api.LookbackNoLimit && limit > gw.stateWaitLookbackLimit { + limit = gw.stateWaitLookbackLimit + } + if err := gw.checkTipsetKey(ctx, from); err != nil { + return nil, err + } + return gw.target.StateSearchMsg(ctx, from, msg, limit, allowReplaced) +} + +func (gw *Node) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if limit == api.LookbackNoLimit { + limit = gw.stateWaitLookbackLimit + } + if gw.stateWaitLookbackLimit != api.LookbackNoLimit && limit > gw.stateWaitLookbackLimit { + limit = gw.stateWaitLookbackLimit + } + return gw.target.StateWaitMsg(ctx, msg, confidence, limit, allowReplaced) +} + +func (gw *Node) StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateReadState(ctx, actor, tsk) +} + +func (gw *Node) StateMinerPower(ctx context.Context, m address.Address, tsk types.TipSetKey) (*api.MinerPower, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateMinerPower(ctx, m, tsk) +} + +func (gw *Node) StateMinerFaults(ctx context.Context, m address.Address, tsk types.TipSetKey) (bitfield.BitField, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return bitfield.BitField{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return bitfield.BitField{}, err + } + return gw.target.StateMinerFaults(ctx, m, tsk) +} + +func (gw *Node) StateMinerRecoveries(ctx context.Context, m address.Address, tsk types.TipSetKey) (bitfield.BitField, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return bitfield.BitField{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return bitfield.BitField{}, err + } + return gw.target.StateMinerRecoveries(ctx, m, tsk) +} + +func (gw *Node) StateMinerInfo(ctx context.Context, m address.Address, tsk types.TipSetKey) (api.MinerInfo, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return api.MinerInfo{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return api.MinerInfo{}, err + } + return gw.target.StateMinerInfo(ctx, m, tsk) +} + +func (gw *Node) StateMinerDeadlines(ctx context.Context, m address.Address, tsk types.TipSetKey) ([]api.Deadline, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateMinerDeadlines(ctx, m, tsk) +} + +func (gw *Node) StateMinerAvailableBalance(ctx context.Context, m address.Address, tsk types.TipSetKey) (types.BigInt, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return types.BigInt{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return types.BigInt{}, err + } + return gw.target.StateMinerAvailableBalance(ctx, m, tsk) +} + +func (gw *Node) StateMinerProvingDeadline(ctx context.Context, m address.Address, tsk types.TipSetKey) (*dline.Info, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateMinerProvingDeadline(ctx, m, tsk) +} + +func (gw *Node) StateCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (abi.TokenAmount, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return abi.TokenAmount{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return abi.TokenAmount{}, err + } + return gw.target.StateCirculatingSupply(ctx, tsk) +} + +func (gw *Node) StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateSectorGetInfo(ctx, maddr, n, tsk) +} + +func (gw *Node) StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return nil, err + } + return gw.target.StateVerifiedClientStatus(ctx, addr, tsk) +} + +func (gw *Node) StateVMCirculatingSupplyInternal(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return api.CirculatingSupply{}, err + } + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return api.CirculatingSupply{}, err + } + return gw.target.StateVMCirculatingSupplyInternal(ctx, tsk) +} + +func (gw *Node) WalletVerify(ctx context.Context, k address.Address, msg []byte, sig *crypto.Signature) (bool, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return false, err + } + return sigs.Verify(sig, k, msg) == nil, nil +} + +func (gw *Node) WalletBalance(ctx context.Context, k address.Address) (types.BigInt, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return types.BigInt{}, err + } + return gw.target.WalletBalance(ctx, k) +} diff --git a/gen/main.go b/gen/main.go index 38ec5935d..a3891778a 100644 --- a/gen/main.go +++ b/gen/main.go @@ -29,12 +29,14 @@ func main() { types.MsgMeta{}, types.ActorV4{}, types.ActorV5{}, - types.MessageReceipt{}, + // types.MessageReceipt{}, // Custom serde to deal with versioning. types.BlockMsg{}, types.ExpTipSet{}, types.BeaconEntry{}, types.StateRoot{}, types.StateInfo0{}, + types.Event{}, + types.EventEntry{}, ) if err != nil { fmt.Println(err) diff --git a/go.mod b/go.mod index 602b1b007..b9eb613ee 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/filecoin-project/dagstore v0.5.2 github.com/filecoin-project/filecoin-ffi v0.30.4-0.20200910194244-f640612a1a1f github.com/filecoin-project/go-address v1.1.0 + github.com/filecoin-project/go-amt-ipld/v4 v4.0.0 github.com/filecoin-project/go-bitfield v0.2.4 github.com/filecoin-project/go-cbor-util v0.0.1 github.com/filecoin-project/go-commp-utils v0.1.3 @@ -39,11 +40,11 @@ require ( github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 github.com/filecoin-project/go-fil-markets v1.26.0 - github.com/filecoin-project/go-jsonrpc v0.1.8 + github.com/filecoin-project/go-jsonrpc v0.1.9 github.com/filecoin-project/go-legs v0.4.4 github.com/filecoin-project/go-padreader v0.0.1 github.com/filecoin-project/go-paramfetch v0.0.4 - github.com/filecoin-project/go-state-types v0.10.0-alpha-5 + github.com/filecoin-project/go-state-types v0.10.0-alpha-10 github.com/filecoin-project/go-statemachine v1.0.2 github.com/filecoin-project/go-statestore v0.2.0 github.com/filecoin-project/go-storedcounter v0.1.0 @@ -123,6 +124,7 @@ require ( github.com/libp2p/go-maddr-filter v0.1.0 github.com/libp2p/go-msgio v0.2.0 github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-sqlite3 v1.14.16 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.1.0 @@ -140,7 +142,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/urfave/cli/v2 v2.16.3 github.com/whyrusleeping/bencher v0.0.0-20190829221104-bb6607aa8bba - github.com/whyrusleeping/cbor-gen v0.0.0-20220514204315-f29c37e9c44c + github.com/whyrusleeping/cbor-gen v0.0.0-20221021053955-c138aae13722 github.com/whyrusleeping/ledger-filecoin-go v0.9.1-0.20201010031517-c3dcc1bddce4 github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 github.com/xorcare/golden v0.6.1-0.20191112154924-b87f686d7542 @@ -152,6 +154,7 @@ require ( go.uber.org/fx v1.15.0 go.uber.org/multierr v1.8.0 go.uber.org/zap v1.23.0 + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 golang.org/x/sync v0.0.0-20220907140024-f12130a52804 @@ -193,7 +196,6 @@ require ( github.com/etclabscore/go-jsonschema-walk v0.0.6 // indirect github.com/filecoin-project/go-amt-ipld/v2 v2.1.0 // indirect github.com/filecoin-project/go-amt-ipld/v3 v3.1.0 // indirect - github.com/filecoin-project/go-amt-ipld/v4 v4.0.0 // indirect github.com/filecoin-project/go-commp-utils/nonffi v0.0.0-20220905160352-62059082a837 // indirect github.com/filecoin-project/go-ds-versioning v0.1.2 // indirect github.com/filecoin-project/go-hamt-ipld v0.1.5 // indirect @@ -331,7 +333,6 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/dig v1.12.0 // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect - golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 879c13433..6df3481e4 100644 --- a/go.sum +++ b/go.sum @@ -340,8 +340,8 @@ github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0/go.mod h1:7aWZdaQ1b16BVoQUYR+ github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AGIZzeifggxtKMU7zmI= github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= -github.com/filecoin-project/go-jsonrpc v0.1.8 h1:uXX/ikAk3Q4f/k8DRd9Zw+fWnfiYb5I+UI1tzlQgHog= -github.com/filecoin-project/go-jsonrpc v0.1.8/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= +github.com/filecoin-project/go-jsonrpc v0.1.9 h1:HRWLxo7HAWzI3xZGeFG4LZJoYpms+Q+8kwmMTLnyS3A= +github.com/filecoin-project/go-jsonrpc v0.1.9/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-legs v0.4.4 h1:mpMmAOOnamaz0CV9rgeKhEWA8j9kMC+f+UGCGrxKaZo= github.com/filecoin-project/go-legs v0.4.4/go.mod h1:JQ3hA6xpJdbR8euZ2rO0jkxaMxeidXf0LDnVuqPAe9s= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= @@ -356,8 +356,8 @@ github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psS github.com/filecoin-project/go-state-types v0.1.6/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.1.8/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= github.com/filecoin-project/go-state-types v0.1.10/go.mod h1:UwGVoMsULoCK+bWjEdd/xLCvLAQFBC7EDT477SKml+Q= -github.com/filecoin-project/go-state-types v0.10.0-alpha-5 h1:k5yLpgqTns8OFjPwMWfDCmSDd+BqpFhsQEQKIquM3cM= -github.com/filecoin-project/go-state-types v0.10.0-alpha-5/go.mod h1:FPgQE05BFwZxKw/vCuIaIrzfJKo4RPQQMMPGd43dAFI= +github.com/filecoin-project/go-state-types v0.10.0-alpha-10 h1:QUpSayVFUADlrtzCh7SDNlbuaNSlYPBR46Nt7WpFl9I= +github.com/filecoin-project/go-state-types v0.10.0-alpha-10/go.mod h1:FPgQE05BFwZxKw/vCuIaIrzfJKo4RPQQMMPGd43dAFI= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= github.com/filecoin-project/go-statemachine v1.0.2 h1:421SSWBk8GIoCoWYYTE/d+qCWccgmRH0uXotXRDjUbc= github.com/filecoin-project/go-statemachine v1.0.2/go.mod h1:jZdXXiHa61n4NmgWFG4w8tnqgvZVHYbJ3yW7+y8bF54= @@ -1408,6 +1408,8 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-xmlrpc v0.0.3/go.mod h1:mqc2dz7tP5x5BKlCahN/n+hs7OSZKJkS9JsHNBRlrxA= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1860,8 +1862,8 @@ github.com/whyrusleeping/cbor-gen v0.0.0-20200826160007-0b9f6c5fb163/go.mod h1:f github.com/whyrusleeping/cbor-gen v0.0.0-20210118024343-169e9d70c0c2/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20210303213153-67a261a1d291/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/cbor-gen v0.0.0-20220323183124-98fa8256a799/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= -github.com/whyrusleeping/cbor-gen v0.0.0-20220514204315-f29c37e9c44c h1:6VPKXBDRt7mDUyiHx9X8ROnPYFDf3L7OfEuKCI5dZDI= -github.com/whyrusleeping/cbor-gen v0.0.0-20220514204315-f29c37e9c44c/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= +github.com/whyrusleeping/cbor-gen v0.0.0-20221021053955-c138aae13722 h1:0HEhvpGQJ2Gd0ngPW83aduQQuF/V9v13+3zpSrR3lrA= +github.com/whyrusleeping/cbor-gen v0.0.0-20221021053955-c138aae13722/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= diff --git a/itests/api_test.go b/itests/api_test.go index 2afbc0bd0..ff303df3e 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -307,5 +307,5 @@ func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { tid, err := address.IDFromAddress(ta) require.NoError(t, err) - require.Equal(t, uint64(1001), tid) + require.Equal(t, uint64(1002), tid) // ETH0 is 1001 } diff --git a/itests/contracts/DelegatecallActor.hex b/itests/contracts/DelegatecallActor.hex new file mode 100644 index 000000000..aed647407 --- /dev/null +++ b/itests/contracts/DelegatecallActor.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5061018c806100206000396000f3fe6080604052600436106100345760003560e01c806361bc221a146100395780636466414b146100645780638ada066e14610080575b600080fd5b34801561004557600080fd5b5061004e6100ab565b60405161005b91906100dd565b60405180910390f35b61007e60048036038101906100799190610129565b6100b1565b005b34801561008c57600080fd5b506100956100bb565b6040516100a291906100dd565b60405180910390f35b60005481565b8060008190555050565b60008054905090565b6000819050919050565b6100d7816100c4565b82525050565b60006020820190506100f260008301846100ce565b92915050565b600080fd5b610106816100c4565b811461011157600080fd5b50565b600081359050610123816100fd565b92915050565b60006020828403121561013f5761013e6100f8565b5b600061014d84828501610114565b9150509291505056fea2646970667358221220cf4567855a30be48cde5cdbff495bdaa4052e2c4540678b97284af53a4e5dbd164736f6c63430008110033 \ No newline at end of file diff --git a/itests/contracts/DelegatecallActor.sol b/itests/contracts/DelegatecallActor.sol new file mode 100644 index 000000000..8671f6298 --- /dev/null +++ b/itests/contracts/DelegatecallActor.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract DelegatecallActor { + uint public counter; + + function getCounter() public view returns (uint){ + return counter; + } + function setVars(uint _counter) public payable { + counter = _counter; + } +} diff --git a/itests/contracts/DelegatecallStorage.hex b/itests/contracts/DelegatecallStorage.hex new file mode 100644 index 000000000..7ce9ef210 --- /dev/null +++ b/itests/contracts/DelegatecallStorage.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50610477806100206000396000f3fe6080604052600436106100345760003560e01c806361bc221a146100395780638ada066e14610064578063d1e0f3081461008f575b600080fd5b34801561004557600080fd5b5061004e6100bf565b60405161005b919061022c565b60405180910390f35b34801561007057600080fd5b506100796100c5565b604051610086919061022c565b60405180910390f35b6100a960048036038101906100a491906102d6565b6100ce565b6040516100b6919061022c565b60405180910390f35b60005481565b60008054905090565b6000808373ffffffffffffffffffffffffffffffffffffffff16836040516024016100f9919061022c565b6040516020818303038152906040527f6466414b000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101839190610387565b600060405180830381855af49150503d80600081146101be576040519150601f19603f3d011682016040523d82523d6000602084013e6101c3565b606091505b5050905080610207576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101fe90610421565b60405180910390fd5b60005491505092915050565b6000819050919050565b61022681610213565b82525050565b6000602082019050610241600083018461021d565b92915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102778261024c565b9050919050565b6102878161026c565b811461029257600080fd5b50565b6000813590506102a48161027e565b92915050565b6102b381610213565b81146102be57600080fd5b50565b6000813590506102d0816102aa565b92915050565b600080604083850312156102ed576102ec610247565b5b60006102fb85828601610295565b925050602061030c858286016102c1565b9150509250929050565b600081519050919050565b600081905092915050565b60005b8381101561034a57808201518184015260208101905061032f565b60008484015250505050565b600061036182610316565b61036b8185610321565b935061037b81856020860161032c565b80840191505092915050565b60006103938284610356565b915081905092915050565b600082825260208201905092915050565b7f4572726f72206d6573736167653a2044656c656761746563616c6c206661696c60008201527f6564000000000000000000000000000000000000000000000000000000000000602082015250565b600061040b60228361039e565b9150610416826103af565b604082019050919050565b6000602082019050818103600083015261043a816103fe565b905091905056fea26469706673582212203663909b8221e9b87047be99420c00339af1430c085260df209b909ed8e0f05164736f6c63430008110033 \ No newline at end of file diff --git a/itests/contracts/DelegatecallStorage.sol b/itests/contracts/DelegatecallStorage.sol new file mode 100644 index 000000000..434cd934e --- /dev/null +++ b/itests/contracts/DelegatecallStorage.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract DelegatecallStorage { + uint public counter; + + function getCounter() public view returns (uint){ + return counter; + } + function setVars(address _contract, uint _counter) public payable returns (uint){ + (bool success, ) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _counter) + ); + require(success, 'Error message: Delegatecall failed'); + return counter; + } +} diff --git a/itests/contracts/EventMatrix.hex b/itests/contracts/EventMatrix.hex new file mode 100644 index 000000000..2b3ad91ad --- /dev/null +++ b/itests/contracts/EventMatrix.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506105eb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063c755553811610071578063c755553814610198578063cbfc3b58146101c6578063cc6f8faf14610212578063cd5b6c3d14610254578063e2a614731461028c578063fb62b28b146102d8576100a9565b80630919b8be146100ae5780636199074d146100e657806366eef3461461012857806375091b1f14610132578063a63ae81a1461016a575b600080fd5b6100e4600480360360408110156100c457600080fd5b81019080803590602001909291908035906020019092919050505061031a565b005b610126600480360360608110156100fc57600080fd5b8101908080359060200190929190803590602001909291908035906020019092919050505061035d565b005b610130610391565b005b6101686004803603604081101561014857600080fd5b8101908080359060200190929190803590602001909291905050506103bf565b005b6101966004803603602081101561018057600080fd5b81019080803590602001909291905050506103fb565b005b6101c4600480360360208110156101ae57600080fd5b8101908080359060200190929190505050610435565b005b610210600480360360808110156101dc57600080fd5b8101908080359060200190929190803590602001909291908035906020019092919080359060200190929190505050610465565b005b6102526004803603606081101561022857600080fd5b810190808035906020019092919080359060200190929190803590602001909291905050506104ba565b005b61028a6004803603604081101561026a57600080fd5b8101908080359060200190929190803590602001909291905050506104f8565b005b6102d6600480360360808110156102a257600080fd5b810190808035906020019092919080359060200190929190803590602001909291908035906020019092919050505061052a565b005b610318600480360360608110156102ee57600080fd5b8101908080359060200190929190803590602001909291908035906020019092919050505061056a565b005b7f5469c6b769315f5668523937f05ca07d4cc87849432bc5f5907f1d90fa73b9f98282604051808381526020018281526020019250505060405180910390a15050565b8082847fb89dabcdb7ff41f1794c0da92f65ece6c19b6b0caeac5407b2a721efe27c080460405160405180910390a4505050565b7fc3f6f1c76bd4e74ee5782052b0b4f8bd5c50b86c3c5a2f52638e03066e50a91b60405160405180910390a1565b817f6709824ebe5f6e620ca3f4b02a3428e8ce2dc97c550816eaeeb3a342b214bd85826040518082815260200191505060405180910390a25050565b7fc804e53d6048af1b3e6a352e246d5f3864fea9d635ace499e023a58c383b3a88816040518082815260200191505060405180910390a150565b807f44a227a31429ab5eb00daf6611c6422f10571619f2267e0e149e9ebe6d2a5d0560405160405180910390a250565b7f28d45631a87b2a52a9625f8520fa37ff8c4d926cdf17042e241985da5cb7b850848484846040518085815260200184815260200183815260200182815260200194505050505060405180910390a150505050565b81837fcd5fe5fbc1d27b90036997224cea7aa565e3779622867265081f636b3a5ccb08836040518082815260200191505060405180910390a3505050565b80827f232f09cef3babc26e58d1cc1346c0a8bc626ffe600c9605b5d747783eda484a760405160405180910390a35050565b8183857f812e73dbcf7e267f27ecb1383bfc902a6650b41b6e7d03ac265108c369673d95846040518082815260200191505060405180910390a450505050565b7fd4d143faaf60340ad98e1f2c96fc26f5695834c21b5200edad339ee7e9a372cc83838360405180848152602001838152602001828152602001935050505060405180910390a150505056fea265627a7a72315820954561fde80ab925299e0a9f3356b01f64fb1976dd335ac2ebd9367441e29f0564736f6c63430005110032 diff --git a/itests/contracts/EventMatrix.sol b/itests/contracts/EventMatrix.sol new file mode 100644 index 000000000..bd008e27b --- /dev/null +++ b/itests/contracts/EventMatrix.sol @@ -0,0 +1,51 @@ +pragma solidity ^0.5.0; + +contract EventMatrix { + event EventZeroData(); + event EventOneData(uint a); + event EventTwoData(uint a, uint b); + event EventThreeData(uint a, uint b, uint c); + event EventFourData(uint a, uint b, uint c, uint d); + + event EventOneIndexed(uint indexed a); + event EventTwoIndexed(uint indexed a, uint indexed b); + event EventThreeIndexed(uint indexed a, uint indexed b, uint indexed c); + + event EventOneIndexedWithData(uint indexed a, uint b); + event EventTwoIndexedWithData(uint indexed a, uint indexed b, uint c); + event EventThreeIndexedWithData(uint indexed a, uint indexed b, uint indexed c, uint d); + + function logEventZeroData() public { + emit EventZeroData(); + } + function logEventOneData(uint a) public { + emit EventOneData(a); + } + function logEventTwoData(uint a, uint b) public { + emit EventTwoData(a,b); + } + function logEventThreeData(uint a, uint b, uint c) public { + emit EventThreeData(a,b,c); + } + function logEventFourData(uint a, uint b, uint c, uint d) public { + emit EventFourData(a,b,c,d); + } + function logEventOneIndexed(uint a) public { + emit EventOneIndexed(a); + } + function logEventTwoIndexed(uint a, uint b) public { + emit EventTwoIndexed(a,b); + } + function logEventThreeIndexed(uint a, uint b, uint c) public { + emit EventThreeIndexed(a,b,c); + } + function logEventOneIndexedWithData(uint a, uint b) public { + emit EventOneIndexedWithData(a,b); + } + function logEventTwoIndexedWithData(uint a, uint b, uint c) public { + emit EventTwoIndexedWithData(a,b,c); + } + function logEventThreeIndexedWithData(uint a, uint b, uint c, uint d) public { + emit EventThreeIndexedWithData(a,b,c,d); + } +} diff --git a/itests/contracts/SimpleCoin.hex b/itests/contracts/SimpleCoin.hex new file mode 100644 index 000000000..8e11ab5b9 --- /dev/null +++ b/itests/contracts/SimpleCoin.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061051c806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061047e565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104b2565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b925082820261046081610337565b915082820484148315176104775761047661040d565b5b5092915050565b600061048982610337565b915061049483610337565b92508282039050818111156104ac576104ab61040d565b5b92915050565b60006104bd82610337565b91506104c883610337565b92508282019050808211156104e0576104df61040d565b5b9291505056fea2646970667358221220050cdcfbe2911d041d2e6c355dbb6a0ca8ca70b500865bf33d9a2e5f4ac5a4e164736f6c63430008110033 \ No newline at end of file diff --git a/itests/contracts/SimpleCoin.sol b/itests/contracts/SimpleCoin.sol new file mode 100644 index 000000000..5318b0cb8 --- /dev/null +++ b/itests/contracts/SimpleCoin.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.2; + +contract SimpleCoin { + mapping(address => uint256) balances; + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + constructor() { + balances[tx.origin] = 10000; + } + + function sendCoin(address receiver, uint256 amount) + public + returns (bool sufficient) + { + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount; + balances[receiver] += amount; + emit Transfer(msg.sender, receiver, amount); + return true; + } + + function getBalanceInEth(address addr) public view returns (uint256) { + return getBalance(addr) * 2; + } + + function getBalance(address addr) public view returns (uint256) { + return balances[addr]; + } +} diff --git a/itests/contracts/compile.sh b/itests/contracts/compile.sh new file mode 100755 index 000000000..1163ad9a6 --- /dev/null +++ b/itests/contracts/compile.sh @@ -0,0 +1,6 @@ +#use the solc compiler https://docs.soliditylang.org/en/v0.8.17/installing-solidity.html +# to compile all of the .sol files to their corresponding evm binary files stored as .hex +# solc outputs to stdout a format that we just want to grab the last line of and then remove the trailing newline on that line + +find -type f -name \*.sol -print0 | + xargs -0 -I{} bash -c 'solc --bin {} |tail -n1 | tr -d "\n" > $(echo {} | sed -e s/.sol$/.hex/)' diff --git a/itests/contracts/events.asm b/itests/contracts/events.asm new file mode 100644 index 000000000..ab96fcedd --- /dev/null +++ b/itests/contracts/events.asm @@ -0,0 +1,47 @@ +# https://github.com/filecoin-project/builtin-actors/blob/b1ba61053de2ceaddd5116e87823d20a8f5e38d7/actors/evm/tests/events.rs +# method dispatch: +# - 0x00000000 -> log_zero_data +# - 0x00000001 -> log_zero_nodata +# - 0x00000002 -> log_four_data +%dispatch_begin() +%dispatch(0x00, log_zero_data) +%dispatch(0x01, log_zero_nodata) +%dispatch(0x02, log_four_data) +%dispatch_end() +#### log a zero topic event with data +log_zero_data: +jumpdest +push8 0x1122334455667788 +push1 0x00 +mstore +push1 0x08 +push1 0x18 ## index 24 into memory as mstore writes a full word +log0 +push1 0x00 +push1 0x00 +return +#### log a zero topic event with no data +log_zero_nodata: +jumpdest +push1 0x00 +push1 0x00 +log0 +push1 0x00 +push1 0x00 +return +#### log a four topic event with data +log_four_data: +jumpdest +push8 0x1122334455667788 +push1 0x00 +mstore +push4 0x4444 +push3 0x3333 +push2 0x2222 +push2 0x1111 +push1 0x08 +push1 0x18 ## index 24 into memory as mstore writes a full word +log4 +push1 0x00 +push1 0x00 +return diff --git a/itests/contracts/events.bin b/itests/contracts/events.bin new file mode 100644 index 000000000..31abec334 --- /dev/null +++ b/itests/contracts/events.bin @@ -0,0 +1 @@ +63000000678063000000116000396000f360003560e01c80600014601f578060011460365780600214604157600080fd5b67112233445566778860005260086018a060006000f35b60006000a060006000f35b67112233445566778860005263000044446200333361222261111160086018a460006000f3 \ No newline at end of file diff --git a/itests/deals_padding_test.go b/itests/deals_padding_test.go index 3535a1227..aaca45360 100644 --- a/itests/deals_padding_test.go +++ b/itests/deals_padding_test.go @@ -33,7 +33,7 @@ func TestDealPadding(t *testing.T) { dh := kit.NewDealHarness(t, client, miner, miner) ctx := context.Background() - client.WaitTillChain(ctx, kit.BlockMinedBy(miner.ActorAddr)) + client.WaitTillChain(ctx, kit.BlocksMinedByAll(miner.ActorAddr)) // Create a random file, would originally be a 256-byte sector res, inFile := client.CreateImportFile(ctx, 1, 200) diff --git a/itests/deals_power_test.go b/itests/deals_power_test.go index 1ca28c6fd..57483cde7 100644 --- a/itests/deals_power_test.go +++ b/itests/deals_power_test.go @@ -52,7 +52,7 @@ func TestFirstDealEnablesMining(t *testing.T) { providerMined := make(chan struct{}) go func() { - _ = client.WaitTillChain(ctx, kit.BlockMinedBy(provider.ActorAddr)) + _ = client.WaitTillChain(ctx, kit.BlocksMinedByAll(provider.ActorAddr)) close(providerMined) }() diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go new file mode 100644 index 000000000..692e4646f --- /dev/null +++ b/itests/eth_account_abstraction_test.go @@ -0,0 +1,315 @@ +package itests + +import ( + "context" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/chain/wallet/key" + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestEthAccountAbstraction goes over the account abstraction 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) { + kit.QuietMiningLogs() + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + secpKey, err := key.GenerateKey(types.KTDelegated) + require.NoError(t, err) + + placeholderAddress, err := client.WalletImport(ctx, &secpKey.KeyInfo) + require.NoError(t, err) + + // create an placeholder actor at the target address + msgCreatePlaceholder := &types.Message{ + From: client.DefaultKey.Address, + To: placeholderAddress, + Value: abi.TokenAmount(types.MustParseFIL("100")), + } + smCreatePlaceholder, err := client.MpoolPushMessage(ctx, msgCreatePlaceholder, nil) + require.NoError(t, err) + mLookup, err := client.StateWaitMsg(ctx, smCreatePlaceholder.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + require.True(t, mLookup.Receipt.ExitCode.IsSuccess()) + + // confirm the placeholder is an placeholder + placeholderActor, err := client.StateGetActor(ctx, placeholderAddress, types.EmptyTSK) + require.NoError(t, err) + + require.Equal(t, uint64(0), placeholderActor.Nonce) + require.True(t, builtin.IsPlaceholderActor(placeholderActor.Code)) + require.Equal(t, msgCreatePlaceholder.Value, placeholderActor.Balance) + + // send a message from the placeholder address + msgFromPlaceholder := &types.Message{ + From: placeholderAddress, + // self-send because an "eth tx payload" can't be to a filecoin address? + To: placeholderAddress, + } + msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) + require.NoError(t, err) + + txArgs, err := ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + require.NoError(t, err) + + digest, err := txArgs.ToRlpUnsignedMsg() + require.NoError(t, err) + + siggy, err := client.WalletSign(ctx, placeholderAddress, digest) + require.NoError(t, err) + + smFromPlaceholderCid, err := client.MpoolPush(ctx, &types.SignedMessage{Message: *msgFromPlaceholder, Signature: *siggy}) + require.NoError(t, err) + + mLookup, err = client.StateWaitMsg(ctx, smFromPlaceholderCid, 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.True(t, mLookup.Receipt.ExitCode.IsSuccess()) + + // confirm ugly Placeholder duckling has turned into a beautiful EthAccount swan + + eoaActor, err := client.StateGetActor(ctx, placeholderAddress, types.EmptyTSK) + require.NoError(t, err) + + require.False(t, builtin.IsPlaceholderActor(eoaActor.Code)) + require.True(t, builtin.IsEthAccountActor(eoaActor.Code)) + require.Equal(t, uint64(1), eoaActor.Nonce) + + // Send another message, it should succeed without any code CID changes + + msgFromPlaceholder = &types.Message{ + From: placeholderAddress, + To: placeholderAddress, + Nonce: 1, + } + + msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) + require.NoError(t, err) + + txArgs, err = ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + require.NoError(t, err) + + digest, err = txArgs.ToRlpUnsignedMsg() + require.NoError(t, err) + + siggy, err = client.WalletSign(ctx, placeholderAddress, digest) + require.NoError(t, err) + + smFromPlaceholderCid, err = client.MpoolPush(ctx, &types.SignedMessage{Message: *msgFromPlaceholder, Signature: *siggy}) + require.NoError(t, err) + + mLookup, err = client.StateWaitMsg(ctx, smFromPlaceholderCid, 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.True(t, mLookup.Receipt.ExitCode.IsSuccess()) + + // confirm no changes in code CID + + eoaActor, err = client.StateGetActor(ctx, placeholderAddress, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, uint64(2), eoaActor.Nonce) + + require.False(t, builtin.IsPlaceholderActor(eoaActor.Code)) + require.True(t, builtin.IsEthAccountActor(eoaActor.Code)) +} + +// Tests that an placeholder turns into an EthAccout even if the message fails +func TestEthAccountAbstractionFailure(t *testing.T) { + kit.QuietMiningLogs() + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + secpKey, err := key.GenerateKey(types.KTDelegated) + require.NoError(t, err) + + placeholderAddress, err := client.WalletImport(ctx, &secpKey.KeyInfo) + require.NoError(t, err) + + // create a placeholder actor at the target address + msgCreatePlaceholder := &types.Message{ + From: client.DefaultKey.Address, + To: placeholderAddress, + Value: abi.TokenAmount(types.MustParseFIL("100")), + } + smCreatePlaceholder, err := client.MpoolPushMessage(ctx, msgCreatePlaceholder, nil) + require.NoError(t, err) + mLookup, err := client.StateWaitMsg(ctx, smCreatePlaceholder.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.True(t, mLookup.Receipt.ExitCode.IsSuccess()) + + // confirm the placeholder is an placeholder + placeholderActor, err := client.StateGetActor(ctx, placeholderAddress, types.EmptyTSK) + require.NoError(t, err) + + require.Equal(t, uint64(0), placeholderActor.Nonce) + require.True(t, builtin.IsPlaceholderActor(placeholderActor.Code)) + require.Equal(t, msgCreatePlaceholder.Value, placeholderActor.Balance) + + // send a message from the placeholder address + msgFromPlaceholder := &types.Message{ + From: placeholderAddress, + To: placeholderAddress, + Value: abi.TokenAmount(types.MustParseFIL("20")), + } + msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) + require.NoError(t, err) + + msgFromPlaceholder.Value = abi.TokenAmount(types.MustParseFIL("1000")) + txArgs, err := ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + require.NoError(t, err) + + digest, err := txArgs.ToRlpUnsignedMsg() + require.NoError(t, err) + + siggy, err := client.WalletSign(ctx, placeholderAddress, digest) + require.NoError(t, err) + + smFromPlaceholderCid, err := client.MpoolPush(ctx, &types.SignedMessage{Message: *msgFromPlaceholder, Signature: *siggy}) + require.NoError(t, err) + + mLookup, err = client.StateWaitMsg(ctx, smFromPlaceholderCid, 3, api.LookbackNoLimit, true) + require.NoError(t, err) + // message should have failed because we didn't have enough $$$ + require.Equal(t, exitcode.SysErrInsufficientFunds, mLookup.Receipt.ExitCode) + + // BUT, ugly Placeholder duckling should have turned into a beautiful EthAccount swan anyway + + eoaActor, err := client.StateGetActor(ctx, placeholderAddress, types.EmptyTSK) + require.NoError(t, err) + + require.False(t, builtin.IsPlaceholderActor(eoaActor.Code)) + require.True(t, builtin.IsEthAccountActor(eoaActor.Code)) + require.Equal(t, uint64(1), eoaActor.Nonce) + + // 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), + } + + msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) + require.NoError(t, err) + + txArgs, err = ethtypes.EthTxArgsFromMessage(msgFromPlaceholder) + require.NoError(t, err) + + digest, err = txArgs.ToRlpUnsignedMsg() + require.NoError(t, err) + + siggy, err = client.WalletSign(ctx, placeholderAddress, digest) + require.NoError(t, err) + + smFromPlaceholderCid, err = client.MpoolPush(ctx, &types.SignedMessage{Message: *msgFromPlaceholder, Signature: *siggy}) + require.NoError(t, err) + + mLookup, err = client.StateWaitMsg(ctx, smFromPlaceholderCid, 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.True(t, mLookup.Receipt.ExitCode.IsSuccess()) + + // confirm no changes in code CID + + eoaActor, err = client.StateGetActor(ctx, placeholderAddress, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, uint64(2), eoaActor.Nonce) + + require.False(t, builtin.IsPlaceholderActor(eoaActor.Code)) + require.True(t, builtin.IsEthAccountActor(eoaActor.Code)) +} + +// Tests that f4 addresess that aren't placeholders/ethaccounts can't be top-level senders +func TestEthAccountAbstractionFailsFromEvmActor(t *testing.T) { + kit.QuietMiningLogs() + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + // install a contract from the placeholder + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.Zero(), + Nonce: 0, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, key.PrivateKey) + + client.EVM().SubmitTransaction(ctx, &tx) + + smsg, err := tx.ToSignedMessage() + require.NoError(t, err) + + ml, err := client.StateWaitMsg(ctx, smsg.Cid(), 1, api.LookbackNoLimit, true) + require.NoError(t, err) + require.True(t, ml.Receipt.ExitCode.IsSuccess()) + + // Get contract address, assert it's an EVM actor + contractAddr, err := client.EVM().ComputeContractAddress(ethAddr, 0).ToFilecoinAddress() + require.NoError(t, err) + + client.AssertActorType(ctx, contractAddr, "evm") + + msgFromContract := &types.Message{ + From: contractAddr, + To: contractAddr, + } + + _, err = client.GasEstimateMessageGas(ctx, msgFromContract, nil, types.EmptyTSK) + require.Error(t, err, "expected gas estimation to fail") + require.Contains(t, err.Error(), "SysErrSenderInvalid") +} diff --git a/itests/eth_balance_test.go b/itests/eth_balance_test.go new file mode 100644 index 000000000..3176aefc8 --- /dev/null +++ b/itests/eth_balance_test.go @@ -0,0 +1,97 @@ +package itests + +import ( + "context" + "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" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestEthGetBalanceExistingF4address(t *testing.T) { + 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() + + _, ethAddr, deployer := client.EVM().NewAccount() + + fundAmount := types.FromFil(0) + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, fundAmount) + + balance, err := client.EthGetBalance(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Equal(t, balance, ethtypes.EthBigInt{Int: fundAmount.Int}) +} + +func TestEthGetBalanceNonExistentF4address(t *testing.T) { + 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() + + _, ethAddr, _ := client.EVM().NewAccount() + + balance, err := client.EthGetBalance(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Equal(t, balance, ethtypes.EthBigIntZero) +} + +func TestEthGetBalanceExistentIDMaskedAddr(t *testing.T) { + 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() + + faddr, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + fid, err := client.StateLookupID(ctx, faddr, types.EmptyTSK) + require.NoError(t, err) + + ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(fid) + require.NoError(t, err) + + balance, err := client.WalletBalance(ctx, fid) + require.NoError(t, err) + + ebal, err := client.EthGetBalance(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Equal(t, ebal, ethtypes.EthBigInt{Int: balance.Int}) +} + +func TestEthGetBalanceBuiltinActor(t *testing.T) { + 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() + + // Address for market actor + fid, err := address.NewFromString("f05") + require.NoError(t, err) + + kit.SendFunds(ctx, t, client, fid, abi.TokenAmount{Int: big.NewInt(10).Int}) + + ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(fid) + require.NoError(t, err) + + ebal, err := client.EthGetBalance(ctx, ethAddr, "latest") + require.NoError(t, err) + require.Equal(t, ethtypes.EthBigInt{Int: big.NewInt(10).Int}, ebal) +} diff --git a/itests/eth_block_hash_test.go b/itests/eth_block_hash_test.go new file mode 100644 index 000000000..ac6506bb2 --- /dev/null +++ b/itests/eth_block_hash_test.go @@ -0,0 +1,65 @@ +package itests + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestEthBlockHashesCorrect_MultiBlockTipset validates that blocks retrieved through +// EthGetBlockByNumber are identical to blocks retrieved through +// EthGetBlockByHash, when using the block hash returned by the former. +// +// Specifically, it checks the system behaves correctly with multiblock tipsets. +// +// Catches regressions around https://github.com/filecoin-project/lotus/issues/10061. +func TestEthBlockHashesCorrect_MultiBlockTipset(t *testing.T) { + // miner is connected to the first node, and we want to observe the chain + // from the second node. + blocktime := 100 * time.Millisecond + n1, m1, m2, ens := kit.EnsembleOneTwo(t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + n1.WaitTillChain(ctx, kit.HeightAtLeast(abi.ChainEpoch(25))) + defer cancel() + + var n2 kit.TestFullNode + ens.FullNode(&n2, kit.ThroughRPC()).Start().Connect(n2, n1) + + // find the first tipset where all miners mined a block. + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Minute) + n2.WaitTillChain(ctx, kit.BlocksMinedByAll(m1.ActorAddr, m2.ActorAddr)) + defer cancel() + + head, err := n2.ChainHead(context.Background()) + require.NoError(t, err) + + // let the chain run a little bit longer to minimise the chance of reorgs + n2.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+50)) + + head, err = n2.ChainHead(context.Background()) + require.NoError(t, err) + + for i := 1; i <= int(head.Height()); i++ { + hex := fmt.Sprintf("0x%x", i) + + ethBlockA, err := n2.EthGetBlockByNumber(ctx, hex, true) + require.NoError(t, err) + + ethBlockB, err := n2.EthGetBlockByHash(ctx, ethBlockA.Hash, true) + require.NoError(t, err) + + require.Equal(t, ethBlockA, ethBlockB) + } +} diff --git a/itests/eth_deploy_test.go b/itests/eth_deploy_test.go new file mode 100644 index 000000000..98038de7b --- /dev/null +++ b/itests/eth_deploy_test.go @@ -0,0 +1,222 @@ +package itests + +import ( + "context" + "encoding/hex" + "encoding/json" + "os" + "reflect" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + "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" + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestDeployment smoke tests the deployment of a contract via the +// Ethereum JSON-RPC endpoint, from an EEOA. +func TestDeployment(t *testing.T) { + // TODO::FVM @raulk the contract installation and invocation can be lifted into utility methods + // He who writes the second test, shall do that. + // kit.QuietMiningLogs() + + // reasonable blocktime so that the tx sits in the mpool for a bit during the test. + // although this is non-deterministic... + blockTime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + // verify balances. + bal := client.EVM().AssertAddressBalanceConsistent(ctx, deployer) + require.Equal(t, types.FromFil(10), bal) + + // verify the deployer address is an Placeholder. + client.AssertActorType(ctx, deployer, manifest.PlaceholderKey) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + // now deploy a contract from the placeholder, and validate it went well + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.Zero(), + Nonce: 0, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, key.PrivateKey) + + pendingFilter, err := client.EthNewPendingTransactionFilter(ctx) + require.NoError(t, err) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + + mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mpoolTx) + + // require that the hashes are identical + require.Equal(t, hash, mpoolTx.Hash) + + // these fields should be nil because the tx hasn't landed on chain. + // TODO::FVM @raulk We can either skip the assertion if the msg has already + // landed, or pause mining between the embryo creation and this assertion. + require.Nil(t, mpoolTx.BlockNumber) + require.Nil(t, mpoolTx.BlockHash) + require.Nil(t, mpoolTx.TransactionIndex) + + changes, err := client.EthGetFilterChanges(ctx, pendingFilter) + require.NoError(t, err) + require.Len(t, changes.Results, 1) + require.Equal(t, hash.String(), changes.Results[0]) + + var receipt *api.EthTxReceipt + for i := 0; i < 20; i++ { + // TODO::FVM @raulk The right time to exit this loop isn't after + // 20 iterations, but when StateWaitMsg returns -- let's wait on that + // event while continuing to make this assertion + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(500 * time.Millisecond) + continue + } + break + } + require.NoError(t, err) + require.NotNil(t, receipt) + // logs must be an empty array, not a nil value, to avoid tooling compatibility issues + require.Empty(t, receipt.Logs) + // a correctly formed logs bloom, albeit empty, has 256 zeroes + require.Len(t, receipt.LogsBloom, 256) + require.Equal(t, ethtypes.EthBytes(make([]byte, 256)), receipt.LogsBloom) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Verify that the chain transaction now has new fields set. + chainTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + + // require that the hashes are identical + require.Equal(t, hash, chainTx.Hash) + require.NotNil(t, chainTx.BlockNumber) + require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) + require.NotNil(t, chainTx.BlockHash) + require.NotEmpty(t, *chainTx.BlockHash) + require.NotNil(t, chainTx.TransactionIndex) + require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction + + // should return error with non-existent block hash + nonExistentHash, err := ethtypes.ParseEthHash("0x62a80aa9262a3e1d3db0706af41c8535257b6275a283174cabf9d108d8946059") + require.Nil(t, err) + _, err = client.EthGetBlockByHash(ctx, nonExistentHash, false) + require.NotNil(t, err) + + // verify block information + block1, err := client.EthGetBlockByHash(ctx, *chainTx.BlockHash, false) + require.Nil(t, err) + require.Equal(t, block1.Hash, *chainTx.BlockHash) + require.Equal(t, block1.Number, *chainTx.BlockNumber) + for _, tx := range block1.Transactions { + _, ok := tx.(string) + require.True(t, ok) + } + require.Contains(t, block1.Transactions, hash.String()) + + // make sure the block got from EthGetBlockByNumber is the same + blkNum := strconv.FormatInt(int64(*chainTx.BlockNumber), 10) + block2, err := client.EthGetBlockByNumber(ctx, blkNum, false) + require.Nil(t, err) + require.True(t, reflect.DeepEqual(block1, block2)) + + // should be able to get the block using latest as well + block3, err := client.EthGetBlockByNumber(ctx, "latest", false) + require.Nil(t, err) + require.True(t, reflect.DeepEqual(block2, block3)) + + // verify that the block contains full tx objects + block4, err := client.EthGetBlockByHash(ctx, *chainTx.BlockHash, true) + require.Nil(t, err) + require.Equal(t, block4.Hash, *chainTx.BlockHash) + require.Equal(t, block4.Number, *chainTx.BlockNumber) + + // the call went through json-rpc and the response was unmarshaled + // into map[string]interface{}, so it has to be converted into ethtypes.EthTx + var foundTx *ethtypes.EthTx + for _, obj := range block4.Transactions { + j, err := json.Marshal(obj) + require.Nil(t, err) + + var tx ethtypes.EthTx + err = json.Unmarshal(j, &tx) + require.Nil(t, err) + + if tx.Hash == chainTx.Hash { + foundTx = &tx + } + } + require.NotNil(t, foundTx) + require.True(t, reflect.DeepEqual(*foundTx, *chainTx)) + + // make sure the block got from EthGetBlockByNumber is the same + block5, err := client.EthGetBlockByNumber(ctx, blkNum, true) + require.Nil(t, err) + require.True(t, reflect.DeepEqual(block4, block5)) + + // Verify that the deployer is now an account. + client.AssertActorType(ctx, deployer, manifest.EthAccountKey) + + // Verify that the nonce was incremented. + nonce, err := client.MpoolGetNonce(ctx, deployer) + require.NoError(t, err) + require.EqualValues(t, 1, nonce) + + // Verify that the deployer is now an account. + client.AssertActorType(ctx, deployer, manifest.EthAccountKey) + + // Get contract address. + contractAddr, err := client.EVM().ComputeContractAddress(ethAddr, 0).ToFilecoinAddress() + require.NoError(t, err) + + client.AssertActorType(ctx, contractAddr, "evm") +} diff --git a/itests/eth_filter_test.go b/itests/eth_filter_test.go new file mode 100644 index 000000000..aba61f934 --- /dev/null +++ b/itests/eth_filter_test.go @@ -0,0 +1,2047 @@ +// stm: #integration +package itests + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "sort" + "strconv" + "strings" + "testing" + "time" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +// SolidityContractDef holds information about one of the test contracts +type SolidityContractDef struct { + Filename string // filename of the hex of the contract, e.g. contracts/EventMatrix.hex + Fn map[string][]byte // mapping of function names to 32-bit selector + Ev map[string][]byte // mapping of event names to 256-bit signature hashes +} + +var EventMatrixContract = SolidityContractDef{ + Filename: "contracts/EventMatrix.hex", + Fn: map[string][]byte{ + "logEventZeroData": ethFunctionHash("logEventZeroData()"), + "logEventOneData": ethFunctionHash("logEventOneData(uint256)"), + "logEventTwoData": ethFunctionHash("logEventTwoData(uint256,uint256)"), + "logEventThreeData": ethFunctionHash("logEventThreeData(uint256,uint256,uint256)"), + "logEventFourData": ethFunctionHash("logEventFourData(uint256,uint256,uint256,uint256)"), + "logEventOneIndexed": ethFunctionHash("logEventOneIndexed(uint256)"), + "logEventTwoIndexed": ethFunctionHash("logEventTwoIndexed(uint256,uint256)"), + "logEventThreeIndexed": ethFunctionHash("logEventThreeIndexed(uint256,uint256,uint256)"), + "logEventOneIndexedWithData": ethFunctionHash("logEventOneIndexedWithData(uint256,uint256)"), + "logEventTwoIndexedWithData": ethFunctionHash("logEventTwoIndexedWithData(uint256,uint256,uint256)"), + "logEventThreeIndexedWithData": ethFunctionHash("logEventThreeIndexedWithData(uint256,uint256,uint256,uint256)"), + }, + Ev: map[string][]byte{ + "EventZeroData": ethTopicHash("EventZeroData()"), + "EventOneData": ethTopicHash("EventOneData(uint256)"), + "EventTwoData": ethTopicHash("EventTwoData(uint256,uint256)"), + "EventThreeData": ethTopicHash("EventThreeData(uint256,uint256,uint256)"), + "EventFourData": ethTopicHash("EventFourData(uint256,uint256,uint256,uint256)"), + "EventOneIndexed": ethTopicHash("EventOneIndexed(uint256)"), + "EventTwoIndexed": ethTopicHash("EventTwoIndexed(uint256,uint256)"), + "EventThreeIndexed": ethTopicHash("EventThreeIndexed(uint256,uint256,uint256)"), + "EventOneIndexedWithData": ethTopicHash("EventOneIndexedWithData(uint256,uint256)"), + "EventTwoIndexedWithData": ethTopicHash("EventTwoIndexedWithData(uint256,uint256,uint256)"), + "EventThreeIndexedWithData": ethTopicHash("EventThreeIndexedWithData(uint256,uint256,uint256,uint256)"), + }, +} + +var EventsContract = SolidityContractDef{ + Filename: "contracts/events.bin", + Fn: map[string][]byte{ + "log_zero_data": {0x00, 0x00, 0x00, 0x00}, + "log_zero_nodata": {0x00, 0x00, 0x00, 0x01}, + "log_four_data": {0x00, 0x00, 0x00, 0x02}, + }, + Ev: map[string][]byte{}, +} + +func TestEthNewPendingTransactionFilter(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + kit.QuietAllLogsExcept("events", "messagepool") + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.WithEthRPC()) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTBLS) + require.NoError(t, err) + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // install filter + filterID, err := client.EthNewPendingTransactionFilter(ctx) + require.NoError(t, err) + + const iterations = 100 + + // we'll send half our balance (saving the other half for gas), + // in `iterations` increments. + toSend := big.Div(bal, big.NewInt(2)) + each := big.Div(toSend, big.NewInt(iterations)) + + waitAllCh := make(chan struct{}) + go func() { + headChangeCh, err := client.ChainNotify(ctx) + require.NoError(t, err) + <-headChangeCh // skip hccurrent + + defer func() { + close(waitAllCh) + }() + + count := 0 + for { + select { + case <-ctx.Done(): + return + case headChanges := <-headChangeCh: + for _, change := range headChanges { + if change.Type == store.HCApply { + msgs, err := client.ChainGetMessagesInTipset(ctx, change.Val.Key()) + require.NoError(t, err) + count += len(msgs) + if count == iterations { + return + } + } + } + } + } + }() + + var sms []*types.SignedMessage + for i := 0; i < iterations; i++ { + msg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: each, + } + + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + require.EqualValues(t, i, sm.Message.Nonce) + + sms = append(sms, sm) + } + + select { + case <-waitAllCh: + case <-ctx.Done(): + t.Errorf("timeout waiting to pack messages") + } + + expected := make(map[string]bool) + for _, sm := range sms { + hash, err := ethtypes.EthHashFromCid(sm.Cid()) + require.NoError(t, err) + expected[hash.String()] = false + } + + // collect filter results + res, err := client.EthGetFilterChanges(ctx, filterID) + require.NoError(t, err) + + // expect to have seen iteration number of mpool messages + require.Equal(t, iterations, len(res.Results), "expected %d tipsets to have been executed", iterations) + + require.Equal(t, len(res.Results), len(expected), "expected number of filter results to equal number of messages") + + for _, txid := range res.Results { + expected[txid.(string)] = true + } + + for _, found := range expected { + require.True(t, found) + } +} + +func TestEthNewBlockFilter(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + kit.QuietAllLogsExcept("events", "messagepool") + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.WithEthRPC()) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTBLS) + require.NoError(t, err) + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // install filter + filterID, err := client.EthNewBlockFilter(ctx) + require.NoError(t, err) + + const iterations = 30 + + // we'll send half our balance (saving the other half for gas), + // in `iterations` increments. + toSend := big.Div(bal, big.NewInt(2)) + each := big.Div(toSend, big.NewInt(iterations)) + + waitAllCh := make(chan struct{}) + tipsetChan := make(chan *types.TipSet, iterations) + go func() { + headChangeCh, err := client.ChainNotify(ctx) + require.NoError(t, err) + <-headChangeCh // skip hccurrent + + defer func() { + close(tipsetChan) + close(waitAllCh) + }() + + count := 0 + for { + select { + case <-ctx.Done(): + return + case headChanges := <-headChangeCh: + for _, change := range headChanges { + if change.Type == store.HCApply || change.Type == store.HCRevert { + count++ + tipsetChan <- change.Val + if count == iterations { + return + } + } + } + } + } + }() + + for i := 0; i < iterations; i++ { + msg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: each, + } + + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + require.EqualValues(t, i, sm.Message.Nonce) + } + + select { + case <-waitAllCh: + case <-ctx.Done(): + t.Errorf("timeout waiting to pack messages") + } + + expected := make(map[string]bool) + for ts := range tipsetChan { + c, err := ts.Key().Cid() + require.NoError(t, err) + hash, err := ethtypes.EthHashFromCid(c) + require.NoError(t, err) + expected[hash.String()] = false + } + + // collect filter results + res, err := client.EthGetFilterChanges(ctx, filterID) + require.NoError(t, err) + + // expect to have seen iteration number of tipsets + require.Equal(t, iterations, len(res.Results), "expected %d tipsets to have been executed", iterations) + + require.Equal(t, len(res.Results), len(expected), "expected number of filter results to equal number of tipsets") + + for _, blockhash := range res.Results { + expected[blockhash.(string)] = true + } + + for _, found := range expected { + require.True(t, found, "expected all tipsets to be present in filter results") + } +} + +func TestEthNewFilterCatchAll(t *testing.T) { + require := require.New(t) + + kit.QuietAllLogsExcept("events", "messagepool") + + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.WithEthRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // install contract + contractHex, err := os.ReadFile("contracts/events.bin") + require.NoError(err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(err) + + fromAddr, err := client.WalletDefaultAddress(ctx) + require.NoError(err) + + result := client.EVM().DeployContract(ctx, fromAddr, contract) + + idAddr, err := address.NewIDAddress(result.ActorID) + require.NoError(err) + t.Logf("actor ID address is %s", idAddr) + + // install filter + filterID, err := client.EthNewFilter(ctx, ðtypes.EthFilterSpec{}) + require.NoError(err) + + const iterations = 3 + ethContractAddr, received := invokeLogFourData(t, client, iterations) + + // collect filter results + res, err := client.EthGetFilterChanges(ctx, filterID) + require.NoError(err) + + // expect to have seen iteration number of events + require.Equal(iterations, len(res.Results)) + + expected := []ExpectedEthLog{ + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + } + + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, expected, received) +} + +func TestEthGetLogsAll(t *testing.T) { + require := require.New(t) + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + invocations := 1 + ethContractAddr, received := invokeLogFourData(t, client, invocations) + + // Build filter spec + spec := newEthFilterBuilder(). + FromBlockEpoch(0). + Topic1OneOf(paddedEthHash([]byte{0x11, 0x11})). + Filter() + + expected := []ExpectedEthLog{ + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + } + + // Use filter + res, err := client.EthGetLogs(ctx, spec) + require.NoError(err) + + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, expected, received) +} + +func TestEthGetLogsByTopic(t *testing.T) { + require := require.New(t) + + kit.QuietAllLogsExcept("events", "messagepool") + + blockTime := 100 * time.Millisecond + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + invocations := 1 + ethContractAddr, received := invokeLogFourData(t, client, invocations) + + // find log by known topic1 + var spec ethtypes.EthFilterSpec + err := json.Unmarshal([]byte(`{"fromBlock":"0x0","topics":["0x0000000000000000000000000000000000000000000000000000000000001111"]}`), &spec) + require.NoError(err) + + res, err := client.EthGetLogs(context.Background(), &spec) + require.NoError(err) + + expected := []ExpectedEthLog{ + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + } + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, expected, received) +} + +func TestEthSubscribeLogs(t *testing.T) { + require := require.New(t) + + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + // install contract + contractHex, err := os.ReadFile("contracts/events.bin") + require.NoError(err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(err) + + fromAddr, err := client.WalletDefaultAddress(ctx) + require.NoError(err) + + result := client.EVM().DeployContract(ctx, fromAddr, contract) + + idAddr, err := address.NewIDAddress(result.ActorID) + require.NoError(err) + t.Logf("actor ID address is %s", idAddr) + + // install filter + respCh, err := client.EthSubscribe(ctx, "logs", nil) + require.NoError(err) + + subResponses := []ethtypes.EthSubscriptionResponse{} + go func() { + for resp := range respCh { + subResponses = append(subResponses, resp) + } + }() + + const iterations = 10 + ethContractAddr, messages := invokeLogFourData(t, client, iterations) + + expected := make([]ExpectedEthLog, iterations) + for i := range expected { + expected[i] = ExpectedEthLog{ + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + } + } + + elogs, err := parseEthLogsFromSubscriptionResponses(subResponses) + require.NoError(err) + AssertEthLogs(t, elogs, expected, messages) +} + +func TestEthGetLogs(t *testing.T) { + require := require.New(t) + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + // Set up the test fixture with a standard list of invocations + contract1, contract2, messages := invokeEventMatrix(ctx, t, client) + + testCases := []struct { + name string + spec *ethtypes.EthFilterSpec + expected []ExpectedEthLog + }{ + { + name: "find all EventZeroData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventZeroData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventZeroData"], + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventZeroData"], + }, + Data: nil, + }, + }, + }, + { + name: "find all EventOneData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneData"], + }, + Data: packUint64Values(23), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneData"], + }, + Data: packUint64Values(44), + }, + }, + }, + { + name: "find all EventTwoData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoData"], + }, + Data: packUint64Values(555, 666), + }, + }, + }, + { + name: "find all EventThreeData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventThreeData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeData"], + }, + Data: packUint64Values(1, 2, 3), + }, + }, + }, + { + name: "find all EventOneIndexed events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneIndexed"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexed"], + paddedUint64(44), + }, + Data: nil, + }, + }, + }, + { + name: "find all EventTwoIndexed events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexed"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(40), + paddedUint64(20), + }, + Data: nil, + }, + }, + }, + { + name: "find all EventThreeIndexed events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventThreeIndexed"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + }, + }, + { + name: "find all EventOneIndexedWithData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneIndexedWithData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(44), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + { + name: "find all EventTwoIndexedWithData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(14), + }, + Data: paddedUint64(19), + }, + }, + }, + { + name: "find all EventThreeIndexedWithData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventThreeIndexedWithData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: paddedUint64(12), + }, + }, + }, + + { + name: "find all events from contract2", + spec: newEthFilterBuilder().FromBlockEpoch(0).AddressOneOf(contract2).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventZeroData"], + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + + { + name: "find all events with topic2 of 44", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(44))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexed"], + paddedUint64(44), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(44), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: paddedUint64(12), + }, + }, + }, + + { + name: "find all events with topic2 of 44 from contract2", + spec: newEthFilterBuilder().FromBlockEpoch(0).AddressOneOf(contract2).Topic2OneOf(paddedEthHash(paddedUint64(44))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + }, + }, + + { + name: "find all EventOneIndexedWithData events from contract1 or contract2", + spec: newEthFilterBuilder(). + FromBlockEpoch(0). + AddressOneOf(contract1, contract2). + Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneIndexedWithData"])). + Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(44), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + + { + name: "find all events with topic2 of 46", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(46))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(14), + }, + Data: paddedUint64(19), + }, + }, + }, + { + name: "find all events with topic2 of 50", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(50))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + { + name: "find all events with topic2 of 46 or 50", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(46)), paddedEthHash(paddedUint64(50))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(14), + }, + Data: paddedUint64(19), + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + + { + name: "find all events with topic1 of EventTwoIndexedWithData and topic3 of 27", + spec: newEthFilterBuilder(). + FromBlockEpoch(0). + Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"])). + Topic3OneOf(paddedEthHash(paddedUint64(27))). + Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + }, + }, + + { + name: "find all events with topic1 of EventTwoIndexedWithData or EventOneIndexed and topic2 of 44", + spec: newEthFilterBuilder(). + FromBlockEpoch(0). + Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"]), paddedEthHash(EventMatrixContract.Ev["EventOneIndexed"])). + Topic2OneOf(paddedEthHash(paddedUint64(44))). + Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexed"], + paddedUint64(44), + }, + Data: nil, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc // appease the lint despot + t.Run(tc.name, func(t *testing.T) { + res, err := client.EthGetLogs(ctx, tc.spec) + require.NoError(err) + + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, tc.expected, messages) + }) + } +} + +func TestEthGetLogsWithBlockRanges(t *testing.T) { + require := require.New(t) + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + // Set up the test fixture with a standard list of invocations + _, _, messages := invokeEventMatrix(ctx, t, client) + + // Organize expected logs into three partitions for range testing + expectedByHeight := map[abi.ChainEpoch][]ExpectedEthLog{} + distinctHeights := map[abi.ChainEpoch]bool{} + + // Select events for partitioning + for _, m := range messages { + if bytes.Equal(m.invocation.Selector, EventMatrixContract.Fn["logEventTwoIndexedWithData"]) { + addr := getContractEthAddress(ctx, t, client, m.invocation.Target) + args := unpackUint64Values(m.invocation.Data) + require.Equal(3, len(args), "logEventTwoIndexedWithData should have 3 arguments") + + distinctHeights[m.ts.Height()] = true + expectedByHeight[m.ts.Height()] = append(expectedByHeight[m.ts.Height()], ExpectedEthLog{ + Address: addr, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(args[0]), + paddedUint64(args[1]), + }, + Data: paddedUint64(args[2]), + }) + } + } + + // Divide heights into 3 partitions, they don't have to be equal + require.True(len(distinctHeights) >= 3, "expected slice should divisible into three partitions") + heights := make([]abi.ChainEpoch, 0, len(distinctHeights)) + for h := range distinctHeights { + heights = append(heights, h) + } + sort.Slice(heights, func(i, j int) bool { + return heights[i] < heights[j] + }) + heightsPerPartition := len(heights) / 3 + + type partition struct { + start abi.ChainEpoch + end abi.ChainEpoch + expected []ExpectedEthLog + } + + var partition1, partition2, partition3 partition + + partition1.start = heights[0] + partition1.end = heights[heightsPerPartition-1] + for e := partition1.start; e <= partition1.end; e++ { + exp, ok := expectedByHeight[e] + if !ok { + continue + } + partition1.expected = append(partition1.expected, exp...) + } + t.Logf("partition1 from %d to %d with %d expected", partition1.start, partition1.end, len(partition1.expected)) + require.True(len(partition1.expected) > 0, "partition should have events") + + partition2.start = heights[heightsPerPartition] + partition2.end = heights[heightsPerPartition*2-1] + for e := partition2.start; e <= partition2.end; e++ { + exp, ok := expectedByHeight[e] + if !ok { + continue + } + partition2.expected = append(partition2.expected, exp...) + } + t.Logf("partition2 from %d to %d with %d expected", partition2.start, partition2.end, len(partition2.expected)) + require.True(len(partition2.expected) > 0, "partition should have events") + + partition3.start = heights[heightsPerPartition*2] + partition3.end = heights[len(heights)-1] + for e := partition3.start; e <= partition3.end; e++ { + exp, ok := expectedByHeight[e] + if !ok { + continue + } + partition3.expected = append(partition3.expected, exp...) + } + t.Logf("partition3 from %d to %d with %d expected", partition3.start, partition3.end, len(partition3.expected)) + require.True(len(partition3.expected) > 0, "partition should have events") + + // these are the topics we selected for partitioning earlier + topics := []ethtypes.EthHash{paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"])} + + union := func(lists ...[]ExpectedEthLog) []ExpectedEthLog { + ret := []ExpectedEthLog{} + for _, list := range lists { + ret = append(ret, list...) + } + return ret + } + + testCases := []struct { + name string + spec *ethtypes.EthFilterSpec + expected []ExpectedEthLog + }{ + { + name: "find all events from genesis", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + + { + name: "find all from start of partition1", + spec: newEthFilterBuilder().FromBlockEpoch(partition1.start).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + + { + name: "find all from start of partition2", + spec: newEthFilterBuilder().FromBlockEpoch(partition2.start).Topic1OneOf(topics...).Filter(), + expected: union(partition2.expected, partition3.expected), + }, + + { + name: "find all from start of partition3", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.start).Topic1OneOf(topics...).Filter(), + expected: union(partition3.expected), + }, + + { + name: "find none after end of partition3", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.end + 1).Topic1OneOf(topics...).Filter(), + expected: nil, + }, + + { + name: "find all events from genesis to end of partition1", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected), + }, + + { + name: "find all events from genesis to end of partition2", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition2.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected), + }, + + { + name: "find all events from genesis to end of partition3", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition3.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + + { + name: "find none from genesis to start of partition1", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition1.start - 1).Topic1OneOf(topics...).Filter(), + expected: nil, + }, + + { + name: "find all events in partition1", + spec: newEthFilterBuilder().FromBlockEpoch(partition1.start).ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected), + }, + + { + name: "find all events in partition2", + spec: newEthFilterBuilder().FromBlockEpoch(partition2.start).ToBlockEpoch(partition2.end).Topic1OneOf(topics...).Filter(), + expected: union(partition2.expected), + }, + + { + name: "find all events in partition3", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.start).ToBlockEpoch(partition3.end).Topic1OneOf(topics...).Filter(), + expected: union(partition3.expected), + }, + + { + name: "find all events from earliest to end of partition1", + spec: newEthFilterBuilder().FromBlock("earliest").ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected), + }, + + { + name: "find all events from start of partition3 to latest", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.start).ToBlock("latest").Topic1OneOf(topics...).Filter(), + expected: union(partition3.expected), + }, + + { + name: "find all events from earliest to latest", + spec: newEthFilterBuilder().FromBlock("earliest").ToBlock("latest").Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + } + + for _, tc := range testCases { + tc := tc // appease the lint despot + t.Run(tc.name, func(t *testing.T) { + res, err := client.EthGetLogs(ctx, tc.spec) + require.NoError(err) + + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, tc.expected, messages) + }) + } +} + +// ------------------------------------------------------------------------------- +// end of tests +// ------------------------------------------------------------------------------- + +type msgInTipset struct { + invocation Invocation // the solidity invocation that generated this message + msg api.Message + events []types.Event // events extracted from receipt + ts *types.TipSet + reverted bool +} + +func getContractEthAddress(ctx context.Context, t *testing.T, client *kit.TestFullNode, addr address.Address) ethtypes.EthAddress { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + actor, err := client.StateGetActor(ctx, addr, head.Key()) + require.NoError(t, err) + require.NotNil(t, actor.Address) + ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address) + require.NoError(t, err) + return ethContractAddr +} + +type Invocation struct { + Sender address.Address + Target address.Address + Selector []byte // function selector + Data []byte + MinHeight abi.ChainEpoch // minimum chain height that must be reached before invoking +} + +func invokeAndWaitUntilAllOnChain(t *testing.T, client *kit.TestFullNode, invocations []Invocation) map[ethtypes.EthHash]msgInTipset { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + msgChan := make(chan msgInTipset, len(invocations)) + + waitAllCh := make(chan struct{}) + waitForFirstHeadChange := make(chan struct{}) + go func() { + headChangeCh, err := client.ChainNotify(ctx) + require.NoError(err) + select { + case <-ctx.Done(): + return + case <-headChangeCh: // skip hccurrent + } + + close(waitForFirstHeadChange) + + defer func() { + close(msgChan) + close(waitAllCh) + }() + + count := 0 + for { + select { + case <-ctx.Done(): + return + case headChanges := <-headChangeCh: + for _, change := range headChanges { + if change.Type == store.HCApply || change.Type == store.HCRevert { + msgs, err := client.ChainGetMessagesInTipset(ctx, change.Val.Key()) + require.NoError(err) + + count += len(msgs) + for _, m := range msgs { + select { + case msgChan <- msgInTipset{msg: m, ts: change.Val, reverted: change.Type == store.HCRevert}: + default: + } + } + + if count == len(invocations) { + return + } + } + } + } + } + }() + + select { + case <-waitForFirstHeadChange: + case <-ctx.Done(): + t.Fatalf("timeout waiting for first head change") + } + + eventMap := map[cid.Cid][]types.Event{} + invocationMap := map[cid.Cid]Invocation{} + for _, inv := range invocations { + if inv.MinHeight > 0 { + for { + ts, err := client.ChainHead(ctx) + require.NoError(err) + if ts.Height() >= inv.MinHeight { + break + } + select { + case <-ctx.Done(): + t.Fatalf("context cancelled") + case <-time.After(100 * time.Millisecond): + } + } + } + ret := client.EVM().InvokeSolidity(ctx, inv.Sender, inv.Target, inv.Selector, inv.Data) + require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed") + + invocationMap[ret.Message] = inv + + require.NotNil(t, ret.Receipt.EventsRoot, "no event root on receipt") + + evs := client.EVM().LoadEvents(ctx, *ret.Receipt.EventsRoot) + eventMap[ret.Message] = evs + } + + select { + case <-waitAllCh: + case <-ctx.Done(): + t.Fatalf("timeout waiting to pack messages") + } + + received := make(map[ethtypes.EthHash]msgInTipset) + for m := range msgChan { + inv, ok := invocationMap[m.msg.Cid] + require.True(ok) + m.invocation = inv + + evs, ok := eventMap[m.msg.Cid] + require.True(ok) + m.events = evs + + eh, err := client.EthGetTransactionHashByCid(ctx, m.msg.Cid) + require.NoError(err) + received[*eh] = m + } + require.Equal(len(invocations), len(received), "all messages on chain") + + return received +} + +func invokeLogFourData(t *testing.T, client *kit.TestFullNode, iterations int) (ethtypes.EthAddress, map[ethtypes.EthHash]msgInTipset) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + fromAddr, idAddr := client.EVM().DeployContractFromFilename(ctx, EventsContract.Filename) + + invocations := make([]Invocation, iterations) + for i := range invocations { + invocations[i] = Invocation{ + Sender: fromAddr, + Target: idAddr, + Selector: EventsContract.Fn["log_four_data"], + Data: nil, + } + } + + messages := invokeAndWaitUntilAllOnChain(t, client, invocations) + + ethAddr := getContractEthAddress(ctx, t, client, idAddr) + + return ethAddr, messages +} + +func invokeEventMatrix(ctx context.Context, t *testing.T, client *kit.TestFullNode) (ethtypes.EthAddress, ethtypes.EthAddress, map[ethtypes.EthHash]msgInTipset) { + sender1, contract1 := client.EVM().DeployContractFromFilename(ctx, EventMatrixContract.Filename) + sender2, contract2 := client.EVM().DeployContractFromFilename(ctx, EventMatrixContract.Filename) + + invocations := []Invocation{ + // log EventZeroData() + // topic1: hash(EventZeroData) + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventZeroData"], + Data: nil, + }, + + // log EventOneData(23) + // topic1: hash(EventOneData) + // data: 23 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneData"], + Data: packUint64Values(23), + }, + + // log EventOneIndexed(44) + // topic1: hash(EventOneIndexed) + // topic2: 44 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneIndexed"], + Data: packUint64Values(44), + }, + + // log EventTwoIndexed(44,19) from contract2 + // topic1: hash(EventTwoIndexed) + // topic2: 44 + // topic3: 19 + { + Sender: sender2, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventTwoIndexed"], + Data: packUint64Values(44, 19), + }, + + // log EventOneData(44) + // topic1: hash(EventOneData) + // data: 44 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneData"], + Data: packUint64Values(44), + }, + + // log EventTwoData(555,666) + // topic1: hash(EventTwoData) + // data: 555,666 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoData"], + Data: packUint64Values(555, 666), + }, + + // log EventZeroData() from contract2 + // topic1: hash(EventZeroData) + { + Sender: sender2, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventZeroData"], + Data: nil, + }, + + // log EventThreeData(1,2,3) + // topic1: hash(EventTwoData) + // data: 1,2,3 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventThreeData"], + Data: packUint64Values(1, 2, 3), + }, + + // log EventThreeIndexed(44,27,19) from contract2 + // topic1: hash(EventThreeIndexed) + // topic2: 44 + // topic3: 27 + // topic4: 19 + { + Sender: sender1, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventThreeIndexed"], + Data: packUint64Values(44, 27, 19), + }, + + // log EventOneIndexedWithData(44,19) + // topic1: hash(EventOneIndexedWithData) + // topic2: 44 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneIndexedWithData"], + Data: packUint64Values(44, 19), + }, + + // log EventOneIndexedWithData(46,12) + // topic1: hash(EventOneIndexedWithData) + // topic2: 46 + // data: 12 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneIndexedWithData"], + Data: packUint64Values(46, 12), + }, + + // log EventTwoIndexedWithData(44,27,19) + // topic1: hash(EventTwoIndexedWithData) + // topic2: 44 + // topic3: 27 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"], + Data: packUint64Values(44, 27, 19), + }, + + // log EventThreeIndexedWithData(44,27,19,12) + // topic1: hash(EventThreeIndexedWithData) + // topic2: 44 + // topic3: 27 + // topic4: 19 + // data: 12 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventThreeIndexedWithData"], + Data: packUint64Values(44, 27, 19, 12), + }, + + // log EventOneIndexedWithData(50,9) + // topic1: hash(EventOneIndexedWithData) + // topic2: 50 + // data: 9 + { + Sender: sender2, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventOneIndexedWithData"], + Data: packUint64Values(50, 9), + }, + + // log EventTwoIndexedWithData(46,27,19) + // topic1: hash(EventTwoIndexedWithData) + // topic2: 46 + // topic3: 27 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"], + Data: packUint64Values(46, 27, 19), + }, + + // log EventTwoIndexedWithData(46,14,19) + // topic1: hash(EventTwoIndexedWithData) + // topic2: 46 + // topic3: 14 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"], + Data: packUint64Values(46, 14, 19), + }, + // log EventTwoIndexed(44,19) from contract1 + // topic1: hash(EventTwoIndexed) + // topic2: 44 + // topic3: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexed"], + Data: packUint64Values(40, 20), + }, + } + + messages := invokeAndWaitUntilAllOnChain(t, client, invocations) + ethAddr1 := getContractEthAddress(ctx, t, client, contract1) + ethAddr2 := getContractEthAddress(ctx, t, client, contract2) + return ethAddr1, ethAddr2, messages +} + +type ExpectedEthLog struct { + // Address is the address of the actor that produced the event log. + Address ethtypes.EthAddress `json:"address"` + + // List of topics associated with the event log. + Topics []ethtypes.EthBytes `json:"topics"` + + // Data is the value of the event log, excluding topics + Data ethtypes.EthBytes `json:"data"` +} + +func AssertEthLogs(t *testing.T, actual []*ethtypes.EthLog, expected []ExpectedEthLog, messages map[ethtypes.EthHash]msgInTipset) { + require := require.New(t) + // require.Equal(len(expected), len(actual), "number of results equal to expected") + + formatTopics := func(topics []ethtypes.EthBytes) string { + ss := make([]string, len(topics)) + for i := range topics { + ss[i] = fmt.Sprintf("%d:%x", i, topics[i]) + } + return strings.Join(ss, ",") + } + + expectedMatched := map[int]bool{} + + for _, elog := range actual { + msg, exists := messages[elog.TransactionHash] + require.True(exists, "message seen on chain") + + tsCid, err := msg.ts.Key().Cid() + require.NoError(err) + + tsCidHash, err := ethtypes.EthHashFromCid(tsCid) + require.NoError(err) + + require.Equal(tsCidHash, elog.BlockHash, "block hash matches tipset key") + + // Try and match the received log against an expected log + matched := false + LoopExpected: + for i, want := range expected { + // each expected log must match only once + if expectedMatched[i] { + continue + } + + if elog.Address != want.Address { + continue + } + + if len(elog.Topics) != len(want.Topics) { + continue + } + + for j := range elog.Topics { + if !bytes.Equal(elog.Topics[j], want.Topics[j]) { + continue LoopExpected + } + } + + if !bytes.Equal(elog.Data, want.Data) { + continue + } + + expectedMatched[i] = true + matched = true + break + } + + if !matched { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("found unexpected log at height %d:\n", msg.ts.Height())) + buf.WriteString(fmt.Sprintf(" address: %s\n", elog.Address)) + buf.WriteString(fmt.Sprintf(" topics: %s\n", formatTopics(elog.Topics))) + buf.WriteString(fmt.Sprintf(" data: %x\n", elog.Data)) + buf.WriteString("original events from receipt were:\n") + for i, ev := range msg.events { + buf.WriteString(fmt.Sprintf("event %d\n", i)) + buf.WriteString(fmt.Sprintf(" emitter: %v\n", ev.Emitter)) + for _, en := range ev.Entries { + buf.WriteString(fmt.Sprintf(" %s=%x\n", en.Key, decodeLogBytes(en.Value))) + } + } + + t.Errorf(buf.String()) + } + } + + for i := range expected { + if _, ok := expectedMatched[i]; !ok { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("did not find expected log with index %d:\n", i)) + buf.WriteString(fmt.Sprintf(" address: %s\n", expected[i].Address)) + buf.WriteString(fmt.Sprintf(" topics: %s\n", formatTopics(expected[i].Topics))) + buf.WriteString(fmt.Sprintf(" data: %x\n", expected[i].Data)) + t.Errorf(buf.String()) + } + } +} + +func parseEthLogsFromSubscriptionResponses(subResponses []ethtypes.EthSubscriptionResponse) ([]*ethtypes.EthLog, error) { + elogs := make([]*ethtypes.EthLog, 0, len(subResponses)) + for i := range subResponses { + rlist, ok := subResponses[i].Result.([]interface{}) + if !ok { + return nil, xerrors.Errorf("expected subscription result to be []interface{}, but was %T", subResponses[i].Result) + } + + for _, r := range rlist { + rmap, ok := r.(map[string]interface{}) + if !ok { + return nil, xerrors.Errorf("expected subscription result entry to be map[string]interface{}, but was %T", r) + } + + elog, err := ParseEthLog(rmap) + if err != nil { + return nil, err + } + elogs = append(elogs, elog) + } + } + + return elogs, nil +} + +func parseEthLogsFromFilterResult(res *ethtypes.EthFilterResult) ([]*ethtypes.EthLog, error) { + elogs := make([]*ethtypes.EthLog, 0, len(res.Results)) + + for _, r := range res.Results { + rmap, ok := r.(map[string]interface{}) + if !ok { + return nil, xerrors.Errorf("expected filter result entry to be map[string]interface{}, but was %T", r) + } + + elog, err := ParseEthLog(rmap) + if err != nil { + return nil, err + } + elogs = append(elogs, elog) + } + + return elogs, nil +} + +func ParseEthLog(in map[string]interface{}) (*ethtypes.EthLog, error) { + el := ðtypes.EthLog{} + + ethHash := func(k string, v interface{}) (ethtypes.EthHash, error) { + s, ok := v.(string) + if !ok { + return ethtypes.EthHash{}, xerrors.Errorf(k + " not a string") + } + return ethtypes.ParseEthHash(s) + } + + ethUint64 := func(k string, v interface{}) (ethtypes.EthUint64, error) { + s, ok := v.(string) + if !ok { + return 0, xerrors.Errorf(k + " not a string") + } + parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64) + if err != nil { + return 0, err + } + return ethtypes.EthUint64(parsedInt), nil + } + + var err error + for k, v := range in { + switch k { + case "removed": + b, ok := v.(bool) + if ok { + el.Removed = b + continue + } + s, ok := v.(string) + if !ok { + return nil, xerrors.Errorf(k + ": not a string") + } + el.Removed, err = strconv.ParseBool(s) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + case "address": + s, ok := v.(string) + if !ok { + return nil, xerrors.Errorf(k + ": not a string") + } + el.Address, err = ethtypes.ParseEthAddress(s) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + case "logIndex": + el.LogIndex, err = ethUint64(k, v) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + case "transactionIndex": + el.TransactionIndex, err = ethUint64(k, v) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + case "blockNumber": + el.BlockNumber, err = ethUint64(k, v) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + case "transactionHash": + el.TransactionHash, err = ethHash(k, v) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + case "blockHash": + el.BlockHash, err = ethHash(k, v) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + case "data": + s, ok := v.(string) + if !ok { + return nil, xerrors.Errorf(k + ": not a string") + } + data, err := hex.DecodeString(s[2:]) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + el.Data = data + + case "topics": + s, ok := v.(string) + if ok { + topic, err := hex.DecodeString(s[2:]) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + el.Topics = append(el.Topics, topic) + continue + } + + sl, ok := v.([]interface{}) + if !ok { + return nil, xerrors.Errorf(k + ": not a slice") + } + for _, s := range sl { + topic, err := hex.DecodeString(s.(string)[2:]) + if err != nil { + return nil, xerrors.Errorf("%s: %w", k, err) + } + el.Topics = append(el.Topics, topic) + } + } + } + + return el, err +} + +func paddedEthBytes(orig []byte) ethtypes.EthBytes { + needed := 32 - len(orig) + if needed <= 0 { + return orig + } + ret := make([]byte, 32) + copy(ret[needed:], orig) + return ret +} + +func paddedUint64(v uint64) ethtypes.EthBytes { + buf := make([]byte, 32) + binary.BigEndian.PutUint64(buf[24:], v) + return buf +} + +func paddedEthHash(orig []byte) ethtypes.EthHash { + if len(orig) > 32 { + panic("exceeds EthHash length") + } + var ret ethtypes.EthHash + needed := 32 - len(orig) + copy(ret[needed:], orig) + return ret +} + +func ethTopicHash(sig string) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(sig)) + return hasher.Sum(nil) +} + +func ethFunctionHash(sig string) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(sig)) + return hasher.Sum(nil)[:4] +} + +func packUint64Values(vals ...uint64) []byte { + ret := []byte{} + for _, v := range vals { + buf := paddedUint64(v) + ret = append(ret, buf...) + } + return ret +} + +func unpackUint64Values(data []byte) []uint64 { + if len(data)%32 != 0 { + panic("data length not a multiple of 32") + } + + var vals []uint64 + for i := 0; i < len(data); i += 32 { + v := binary.BigEndian.Uint64(data[i+24 : i+32]) + vals = append(vals, v) + } + return vals +} + +func newEthFilterBuilder() *ethFilterBuilder { return ðFilterBuilder{} } + +type ethFilterBuilder struct { + filter ethtypes.EthFilterSpec +} + +func (e *ethFilterBuilder) Filter() *ethtypes.EthFilterSpec { return &e.filter } + +func (e *ethFilterBuilder) FromBlock(v string) *ethFilterBuilder { + e.filter.FromBlock = &v + return e +} + +func (e *ethFilterBuilder) FromBlockEpoch(v abi.ChainEpoch) *ethFilterBuilder { + s := ethtypes.EthUint64(v).Hex() + e.filter.FromBlock = &s + return e +} + +func (e *ethFilterBuilder) ToBlock(v string) *ethFilterBuilder { + e.filter.ToBlock = &v + return e +} + +func (e *ethFilterBuilder) ToBlockEpoch(v abi.ChainEpoch) *ethFilterBuilder { + s := ethtypes.EthUint64(v).Hex() + e.filter.ToBlock = &s + return e +} + +func (e *ethFilterBuilder) BlockHash(h ethtypes.EthHash) *ethFilterBuilder { + e.filter.BlockHash = &h + return e +} + +func (e *ethFilterBuilder) AddressOneOf(as ...ethtypes.EthAddress) *ethFilterBuilder { + e.filter.Address = as + return e +} + +func (e *ethFilterBuilder) Topic1OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + if len(e.filter.Topics) == 0 { + e.filter.Topics = make(ethtypes.EthTopicSpec, 1) + } + e.filter.Topics[0] = hs + return e +} + +func (e *ethFilterBuilder) Topic2OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + for len(e.filter.Topics) < 2 { + e.filter.Topics = append(e.filter.Topics, nil) + } + e.filter.Topics[1] = hs + return e +} + +func (e *ethFilterBuilder) Topic3OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + for len(e.filter.Topics) < 3 { + e.filter.Topics = append(e.filter.Topics, nil) + } + e.filter.Topics[2] = hs + return e +} + +func (e *ethFilterBuilder) Topic4OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + for len(e.filter.Topics) < 4 { + e.filter.Topics = append(e.filter.Topics, nil) + } + e.filter.Topics[3] = hs + return e +} + +func decodeLogBytes(orig []byte) []byte { + if len(orig) == 0 { + return orig + } + decoded, err := cbg.ReadByteArray(bytes.NewReader(orig), uint64(len(orig))) + if err != nil { + return orig + } + return decoded +} diff --git a/itests/eth_hash_lookup_test.go b/itests/eth_hash_lookup_test.go new file mode 100644 index 000000000..37d069796 --- /dev/null +++ b/itests/eth_hash_lookup_test.go @@ -0,0 +1,510 @@ +package itests + +import ( + "context" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "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/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestTransactionHashLookup tests to see if lotus correctly stores a mapping from ethereum transaction hash to +// Filecoin Message Cid +func TestTransactionHashLookup(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + // now deploy a contract from the embryo, and validate it went well + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.Zero(), + Nonce: 0, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, key.PrivateKey) + + rawTxHash, err := tx.TxHash() + require.NoError(t, err) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + require.Equal(t, rawTxHash, hash) + + mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, mpoolTx.Hash) + + // Wait for message to land on chain + var receipt *api.EthTxReceipt + for i := 0; i < 20; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(blocktime) + continue + } + break + } + require.NoError(t, err) + require.NotNil(t, receipt) + + // Verify that the chain transaction now has new fields set. + chainTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, chainTx.Hash) + + // require that the hashes are identical + require.Equal(t, hash, chainTx.Hash) + require.NotNil(t, chainTx.BlockNumber) + require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) + require.NotNil(t, chainTx.BlockHash) + require.NotEmpty(t, *chainTx.BlockHash) + require.NotNil(t, chainTx.TransactionIndex) + require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction +} + +// TestTransactionHashLookupBlsFilecoinMessage tests to see if lotus can find a BLS Filecoin Message using the transaction hash +func TestTransactionHashLookupBlsFilecoinMessage(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTBLS) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + msg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(sm.Message.Cid()) + require.NoError(t, err) + + mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, mpoolTx.Hash) + + // Wait for message to land on chain + var receipt *api.EthTxReceipt + for i := 0; i < 20; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(blocktime) + continue + } + break + } + require.NoError(t, err) + require.NotNil(t, receipt) + require.Equal(t, hash, receipt.TransactionHash) + + // Verify that the chain transaction now has new fields set. + chainTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, chainTx.Hash) + + // require that the hashes are identical + require.Equal(t, hash, chainTx.Hash) + require.NotNil(t, chainTx.BlockNumber) + require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) + require.NotNil(t, chainTx.BlockHash) + require.NotEmpty(t, *chainTx.BlockHash) + require.NotNil(t, chainTx.TransactionIndex) + require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction +} + +// TestTransactionHashLookupSecpFilecoinMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash +func TestTransactionHashLookupSecpFilecoinMessage(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + setupMsg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + setupSmsg, err := client.MpoolPushMessage(ctx, setupMsg, nil) + require.NoError(t, err) + + _, err = client.StateWaitMsg(ctx, setupSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + // Send message for secp account + secpMsg := &types.Message{ + From: addr, + To: client.DefaultKey.Address, + Value: big.Div(toSend, big.NewInt(2)), + } + + secpSmsg, err := client.MpoolPushMessage(ctx, secpMsg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(secpSmsg.Cid()) + require.NoError(t, err) + + mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, mpoolTx.Hash) + + _, err = client.StateWaitMsg(ctx, secpSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + receipt, err := client.EthGetTransactionReceipt(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.Equal(t, hash, receipt.TransactionHash) + + // Verify that the chain transaction now has new fields set. + chainTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, chainTx.Hash) + + // require that the hashes are identical + require.Equal(t, hash, chainTx.Hash) + require.NotNil(t, chainTx.BlockNumber) + require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) + require.NotNil(t, chainTx.BlockHash) + require.NotEmpty(t, *chainTx.BlockHash) + require.NotNil(t, chainTx.TransactionIndex) + require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction +} + +// TestTransactionHashLookupSecpFilecoinMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash +func TestTransactionHashLookupNonexistentMessage(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + cid := build.MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e") + + // We shouldn't be able to return a hash for this fake cid + chainHash, err := client.EthGetTransactionHashByCid(ctx, cid) + require.NoError(t, err) + require.Nil(t, chainHash) + + calculatedHash, err := ethtypes.EthHashFromCid(cid) + require.NoError(t, err) + + // We shouldn't be able to return a cid for this fake hash + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &calculatedHash) + require.NoError(t, err) + require.Nil(t, chainCid) +} + +func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + // now deploy a contract from the embryo, and validate it went well + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.Zero(), + Nonce: 0, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, key.PrivateKey) + + sender, err := tx.Sender() + require.NoError(t, err) + + unsignedMessage, err := tx.ToUnsignedMessage(sender) + require.NoError(t, err) + + rawTxHash, err := tx.TxHash() + require.NoError(t, err) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + require.Equal(t, rawTxHash, hash) + + mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mpoolCid) + + mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, mpoolTx) + require.Equal(t, *unsignedMessage, *mpoolTx) + + // Wait for message to land on chain + var receipt *api.EthTxReceipt + for i := 0; i < 20; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(blocktime) + continue + } + break + } + require.NoError(t, err) + require.NotNil(t, receipt) + + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, chainCid) + + chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, chainTx) + require.Equal(t, *unsignedMessage, *chainTx) +} + +func TestEthGetMessageCidByTransactionHashSecp(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + setupMsg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + setupSmsg, err := client.MpoolPushMessage(ctx, setupMsg, nil) + require.NoError(t, err) + + _, err = client.StateWaitMsg(ctx, setupSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + // Send message for secp account + secpMsg := &types.Message{ + From: addr, + To: client.DefaultKey.Address, + Value: big.Div(toSend, big.NewInt(2)), + } + + secpSmsg, err := client.MpoolPushMessage(ctx, secpMsg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(secpSmsg.Cid()) + require.NoError(t, err) + + mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mpoolCid) + + mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, mpoolTx) + require.Equal(t, secpSmsg.Message, *mpoolTx) + + _, err = client.StateWaitMsg(ctx, secpSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, chainCid) + + chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, chainTx) + require.Equal(t, secpSmsg.Message, *chainTx) +} + +func TestEthGetMessageCidByTransactionHashBLS(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTBLS) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + msg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(sm.Cid()) + require.NoError(t, err) + + mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mpoolCid) + + mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, mpoolTx) + require.Equal(t, sm.Message, *mpoolTx) + + _, err = client.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, chainCid) + + chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, chainTx) + require.Equal(t, sm.Message, *chainTx) +} diff --git a/itests/eth_transactions_test.go b/itests/eth_transactions_test.go new file mode 100644 index 000000000..0c8f1baa5 --- /dev/null +++ b/itests/eth_transactions_test.go @@ -0,0 +1,300 @@ +package itests + +import ( + "context" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + "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" + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestValueTransferValidSignature(t *testing.T) { + 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() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + _, ethAddr2, _ := client.EVM().NewAccount() + + kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000)) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + 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: ðAddr2, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, key.PrivateKey) + // Mangle signature + tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) + + signed, err := tx.ToRlpSignedMsg() + require.NoError(t, err) + // Submit transaction with bad signature + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + + // Submit transaction with valid signature + client.EVM().SignTransaction(&tx, key.PrivateKey) + hash := client.EVM().SubmitTransaction(ctx, &tx) + + receipt, err := waitForEthTxReceipt(ctx, client, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) +} + +func TestLegacyTransaction(t *testing.T) { + 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() + + // This is a legacy style transaction obtained from etherscan + // Tx details: https://etherscan.io/getRawTx?tx=0x0763262208d89efeeb50c8bb05b50c537903fe9d7bdef3b223fd1f5f69f69b32 + txBytes, err := hex.DecodeString("f86f830131cf8504a817c800825208942cf1e5a8250ded8835694ebeb90cfa0237fcb9b1882ec4a5251d1100008026a0f5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911a015be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930") + require.NoError(t, err) + _, err = client.EVM().EthSendRawTransaction(ctx, txBytes) + require.ErrorContains(t, err, "legacy transaction is not supported") + +} + +func TestContractDeploymentValidSignature(t *testing.T) { + + 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() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + // verify the deployer address is a placeholder. + client.AssertActorType(ctx, deployer, manifest.PlaceholderKey) + + tx, err := deployContractTx(ctx, client, ethAddr, contract) + require.NoError(t, err) + + client.EVM().SignTransaction(tx, key.PrivateKey) + // Mangle signature + tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int) + + signed, err := tx.ToRlpSignedMsg() + require.NoError(t, err) + // Submit transaction with bad signature + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + + // Submit transaction with valid signature + client.EVM().SignTransaction(tx, key.PrivateKey) + hash := client.EVM().SubmitTransaction(ctx, tx) + + receipt, err := waitForEthTxReceipt(ctx, client, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Verify that the deployer is now an account. + client.AssertActorType(ctx, deployer, manifest.EthAccountKey) + + // Verify that the nonce was incremented. + nonce, err := client.MpoolGetNonce(ctx, deployer) + require.NoError(t, err) + require.EqualValues(t, 1, nonce) + + // Verify that the deployer is now an account. + client.AssertActorType(ctx, deployer, manifest.EthAccountKey) +} + +func TestContractInvocation(t *testing.T) { + 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() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + // DEPLOY CONTRACT + tx, err := deployContractTx(ctx, client, ethAddr, contract) + require.NoError(t, err) + + client.EVM().SignTransaction(tx, key.PrivateKey) + hash := client.EVM().SubmitTransaction(ctx, tx) + + receipt, err := waitForEthTxReceipt(ctx, client, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + + // Get contract address. + contractAddr := client.EVM().ComputeContractAddress(ethAddr, 0) + + // INVOKE CONTRACT + + // Params + // entry point for getBalance - f8b2cb4f + // address - ff00000000000000000000000000000000000064 + params, err := hex.DecodeString("f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064") + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + To: &contractAddr, + Data: params, + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + invokeTx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + To: &contractAddr, + Value: big.Zero(), + Nonce: 1, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: params, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&invokeTx, key.PrivateKey) + // Mangle signature + invokeTx.V.Int.Xor(invokeTx.V.Int, big.NewInt(1).Int) + + signed, err := invokeTx.ToRlpSignedMsg() + require.NoError(t, err) + // Submit transaction with bad signature + _, err = client.EVM().EthSendRawTransaction(ctx, signed) + require.Error(t, err) + + // Submit transaction with valid signature + client.EVM().SignTransaction(&invokeTx, key.PrivateKey) + hash = client.EVM().SubmitTransaction(ctx, &invokeTx) + + receipt, err = waitForEthTxReceipt(ctx, client, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + + // Success. + require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status) + +} + +func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthTxArgs, error) { + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + if err != nil { + return nil, err + } + + // now deploy a contract from the embryo, and validate it went well + return ðtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.Zero(), + Nonce: 0, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + }, nil +} + +func waitForEthTxReceipt(ctx context.Context, client *kit.TestFullNode, hash ethtypes.EthHash) (*api.EthTxReceipt, error) { + var receipt *api.EthTxReceipt + var err error + for i := 0; i < 10000000000; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(500 * time.Millisecond) + continue + } + break + } + return receipt, err +} diff --git a/itests/fevm_address_test.go b/itests/fevm_address_test.go new file mode 100644 index 000000000..a23e96860 --- /dev/null +++ b/itests/fevm_address_test.go @@ -0,0 +1,134 @@ +package itests + +import ( + "bytes" + "context" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/builtin/v10/eam" + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +func effectiveEthAddressForCreate(t *testing.T, sender address.Address) ethtypes.EthAddress { + switch sender.Protocol() { + case address.SECP256K1, address.BLS: + hasher := sha3.NewLegacyKeccak256() + hasher.Write(sender.Bytes()) + addr, err := ethtypes.CastEthAddress(hasher.Sum(nil)[12:]) + require.NoError(t, err) + return addr + case address.Delegated: + addr, err := ethtypes.EthAddressFromFilecoinAddress(sender) + require.NoError(t, err) + return addr + default: + require.FailNow(t, "unsupported protocol %d", sender.Protocol()) + } + panic("unreachable") +} + +func TestAddressCreationBeforeDeploy(t *testing.T) { + kit.QuietMiningLogs() + + 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() + + // install contract + contractHex, err := os.ReadFile("contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + fromAddr, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + + // We hash the f1/f3 address into the EVM's address space when deploying contracts from + // accounts. + effectiveEvmAddress := effectiveEthAddressForCreate(t, fromAddr) + ethAddr := client.EVM().ComputeContractAddress(effectiveEvmAddress, 1) + + contractFilAddr, err := ethAddr.ToFilecoinAddress() + require.NoError(t, err) + + // Send contract address some funds + + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + sendAmount := big.Div(bal, big.NewInt(2)) + + sendMsg := &types.Message{ + From: fromAddr, + To: contractFilAddr, + Value: sendAmount, + } + signedMsg, err := client.MpoolPushMessage(ctx, sendMsg, nil) + require.NoError(t, err) + mLookup, err := client.StateWaitMsg(ctx, signedMsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) + + // Check if actor at new address is a placeholder actor + actor, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK) + require.NoError(t, err) + require.True(t, builtin.IsPlaceholderActor(actor.Code)) + + // Create and deploy evm actor + + method := builtintypes.MethodsEAM.CreateExternal + contractParams := abi.CborBytes(contract) + params, err := actors.SerializeParams(&contractParams) + require.NoError(t, err) + + createMsg := &types.Message{ + To: builtintypes.EthereumAddressManagerActorAddr, + From: fromAddr, + Value: big.Zero(), + Method: method, + Params: params, + } + smsg, err := client.MpoolPushMessage(ctx, createMsg, nil) + require.NoError(t, err) + + wait, err := client.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false) + require.NoError(t, err) + require.Equal(t, exitcode.Ok, wait.Receipt.ExitCode) + + // Check if eth address returned from CreateExternal is the same as eth address predicted at the start + var createExternalReturn eam.CreateExternalReturn + err = createExternalReturn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)) + require.NoError(t, err) + + createdEthAddr, err := ethtypes.CastEthAddress(createExternalReturn.EthAddress[:]) + require.NoError(t, err) + require.Equal(t, ethAddr, createdEthAddr) + + // Check if newly deployed actor still has funds + actorPostCreate, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK) + require.NoError(t, err) + + require.Equal(t, actorPostCreate.Balance, sendAmount) + require.True(t, builtin.IsEvmActor(actorPostCreate.Code)) + +} diff --git a/itests/fevm_events_test.go b/itests/fevm_events_test.go new file mode 100644 index 000000000..079a7a699 --- /dev/null +++ b/itests/fevm_events_test.go @@ -0,0 +1,84 @@ +package itests + +import ( + "context" + "encoding/hex" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestFEVMEvents does a basic events smoke test. +func TestFEVMEvents(t *testing.T) { + require := require.New(t) + + kit.QuietMiningLogs() + + 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() + + // install contract + // See https://github.com/filecoin-project/builtin-actors/blob/next/actors/evm/tests/events.rs#L12 + contractHex, err := os.ReadFile("contracts/events.bin") + require.NoError(err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(err) + + fromAddr, err := client.WalletDefaultAddress(ctx) + require.NoError(err) + + result := client.EVM().DeployContract(ctx, fromAddr, contract) + + idAddr, err := address.NewIDAddress(result.ActorID) + require.NoError(err) + t.Logf("actor ID address is %s", idAddr) + + var ( + earliest = "earliest" + latest = "latest" + ) + + // Install a filter. + filter, err := client.EthNewFilter(ctx, ðtypes.EthFilterSpec{ + FromBlock: &earliest, + ToBlock: &latest, + }) + require.NoError(err) + + // No logs yet. + res, err := client.EthGetFilterLogs(ctx, filter) + require.NoError(err) + require.Empty(res.Results) + + // log a zero topic event with data + ret := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, []byte{0x00, 0x00, 0x00, 0x00}, nil) + require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed") + require.NotNil(ret.Receipt.EventsRoot) + fmt.Println(ret) + fmt.Printf("Events:\n %+v\n", client.EVM().LoadEvents(ctx, *ret.Receipt.EventsRoot)) + + // log a zero topic event with no data + ret = client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, []byte{0x00, 0x00, 0x00, 0x01}, nil) + require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed") + fmt.Println(ret) + fmt.Printf("Events:\n %+v\n", client.EVM().LoadEvents(ctx, *ret.Receipt.EventsRoot)) + + // log a four topic event with data + ret = client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, []byte{0x00, 0x00, 0x00, 0x02}, nil) + require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed") + fmt.Println(ret) + fmt.Printf("Events:\n %+v\n", client.EVM().LoadEvents(ctx, *ret.Receipt.EventsRoot)) +} diff --git a/itests/fevm_test.go b/itests/fevm_test.go new file mode 100644 index 000000000..e05b0e2cc --- /dev/null +++ b/itests/fevm_test.go @@ -0,0 +1,141 @@ +package itests + +import ( + "context" + "encoding/hex" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +// convert a simple byte array into input data which is a left padded 32 byte array +func inputDataFromArray(input []byte) []byte { + inputData := make([]byte, 32) + copy(inputData[32-len(input):], input[:]) + return inputData +} + +// convert a "from" address into input data which is a left padded 32 byte array +func inputDataFromFrom(ctx context.Context, t *testing.T, client *kit.TestFullNode, from address.Address) []byte { + fromId, err := client.StateLookupID(ctx, from, types.EmptyTSK) + require.NoError(t, err) + + senderEthAddr, err := ethtypes.EthAddressFromFilecoinAddress(fromId) + require.NoError(t, err) + inputData := make([]byte, 32) + copy(inputData[32-len(senderEthAddr):], senderEthAddr[:]) + return inputData +} + +func setupFEVMTest(t *testing.T) (context.Context, context.CancelFunc, *kit.TestFullNode) { + kit.QuietMiningLogs() + 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) + return ctx, cancel, client +} + +// TestFEVMBasic does a basic fevm contract installation and invocation +func TestFEVMBasic(t *testing.T) { + + ctx, cancel, client := setupFEVMTest(t) + defer cancel() + + filename := "contracts/SimpleCoin.hex" + // install contract + fromAddr, idAddr := client.EVM().DeployContractFromFilename(ctx, filename) + + // invoke the contract with owner + { + inputData := inputDataFromFrom(ctx, t, client, fromAddr) + result := client.EVM().InvokeContractByFuncName(ctx, fromAddr, idAddr, "getBalance(address)", inputData) + + expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000002710") + require.NoError(t, err) + require.Equal(t, result, expectedResult) + } + + // invoke the contract with non owner + { + inputData := inputDataFromFrom(ctx, t, client, fromAddr) + inputData[31]++ // change the pub address to one that has 0 balance by incrementing the last byte of the address + result := client.EVM().InvokeContractByFuncName(ctx, fromAddr, idAddr, "getBalance(address)", inputData) + + expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + require.Equal(t, result, expectedResult) + } +} + +// TestFEVMETH0 tests that the ETH0 actor is in genesis +func TestFEVMETH0(t *testing.T) { + ctx, cancel, client := setupFEVMTest(t) + defer cancel() + + eth0id, err := address.NewIDAddress(1001) + require.NoError(t, err) + + client.AssertActorType(ctx, eth0id, manifest.EthAccountKey) + + act, err := client.StateGetActor(ctx, eth0id, types.EmptyTSK) + require.NoError(t, err) + + eth0Addr, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, make([]byte, 20)) + require.NoError(t, err) + require.Equal(t, *act.Address, eth0Addr) +} + +// TestFEVMDelegateCall deploys two contracts and makes a delegate call transaction +func TestFEVMDelegateCall(t *testing.T) { + + ctx, cancel, client := setupFEVMTest(t) + defer cancel() + + //install contract Actor + filenameActor := "contracts/DelegatecallActor.hex" + fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) + //install contract Storage + filenameStorage := "contracts/DelegatecallStorage.hex" + fromAddrStorage, storageAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) + require.Equal(t, fromAddr, fromAddrStorage) + + //call Contract Storage which makes a delegatecall to contract Actor + //this contract call sets the "counter" variable to 7, from default value 0 + + inputDataContract := inputDataFromFrom(ctx, t, client, actorAddr) + inputDataValue := inputDataFromArray([]byte{7}) + inputData := append(inputDataContract, inputDataValue...) + + //verify that the returned value of the call to setvars is 7 + result := client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "setVars(address,uint256)", inputData) + expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000007") + require.NoError(t, err) + require.Equal(t, result, expectedResult) + + //test the value is 7 via calling the getter + result = client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "getCounter()", []byte{}) + require.Equal(t, result, expectedResult) + + //test the value is 0 via calling the getter on the Actor contract + result = client.EVM().InvokeContractByFuncName(ctx, fromAddr, actorAddr, "getCounter()", []byte{}) + expectedResultActor, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + require.Equal(t, result, expectedResultActor) +} + +func TestEVMRpcDisable(t *testing.T) { + client, _, _ := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.DisableEthRPC()) + + _, err := client.EthBlockNumber(context.Background()) + require.ErrorContains(t, err, "module disabled, enable with Fevm.EnableEthRPC") +} diff --git a/itests/kit/evm.go b/itests/kit/evm.go new file mode 100644 index 000000000..46aaf52db --- /dev/null +++ b/itests/kit/evm.go @@ -0,0 +1,303 @@ +package kit + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "fmt" + "os" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-varint" + "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/crypto/sha3" + + "github.com/filecoin-project/go-address" + amt4 "github.com/filecoin-project/go-amt-ipld/v4" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/builtin/v10/eam" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/chain/wallet/key" + "github.com/filecoin-project/lotus/lib/sigs" +) + +// EVM groups EVM-related actions. +type EVM struct{ *TestFullNode } + +func (f *TestFullNode) EVM() *EVM { + return &EVM{f} +} + +func (e *EVM) DeployContract(ctx context.Context, sender address.Address, bytecode []byte) eam.CreateReturn { + require := require.New(e.t) + + nonce, err := e.MpoolGetNonce(ctx, sender) + if err != nil { + nonce = 0 // assume a zero nonce on error (e.g. sender doesn't exist). + } + + var salt [32]byte + binary.BigEndian.PutUint64(salt[:], nonce) + + method := builtintypes.MethodsEAM.CreateExternal + initcode := abi.CborBytes(bytecode) + params, err := actors.SerializeParams(&initcode) + require.NoError(err) + + msg := &types.Message{ + To: builtintypes.EthereumAddressManagerActorAddr, + From: sender, + Value: big.Zero(), + Method: method, + Params: params, + } + + e.t.Log("sending create message") + smsg, err := e.MpoolPushMessage(ctx, msg, nil) + require.NoError(err) + + e.t.Log("waiting for message to execute") + wait, err := e.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false) + require.NoError(err) + + require.True(wait.Receipt.ExitCode.IsSuccess(), "contract installation failed") + + var result eam.CreateReturn + r := bytes.NewReader(wait.Receipt.Return) + err = result.UnmarshalCBOR(r) + require.NoError(err) + + return result +} + +func (e *EVM) DeployContractFromFilename(ctx context.Context, binFilename string) (address.Address, address.Address) { + contractHex, err := os.ReadFile(binFilename) + require.NoError(e.t, err) + + // strip any trailing newlines from the file + contractHex = bytes.TrimRight(contractHex, "\n") + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(e.t, err) + + fromAddr, err := e.WalletDefaultAddress(ctx) + require.NoError(e.t, err) + + result := e.DeployContract(ctx, fromAddr, contract) + + idAddr, err := address.NewIDAddress(result.ActorID) + require.NoError(e.t, err) + return fromAddr, idAddr +} + +func (e *EVM) InvokeSolidity(ctx context.Context, sender address.Address, target address.Address, selector []byte, inputData []byte) *api.MsgLookup { + require := require.New(e.t) + + params := append(selector, inputData...) + var buffer bytes.Buffer + err := cbg.WriteByteArray(&buffer, params) + require.NoError(err) + params = buffer.Bytes() + + msg := &types.Message{ + To: target, + From: sender, + Value: big.Zero(), + Method: builtintypes.MethodsEVM.InvokeContract, + Params: params, + } + + e.t.Log("sending invoke message") + smsg, err := e.MpoolPushMessage(ctx, msg, nil) + require.NoError(err) + + e.t.Log("waiting for message to execute") + wait, err := e.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false) + require.NoError(err) + + return wait +} + +// LoadEvents loads all events in an event AMT. +func (e *EVM) LoadEvents(ctx context.Context, eventsRoot cid.Cid) []types.Event { + require := require.New(e.t) + + s := &apiIpldStore{ctx, e} + amt, err := amt4.LoadAMT(ctx, s, eventsRoot, amt4.UseTreeBitWidth(types.EventAMTBitwidth)) + require.NoError(err) + + ret := make([]types.Event, 0, amt.Len()) + err = amt.ForEach(ctx, func(u uint64, deferred *cbg.Deferred) error { + var evt types.Event + if err := evt.UnmarshalCBOR(bytes.NewReader(deferred.Raw)); err != nil { + return err + } + ret = append(ret, evt) + return nil + }) + require.NoError(err) + return ret +} + +func (e *EVM) NewAccount() (*key.Key, ethtypes.EthAddress, address.Address) { + // Generate a secp256k1 key; this will back the Ethereum identity. + key, err := key.GenerateKey(types.KTSecp256k1) + require.NoError(e.t, err) + + ethAddr, err := ethtypes.EthAddressFromPubKey(key.PublicKey) + require.NoError(e.t, err) + + ea, err := ethtypes.CastEthAddress(ethAddr) + require.NoError(e.t, err) + + addr, err := ea.ToFilecoinAddress() + require.NoError(e.t, err) + + return key, *(*ethtypes.EthAddress)(ethAddr), addr +} + +// AssertAddressBalanceConsistent checks that the balance reported via the +// Filecoin and Ethereum operations for an f410 address is identical, returning +// the balance. +func (e *EVM) AssertAddressBalanceConsistent(ctx context.Context, addr address.Address) big.Int { + // Validate the arg is an f410 address. + require.Equal(e.t, address.Delegated, addr.Protocol()) + payload := addr.Payload() + namespace, _, err := varint.FromUvarint(payload) + require.NoError(e.t, err) + require.Equal(e.t, builtintypes.EthereumAddressManagerActorID, namespace) + + fbal, err := e.WalletBalance(ctx, addr) + require.NoError(e.t, err) + + ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(addr) + require.NoError(e.t, err) + + ebal, err := e.EthGetBalance(ctx, ethAddr, "latest") + require.NoError(e.t, err) + + require.Equal(e.t, fbal, types.BigInt(ebal)) + return fbal +} + +// SignTransaction signs an Ethereum transaction in place with the supplied private key. +func (e *EVM) SignTransaction(tx *ethtypes.EthTxArgs, privKey []byte) { + preimage, err := tx.ToRlpUnsignedMsg() + require.NoError(e.t, err) + + // sign the RLP payload + signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage) + require.NoError(e.t, err) + + r, s, v, err := ethtypes.RecoverSignature(*signature) + require.NoError(e.t, err) + + tx.V = big.Int(v) + tx.R = big.Int(r) + tx.S = big.Int(s) +} + +// SubmitTransaction submits the transaction via the Eth endpoint. +func (e *EVM) SubmitTransaction(ctx context.Context, tx *ethtypes.EthTxArgs) ethtypes.EthHash { + signed, err := tx.ToRlpSignedMsg() + require.NoError(e.t, err) + + hash, err := e.EthSendRawTransaction(ctx, signed) + require.NoError(e.t, err) + + return hash +} + +// ComputeContractAddress computes the address of a contract deployed by the +// specified address with the specified nonce. +func (e *EVM) ComputeContractAddress(deployer ethtypes.EthAddress, nonce uint64) ethtypes.EthAddress { + nonceRlp, err := formatInt(int(nonce)) + require.NoError(e.t, err) + + encoded, err := ethtypes.EncodeRLP([]interface{}{ + deployer[:], + nonceRlp, + }) + require.NoError(e.t, err) + + hasher := sha3.NewLegacyKeccak256() + hasher.Write(encoded) + return *(*ethtypes.EthAddress)(hasher.Sum(nil)[12:]) +} + +func (e *EVM) InvokeContractByFuncName(ctx context.Context, fromAddr address.Address, idAddr address.Address, funcSignature string, inputData []byte) []byte { + entryPoint := CalcFuncSignature(funcSignature) + wait := e.InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData) + require.True(e.t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed") + result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return))) + require.NoError(e.t, err) + return result +} + +// function signatures are the first 4 bytes of the hash of the function name and types +func CalcFuncSignature(funcName string) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(funcName)) + hash := hasher.Sum(nil) + return hash[:4] +} + +// TODO: cleanup and put somewhere reusable. +type apiIpldStore struct { + ctx context.Context + api api.FullNode +} + +func (ht *apiIpldStore) Context() context.Context { + return ht.ctx +} + +func (ht *apiIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) error { + raw, err := ht.api.ChainReadObj(ctx, c) + if err != nil { + return err + } + + cu, ok := out.(cbg.CBORUnmarshaler) + if ok { + if err := cu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil { + return err + } + return nil + } + + return fmt.Errorf("object does not implement CBORUnmarshaler") +} + +func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) { + panic("No mutations allowed") +} + +func formatInt(val int) ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, int64(val)) + if err != nil { + return nil, err + } + return removeLeadingZeros(buf.Bytes()), nil +} + +func removeLeadingZeros(data []byte) []byte { + firstNonZeroIndex := len(data) + for i, b := range data { + if b > 0 { + firstNonZeroIndex = i + break + } + } + return data[firstNonZeroIndex:] +} diff --git a/itests/kit/log.go b/itests/kit/log.go index beac3895c..0da9adfeb 100644 --- a/itests/kit/log.go +++ b/itests/kit/log.go @@ -1,6 +1,9 @@ package kit import ( + "io" + "log" + logging "github.com/ipfs/go-log/v2" "github.com/filecoin-project/lotus/lib/lotuslog" @@ -20,3 +23,13 @@ func QuietMiningLogs() { _ = logging.SetLogLevel("rpc", "ERROR") _ = logging.SetLogLevel("dht/RtRefreshManager", "ERROR") } + +func QuietAllLogsExcept(names ...string) { + log.SetOutput(io.Discard) // suppress LogDatastore messages + + lotuslog.SetupLogLevels() + logging.SetAllLoggers(logging.LevelError) + for _, name := range names { + _ = logging.SetLogLevel(name, "INFO") + } +} diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go index 12db91c68..4546f5a03 100644 --- a/itests/kit/node_full.go +++ b/itests/kit/node_full.go @@ -135,13 +135,21 @@ func HeightAtLeast(target abi.ChainEpoch) ChainPredicate { } } -// BlockMinedBy returns a ChainPredicate that is satisfied when we observe the -// first block mined by the specified miner. -func BlockMinedBy(miner address.Address) ChainPredicate { +// BlocksMinedByAll returns a ChainPredicate that is satisfied when we observe a +// tipset including blocks from all the specified miners, in no particular order. +func BlocksMinedByAll(miner ...address.Address) ChainPredicate { return func(ts *types.TipSet) bool { + seen := make([]bool, len(miner)) + var done int for _, b := range ts.Blocks() { - if b.Miner == miner { - return true + for i, m := range miner { + if b.Miner != m || seen[i] { + continue + } + seen[i] = true + if done++; done == len(miner) { + return true + } } } return false diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index 14b0bccc8..5d418c5be 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -58,6 +58,15 @@ var DefaultNodeOpts = nodeOpts{ sectors: DefaultPresealsPerBootstrapMiner, sectorSize: abi.SectorSize(2 << 10), // 2KiB. + cfgOpts: []CfgOption{ + func(cfg *config.FullNode) error { + // test defaults + + cfg.Fevm.EnableEthRPC = true + return nil + }, + }, + workerTasks: []sealtasks.TaskType{sealtasks.TTFetch, sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTFinalizeUnsealed}, workerStorageOpt: func(store paths.Store) paths.Store { return store }, } @@ -280,3 +289,17 @@ func SplitstoreMessges() NodeOpt { return nil }) } + +func WithEthRPC() NodeOpt { + return WithCfgOpt(func(cfg *config.FullNode) error { + cfg.Fevm.EnableEthRPC = true + return nil + }) +} + +func DisableEthRPC() NodeOpt { + return WithCfgOpt(func(cfg *config.FullNode) error { + cfg.Fevm.EnableEthRPC = false + return nil + }) +} diff --git a/itests/kit/state.go b/itests/kit/state.go new file mode 100644 index 000000000..e66576be3 --- /dev/null +++ b/itests/kit/state.go @@ -0,0 +1,33 @@ +package kit + +import ( + "context" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" +) + +// AssertActorType verifies that the supplied address is an actor of the +// specified type (as per its manifest key). +func (f *TestFullNode) AssertActorType(ctx context.Context, addr address.Address, actorType string) { + // validate that an placeholder was created + act, err := f.StateGetActor(ctx, addr, types.EmptyTSK) + require.NoError(f.t, err) + + nv, err := f.StateNetworkVersion(ctx, types.EmptyTSK) + require.NoError(f.t, err) + + av, err := actorstypes.VersionForNetwork(nv) + require.NoError(f.t, err) + + codecid, exists := actors.GetActorCodeID(av, actorType) + require.True(f.t, exists) + + // check the code CID + require.Equal(f.t, codecid, act.Code) +} diff --git a/itests/migration_nv18_test.go b/itests/migration_nv18_test.go new file mode 100644 index 000000000..44bf3806c --- /dev/null +++ b/itests/migration_nv18_test.go @@ -0,0 +1,98 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" + gstStore "github.com/filecoin-project/go-state-types/store" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + builtin2 "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/node/impl" +) + +func TestMigrationNV18(t *testing.T) { + kit.QuietMiningLogs() + + nv18epoch := abi.ChainEpoch(100) + testClient, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), + kit.UpgradeSchedule(stmgr.Upgrade{ + Network: network.Version17, + Height: -1, + }, stmgr.Upgrade{ + Network: network.Version18, + Height: nv18epoch, + Migration: filcns.UpgradeActorsV10, + }, + )) + + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + clientApi := testClient.FullNode.(*impl.FullNodeAPI) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testClient.WaitTillChain(ctx, kit.HeightAtLeast(nv18epoch+5)) + + // Now that we have upgraded, we need to: + // - the EAM exists, has "empty" state + // - the EthZeroAddress exists + // - all actors have nil Address fields + + bs := blockstore.NewAPIBlockstore(testClient) + ctxStore := gstStore.WrapBlockStore(ctx, bs) + + currTs, err := clientApi.ChainHead(ctx) + require.NoError(t, err) + + newStateTree, err := state.LoadStateTree(ctxStore, currTs.Blocks()[0].ParentStateRoot) + require.NoError(t, err) + + require.Equal(t, types.StateTreeVersion5, newStateTree.Version()) + + codeIDsv10, ok := actors.GetActorCodeIDsFromManifest(actorstypes.Version10) + require.True(t, ok) + + // check the EAM actor + EAMActor, err := newStateTree.GetActor(builtin.EthereumAddressManagerActorAddr) + require.NoError(t, err) + require.Equal(t, vm.EmptyObjectCid, EAMActor.Head) + EAMCodeID, ok := codeIDsv10[manifest.EamKey] + require.True(t, ok) + require.Equal(t, EAMCodeID, EAMActor.Code) + + // check the EthZeroAddress + ethZeroAddr, err := (ethtypes.EthAddress{}).ToFilecoinAddress() + require.NoError(t, err) + ethZeroAddrID, err := newStateTree.LookupID(ethZeroAddr) + require.NoError(t, err) + ethZeroActor, err := newStateTree.GetActor(ethZeroAddrID) + require.NoError(t, err) + require.True(t, builtin2.IsEthAccountActor(ethZeroActor.Code)) + require.Equal(t, vm.EmptyObjectCid, ethZeroActor.Head) + + // check all actor's Address fields + require.NoError(t, newStateTree.ForEach(func(address address.Address, actor *types.Actor) error { + if address != ethZeroAddrID { + require.Nil(t, actor.Address) + } + return nil + })) +} diff --git a/itests/multisig_test.go b/itests/multisig_test.go index b20dcf16b..92d9afca7 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -133,7 +133,7 @@ func TestMultisigReentrant(t *testing.T) { sl, err := client.StateReplay(ctx, types.EmptyTSK, pm.Cid()) require.NoError(t, err, "failed to replay reentrant propose message (StateWaitMsg)") - require.Equal(t, 1025, countDepth(sl.ExecutionTrace), "failed: %s", sl.Error) + require.Equal(t, 1024, countDepth(sl.ExecutionTrace), "failed: %s", sl.Error) } func countDepth(trace types.ExecutionTrace) int { diff --git a/itests/splitstore_test.go b/itests/splitstore_test.go index 957efe32f..4bbe56536 100644 --- a/itests/splitstore_test.go +++ b/itests/splitstore_test.go @@ -249,6 +249,49 @@ func TestMessagesMode(t *testing.T) { assert.True(g.t, g.Exists(ctx, garbageM), "Garbage message not found in splitstore") } +func TestCompactRetainsTipSetRef(t *testing.T) { + ctx := context.Background() + // disable sync checking because efficient itests require that the node is out of sync : / + splitstore.CheckSyncGap = false + opts := []interface{}{kit.MockProofs(), kit.SplitstoreDiscard()} + full, genesisMiner, ens := kit.EnsembleMinimal(t, opts...) + bm := ens.InterconnectAll().BeginMining(4 * time.Millisecond)[0] + _ = genesisMiner + _ = bm + + check, err := full.ChainHead(ctx) + require.NoError(t, err) + e := check.Height() + checkRef, err := check.Key().Cid() + require.NoError(t, err) + assert.True(t, ipldExists(ctx, t, checkRef, full)) // reference to tipset key should be persisted before compaction + + // Determine index of compaction that covers tipset "check" and wait for compaction + for { + bm.Pause() + if splitStoreCompacting(ctx, t, full) { + bm.Restart() + time.Sleep(1 * time.Second) + } else { + break + } + } + lastCompactionEpoch := splitStoreBaseEpoch(ctx, t, full) + garbageCompactionIndex := splitStoreCompactionIndex(ctx, t, full) + 1 + boundary := lastCompactionEpoch + splitstore.CompactionThreshold - splitstore.CompactionBoundary + + for e > boundary { + boundary += splitstore.CompactionThreshold - splitstore.CompactionBoundary + garbageCompactionIndex++ + } + bm.Restart() + + // wait for compaction to occur + waitForCompaction(ctx, t, garbageCompactionIndex, full) + assert.True(t, ipldExists(ctx, t, checkRef, full)) // reference to tipset key should be persisted after compaction + bm.Stop() +} + func waitForCompaction(ctx context.Context, t *testing.T, cIdx int64, n *kit.TestFullNode) { for { if splitStoreCompactionIndex(ctx, t, n) >= cIdx { @@ -307,6 +350,14 @@ func splitStorePruneIndex(ctx context.Context, t *testing.T, n *kit.TestFullNode return pruneIndex } +func ipldExists(ctx context.Context, t *testing.T, c cid.Cid, n *kit.TestFullNode) bool { + found, err := n.ChainHasObj(ctx, c) + if err != nil { + t.Fatalf("ChainHasObj failure: %s", err) + } + return found +} + // Create on chain unreachable garbage for a network to exercise splitstore // one garbage cid created at a time // @@ -361,12 +412,10 @@ func (g *Garbager) Exists(ctx context.Context, c cid.Cid) bool { return false } else if err != nil { g.t.Fatalf("ChainReadObj failure on existence check: %s", err) + return false // unreachable } else { return true } - - g.t.Fatal("unreachable") - return false } func (g *Garbager) newPeerID(ctx context.Context) abi.ChainEpoch { diff --git a/lib/sigs/delegated/init.go b/lib/sigs/delegated/init.go new file mode 100644 index 000000000..81886ceaa --- /dev/null +++ b/lib/sigs/delegated/init.go @@ -0,0 +1,79 @@ +package delegated + +import ( + "fmt" + + "golang.org/x/crypto/sha3" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + "github.com/filecoin-project/go-state-types/builtin" + crypto1 "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/lib/sigs" +) + +type delegatedSigner struct{} + +func (delegatedSigner) GenPrivate() ([]byte, error) { + priv, err := gocrypto.GenerateKey() + if err != nil { + return nil, err + } + return priv, nil +} + +func (delegatedSigner) ToPublic(pk []byte) ([]byte, error) { + return gocrypto.PublicKey(pk), nil +} + +func (s delegatedSigner) Sign(pk []byte, msg []byte) ([]byte, error) { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hashSum := hasher.Sum(nil) + sig, err := gocrypto.Sign(pk, hashSum) + if err != nil { + return nil, err + } + + return sig, nil +} + +func (delegatedSigner) Verify(sig []byte, a address.Address, msg []byte) error { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hash := hasher.Sum(nil) + + pubk, err := gocrypto.EcRecover(hash, sig) + if err != nil { + return err + } + + // if we get an uncompressed public key (that's what we get from the library, + // but putting this check here for defensiveness), strip the prefix + if pubk[0] == 0x04 { + pubk = pubk[1:] + } + + hasher.Reset() + hasher.Write(pubk) + addrHash := hasher.Sum(nil) + + // The address hash will not start with [12]byte{0xff}, so we don't have to use + // EthAddr.ToFilecoinAddress() to handle the case with an id address + // Also, importing ethtypes here will cause circulating import + maybeaddr, err := address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, addrHash[12:]) + if err != nil { + return err + } + + if maybeaddr != a { + return fmt.Errorf("signature did not match maybeaddr: %s, signer: %s", maybeaddr, a) + } + + return nil +} + +func init() { + sigs.RegisterSignature(crypto1.SigTypeDelegated, delegatedSigner{}) +} diff --git a/node/builder.go b/node/builder.go index eaf932e2e..ddba82112 100644 --- a/node/builder.go +++ b/node/builder.go @@ -30,6 +30,7 @@ import ( "github.com/filecoin-project/lotus/lib/lotuslog" "github.com/filecoin-project/lotus/lib/peermgr" _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node/config" diff --git a/node/builder_chain.go b/node/builder_chain.go index 87e994c4d..07b082cd2 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -17,6 +17,7 @@ import ( "github.com/filecoin-project/lotus/chain/beacon" "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/exchange" "github.com/filecoin-project/lotus/chain/gen/slashfilter" "github.com/filecoin-project/lotus/chain/market" @@ -151,6 +152,8 @@ var ChainNode = Options( Override(new(full.MpoolModuleAPI), From(new(api.Gateway))), Override(new(full.StateModuleAPI), From(new(api.Gateway))), Override(new(stmgr.StateManagerAPI), rpcstmgr.NewRPCStateManager), + Override(new(full.EthModuleAPI), From(new(api.Gateway))), + Override(new(full.EthEventAPI), From(new(api.Gateway))), ), // Full node API / service startup @@ -241,6 +244,7 @@ func ConfigFullNode(c interface{}) Option { Unset(new(*wallet.LocalWallet)), Override(new(wallet.Default), wallet.NilDefault), ), + // Chain node cluster enabled If(cfg.Cluster.ClusterModeEnabled, Override(new(*gorpc.Client), modules.NewRPCClient), @@ -251,6 +255,14 @@ func ConfigFullNode(c interface{}) Option { Override(new(*modules.RPCHandler), modules.NewRPCHandler), Override(GoRPCServer, modules.NewRPCServer), ), + + // Actor event filtering support + Override(new(events.EventAPI), From(new(modules.EventAPI))), + // in lite-mode Eth event api is provided by gateway + ApplyIf(isFullNode, Override(new(full.EthEventAPI), modules.EthEventAPI(cfg.Fevm))), + + If(cfg.Fevm.EnableEthRPC, Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm))), + If(!cfg.Fevm.EnableEthRPC, Override(new(full.EthModuleAPI), &full.EthModuleDummy{})), ) } diff --git a/node/config/def.go b/node/config/def.go index dc26f1661..9a24449ba 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -70,11 +70,12 @@ func defCommon() Common { DirectPeers: nil, }, } - } -var DefaultDefaultMaxFee = types.MustParseFIL("0.07") -var DefaultSimultaneousTransfers = uint64(20) +var ( + DefaultDefaultMaxFee = types.MustParseFIL("0.07") + DefaultSimultaneousTransfers = uint64(20) +) // DefaultFullNode returns the default config func DefaultFullNode() *FullNode { @@ -98,6 +99,18 @@ func DefaultFullNode() *FullNode { }, }, Cluster: *DefaultUserRaftConfig(), + Fevm: FevmConfig{ + EnableEthRPC: false, + EthTxHashMappingLifetimeDays: 0, + Events: Events{ + DisableRealTimeFilterAPI: false, + DisableHistoricFilterAPI: false, + FilterTTL: Duration(time.Hour * 24), + MaxFilters: 100, + MaxFilterResults: 10000, + MaxFilterHeightRange: 2880, // conservative limit of one day + }, + }, } } @@ -255,8 +268,10 @@ func DefaultStorageMiner() *StorageMiner { return cfg } -var _ encoding.TextMarshaler = (*Duration)(nil) -var _ encoding.TextUnmarshaler = (*Duration)(nil) +var ( + _ encoding.TextMarshaler = (*Duration)(nil) + _ encoding.TextUnmarshaler = (*Duration)(nil) +) // Duration is a wrapper type for time.Duration // for decoding and encoding from/to TOML diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 4723cc59f..8b79bed4f 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -341,6 +341,59 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Comment: ``, }, }, + "Events": []DocField{ + { + Name: "DisableRealTimeFilterAPI", + Type: "bool", + + Comment: `EnableEthRPC enables APIs that +DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. +The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag.`, + }, + { + Name: "DisableHistoricFilterAPI", + Type: "bool", + + Comment: `DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events +that occurred in the past. HistoricFilterAPI maintains a queryable index of events. +The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag.`, + }, + { + Name: "FilterTTL", + Type: "Duration", + + Comment: `FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than +this time become eligible for automatic deletion.`, + }, + { + Name: "MaxFilters", + Type: "int", + + Comment: `MaxFilters specifies the maximum number of filters that may exist at any one time.`, + }, + { + Name: "MaxFilterResults", + Type: "int", + + Comment: `MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter.`, + }, + { + Name: "MaxFilterHeightRange", + Type: "uint64", + + Comment: `MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying +the entire chain)`, + }, + { + Name: "DatabasePath", + Type: "string", + + Comment: `DatabasePath is the full path to a sqlite database that will be used to index actor events to +support the historic filter APIs. If the database does not exist it will be created. The directory containing +the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as +relative to the CWD (current working directory).`, + }, + }, "FeeConfig": []DocField{ { Name: "DefaultMaxFee", @@ -349,6 +402,28 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Comment: ``, }, }, + "FevmConfig": []DocField{ + { + Name: "EnableEthRPC", + Type: "bool", + + Comment: `EnableEthRPC enables eth_ rpc, and enables storing a mapping of eth transaction hashes to filecoin message Cids. +This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above.`, + }, + { + Name: "EthTxHashMappingLifetimeDays", + Type: "int", + + Comment: `EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days +Set to 0 to keep all mappings`, + }, + { + Name: "Events", + Type: "Events", + + Comment: ``, + }, + }, "FullNode": []DocField{ { Name: "Client", @@ -378,6 +453,12 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Name: "Cluster", Type: "UserRaftConfig", + Comment: ``, + }, + { + Name: "Fevm", + Type: "FevmConfig", + Comment: ``, }, }, diff --git a/node/config/types.go b/node/config/types.go index cc1a943f4..690e8caee 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -27,6 +27,7 @@ type FullNode struct { Fees FeeConfig Chainstore Chainstore Cluster UserRaftConfig + Fevm FevmConfig } // // Common @@ -168,7 +169,6 @@ type DealmakingConfig struct { } type IndexProviderConfig struct { - // Enable set whether to enable indexing announcement to the network and expose endpoints that // allow indexer nodes to process announcements. Enabled by default. Enable bool @@ -658,3 +658,52 @@ type UserRaftConfig struct { // Tracing enables propagation of contexts across binary boundaries. Tracing bool } + +type FevmConfig struct { + // EnableEthRPC enables eth_ rpc, and enables storing a mapping of eth transaction hashes to filecoin message Cids. + // This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above. + EnableEthRPC bool + + // EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days + // Set to 0 to keep all mappings + EthTxHashMappingLifetimeDays int + + Events Events +} + +type Events struct { + // EnableEthRPC enables APIs that + // DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. + // The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + DisableRealTimeFilterAPI bool + + // DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events + // that occurred in the past. HistoricFilterAPI maintains a queryable index of events. + // The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + DisableHistoricFilterAPI bool + + // FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than + // this time become eligible for automatic deletion. + FilterTTL Duration + + // MaxFilters specifies the maximum number of filters that may exist at any one time. + MaxFilters int + + // MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter. + MaxFilterResults int + + // MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying + // the entire chain) + MaxFilterHeightRange uint64 + + // DatabasePath is the full path to a sqlite database that will be used to index actor events to + // support the historic filter APIs. If the database does not exist it will be created. The directory containing + // the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as + // relative to the CWD (current working directory). + DatabasePath string + + // Others, not implemented yet: + // Set a limit on the number of active websocket subscriptions (may be zero) + // Set a timeout for subscription clients + // Set upper bound on index size +} diff --git a/node/impl/full.go b/node/impl/full.go index 03a28eb75..affcc960e 100644 --- a/node/impl/full.go +++ b/node/impl/full.go @@ -35,6 +35,7 @@ type FullNodeAPI struct { full.WalletAPI full.SyncAPI full.RaftAPI + full.EthAPI DS dtypes.MetadataDS NetworkName dtypes.NetworkName diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index ca245dcda..8448a542e 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "io" + "math" "strconv" "strings" "sync" @@ -24,6 +25,7 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + amt4 "github.com/filecoin-project/go-amt-ipld/v4" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -654,6 +656,33 @@ func (a *ChainAPI) ChainBlockstoreInfo(ctx context.Context) (map[string]interfac return info.Info(), nil } +// ChainGetEvents returns the events under an event AMT root CID. +// +// TODO (raulk) make copies of this logic elsewhere use this (e.g. itests, CLI, events filter). +func (a *ChainAPI) ChainGetEvents(ctx context.Context, root cid.Cid) ([]types.Event, error) { + store := cbor.NewCborStore(a.ExposedBlockstore) + evtArr, err := amt4.LoadAMT(ctx, store, root, amt4.UseTreeBitWidth(types.EventAMTBitwidth)) + if err != nil { + return nil, xerrors.Errorf("load events amt: %w", err) + } + + ret := make([]types.Event, 0, evtArr.Len()) + var evt types.Event + err = evtArr.ForEach(ctx, func(u uint64, deferred *cbg.Deferred) error { + if u > math.MaxInt { + return xerrors.Errorf("too many events") + } + if err := evt.UnmarshalCBOR(bytes.NewReader(deferred.Raw)); err != nil { + return err + } + + ret = append(ret, evt) + return nil + }) + + return ret, err +} + func (a *ChainAPI) ChainPrune(ctx context.Context, opts api.PruneOpts) error { pruner, ok := a.BaseBlockstore.(interface { PruneChain(opts api.PruneOpts) error diff --git a/node/impl/full/dummy.go b/node/impl/full/dummy.go new file mode 100644 index 000000000..aa1450212 --- /dev/null +++ b/node/impl/full/dummy.go @@ -0,0 +1,121 @@ +package full + +import ( + "context" + "errors" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +var ErrModuleDisabled = errors.New("module disabled, enable with Fevm.EnableEthRPC / LOTUS_FEVM_ENABLEETHPRC") + +type EthModuleDummy struct{} + +func (e *EthModuleDummy) EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) { + return 0, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) { + return 0, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) { + return 0, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) { + return ethtypes.EthBlock{}, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) { + return ethtypes.EthBlock{}, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) { + return 0, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + return ethtypes.EthTx{}, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + return ethtypes.EthTx{}, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) { + return ethtypes.EthBigIntZero, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) { + return ethtypes.EthFeeHistory{}, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) { + return 0, ErrModuleDisabled +} + +func (e *EthModuleDummy) NetVersion(ctx context.Context) (string, error) { + return "", ErrModuleDisabled +} + +func (e *EthModuleDummy) NetListening(ctx context.Context) (bool, error) { + return false, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) { + return 0, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) { + return ethtypes.EthBigIntZero, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { + return 0, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) { + return ethtypes.EthBigIntZero, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { + return ethtypes.EthHash{}, ErrModuleDisabled +} + +var _ EthModuleAPI = &EthModuleDummy{} diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go new file mode 100644 index 000000000..7eb992a22 --- /dev/null +++ b/node/impl/full/eth.go @@ -0,0 +1,1902 @@ +package full + +import ( + "bytes" + "context" + "errors" + "fmt" + "strconv" + "sync" + "time" + + "github.com/google/uuid" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "go.uber.org/fx" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/builtin/v10/eam" + "github.com/filecoin-project/go-state-types/builtin/v10/evm" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/ethhashlookup" + "github.com/filecoin-project/lotus/chain/events/filter" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +type EthModuleAPI interface { + EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) + EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) + EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) + EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) + EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) + EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) + EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) + EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) + EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) + 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) + EthChainId(ctx context.Context) (ethtypes.EthUint64, error) + NetVersion(ctx context.Context) (string, error) + 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) + EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) + EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) + EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) +} + +type EthEventAPI interface { + EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) + EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) + EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) + EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) + EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) +} + +var ( + _ EthModuleAPI = *new(api.FullNode) + _ EthEventAPI = *new(api.FullNode) +) + +// EthModule provides the default implementation of the standard Ethereum JSON-RPC API. +// +// # Execution model reconciliation +// +// Ethereum relies on an immediate block-based execution model. The block that includes +// a transaction is also the block that executes it. Each block specifies the state root +// resulting from executing all transactions within it (output state). +// +// In Filecoin, at every epoch there is an unknown number of round winners all of whom are +// entitled to publish a block. Blocks are collected into a tipset. A tipset is committed +// only when the subsequent tipset is built on it (i.e. it becomes a parent). Block producers +// execute the parent tipset and specify the resulting state root in the block being produced. +// In other words, contrary to Ethereum, each block specifies the input state root. +// +// Ethereum clients expect transactions returned via eth_getBlock* to have a receipt +// (due to immediate execution). For this reason: +// +// - eth_blockNumber returns the latest executed epoch (head - 1) +// - The 'latest' block refers to the latest executed epoch (head - 1) +// - The 'pending' block refers to the current speculative tipset (head) +// - eth_getTransactionByHash returns the inclusion tipset of a message, but +// only after it has executed. +// - eth_getTransactionReceipt ditto. +// +// "Latest executed epoch" refers to the tipset that this node currently +// accepts as the best parent tipset, based on the blocks it is accumulating +// within the HEAD tipset. +type EthModule struct { + Chain *store.ChainStore + Mpool *messagepool.MessagePool + StateManager *stmgr.StateManager + EthTxHashManager *EthTxHashManager + + ChainAPI + MpoolAPI + StateAPI +} + +var _ EthModuleAPI = (*EthModule)(nil) + +type EthEvent struct { + Chain *store.ChainStore + EventFilterManager *filter.EventFilterManager + TipSetFilterManager *filter.TipSetFilterManager + MemPoolFilterManager *filter.MemPoolFilterManager + FilterStore filter.FilterStore + SubManager *EthSubscriptionManager + MaxFilterHeightRange abi.ChainEpoch +} + +var _ EthEventAPI = (*EthEvent)(nil) + +type EthAPI struct { + fx.In + + Chain *store.ChainStore + + EthModuleAPI + EthEventAPI +} + +func (a *EthModule) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) { + return stmgr.GetNetworkName(ctx, a.StateManager, a.Chain.GetHeaviestTipSet().ParentState()) +} + +func (a *EthModule) EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) { + // eth_blockNumber needs to return the height of the latest committed tipset. + // Ethereum clients expect all transactions included in this block to have execution outputs. + // This is the parent of the head tipset. The head tipset is speculative, has not been + // recognized by the network, and its messages are only included, not executed. + // See https://github.com/filecoin-project/ref-fvm/issues/1135. + heaviest := a.Chain.GetHeaviestTipSet() + if height := heaviest.Height(); height == 0 { + // we're at genesis. + return ethtypes.EthUint64(height), nil + } + // First non-null parent. + effectiveParent := heaviest.Parents() + parent, err := a.Chain.GetTipSetFromKey(ctx, effectiveParent) + if err != nil { + return 0, err + } + return ethtypes.EthUint64(parent.Height()), nil +} + +func (a *EthModule) EthAccounts(context.Context) ([]ethtypes.EthAddress, error) { + // The lotus node is not expected to hold manage accounts, so we'll always return an empty array + return []ethtypes.EthAddress{}, nil +} + +func (a *EthModule) countTipsetMsgs(ctx context.Context, ts *types.TipSet) (int, error) { + blkMsgs, err := a.Chain.BlockMsgsForTipset(ctx, ts) + if err != nil { + return 0, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err) + } + + count := 0 + for _, blkMsg := range blkMsgs { + // TODO: may need to run canonical ordering and deduplication here + count += len(blkMsg.BlsMessages) + len(blkMsg.SecpkMessages) + } + return count, nil +} + +func (a *EthModule) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) { + ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(blkNum), nil, false) + if err != nil { + return ethtypes.EthUint64(0), xerrors.Errorf("error loading tipset %s: %w", ts, err) + } + + count, err := a.countTipsetMsgs(ctx, ts) + return ethtypes.EthUint64(count), err +} + +func (a *EthModule) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) { + ts, err := a.Chain.GetTipSetByCid(ctx, blkHash.ToCid()) + if err != nil { + return ethtypes.EthUint64(0), xerrors.Errorf("error loading tipset %s: %w", ts, err) + } + count, err := a.countTipsetMsgs(ctx, ts) + return ethtypes.EthUint64(count), err +} + +func (a *EthModule) EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) { + ts, err := a.Chain.GetTipSetByCid(ctx, blkHash.ToCid()) + if err != nil { + return ethtypes.EthBlock{}, xerrors.Errorf("error loading tipset %s: %w", ts, err) + } + return newEthBlockFromFilecoinTipSet(ctx, ts, fullTxInfo, a.Chain, a.ChainAPI, a.StateAPI) +} + +func (a *EthModule) parseBlkParam(ctx context.Context, blkParam string) (tipset *types.TipSet, err error) { + if blkParam == "earliest" { + return nil, fmt.Errorf("block param \"earliest\" is not supported") + } + + head := a.Chain.GetHeaviestTipSet() + switch blkParam { + case "pending": + return head, nil + case "latest": + parent, err := a.Chain.GetTipSetFromKey(ctx, head.Parents()) + if err != nil { + return nil, fmt.Errorf("cannot get parent tipset") + } + return parent, nil + default: + var num ethtypes.EthUint64 + err := num.UnmarshalJSON([]byte(`"` + blkParam + `"`)) + if err != nil { + return nil, fmt.Errorf("cannot parse block number: %v", err) + } + ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(num), nil, false) + if err != nil { + return nil, fmt.Errorf("cannot get tipset at height: %v", num) + } + return ts, nil + } +} + +func (a *EthModule) EthGetBlockByNumber(ctx context.Context, blkParam string, fullTxInfo bool) (ethtypes.EthBlock, error) { + ts, err := a.parseBlkParam(ctx, blkParam) + if err != nil { + return ethtypes.EthBlock{}, err + } + return newEthBlockFromFilecoinTipSet(ctx, ts, fullTxInfo, a.Chain, a.ChainAPI, a.StateAPI) +} + +func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) { + // Ethereum's behavior is to return null when the txHash is invalid, so we use nil to check if txHash is valid + if txHash == nil { + return nil, nil + } + + c, err := a.EthTxHashManager.TransactionHashLookup.GetCidFromHash(*txHash) + if err != nil { + log.Debug("could not find transaction hash %s in lookup table", txHash.String()) + } + + // This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message + if c == cid.Undef { + c = txHash.ToCid() + } + + // first, try to get the cid from mined transactions + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, api.LookbackNoLimit, true) + if err == nil { + tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI) + if err == nil { + return &tx, nil + } + } + + // if not found, try to get it from the mempool + pending, err := a.MpoolAPI.MpoolPending(ctx, types.EmptyTSK) + if err != nil { + // inability to fetch mpool pending transactions is an internal node error + // that needs to be reported as-is + return nil, fmt.Errorf("cannot get pending txs from mpool: %s", err) + } + + for _, p := range pending { + if p.Cid() == c { + tx, err := NewEthTxFromFilecoinMessage(ctx, p, a.StateAPI) + if err != nil { + return nil, fmt.Errorf("could not convert Filecoin message into tx: %s", err) + } + return &tx, nil + } + } + // Ethereum clients expect an empty response when the message was not found + return nil, nil +} + +func (a *EthModule) EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) { + // Ethereum's behavior is to return null when the txHash is invalid, so we use nil to check if txHash is valid + if txHash == nil { + return nil, nil + } + + c, err := a.EthTxHashManager.TransactionHashLookup.GetCidFromHash(*txHash) + // We fall out of the first condition and continue + if errors.Is(err, ethhashlookup.ErrNotFound) { + log.Debug("could not find transaction hash %s in lookup table", txHash.String()) + } else if err != nil { + return nil, xerrors.Errorf("database error: %w", err) + } else { + return &c, nil + } + + // This isn't an eth transaction we have the mapping for, so let's try looking it up as a filecoin message + if c == cid.Undef { + c = txHash.ToCid() + } + + _, err = a.StateAPI.Chain.GetSignedMessage(ctx, c) + if err == nil { + // This is an Eth Tx, Secp message, Or BLS message in the mpool + return &c, nil + } + + _, err = a.StateAPI.Chain.GetMessage(ctx, c) + if err == nil { + // This is a BLS message + return &c, nil + } + + // Ethereum clients expect an empty response when the message was not found + return nil, nil +} + +func (a *EthModule) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) { + hash, err := EthTxHashFromFilecoinMessageCid(ctx, cid, a.StateAPI) + if hash == ethtypes.EmptyEthHash { + // not found + return nil, nil + } + + return &hash, err +} + +func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkParam string) (ethtypes.EthUint64, error) { + addr, err := sender.ToFilecoinAddress() + if err != nil { + return ethtypes.EthUint64(0), nil + } + + ts, err := a.parseBlkParam(ctx, blkParam) + if err != nil { + return ethtypes.EthUint64(0), xerrors.Errorf("cannot parse block param: %s", blkParam) + } + + nonce, err := a.Mpool.GetNonce(ctx, addr, ts.Key()) + if err != nil { + return ethtypes.EthUint64(0), nil + } + return ethtypes.EthUint64(nonce), nil +} + +func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) { + c, err := a.EthTxHashManager.TransactionHashLookup.GetCidFromHash(txHash) + if err != nil { + log.Debug("could not find transaction hash %s in lookup table", txHash.String()) + } + + // This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message + if c == cid.Undef { + c = txHash.ToCid() + } + + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, api.LookbackNoLimit, true) + if err != nil || msgLookup == nil { + return nil, nil + } + + tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI) + if err != nil { + return nil, nil + } + + replay, err := a.StateAPI.StateReplay(ctx, types.EmptyTSK, c) + if err != nil { + return nil, nil + } + + var events []types.Event + if rct := replay.MsgRct; rct != nil && rct.EventsRoot != nil { + events, err = a.ChainAPI.ChainGetEvents(ctx, *rct.EventsRoot) + if err != nil { + return nil, nil + } + } + + receipt, err := newEthTxReceipt(ctx, tx, msgLookup, replay, events, a.StateAPI) + if err != nil { + return nil, nil + } + + return &receipt, nil +} + +func (a *EthModule) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + return ethtypes.EthTx{}, nil +} + +func (a *EthModule) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + return ethtypes.EthTx{}, nil +} + +// EthGetCode returns string value of the compiled bytecode +func (a *EthModule) EthGetCode(ctx context.Context, ethAddr ethtypes.EthAddress, blkParam string) (ethtypes.EthBytes, error) { + to, err := ethAddr.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("cannot get Filecoin address: %w", err) + } + + // use the system actor as the caller + from, err := address.NewIDAddress(0) + if err != nil { + return nil, fmt.Errorf("failed to construct system sender address: %w", err) + } + msg := &types.Message{ + From: from, + To: to, + Value: big.Zero(), + Method: builtintypes.MethodsEVM.GetBytecode, + Params: nil, + GasLimit: build.BlockGasLimit, + GasFeeCap: big.Zero(), + GasPremium: big.Zero(), + } + + ts, err := a.parseBlkParam(ctx, blkParam) + if err != nil { + return nil, xerrors.Errorf("cannot parse block param: %s", blkParam) + } + + // Try calling until we find a height with no migration. + var res *api.InvocResult + for { + res, err = a.StateManager.Call(ctx, msg, ts) + if err != stmgr.ErrExpensiveFork { + break + } + ts, err = a.Chain.GetTipSetFromKey(ctx, ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("getting parent tipset: %w", err) + } + } + + if err != nil { + // if the call resulted in error, this is not an EVM smart contract; + // return no bytecode. + return nil, nil + } + + if res.MsgRct == nil { + return nil, fmt.Errorf("no message receipt") + } + + if res.MsgRct.ExitCode.IsError() { + return nil, xerrors.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error) + } + + var bytecodeCid cbg.CborCid + if err := bytecodeCid.UnmarshalCBOR(bytes.NewReader(res.MsgRct.Return)); err != nil { + return nil, fmt.Errorf("failed to decode EVM bytecode CID: %w", err) + } + + blk, err := a.Chain.StateBlockstore().Get(ctx, cid.Cid(bytecodeCid)) + if err != nil { + return nil, fmt.Errorf("failed to get EVM bytecode: %w", err) + } + + return blk.RawData(), nil +} + +func (a *EthModule) EthGetStorageAt(ctx context.Context, ethAddr ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) { + l := len(position) + if l > 32 { + return nil, fmt.Errorf("supplied storage key is too long") + } + + // pad with zero bytes if smaller than 32 bytes + position = append(make([]byte, 32-l, 32-l), position...) + + to, err := ethAddr.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("cannot get Filecoin address: %w", err) + } + + // use the system actor as the caller + from, err := address.NewIDAddress(0) + if err != nil { + return nil, fmt.Errorf("failed to construct system sender address: %w", err) + } + + // TODO super duper hack (raulk). The EVM runtime actor uses the U256 parameter type in + // GetStorageAtParams, which serializes as a hex-encoded string. It should serialize + // as bytes. We didn't get to fix in time for Iron, so for now we just pass + // through the hex-encoded value passed through the Eth JSON-RPC API, by remarshalling it. + // We don't fix this at origin (builtin-actors) because we are not updating the bundle + // for Iron. + tmp, err := position.MarshalJSON() + if err != nil { + panic(err) + } + params, err := actors.SerializeParams(&evm.GetStorageAtParams{ + StorageKey: tmp[1 : len(tmp)-1], // TODO strip the JSON-encoding quotes -- yuck + }) + if err != nil { + return nil, fmt.Errorf("failed to serialize parameters: %w", err) + } + + msg := &types.Message{ + From: from, + To: to, + Value: big.Zero(), + Method: builtintypes.MethodsEVM.GetStorageAt, + Params: params, + GasLimit: build.BlockGasLimit, + GasFeeCap: big.Zero(), + GasPremium: big.Zero(), + } + + ts := a.Chain.GetHeaviestTipSet() + + // Try calling until we find a height with no migration. + var res *api.InvocResult + for { + res, err = a.StateManager.Call(ctx, msg, ts) + if err != stmgr.ErrExpensiveFork { + break + } + ts, err = a.Chain.GetTipSetFromKey(ctx, ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("getting parent tipset: %w", err) + } + } + + if err != nil { + return nil, xerrors.Errorf("Call failed: %w", err) + } + + if res.MsgRct == nil { + return nil, fmt.Errorf("no message receipt") + } + + return res.MsgRct.Return, nil +} + +func (a *EthModule) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) { + filAddr, err := address.ToFilecoinAddress() + if err != nil { + return ethtypes.EthBigInt{}, err + } + + ts, err := a.parseBlkParam(ctx, blkParam) + if err != nil { + return ethtypes.EthBigInt{}, xerrors.Errorf("cannot parse block param: %s", blkParam) + } + + actor, err := a.StateGetActor(ctx, filAddr, ts.Key()) + if xerrors.Is(err, types.ErrActorNotFound) { + return ethtypes.EthBigIntZero, nil + } else if err != nil { + return ethtypes.EthBigInt{}, err + } + + return ethtypes.EthBigInt{Int: actor.Balance.Int}, nil +} + +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 { + 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(`"` + 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(blkCount) <= newestBlkHeight { + oldestBlkHeight = newestBlkHeight - uint64(blkCount) + 1 + } + + ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(newestBlkHeight), nil, false) + if err != nil { + return ethtypes.EthFeeHistory{}, fmt.Errorf("cannot load find block height: %v", newestBlkHeight) + } + + // 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. + baseFeeArray := []ethtypes.EthBigInt{ethtypes.EthBigInt(ts.Blocks()[0].ParentBaseFee)} + gasUsedRatioArray := []float64{} + + for ts.Height() >= abi.ChainEpoch(oldestBlkHeight) { + // Unfortunately we need to rebuild the full message view so we can + // totalize gas used in the tipset. + block, err := newEthBlockFromFilecoinTipSet(ctx, ts, false, a.Chain, a.ChainAPI, a.StateAPI) + if err != nil { + return ethtypes.EthFeeHistory{}, fmt.Errorf("cannot create eth block: %v", err) + } + + // both arrays should be reversed at the end + baseFeeArray = append(baseFeeArray, ethtypes.EthBigInt(ts.Blocks()[0].ParentBaseFee)) + gasUsedRatioArray = append(gasUsedRatioArray, float64(block.GasUsed)/float64(build.BlockGasLimit)) + + parentTsKey := ts.Parents() + ts, err = a.Chain.LoadTipSet(ctx, parentTsKey) + if err != nil { + return ethtypes.EthFeeHistory{}, fmt.Errorf("cannot load tipset key: %v", parentTsKey) + } + } + + // 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] + } + for i, j := 0, len(gasUsedRatioArray)-1; i < j; i, j = i+1, j-1 { + gasUsedRatioArray[i], gasUsedRatioArray[j] = gasUsedRatioArray[j], gasUsedRatioArray[i] + } + + return ethtypes.EthFeeHistory{ + OldestBlock: oldestBlkHeight, + BaseFeePerGas: baseFeeArray, + GasUsedRatio: gasUsedRatioArray, + }, nil +} + +func (a *EthModule) NetVersion(ctx context.Context) (string, error) { + // Note that networkId is not encoded in hex + nv, err := a.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return "", err + } + return strconv.FormatUint(uint64(nv), 10), nil +} + +func (a *EthModule) NetListening(ctx context.Context) (bool, error) { + return true, nil +} + +func (a *EthModule) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) { + height := a.Chain.GetHeaviestTipSet().Height() + return ethtypes.EthUint64(a.StateManager.GetNetworkVersion(ctx, height)), nil +} + +func (a *EthModule) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) { + gasPremium, err := a.GasAPI.GasEstimateGasPremium(ctx, 0, builtinactors.SystemActorAddr, 10000, types.EmptyTSK) + if err != nil { + return ethtypes.EthBigInt(big.Zero()), err + } + return ethtypes.EthBigInt(gasPremium), nil +} + +func (a *EthModule) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) { + // According to Geth's implementation, eth_gasPrice should return base + tip + // Ref: https://github.com/ethereum/pm/issues/328#issuecomment-853234014 + + ts := a.Chain.GetHeaviestTipSet() + baseFee := ts.Blocks()[0].ParentBaseFee + + premium, err := a.EthMaxPriorityFeePerGas(ctx) + if err != nil { + return ethtypes.EthBigInt(big.Zero()), nil + } + + gasPrice := big.Add(baseFee, big.Int(premium)) + return ethtypes.EthBigInt(gasPrice), nil +} + +func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { + txArgs, err := ethtypes.ParseEthTxArgs(rawTx) + if err != nil { + return ethtypes.EmptyEthHash, err + } + + smsg, err := txArgs.ToSignedMessage() + if err != nil { + return ethtypes.EmptyEthHash, err + } + + _, err = a.StateAPI.StateGetActor(ctx, smsg.Message.To, types.EmptyTSK) + if err != nil { + // if actor does not exist on chain yet, set the method to 0 because + // placeholders only implement method 0 + smsg.Message.Method = builtinactors.MethodSend + } + + _, err = a.MpoolAPI.MpoolPush(ctx, smsg) + if err != nil { + return ethtypes.EmptyEthHash, err + } + + return ethtypes.EthHashFromTxBytes(rawTx), nil +} + +func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.EthCall) (*types.Message, error) { + var from address.Address + if tx.From == nil || *tx.From == (ethtypes.EthAddress{}) { + // Send from the filecoin "system" address. + var err error + from, err = (ethtypes.EthAddress{}).ToFilecoinAddress() + if err != nil { + return nil, fmt.Errorf("failed to construct the ethereum system address: %w", err) + } + } else { + // The from address must be translatable to an f4 address. + var err error + from, err = tx.From.ToFilecoinAddress() + if err != nil { + return nil, fmt.Errorf("failed to translate sender address (%s): %w", tx.From.String(), err) + } + if p := from.Protocol(); p != address.Delegated { + return nil, fmt.Errorf("expected a class 4 address, got: %d: %w", p, err) + } + } + + var params []byte + var to address.Address + var method abi.MethodNum + if tx.To == nil { + // this is a contract creation + to = builtintypes.EthereumAddressManagerActorAddr + + initcode := abi.CborBytes(tx.Data) + params2, err := actors.SerializeParams(&initcode) + if err != nil { + return nil, fmt.Errorf("failed to serialize Create params: %w", err) + } + params = params2 + method = builtintypes.MethodsEAM.CreateExternal + } else { + addr, err := tx.To.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("cannot get Filecoin address: %w", err) + } + to = addr + + if len(tx.Data) > 0 { + var buf bytes.Buffer + if err := cbg.WriteByteArray(&buf, tx.Data); err != nil { + 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 + } + } + + return &types.Message{ + From: from, + To: to, + Value: big.Int(tx.Value), + Method: method, + Params: params, + GasLimit: build.BlockGasLimit, + GasFeeCap: big.Zero(), + GasPremium: big.Zero(), + }, nil +} + +func (a *EthModule) applyMessage(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) { + ts, err := a.Chain.GetTipSetFromKey(ctx, tsk) + if err != nil { + return nil, xerrors.Errorf("cannot get tipset: %w", err) + } + + // Try calling until we find a height with no migration. + for { + res, err = a.StateManager.CallWithGas(ctx, msg, []types.ChainMsg{}, ts) + if err != stmgr.ErrExpensiveFork { + break + } + ts, err = a.Chain.GetTipSetFromKey(ctx, ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("getting parent tipset: %w", err) + } + } + if err != nil { + return nil, xerrors.Errorf("CallWithGas failed: %w", err) + } + if res.MsgRct.ExitCode.IsError() { + return nil, xerrors.Errorf("message execution failed: exit %s, msg receipt: %s, reason: %s", res.MsgRct.ExitCode, res.MsgRct.Return, res.Error) + } + return res, nil +} + +func (a *EthModule) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { + msg, err := a.ethCallToFilecoinMessage(ctx, tx) + if err != nil { + return ethtypes.EthUint64(0), err + } + + // Set the gas limit to the zero sentinel value, which makes + // gas estimation actually run. + msg.GasLimit = 0 + + msg, err = a.GasAPI.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK) + if err != nil { + return ethtypes.EthUint64(0), err + } + + return ethtypes.EthUint64(msg.GasLimit), nil +} + +func (a *EthModule) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) { + msg, err := a.ethCallToFilecoinMessage(ctx, tx) + if err != nil { + return nil, err + } + ts, err := a.parseBlkParam(ctx, blkParam) + if err != nil { + return nil, xerrors.Errorf("cannot parse block param: %s", blkParam) + } + + invokeResult, err := a.applyMessage(ctx, msg, ts.Key()) + if err != nil { + return nil, err + } + if len(invokeResult.MsgRct.Return) > 0 { + return cbg.ReadByteArray(bytes.NewReader(invokeResult.MsgRct.Return), uint64(len(invokeResult.MsgRct.Return))) + } + return ethtypes.EthBytes{}, nil +} + +func (e *EthEvent) EthGetLogs(ctx context.Context, filterSpec *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + if e.EventFilterManager == nil { + return nil, api.ErrNotSupported + } + + // Create a temporary filter + f, err := e.installEthFilterSpec(ctx, filterSpec) + if err != nil { + return nil, err + } + ces := f.TakeCollectedEvents(ctx) + + _ = e.uninstallFilter(ctx, f) + + return ethFilterResultFromEvents(ces, e.SubManager.StateAPI) +} + +func (e *EthEvent) EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if e.FilterStore == nil { + return nil, api.ErrNotSupported + } + + f, err := e.FilterStore.Get(ctx, types.FilterID(id)) + if err != nil { + return nil, err + } + + switch fc := f.(type) { + case filterEventCollector: + return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.SubManager.StateAPI) + case filterTipSetCollector: + return ethFilterResultFromTipSets(fc.TakeCollectedTipSets(ctx)) + case filterMessageCollector: + return ethFilterResultFromMessages(fc.TakeCollectedMessages(ctx), e.SubManager.StateAPI) + } + + return nil, xerrors.Errorf("unknown filter type") +} + +func (e *EthEvent) EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if e.FilterStore == nil { + return nil, api.ErrNotSupported + } + + f, err := e.FilterStore.Get(ctx, types.FilterID(id)) + if err != nil { + return nil, err + } + + switch fc := f.(type) { + case filterEventCollector: + return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.SubManager.StateAPI) + } + + return nil, xerrors.Errorf("wrong filter type") +} + +func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtypes.EthFilterSpec) (*filter.EventFilter, error) { + var ( + minHeight abi.ChainEpoch + maxHeight abi.ChainEpoch + tipsetCid cid.Cid + addresses []address.Address + keys = map[string][][]byte{} + ) + + if filterSpec.BlockHash != nil { + if filterSpec.FromBlock != nil || filterSpec.ToBlock != nil { + return nil, xerrors.Errorf("must not specify block hash and from/to block") + } + + // TODO: derive a tipset hash from eth hash - might need to push this down into the EventFilterManager + } else { + if filterSpec.FromBlock == nil || *filterSpec.FromBlock == "latest" { + ts := e.Chain.GetHeaviestTipSet() + minHeight = ts.Height() + } else if *filterSpec.FromBlock == "earliest" { + minHeight = 0 + } else if *filterSpec.FromBlock == "pending" { + return nil, api.ErrNotSupported + } else { + epoch, err := ethtypes.EthUint64FromHex(*filterSpec.FromBlock) + if err != nil { + return nil, xerrors.Errorf("invalid epoch") + } + minHeight = abi.ChainEpoch(epoch) + } + + if filterSpec.ToBlock == nil || *filterSpec.ToBlock == "latest" { + // here latest means the latest at the time + maxHeight = -1 + } else if *filterSpec.ToBlock == "earliest" { + maxHeight = 0 + } else if *filterSpec.ToBlock == "pending" { + return nil, api.ErrNotSupported + } else { + epoch, err := ethtypes.EthUint64FromHex(*filterSpec.ToBlock) + if err != nil { + return nil, xerrors.Errorf("invalid epoch") + } + maxHeight = abi.ChainEpoch(epoch) + } + + // Validate height ranges are within limits set by node operator + if minHeight == -1 && maxHeight > 0 { + // Here the client is looking for events between the head and some future height + ts := e.Chain.GetHeaviestTipSet() + if maxHeight-ts.Height() > e.MaxFilterHeightRange { + return nil, xerrors.Errorf("invalid epoch range: to block is too far in the future (maximum: %d)", e.MaxFilterHeightRange) + } + } else if minHeight >= 0 && maxHeight == -1 { + // Here the client is looking for events between some time in the past and the current head + ts := e.Chain.GetHeaviestTipSet() + if ts.Height()-minHeight > e.MaxFilterHeightRange { + return nil, xerrors.Errorf("invalid epoch range: from block is too far in the past (maximum: %d)", e.MaxFilterHeightRange) + } + + } else if minHeight >= 0 && maxHeight >= 0 { + if minHeight > maxHeight { + return nil, xerrors.Errorf("invalid epoch range: to block (%d) must be after from block (%d)", minHeight, maxHeight) + } else if maxHeight-minHeight > e.MaxFilterHeightRange { + return nil, xerrors.Errorf("invalid epoch range: range between to and from blocks is too large (maximum: %d)", e.MaxFilterHeightRange) + } + } + + } + + // Convert all addresses to filecoin f4 addresses + for _, ea := range filterSpec.Address { + a, err := ea.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("invalid address %x", ea) + } + addresses = append(addresses, a) + } + + for idx, vals := range filterSpec.Topics { + if len(vals) == 0 { + continue + } + // Ethereum topics are emitted using `LOG{0..4}` opcodes resulting in topics1..4 + key := fmt.Sprintf("topic%d", idx+1) + for _, v := range vals { + buf := make([]byte, len(v[:])) + copy(buf, v[:]) + keys[key] = append(keys[key], buf) + } + } + + return e.EventFilterManager.Install(ctx, minHeight, maxHeight, tipsetCid, addresses, keys) +} + +func (e *EthEvent) EthNewFilter(ctx context.Context, filterSpec *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + if e.FilterStore == nil || e.EventFilterManager == nil { + return ethtypes.EthFilterID{}, api.ErrNotSupported + } + + f, err := e.installEthFilterSpec(ctx, filterSpec) + if err != nil { + return ethtypes.EthFilterID{}, err + } + + if err := e.FilterStore.Add(ctx, f); err != nil { + // Could not record in store, attempt to delete filter to clean up + err2 := e.TipSetFilterManager.Remove(ctx, f.ID()) + if err2 != nil { + return ethtypes.EthFilterID{}, xerrors.Errorf("encountered error %v while removing new filter due to %v", err2, err) + } + + return ethtypes.EthFilterID{}, err + } + + return ethtypes.EthFilterID(f.ID()), nil +} + +func (e *EthEvent) EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) { + if e.FilterStore == nil || e.TipSetFilterManager == nil { + return ethtypes.EthFilterID{}, api.ErrNotSupported + } + + f, err := e.TipSetFilterManager.Install(ctx) + if err != nil { + return ethtypes.EthFilterID{}, err + } + + if err := e.FilterStore.Add(ctx, f); err != nil { + // Could not record in store, attempt to delete filter to clean up + err2 := e.TipSetFilterManager.Remove(ctx, f.ID()) + if err2 != nil { + return ethtypes.EthFilterID{}, xerrors.Errorf("encountered error %v while removing new filter due to %v", err2, err) + } + + return ethtypes.EthFilterID{}, err + } + + return ethtypes.EthFilterID(f.ID()), nil +} + +func (e *EthEvent) EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) { + if e.FilterStore == nil || e.MemPoolFilterManager == nil { + return ethtypes.EthFilterID{}, api.ErrNotSupported + } + + f, err := e.MemPoolFilterManager.Install(ctx) + if err != nil { + return ethtypes.EthFilterID{}, err + } + + if err := e.FilterStore.Add(ctx, f); err != nil { + // Could not record in store, attempt to delete filter to clean up + err2 := e.MemPoolFilterManager.Remove(ctx, f.ID()) + if err2 != nil { + return ethtypes.EthFilterID{}, xerrors.Errorf("encountered error %v while removing new filter due to %v", err2, err) + } + + return ethtypes.EthFilterID{}, err + } + + return ethtypes.EthFilterID(f.ID()), nil +} + +func (e *EthEvent) EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) { + if e.FilterStore == nil { + return false, api.ErrNotSupported + } + + f, err := e.FilterStore.Get(ctx, types.FilterID(id)) + if err != nil { + if errors.Is(err, filter.ErrFilterNotFound) { + return false, nil + } + return false, err + } + + if err := e.uninstallFilter(ctx, f); err != nil { + return false, err + } + + return true, nil +} + +func (e *EthEvent) uninstallFilter(ctx context.Context, f filter.Filter) error { + switch f.(type) { + case *filter.EventFilter: + err := e.EventFilterManager.Remove(ctx, f.ID()) + if err != nil && !errors.Is(err, filter.ErrFilterNotFound) { + return err + } + case *filter.TipSetFilter: + err := e.TipSetFilterManager.Remove(ctx, f.ID()) + if err != nil && !errors.Is(err, filter.ErrFilterNotFound) { + return err + } + case *filter.MemPoolFilter: + err := e.MemPoolFilterManager.Remove(ctx, f.ID()) + if err != nil && !errors.Is(err, filter.ErrFilterNotFound) { + return err + } + default: + return xerrors.Errorf("unknown filter type") + } + + return e.FilterStore.Remove(ctx, f.ID()) +} + +const ( + EthSubscribeEventTypeHeads = "newHeads" + EthSubscribeEventTypeLogs = "logs" +) + +func (e *EthEvent) EthSubscribe(ctx context.Context, eventType string, params *ethtypes.EthSubscriptionParams) (<-chan ethtypes.EthSubscriptionResponse, error) { + if e.SubManager == nil { + return nil, api.ErrNotSupported + } + // Note that go-jsonrpc will set the method field of the response to "xrpc.ch.val" but the ethereum api expects the name of the + // method to be "eth_subscription". This probably doesn't matter in practice. + + sub, err := e.SubManager.StartSubscription(ctx) + if err != nil { + return nil, err + } + + switch eventType { + case EthSubscribeEventTypeHeads: + f, err := e.TipSetFilterManager.Install(ctx) + if err != nil { + // clean up any previous filters added and stop the sub + _, _ = e.EthUnsubscribe(ctx, sub.id) + return nil, err + } + sub.addFilter(ctx, f) + + case EthSubscribeEventTypeLogs: + keys := map[string][][]byte{} + if params != nil { + for idx, vals := range params.Topics { + // Ethereum topics are emitted using `LOG{0..4}` opcodes resulting in topics1..4 + key := fmt.Sprintf("topic%d", idx+1) + keyvals := make([][]byte, len(vals)) + for i, v := range vals { + keyvals[i] = v[:] + } + keys[key] = keyvals + } + } + + f, err := e.EventFilterManager.Install(ctx, -1, -1, cid.Undef, []address.Address{}, keys) + if err != nil { + // clean up any previous filters added and stop the sub + _, _ = e.EthUnsubscribe(ctx, sub.id) + return nil, err + } + sub.addFilter(ctx, f) + default: + return nil, xerrors.Errorf("unsupported event type: %s", eventType) + } + + return sub.out, nil +} + +func (e *EthEvent) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) { + if e.SubManager == nil { + return false, api.ErrNotSupported + } + + filters, err := e.SubManager.StopSubscription(ctx, id) + if err != nil { + return false, nil + } + + for _, f := range filters { + if err := e.uninstallFilter(ctx, f); err != nil { + // this will leave the filter a zombie, collecting events up to the maximum allowed + log.Warnf("failed to remove filter when unsubscribing: %v", err) + } + } + + return true, nil +} + +// GC runs a garbage collection loop, deleting filters that have not been used within the ttl window +func (e *EthEvent) GC(ctx context.Context, ttl time.Duration) { + if e.FilterStore == nil { + return + } + + tt := time.NewTicker(time.Minute * 30) + defer tt.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-tt.C: + fs := e.FilterStore.NotTakenSince(time.Now().Add(-ttl)) + for _, f := range fs { + if err := e.uninstallFilter(ctx, f); err != nil { + log.Warnf("Failed to remove actor event filter during garbage collection: %v", err) + } + } + } + } +} + +type filterEventCollector interface { + TakeCollectedEvents(context.Context) []*filter.CollectedEvent +} + +type filterMessageCollector interface { + TakeCollectedMessages(context.Context) []*types.SignedMessage +} + +type filterTipSetCollector interface { + TakeCollectedTipSets(context.Context) []types.TipSetKey +} + +func ethFilterResultFromEvents(evs []*filter.CollectedEvent, sa StateAPI) (*ethtypes.EthFilterResult, error) { + res := ðtypes.EthFilterResult{} + for _, ev := range evs { + log := ethtypes.EthLog{ + Removed: ev.Reverted, + LogIndex: ethtypes.EthUint64(ev.EventIdx), + TransactionIndex: ethtypes.EthUint64(ev.MsgIdx), + BlockNumber: ethtypes.EthUint64(ev.Height), + } + + var err error + + for _, entry := range ev.Entries { + value := ethtypes.EthBytes(leftpad32(entry.Value)) // value has already been cbor-decoded but see https://github.com/filecoin-project/ref-fvm/issues/1345 + if entry.Key == ethtypes.EthTopic1 || entry.Key == ethtypes.EthTopic2 || entry.Key == ethtypes.EthTopic3 || entry.Key == ethtypes.EthTopic4 { + log.Topics = append(log.Topics, value) + } else { + log.Data = value + } + } + + log.Address, err = ethtypes.EthAddressFromFilecoinAddress(ev.EmitterAddr) + if err != nil { + return nil, err + } + + log.TransactionHash, err = EthTxHashFromFilecoinMessageCid(context.TODO(), ev.MsgCid, sa) + if err != nil { + return nil, err + } + c, err := ev.TipSetKey.Cid() + if err != nil { + return nil, err + } + log.BlockHash, err = ethtypes.EthHashFromCid(c) + if err != nil { + return nil, err + } + + res.Results = append(res.Results, log) + } + + return res, nil +} + +func ethFilterResultFromTipSets(tsks []types.TipSetKey) (*ethtypes.EthFilterResult, error) { + res := ðtypes.EthFilterResult{} + + for _, tsk := range tsks { + c, err := tsk.Cid() + if err != nil { + return nil, err + } + hash, err := ethtypes.EthHashFromCid(c) + if err != nil { + return nil, err + } + + res.Results = append(res.Results, hash) + } + + return res, nil +} + +func ethFilterResultFromMessages(cs []*types.SignedMessage, sa StateAPI) (*ethtypes.EthFilterResult, error) { + res := ðtypes.EthFilterResult{} + + for _, c := range cs { + hash, err := EthTxHashFromSignedFilecoinMessage(context.TODO(), c, sa) + if err != nil { + return nil, err + } + + res.Results = append(res.Results, hash) + } + + return res, nil +} + +type EthSubscriptionManager struct { + Chain *store.ChainStore + StateAPI StateAPI + ChainAPI ChainAPI + mu sync.Mutex + subs map[ethtypes.EthSubscriptionID]*ethSubscription +} + +func (e *EthSubscriptionManager) StartSubscription(ctx context.Context) (*ethSubscription, error) { // nolint + rawid, err := uuid.NewRandom() + if err != nil { + return nil, xerrors.Errorf("new uuid: %w", err) + } + id := ethtypes.EthSubscriptionID{} + copy(id[:], rawid[:]) // uuid is 16 bytes + + ctx, quit := context.WithCancel(ctx) + + sub := ðSubscription{ + Chain: e.Chain, + StateAPI: e.StateAPI, + ChainAPI: e.ChainAPI, + id: id, + in: make(chan interface{}, 200), + out: make(chan ethtypes.EthSubscriptionResponse, 20), + quit: quit, + } + + e.mu.Lock() + if e.subs == nil { + e.subs = make(map[ethtypes.EthSubscriptionID]*ethSubscription) + } + e.subs[sub.id] = sub + e.mu.Unlock() + + go sub.start(ctx) + + return sub, nil +} + +func (e *EthSubscriptionManager) StopSubscription(ctx context.Context, id ethtypes.EthSubscriptionID) ([]filter.Filter, error) { + e.mu.Lock() + defer e.mu.Unlock() + + sub, ok := e.subs[id] + if !ok { + return nil, xerrors.Errorf("subscription not found") + } + sub.stop() + delete(e.subs, id) + + return sub.filters, nil +} + +type ethSubscription struct { + Chain *store.ChainStore + StateAPI StateAPI + ChainAPI ChainAPI + id ethtypes.EthSubscriptionID + in chan interface{} + out chan ethtypes.EthSubscriptionResponse + + mu sync.Mutex + filters []filter.Filter + quit func() +} + +func (e *ethSubscription) addFilter(ctx context.Context, f filter.Filter) { + e.mu.Lock() + defer e.mu.Unlock() + + f.SetSubChannel(e.in) + e.filters = append(e.filters, f) +} + +func (e *ethSubscription) start(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case v := <-e.in: + resp := ethtypes.EthSubscriptionResponse{ + SubscriptionID: e.id, + } + + var err error + switch vt := v.(type) { + case *filter.CollectedEvent: + resp.Result, err = ethFilterResultFromEvents([]*filter.CollectedEvent{vt}, e.StateAPI) + case *types.TipSet: + eb, err := newEthBlockFromFilecoinTipSet(ctx, vt, true, e.Chain, e.ChainAPI, e.StateAPI) + if err != nil { + break + } + + resp.Result = eb + default: + log.Warnf("unexpected subscription value type: %T", vt) + } + + if err != nil { + continue + } + + select { + case e.out <- resp: + default: + // Skip if client is not reading responses + } + } + } +} + +func (e *ethSubscription) stop() { + e.mu.Lock() + defer e.mu.Unlock() + + if e.quit != nil { + e.quit() + close(e.out) + e.quit = nil + } +} + +func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool, cs *store.ChainStore, ca ChainAPI, sa StateAPI) (ethtypes.EthBlock, error) { + parent, err := cs.LoadTipSet(ctx, ts.Parents()) + if err != nil { + return ethtypes.EthBlock{}, err + } + parentKeyCid, err := parent.Key().Cid() + if err != nil { + return ethtypes.EthBlock{}, err + } + parentBlkHash, err := ethtypes.EthHashFromCid(parentKeyCid) + if err != nil { + return ethtypes.EthBlock{}, err + } + + blkCid, err := ts.Key().Cid() + if err != nil { + return ethtypes.EthBlock{}, err + } + blkHash, err := ethtypes.EthHashFromCid(blkCid) + if err != nil { + return ethtypes.EthBlock{}, err + } + + msgs, err := cs.MessagesForTipset(ctx, ts) + if err != nil { + return ethtypes.EthBlock{}, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err) + } + + block := ethtypes.NewEthBlock() + + // this seems to be a very expensive way to get gasUsed of the block. may need to find an efficient way to do it + gasUsed := int64(0) + for txIdx, msg := range msgs { + msgLookup, err := sa.StateSearchMsg(ctx, types.EmptyTSK, msg.Cid(), api.LookbackNoLimit, false) + if err != nil || msgLookup == nil { + return ethtypes.EthBlock{}, nil + } + gasUsed += msgLookup.Receipt.GasUsed + + tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, txIdx, cs, sa) + if err != nil { + return ethtypes.EthBlock{}, nil + } + + if fullTxInfo { + block.Transactions = append(block.Transactions, tx) + } else { + block.Transactions = append(block.Transactions, tx.Hash.String()) + } + } + + block.Hash = blkHash + block.Number = ethtypes.EthUint64(ts.Height()) + block.ParentHash = parentBlkHash + block.Timestamp = ethtypes.EthUint64(ts.Blocks()[0].Timestamp) + block.BaseFeePerGas = ethtypes.EthBigInt{Int: ts.Blocks()[0].ParentBaseFee.Int} + block.GasUsed = ethtypes.EthUint64(gasUsed) + return block, nil +} + +// lookupEthAddress makes its best effort at finding the Ethereum address for a +// Filecoin address. It does the following: +// +// 1. If the supplied address is an f410 address, we return its payload as the EthAddress. +// 2. Otherwise (f0, f1, f2, f3), we look up the actor on the state tree. If it has a delegated address, we return it if it's f410 address. +// 3. Otherwise, we fall back to returning a masked ID Ethereum address. If the supplied address is an f0 address, we +// use that ID to form the masked ID address. +// 4. Otherwise, we fetch the actor's ID from the state tree and form the masked ID with it. +func lookupEthAddress(ctx context.Context, addr address.Address, sa StateAPI) (ethtypes.EthAddress, error) { + // BLOCK A: We are trying to get an actual Ethereum address from an f410 address. + // Attempt to convert directly, if it's an f4 address. + ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(addr) + if err == nil && !ethAddr.IsMaskedID() { + return ethAddr, nil + } + + // Lookup on the target actor and try to get an f410 address. + if actor, err := sa.StateGetActor(ctx, addr, types.EmptyTSK); err != nil { + return ethtypes.EthAddress{}, err + } else if actor.Address != nil { + if ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address); err == nil && !ethAddr.IsMaskedID() { + return ethAddr, nil + } + } + + // BLOCK B: We gave up on getting an actual Ethereum address and are falling back to a Masked ID address. + // Check if we already have an ID addr, and use it if possible. + if err == nil && ethAddr.IsMaskedID() { + return ethAddr, nil + } + + // Otherwise, resolve the ID addr. + idAddr, err := sa.StateLookupID(ctx, addr, types.EmptyTSK) + if err != nil { + return ethtypes.EthAddress{}, err + } + return ethtypes.EthAddressFromFilecoinAddress(idAddr) +} + +func EthTxHashFromFilecoinMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethtypes.EthHash, error) { + smsg, err := sa.Chain.GetSignedMessage(ctx, c) + if err == nil { + // This is an Eth Tx, Secp message, Or BLS message in the mpool + return EthTxHashFromSignedFilecoinMessage(ctx, smsg, sa) + } + + _, err = sa.Chain.GetMessage(ctx, c) + if err == nil { + // This is a BLS message + return ethtypes.EthHashFromCid(c) + } + + return ethtypes.EmptyEthHash, nil +} + +func EthTxHashFromSignedFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthHash, error) { + if smsg.Signature.Type == crypto.SigTypeDelegated { + ethTx, err := NewEthTxFromFilecoinMessage(ctx, smsg, sa) + if err != nil { + return ethtypes.EmptyEthHash, err + } + return ethTx.Hash, nil + } + + return ethtypes.EthHashFromCid(smsg.Cid()) +} + +func NewEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthTx, error) { + // Ignore errors here so we can still parse non-eth messages + fromEthAddr, _ := lookupEthAddress(ctx, smsg.Message.From, sa) + toEthAddr, _ := lookupEthAddress(ctx, smsg.Message.To, sa) + + toAddr := &toEthAddr + input := smsg.Message.Params + var err error + // Check to see if we need to decode as contract deployment. + // We don't need to resolve the to address, because there's only one form (an ID). + if smsg.Message.To == builtintypes.EthereumAddressManagerActorAddr { + switch smsg.Message.Method { + case builtintypes.MethodsEAM.Create: + toAddr = nil + var params eam.CreateParams + err = params.UnmarshalCBOR(bytes.NewReader(smsg.Message.Params)) + input = params.Initcode + case builtintypes.MethodsEAM.Create2: + toAddr = nil + var params eam.Create2Params + err = params.UnmarshalCBOR(bytes.NewReader(smsg.Message.Params)) + input = params.Initcode + case builtintypes.MethodsEAM.CreateExternal: + toAddr = nil + var params abi.CborBytes + err = params.UnmarshalCBOR(bytes.NewReader(smsg.Message.Params)) + input = []byte(params) + } + if err != nil { + return ethtypes.EthTx{}, err + } + } + // Otherwise, try to decode as a cbor byte array. + // TODO: Actually check if this is an ethereum call. This code will work for demo purposes, but is not correct. + if toAddr != nil { + if decodedParams, err := cbg.ReadByteArray(bytes.NewReader(smsg.Message.Params), uint64(len(smsg.Message.Params))); err == nil { + input = decodedParams + } + } + + r, s, v, err := ethtypes.RecoverSignature(smsg.Signature) + if err != nil { + // we don't want to return error if the message is not an Eth tx + r, s, v = ethtypes.EthBigIntZero, ethtypes.EthBigIntZero, ethtypes.EthBigIntZero + } + + tx := ethtypes.EthTx{ + Nonce: ethtypes.EthUint64(smsg.Message.Nonce), + ChainID: ethtypes.EthUint64(build.Eip155ChainId), + From: fromEthAddr, + To: toAddr, + Value: ethtypes.EthBigInt(smsg.Message.Value), + Type: ethtypes.EthUint64(2), + Input: input, + Gas: ethtypes.EthUint64(smsg.Message.GasLimit), + MaxFeePerGas: ethtypes.EthBigInt(smsg.Message.GasFeeCap), + MaxPriorityFeePerGas: ethtypes.EthBigInt(smsg.Message.GasPremium), + V: v, + R: r, + S: s, + } + + // This is an eth tx + if smsg.Signature.Type == crypto.SigTypeDelegated { + tx.Hash, err = tx.TxHash() + if err != nil { + return tx, err + } + } else if smsg.Signature.Type == crypto.SigTypeUnknown { // BLS Filecoin message + tx.Hash, err = ethtypes.EthHashFromCid(smsg.Message.Cid()) + if err != nil { + return tx, err + } + } else { // Secp Filecoin Message + tx.Hash, err = ethtypes.EthHashFromCid(smsg.Cid()) + if err != nil { + return tx, err + } + } + + return tx, nil +} + +// newEthTxFromFilecoinMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed +// into the function, it looksup the transaction index of the message in the tipset, otherwise it uses the txIdx passed into the +// function +func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, txIdx int, cs *store.ChainStore, sa StateAPI) (ethtypes.EthTx, error) { + if msgLookup == nil { + return ethtypes.EthTx{}, fmt.Errorf("msg does not exist") + } + + ts, err := cs.LoadTipSet(ctx, msgLookup.TipSet) + if err != nil { + return ethtypes.EthTx{}, err + } + + // This tx is located in the parent tipset + parentTs, err := cs.LoadTipSet(ctx, ts.Parents()) + if err != nil { + return ethtypes.EthTx{}, err + } + + parentTsCid, err := parentTs.Key().Cid() + if err != nil { + return ethtypes.EthTx{}, err + } + + // lookup the transactionIndex + if txIdx < 0 { + msgs, err := cs.MessagesForTipset(ctx, parentTs) + if err != nil { + return ethtypes.EthTx{}, err + } + for i, msg := range msgs { + if msg.Cid() == msgLookup.Message { + txIdx = i + break + } + } + if txIdx < 0 { + return ethtypes.EthTx{}, fmt.Errorf("cannot find the msg in the tipset") + } + } + + blkHash, err := ethtypes.EthHashFromCid(parentTsCid) + if err != nil { + return ethtypes.EthTx{}, err + } + + smsg, err := cs.GetSignedMessage(ctx, msgLookup.Message) + if err != nil { + // We couldn't find the signed message, it might be a BLS message, so search for a regular message. + msg, err := cs.GetMessage(ctx, msgLookup.Message) + if err != nil { + return ethtypes.EthTx{}, err + } + smsg = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeUnknown, + Data: nil, + }, + } + } + + tx, err := NewEthTxFromFilecoinMessage(ctx, smsg, sa) + if err != nil { + return ethtypes.EthTx{}, err + } + + var ( + bn = ethtypes.EthUint64(parentTs.Height()) + ti = ethtypes.EthUint64(txIdx) + ) + + tx.ChainID = ethtypes.EthUint64(build.Eip155ChainId) + tx.BlockHash = &blkHash + tx.BlockNumber = &bn + tx.TransactionIndex = &ti + return tx, nil +} + +func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLookup, replay *api.InvocResult, events []types.Event, sa StateAPI) (api.EthTxReceipt, error) { + var ( + transactionIndex ethtypes.EthUint64 + blockHash ethtypes.EthHash + blockNumber ethtypes.EthUint64 + ) + + if tx.TransactionIndex != nil { + transactionIndex = *tx.TransactionIndex + } + if tx.BlockHash != nil { + blockHash = *tx.BlockHash + } + if tx.BlockNumber != nil { + blockNumber = *tx.BlockNumber + } + + receipt := api.EthTxReceipt{ + TransactionHash: tx.Hash, + From: tx.From, + To: tx.To, + TransactionIndex: transactionIndex, + BlockHash: blockHash, + BlockNumber: blockNumber, + Type: ethtypes.EthUint64(2), + Logs: []ethtypes.EthLog{}, // empty log array is compulsory when no logs, or libraries like ethers.js break + LogsBloom: ethtypes.EmptyEthBloom[:], + } + + if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() { + // Create and Create2 return the same things. + var ret eam.CreateReturn + if err := ret.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil { + return api.EthTxReceipt{}, xerrors.Errorf("failed to parse contract creation result: %w", err) + } + addr := ethtypes.EthAddress(ret.EthAddress) + receipt.ContractAddress = &addr + } + + if lookup.Receipt.ExitCode.IsSuccess() { + receipt.Status = 1 + } + if lookup.Receipt.ExitCode.IsError() { + receipt.Status = 0 + } + + if len(events) > 0 { + // TODO return a dummy non-zero bloom to signal that there are logs + // need to figure out how worth it is to populate with a real bloom + // should be feasible here since we are iterating over the logs anyway + receipt.LogsBloom[255] = 0x01 + + receipt.Logs = make([]ethtypes.EthLog, 0, len(events)) + for i, evt := range events { + l := ethtypes.EthLog{ + Removed: false, + LogIndex: ethtypes.EthUint64(i), + TransactionHash: tx.Hash, + TransactionIndex: transactionIndex, + BlockHash: blockHash, + BlockNumber: blockNumber, + } + + for _, entry := range evt.Entries { + value := ethtypes.EthBytes(leftpad32(entry.Value)) // value has already been cbor-decoded but see https://github.com/filecoin-project/ref-fvm/issues/1345 + if entry.Key == ethtypes.EthTopic1 || entry.Key == ethtypes.EthTopic2 || entry.Key == ethtypes.EthTopic3 || entry.Key == ethtypes.EthTopic4 { + l.Topics = append(l.Topics, value) + } else { + l.Data = value + } + } + + addr, err := address.NewIDAddress(uint64(evt.Emitter)) + if err != nil { + return api.EthTxReceipt{}, xerrors.Errorf("failed to create ID address: %w", err) + } + + l.Address, err = lookupEthAddress(ctx, addr, sa) + if err != nil { + return api.EthTxReceipt{}, xerrors.Errorf("failed to resolve Ethereum address: %w", err) + } + + receipt.Logs = append(receipt.Logs, l) + } + } + + receipt.GasUsed = ethtypes.EthUint64(lookup.Receipt.GasUsed) + + // TODO: handle CumulativeGasUsed + receipt.CumulativeGasUsed = ethtypes.EmptyEthInt + + effectiveGasPrice := big.Div(replay.GasCost.TotalCost, big.NewInt(lookup.Receipt.GasUsed)) + receipt.EffectiveGasPrice = ethtypes.EthBigInt(effectiveGasPrice) + + return receipt, nil +} + +func (m *EthTxHashManager) Apply(ctx context.Context, from, to *types.TipSet) error { + for _, blk := range to.Blocks() { + _, smsgs, err := m.StateAPI.Chain.MessagesForBlock(ctx, blk) + if err != nil { + return err + } + + for _, smsg := range smsgs { + if smsg.Signature.Type != crypto.SigTypeDelegated { + continue + } + + hash, err := EthTxHashFromSignedFilecoinMessage(ctx, smsg, m.StateAPI) + if err != nil { + return err + } + + err = m.TransactionHashLookup.UpsertHash(hash, smsg.Cid()) + if err != nil { + return err + } + } + } + + return nil +} + +type EthTxHashManager struct { + StateAPI StateAPI + TransactionHashLookup *ethhashlookup.EthTxHashLookup +} + +func (m *EthTxHashManager) Revert(ctx context.Context, from, to *types.TipSet) error { + return nil +} + +func WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate, manager *EthTxHashManager) { + for { + select { + case <-ctx.Done(): + return + case u := <-ch: + if u.Type != api.MpoolAdd { + continue + } + if u.Message.Signature.Type != crypto.SigTypeDelegated { + continue + } + + ethTx, err := NewEthTxFromFilecoinMessage(ctx, u.Message, manager.StateAPI) + if err != nil { + log.Errorf("error converting filecoin message to eth tx: %s", err) + } + + err = manager.TransactionHashLookup.UpsertHash(ethTx.Hash, u.Message.Cid()) + if err != nil { + log.Errorf("error inserting tx mapping to db: %s", err) + } + } + } +} + +func EthTxHashGC(ctx context.Context, retentionDays int, manager *EthTxHashManager) { + if retentionDays == 0 { + return + } + + gcPeriod := 1 * time.Hour + for { + entriesDeleted, err := manager.TransactionHashLookup.DeleteEntriesOlderThan(retentionDays) + if err != nil { + log.Errorf("error garbage collecting eth transaction hash database: %s", err) + } + log.Info("garbage collection run on eth transaction hash lookup database. %d entries deleted", entriesDeleted) + time.Sleep(gcPeriod) + } +} + +// TODO we could also emit full EVM words from the EVM runtime, but not doing so +// makes the contract slightly cheaper (and saves storage bytes), at the expense +// of having to left pad in the API, which is a pretty acceptable tradeoff at +// face value. There may be other protocol implications to consider. +func leftpad32(orig []byte) []byte { + needed := 32 - len(orig) + if needed <= 0 { + return orig + } + ret := make([]byte, 32) + copy(ret[needed:], orig) + return ret +} diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index 8cbe9ea1c..435e2c65b 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -261,7 +261,7 @@ func gasEstimateGasLimit( msg.GasFeeCap = big.Zero() msg.GasPremium = big.Zero() - fromA, err := smgr.ResolveToKeyAddress(ctx, msgIn.From, currTs) + fromA, err := smgr.ResolveToDeterministicAddress(ctx, msgIn.From, currTs) if err != nil { return -1, xerrors.Errorf("getting key address: %w", err) } @@ -323,7 +323,7 @@ func gasEstimateGasLimit( transitionalMulti = 4.095 case 7: // skip, stay at 2.0 - //transitionalMulti = 1.289 + // transitionalMulti = 1.289 case 11: transitionalMulti = 17.8758 case 16: diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 31d134dac..addcc41be 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -175,7 +175,7 @@ func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spe } } - fromA, err := a.Stmgr.ResolveToKeyAddress(ctx, msg.From, nil) + fromA, err := a.Stmgr.ResolveToDeterministicAddress(ctx, msg.From, nil) if err != nil { return nil, xerrors.Errorf("getting key address: %w", err) } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 91ffd3b9e..e6142a36f 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "strconv" @@ -507,7 +508,7 @@ func (m *StateModule) StateAccountKey(ctx context.Context, addr address.Address, return address.Undef, xerrors.Errorf("loading tipset %s: %w", tsk, err) } - return m.StateManager.ResolveToKeyAddress(ctx, addr, ts) + return m.StateManager.ResolveToDeterministicAddress(ctx, addr, ts) } func (a *StateAPI) StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error) { @@ -615,16 +616,22 @@ func (m *StateModule) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence vmsg := cmsg.VMMessage() - t, err := stmgr.GetReturnType(ctx, m.StateManager, vmsg.To, vmsg.Method, ts) - if err != nil { + switch t, err := stmgr.GetReturnType(ctx, m.StateManager, vmsg.To, vmsg.Method, ts); { + case errors.Is(err, stmgr.ErrMetadataNotFound): + // This is not necessarily an error -- EVM methods (and in the future native actors) may + // return just bytes, and in the not so distant future we'll have native wasm actors + // that are by definition not in the registry. + // So in this case, log a debug message and retun the raw bytes. + log.Debugf("failed to get return type: %s", err) + returndec = recpt.Return + case err != nil: return nil, xerrors.Errorf("failed to get return type: %w", err) + default: + if err := t.UnmarshalCBOR(bytes.NewReader(recpt.Return)); err != nil { + return nil, err + } + returndec = t } - - if err := t.UnmarshalCBOR(bytes.NewReader(recpt.Return)); err != nil { - return nil, err - } - - returndec = t } return &api.MsgLookup{ @@ -1800,6 +1807,7 @@ func (a *StateAPI) StateGetNetworkParams(ctx context.Context) (*api.NetworkParam UpgradeOhSnapHeight: build.UpgradeOhSnapHeight, UpgradeSkyrHeight: build.UpgradeSkyrHeight, UpgradeSharkHeight: build.UpgradeSharkHeight, + UpgradeHyggeHeight: build.UpgradeHyggeHeight, }, }, nil } diff --git a/node/impl/full/wallet.go b/node/impl/full/wallet.go index ae2550d77..0927e5aca 100644 --- a/node/impl/full/wallet.go +++ b/node/impl/full/wallet.go @@ -11,9 +11,11 @@ import ( "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/messagesigner" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/chain/wallet/key" "github.com/filecoin-project/lotus/lib/sigs" ) @@ -36,7 +38,7 @@ func (a *WalletAPI) WalletBalance(ctx context.Context, addr address.Address) (ty } func (a *WalletAPI) WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) { - keyAddr, err := a.StateManagerAPI.ResolveToKeyAddress(ctx, k, nil) + keyAddr, err := a.StateManagerAPI.ResolveToDeterministicAddress(ctx, k, nil) if err != nil { return nil, xerrors.Errorf("failed to resolve ID address: %w", keyAddr) } @@ -46,17 +48,25 @@ func (a *WalletAPI) WalletSign(ctx context.Context, k address.Address, msg []byt } func (a *WalletAPI) WalletSignMessage(ctx context.Context, k address.Address, msg *types.Message) (*types.SignedMessage, error) { - keyAddr, err := a.StateManagerAPI.ResolveToKeyAddress(ctx, k, nil) + keyAddr, err := a.StateManagerAPI.ResolveToDeterministicAddress(ctx, k, nil) if err != nil { return nil, xerrors.Errorf("failed to resolve ID address: %w", keyAddr) } + keyInfo, err := a.Wallet.WalletExport(ctx, k) + if err != nil { + return nil, err + } + sb, err := messagesigner.SigningBytes(msg, key.ActSigType(keyInfo.Type)) + if err != nil { + return nil, err + } mb, err := msg.ToStorageBlock() if err != nil { return nil, xerrors.Errorf("serializing message: %w", err) } - sig, err := a.Wallet.WalletSign(ctx, keyAddr, mb.Cid().Bytes(), api.MsgMeta{ + sig, err := a.Wallet.WalletSign(ctx, keyAddr, sb, api.MsgMeta{ Type: api.MTChainMsg, Extra: mb.RawData(), }) diff --git a/node/modules/actorevent.go b/node/modules/actorevent.go new file mode 100644 index 000000000..eb5afb8e6 --- /dev/null +++ b/node/modules/actorevent.go @@ -0,0 +1,152 @@ +package modules + +import ( + "context" + "path/filepath" + "time" + + "github.com/multiformats/go-varint" + "go.uber.org/fx" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + + "github.com/filecoin-project/lotus/chain/events" + "github.com/filecoin-project/lotus/chain/events/filter" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/impl/full" + "github.com/filecoin-project/lotus/node/modules/helpers" + "github.com/filecoin-project/lotus/node/repo" +) + +type EventAPI struct { + fx.In + + full.ChainAPI + full.StateAPI +} + +var _ events.EventAPI = &EventAPI{} + +func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEvent, 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) (*full.EthEvent, error) { + ctx := helpers.LifecycleCtx(mctx, lc) + + ee := &full.EthEvent{ + Chain: cs, + MaxFilterHeightRange: abi.ChainEpoch(cfg.Events.MaxFilterHeightRange), + } + + if !cfg.EnableEthRPC || cfg.Events.DisableRealTimeFilterAPI { + // all event functionality is disabled + // the historic filter API relies on the real time one + return ee, nil + } + + ee.SubManager = &full.EthSubscriptionManager{ + Chain: cs, + StateAPI: stateapi, + ChainAPI: chainapi, + } + ee.FilterStore = filter.NewMemFilterStore(cfg.Events.MaxFilters) + + // Start garbage collection for filters + lc.Append(fx.Hook{ + OnStart: func(context.Context) error { + go ee.GC(ctx, time.Duration(cfg.Events.FilterTTL)) + return nil + }, + }) + + // Enable indexing of actor events + var eventIndex *filter.EventIndex + if !cfg.Events.DisableHistoricFilterAPI { + var dbPath string + if cfg.Events.DatabasePath == "" { + sqlitePath, err := r.SqlitePath() + if err != nil { + return nil, err + } + dbPath = filepath.Join(sqlitePath, "events.db") + } else { + dbPath = cfg.Events.DatabasePath + } + + var err error + eventIndex, err = filter.NewEventIndex(dbPath) + if err != nil { + return nil, err + } + + lc.Append(fx.Hook{ + OnStop: func(context.Context) error { + return eventIndex.Close() + }, + }) + } + + ee.EventFilterManager = &filter.EventFilterManager{ + ChainStore: cs, + EventIndex: eventIndex, // will be nil unless EnableHistoricFilterAPI is true + AddressResolver: func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { + // we only want to match using f4 addresses + idAddr, err := address.NewIDAddress(uint64(emitter)) + if err != nil { + return address.Undef, false + } + + actor, err := sm.LoadActor(ctx, idAddr, ts) + if err != nil || actor.Address == nil { + return address.Undef, false + } + + // if robust address is not f4 then we won't match against it so bail early + if actor.Address.Protocol() != address.Delegated { + return address.Undef, false + } + // we have an f4 address, make sure it's assigned by the EAM + if namespace, _, err := varint.FromUvarint(actor.Address.Payload()); err != nil || namespace != builtintypes.EthereumAddressManagerActorID { + return address.Undef, false + } + return *actor.Address, true + }, + + MaxFilterResults: cfg.Events.MaxFilterResults, + } + ee.TipSetFilterManager = &filter.TipSetFilterManager{ + MaxFilterResults: cfg.Events.MaxFilterResults, + } + ee.MemPoolFilterManager = &filter.MemPoolFilterManager{ + MaxFilterResults: cfg.Events.MaxFilterResults, + } + + const ChainHeadConfidence = 1 + + lc.Append(fx.Hook{ + OnStart: func(context.Context) error { + ev, err := events.NewEventsWithConfidence(ctx, &evapi, ChainHeadConfidence) + if err != nil { + return err + } + // ignore returned tipsets + _ = ev.Observe(ee.EventFilterManager) + _ = ev.Observe(ee.TipSetFilterManager) + + ch, err := mp.Updates(ctx) + if err != nil { + return err + } + go ee.MemPoolFilterManager.WaitForMpoolUpdates(ctx, ch) + + return nil + }, + }) + + return ee, nil + } +} diff --git a/node/modules/ethmodule.go b/node/modules/ethmodule.go new file mode 100644 index 000000000..9889233f4 --- /dev/null +++ b/node/modules/ethmodule.go @@ -0,0 +1,79 @@ +package modules + +import ( + "context" + "path/filepath" + + "go.uber.org/fx" + + "github.com/filecoin-project/lotus/chain/ethhashlookup" + "github.com/filecoin-project/lotus/chain/events" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/impl/full" + "github.com/filecoin-project/lotus/node/modules/helpers" + "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) { + sqlitePath, err := r.SqlitePath() + if err != nil { + return nil, err + } + + transactionHashLookup, err := ethhashlookup.NewTransactionHashLookup(filepath.Join(sqlitePath, "txhash.db")) + if err != nil { + return nil, err + } + + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return transactionHashLookup.Close() + }, + }) + + ethTxHashManager := full.EthTxHashManager{ + StateAPI: stateapi, + TransactionHashLookup: transactionHashLookup, + } + + const ChainHeadConfidence = 1 + + ctx := helpers.LifecycleCtx(mctx, lc) + lc.Append(fx.Hook{ + OnStart: func(context.Context) error { + ev, err := events.NewEventsWithConfidence(ctx, &evapi, ChainHeadConfidence) + if err != nil { + return err + } + + // Tipset listener + _ = ev.Observe(ðTxHashManager) + + ch, err := mp.Updates(ctx) + if err != nil { + return err + } + go full.WaitForMpoolUpdates(ctx, ch, ðTxHashManager) + go full.EthTxHashGC(ctx, cfg.EthTxHashMappingLifetimeDays, ðTxHashManager) + + return nil + }, + }) + + return &full.EthModule{ + Chain: cs, + Mpool: mp, + StateManager: sm, + + ChainAPI: chainapi, + MpoolAPI: mpoolapi, + StateAPI: stateapi, + + EthTxHashManager: ðTxHashManager, + }, nil + } +} diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index 68550e389..bd387babf 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -37,6 +37,7 @@ const ( fsDatastore = "datastore" fsLock = "repo.lock" fsKeystore = "keystore" + fsSqlite = "sqlite" ) func NewRepoTypeFromString(t string) RepoType { @@ -411,6 +412,10 @@ type fsLockedRepo struct { ssErr error ssOnce sync.Once + sqlPath string + sqlErr error + sqlOnce sync.Once + storageLk sync.Mutex configLk sync.Mutex } @@ -515,6 +520,21 @@ func (fsr *fsLockedRepo) SplitstorePath() (string, error) { return fsr.ssPath, fsr.ssErr } +func (fsr *fsLockedRepo) SqlitePath() (string, error) { + fsr.sqlOnce.Do(func() { + path := fsr.join(fsSqlite) + + if err := os.MkdirAll(path, 0755); err != nil { + fsr.sqlErr = err + return + } + + fsr.sqlPath = path + }) + + return fsr.sqlPath, fsr.sqlErr +} + // join joins path elements with fsr.path func (fsr *fsLockedRepo) join(paths ...string) string { return filepath.Join(append([]string{fsr.path}, paths...)...) diff --git a/node/repo/interface.go b/node/repo/interface.go index dd0839559..328862b92 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -69,6 +69,9 @@ type LockedRepo interface { // SplitstorePath returns the path for the SplitStore SplitstorePath() (string, error) + // SqlitePath returns the path for the Sqlite database + SqlitePath() (string, error) + // Returns config in this repo Config() (interface{}, error) SetConfig(func(interface{})) error diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 61d960872..7817776a9 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -277,6 +277,14 @@ func (lmem *lockedMemRepo) SplitstorePath() (string, error) { return splitstorePath, nil } +func (lmem *lockedMemRepo) SqlitePath() (string, error) { + sqlitePath := filepath.Join(lmem.Path(), "sqlite") + if err := os.MkdirAll(sqlitePath, 0755); err != nil { + return "", err + } + return sqlitePath, nil +} + func (lmem *lockedMemRepo) ListDatastores(ns string) ([]int64, error) { return nil, nil } diff --git a/node/rpc.go b/node/rpc.go index 28ea67eb5..1dab2a61f 100644 --- a/node/rpc.go +++ b/node/rpc.go @@ -79,6 +79,8 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server rpcServer.Register("Filecoin", hnd) rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") + api.CreateEthRPCAliases(rpcServer) + var handler http.Handler = rpcServer if permissioned { handler = &auth.Handler{Verify: a.AuthVerify, Next: rpcServer.ServeHTTP} @@ -106,7 +108,6 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server Next: handleImportFunc, } m.Handle("/rest/v0/import", importAH) - exportAH := &auth.Handler{ Verify: a.AuthVerify, Next: handleExportFunc, diff --git a/paychmgr/manager.go b/paychmgr/manager.go index fda9b101f..b1b6a7517 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -28,7 +28,7 @@ var errProofNotSupported = errors.New("payment channel proof parameter is not su // stateManagerAPI defines the methods needed from StateManager type stateManagerAPI interface { - ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) + ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) } diff --git a/paychmgr/mock_test.go b/paychmgr/mock_test.go index 739bae25a..5d36e60f0 100644 --- a/paychmgr/mock_test.go +++ b/paychmgr/mock_test.go @@ -63,7 +63,7 @@ func (sm *mockStateManager) setPaychState(a address.Address, actor *types.Actor, sm.paychState[a] = mockPchState{actor, state} } -func (sm *mockStateManager) ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { +func (sm *mockStateManager) ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { sm.lk.Lock() defer sm.lk.Unlock() keyAddr, ok := sm.accountState[addr] diff --git a/paychmgr/paych.go b/paychmgr/paych.go index c683aaadd..1eb496dba 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -211,7 +211,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add return nil, err } - from, err := ca.api.ResolveToKeyAddress(ctx, f, nil) + from, err := ca.api.ResolveToDeterministicAddress(ctx, f, nil) if err != nil { return nil, err } diff --git a/paychmgr/state.go b/paychmgr/state.go index 65963d2a0..0466d2d36 100644 --- a/paychmgr/state.go +++ b/paychmgr/state.go @@ -28,7 +28,7 @@ func (ca *stateAccessor) loadStateChannelInfo(ctx context.Context, ch address.Ad if err != nil { return nil, err } - from, err := ca.sm.ResolveToKeyAddress(ctx, f, nil) + from, err := ca.sm.ResolveToDeterministicAddress(ctx, f, nil) if err != nil { return nil, err } @@ -36,7 +36,7 @@ func (ca *stateAccessor) loadStateChannelInfo(ctx context.Context, ch address.Ad if err != nil { return nil, err } - to, err := ca.sm.ResolveToKeyAddress(ctx, t, nil) + to, err := ca.sm.ResolveToDeterministicAddress(ctx, t, nil) if err != nil { return nil, err }