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 returns some basic information about the blockstore
ChainBlockstoreInfo(context.Context) (map[string]interface{}, error) //perm:read 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 estimates gas fee cap
GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) //perm:read GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) //perm:read

View File

@ -17,11 +17,20 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/go-address" "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/go-state-types/big"
builtintypes "github.com/filecoin-project/go-state-types/builtin" 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/eam"
"github.com/filecoin-project/lotus/build" "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 type EthUint64 uint64
@ -185,14 +194,12 @@ func (c *EthCall) UnmarshalJSON(b []byte) error {
} }
type EthTxReceipt struct { type EthTxReceipt struct {
TransactionHash EthHash `json:"transactionHash"` TransactionHash EthHash `json:"transactionHash"`
TransactionIndex EthUint64 `json:"transactionIndex"` TransactionIndex EthUint64 `json:"transactionIndex"`
BlockHash EthHash `json:"blockHash"` BlockHash EthHash `json:"blockHash"`
BlockNumber EthUint64 `json:"blockNumber"` BlockNumber EthUint64 `json:"blockNumber"`
From EthAddress `json:"from"` From EthAddress `json:"from"`
To *EthAddress `json:"to"` To *EthAddress `json:"to"`
// Logs
// LogsBloom
StateRoot EthHash `json:"root"` StateRoot EthHash `json:"root"`
Status EthUint64 `json:"status"` Status EthUint64 `json:"status"`
ContractAddress *EthAddress `json:"contractAddress"` ContractAddress *EthAddress `json:"contractAddress"`
@ -200,10 +207,10 @@ type EthTxReceipt struct {
GasUsed EthUint64 `json:"gasUsed"` GasUsed EthUint64 `json:"gasUsed"`
EffectiveGasPrice EthBigInt `json:"effectiveGasPrice"` EffectiveGasPrice EthBigInt `json:"effectiveGasPrice"`
LogsBloom EthBytes `json:"logsBloom"` 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{ receipt := EthTxReceipt{
TransactionHash: tx.Hash, TransactionHash: tx.Hash,
TransactionIndex: tx.TransactionIndex, TransactionIndex: tx.TransactionIndex,
@ -213,7 +220,6 @@ func NewEthTxReceipt(tx EthTx, lookup *MsgLookup, replay *InvocResult) (EthTxRec
To: tx.To, To: tx.To,
StateRoot: EmptyEthHash, StateRoot: EmptyEthHash,
LogsBloom: []byte{0}, LogsBloom: []byte{0},
Logs: []string{},
} }
if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() { if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() {
@ -233,6 +239,41 @@ func NewEthTxReceipt(tx EthTx, lookup *MsgLookup, replay *InvocResult) (EthTxRec
receipt.Status = 0 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) receipt.GasUsed = EthUint64(lookup.Receipt.GasUsed)
// TODO: handle CumulativeGasUsed // 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) 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. // ChainGetGenesis mocks base method.
func (m *MockFullNode) ChainGetGenesis(arg0 context.Context) (*types.TipSet, error) { func (m *MockFullNode) ChainGetGenesis(arg0 context.Context) (*types.TipSet, error) {
m.ctrl.T.Helper() 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"` 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"` ChainGetGenesis func(p0 context.Context) (*types.TipSet, error) `perm:"read"`
ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, 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 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) { func (s *FullNodeStruct) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) {
if s.Internal.ChainGetGenesis == nil { if s.Internal.ChainGetGenesis == nil {
return nil, ErrNotSupported 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) * [ChainExport](#ChainExport)
* [ChainGetBlock](#ChainGetBlock) * [ChainGetBlock](#ChainGetBlock)
* [ChainGetBlockMessages](#ChainGetBlockMessages) * [ChainGetBlockMessages](#ChainGetBlockMessages)
* [ChainGetEvents](#ChainGetEvents)
* [ChainGetGenesis](#ChainGetGenesis) * [ChainGetGenesis](#ChainGetGenesis)
* [ChainGetMessage](#ChainGetMessage) * [ChainGetMessage](#ChainGetMessage)
* [ChainGetMessagesInTipset](#ChainGetMessagesInTipset) * [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
ChainGetGenesis returns the genesis tipset. ChainGetGenesis returns the genesis tipset.
@ -2703,7 +2735,21 @@ Response:
"effectiveGasPrice": "0x0", "effectiveGasPrice": "0x0",
"logsBloom": "0x07", "logsBloom": "0x07",
"logs": [ "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" "context"
"encoding/json" "encoding/json"
"io" "io"
"math"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -24,6 +25,7 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/go-address" "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/abi"
"github.com/filecoin-project/specs-actors/actors/util/adt" "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 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 { func (a *ChainAPI) ChainPrune(ctx context.Context, opts api.PruneOpts) error {
pruner, ok := a.BaseBlockstore.(interface { pruner, ok := a.BaseBlockstore.(interface {
PruneChain(opts api.PruneOpts) error 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) EthCall(ctx context.Context, tx api.EthCall, blkParam string) (api.EthBytes, error)
EthMaxPriorityFeePerGas(ctx context.Context) (api.EthBigInt, error) EthMaxPriorityFeePerGas(ctx context.Context) (api.EthBigInt, error)
EthSendRawTransaction(ctx context.Context, rawTx api.EthBytes) (api.EthHash, error) EthSendRawTransaction(ctx context.Context, rawTx api.EthBytes) (api.EthHash, error)
// EthFeeHistory(ctx context.Context, blkCount string)
} }
type EthEventAPI interface { type EthEventAPI interface {
@ -237,7 +236,29 @@ func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash api.Eth
return nil, nil 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 { if err != nil {
return nil, nil return nil, nil
} }
@ -1242,13 +1263,6 @@ type filterTipSetCollector interface {
TakeCollectedTipSets(context.Context) []types.TipSetKey TakeCollectedTipSets(context.Context) []types.TipSetKey
} }
var (
ethTopic1 = "topic1"
ethTopic2 = "topic2"
ethTopic3 = "topic3"
ethTopic4 = "topic4"
)
func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*api.EthFilterResult, error) { func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*api.EthFilterResult, error) {
res := &api.EthFilterResult{} res := &api.EthFilterResult{}
for _, ev := range evs { for _, ev := range evs {
@ -1263,7 +1277,7 @@ func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*api.EthFilterResu
for _, entry := range ev.Entries { for _, entry := range ev.Entries {
hash := api.EthHashData(entry.Value) 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) log.Topics = append(log.Topics, hash)
} else { } else {
log.Data = append(log.Data, hash) log.Data = append(log.Data, hash)