Eth JSON-RPC API: return logs in eth_getTransactionReceipt.

This commit is contained in:
Raúl Kripalani 2022-11-16 13:53:27 +00:00
parent 7c2dcc8db6
commit 32385a97cd
11 changed files with 183 additions and 22 deletions

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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"
}
]
}
```

View File

@ -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

View File

@ -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)