diff --git a/.circleci/config.yml b/.circleci/config.yml index 292f778fc4f..bfa65b95100 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -726,6 +726,46 @@ workflows: suite: itest-dup_mpool_messages target: "./itests/dup_mpool_messages_test.go" + - test: + name: test-itest-eth_account_abstraction + suite: itest-eth_account_abstraction + target: "./itests/eth_account_abstraction_test.go" + + - test: + name: test-itest-eth_balance + suite: itest-eth_balance + target: "./itests/eth_balance_test.go" + + - test: + name: test-itest-eth_deploy + suite: itest-eth_deploy + target: "./itests/eth_deploy_test.go" + + - test: + name: test-itest-eth_filter + suite: itest-eth_filter + target: "./itests/eth_filter_test.go" + + - test: + name: test-itest-eth_transactions + suite: itest-eth_transactions + target: "./itests/eth_transactions_test.go" + + - test: + name: test-itest-fevm_address + suite: itest-fevm_address + target: "./itests/fevm_address_test.go" + + - test: + name: test-itest-fevm_events + suite: itest-fevm_events + target: "./itests/fevm_events_test.go" + + - test: + name: test-itest-fevm + suite: itest-fevm + target: "./itests/fevm_test.go" + - test: name: test-itest-gas_estimation suite: itest-gas_estimation @@ -761,6 +801,11 @@ workflows: suite: itest-migration_nv17 target: "./itests/migration_nv17_test.go" + - test: + name: test-itest-migration_nv18 + suite: itest-migration_nv18 + target: "./itests/migration_nv18_test.go" + - test: name: test-itest-mpool_msg_uuid suite: itest-mpool_msg_uuid diff --git a/Makefile b/Makefile index 43c362b86d4..c085faafba5 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,12 @@ butterflynet: build-devnets interopnet: GOFLAGS+=-tags=interopnet interopnet: build-devnets +wallabynet: GOFLAGS+=-tags=wallabynet +wallabynet: build-devnets + +hyperspacenet: GOFLAGS+=-tags=hyperspacenet +hyperspacenet: build-devnets + lotus: $(BUILD_DEPS) rm -f lotus $(GOCC) build $(GOFLAGS) -o lotus ./cmd/lotus diff --git a/api/api_full.go b/api/api_full.go index 0c281c12d65..41fbe6b3ef3 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" ) @@ -151,7 +152,7 @@ type FullNode interface { // ChainGetPath returns a set of revert/apply operations needed to get from // one tipset to another, for example: - //``` + // ``` // to // ^ // from tAA @@ -160,7 +161,7 @@ type FullNode interface { // ^---*--^ // ^ // tRR - //``` + // ``` // Would return `[revert(tBA), apply(tAB), apply(tAA)]` ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*HeadChange, error) //perm:read @@ -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 @@ -388,12 +392,12 @@ type FullNode interface { ClientCancelRetrievalDeal(ctx context.Context, dealid retrievalmarket.DealID) error //perm:write // ClientUnimport removes references to the specified file from filestore - //ClientUnimport(path string) + // ClientUnimport(path string) // ClientListImports lists imported files and their root CIDs ClientListImports(ctx context.Context) ([]Import, error) //perm:write - //ClientListAsks() []Ask + // ClientListAsks() []Ask // MethodGroup: State // The State methods are used to query, inspect, and interact with chain state. @@ -640,14 +644,14 @@ type FullNode interface { // It takes the following params: , , MsigGetVested(context.Context, address.Address, types.TipSetKey, types.TipSetKey) (types.BigInt, error) //perm:read - //MsigGetPending returns pending transactions for the given multisig - //wallet. Once pending transactions are fully approved, they will no longer - //appear here. + // MsigGetPending returns pending transactions for the given multisig + // wallet. Once pending transactions are fully approved, they will no longer + // appear here. MsigGetPending(context.Context, address.Address, types.TipSetKey) ([]*MsigTransaction, error) //perm:read // MsigCreate creates a multisig wallet // It takes the following params: , , - //, , + // , , MsigCreate(context.Context, uint64, []address.Address, abi.ChainEpoch, types.BigInt, address.Address, types.BigInt) (*MessagePrototype, error) //perm:sign // MsigPropose proposes a multisig message @@ -759,6 +763,77 @@ 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 + 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 +1327,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_storage.go b/api/api_storage.go index 05120678741..0c00b9b933a 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 e8c667770b0..606ae9d8e22 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/mocks/mock_full.go b/api/mocks/mock_full.go index b5bf2bfead3..10ff250e6da 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,471 @@ 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) +} + +// 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) +} + +// 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 +2324,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 +2471,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 14d5c999d2d..d308533e947 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,68 @@ 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"` + + 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"` + + 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 +371,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"` @@ -687,7 +756,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 +1336,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 +1875,347 @@ 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) 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) 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 +2700,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 @@ -4182,15 +4625,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 5cbe0edef5c..e6790343688 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/v0api/full.go b/api/v0api/full.go index ca137179410..87d5cba4e99 100644 --- a/api/v0api/full.go +++ b/api/v0api/full.go @@ -141,7 +141,7 @@ type FullNode interface { // ChainGetPath returns a set of revert/apply operations needed to get from // one tipset to another, for example: - //``` + // ``` // to // ^ // from tAA @@ -150,7 +150,7 @@ type FullNode interface { // ^---*--^ // ^ // tRR - //``` + // ``` // Would return `[revert(tBA), apply(tAB), apply(tAA)]` ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*api.HeadChange, error) //perm:read @@ -367,12 +367,12 @@ type FullNode interface { ClientCancelRetrievalDeal(ctx context.Context, dealid retrievalmarket.DealID) error //perm:write // ClientUnimport removes references to the specified file from filestore - //ClientUnimport(path string) + // ClientUnimport(path string) // ClientListImports lists imported files and their root CIDs ClientListImports(ctx context.Context) ([]api.Import, error) //perm:write - //ClientListAsks() []Ask + // ClientListAsks() []Ask // MethodGroup: State // The State methods are used to query, inspect, and interact with chain state. @@ -641,14 +641,14 @@ type FullNode interface { // It takes the following params: , , MsigGetVested(context.Context, address.Address, types.TipSetKey, types.TipSetKey) (types.BigInt, error) //perm:read - //MsigGetPending returns pending transactions for the given multisig - //wallet. Once pending transactions are fully approved, they will no longer - //appear here. + // MsigGetPending returns pending transactions for the given multisig + // wallet. Once pending transactions are fully approved, they will no longer + // appear here. MsigGetPending(context.Context, address.Address, types.TipSetKey) ([]*api.MsigTransaction, error) //perm:read // MsigCreate creates a multisig wallet // It takes the following params: , , - //, , + // , , MsigCreate(context.Context, uint64, []address.Address, abi.ChainEpoch, types.BigInt, address.Address, types.BigInt) (cid.Cid, error) //perm:sign // MsigPropose proposes a multisig message // It takes the following params: , , , diff --git a/blockstore/splitstore/splitstore_compact.go b/blockstore/splitstore/splitstore_compact.go index 1c4c903ffd8..6ffb3572652 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/pack.sh b/build/actors/pack.sh index c2060e67c1a..2702c80d547 100755 --- a/build/actors/pack.sh +++ b/build/actors/pack.sh @@ -1,6 +1,6 @@ #!/bin/bash -NETWORKS=(devnet mainnet caterpillarnet butterflynet testing testing-fake-proofs calibrationnet) +NETWORKS=(devnet mainnet caterpillarnet butterflynet testing testing-fake-proofs calibrationnet hyperspace) set -e diff --git a/build/actors/v10.tar.zst b/build/actors/v10.tar.zst index f5644f4749c..5d6f1667809 100644 Binary files a/build/actors/v10.tar.zst and b/build/actors/v10.tar.zst differ diff --git a/build/bootstrap/wallabynet.pi b/build/bootstrap/wallabynet.pi new file mode 100644 index 00000000000..322e550bbab --- /dev/null +++ b/build/bootstrap/wallabynet.pi @@ -0,0 +1,4 @@ +/dns4/de0.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWHAvUVk5XuxSwi2dNLWbTDDRSGeHxMuWdQ3SQpRuNHbLz +/dns4/de1.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWBRqtxhJCtiLmCwKgAQozJtdGinEDdJGoS5oHw7vCjMGc +/dns4/ca0.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWCApBpUk7EX9pmEfyky1gKC6N2KJ74S1AwFfvnkDqw3pK +/dns4/sg0.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWLnYqr4hRoNHBJQVXsFGkDoKuoVfw5R2ASw1bHzrWU5Px \ No newline at end of file diff --git a/build/builtin_actors_gen.go b/build/builtin_actors_gen.go index fa10798ef64..55d3b0aa750 100644 --- a/build/builtin_actors_gen.go +++ b/build/builtin_actors_gen.go @@ -44,23 +44,24 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "butterflynet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaceciz4ytt5gnn6gc4epez7v6xeg6efkgbvwfxkoa34o2gj3hp5f7zc"), + ManifestCid: MustParseCid("bafy2bzaceav7txndea2xt6kvaosokp42vyjkhtplbb67tpkov3jbsvbwplnz4"), 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("bafk2bzacec34ox7drngorgal3ujxat2a2dlsh7eissgiisrv2uubuub2avbe2"), + "cron": MustParseCid("bafk2bzaceb77n6gkytpkn2wguemnpfqfaynv7u6ci4j247leg2w3dhcoxa5ns"), + "datacap": MustParseCid("bafk2bzacebj2ztqmlb7mmkggaqf66sv7gao5722vzmpnngiuncu4efpsjyhy4"), + "eam": MustParseCid("bafk2bzacecvl6xpmldfk5oyehqhmuasp7cbx3kh3y425curdy65hlmhovc4oi"), + "ethaccount": MustParseCid("bafk2bzacec2wy3fknb63r5zili7qojvt4f3rvstweqvnjne5adarx3lskgz5m"), + "evm": MustParseCid("bafk2bzacebtoqzucrh7kvtxpo4ruzisey67z6t3z5cbff4c36du3dlm3aj4l6"), + "init": MustParseCid("bafk2bzacebvc5t5u3opeyx4rxbeinshjsghjhttdbsyqifb4ikzpa2ic5mkhu"), + "multisig": MustParseCid("bafk2bzaceckmb2bcw2m5o4hifhodrq2j6ow5nhouj5wpqsxpzntgveft3hplu"), + "paymentchannel": MustParseCid("bafk2bzacebsvkpbavcdjguev4dxfnjx5j5bqzeeyk3petwbkmfptoej5bqlqc"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzacea3rkyhzmugj6ap3uv5m5jnhxt64y2775vo6vemdi5aafzxty3nmq"), + "storagemarket": MustParseCid("bafk2bzacebfacbnuauxhq63f5jbchoi2xwc5ljrxnwgj2xulkx4yzfu6i4lhg"), + "storageminer": MustParseCid("bafk2bzaceal4ct6gwlzl3owu6d4iiudf4ioxth5gn6cvm37enl465px2bhznk"), + "storagepower": MustParseCid("bafk2bzaceb5pdkt55d7wxjgmhvzvnlkuw5r6eamypgqm5kem5tz3sxddwtizw"), + "system": MustParseCid("bafk2bzacecuu2y5b6r4jrj64w5yuh3klnoevrcnbdxozmtagdcyfve7oe27ri"), + "verifiedregistry": MustParseCid("bafk2bzacediez3q42tjeit7hbsglv33ltfeamoa43lftwsxh2nyov5ijhuihm"), }, }, { Network: "calibrationnet", @@ -100,23 +101,24 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "calibrationnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaced7wbd43lvgc55xb37mkoo4ppev6ig4jj4j7dtswtjfjq4u5qmpck"), + ManifestCid: MustParseCid("bafy2bzacebb4qaymahytofcakf3vcyuv4tnu4zsgxp4iikc6kkmfagtaierro"), 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("bafk2bzaceakcix46r7hh4wjhzaksnha6f5elbg62ld6dklz6ttkhisnppmoe2"), + "cron": MustParseCid("bafk2bzaceb5ezi5dlgxjmxffecrmgiajwseiblgivho5qnfejivuqxfclooma"), + "datacap": MustParseCid("bafk2bzaceag4j2myqvwevm3mdtvmqoeguwtjheyezodjcpq5ugibjx6gtz65u"), + "eam": MustParseCid("bafk2bzacedwnuqthwpl3si5gs27xnxq7bcr5ucg3vg4utltfhnytaqzwlk33g"), + "ethaccount": MustParseCid("bafk2bzacechx3zdc4yw7ehtelecwsp6hb2iguefusfccqxfmdinjjoa64ado4"), + "evm": MustParseCid("bafk2bzaceaqtomr2odeyjabgkwnwfgm54b44cpshpkssn7nq35umzogkqqbee"), + "init": MustParseCid("bafk2bzacebtqjmdudvfl5cq6yqswortlc46cjpz36qj5igzfglu55kppuovyw"), + "multisig": MustParseCid("bafk2bzaceb2hlrmljz26gecv7zj5ymeleiuebf6mnvc7vng3qbcxvxagrlc22"), + "paymentchannel": MustParseCid("bafk2bzacecbn4m6evxxoalujyt4wnas3hdttffzjee5qjp62w7dsjdoeuhzka"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzaceds2kn4fn27nwlxx2raawldkytlro6i2qeh6rdtmme6ybd4otpwqq"), + "storagemarket": MustParseCid("bafk2bzacec35rrhgiqqrwqjtuhoseg7e4g67sqaa74b2x2p7f37ylfulc672i"), + "storageminer": MustParseCid("bafk2bzaceaiwkboswmk4kmzctnzd5txf2axdvladlf6x5as7ey7avxa5dgfd4"), + "storagepower": MustParseCid("bafk2bzaceb2nlgx5aw2psgiedc7oqefs6mttwcloczsbucxtit55zzpbgr4fc"), + "system": MustParseCid("bafk2bzacecki2gyvfguathvwva4ilovedftfuxvk3rhuw4y2t4aawkm2e5ttq"), + "verifiedregistry": MustParseCid("bafk2bzacebvicauwopayihp5jjegtma26nuhubauya73tkhpdfs27xwjutn4w"), }, }, { Network: "caterpillarnet", @@ -156,23 +158,24 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "caterpillarnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzacea5csj2os7h76a6yvf6shgpwkysawijxemk5uvvzejxrwjo6ir4yg"), + ManifestCid: MustParseCid("bafy2bzacedktxutlehqmx2uryphph6dln4wbgy6yfsj7un36b2u7l7a32n3km"), 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("bafk2bzaceaoina2vmmq24ij5kqbgawjrlnxkmj6arzucoigabt25ch6cdvbyc"), + "cron": MustParseCid("bafk2bzaceancnphwoym4pmzatrzfxo3bac72gk4bjgaqxedrigfrua62an3n2"), + "datacap": MustParseCid("bafk2bzaceaoebtmqyqvyv7oq7ehdkhl6fxjamz5fjdje7axslxsc7rhqchcdm"), + "eam": MustParseCid("bafk2bzaceazfxudfagmhdmwx46sjeyqoba3quy7cllqv2nuksh3ikc6gw63yg"), + "ethaccount": MustParseCid("bafk2bzaceblsm6aaymb2ua64eqbe32uyxdoyqzger5recw6k4p43yeu2oyigi"), + "evm": MustParseCid("bafk2bzacea7jveqmq5u6ijabkbuujtgqzmy3p5zl5x45frnygio3hqqnabbso"), + "init": MustParseCid("bafk2bzacecdewh4goftyp2cmuq3zpkyjinv6faysksjgqxtbk2j5dljv75rgq"), + "multisig": MustParseCid("bafk2bzacecpscu24o2fwlspi64k3fiaufeh3nnrv6wgumvaeco2jduhposcf4"), + "paymentchannel": MustParseCid("bafk2bzacedxc3fy436wlebsusgwcwqf6lzo7yv5iugy75z5hwrdt33pq2rhho"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzacebzvjtzzuo6ijkbx2yx3ly6vlrgw37fymasdwcdymlxc26znib2og"), + "storagemarket": MustParseCid("bafk2bzaceabdr5l4qluc4rgxjxo4xnoyc5e6slp7s3fojhnv4ng3swu7qv556"), + "storageminer": MustParseCid("bafk2bzaceclwbgfqr7wqepi6xkpoqz2bl6p22mlbmsgs4zarxki2cub5rspxy"), + "storagepower": MustParseCid("bafk2bzacean3trpjoxvwztrmbw7lx6osapicxzclyz2cgou4upq432norjnaq"), + "system": MustParseCid("bafk2bzacedeexlnmp677eauba76trar47p2zddbspiodri5aof6ccasyicxxo"), + "verifiedregistry": MustParseCid("bafk2bzaceayctmu2avbckz4scuep3ocxsw5r3eqxz7wu27volaxnvfvoxjhbq"), }, }, { Network: "devnet", @@ -212,23 +215,46 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "devnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzacea73thrlpfejrswlcu5uhe7rcgdewvmrcwoef6jzngsba3i4v5ibi"), + ManifestCid: MustParseCid("bafy2bzacea2wblucgi2tztgk52fap2wsjddrrobito5zdklqdhpdwhmyr7lbk"), 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("bafk2bzacealrkumvvuyeefrnedyh2ilgozgrdp5canubuakp723pczjdcogvw"), + "cron": MustParseCid("bafk2bzacedcfqpqgwj4tgtccliqzwnjbxiceyc3lzylnf6owu54etiv3udjxi"), + "datacap": MustParseCid("bafk2bzacear73heimtdso2qnw77gx6lzqmnr5f5etlggwevut5di6d7nzaoqu"), + "eam": MustParseCid("bafk2bzacecff4dgsqjcele3zejp77weofbqkef57r2tcy4alkx7e7n3kbkmyy"), + "ethaccount": MustParseCid("bafk2bzacec75pm3q66lsex2mqpm7fax7h6bfbkpwodf4o5gzgnrc2cdifbvfw"), + "evm": MustParseCid("bafk2bzaceacbkauvu7ia3euoz5jfdrkw5s7mk4ga5byf3oqndxeaxyxhgnk4m"), + "init": MustParseCid("bafk2bzacebtc7p3fq4d7m76jphagzpav2kfxfok7d56wrkek4zqyfqvtpihwm"), + "multisig": MustParseCid("bafk2bzaceci3czx4l42u22iozsgg6zkls5wdtrztekmzy4qnybg4qlv4b3qli"), + "paymentchannel": MustParseCid("bafk2bzacea54hgf2czdhlxvn66pyoon5cw3fwdlwx4kp4fwftv2tat4r4nnqg"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzacebd7ia2qfpxob2enuazpy3yc2wpach5rq2qbm7uwgknyzqqruefmm"), + "storagemarket": MustParseCid("bafk2bzaceam7ns7axlv3sghrwdo7kriw2hrlblim2pingllnvatqu2xfjfhgs"), + "storageminer": MustParseCid("bafk2bzacecbfmgzg5unc3ia7yme75psji2j6uhalt5jco6niu6wcn3pdufavy"), + "storagepower": MustParseCid("bafk2bzacedxzvoqa3a4lzajoke75q2ujhmazm4inb7robfikgtywr4sp6mgcy"), + "system": MustParseCid("bafk2bzaceb5wpgomztoaxfv2hhgb5xtbmq4t53wev6mg4yonax5glbi4dtcoe"), + "verifiedregistry": MustParseCid("bafk2bzacedf2f53g4cuvt6efwlga2sbovuy3tzcc2rhjh5eaa5f2ivri25ehc"), + }, +}, { + Network: "hyperspace", + Version: 10, + ManifestCid: MustParseCid("bafy2bzacearfqpg6omcyjot2zc5yynkb4bsfkykqacmwaiwiodvhlin4j4xta"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacec63ehicn6roe4hwtktiqjdpn7buw4vewix2fshvk7ruk6bg54bga"), + "cron": MustParseCid("bafk2bzacedgz5736vxapicnbdrk5yrbgwbjizhr5hjglxwkb6sonqpoyaid4u"), + "datacap": MustParseCid("bafk2bzacedmuhap3ephpwcvyqxry54irmw5zaz74abhmnjlwrf6ioihrzcqgu"), + "eam": MustParseCid("bafk2bzacedx5iw3jyrdfkvgqcas244e6hqotli72uinwiy74ae7s4imax6agc"), + "ethaccount": MustParseCid("bafk2bzacectie3kbxquentwvrlcwoqzfisfcqfgngzgyndhjgclmg7cyf6zwa"), + "evm": MustParseCid("bafk2bzacedtbvq77pb4vd7rx2inklizjeipt7myb7tim5hms2vzzmuareekrw"), + "init": MustParseCid("bafk2bzacebcmb4nwbhambosg2xcfhifkqnaoxsotfwekmxeq7phgqgxzzxnts"), + "multisig": MustParseCid("bafk2bzacedw73tmdl7stykyycxmhsb3iom7notxmx24b2647g3vwmrg2brgqu"), + "paymentchannel": MustParseCid("bafk2bzaced5frgcp6bszhis5kxtm6uko5jlgbuzyczqsj7uavovkayrbizg4s"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzaceddc5c45lnbo7i3uknk5zi3qbq6k4rocfoq3n7x3qerehyevjl7ja"), + "storagemarket": MustParseCid("bafk2bzaceafqt55lykve6ex5bw65ueuec6bjcwfmq6vbhwbgjmpt4cztldzle"), + "storageminer": MustParseCid("bafk2bzacebongyek6ijaqzup6fe2kplq24dr3dtvweu5fmfjl236rjhyqv3gs"), + "storagepower": MustParseCid("bafk2bzaceapgnms6ldjpewmniaqawu5sflssycjbwydqvilja66y5wm4lulwq"), + "system": MustParseCid("bafk2bzaceaki4yktxjcaxuj4be4q2ybrjiqcqq7orgzqgmidcavn3dzxyo7ds"), + "verifiedregistry": MustParseCid("bafk2bzacedrd4uxho6tpksyullgb6lofudlwntjwt7sqrtdgzjoj6eu7onwnc"), }, }, { Network: "mainnet", @@ -268,23 +294,24 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "mainnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaceduyggnyqhlr346hfw32tbobzrvhzhill33zhe7jw64pmwjci2xoc"), + ManifestCid: MustParseCid("bafy2bzacedjq47v3yxxptl3vvtpin4optnhoe7ynxjncuhpak2ifdvff2mrfy"), 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("bafk2bzaceb7wftmnoa5zbeu6jsrzvqpjfd7kudhueve6duwxcflcaqeagqg6g"), + "cron": MustParseCid("bafk2bzacean2xecc6kfbrueglsujjqswz5nvjstmtvdq2zc5vk4cbh6gvgxcq"), + "datacap": MustParseCid("bafk2bzacear4esja4asfsdeqto6o5cjn5dbgxmzvu2uel36tzdp2s26fkte4y"), + "eam": MustParseCid("bafk2bzacectduzxzk23xffgohmsmq4gisl4etaguq2xe6h52y7aiaaibmi2pg"), + "ethaccount": MustParseCid("bafk2bzacecqoxzy2p3i46ncuvw6wzlesaw5iobqpp7nmjtkbxyiwblg4frzmg"), + "evm": MustParseCid("bafk2bzacecgz6klga2lwjp3d3iyzkzm4td5aefeaymmn3rmixgg7a4vwoewcu"), + "init": MustParseCid("bafk2bzacebzrlucqk23kkjv26srpzjgztx7qjm23suqbyzu2qdto5jbpifm7y"), + "multisig": MustParseCid("bafk2bzacedls4tgmkfkasgcsfrtnkjoi6yoze5yq4cafqzbjvbty5gfeut4n4"), + "paymentchannel": MustParseCid("bafk2bzaceazuokt65n3hqjgmwmgoi6gpcvete2b46nlecnynjkgrzp4wnl4ii"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzacecqjpnbxravwqb7liwxowb5bdml4x2qboo5vqxk7slprq3eo4ujp2"), + "storagemarket": MustParseCid("bafk2bzacecuedinzm256nup2pbsdrnxerfk33hqxhakupb3c26fwk576ehgiw"), + "storageminer": MustParseCid("bafk2bzaceb4c7iqlmk4lgnkccpdvzdv6kbwqz2leyluuehh2ino2m6e5cd7d6"), + "storagepower": MustParseCid("bafk2bzacedkwcuzvlx43nbwdwms5smerdaeja45rh7d646k4nc4s5toyryuxi"), + "system": MustParseCid("bafk2bzacedexhpixywvxk37jyp3sehcqmomyq4kpxu3wjhrehawyjhv6grav2"), + "verifiedregistry": MustParseCid("bafk2bzacebsnm343frbrfted3vbia4to7iiav4qm3tvpkcbsgkd5fynop3q7s"), }, }, { Network: "testing", @@ -324,23 +351,24 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "testing", Version: 10, - ManifestCid: MustParseCid("bafy2bzacearlgbespxi2zdrybtp2rrbwscmtbyou5qa2egbdvcz6v2yjjqvjo"), + ManifestCid: MustParseCid("bafy2bzacececfnvx4hay5b6yhtkxdqgkauupeqpwnlgainug6blp2isihwtww"), 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("bafk2bzaceb56iceglbwg3s2skdjj5vhak7j6srlaxvd7v6hzvxpccwsrvfaxi"), + "cron": MustParseCid("bafk2bzacearjb3buxy4jwhcn4jeqbof3dinsh52zyj74djkfghmjdoxbsvjkq"), + "datacap": MustParseCid("bafk2bzacec2dghsnwhyhquwkbsfcct53pgglxzgmw7j66y3keniklbiq4q7ti"), + "eam": MustParseCid("bafk2bzacedojoymbgz275lzvtlpaf2thoydz7fb274mhvadbpk2thbgpye72s"), + "ethaccount": MustParseCid("bafk2bzaceddhvqqkwc4p7exdgv2bwefkk3lnq2rw6chvar7hdeowahtjdmznw"), + "evm": MustParseCid("bafk2bzacecgzk3ompisq5n7oualijioce6nhsm6zwil7p5p57nojuyrdckti2"), + "init": MustParseCid("bafk2bzaceacdto7l5qp65ukclc3qqlfpv3tdio7u5lxufg2uc3hrx5hpqt2x2"), + "multisig": MustParseCid("bafk2bzacebw6ujt54gyhxvo5jmimg3z54crmfzbbcr677ljqmmb2ejh6srlsm"), + "paymentchannel": MustParseCid("bafk2bzacebfht4drwm5aagcx4kvuiwclldd6rnosce42474u4asnurjqyxhna"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzacedqmiebckz7xqs7f7gcj67wzzpko2q3jhom6rwtopogv7iz5bnwlu"), + "storagemarket": MustParseCid("bafk2bzaceam3n2xjbvkyyifw7jvkc4z4lxvblnbmx4ruzr5lpesgmpuhbmb2w"), + "storageminer": MustParseCid("bafk2bzacebcbyuzniiqksk47v7zyfc56noblbsmlqblv3n6s7l5un3heigdwk"), + "storagepower": MustParseCid("bafk2bzacechzbqmunxv6o2zgp4uicswapmgexc3uejco2n2r7cirbijrbgewc"), + "system": MustParseCid("bafk2bzaceb6obdybgoyjvdfxvxg5uxhrpnoixtbof663dllo2eelcuvsfycew"), + "verifiedregistry": MustParseCid("bafk2bzacedlogwaofqaqou4pckoatwei2ulbz3ucjmbsm3lfwuw3tr7g5opjc"), }, }, { Network: "testing-fake-proofs", @@ -380,22 +408,23 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "testing-fake-proofs", Version: 10, - ManifestCid: MustParseCid("bafy2bzacea4irr2oxhclwt4mvtrevbzb7mbqddcebjz7bkqjq6eoflpfhencc"), + ManifestCid: MustParseCid("bafy2bzacec4yawvg2rbdvzlez4lyf4e7ysjfjdrg5mpclvvwxt7texyeeletg"), 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("bafk2bzaceb56iceglbwg3s2skdjj5vhak7j6srlaxvd7v6hzvxpccwsrvfaxi"), + "cron": MustParseCid("bafk2bzacearjb3buxy4jwhcn4jeqbof3dinsh52zyj74djkfghmjdoxbsvjkq"), + "datacap": MustParseCid("bafk2bzacec2dghsnwhyhquwkbsfcct53pgglxzgmw7j66y3keniklbiq4q7ti"), + "eam": MustParseCid("bafk2bzacedojoymbgz275lzvtlpaf2thoydz7fb274mhvadbpk2thbgpye72s"), + "ethaccount": MustParseCid("bafk2bzaceddhvqqkwc4p7exdgv2bwefkk3lnq2rw6chvar7hdeowahtjdmznw"), + "evm": MustParseCid("bafk2bzacecgzk3ompisq5n7oualijioce6nhsm6zwil7p5p57nojuyrdckti2"), + "init": MustParseCid("bafk2bzaceacdto7l5qp65ukclc3qqlfpv3tdio7u5lxufg2uc3hrx5hpqt2x2"), + "multisig": MustParseCid("bafk2bzacebw6ujt54gyhxvo5jmimg3z54crmfzbbcr677ljqmmb2ejh6srlsm"), + "paymentchannel": MustParseCid("bafk2bzacebfht4drwm5aagcx4kvuiwclldd6rnosce42474u4asnurjqyxhna"), + "placeholder": MustParseCid("bafk2bzacedv773z6clfjh7wxvlqd6ki7bncztt73org7apnnt2acjigrjdg4a"), + "reward": MustParseCid("bafk2bzacedqmiebckz7xqs7f7gcj67wzzpko2q3jhom6rwtopogv7iz5bnwlu"), + "storagemarket": MustParseCid("bafk2bzaceam3n2xjbvkyyifw7jvkc4z4lxvblnbmx4ruzr5lpesgmpuhbmb2w"), + "storageminer": MustParseCid("bafk2bzaceataoi4vlifq2roanrfjt2f2cql2dq4osjs5i3nt4v76462mtrcm2"), + "storagepower": MustParseCid("bafk2bzacede4nq6cbym2z7p5vc2gwbxhnd34qxtd7sy4rmwula3kk6yhbqtve"), + "system": MustParseCid("bafk2bzaceb6obdybgoyjvdfxvxg5uxhrpnoixtbof663dllo2eelcuvsfycew"), + "verifiedregistry": MustParseCid("bafk2bzacedlogwaofqaqou4pckoatwei2ulbz3ucjmbsm3lfwuw3tr7g5opjc"), }, }} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 114fb584a98..b59fd8860e9 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 8b3fc78f20b..9277b2d1fff 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 0424b7cada3..3937893cb81 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 72a40797aa7..3ecfb6abfc9 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 f822d701ed4..081007dd191 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 6f0a64598ae..7cd02bce91b 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 f1aacc506ab..0661bb83927 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_hyperspace.go b/build/params_hyperspace.go new file mode 100644 index 00000000000..e1f082f41b0 --- /dev/null +++ b/build/params_hyperspace.go @@ -0,0 +1,98 @@ +//go:build hyperspace +// +build hyperspace + +package build + +import ( + "github.com/ipfs/go-cid" + + "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/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +var NetworkBundle = "hyperspace" +var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = true + +const BootstrappersFile = "hyperspace.pi" +const GenesisFile = "hyperspace.car" + +const GenesisNetworkVersion = network.Version18 + +var NetworkBundle = "hyperspacenet" +var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = false + +var UpgradeBreezeHeight = abi.ChainEpoch(-1) + +const BreezeGasTampingDuration = 120 + +var UpgradeSmokeHeight = abi.ChainEpoch(-1) +var UpgradeIgnitionHeight = abi.ChainEpoch(-2) +var UpgradeRefuelHeight = abi.ChainEpoch(-3) +var UpgradeTapeHeight = abi.ChainEpoch(-4) + +var UpgradeAssemblyHeight = abi.ChainEpoch(-5) +var UpgradeLiftoffHeight = abi.ChainEpoch(-6) + +var UpgradeKumquatHeight = abi.ChainEpoch(-7) +var UpgradeCalicoHeight = abi.ChainEpoch(-9) +var UpgradePersianHeight = abi.ChainEpoch(-10) +var UpgradeOrangeHeight = abi.ChainEpoch(-11) +var UpgradeClausHeight = abi.ChainEpoch(-12) + +var UpgradeTrustHeight = abi.ChainEpoch(-13) + +var UpgradeNorwegianHeight = abi.ChainEpoch(-14) + +var UpgradeTurboHeight = abi.ChainEpoch(-15) + +var UpgradeHyperdriveHeight = abi.ChainEpoch(-16) +var UpgradeChocolateHeight = abi.ChainEpoch(-17) +var UpgradeOhSnapHeight = abi.ChainEpoch(-18) +var UpgradeSkyrHeight = abi.ChainEpoch(-19) +var UpgradeSharkHeight = abi.ChainEpoch(-20) +var UpgradeHyggeHeight = abi.ChainEpoch(-21) + +var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandMainnet, +} + +var SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg512MiBV1, + abi.RegisteredSealProof_StackedDrg32GiBV1, + abi.RegisteredSealProof_StackedDrg64GiBV1, +} +var ConsensusMinerMinPower = abi.NewStoragePower(16 << 30) +var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) +var PreCommitChallengeDelay = abi.ChainEpoch(10) + +func init() { + policy.SetSupportedProofTypes(SupportedProofTypes...) + policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) + policy.SetMinVerifiedDealSize(MinVerifiedDealSize) + policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) + + BuildType = BuildHyperspacenet + SetAddressNetwork(address.Testnet) + Devnet = true + +} + +const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) + +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 = 3141 + +var WhitelistedBlock = cid.Undef diff --git a/build/params_interop.go b/build/params_interop.go index dbc619e1b82..4d94de049c0 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 2967931312a..f95525dbcdb 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -1,5 +1,5 @@ -//go:build !debug && !2k && !testground && !calibnet && !butterflynet && !interopnet -// +build !debug,!2k,!testground,!calibnet,!butterflynet,!interopnet +//go:build !debug && !2k && !testground && !calibnet && !butterflynet && !interopnet && !wallabynet && !hyperspacenet +// +build !debug,!2k,!testground,!calibnet,!butterflynet,!interopnet,!wallabynet,!hyperspacenet package build @@ -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 dcdee888dbb..ed818f4a546 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -108,6 +108,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 +117,7 @@ var ( GenesisNetworkVersion = network.Version0 NetworkBundle = "devnet" BundleOverrides map[actorstypes.Version]string + ActorDebugging = true NewestNetworkVersion = network.Version16 ActorUpgradeNetworkVersion = network.Version16 @@ -129,3 +131,7 @@ var ( ) 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/build/params_wallaby.go b/build/params_wallaby.go new file mode 100644 index 00000000000..98231672b60 --- /dev/null +++ b/build/params_wallaby.go @@ -0,0 +1,94 @@ +//go:build wallabynet +// +build wallabynet + +package build + +import ( + "github.com/ipfs/go-cid" + + "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/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +var NetworkBundle = "wallaby" +var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = true + +const BootstrappersFile = "wallabynet.pi" +const GenesisFile = "wallabynet.car" + +const GenesisNetworkVersion = network.Version18 + +var UpgradeBreezeHeight = abi.ChainEpoch(-1) + +const BreezeGasTampingDuration = 120 + +var UpgradeSmokeHeight = abi.ChainEpoch(-1) +var UpgradeIgnitionHeight = abi.ChainEpoch(-2) +var UpgradeRefuelHeight = abi.ChainEpoch(-3) +var UpgradeTapeHeight = abi.ChainEpoch(-4) + +var UpgradeAssemblyHeight = abi.ChainEpoch(-5) +var UpgradeLiftoffHeight = abi.ChainEpoch(-6) + +var UpgradeKumquatHeight = abi.ChainEpoch(-7) +var UpgradeCalicoHeight = abi.ChainEpoch(-9) +var UpgradePersianHeight = abi.ChainEpoch(-10) +var UpgradeOrangeHeight = abi.ChainEpoch(-11) +var UpgradeClausHeight = abi.ChainEpoch(-12) + +var UpgradeTrustHeight = abi.ChainEpoch(-13) + +var UpgradeNorwegianHeight = abi.ChainEpoch(-14) + +var UpgradeTurboHeight = abi.ChainEpoch(-15) + +var UpgradeHyperdriveHeight = abi.ChainEpoch(-16) +var UpgradeChocolateHeight = abi.ChainEpoch(-17) +var UpgradeOhSnapHeight = abi.ChainEpoch(-18) +var UpgradeSkyrHeight = abi.ChainEpoch(-19) +var UpgradeSharkHeight = abi.ChainEpoch(-20) +var UpgradeHyggeHeight = abi.ChainEpoch(-21) + +var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandMainnet, +} + +var SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg512MiBV1, + abi.RegisteredSealProof_StackedDrg32GiBV1, + abi.RegisteredSealProof_StackedDrg64GiBV1, +} +var ConsensusMinerMinPower = abi.NewStoragePower(16 << 30) +var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) +var PreCommitChallengeDelay = abi.ChainEpoch(10) + +func init() { + policy.SetSupportedProofTypes(SupportedProofTypes...) + policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) + policy.SetMinVerifiedDealSize(MinVerifiedDealSize) + policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) + + BuildType = BuildWallabynet + SetAddressNetwork(address.Testnet) + Devnet = true + +} + +const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) + +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 = 31415 + +var WhitelistedBlock = cid.Undef diff --git a/build/version.go b/build/version.go index 70e27ad507f..4946059e212 100644 --- a/build/version.go +++ b/build/version.go @@ -6,13 +6,15 @@ var CurrentCommit string var BuildType int const ( - BuildDefault = 0 - BuildMainnet = 0x1 - Build2k = 0x2 - BuildDebug = 0x3 - BuildCalibnet = 0x4 - BuildInteropnet = 0x5 - BuildButterflynet = 0x7 + BuildDefault = 0 + BuildMainnet = 0x1 + Build2k = 0x2 + BuildDebug = 0x3 + BuildCalibnet = 0x4 + BuildInteropnet = 0x5 + BuildButterflynet = 0x7 + BuildWallabynet = 0x8 + BuildHyperspacenet = 0x9 ) func BuildTypeString() string { @@ -31,6 +33,10 @@ func BuildTypeString() string { return "+interopnet" case BuildButterflynet: return "+butterflynet" + case BuildWallabynet: + return "+wallabynet" + case BuildHyperspacenet: + return "+hyperspacenet" default: return "+huh?" } diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go index 7811aab6c07..116ed35d071 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 76627945139..977217b1a0a 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 ec487275bab..4ce48098d60 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 b38cd41c6bd..a63b00917fb 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/filcns/compute_state.go b/chain/consensus/filcns/compute_state.go index ece97379847..4b1ff245d42 100644 --- a/chain/consensus/filcns/compute_state.go +++ b/chain/consensus/filcns/compute_state.go @@ -93,6 +93,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/filecoin.go b/chain/consensus/filcns/filecoin.go index de3cf7cf7d6..2cd6807462c 100644 --- a/chain/consensus/filcns/filecoin.go +++ b/chain/consensus/filcns/filecoin.go @@ -14,6 +14,7 @@ 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" "go.opencensus.io/trace" @@ -21,6 +22,7 @@ import ( "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/network" blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -433,6 +435,39 @@ func (filec *FilecoinEC) VerifyWinningPoStProof(ctx context.Context, nv network. return nil } +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 +} + // TODO: We should extract this somewhere else and make the message pool and miner use the same logic func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBlock, baseTs *types.TipSet) error { { @@ -505,7 +540,7 @@ func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBl 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 @@ -542,25 +577,23 @@ func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBl smArr := blockadt.MakeEmptyArray(tmpstore) for i, m := range b.SecpkMessages { - if filec.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 && !chain.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 := filec.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 := filec.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 := chain.AuthenticateMessage(m, kaddr); err != nil { + return xerrors.Errorf("failed to validate signature: %w", err) } c, err := store.PutMessage(ctx, tmpbs, m) diff --git a/chain/consensus/filcns/mine.go b/chain/consensus/filcns/mine.go index 35e38883d97..234c1d6546d 100644 --- a/chain/consensus/filcns/mine.go +++ b/chain/consensus/filcns/mine.go @@ -9,6 +9,7 @@ import ( "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain" "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" @@ -54,6 +55,7 @@ func (filec *FilecoinEC) CreateBlock(ctx context.Context, w api.Wallet, bt *api. var blsMsgCids, secpkMsgCids []cid.Cid var blsSigs []crypto.Signature + nv := filec.sm.GetNetworkVersion(ctx, bt.Epoch) for _, msg := range bt.Messages { if msg.Signature.Type == crypto.SigTypeBLS { blsSigs = append(blsSigs, msg.Signature) @@ -65,7 +67,7 @@ func (filec *FilecoinEC) CreateBlock(ctx context.Context, w api.Wallet, bt *api. } blsMsgCids = append(blsMsgCids, c) - } else if msg.Signature.Type == crypto.SigTypeSecp256k1 { + } else if chain.IsValidSecpkSigType(nv, msg.Signature.Type) { c, err := filec.sm.ChainStore().PutMessage(ctx, msg) if err != nil { return nil, err diff --git a/chain/consensus/filcns/upgrades.go b/chain/consensus/filcns/upgrades.go index 54e507277ce..5a268c8a69b 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 + } + + 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) + } - // 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") + // 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/events/filter/event.go b/chain/events/filter/event.go new file mode 100644 index 00000000000..a19f49a50f5 --- /dev/null +++ b/chain/events/filter/event.go @@ -0,0 +1,474 @@ +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 + } + + // event matches filter, so record it + cev := &CollectedEvent{ + Entries: ev.Entries, + 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.Message + rct *types.MessageReceipt + // events extracted from receipt + evs []*types.Event +} + +func (e *executedMessage) Message() *types.Message { + 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].VMMessage() + + 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 00000000000..80c4c7fc014 --- /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 00000000000..45cabaa1156 --- /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 orig == nil { + 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 00000000000..ee2ae8611b5 --- /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 00000000000..250fc5961fc --- /dev/null +++ b/chain/events/filter/mempool.go @@ -0,0 +1,143 @@ +package filter + +import ( + "context" + "sync" + "time" + + "github.com/ipfs/go-cid" + "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 []cid.Cid + 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.Cid()) +} + +func (f *MemPoolFilter) TakeCollectedMessages(context.Context) []cid.Cid { + 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 00000000000..d3c173ec015 --- /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 00000000000..be734c6f74f --- /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 fe748020237..f3b1ae212ee 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -468,10 +468,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 300571f154c..d1c7d308eac 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 00000000000..d5aa2f0b51b --- /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 94ae6267357..dabd2cb3310 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -33,12 +33,13 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "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 +283,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 +299,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 +319,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 +345,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 +477,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 +773,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 +794,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 := chain.AuthenticateMessage(m, m.Message.From); err != nil { + return xerrors.Errorf("failed to validate signature: %w", err) } mp.sigValCache.Add(sck, struct{}{}) @@ -816,7 +815,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 +852,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 !filcns.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 9495400c696..20da2317e9b 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 f8bbbc01e98..123a2607ea0 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 e84962869c4..bd504412863 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 a5b2f326602..c3a5c6d6f3a 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/signatures.go b/chain/signatures.go new file mode 100644 index 00000000000..aceca0c5e97 --- /dev/null +++ b/chain/signatures.go @@ -0,0 +1,53 @@ +package chain + +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.NewEthTxArgsFromMessage(&msg.Message) + if err != nil { + return xerrors.Errorf("failed to reconstruct eth transaction: %w", err) + } + msg, err := txArgs.ToRlpUnsignedMsg() + if err != nil { + return xerrors.Errorf("failed to repack eth rlp message: %w", err) + } + digest = msg + 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/stmgr/actors.go b/chain/stmgr/actors.go index 0c2524b7bcc..4de39c7f172 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 c110dd4bd0d..8f8aebf63cf 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,24 @@ 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), + }, + } + default: + // XXX: Hack to make sending from f099 (and others) "just work". + // REMOVE ME. + msgApply = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeSecp256k1, + 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 2c9893cc0d0..9186501eab9 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 926646a4ce1..ee9338e63d1 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 af4a0cd9e2a..c93267d50f8 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/store.go b/chain/store/store.go index 6313492a798..9ab08c74f76 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -1,6 +1,7 @@ package store import ( + "bytes" "context" "encoding/json" "errors" @@ -375,10 +376,8 @@ 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]) @@ -646,7 +645,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 +957,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 +1042,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 { @@ -1165,6 +1164,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/sync.go b/chain/sync.go index 634313855d7..b0c8b50ff96 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.PersistBlockHeaders(ctx, toPersist...); err != nil { - err = xerrors.Errorf("failed to persist synced blocks to the chainstore: %w", err) - ss.Error(err) - return err + 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 + } } - toPersist = nil ss.SetStage(api.StageMessages) diff --git a/chain/sync_test.go b/chain/sync_test.go index 18520a07fe4..a86d42f17e6 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 29a6865eb2d..8ba61161739 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 da42d760317..709f7dd88d2 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 00000000000..16c06ca0486 --- /dev/null +++ b/chain/types/ethtypes/eth_transactions.go @@ -0,0 +1,595 @@ +package ethtypes + +import ( + "bytes" + "encoding/binary" + "fmt" + mathbig "math/big" + + cbg "github.com/whyrusleeping/cbor-gen" + "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/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" + typescrypto "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "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 NewEthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) { + var ( + to *EthAddress + decodedParams []byte + paramsReader = bytes.NewReader(msg.Params) + ) + + if msg.To == builtintypes.EthereumAddressManagerActorAddr { + switch msg.Method { + case builtintypes.MethodsEAM.Create: + var create eam.CreateParams + if err := create.UnmarshalCBOR(paramsReader); err != nil { + return EthTxArgs{}, err + } + decodedParams = create.Initcode + case builtintypes.MethodsEAM.Create2: + var create2 eam.Create2Params + if err := create2.UnmarshalCBOR(paramsReader); err != nil { + return EthTxArgs{}, err + } + decodedParams = create2.Initcode + 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 { + params, err := cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) + if err != nil { + return EthTxArgs{}, err + } + decodedParams = params + } + } + + return EthTxArgs{ + ChainID: build.Eip155ChainId, + Nonce: int(msg.Nonce), + To: to, + Value: msg.Value, + Input: decodedParams, + MaxFeePerGas: msg.GasFeeCap, + MaxPriorityFeePerGas: msg.GasPremium, + GasLimit: int(msg.GasLimit), + }, nil +} + +func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) { + from, err := tx.Sender() + if err != nil { + return nil, err + } + + var to address.Address + var params []byte + + if len(tx.To) == 0 && len(tx.Input) == 0 { + return nil, fmt.Errorf("to and input cannot both be empty") + } + + var method abi.MethodNum + if tx.To == nil { + // TODO unify with applyEvmMsg + + // this is a contract creation + to = builtintypes.EthereumAddressManagerActorAddr + + params2, err := actors.SerializeParams(&eam.CreateParams{ + Initcode: tx.Input, + Nonce: uint64(tx.Nonce), + }) + if err != nil { + return nil, fmt.Errorf("failed to serialize Create params: %w", err) + } + params = params2 + method = builtintypes.MethodsEAM.Create + } else { + addr, err := tx.To.ToFilecoinAddress() + if err != nil { + return nil, err + } + to = addr + + if len(tx.Input) > 0 { + var buf bytes.Buffer + if err := cbg.WriteByteArray(&buf, tx.Input); 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 + } + } + + msg := &types.Message{ + Nonce: uint64(tx.Nonce), + From: from, + To: to, + Value: tx.Value, + Method: method, + Params: params, + GasLimit: int64(tx.GasLimit), + GasFeeCap: tx.MaxFeePerGas, + GasPremium: tx.MaxPriorityFeePerGas, + } + + sig, err := tx.Signature() + if err != nil { + return nil, err + } + + signedMsg := types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + return &signedMsg, 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 *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 00000000000..10131d9ac9c --- /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) + + msgRecovered, err := txArgs.ToRlpUnsignedMsg() + require.NoError(t, err) + require.Equal(t, tc.NosigTx, "0x"+hex.EncodeToString(msgRecovered), comment) + + // verify signatures + from, err := txArgs.Sender() + require.NoError(t, err) + + smsg, err := txArgs.ToSignedMessage() + require.NoError(t, err) + + err = sigs.Verify(&smsg.Signature, from, msgRecovered) + require.NoError(t, err) + + // verify data + require.Equal(t, tc.Output.ChainID, txArgs.ChainID) + require.Equal(t, tc.Output.Nonce, txArgs.Nonce) + require.Equal(t, tc.Output.To, txArgs.To) + } +} + +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":"0x02f84e82013a80808080808080c080a002d9af9415b94bac9fb29efa168e800fe8390ec22dd6dd3b6848632f999e5fa6a04b0bd833d6993eb37c3b0b5f89551cbbd5412b3a1fed84ca1e94ab2b936be12b","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cb82013a80808080808080c0"},{"input":"0x02f84f82013a81c8808080808080c080a0a9177c9fc995b0f83480113a62b797a3520e6bc15d0e9c722c662c40d443b893a01eec355e019308be6acf89a55288a40ae247b6f57c0ca31545efea5954f788d5","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02cc82013a81c8808080808080c0"},{"input":"0x02f84e82013a80808080808080c080a002d9af9415b94bac9fb29efa168e800fe8390ec22dd6dd3b6848632f999e5fa6a04b0bd833d6993eb37c3b0b5f89551cbbd5412b3a1fed84ca1e94ab2b936be12b","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cb82013a80808080808080c0"},{"input":"0x02f84f82013a81c8808080808080c080a0a9177c9fc995b0f83480113a62b797a3520e6bc15d0e9c722c662c40d443b893a01eec355e019308be6acf89a55288a40ae247b6f57c0ca31545efea5954f788d5","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02cc82013a81c8808080808080c0"},{"input":"0x02f87282013a808080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0a0ab14f6fcca6c9905f447e961f128f2c00f5a00e7b1ae18f5d4f9e024a9b7a6a06c4126378d89035f4ab6085fa9a01d92bf798cd799b9338f7818bc48bcba0c8e","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ef82013a808080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87382013a81c88080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0884e063f65a2986844a9e92f9e02561789c231136976715d5afb581435359e87a044295113d06dd7b8bdf105dd412c76fbd966ef6dbe1d8ace984886296a36018c","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f082013a81c88080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85082013a808082ea6080808080c080a02b86cbd16667f7e035bd908d250d842e7d06f888716131de27897655dce01666a055f43bf2a758e6c250a14a31d33056fcb764e0fce4fffdeb0e11d84caa5ca57b","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a808082ea6080808080c0"},{"input":"0x02f85182013a81c88082ea6080808080c001a07f1e363b3d38607f8854013e68a80750befd3ba78cc9ce116d6ef6a09359a7aea05ef2c89ffc70ef7f2eaae90a2b4b2a4ffb418108d458e1237b26469e6381fdce","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c88082ea6080808080c0"},{"input":"0x02f85082013a808082ea6080808080c080a02b86cbd16667f7e035bd908d250d842e7d06f888716131de27897655dce01666a055f43bf2a758e6c250a14a31d33056fcb764e0fce4fffdeb0e11d84caa5ca57b","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a808082ea6080808080c0"},{"input":"0x02f85182013a81c88082ea6080808080c001a07f1e363b3d38607f8854013e68a80750befd3ba78cc9ce116d6ef6a09359a7aea05ef2c89ffc70ef7f2eaae90a2b4b2a4ffb418108d458e1237b26469e6381fdce","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c88082ea6080808080c0"},{"input":"0x02f87482013a808082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0ddb915f82dd7835ad51b718abcfce007d8626019b122763063b92fa40b987248a03bd8dfd6c9b9c33affcdcd3a16b8d28ba9554ace2b5333c7d51d218f9d9ae175","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f182013a808082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87582013a81c88082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0606a3f7eeae6d8e7167d68bdd18c8d6f1dafa2aa308cc972d00c17fba8a00ed5a023ab15156103722a5b318570eed11eaa54d943f49135fae65288cb08fdb63aa1","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f282013a81c88082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85082013a8082ea608080808080c080a06ad97b54cb997b20c8c10e8aa231582470e9fb29c7b4353ad349ede58988e167a0536ca437b1e2c2346b44ac24d0c5c2bc84ef76f2fa9fb601e5120ab08172188d","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a8082ea608080808080c0"},{"input":"0x02f85182013a81c882ea608080808080c080a07765925917407579de914453373b3169a9c7bd54ffbdc2baf95d27d628782b6aa07bdb11529410353d7fcf128e47d4a11f78805bdea8317df3060f75574dd56049","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c882ea608080808080c0"},{"input":"0x02f85082013a8082ea608080808080c080a06ad97b54cb997b20c8c10e8aa231582470e9fb29c7b4353ad349ede58988e167a0536ca437b1e2c2346b44ac24d0c5c2bc84ef76f2fa9fb601e5120ab08172188d","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a8082ea608080808080c0"},{"input":"0x02f85182013a81c882ea608080808080c080a07765925917407579de914453373b3169a9c7bd54ffbdc2baf95d27d628782b6aa07bdb11529410353d7fcf128e47d4a11f78805bdea8317df3060f75574dd56049","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c882ea608080808080c0"},{"input":"0x02f87482013a8082ea6080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0cea8956fdd5588f6ecb10da330d2b792f1f65e44ad7ce7ef8f084054c036a1f4a0429db8240b6da9dbc82286141ec5af4abcf38a3c1aabe2f918274aca59cfc315","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f182013a8082ea6080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87582013a81c882ea6080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0936dede60bd875866a5909ff0f6bbdfc12d3e9b171904140d745f8fb8f58dd1aa005d646d18d5abcd9dfdc448226be2b2395bb725603d548ce20e99968c7d1165b","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f282013a81c882ea6080808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85282013a8082ea6082ea6080808080c001a08d8e15db9b109df23e6a888073dfbd4ff82650072a6fabc2acb7a8077221eee6a04d6b3b42271a428998881b4a061efa181b66113dbbb68ea559bc07668b4be8e5","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea6082ea6080808080c0"},{"input":"0x02f85382013a81c882ea6082ea6080808080c080a0cf0e64a9535ae3e48045e863970f9e50b2742c641a5fe87a68ce60711e7f0d37a077ec4be433ce17b81eeccc857edde772bceaf7f1ef5dec398a0a5c514db554ab","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea6082ea6080808080c0"},{"input":"0x02f85282013a8082ea6082ea6080808080c001a08d8e15db9b109df23e6a888073dfbd4ff82650072a6fabc2acb7a8077221eee6a04d6b3b42271a428998881b4a061efa181b66113dbbb68ea559bc07668b4be8e5","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea6082ea6080808080c0"},{"input":"0x02f85382013a81c882ea6082ea6080808080c080a0cf0e64a9535ae3e48045e863970f9e50b2742c641a5fe87a68ce60711e7f0d37a077ec4be433ce17b81eeccc857edde772bceaf7f1ef5dec398a0a5c514db554ab","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea6082ea6080808080c0"},{"input":"0x02f87582013a8082ea6082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a087081d2d818f81fb4d6e7063e1c99b0c615166815dcfab4ada162a3bb9888f689f0d4db69544b662926e8d06d838739691ddc15c23e5bde43b39b4908248abc2","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f382013a8082ea6082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87782013a81c882ea6082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a050d9da7b09da1c7b8f8acf8a4757ef0a30e051c9f3ad2175f23f00ce1c9b830da047e0d9bb275b89be86bd3e5c711d6ec93220df5072ba363f2d49b2b1c2487235","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f482013a81c882ea6082ea60808080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85082013a80808082ea60808080c080a07b741f41b9adbaff98e97624a7910c544ca9fe9d6d4df4c72f9c6388eae73feda02206314ddfccf58fd2859b09b186c5d620b3da4629b4f5ea7182f5f20166cc17","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a80808082ea60808080c0"},{"input":"0x02f85182013a81c8808082ea60808080c080a0de264ea9a2f3130cf8b701f1e9683e7966fc11f889e2662ce019bde8b230c76ea0757eeb15f1425b63a1b30404a90e7fdd71d260144370400b90759151dc9e5c0a","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c8808082ea60808080c0"},{"input":"0x02f85082013a80808082ea60808080c080a07b741f41b9adbaff98e97624a7910c544ca9fe9d6d4df4c72f9c6388eae73feda02206314ddfccf58fd2859b09b186c5d620b3da4629b4f5ea7182f5f20166cc17","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a80808082ea60808080c0"},{"input":"0x02f85182013a81c8808082ea60808080c080a0de264ea9a2f3130cf8b701f1e9683e7966fc11f889e2662ce019bde8b230c76ea0757eeb15f1425b63a1b30404a90e7fdd71d260144370400b90759151dc9e5c0a","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c8808082ea60808080c0"},{"input":"0x02f87482013a80808082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0b88b1993c94f40aa95afdb48d12fe68872b2ad2db152a4af2e333926e57bab16a0134405c059bd7006ffde093895cb5f032bfb2ed2a2c2b1a8bc0fd404d34e397c","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f182013a80808082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87582013a81c8808082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a016bd6bdb9a4a99679bb85cfb0d17b49d635c0837589827aa95b23ccb2e6899c9a0041e3d19f409ddef0eb8b3d5197cf7f6097d7ad7c8f55bc976ded6195e27f373","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f282013a81c8808082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85282013a808082ea6082ea60808080c001a054c4ed8414c0103afb7d19ad69433cd0e9d3b988f33ace6c050795c583ffa681a06e4aa13eb0459ceeb0b25a8099ec17fa40ca7ca381191a940c68199a95e8d98d","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a808082ea6082ea60808080c0"},{"input":"0x02f85282013a81c88082ea6082ea60808080c001a0d3b7b7cd9a61dd80070781bbba5b4b84a8006715e628ea0c0f891446e524d2f89fd149593d7205b5baf322da6c1a770b402b4cd075cf476f6bdc5331c9ebb5c3","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c88082ea6082ea60808080c0"},{"input":"0x02f85282013a808082ea6082ea60808080c001a054c4ed8414c0103afb7d19ad69433cd0e9d3b988f33ace6c050795c583ffa681a06e4aa13eb0459ceeb0b25a8099ec17fa40ca7ca381191a940c68199a95e8d98d","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a808082ea6082ea60808080c0"},{"input":"0x02f85282013a81c88082ea6082ea60808080c001a0d3b7b7cd9a61dd80070781bbba5b4b84a8006715e628ea0c0f891446e524d2f89fd149593d7205b5baf322da6c1a770b402b4cd075cf476f6bdc5331c9ebb5c3","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c88082ea6082ea60808080c0"},{"input":"0x02f87682013a808082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0ca465473de19d169c5ec35304368da7d03767e8bbbdfc3e01a7b38822a3e2899a05ea2562bbd0bf90d2ee8944997129ff42618b123b7b6d3221208c34c37e6d7b2","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f382013a808082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87782013a81c88082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a025e211e1e66aa58549f12d352f1f7c4c644c0a208c0ccf48e1c0b6642ad1fadfa07275f712cf8e1f257722893c99fcd794aabf4eb2124ce2025cc404d326f69f95","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f482013a81c88082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85182013a8082ea608082ea60808080c0019fadd8e2ba459fd435d0e5ff745057e84f2ef3982952da514d1b59e12acb8639a04ee061901f178335a5498809e96e5984186e3015800ac95ae05bb17c2ad0eb2f","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea608082ea60808080c0"},{"input":"0x02f85382013a81c882ea608082ea60808080c001a05ae3f41d9ecdbf2f944f532cd63afafdcb6ea93d849925a3713af37133acacfba06f3fca48b5d91f3f7475a3b24bc9e406584dfca736a3e818506156a8a2955f77","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea608082ea60808080c0"},{"input":"0x02f85182013a8082ea608082ea60808080c0019fadd8e2ba459fd435d0e5ff745057e84f2ef3982952da514d1b59e12acb8639a04ee061901f178335a5498809e96e5984186e3015800ac95ae05bb17c2ad0eb2f","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea608082ea60808080c0"},{"input":"0x02f85382013a81c882ea608082ea60808080c001a05ae3f41d9ecdbf2f944f532cd63afafdcb6ea93d849925a3713af37133acacfba06f3fca48b5d91f3f7475a3b24bc9e406584dfca736a3e818506156a8a2955f77","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea608082ea60808080c0"},{"input":"0x02f87682013a8082ea608082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0df6e0b1a5df0b64974bd19cb70cfba37732beca6b172708d10b27861d2f05a7ca04d16fd21111cd6867a0a341a9355599665bf951d0a8e4bb70714ed73007c309b","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f382013a8082ea608082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87782013a81c882ea608082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a00ffa95e5be67c550de3b12b20ff369cdfc994faafc2c8bbd9fac95dc2d20e904a019ad18231fb1965b13c43f3f5a69f02c21c38fae141c8b391f598e7ed12ecbcb","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f482013a81c882ea608082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85482013a8082ea6082ea6082ea60808080c080a08f0078606ffc32decf3ee390a30f9610b8dc49cb55666a7d037325d367ef09bca0767479734098f684fca992e7237b369343b223940c7a3dcf486ae0763c2d3707","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d182013a8082ea6082ea6082ea60808080c0"},{"input":"0x02f85582013a81c882ea6082ea6082ea60808080c001a088ddf4e4ce8c4ad8b2d1b197ba41692ffd35e91feb4cb7f944019b9fd2b05fa4a04c87674f13d7ea3fa09b4a5f7d84a70206aed313136c5d2cdff65b6028e0efb2","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d282013a81c882ea6082ea6082ea60808080c0"},{"input":"0x02f85482013a8082ea6082ea6082ea60808080c080a08f0078606ffc32decf3ee390a30f9610b8dc49cb55666a7d037325d367ef09bca0767479734098f684fca992e7237b369343b223940c7a3dcf486ae0763c2d3707","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d182013a8082ea6082ea6082ea60808080c0"},{"input":"0x02f85582013a81c882ea6082ea6082ea60808080c001a088ddf4e4ce8c4ad8b2d1b197ba41692ffd35e91feb4cb7f944019b9fd2b05fa4a04c87674f13d7ea3fa09b4a5f7d84a70206aed313136c5d2cdff65b6028e0efb2","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d282013a81c882ea6082ea6082ea60808080c0"},{"input":"0x02f87882013a8082ea6082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a062385c6b319fd40197a42e418525993e08804552961eb34e3de3cdddb1e1c17aa01f846e856717054b4974814048483e8be23f7acb0382e883f8a36e527a2067cb","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f582013a8082ea6082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87982013a81c882ea6082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a091f71b17ab4d27b10ffa21aa757fa361adac0ec0084bc0ca499f5eb7b09677d5a034d9e5954f9a3e4b0f871c3045ccb11ad0e26bc6f4e32c19bc91e80931f2fd5d","output":"{\"to\":null,\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f682013a81c882ea6082ea6082ea608080a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f84e82013a80808080806480c080a0e3e8ae1c2f71c3657729422bc3d48239f877d9133c2608966ccbe899d477b2bba0272d93dbc97ee070fdc86870712795a8a87779f706b45baa1643092290258878","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cb82013a80808080806480c0"},{"input":"0x02f84f82013a81c8808080806480c080a097cb9161ea9dc2753dd6d0866d9b03b39c4cdfef2f44fa6c73fc1d5def02dee9a05e723c214ed4cc12d1df5842e422947805afe44c85e8184855174a673ce68d74","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02cc82013a81c8808080806480c0"},{"input":"0x02f84e82013a80808080806480c080a0e3e8ae1c2f71c3657729422bc3d48239f877d9133c2608966ccbe899d477b2bba0272d93dbc97ee070fdc86870712795a8a87779f706b45baa1643092290258878","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cb82013a80808080806480c0"},{"input":"0x02f84f82013a81c8808080806480c080a097cb9161ea9dc2753dd6d0866d9b03b39c4cdfef2f44fa6c73fc1d5def02dee9a05e723c214ed4cc12d1df5842e422947805afe44c85e8184855174a673ce68d74","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02cc82013a81c8808080806480c0"},{"input":"0x02f87282013a808080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a09459bcb2fd569e48e6d854b142924b3cb5f63b774705f691238fdba4722896bda0137170a8cf29602fcf9a6ae1f692e20371f7ad6a73856e96e0b1f58535b5938c","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ef82013a808080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87382013a81c88080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a09a229970bf10c22518b219419cb17688b2db26110eaef65458b54e7cfb3de4f2a07dc083b02a1e86f4c5037ecc2906e49978da216b5e40d629c5856508c5475f89","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f082013a81c88080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85082013a808082ea6080806480c001a0ad1c4e4febc7dca2391a4a15118f7a86806c2556049ad60cfb8b056fadad3ecfa058f215be70dfa961d7072f4dc438510e9c8db0a5791caa79ff0b7b706cf316ed","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a808082ea6080806480c0"},{"input":"0x02f85182013a81c88082ea6080806480c001a0f31747ec765743a700ca8df903fc916cda923abf5b44a979db85d306f59cbde6a02e139ced4f0a12955f8b3782a7d921cc460f7bb066b65490c81f68ce7f7a50dd","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c88082ea6080806480c0"},{"input":"0x02f85082013a808082ea6080806480c001a0ad1c4e4febc7dca2391a4a15118f7a86806c2556049ad60cfb8b056fadad3ecfa058f215be70dfa961d7072f4dc438510e9c8db0a5791caa79ff0b7b706cf316ed","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a808082ea6080806480c0"},{"input":"0x02f85182013a81c88082ea6080806480c001a0f31747ec765743a700ca8df903fc916cda923abf5b44a979db85d306f59cbde6a02e139ced4f0a12955f8b3782a7d921cc460f7bb066b65490c81f68ce7f7a50dd","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c88082ea6080806480c0"},{"input":"0x02f87482013a808082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0a810714a260023b52cda70890e61f16ee4af42f46fa9e1566f470c453bd25b88a02d6a88558e5692dbd2e59bed18109d505005ae48d5e59742b408dcef5bd9e367","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f182013a808082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87582013a81c88082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d11b1322d114f5b9f9c4b1e5231a2db2171b5608122e0317b29d4959770d7ef6a011fd8d3641694c019c62b010c37af30aa5ab41ee34d8f69b9a9f98a3c1c5d578","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f282013a81c88082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85082013a8082ea608080806480c001a0320db73af75876fedea2856c3aeeb4bb20e6514b1ed0e6c30e81723a7db3daa5a03a7fce79ffb6638b7a44aa1ef72073d6e55cca462c2fca9d85ffe2a246a5908d","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a8082ea608080806480c0"},{"input":"0x02f85182013a81c882ea608080806480c080a0dac7634fa49fc1a622bf8bb562449f712b8d611315d1a4c42437fbab9ce6db84a07b8ee93bb2e8267646b2f0cf6d833b179cea243a64c87d3404a845f2c5f2e5de","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c882ea608080806480c0"},{"input":"0x02f85082013a8082ea608080806480c001a0320db73af75876fedea2856c3aeeb4bb20e6514b1ed0e6c30e81723a7db3daa5a03a7fce79ffb6638b7a44aa1ef72073d6e55cca462c2fca9d85ffe2a246a5908d","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a8082ea608080806480c0"},{"input":"0x02f85182013a81c882ea608080806480c080a0dac7634fa49fc1a622bf8bb562449f712b8d611315d1a4c42437fbab9ce6db84a07b8ee93bb2e8267646b2f0cf6d833b179cea243a64c87d3404a845f2c5f2e5de","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c882ea608080806480c0"},{"input":"0x02f87482013a8082ea6080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a03f00cc26a7c13191e82fe3ee79f43a3c7addeb4b3ff14c1382b5eb7dedac6745a02e994b354d3fa24bf97350f36d5177356f1c40cec2c0ff3e3c6c6620afe2a637","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f182013a8082ea6080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87582013a81c882ea6080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0ec35e69a3bbcf761e388401ce39cd25d7dab736bb200bb632e7d484aad96f506a05a3ee4422b29d1192434c4d3c9d64af412538f0deeeb7d1a18b931eb4af38df3","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f282013a81c882ea6080808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85282013a8082ea6082ea6080806480c080a0eddd719e0b0de7f96549dd1de6f5ef13f9fb5f9e67bdaad08545ca348058c431a0687a16c86b9068385f6175475e91907f9c37377e1a54bd4b41d088d27bc7088a","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea6082ea6080806480c0"},{"input":"0x02f85382013a81c882ea6082ea6080806480c001a0788913be6a613572ca175c16ffaddf01a167b58c32f81defd8e4de53625fc812a004c93984e30bdba3603c1f57feee0b5115a87031e0124516c251cb73ef27de10","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea6082ea6080806480c0"},{"input":"0x02f85282013a8082ea6082ea6080806480c080a0eddd719e0b0de7f96549dd1de6f5ef13f9fb5f9e67bdaad08545ca348058c431a0687a16c86b9068385f6175475e91907f9c37377e1a54bd4b41d088d27bc7088a","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea6082ea6080806480c0"},{"input":"0x02f85382013a81c882ea6082ea6080806480c001a0788913be6a613572ca175c16ffaddf01a167b58c32f81defd8e4de53625fc812a004c93984e30bdba3603c1f57feee0b5115a87031e0124516c251cb73ef27de10","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea6082ea6080806480c0"},{"input":"0x02f87682013a8082ea6082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a07f7f9b35e2759a1b6dc06506c1195468e29fd8b80e1ff90e3f40723a9334fed8a039cac5524d7e57b22be8c4390f8c4aedb2ce7abe8e9c6c0a3fbe90c48d754387","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f382013a8082ea6082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87782013a81c882ea6082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a07eddd29705e5e913d22de47daf6d93b20a7135f0e3ac8aa16c5b1c25efa95815a0082caa535a5e00776dcc47a19aca103f0e815fb984ecf90d3407dcc43655eece","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f482013a81c882ea6082ea60808064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85082013a80808082ea60806480c080a0f8102947aec0f244de8b3c3791a4e4214f693c3314bf50696546f5ca7c54930ca002769e82a368edbb1c9cb53d1857541c1386fcc6d0a11d3e58655025f8ce7312","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a80808082ea60806480c0"},{"input":"0x02f85182013a81c8808082ea60806480c080a0ce8df3004e424af808d77c6a11b2f0391842bbd5ac686e5b432e1e8a1f5fd1d4a00d55cc4ff21cacc2581d8756b0a8227111fdcbb030c6bbd692f775525be29902","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c8808082ea60806480c0"},{"input":"0x02f85082013a80808082ea60806480c080a0f8102947aec0f244de8b3c3791a4e4214f693c3314bf50696546f5ca7c54930ca002769e82a368edbb1c9cb53d1857541c1386fcc6d0a11d3e58655025f8ce7312","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cd82013a80808082ea60806480c0"},{"input":"0x02f85182013a81c8808082ea60806480c080a0ce8df3004e424af808d77c6a11b2f0391842bbd5ac686e5b432e1e8a1f5fd1d4a00d55cc4ff21cacc2581d8756b0a8227111fdcbb030c6bbd692f775525be29902","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ce82013a81c8808082ea60806480c0"},{"input":"0x02f87482013a80808082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a009f2b5837ed29f7b472468b9dc636f39e989796d97ca9400e35f094f234b7231a0345874d675700c4488978338d12a7c62f9d8a0096798342bff7f35e444604154","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f182013a80808082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87582013a81c8808082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09c0580e0842bb3f50d0a28402f4a99fd6e156b978633b629c0f2cc4172252153a051f9ecab32316215af8da2abb81dcf859aec81ad75fc0e22f2c281d512333928","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f282013a81c8808082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85282013a808082ea6082ea60806480c080a0fcdad7ad0562a7129b4cc73bfb8565959feb5a8259993cd68ece3d6f6bd4f0e1a0121e6ae7e4819a865ffbacbe8ad67d5cc8634e9dbe8f5528cc8841c9d4453ccc","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a808082ea6082ea60806480c0"},{"input":"0x02f85382013a81c88082ea6082ea60806480c001a083dc862eaf257068751ac014ff021ab5a729c68fc3f665bcc3abe3d7393cb0d2a02400cc4059f66c8feaa0d717a659d763ac5d4d067a45b9571037f5d573fdd7c9","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c88082ea6082ea60806480c0"},{"input":"0x02f85282013a808082ea6082ea60806480c080a0fcdad7ad0562a7129b4cc73bfb8565959feb5a8259993cd68ece3d6f6bd4f0e1a0121e6ae7e4819a865ffbacbe8ad67d5cc8634e9dbe8f5528cc8841c9d4453ccc","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a808082ea6082ea60806480c0"},{"input":"0x02f85382013a81c88082ea6082ea60806480c001a083dc862eaf257068751ac014ff021ab5a729c68fc3f665bcc3abe3d7393cb0d2a02400cc4059f66c8feaa0d717a659d763ac5d4d067a45b9571037f5d573fdd7c9","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c88082ea6082ea60806480c0"},{"input":"0x02f87682013a808082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0a5b1529fb5158abf54d61689aeafbcf7aba86c95efa53726299c64349e8cb277a06f68a0c684c1e1ddba000a3bdd18019011ca5a01f627914445a8e8ff77d11eed","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f382013a808082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87782013a81c88082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a049bc9983635fe90f3c5ac42c4919695c2bd1bf3600e731a85e671923dab1d24da02c2140f4339ff23855d599c7c88257709fbf6a363bcd7211e6218cf1ad3936d9","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f482013a81c88082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85282013a8082ea608082ea60806480c001a03a08bf6de5d7d22ca0154c5a705bb3f5b0e3105c343eabc0307fc99bb89f91d0a021f8c7c5867ca59acd770eb06ac8c4488bd8f40c4bfd0841185ac60f8cb311de","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea608082ea60806480c0"},{"input":"0x02f85382013a81c882ea608082ea60806480c001a09449cea6b7b492b80c6ebe08588c3b3bb94f73cddcf5b60a4bdf9c10462f9264a01682ef0a841044bd58f392b252418d9dd473e08bf561632976126652f1ac1b88","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea608082ea60806480c0"},{"input":"0x02f85282013a8082ea608082ea60806480c001a03a08bf6de5d7d22ca0154c5a705bb3f5b0e3105c343eabc0307fc99bb89f91d0a021f8c7c5867ca59acd770eb06ac8c4488bd8f40c4bfd0841185ac60f8cb311de","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02cf82013a8082ea608082ea60806480c0"},{"input":"0x02f85382013a81c882ea608082ea60806480c001a09449cea6b7b492b80c6ebe08588c3b3bb94f73cddcf5b60a4bdf9c10462f9264a01682ef0a841044bd58f392b252418d9dd473e08bf561632976126652f1ac1b88","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d082013a81c882ea608082ea60806480c0"},{"input":"0x02f87682013a8082ea608082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a07e9ba318f9c63d1a6b2afeccbca77eb512e85e1ac661e6fc050970449c4b2a26a0478a9c96e9d9e900dc3ae4dba33a425254ff3578af30255c1be68fe316c40119","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f382013a8082ea608082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87782013a81c882ea608082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a08c6c7c76220d18df6d45175256f1c615757a456163bcf732a605d9a063509c79a022b364054d69f91213c521ffad2d0c5c349a5808b03543d4813f8b23e342e8f3","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f482013a81c882ea608082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85482013a8082ea6082ea6082ea60806480c080a0825a042c44b472317d1387a9cf190f4d0c770a84300c299452ff5a220e2c1590a00403abbbf3b147d4895f68305b0db21be477616987d604cea0615cf6ac377a77","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d182013a8082ea6082ea6082ea60806480c0"},{"input":"0x02f85582013a81c882ea6082ea6082ea60806480c001a044bdb37e9b26c8a09c9ce61acd01f68badc063005f5dcbfd32836f5950049695a0499a9d378b97c4a5bb6ec105a00ad581b224bf227439799d0b97ea8f5faee767","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d282013a81c882ea6082ea6082ea60806480c0"},{"input":"0x02f85482013a8082ea6082ea6082ea60806480c080a0825a042c44b472317d1387a9cf190f4d0c770a84300c299452ff5a220e2c1590a00403abbbf3b147d4895f68305b0db21be477616987d604cea0615cf6ac377a77","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d182013a8082ea6082ea6082ea60806480c0"},{"input":"0x02f85582013a81c882ea6082ea6082ea60806480c001a044bdb37e9b26c8a09c9ce61acd01f68badc063005f5dcbfd32836f5950049695a0499a9d378b97c4a5bb6ec105a00ad581b224bf227439799d0b97ea8f5faee767","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d282013a81c882ea6082ea6082ea60806480c0"},{"input":"0x02f87882013a8082ea6082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c73e7dcd11a8f1e1b59b81f5e626c898597c84dc540966dad1dbb51fcc106814a0352250cf688c74808d2939ff919cdef19a4c9c9fa61698224565ce912a20429a","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f582013a8082ea6082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87982013a81c882ea6082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a08020e129ccb7034377ea4cbb03a313997c6e9a370891de88d1d3d61c0e87e1b4a00668935ceed45538772745b760bb6471e79963e44ffce163440240b3578cd262","output":"{\"to\":null,\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f682013a81c882ea6082ea6082ea608064a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85682013a8080808080880de0b6b3a764000080c080a0f31df874009680817aea98fdd57d6a0f8d596e251b573e86d44e0b944a7913f3a03baa16757b2351cd5784c4b0213e66c2f3839e98af99f20606d8c33d9d45f661","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d382013a8080808080880de0b6b3a764000080c0"},{"input":"0x02f85782013a81c880808080880de0b6b3a764000080c001a0cf54b9a861cf6a6951b38578a73de9c51688b124b8e7dd0ee7ba10f0136e4efba003a5bf873c58a9dc269727e1ebc2b2c690ed2e7e3a64482c1f58e6b526092944","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d482013a81c880808080880de0b6b3a764000080c0"},{"input":"0x02f85682013a8080808080880de0b6b3a764000080c080a0f31df874009680817aea98fdd57d6a0f8d596e251b573e86d44e0b944a7913f3a03baa16757b2351cd5784c4b0213e66c2f3839e98af99f20606d8c33d9d45f661","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d382013a8080808080880de0b6b3a764000080c0"},{"input":"0x02f85782013a81c880808080880de0b6b3a764000080c001a0cf54b9a861cf6a6951b38578a73de9c51688b124b8e7dd0ee7ba10f0136e4efba003a5bf873c58a9dc269727e1ebc2b2c690ed2e7e3a64482c1f58e6b526092944","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d482013a81c880808080880de0b6b3a764000080c0"},{"input":"0x02f87a82013a8080808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0907be9b9c50d1ebc3489dde0599802464a2792e6a4c3014d2d42db50f4ba3c75a02330372fe92742fce0fe28bc0583813f17640e3c5080141008b4ac803e61911a","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f782013a8080808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87b82013a81c880808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09c566bb25bc5010ed9c7a5eedb662d3390bf065fcc10571125dc248d810a6731a024531646c80970757cff49228a332f238d8e3e4ce7038fd6943610f877eedff9","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83882013a81c880808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85882013a808082ea608080880de0b6b3a764000080c001a0b7079e6b62999bc486de04bed329e79a52aa17752ba4880b996710349b40c7e1a014869354847aebbfdddc473183b9baa8584d86b2dc48ecf647ae20430f146f35","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d582013a808082ea608080880de0b6b3a764000080c0"},{"input":"0x02f85982013a81c88082ea608080880de0b6b3a764000080c080a04f4cd4dcfef7d6a5b654d9bdf8a89c4929dc32e375d199b40f5a114b9edabd45a01aabbcd6fb9b5f96597f180a70ba57e8d238995c392c96bb204879f134a12a93","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d682013a81c88082ea608080880de0b6b3a764000080c0"},{"input":"0x02f85882013a808082ea608080880de0b6b3a764000080c001a0b7079e6b62999bc486de04bed329e79a52aa17752ba4880b996710349b40c7e1a014869354847aebbfdddc473183b9baa8584d86b2dc48ecf647ae20430f146f35","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d582013a808082ea608080880de0b6b3a764000080c0"},{"input":"0x02f85982013a81c88082ea608080880de0b6b3a764000080c080a04f4cd4dcfef7d6a5b654d9bdf8a89c4929dc32e375d199b40f5a114b9edabd45a01aabbcd6fb9b5f96597f180a70ba57e8d238995c392c96bb204879f134a12a93","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d682013a81c88082ea608080880de0b6b3a764000080c0"},{"input":"0x02f87c82013a808082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a06f5262d03380227973c7507fbb1931ce3ae684521b2803d967fc67cead8734fda02801ed8fc53083936ec4bbefa3216a5241d25c8940aa16c9bc0ed51d302907b7","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83982013a808082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87d82013a81c88082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a046b8e78656538c428e14378d9e3fe1c19a3a9a96251aeb7b04fd416858ceff23a017bd04e5f3059b34c8c9a42a0ee67517bf67592c95d4b8553716808e8fde4400","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83a82013a81c88082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85882013a8082ea60808080880de0b6b3a764000080c001a0d93a26bd5294c50b7dedf5c6d7cbc5a1a5ec40d80cfb811f463788b80dc45d0fa05598f7fc9dff96717999450035d4f9b503988b616e3990a20d165cdd638be362","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d582013a8082ea60808080880de0b6b3a764000080c0"},{"input":"0x02f85982013a81c882ea60808080880de0b6b3a764000080c001a08890ab494d178516833623e2c05e7ffb600ec980b141f8b6e8733a94bf7b5289a04bc7ff419352ca8eb396d1b7b57d50bc1ec80a236a71055241bf2e7535f92901","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d682013a81c882ea60808080880de0b6b3a764000080c0"},{"input":"0x02f85882013a8082ea60808080880de0b6b3a764000080c001a0d93a26bd5294c50b7dedf5c6d7cbc5a1a5ec40d80cfb811f463788b80dc45d0fa05598f7fc9dff96717999450035d4f9b503988b616e3990a20d165cdd638be362","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d582013a8082ea60808080880de0b6b3a764000080c0"},{"input":"0x02f85982013a81c882ea60808080880de0b6b3a764000080c001a08890ab494d178516833623e2c05e7ffb600ec980b141f8b6e8733a94bf7b5289a04bc7ff419352ca8eb396d1b7b57d50bc1ec80a236a71055241bf2e7535f92901","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d682013a81c882ea60808080880de0b6b3a764000080c0"},{"input":"0x02f87c82013a8082ea60808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0d9d52087d771d397a9e1630452cc4649671941c3486b0bb24019087b4d897769a04e2ecc1e542690cfcf341c6fce6987ac4cd8cdecdbdfd5c9521d268d2085c84e","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83982013a8082ea60808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87d82013a81c882ea60808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0eadf0da93be9c532794684382b7fe49093a093a8f082b79b4b9bb9ee686de274a021648c60e5dd6e4c3e102befcfdc4512a246e9d66e24fff3053545144955cb80","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83a82013a81c882ea60808080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85a82013a8082ea6082ea608080880de0b6b3a764000080c080a0ee344021ecd122a30b44535901718a9eefd06f5ecccc41c530c1e9cfe4cd1f13a07425a2aa7612bb4e5cffe400af1d162ab8cdbe185d92b1e6c166de7fa571a9f3","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d782013a8082ea6082ea608080880de0b6b3a764000080c0"},{"input":"0x02f85b82013a81c882ea6082ea608080880de0b6b3a764000080c001a0da86086e5a046a1b41dbc4b55b97ba517696db207e0a9da2e3b46af1507d17c5a07779b1c12a3dc37378985b5d7a4fcc95f1fbd0cbba522d7cc1a20d535faeb30e","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d882013a81c882ea6082ea608080880de0b6b3a764000080c0"},{"input":"0x02f85a82013a8082ea6082ea608080880de0b6b3a764000080c080a0ee344021ecd122a30b44535901718a9eefd06f5ecccc41c530c1e9cfe4cd1f13a07425a2aa7612bb4e5cffe400af1d162ab8cdbe185d92b1e6c166de7fa571a9f3","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d782013a8082ea6082ea608080880de0b6b3a764000080c0"},{"input":"0x02f85b82013a81c882ea6082ea608080880de0b6b3a764000080c001a0da86086e5a046a1b41dbc4b55b97ba517696db207e0a9da2e3b46af1507d17c5a07779b1c12a3dc37378985b5d7a4fcc95f1fbd0cbba522d7cc1a20d535faeb30e","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d882013a81c882ea6082ea608080880de0b6b3a764000080c0"},{"input":"0x02f87e82013a8082ea6082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0fc1e27c941325403a95a8adf21eb77fbc947b20a0a1dabb245ab514ef09d0171a03e014156069072bf3682e34da2719af03d784691c199beb012c71682dfda20ae","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83b82013a8082ea6082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87f82013a81c882ea6082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a03412dc4f1e4b8de363a753b08e7bed249c1e3c5a95515f5a50997ad7f3bb6c50a05fb5a87024da9009bb50337a28bd86dd448c3772ba93abba952d9c3b5fc927f3","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83c82013a81c882ea6082ea608080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85882013a80808082ea6080880de0b6b3a764000080c080a00b2cab27d46f5b18c95bcbbbbdbc128e1798e8c633f3126f8e0b4d47822899c8a00e2dfee3c9698df3dacea25569c96f927baa0e83a2dc3804c2007215ff1fd16e","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d582013a80808082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85982013a81c8808082ea6080880de0b6b3a764000080c001a098d11fdf17851c4393dcdeca863464936f901cb89b5959be1d5fbe5cf47fce64a070acf0beaedc55af8a67b637d43dd8f70b10026501f146b32ec9b413689c3018","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d682013a81c8808082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85882013a80808082ea6080880de0b6b3a764000080c080a00b2cab27d46f5b18c95bcbbbbdbc128e1798e8c633f3126f8e0b4d47822899c8a00e2dfee3c9698df3dacea25569c96f927baa0e83a2dc3804c2007215ff1fd16e","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d582013a80808082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85982013a81c8808082ea6080880de0b6b3a764000080c001a098d11fdf17851c4393dcdeca863464936f901cb89b5959be1d5fbe5cf47fce64a070acf0beaedc55af8a67b637d43dd8f70b10026501f146b32ec9b413689c3018","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d682013a81c8808082ea6080880de0b6b3a764000080c0"},{"input":"0x02f87c82013a80808082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a062d300529917ba1c03873cfdf35ef669d0e1d3f52115b305916609f1659d67c4a0111fc401c2a10f874c02c20b3e938412f9c35f24fb83b121fadb8fd107182726","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83982013a80808082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87d82013a81c8808082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02b66e32d2b4bda0ddbfa74d8ea2f8a0267ba7019abb366e0d3f8b247b129982aa02c163d63b6e727cbd6b17add5b2f98d60b7cbe00af5b0224d809e94924a5c129","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83a82013a81c8808082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85a82013a808082ea6082ea6080880de0b6b3a764000080c001a0055ae00874a381bf2c6f1b518f318270f2c4109b2eb071fcc17d934b1faf2c81a04a2a6dd88a31a2929656581bf31d42109beb431dbc99bdea4ec0636d697b7137","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d782013a808082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85b82013a81c88082ea6082ea6080880de0b6b3a764000080c080a0ceb7acb4eccb1683ed065ed0df5b3e1e747a741449c17f4170fb4688af79169da04647dc401f5170ce6824219e4b877100a79ff252e73177bc3be6904f04457c5e","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d882013a81c88082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85a82013a808082ea6082ea6080880de0b6b3a764000080c001a0055ae00874a381bf2c6f1b518f318270f2c4109b2eb071fcc17d934b1faf2c81a04a2a6dd88a31a2929656581bf31d42109beb431dbc99bdea4ec0636d697b7137","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d782013a808082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85b82013a81c88082ea6082ea6080880de0b6b3a764000080c080a0ceb7acb4eccb1683ed065ed0df5b3e1e747a741449c17f4170fb4688af79169da04647dc401f5170ce6824219e4b877100a79ff252e73177bc3be6904f04457c5e","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d882013a81c88082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f87e82013a808082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a020e9ce595105ad63212d5c8e1e598d2d90347c94ca57aa8454768222b5ee91a5a019bcc3d4692a00fbf0ee91605eb2ebd7aab58f94ead5f03b005cee1e6a328a9a","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83b82013a808082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87f82013a81c88082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0203be5acf80eea1a22f151475993fdea0b429a90a7ce53554f303309ba5cbb75a05ba185145f6f072158cff4a2c63bb4e7c5ffaf3ef2029d2688c24cd238495657","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83c82013a81c88082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85a82013a8082ea608082ea6080880de0b6b3a764000080c080a02200b34117aec43fbf36c19584d3f5371662e02e06e41ab18893f6ea4eae9db7a036cfb853039002767d6b41625ffdf926ab2adec33d668b83209f609d4a64661b","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d782013a8082ea608082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85b82013a81c882ea608082ea6080880de0b6b3a764000080c001a0d5bf42725de1f608b811ad94bd96f1419606b5edc2b2885252be998e21c19e64a026b18d19b371fc68a48c0b348192956158cfc3aef4c2995a04e455193156ab9b","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d882013a81c882ea608082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85a82013a8082ea608082ea6080880de0b6b3a764000080c080a02200b34117aec43fbf36c19584d3f5371662e02e06e41ab18893f6ea4eae9db7a036cfb853039002767d6b41625ffdf926ab2adec33d668b83209f609d4a64661b","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d782013a8082ea608082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85b82013a81c882ea608082ea6080880de0b6b3a764000080c001a0d5bf42725de1f608b811ad94bd96f1419606b5edc2b2885252be998e21c19e64a026b18d19b371fc68a48c0b348192956158cfc3aef4c2995a04e455193156ab9b","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02d882013a81c882ea608082ea6080880de0b6b3a764000080c0"},{"input":"0x02f87e82013a8082ea608082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0048571da63d488ba7d100e9ef32c0b4a06ffe51354e82a40ff953c377959033da069fae6fc2a68e6f96480fbb2768ba3bdcb9c9fa5523fe3ca428ef2d377b67ed2","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83b82013a8082ea608082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87f82013a81c882ea608082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0e4bf87c6adc1f903dde3bae06b12c00d62e0b4bb8178669b9cb3f658d43059dba07f0cdef6581cbb312cceef38703b154e5744df03693423e07aa2464daf67fc28","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83c82013a81c882ea608082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f85c82013a8082ea6082ea6082ea6080880de0b6b3a764000080c001a0adb3a7c410f3a4d89792fcede5c7a5437c5fab3881d9145cdbf96ee6d4050311a066866bcc349f1e78ea7ecb8d71c649cd3e8bcf8614494c6facd5c2dd68a6352f","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d982013a8082ea6082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85d82013a81c882ea6082ea6082ea6080880de0b6b3a764000080c080a0ba51668b00de8e0355e0ab99c49377f61be25d30f7598bd798178fa500bbe262a06bba3437dea06d5a63f3122d4fa7013238e47289b5801bf18444383180484900","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02da82013a81c882ea6082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85c82013a8082ea6082ea6082ea6080880de0b6b3a764000080c001a0adb3a7c410f3a4d89792fcede5c7a5437c5fab3881d9145cdbf96ee6d4050311a066866bcc349f1e78ea7ecb8d71c649cd3e8bcf8614494c6facd5c2dd68a6352f","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02d982013a8082ea6082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f85d82013a81c882ea6082ea6082ea6080880de0b6b3a764000080c080a0ba51668b00de8e0355e0ab99c49377f61be25d30f7598bd798178fa500bbe262a06bba3437dea06d5a63f3122d4fa7013238e47289b5801bf18444383180484900","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02da82013a81c882ea6082ea6082ea6080880de0b6b3a764000080c0"},{"input":"0x02f88082013a8082ea6082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0a960e8dd8eafea3b38178e802b4ed9d4ef3613c9de83c96a5bb9178565bb0b90a0250e480879b64829e0ca2296c6a5f07b4d96bf2654ec70af3be447a01ca950ad","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83d82013a8082ea6082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88182013a81c882ea6082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c5b361f372b4b9f115de0510913e11e80b22649ec6a54fdd6cd8c70abe1c4fa4a00c109e6bf278b5bc9e93f293c1ed7cdfebf315d908a7f9c1ae255e165832c37c","output":"{\"to\":null,\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f83e82013a81c882ea6082ea6082ea6080880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"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 00000000000..a79238bf8d2 --- /dev/null +++ b/chain/types/ethtypes/eth_types.go @@ -0,0 +1,600 @@ +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) { + if e == 0 { + return json.Marshal("0x0") + } + return json.Marshal(fmt.Sprintf("0x%x", e)) +} + +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 +} + +// 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) { + // 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 + } + 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 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 (h EthHash) String() string { + return "0x" + hex.EncodeToString(h[:]) +} + +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 cid of the 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 00000000000..89c38ba29f8 --- /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 00000000000..049ea6fc4c2 --- /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 00000000000..bdbedff0052 --- /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 00000000000..00c25ca4cb6 --- /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 107c1fbe3ab..8e8d9192bb2 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 57761680d20..b0db3b74db4 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 00000000000..e1364e654d8 --- /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 00000000000..f0b341f5597 --- /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 cb981e0f01d..c1aa90fc9da 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 2702153b6a7..83ad8131505 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 fbc96d2c388..ce9f1baf872 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 65311058609..2ac70e6ef4a 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 ca6e5571aa5..c9007d3f104 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 a97a454bf0f..47bd2e326e0 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 05f8de2f095..daa55e4f4bd 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 f6adc894030..68dbbb2df0a 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 ecd87caf050..e18c4aea960 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 66053525b55..4220666108e 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 2f382d5f8ea..76af663c780 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 9270c990bb4..57316d22d25 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/cmd.go b/cli/cmd.go index 79023917b46..802df0c99ac 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 00000000000..afc202e7971 --- /dev/null +++ b/cli/evm.go @@ -0,0 +1,503 @@ +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", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "filAddr", + Usage: "Filecoin address", + }, + &cli.StringFlag{ + Name: "ethAddr", + Usage: "Ethereum address", + }, + }, + Action: func(cctx *cli.Context) error { + + filAddr := cctx.String("filAddr") + ethAddr := cctx.String("ethAddr") + + var faddr address.Address + var eaddr ethtypes.EthAddress + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if filAddr != "" { + addr, err := address.NewFromString(filAddr) + if err != nil { + return err + } + eaddr, faddr, err = ethAddrFromFilecoinAddress(ctx, addr, api) + if err != nil { + return err + } + } else if ethAddr != "" { + eaddr, err = ethtypes.ParseEthAddress(ethAddr) + if err != nil { + return err + } + faddr, err = eaddr.ToFilecoinAddress() + if err != nil { + return err + } + } else { + return xerrors.Errorf("Neither filAddr nor ethAddr specified") + } + + 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 := hex.DecodeString(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 := hex.DecodeString(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 := hex.DecodeString(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 = hex.DecodeString(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 + } + + nonce, err := api.MpoolGetNonce(ctx, fromAddr) + if err != nil { + nonce = 0 // assume a zero nonce on error (e.g. sender doesn't exist). + } + + params, err := actors.SerializeParams(&eam.CreateParams{ + Initcode: contract, + Nonce: nonce, + }) + 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.Create, + 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 || argc > 3 { + return xerrors.Errorf("must pass the address, entry point and (optionally) input data") + } + + 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 = hex.DecodeString(cctx.Args().Get(2)) + 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: abi.MethodNum(2), + 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/state.go b/cli/state.go index 434fb1a1cd7..9513098d360 100644 --- a/cli/state.go +++ b/cli/state.go @@ -776,6 +776,7 @@ 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) + 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 c0a3c4a5345..13824d07d62 100644 --- a/cmd/lotus-bench/import.go +++ b/cmd/lotus-bench/import.go @@ -41,6 +41,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 1970d5074ba..41993a16990 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 373964dc6c9..38f5ee6fefe 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 ce570c6acd2..a7e0ee34fa7 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" @@ -47,9 +49,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", @@ -65,16 +67,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 err + return fmt.Errorf("failed to parse network version: %w", err) } - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) + upgradeActorsFunc, preUpgradeActorsFunc, checkInvariantsFunc, err := getMigrationFuncsForNetwork(network.Version(nv)) + if err != nil { + return err } - 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) } @@ -129,7 +136,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 } @@ -142,7 +149,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 { @@ -151,7 +158,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 } @@ -165,7 +172,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 } @@ -174,7 +181,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 } @@ -191,7 +198,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 } @@ -201,7 +211,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 341877a88c3..4dddba921ac 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 fc03a48d8c5..39bfecd9f5e 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -27,7 +27,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 cb94cf3f753..d18be03f5b9 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 89b42b53ef4..fe639b2f3b2 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 24fd88c9591..fe5dd542c19 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,38 @@ * [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) + * [EthGetStorageAt](#EthGetStorageAt) + * [EthGetTransactionByBlockHashAndIndex](#EthGetTransactionByBlockHashAndIndex) + * [EthGetTransactionByBlockNumberAndIndex](#EthGetTransactionByBlockNumberAndIndex) + * [EthGetTransactionByHash](#EthGetTransactionByHash) + * [EthGetTransactionCount](#EthGetTransactionCount) + * [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 +168,7 @@ * [NetDisconnect](#NetDisconnect) * [NetFindPeer](#NetFindPeer) * [NetLimit](#NetLimit) + * [NetListening](#NetListening) * [NetPeerInfo](#NetPeerInfo) * [NetPeers](#NetPeers) * [NetPing](#NetPing) @@ -144,6 +178,7 @@ * [NetPubsubScores](#NetPubsubScores) * [NetSetLimit](#NetSetLimit) * [NetStat](#NetStat) + * [NetVersion](#NetVersion) * [Node](#Node) * [NodeStatus](#NodeStatus) * [Paych](#Paych) @@ -581,6 +616,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 +832,10 @@ Response: { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } ] ``` @@ -1291,7 +1360,9 @@ Inputs: { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - null + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } ] ``` @@ -1303,7 +1374,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "Piece": null, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Size": 42, "MinPrice": "0", "UnsealPrice": "0", @@ -1314,7 +1387,9 @@ Response: "MinerPeer": { "Address": "f01234", "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } } } ] @@ -1385,7 +1460,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1489,7 +1566,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1554,7 +1633,9 @@ Response: "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, "ID": 5, - "PieceCID": null, + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PricePerByte": "0", "UnsealPrice": "0", "Status": 0, @@ -1727,7 +1808,9 @@ Response: "Root": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, - "PieceCid": null, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PieceSize": 1024, "RawBlockSize": 42 }, @@ -1792,7 +1875,9 @@ Response: { "Key": 50, "Err": "string value", - "Root": null, + "Root": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "Source": "string value", "FilePath": "string value", "CARPath": "string value" @@ -1816,7 +1901,9 @@ Response: "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" }, "ID": 5, - "PieceCID": null, + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "PricePerByte": "0", "UnsealPrice": "0", "Status": 0, @@ -1864,8 +1951,748 @@ Response: ] ``` -### ClientMinerQueryOffer -ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. +### ClientMinerQueryOffer +ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. + + +Perms: read + +Inputs: +```json +[ + "f01234", + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } +] +``` + +Response: +```json +{ + "Err": "string value", + "Root": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42, + "MinPrice": "0", + "UnsealPrice": "0", + "PricePerByte": "0", + "PaymentInterval": 42, + "PaymentIntervalIncrease": 42, + "Miner": "f01234", + "MinerPeer": { + "Address": "f01234", + "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } + } +} +``` + +### ClientQueryAsk +ClientQueryAsk returns a signed StorageAsk from the specified miner. + + +Perms: read + +Inputs: +```json +[ + "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", + "f01234" +] +``` + +Response: +```json +{ + "Response": { + "Price": "0", + "VerifiedPrice": "0", + "MinPieceSize": 1032, + "MaxPieceSize": 1032, + "Miner": "f01234", + "Timestamp": 10101, + "Expiry": 10101, + "SeqNo": 42 + }, + "DealProtocols": [ + "string value" + ] +} +``` + +### ClientRemoveImport +ClientRemoveImport removes file import + + +Perms: admin + +Inputs: +```json +[ + 50 +] +``` + +Response: `{}` + +### ClientRestartDataTransfer +ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer + + +Perms: write + +Inputs: +```json +[ + 3, + "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", + true +] +``` + +Response: `{}` + +### ClientRetrieve +ClientRetrieve initiates the retrieval of a file, as specified in the order. + + +Perms: admin + +Inputs: +```json +[ + { + "Root": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Piece": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "DataSelector": "Links/21/Hash/Links/42/Hash", + "Size": 42, + "Total": "0", + "UnsealPrice": "0", + "PaymentInterval": 42, + "PaymentIntervalIncrease": 42, + "Client": "f01234", + "Miner": "f01234", + "MinerPeer": { + "Address": "f01234", + "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", + "PieceCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } + }, + "RemoteStore": "00000000-0000-0000-0000-000000000000" + } +] +``` + +Response: +```json +{ + "DealID": 5 +} +``` + +### ClientRetrieveTryRestartInsufficientFunds +ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel +which are stuck due to insufficient funds + + +Perms: write + +Inputs: +```json +[ + "f01234" +] +``` + +Response: `{}` + +### ClientRetrieveWait +ClientRetrieveWait waits for retrieval to be complete + + +Perms: admin + +Inputs: +```json +[ + 5 +] +``` + +Response: `{}` + +### ClientStartDeal +ClientStartDeal proposes a deal with a miner. + + +Perms: admin + +Inputs: +```json +[ + { + "Data": { + "TransferType": "string value", + "Root": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "PieceSize": 1024, + "RawBlockSize": 42 + }, + "Wallet": "f01234", + "Miner": "f01234", + "EpochPrice": "0", + "MinBlocksDuration": 42, + "ProviderCollateral": "0", + "DealStartEpoch": 10101, + "FastRetrieval": true, + "VerifiedDeal": true + } +] +``` + +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` + +### ClientStatelessDeal +ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. + + +Perms: write + +Inputs: +```json +[ + { + "Data": { + "TransferType": "string value", + "Root": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "PieceCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "PieceSize": 1024, + "RawBlockSize": 42 + }, + "Wallet": "f01234", + "Miner": "f01234", + "EpochPrice": "0", + "MinBlocksDuration": 42, + "ProviderCollateral": "0", + "DealStartEpoch": 10101, + "FastRetrieval": true, + "VerifiedDeal": true + } +] +``` + +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` + +## Create + + +### CreateBackup +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 +the path specified when calling CreateBackup is within the base path + + +Perms: admin + +Inputs: +```json +[ + "string value" +] +``` + +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 +[ + {} +] +``` + +### 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 @@ -1873,39 +2700,35 @@ Perms: read Inputs: ```json [ - "f01234", - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - null + "0x5", + "0x5" ] ``` Response: ```json { - "Err": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": null, - "Size": 42, - "MinPrice": "0", - "UnsealPrice": "0", - "PricePerByte": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null - } + "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" } ``` -### ClientQueryAsk -ClientQueryAsk returns a signed StorageAsk from the specified miner. +### EthGetTransactionByHash Perms: read @@ -1913,167 +2736,345 @@ Perms: read Inputs: ```json [ - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "f01234" + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ] ``` Response: ```json { - "Response": { - "Price": "0", - "VerifiedPrice": "0", - "MinPieceSize": 1032, - "MaxPieceSize": 1032, - "Miner": "f01234", - "Timestamp": 10101, - "Expiry": 10101, - "SeqNo": 42 - }, - "DealProtocols": [ - "string value" - ] + "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" } ``` -### ClientRemoveImport -ClientRemoveImport removes file import +### EthGetTransactionCount -Perms: admin +Perms: read Inputs: ```json [ - 50 + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "string value" ] ``` -Response: `{}` +Response: `"0x5"` -### ClientRestartDataTransfer -ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer +### EthGetTransactionReceipt -Perms: write +Perms: read Inputs: ```json [ - 3, - "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - true + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" ] ``` -Response: `{}` +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" +} +``` -### ClientRetrieve -ClientRetrieve initiates the retrieval of a file, as specified in the order. +### EthMaxPriorityFeePerGas -Perms: admin +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 [ { - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "Piece": null, - "DataSelector": "Links/21/Hash/Links/42/Hash", - "Size": 42, - "Total": "0", - "UnsealPrice": "0", - "PaymentInterval": 42, - "PaymentIntervalIncrease": 42, - "Client": "f01234", - "Miner": "f01234", - "MinerPeer": { - "Address": "f01234", - "ID": "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf", - "PieceCID": null - }, - "RemoteStore": "00000000-0000-0000-0000-000000000000" + "fromBlock": "2301220", + "address": [ + "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031" + ], + "topics": null } ] ``` Response: ```json -{ - "DealID": 5 -} +[ + 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 +] ``` -### ClientRetrieveTryRestartInsufficientFunds -ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel -which are stuck due to insufficient funds +### EthNewPendingTransactionFilter +Installs a persistent filter to notify when new messages arrive in the message pool. Perms: write -Inputs: +Inputs: `null` + +Response: ```json [ - "f01234" + 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: `{}` +### EthProtocolVersion -### ClientRetrieveWait -ClientRetrieveWait waits for retrieval to be complete +Perms: read + +Inputs: `null` + +Response: `"0x5"` -Perms: admin +### EthSendRawTransaction + + +Perms: read Inputs: ```json [ - 5 + "0x07" ] ``` -Response: `{}` +Response: `"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"` -### ClientStartDeal -ClientStartDeal proposes a deal with a miner. +### 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: admin +Perms: write Inputs: ```json [ + "string value", { - "Data": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": null, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "Wallet": "f01234", - "Miner": "f01234", - "EpochPrice": "0", - "MinBlocksDuration": 42, - "ProviderCollateral": "0", - "DealStartEpoch": 10101, - "FastRetrieval": true, - "VerifiedDeal": true + "topics": [ + [ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" + ] + ] } ] ``` -Response: `null` +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": {} +} +``` -### ClientStatelessDeal -ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. +### EthUninstallFilter +Uninstalls a filter with given id. Perms: write @@ -2081,50 +3082,92 @@ Perms: write Inputs: ```json [ - { - "Data": { - "TransferType": "string value", - "Root": { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - "PieceCid": null, - "PieceSize": 1024, - "RawBlockSize": 42 - }, - "Wallet": "f01234", - "Miner": "f01234", - "EpochPrice": "0", - "MinBlocksDuration": 42, - "ProviderCollateral": "0", - "DealStartEpoch": 10101, - "FastRetrieval": true, - "VerifiedDeal": true - } + [ + 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: `null` - -## Create - +Response: `true` -### CreateBackup -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 -the path specified when calling CreateBackup is within the base path +### EthUnsubscribe +Unsubscribe from a websocket subscription -Perms: admin +Perms: write Inputs: ```json [ - "string value" + [ + 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: `{}` +Response: `true` ## Gas @@ -2630,7 +3673,9 @@ Response: { "SealProof": 8, "SectorNumber": 9, - "SectorKey": null, + "SectorKey": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SealedCID": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" } @@ -4249,6 +5294,15 @@ Response: } ``` +### NetListening + + +Perms: read + +Inputs: `null` + +Response: `true` + ### NetPeerInfo @@ -4482,6 +5536,15 @@ Response: } ``` +### NetVersion + + +Perms: read + +Inputs: `null` + +Response: `"string value"` + ## Node These methods are general node management and status commands @@ -4556,7 +5619,9 @@ Response: "PendingAmt": "0", "NonReservedAmt": "0", "PendingAvailableAmt": "0", - "PendingWaitSentinel": null, + "PendingWaitSentinel": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" } @@ -4585,7 +5650,9 @@ Response: "PendingAmt": "0", "NonReservedAmt": "0", "PendingAvailableAmt": "0", - "PendingWaitSentinel": null, + "PendingWaitSentinel": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" } @@ -5255,7 +6322,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -5288,7 +6358,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5332,7 +6405,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5522,7 +6598,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -5555,7 +6634,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5599,7 +6681,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -5998,7 +7083,8 @@ Response: "UpgradeChocolateHeight": 10101, "UpgradeOhSnapHeight": 10101, "UpgradeSkyrHeight": 10101, - "UpgradeSharkHeight": 10101 + "UpgradeSharkHeight": 10101, + "UpgradeHyggeHeight": 10101 } } ``` @@ -6385,7 +7471,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ] @@ -6581,7 +7669,9 @@ Inputs: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, [ { @@ -6703,7 +7793,9 @@ Inputs: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, [ { @@ -6886,7 +7978,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ] @@ -7019,7 +8113,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "GasCost": { "Message": { @@ -7052,7 +8149,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -7096,7 +8196,10 @@ Response: "MsgRct": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "Error": "string value", "Duration": 60000000000, @@ -7178,7 +8281,10 @@ Response: "Receipt": { "ExitCode": 0, "Return": "Ynl0ZSBhcnJheQ==", - "GasUsed": 9 + "GasUsed": 9, + "EventsRoot": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "ReturnDec": {}, "TipSet": [ @@ -7267,7 +8373,9 @@ Response: "ExpectedStoragePledge": "0", "ReplacedSectorAge": 10101, "ReplacedDayReward": "0", - "SectorKeyCID": null, + "SectorKeyCID": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, "SimpleQAPower": true } ``` @@ -7343,7 +8451,9 @@ Response: 5432 ], "Expiration": 10101, - "UnsealedCid": null + "UnsealedCid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } }, "PreCommitDeposit": "0", "PreCommitEpoch": 10101 @@ -7499,7 +8609,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 2f662f017cd..616a4a3561e 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 @@ -2539,6 +2540,95 @@ 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] [arguments...] + +OPTIONS: + --ethAddr value Ethereum address + --filAddr value Filecoin address + +``` + +### 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 7f4ff591b32..7a1ab7bc540 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -293,3 +293,53 @@ #Tracing = false +[ActorEvent] + # EnableRealTimeFilterAPI enables APIs that can create and query filters for actor events as they are emitted. + # + # type: bool + # env var: LOTUS_ACTOREVENT_ENABLEREALTIMEFILTERAPI + #EnableRealTimeFilterAPI = false + + # EnableHistoricFilterAPI enables APIs that can create and query filters for actor events that occurred in the past. + # A queryable index of events will be maintained. + # + # type: bool + # env var: LOTUS_ACTOREVENT_ENABLEHISTORICFILTERAPI + #EnableHistoricFilterAPI = 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_ACTOREVENT_FILTERTTL + #FilterTTL = "24h0m0s" + + # MaxFilters specifies the maximum number of filters that may exist at any one time. + # + # type: int + # env var: LOTUS_ACTOREVENT_MAXFILTERS + #MaxFilters = 100 + + # MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter. + # + # type: int + # env var: LOTUS_ACTOREVENT_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_ACTOREVENT_MAXFILTERHEIGHTRANGE + #MaxFilterHeightRange = 2880 + + # ActorEventDatabasePath 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_ACTOREVENT_ACTOREVENTDATABASEPATH + #ActorEventDatabasePath = "" + + diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 02ebb2d6169..c4adeb45327 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 02ebb2d6169131cfe489e1063e896f14982c463d +Subproject commit c4adeb4532719acf7b1c182cb98a3cca7b955a14 diff --git a/gateway/node.go b/gateway/node.go index 7e84092e316..13ac57c82f7 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -25,6 +25,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/sigs" _ "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" diff --git a/gen/main.go b/gen/main.go index 38ec5935de6..a3891778a10 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 7ac89434930..f75bbcae499 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.25.2 - 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-9 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 77787e04ca0..41981069539 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-9 h1:Rriwh/Fs/hV15QqHuL47PkJMz4e8kLGRwgsdh+G+S5I= +github.com/filecoin-project/go-state-types v0.10.0-alpha-9/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 2afbc0bd029..ff303df3ec1 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/SimpleCoin.hex b/itests/contracts/SimpleCoin.hex new file mode 100644 index 00000000000..55b83cb12c9 --- /dev/null +++ b/itests/contracts/SimpleCoin.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061051c806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061047e565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104b2565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b925082820261046081610337565b915082820484148315176104775761047661040d565b5b5092915050565b600061048982610337565b915061049483610337565b92508282039050818111156104ac576104ab61040d565b5b92915050565b60006104bd82610337565b91506104c883610337565b92508282019050808211156104e0576104df61040d565b5b9291505056fea26469706673582212205ede41ff9072784ccc19ac18de0781558d305a8139361fa85dc51a8614e47d8c64736f6c63430008110033 \ No newline at end of file diff --git a/itests/contracts/SimpleCoin.sol b/itests/contracts/SimpleCoin.sol new file mode 100644 index 00000000000..5318b0cb886 --- /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/events.bin b/itests/contracts/events.bin new file mode 100644 index 00000000000..31abec33466 --- /dev/null +++ b/itests/contracts/events.bin @@ -0,0 +1 @@ +63000000678063000000116000396000f360003560e01c80600014601f578060011460365780600214604157600080fd5b67112233445566778860005260086018a060006000f35b60006000a060006000f35b67112233445566778860005263000044446200333361222261111160086018a460006000f3 \ No newline at end of file diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go new file mode 100644 index 00000000000..b6ad4872084 --- /dev/null +++ b/itests/eth_account_abstraction_test.go @@ -0,0 +1,131 @@ +package itests + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/api" + "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() + + 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() + + secpKey, err := key.GenerateKey(types.KTDelegated) + require.NoError(t, err) + + placeholderAddress, err := client.WalletImport(ctx, &secpKey.KeyInfo) + require.NoError(t, err) + + fmt.Println(placeholderAddress) + + // 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.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) + + // 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)) + + // 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.NewEthTxArgsFromMessage(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.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) + + // 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.NewEthTxArgsFromMessage(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.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) + + // 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)) +} diff --git a/itests/eth_balance_test.go b/itests/eth_balance_test.go new file mode 100644 index 00000000000..3176aefc854 --- /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_deploy_test.go b/itests/eth_deploy_test.go new file mode 100644 index 00000000000..13a68ce4691 --- /dev/null +++ b/itests/eth_deploy_test.go @@ -0,0 +1,227 @@ +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" + "github.com/filecoin-project/lotus/node/config" +) + +// 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(), + kit.WithCfgOpt(func(cfg *config.FullNode) error { + cfg.ActorEvent.EnableRealTimeFilterAPI = true + return nil + }), + ) + 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 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 00000000000..bee9a8c3848 --- /dev/null +++ b/itests/eth_filter_test.go @@ -0,0 +1,815 @@ +// stm: #integration +package itests + +import ( + "context" + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "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" +) + +func TestEthNewPendingTransactionFilter(t *testing.T) { + ctx := context.Background() + + kit.QuietMiningLogs() + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) + 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 + + count := 0 + for { + select { + 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 { + waitAllCh <- struct{}{} + } + } + } + } + } + }() + + 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 <-time.After(time.Minute): + t.Errorf("timeout to wait for 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)) + for _, txid := range res.Results { + expected[txid.(string)] = true + } + + for _, found := range expected { + require.True(t, found) + } +} + +func TestEthNewBlockFilter(t *testing.T) { + ctx := context.Background() + + kit.QuietMiningLogs() + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) + 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{}) + go func() { + headChangeCh, err := client.ChainNotify(ctx) + require.NoError(t, err) + <-headChangeCh // skip hccurrent + + count := 0 + for { + select { + case headChanges := <-headChangeCh: + for _, change := range headChanges { + if change.Type == store.HCApply || change.Type == store.HCRevert { + count++ + if count == iterations { + waitAllCh <- struct{}{} + } + } + } + } + } + }() + + // 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) + + // FIXME this was here and unused. Use or remove. + // sms = append(sms, sm) + } + + select { + case <-waitAllCh: + case <-time.After(time.Minute): + t.Errorf("timeout to wait for pack messages") + } + + // 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)) +} + +func TestEthNewFilterCatchAll(t *testing.T) { + require := require.New(t) + + kit.QuietMiningLogs() + + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) + 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 = 10 + + type msgInTipset struct { + msg api.Message + ts *types.TipSet + } + + msgChan := make(chan msgInTipset, iterations) + + waitAllCh := make(chan struct{}) + go func() { + headChangeCh, err := client.ChainNotify(ctx) + require.NoError(err) + <-headChangeCh // skip hccurrent + + count := 0 + for { + select { + 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}: + default: + } + } + + if count == iterations { + close(msgChan) + close(waitAllCh) + return + } + } + } + } + } + }() + + time.Sleep(blockTime * 6) + + for i := 0; i < iterations; i++ { + // 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") + } + + select { + case <-waitAllCh: + case <-time.After(time.Minute): + t.Errorf("timeout to wait for pack messages") + } + + received := make(map[ethtypes.EthHash]msgInTipset) + for m := range msgChan { + eh, err := ethtypes.EthHashFromCid(m.msg.Cid) + require.NoError(err) + received[eh] = m + } + require.Equal(iterations, len(received), "all messages on chain") + + ts, err := client.ChainHead(ctx) + require.NoError(err) + + actor, err := client.StateGetActor(ctx, idAddr, ts.Key()) + require.NoError(err) + require.NotNil(actor.Address) + ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address) + require.NoError(err) + + // 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)) + + topic1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x11})) + topic2 := ethtypes.EthBytes(leftpad32([]byte{0x22, 0x22})) + topic3 := ethtypes.EthBytes(leftpad32([]byte{0x33, 0x33})) + topic4 := ethtypes.EthBytes(leftpad32([]byte{0x44, 0x44})) + data1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})) + + for _, r := range res.Results { + // since response is a union and Go doesn't support them well, go-jsonrpc won't give us typed results + rc, ok := r.(map[string]interface{}) + require.True(ok, "result type") + + elog, err := ParseEthLog(rc) + require.NoError(err) + + require.Equal(ethContractAddr, elog.Address, "event address") + require.Equal(ethtypes.EthUint64(0), elog.TransactionIndex, "transaction index") // only one message per tipset + + msg, exists := received[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") + + require.Equal(4, len(elog.Topics), "number of topics") + require.Equal(topic1, elog.Topics[0], "topic1") + require.Equal(topic2, elog.Topics[1], "topic2") + require.Equal(topic3, elog.Topics[2], "topic3") + require.Equal(topic4, elog.Topics[3], "topic4") + + require.Equal(data1, elog.Data, "data1") + + } +} + +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 +} + +type msgInTipset struct { + msg api.Message + ts *types.TipSet + reverted bool +} + +func invokeContractAndWaitUntilAllOnChain(t *testing.T, client *kit.TestFullNode, iterations int) (ethtypes.EthAddress, map[ethtypes.EthHash]msgInTipset) { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + blockTime := 100 * time.Millisecond + + // 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) + + msgChan := make(chan msgInTipset, iterations) + + waitAllCh := make(chan struct{}) + go func() { + headChangeCh, err := client.ChainNotify(ctx) + require.NoError(err) + <-headChangeCh // skip hccurrent + + count := 0 + for { + select { + 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 == iterations { + close(msgChan) + close(waitAllCh) + return + } + } + } + } + } + }() + + time.Sleep(blockTime * 6) + + for i := 0; i < iterations; i++ { + // 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") + } + + select { + case <-waitAllCh: + case <-time.After(time.Minute): + t.Errorf("timeout to wait for pack messages") + } + + received := make(map[ethtypes.EthHash]msgInTipset) + for m := range msgChan { + eh, err := ethtypes.EthHashFromCid(m.msg.Cid) + require.NoError(err) + received[eh] = m + } + require.Equal(iterations, len(received), "all messages on chain") + + head, err := client.ChainHead(ctx) + require.NoError(err) + + actor, err := client.StateGetActor(ctx, idAddr, head.Key()) + require.NoError(err) + require.NotNil(actor.Address) + ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address) + require.NoError(err) + + return ethContractAddr, received +} + +func TestEthGetLogsAll(t *testing.T) { + require := require.New(t) + + kit.QuietMiningLogs() + + blockTime := 100 * time.Millisecond + dbpath := filepath.Join(t.TempDir(), "actorevents.db") + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.HistoricFilterAPI(dbpath)) + ens.InterconnectAll().BeginMining(blockTime) + + ethContractAddr, received := invokeContractAndWaitUntilAllOnChain(t, client, 10) + + topic1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x11})) + topic2 := ethtypes.EthBytes(leftpad32([]byte{0x22, 0x22})) + topic3 := ethtypes.EthBytes(leftpad32([]byte{0x33, 0x33})) + topic4 := ethtypes.EthBytes(leftpad32([]byte{0x44, 0x44})) + data1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})) + + pstring := func(s string) *string { return &s } + + // get all logs + res, err := client.EthGetLogs(context.Background(), ðtypes.EthFilterSpec{ + FromBlock: pstring("0x0"), + }) + require.NoError(err) + + // expect to have all messages sent + require.Equal(len(received), len(res.Results)) + + for _, r := range res.Results { + // since response is a union and Go doesn't support them well, go-jsonrpc won't give us typed results + rc, ok := r.(map[string]interface{}) + require.True(ok, "result type") + + elog, err := ParseEthLog(rc) + require.NoError(err) + + require.Equal(ethContractAddr, elog.Address, "event address") + require.Equal(ethtypes.EthUint64(0), elog.TransactionIndex, "transaction index") // only one message per tipset + + msg, exists := received[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") + + require.Equal(4, len(elog.Topics), "number of topics") + require.Equal(topic1, elog.Topics[0], "topic1") + require.Equal(topic2, elog.Topics[1], "topic2") + require.Equal(topic3, elog.Topics[2], "topic3") + require.Equal(topic4, elog.Topics[3], "topic4") + + require.Equal(data1, elog.Data, "data1") + + } +} + +func TestEthGetLogsByTopic(t *testing.T) { + require := require.New(t) + + kit.QuietMiningLogs() + + blockTime := 100 * time.Millisecond + dbpath := filepath.Join(t.TempDir(), "actorevents.db") + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.HistoricFilterAPI(dbpath)) + ens.InterconnectAll().BeginMining(blockTime) + + invocations := 1 + + ethContractAddr, received := invokeContractAndWaitUntilAllOnChain(t, client, invocations) + + topic1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x11})) + topic2 := ethtypes.EthBytes(leftpad32([]byte{0x22, 0x22})) + topic3 := ethtypes.EthBytes(leftpad32([]byte{0x33, 0x33})) + topic4 := ethtypes.EthBytes(leftpad32([]byte{0x44, 0x44})) + data1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})) + + // 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) + + require.Equal(invocations, len(res.Results)) + + for _, r := range res.Results { + // since response is a union and Go doesn't support them well, go-jsonrpc won't give us typed results + rc, ok := r.(map[string]interface{}) + require.True(ok, "result type") + + elog, err := ParseEthLog(rc) + require.NoError(err) + + require.Equal(ethContractAddr, elog.Address, "event address") + require.Equal(ethtypes.EthUint64(0), elog.TransactionIndex, "transaction index") // only one message per tipset + + msg, exists := received[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") + + require.Equal(4, len(elog.Topics), "number of topics") + require.Equal(topic1, elog.Topics[0], "topic1") + require.Equal(topic2, elog.Topics[1], "topic2") + require.Equal(topic3, elog.Topics[2], "topic3") + require.Equal(topic4, elog.Topics[3], "topic4") + + require.Equal(data1, elog.Data, "data1") + + } +} + +func TestEthSubscribeLogs(t *testing.T) { + require := require.New(t) + + kit.QuietMiningLogs() + + blockTime := 100 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) + 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 + + type msgInTipset struct { + msg api.Message + ts *types.TipSet + } + + msgChan := make(chan msgInTipset, iterations) + + waitAllCh := make(chan struct{}) + go func() { + headChangeCh, err := client.ChainNotify(ctx) + require.NoError(err) + <-headChangeCh // skip hccurrent + + count := 0 + for { + select { + 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}: + default: + } + } + + if count == iterations { + close(msgChan) + close(waitAllCh) + return + } + } + } + } + } + }() + + time.Sleep(blockTime * 6) + + for i := 0; i < iterations; i++ { + // 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") + } + + select { + case <-waitAllCh: + case <-time.After(time.Minute): + t.Errorf("timeout to wait for pack messages") + } + + if len(subResponses) > 0 { + ok, err := client.EthUnsubscribe(ctx, subResponses[0].SubscriptionID) + require.NoError(err) + require.True(ok, "unsubscribed") + } + + received := make(map[ethtypes.EthHash]msgInTipset) + for m := range msgChan { + eh, err := ethtypes.EthHashFromCid(m.msg.Cid) + require.NoError(err) + received[eh] = m + } + require.Equal(iterations, len(received), "all messages on chain") + + // expect to have seen all logs + require.Equal(len(received), len(subResponses)) +} + +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/itests/eth_transactions_test.go b/itests/eth_transactions_test.go new file mode 100644 index 00000000000..0c8f1baa5e0 --- /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 00000000000..ba5651225f8 --- /dev/null +++ b/itests/fevm_address_test.go @@ -0,0 +1,123 @@ +package itests + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "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 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) + fromId, err := client.StateLookupID(ctx, fromAddr, types.EmptyTSK) + require.NoError(t, err) + + senderEthAddr, err := ethtypes.EthAddressFromFilecoinAddress(fromId) + require.NoError(t, err) + + var salt [32]byte + binary.BigEndian.PutUint64(salt[:], 1) + + // Generate contract address before actually deploying contract + ethAddr, err := ethtypes.GetContractEthAddressFromCode(senderEthAddr, salt, contract) + require.NoError(t, err) + + 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 an 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.Create2 + params, err := actors.SerializeParams(&eam.Create2Params{ + Initcode: contract, + Salt: salt, + }) + 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 Create2 is the same as eth address predicted at the start + var create2Return eam.Create2Return + err = create2Return.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)) + require.NoError(t, err) + + createdEthAddr, err := ethtypes.CastEthAddress(create2Return.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 00000000000..30dd7015f78 --- /dev/null +++ b/itests/fevm_events_test.go @@ -0,0 +1,83 @@ +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/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, &api.EthFilterSpec{ + // FromBlock: &earliest, + // ToBlock: &latest, + // }) + // require.NoError(err) + // + // // No logs yet. + // res, err := client.EthGetFilterLogs(ctx, filter) + // require.NoError(err) + // require.Empty(res.NewLogs) + + // 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 00000000000..af29f83a8de --- /dev/null +++ b/itests/fevm_test.go @@ -0,0 +1,114 @@ +package itests + +import ( + "bytes" + "context" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" + + "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/itests/kit" +) + +// TestFEVMBasic does a basic fevm contract installation and invocation +func TestFEVMBasic(t *testing.T) { + // TODO the contract installation and invocation can be lifted into utility methods + // He who writes the second test, shall do that. + 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) + + result := client.EVM().DeployContract(ctx, fromAddr, contract) + + idAddr, err := address.NewIDAddress(result.ActorID) + require.NoError(t, err) + t.Logf("actor ID address is %s", idAddr) + + // invoke the contract with owner + { + entryPoint, err := hex.DecodeString("f8b2cb4f") + require.NoError(t, err) + + inputData, err := hex.DecodeString("000000000000000000000000ff00000000000000000000000000000000000064") + require.NoError(t, err) + + wait := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData) + + require.True(t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed") + + result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return))) + require.NoError(t, err) + + expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000002710") + require.NoError(t, err) + require.Equal(t, result, expectedResult) + } + + // invoke the contract with non owner + { + entryPoint, err := hex.DecodeString("f8b2cb4f") + require.NoError(t, err) + + inputData, err := hex.DecodeString("000000000000000000000000ff00000000000000000000000000000000000065") + require.NoError(t, err) + + wait := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData) + require.True(t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed") + + result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return))) + require.NoError(t, err) + + 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) { + 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() + + 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) +} diff --git a/itests/kit/evm.go b/itests/kit/evm.go new file mode 100644 index 00000000000..06137778fcb --- /dev/null +++ b/itests/kit/evm.go @@ -0,0 +1,265 @@ +package kit + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + + "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/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.Create2 + params, err := actors.SerializeParams(&eam.Create2Params{ + Initcode: bytecode, + Salt: salt, + }) + 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) 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:]) +} + +// 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/node_opts.go b/itests/kit/node_opts.go index 14b0bccc835..a220a0d1be4 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -280,3 +280,19 @@ func SplitstoreMessges() NodeOpt { return nil }) } + +func RealTimeFilterAPI() NodeOpt { + return WithCfgOpt(func(cfg *config.FullNode) error { + cfg.ActorEvent.EnableRealTimeFilterAPI = true + return nil + }) +} + +func HistoricFilterAPI(dbpath string) NodeOpt { + return WithCfgOpt(func(cfg *config.FullNode) error { + cfg.ActorEvent.EnableRealTimeFilterAPI = true + cfg.ActorEvent.EnableHistoricFilterAPI = true + cfg.ActorEvent.ActorEventDatabasePath = dbpath + return nil + }) +} diff --git a/itests/kit/state.go b/itests/kit/state.go new file mode 100644 index 00000000000..e66576be393 --- /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 00000000000..44bf3806cf4 --- /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 b20dcf16b28..92d9afca7dd 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 957efe32fdf..b5339d24cc6 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,16 @@ 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 { + _, err := n.ChainReadObj(ctx, c) + if ipld.IsNotFound(err) { + return false + } else if err != nil { + t.Fatalf("ChainReadObj failure on existence check: %s", err) + } + return true +} + // Create on chain unreachable garbage for a network to exercise splitstore // one garbage cid created at a time // diff --git a/lib/sigs/delegated/init.go b/lib/sigs/delegated/init.go new file mode 100644 index 00000000000..4db83b3e76f --- /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") + } + + return nil +} + +func init() { + sigs.RegisterSignature(crypto1.SigTypeDelegated, delegatedSigner{}) +} diff --git a/node/builder.go b/node/builder.go index eaf932e2e65..ddba821128e 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 2201be2e6b3..584cf3a6051 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), + // this to make tests pass, but we should consider actually implementing it in the gateway + Override(new(full.EthModuleAPI), new(full.EthModuleDummy)), ), // Full node API / service startup @@ -158,6 +161,7 @@ var ChainNode = Options( Override(new(messagepool.Provider), messagepool.NewProvider), Override(new(messagepool.MpoolNonceAPI), From(new(*messagepool.MessagePool))), Override(new(full.ChainModuleAPI), From(new(full.ChainModule))), + Override(new(full.EthModuleAPI), From(new(full.EthModule))), Override(new(full.GasModuleAPI), From(new(full.GasModule))), Override(new(full.MpoolModuleAPI), From(new(full.MpoolModule))), Override(new(full.StateModuleAPI), From(new(full.StateModule))), @@ -241,6 +245,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 +256,10 @@ 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))), + Override(new(full.EthEventAPI), modules.EthEventAPI(cfg.ActorEvent)), ) } diff --git a/node/config/def.go b/node/config/def.go index dc26f1661a4..7540aa3f74e 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,14 @@ func DefaultFullNode() *FullNode { }, }, Cluster: *DefaultUserRaftConfig(), + ActorEvent: ActorEventConfig{ + EnableRealTimeFilterAPI: false, + EnableHistoricFilterAPI: false, + FilterTTL: Duration(time.Hour * 24), + MaxFilters: 100, + MaxFilterResults: 10000, + MaxFilterHeightRange: 2880, // conservative limit of one day + }, } } @@ -255,8 +264,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 4723cc59f8d..0da9c7853ec 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -29,6 +29,56 @@ var Doc = map[string][]DocField{ Comment: ``, }, }, + "ActorEventConfig": []DocField{ + { + Name: "EnableRealTimeFilterAPI", + Type: "bool", + + Comment: `EnableRealTimeFilterAPI enables APIs that can create and query filters for actor events as they are emitted.`, + }, + { + Name: "EnableHistoricFilterAPI", + Type: "bool", + + Comment: `EnableHistoricFilterAPI enables APIs that can create and query filters for actor events that occurred in the past. +A queryable index of events will be maintained.`, + }, + { + 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: "ActorEventDatabasePath", + Type: "string", + + Comment: `ActorEventDatabasePath 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).`, + }, + }, "Backup": []DocField{ { Name: "DisableMetadataLog", @@ -378,6 +428,12 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Name: "Cluster", Type: "UserRaftConfig", + Comment: ``, + }, + { + Name: "ActorEvent", + Type: "ActorEventConfig", + Comment: ``, }, }, diff --git a/node/config/types.go b/node/config/types.go index cc1a943f420..b0f9b63c006 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -27,6 +27,7 @@ type FullNode struct { Fees FeeConfig Chainstore Chainstore Cluster UserRaftConfig + ActorEvent ActorEventConfig } // // 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,37 @@ type UserRaftConfig struct { // Tracing enables propagation of contexts across binary boundaries. Tracing bool } + +type ActorEventConfig struct { + // EnableRealTimeFilterAPI enables APIs that can create and query filters for actor events as they are emitted. + EnableRealTimeFilterAPI bool + + // EnableHistoricFilterAPI enables APIs that can create and query filters for actor events that occurred in the past. + // A queryable index of events will be maintained. + EnableHistoricFilterAPI 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 + + // ActorEventDatabasePath 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). + ActorEventDatabasePath 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 03a28eb75ac..affcc960e09 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 ca245dcdad3..8448a542e9b 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 00000000000..865e14c9a74 --- /dev/null +++ b/node/impl/full/dummy.go @@ -0,0 +1,109 @@ +package full + +import ( + "context" + "errors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +var ErrImplementMe = errors.New("Not implemented yet") + +type EthModuleDummy struct{} + +func (e *EthModuleDummy) EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) { + return 0, ErrImplementMe +} + +func (e *EthModuleDummy) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) { + return nil, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) { + return 0, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) { + return 0, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) { + return ethtypes.EthBlock{}, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) { + return ethtypes.EthBlock{}, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) { + return nil, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) { + return 0, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) { + return nil, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + return ethtypes.EthTx{}, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { + return ethtypes.EthTx{}, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) { + return nil, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) { + return nil, ErrImplementMe +} + +func (e *EthModuleDummy) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) { + return ethtypes.EthBigIntZero, ErrImplementMe +} + +func (e *EthModuleDummy) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) { + return ethtypes.EthFeeHistory{}, ErrImplementMe +} + +func (e *EthModuleDummy) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) { + return 0, ErrImplementMe +} + +func (e *EthModuleDummy) NetVersion(ctx context.Context) (string, error) { + return "", ErrImplementMe +} + +func (e *EthModuleDummy) NetListening(ctx context.Context) (bool, error) { + return false, ErrImplementMe +} + +func (e *EthModuleDummy) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) { + return 0, ErrImplementMe +} + +func (e *EthModuleDummy) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) { + return ethtypes.EthBigIntZero, ErrImplementMe +} + +func (e *EthModuleDummy) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { + return 0, ErrImplementMe +} + +func (e *EthModuleDummy) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) { + return nil, ErrImplementMe +} + +func (e *EthModuleDummy) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) { + return ethtypes.EthBigIntZero, ErrImplementMe +} + +func (e *EthModuleDummy) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { + return ethtypes.EthHash{}, ErrImplementMe +} diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go new file mode 100644 index 00000000000..c400dae3143 --- /dev/null +++ b/node/impl/full/eth.go @@ -0,0 +1,1733 @@ +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/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/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) + 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 { + fx.In + + Chain *store.ChainStore + Mpool *messagepool.MessagePool + StateManager *stmgr.StateManager + + 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 + } + + cid := txHash.ToCid() + + // first, try to get the cid from mined transactions + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, 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() == cid { + 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) 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) { + cid := txHash.ToCid() + + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true) + if err != 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, cid) + 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 + } + + cid, err := a.MpoolAPI.MpoolPush(ctx, smsg) + if err != nil { + return ethtypes.EmptyEthHash, err + } + return ethtypes.EthHashFromCid(cid) +} + +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 + + nonce, err := a.Mpool.GetNonce(ctx, from, types.EmptyTSK) + if err != nil { + nonce = 0 // assume a zero nonce on error (e.g. sender doesn't exist). + } + + params2, err := actors.SerializeParams(&eam.CreateParams{ + Initcode: tx.Data, + Nonce: nonce, + }) + if err != nil { + return nil, fmt.Errorf("failed to serialize Create params: %w", err) + } + params = params2 + method = builtintypes.MethodsEAM.Create + } 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) +} + +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)) + case filterTipSetCollector: + return ethFilterResultFromTipSets(fc.TakeCollectedTipSets(ctx)) + case filterMessageCollector: + return ethFilterResultFromMessages(fc.TakeCollectedMessages(ctx)) + } + + 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)) + } + + 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") + } + } 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") + } + + } else if minHeight >= 0 && maxHeight >= 0 { + if minHeight > maxHeight || maxHeight-minHeight > e.MaxFilterHeightRange { + return nil, xerrors.Errorf("invalid epoch range") + } + } + + } + + // 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 { + // 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 + } + + 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) []cid.Cid +} + +type filterTipSetCollector interface { + TakeCollectedTipSets(context.Context) []types.TipSetKey +} + +func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*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(decodeLogBytes(entry.Value))) + 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 = ethtypes.EthHashFromCid(ev.MsgCid) + 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 []cid.Cid) (*ethtypes.EthFilterResult, error) { + res := ðtypes.EthFilterResult{} + + for _, c := range cs { + hash, err := ethtypes.EthHashFromCid(c) + 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}) + 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 + + if fullTxInfo { + tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, txIdx, cs, sa) + if err != nil { + return ethtypes.EthBlock{}, nil + } + block.Transactions = append(block.Transactions, tx) + } else { + hash, err := ethtypes.EthHashFromCid(msg.Cid()) + if err != nil { + return ethtypes.EthBlock{}, err + } + block.Transactions = append(block.Transactions, 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 newEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthTx, error) { + fromEthAddr, err := lookupEthAddress(ctx, smsg.Message.From, sa) + if err != nil { + return ethtypes.EthTx{}, err + } + + toEthAddr, err := lookupEthAddress(ctx, smsg.Message.To, sa) + if err != nil { + return ethtypes.EthTx{}, err + } + + toAddr := &toEthAddr + input := smsg.Message.Params + // 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 + } + 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 + } + + hash, err := ethtypes.EthHashFromCid(smsg.Cid()) + if err != nil { + return ethtypes.EthTx{}, err + } + + tx := ethtypes.EthTx{ + Hash: hash, + 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), + Gas: ethtypes.EthUint64(smsg.Message.GasLimit), + MaxFeePerGas: ethtypes.EthBigInt(smsg.Message.GasFeeCap), + MaxPriorityFeePerGas: ethtypes.EthBigInt(smsg.Message.GasPremium), + V: v, + R: r, + S: s, + Input: input, + } + + 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") + } + cid := msgLookup.Message + txHash, err := ethtypes.EthHashFromCid(cid) + if err != nil { + return ethtypes.EthTx{}, err + } + + 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 { + return ethtypes.EthTx{}, err + } + + 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.Hash = txHash + 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(decodeLogBytes(entry.Value))) + 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 +} + +// 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 orig == nil { + return orig + } + decoded, err := cbg.ReadByteArray(bytes.NewReader(orig), uint64(len(orig))) + if err != nil { + return orig + } + return decoded +} + +// 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 8cbe9ea1c74..435e2c65b55 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 31d134dace5..addcc41be43 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 91ffd3b9ee7..e6142a36f15 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 ae2550d77c7..67bb9610199 100644 --- a/node/impl/full/wallet.go +++ b/node/impl/full/wallet.go @@ -36,7 +36,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,7 +46,7 @@ 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) } diff --git a/node/modules/actorevent.go b/node/modules/actorevent.go new file mode 100644 index 00000000000..1c574cb68b7 --- /dev/null +++ b/node/modules/actorevent.go @@ -0,0 +1,139 @@ +package modules + +import ( + "context" + "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" +) + +type EventAPI struct { + fx.In + + full.ChainAPI + full.StateAPI +} + +var _ events.EventAPI = &EventAPI{} + +func EthEventAPI(cfg config.ActorEventConfig) func(helpers.MetricsCtx, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEvent, error) { + return func(mctx helpers.MetricsCtx, 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.MaxFilterHeightRange), + } + + if !cfg.EnableRealTimeFilterAPI { + // 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.MaxFilters) + + // Start garbage collection for filters + lc.Append(fx.Hook{ + OnStart: func(context.Context) error { + go ee.GC(ctx, time.Duration(cfg.FilterTTL)) + return nil + }, + }) + + // Enable indexing of actor events + var eventIndex *filter.EventIndex + if cfg.EnableHistoricFilterAPI { + var err error + eventIndex, err = filter.NewEventIndex(cfg.ActorEventDatabasePath) + 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.MaxFilterResults, + } + ee.TipSetFilterManager = &filter.TipSetFilterManager{ + MaxFilterResults: cfg.MaxFilterResults, + } + ee.MemPoolFilterManager = &filter.MemPoolFilterManager{ + MaxFilterResults: cfg.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/rpc.go b/node/rpc.go index 34f68097346..c454e39be0d 100644 --- a/node/rpc.go +++ b/node/rpc.go @@ -79,6 +79,45 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server rpcServer.Register("Filecoin", hnd) rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") + // TODO: use reflect to automatically register all the eth aliases + rpcServer.AliasMethod("eth_accounts", "Filecoin.EthAccounts") + rpcServer.AliasMethod("eth_blockNumber", "Filecoin.EthBlockNumber") + rpcServer.AliasMethod("eth_getBlockTransactionCountByNumber", "Filecoin.EthGetBlockTransactionCountByNumber") + rpcServer.AliasMethod("eth_getBlockTransactionCountByHash", "Filecoin.EthGetBlockTransactionCountByHash") + + rpcServer.AliasMethod("eth_getBlockByHash", "Filecoin.EthGetBlockByHash") + rpcServer.AliasMethod("eth_getBlockByNumber", "Filecoin.EthGetBlockByNumber") + rpcServer.AliasMethod("eth_getTransactionByHash", "Filecoin.EthGetTransactionByHash") + rpcServer.AliasMethod("eth_getTransactionCount", "Filecoin.EthGetTransactionCount") + rpcServer.AliasMethod("eth_getTransactionReceipt", "Filecoin.EthGetTransactionReceipt") + rpcServer.AliasMethod("eth_getTransactionByBlockHashAndIndex", "Filecoin.EthGetTransactionByBlockHashAndIndex") + rpcServer.AliasMethod("eth_getTransactionByBlockNumberAndIndex", "Filecoin.EthGetTransactionByBlockNumberAndIndex") + + rpcServer.AliasMethod("eth_getCode", "Filecoin.EthGetCode") + rpcServer.AliasMethod("eth_getStorageAt", "Filecoin.EthGetStorageAt") + rpcServer.AliasMethod("eth_getBalance", "Filecoin.EthGetBalance") + rpcServer.AliasMethod("eth_chainId", "Filecoin.EthChainId") + rpcServer.AliasMethod("eth_feeHistory", "Filecoin.EthFeeHistory") + rpcServer.AliasMethod("eth_protocolVersion", "Filecoin.EthProtocolVersion") + rpcServer.AliasMethod("eth_maxPriorityFeePerGas", "Filecoin.EthMaxPriorityFeePerGas") + rpcServer.AliasMethod("eth_gasPrice", "Filecoin.EthGasPrice") + rpcServer.AliasMethod("eth_sendRawTransaction", "Filecoin.EthSendRawTransaction") + rpcServer.AliasMethod("eth_estimateGas", "Filecoin.EthEstimateGas") + rpcServer.AliasMethod("eth_call", "Filecoin.EthCall") + + rpcServer.AliasMethod("eth_getLogs", "Filecoin.EthGetLogs") + rpcServer.AliasMethod("eth_getFilterChanges", "Filecoin.EthGetFilterChanges") + rpcServer.AliasMethod("eth_getFilterLogs", "Filecoin.EthGetFilterLogs") + rpcServer.AliasMethod("eth_newFilter", "Filecoin.EthNewFilter") + rpcServer.AliasMethod("eth_newBlockFilter", "Filecoin.EthNewBlockFilter") + rpcServer.AliasMethod("eth_newPendingTransactionFilter", "Filecoin.EthNewPendingTransactionFilter") + rpcServer.AliasMethod("eth_uninstallFilter", "Filecoin.EthUninstallFilter") + rpcServer.AliasMethod("eth_subscribe", "Filecoin.EthSubscribe") + rpcServer.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe") + + rpcServer.AliasMethod("net_version", "Filecoin.NetVersion") + rpcServer.AliasMethod("net_listening", "Filecoin.NetListening") + var handler http.Handler = rpcServer if permissioned { handler = &auth.Handler{Verify: a.AuthVerify, Next: rpcServer.ServeHTTP}