diff --git a/api/api_full.go b/api/api_full.go index 1f17c627c..3265551c0 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -181,6 +181,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 diff --git a/api/eth_types.go b/api/eth_types.go index 9835f6112..3f96a02d4 100644 --- a/api/eth_types.go +++ b/api/eth_types.go @@ -17,11 +17,20 @@ import ( "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/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + EthTopic1 = "topic1" + EthTopic2 = "topic2" + EthTopic3 = "topic3" + EthTopic4 = "topic4" ) type EthUint64 uint64 @@ -185,14 +194,12 @@ func (c *EthCall) UnmarshalJSON(b []byte) error { } type EthTxReceipt struct { - TransactionHash EthHash `json:"transactionHash"` - TransactionIndex EthUint64 `json:"transactionIndex"` - BlockHash EthHash `json:"blockHash"` - BlockNumber EthUint64 `json:"blockNumber"` - From EthAddress `json:"from"` - To *EthAddress `json:"to"` - // Logs - // LogsBloom + TransactionHash EthHash `json:"transactionHash"` + TransactionIndex EthUint64 `json:"transactionIndex"` + BlockHash EthHash `json:"blockHash"` + BlockNumber EthUint64 `json:"blockNumber"` + From EthAddress `json:"from"` + To *EthAddress `json:"to"` StateRoot EthHash `json:"root"` Status EthUint64 `json:"status"` ContractAddress *EthAddress `json:"contractAddress"` @@ -200,10 +207,10 @@ type EthTxReceipt struct { GasUsed EthUint64 `json:"gasUsed"` EffectiveGasPrice EthBigInt `json:"effectiveGasPrice"` LogsBloom EthBytes `json:"logsBloom"` - Logs []string `json:"logs"` + Logs []EthLog `json:"logs"` } -func NewEthTxReceipt(tx EthTx, lookup *MsgLookup, replay *InvocResult) (EthTxReceipt, error) { +func NewEthTxReceipt(tx EthTx, lookup *MsgLookup, replay *InvocResult, events []types.Event, addressResolver func(id abi.ActorID) (address.Address, bool, error)) (EthTxReceipt, error) { receipt := EthTxReceipt{ TransactionHash: tx.Hash, TransactionIndex: tx.TransactionIndex, @@ -213,7 +220,6 @@ func NewEthTxReceipt(tx EthTx, lookup *MsgLookup, replay *InvocResult) (EthTxRec To: tx.To, StateRoot: EmptyEthHash, LogsBloom: []byte{0}, - Logs: []string{}, } if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() { @@ -233,6 +239,41 @@ func NewEthTxReceipt(tx EthTx, lookup *MsgLookup, replay *InvocResult) (EthTxRec receipt.Status = 0 } + if len(events) > 0 { + receipt.Logs = make([]EthLog, 0, len(events)) + for i, evt := range events { + l := EthLog{ + Removed: false, + LogIndex: EthUint64(i), + TransactionIndex: tx.TransactionIndex, + TransactionHash: tx.Hash, + BlockHash: tx.BlockHash, + BlockNumber: tx.BlockNumber, + } + + for _, entry := range evt.Entries { + hash := EthHashData(entry.Value) + if entry.Key == EthTopic1 || entry.Key == EthTopic2 || entry.Key == EthTopic3 || entry.Key == EthTopic4 { + l.Topics = append(l.Topics, hash) + } else { + l.Data = append(l.Data, hash) + } + } + + f4addr, ok, err := addressResolver(evt.Emitter) + if err != nil || !ok { + return EthTxReceipt{}, xerrors.Errorf("failed to resolve predictable address: %w", err) + } + + l.Address, err = EthAddressFromFilecoinAddress(f4addr) + if err != nil { + return EthTxReceipt{}, xerrors.Errorf("failed to translate to Ethereum address: %w", err) + } + + receipt.Logs = append(receipt.Logs, l) + } + } + receipt.GasUsed = EthUint64(lookup.Receipt.GasUsed) // TODO: handle CumulativeGasUsed diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 2e915d6d6..e4338763c 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -183,6 +183,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() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index a7eebe436..5f28d5b0b 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -123,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"` @@ -1330,6 +1332,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 diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 31db1f5da..e88f7a076 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 0a451717e..aeb2fba29 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 60110421c..e06a28ade 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 2e9fb4cee..a035f920a 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index e58106849..9af3b699e 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) @@ -612,6 +613,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. @@ -2703,7 +2735,21 @@ Response: "effectiveGasPrice": "0x0", "logsBloom": "0x07", "logs": [ - "string value" + { + "address": "0x0707070707070707070707070707070707070707", + "data": [ + "0x0707070707070707070707070707070707070707070707070707070707070707" + ], + "topics": [ + "0x0707070707070707070707070707070707070707070707070707070707070707" + ], + "removed": true, + "logIndex": "0x5", + "transactionIndex": "0x5", + "transactionHash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "blockNumber": "0x5" + } ] } ``` diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index ca245dcda..d06c85402 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(5)) + 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/eth.go b/node/impl/full/eth.go index 9a25b810b..18f47f310 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -59,7 +59,6 @@ type EthModuleAPI interface { EthCall(ctx context.Context, tx api.EthCall, blkParam string) (api.EthBytes, error) EthMaxPriorityFeePerGas(ctx context.Context) (api.EthBigInt, error) EthSendRawTransaction(ctx context.Context, rawTx api.EthBytes) (api.EthHash, error) - // EthFeeHistory(ctx context.Context, blkCount string) } type EthEventAPI interface { @@ -237,7 +236,29 @@ func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash api.Eth return nil, nil } - receipt, err := api.NewEthTxReceipt(tx, msgLookup, replay) + 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 := api.NewEthTxReceipt(tx, msgLookup, replay, events, func(id abi.ActorID) (address.Address, bool, error) { + addr, err := address.NewIDAddress(uint64(id)) + if err != nil { + return address.Undef, false, xerrors.Errorf("failed to create ID address: %w", err) + } + actor, err := a.StateGetActor(ctx, addr, types.EmptyTSK) + if err != nil { + return address.Undef, false, xerrors.Errorf("failed to load actor: %w", err) + } + if actor.Address == nil { + return address.Undef, false, nil + } + return *actor.Address, true, nil + }) + if err != nil { return nil, nil } @@ -1242,13 +1263,6 @@ type filterTipSetCollector interface { TakeCollectedTipSets(context.Context) []types.TipSetKey } -var ( - ethTopic1 = "topic1" - ethTopic2 = "topic2" - ethTopic3 = "topic3" - ethTopic4 = "topic4" -) - func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*api.EthFilterResult, error) { res := &api.EthFilterResult{} for _, ev := range evs { @@ -1263,7 +1277,7 @@ func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*api.EthFilterResu for _, entry := range ev.Entries { hash := api.EthHashData(entry.Value) - if entry.Key == ethTopic1 || entry.Key == ethTopic2 || entry.Key == ethTopic3 || entry.Key == ethTopic4 { + if entry.Key == api.EthTopic1 || entry.Key == api.EthTopic2 || entry.Key == api.EthTopic3 || entry.Key == api.EthTopic4 { log.Topics = append(log.Topics, hash) } else { log.Data = append(log.Data, hash)