Add new tracing API

This commit is contained in:
Fridrik Asmundsson 2023-07-24 14:55:42 +00:00
parent c4214e23bf
commit 1b0f54a61b
16 changed files with 613 additions and 22 deletions

View File

@ -868,6 +868,13 @@ type FullNode interface {
// Returns the client version
Web3ClientVersion(ctx context.Context) (string, error) //perm:read
// TraceAPI related methods
//
// Returns traces created at given block
TraceBlock(ctx context.Context, blkNum string) (interface{}, error) //perm:read
// Replays all transactions in a block returning the requested traces for each transaction
TraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) (interface{}, error) //perm:read
// 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

View File

@ -127,4 +127,6 @@ type Gateway interface {
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
Web3ClientVersion(ctx context.Context) (string, error)
TraceBlock(ctx context.Context, blkNum string) (interface{}, error)
TraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) (interface{}, error)
}

View File

@ -40,6 +40,9 @@ func CreateEthRPCAliases(as apitypes.Aliaser) {
as.AliasMethod("eth_subscribe", "Filecoin.EthSubscribe")
as.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe")
as.AliasMethod("trace_block", "Filecoin.TraceBlock")
as.AliasMethod("trace_replayBlockTransactions", "Filecoin.TraceReplayBlockTransactions")
as.AliasMethod("net_version", "Filecoin.NetVersion")
as.AliasMethod("net_listening", "Filecoin.NetListening")

View File

@ -4023,6 +4023,36 @@ func (mr *MockFullNodeMockRecorder) SyncValidateTipset(arg0, arg1 interface{}) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncValidateTipset", reflect.TypeOf((*MockFullNode)(nil).SyncValidateTipset), arg0, arg1)
}
// TraceBlock mocks base method.
func (m *MockFullNode) TraceBlock(arg0 context.Context, arg1 string) (interface{}, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TraceBlock", arg0, arg1)
ret0, _ := ret[0].(interface{})
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TraceBlock indicates an expected call of TraceBlock.
func (mr *MockFullNodeMockRecorder) TraceBlock(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TraceBlock", reflect.TypeOf((*MockFullNode)(nil).TraceBlock), arg0, arg1)
}
// TraceReplayBlockTransactions mocks base method.
func (m *MockFullNode) TraceReplayBlockTransactions(arg0 context.Context, arg1 string, arg2 []string) (interface{}, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TraceReplayBlockTransactions", arg0, arg1, arg2)
ret0, _ := ret[0].(interface{})
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TraceReplayBlockTransactions indicates an expected call of TraceReplayBlockTransactions.
func (mr *MockFullNodeMockRecorder) TraceReplayBlockTransactions(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TraceReplayBlockTransactions", reflect.TypeOf((*MockFullNode)(nil).TraceReplayBlockTransactions), arg0, arg1, arg2)
}
// Version mocks base method.
func (m *MockFullNode) Version(arg0 context.Context) (api.APIVersion, error) {
m.ctrl.T.Helper()

View File

@ -596,6 +596,10 @@ type FullNodeMethods struct {
SyncValidateTipset func(p0 context.Context, p1 types.TipSetKey) (bool, error) `perm:"read"`
TraceBlock func(p0 context.Context, p1 string) (interface{}, error) `perm:"read"`
TraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) (interface{}, error) `perm:"read"`
WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `perm:"read"`
WalletDefaultAddress func(p0 context.Context) (address.Address, error) `perm:"write"`
@ -814,6 +818,10 @@ type GatewayMethods struct {
StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) ``
TraceBlock func(p0 context.Context, p1 string) (interface{}, error) ``
TraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) (interface{}, error) ``
Version func(p0 context.Context) (APIVersion, error) ``
WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) ``
@ -3997,6 +4005,28 @@ func (s *FullNodeStub) SyncValidateTipset(p0 context.Context, p1 types.TipSetKey
return false, ErrNotSupported
}
func (s *FullNodeStruct) TraceBlock(p0 context.Context, p1 string) (interface{}, error) {
if s.Internal.TraceBlock == nil {
return nil, ErrNotSupported
}
return s.Internal.TraceBlock(p0, p1)
}
func (s *FullNodeStub) TraceBlock(p0 context.Context, p1 string) (interface{}, error) {
return nil, ErrNotSupported
}
func (s *FullNodeStruct) TraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) (interface{}, error) {
if s.Internal.TraceReplayBlockTransactions == nil {
return nil, ErrNotSupported
}
return s.Internal.TraceReplayBlockTransactions(p0, p1, p2)
}
func (s *FullNodeStub) TraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) (interface{}, error) {
return nil, ErrNotSupported
}
func (s *FullNodeStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) {
if s.Internal.WalletBalance == nil {
return *new(types.BigInt), ErrNotSupported
@ -5130,6 +5160,28 @@ func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3
return nil, ErrNotSupported
}
func (s *GatewayStruct) TraceBlock(p0 context.Context, p1 string) (interface{}, error) {
if s.Internal.TraceBlock == nil {
return nil, ErrNotSupported
}
return s.Internal.TraceBlock(p0, p1)
}
func (s *GatewayStub) TraceBlock(p0 context.Context, p1 string) (interface{}, error) {
return nil, ErrNotSupported
}
func (s *GatewayStruct) TraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) (interface{}, error) {
if s.Internal.TraceReplayBlockTransactions == nil {
return nil, ErrNotSupported
}
return s.Internal.TraceReplayBlockTransactions(p0, p1, p2)
}
func (s *GatewayStub) TraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) (interface{}, error) {
return nil, ErrNotSupported
}
func (s *GatewayStruct) Version(p0 context.Context) (APIVersion, error) {
if s.Internal.Version == nil {
return *new(APIVersion), ErrNotSupported

View File

@ -2289,7 +2289,7 @@ func (t *GasTrace) UnmarshalCBOR(r io.Reader) (err error) {
return nil
}
var lengthBufMessageTrace = []byte{134}
var lengthBufMessageTrace = []byte{137}
func (t *MessageTrace) MarshalCBOR(w io.Writer) error {
if t == nil {
@ -2343,6 +2343,23 @@ func (t *MessageTrace) MarshalCBOR(w io.Writer) error {
return err
}
// t.GasLimit (uint64) (uint64)
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasLimit)); err != nil {
return err
}
// t.ReadOnly (bool) (bool)
if err := cbg.WriteBool(w, t.ReadOnly); err != nil {
return err
}
// t.CodeCid (cid.Cid) (struct)
if err := cbg.WriteCid(cw, t.CodeCid); err != nil {
return xerrors.Errorf("failed to write cid field t.CodeCid: %w", err)
}
return nil
}
@ -2365,7 +2382,7 @@ func (t *MessageTrace) UnmarshalCBOR(r io.Reader) (err error) {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 6 {
if extra != 9 {
return fmt.Errorf("cbor input had wrong number of fields")
}
@ -2444,6 +2461,49 @@ func (t *MessageTrace) UnmarshalCBOR(r io.Reader) (err error) {
}
t.ParamsCodec = uint64(extra)
}
// t.GasLimit (uint64) (uint64)
{
maj, extra, err = cr.ReadHeader()
if err != nil {
return err
}
if maj != cbg.MajUnsignedInt {
return fmt.Errorf("wrong type for uint64 field")
}
t.GasLimit = uint64(extra)
}
// t.ReadOnly (bool) (bool)
maj, extra, err = cr.ReadHeader()
if err != nil {
return err
}
if maj != cbg.MajOther {
return fmt.Errorf("booleans must be major type 7")
}
switch extra {
case 20:
t.ReadOnly = false
case 21:
t.ReadOnly = true
default:
return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra)
}
// t.CodeCid (cid.Cid) (struct)
{
c, err := cbg.ReadCid(cr)
if err != nil {
return xerrors.Errorf("failed to read cid field t.CodeCid: %w", err)
}
t.CodeCid = c
}
return nil
}

View File

@ -4,6 +4,8 @@ import (
"encoding/json"
"time"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/exitcode"
@ -24,6 +26,9 @@ type MessageTrace struct {
Method abi.MethodNum
Params []byte
ParamsCodec uint64
GasLimit uint64
ReadOnly bool
CodeCid cid.Cid
}
type ReturnTrace struct {

View File

@ -4873,7 +4873,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -4897,7 +4902,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -5103,7 +5113,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -5127,7 +5142,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -6493,7 +6513,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -6517,7 +6542,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,

View File

@ -287,6 +287,9 @@
* [SyncUnmarkAllBad](#SyncUnmarkAllBad)
* [SyncUnmarkBad](#SyncUnmarkBad)
* [SyncValidateTipset](#SyncValidateTipset)
* [Trace](#Trace)
* [TraceBlock](#TraceBlock)
* [TraceReplayBlockTransactions](#TraceReplayBlockTransactions)
* [Wallet](#Wallet)
* [WalletBalance](#WalletBalance)
* [WalletDefaultAddress](#WalletDefaultAddress)
@ -6312,7 +6315,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -6336,7 +6344,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -6542,7 +6555,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -6566,7 +6584,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -8061,7 +8084,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -8085,7 +8113,12 @@ Response:
"Value": "0",
"Method": 1,
"Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42
"ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
},
"MsgRct": {
"ExitCode": 0,
@ -8790,6 +8823,44 @@ Inputs:
Response: `true`
## Trace
### TraceBlock
TraceAPI related methods
Returns traces created at given block
Perms: read
Inputs:
```json
[
"string value"
]
```
Response: `{}`
### TraceReplayBlockTransactions
Replays all transactions in a block returning the requested traces for each transaction
Perms: read
Inputs:
```json
[
"string value",
[
"string value"
]
]
```
Response: `{}`
## Wallet

View File

@ -144,6 +144,8 @@ type TargetAPI interface {
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
Web3ClientVersion(ctx context.Context) (string, error)
TraceBlock(ctx context.Context, blkNum string) (interface{}, error)
TraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) (interface{}, error)
}
var _ TargetAPI = *new(api.FullNode) // gateway depends on latest

View File

@ -21,14 +21,6 @@ import (
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)
func (gw *Node) Web3ClientVersion(ctx context.Context) (string, error) {
if err := gw.limit(ctx, basicRateLimitTokens); err != nil {
return "", err
}
return gw.target.Web3ClientVersion(ctx)
}
func (gw *Node) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) {
// gateway provides public API, so it can't hold user accounts
return []ethtypes.EthAddress{}, nil
@ -582,6 +574,38 @@ func (gw *Node) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionI
return ok, nil
}
func (gw *Node) Web3ClientVersion(ctx context.Context) (string, error) {
if err := gw.limit(ctx, basicRateLimitTokens); err != nil {
return "", err
}
return gw.target.Web3ClientVersion(ctx)
}
func (gw *Node) TraceBlock(ctx context.Context, blkNum string) (interface{}, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return 0, err
}
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
return ethtypes.EthBlock{}, err
}
return gw.target.TraceBlock(ctx, blkNum)
}
func (gw *Node) TraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) (interface{}, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return 0, err
}
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
return ethtypes.EthBlock{}, err
}
return gw.target.TraceReplayBlockTransactions(ctx, blkNum, traceTypes)
}
var EthMaxFiltersPerConn = 16 // todo make this configurable
func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) {

View File

@ -155,6 +155,7 @@ var ChainNode = Options(
Override(new(stmgr.StateManagerAPI), rpcstmgr.NewRPCStateManager),
Override(new(full.EthModuleAPI), From(new(api.Gateway))),
Override(new(full.EthEventAPI), From(new(api.Gateway))),
Override(new(full.EthTraceAPI), From(new(api.Gateway))),
),
// Full node API / service startup
@ -270,10 +271,12 @@ func ConfigFullNode(c interface{}) Option {
If(cfg.Fevm.EnableEthRPC,
Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm)),
Override(new(full.EthEventAPI), modules.EthEventAPI(cfg.Fevm)),
Override(new(full.EthTraceAPI), modules.EthTraceAPI()),
),
If(!cfg.Fevm.EnableEthRPC,
Override(new(full.EthModuleAPI), &full.EthModuleDummy{}),
Override(new(full.EthEventAPI), &full.EthModuleDummy{}),
Override(new(full.EthTraceAPI), &full.EthModuleDummy{}),
),
),

View File

@ -36,6 +36,7 @@ type FullNodeAPI struct {
full.SyncAPI
full.RaftAPI
full.EthAPI
full.EthTraceAPI
DS dtypes.MetadataDS
NetworkName dtypes.NetworkName

View File

@ -178,5 +178,14 @@ func (e *EthModuleDummy) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubs
return false, ErrModuleDisabled
}
func (e *EthModuleDummy) TraceBlock(ctx context.Context, blkNum string) (interface{}, error) {
return nil, ErrModuleDisabled
}
func (e *EthModuleDummy) TraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) (interface{}, error) {
return nil, ErrModuleDisabled
}
var _ EthModuleAPI = &EthModuleDummy{}
var _ EthEventAPI = &EthModuleDummy{}
var _ EthTraceAPI = &EthModuleDummy{}

273
node/impl/full/trace.go Normal file
View File

@ -0,0 +1,273 @@
package full
import (
"context"
"encoding/hex"
"fmt"
"go.uber.org/fx"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/api"
"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"
)
type EthTraceAPI interface {
TraceBlock(ctx context.Context, blkNum string) (interface{}, error)
TraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) (interface{}, error)
}
var (
_ EthTraceAPI = *new(api.FullNode)
)
type EthTrace struct {
fx.In
Chain *store.ChainStore
StateManager *stmgr.StateManager
ChainAPI
EthModuleAPI
}
var _ EthTraceAPI = (*EthTrace)(nil)
type Trace struct {
Action Action `json:"action"`
Result Result `json:"result"`
Subtraces int `json:"subtraces"`
TraceAddress []int `json:"traceAddress"`
Type string `json:"Type"`
}
type TraceBlock struct {
*Trace
BlockHash ethtypes.EthHash `json:"blockHash"`
BlockNumber int64 `json:"blockNumber"`
TransactionHash ethtypes.EthHash `json:"transactionHash"`
TransactionPosition int `json:"transactionPosition"`
}
type TraceReplayBlockTransaction struct {
Output string `json:"output"`
StateDiff *string `json:"stateDiff"`
Trace []*Trace `json:"trace"`
TransactionHash ethtypes.EthHash `json:"transactionHash"`
VmTrace *string `json:"vmTrace"`
}
type Action struct {
CallType string `json:"callType"`
From string `json:"from"`
To string `json:"to"`
Gas ethtypes.EthUint64 `json:"gas"`
Input string `json:"input"`
Value ethtypes.EthBigInt `json:"value"`
}
type Result struct {
GasUsed ethtypes.EthUint64 `json:"gasUsed"`
Output string `json:"output"`
}
func (e *EthTrace) TraceBlock(ctx context.Context, blkNum string) (interface{}, error) {
ts, err := e.getTipsetByBlockNr(ctx, blkNum, false)
if err != nil {
return nil, err
}
_, trace, err := e.StateManager.ExecutionTrace(ctx, ts)
if err != nil {
return nil, xerrors.Errorf("failed to compute base state: %w", err)
}
tsParent, err := e.ChainAPI.ChainGetTipSetByHeight(ctx, ts.Height()+1, e.Chain.GetHeaviestTipSet().Key())
if err != nil {
return nil, fmt.Errorf("cannot get tipset at height: %v", ts.Height()+1)
}
msgs, err := e.ChainGetParentMessages(ctx, tsParent.Blocks()[0].Cid())
if err != nil {
return nil, err
}
cid, err := ts.Key().Cid()
if err != nil {
return nil, err
}
blkHash, err := ethtypes.EthHashFromCid(cid)
if err != nil {
return nil, err
}
allTraces := make([]*TraceBlock, 0, len(trace))
for _, ir := range trace {
// ignore messages from f00
if ir.Msg.From.String() == "f00" {
continue
}
idx := -1
for msgIdx, msg := range msgs {
if ir.Msg.From == msg.Message.From {
idx = msgIdx
break
}
}
if idx == -1 {
log.Warnf("cannot resolve message index for cid: %s", ir.MsgCid)
continue
}
txHash, err := e.EthGetTransactionHashByCid(ctx, ir.MsgCid)
if err != nil {
return nil, err
}
if txHash == nil {
log.Warnf("cannot find transaction hash for cid %s", ir.MsgCid)
continue
}
traces := []*Trace{}
buildTraces(&traces, []int{}, ir.ExecutionTrace)
traceBlocks := make([]*TraceBlock, 0, len(trace))
for _, trace := range traces {
traceBlocks = append(traceBlocks, &TraceBlock{
Trace: trace,
BlockHash: blkHash,
BlockNumber: int64(ts.Height()),
TransactionHash: *txHash,
TransactionPosition: idx,
})
}
allTraces = append(allTraces, traceBlocks...)
}
return allTraces, nil
}
func (e *EthTrace) TraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) (interface{}, error) {
if len(traceTypes) != 1 || traceTypes[0] != "trace" {
return nil, fmt.Errorf("only 'trace' is supported")
}
ts, err := e.getTipsetByBlockNr(ctx, blkNum, false)
if err != nil {
return nil, err
}
_, trace, err := e.StateManager.ExecutionTrace(ctx, ts)
if err != nil {
return nil, xerrors.Errorf("failed when calling ExecutionTrace: %w", err)
}
allTraces := make([]*TraceReplayBlockTransaction, 0, len(trace))
for _, ir := range trace {
// ignore messages from f00
if ir.Msg.From.String() == "f00" {
continue
}
txHash, err := e.EthGetTransactionHashByCid(ctx, ir.MsgCid)
if err != nil {
return nil, err
}
if txHash == nil {
log.Warnf("cannot find transaction hash for cid %s", ir.MsgCid)
continue
}
t := TraceReplayBlockTransaction{
Output: hex.EncodeToString(ir.MsgRct.Return),
TransactionHash: *txHash,
StateDiff: nil,
VmTrace: nil,
}
buildTraces(&t.Trace, []int{}, ir.ExecutionTrace)
allTraces = append(allTraces, &t)
}
return allTraces, nil
}
// buildTraces recursively builds the traces for a given ExecutionTrace by walking the subcalls
func buildTraces(traces *[]*Trace, addr []int, et types.ExecutionTrace) {
callType := "call"
if et.Msg.ReadOnly {
callType = "staticcall"
}
// TODO: add check for determining if this this should be delegatecall
if false {
callType = "delegatecall"
}
*traces = append(*traces, &Trace{
Action: Action{
CallType: callType,
From: et.Msg.From.String(),
To: et.Msg.To.String(),
Gas: ethtypes.EthUint64(et.Msg.GasLimit),
Input: hex.EncodeToString(et.Msg.Params),
Value: ethtypes.EthBigInt(et.Msg.Value),
},
Result: Result{
GasUsed: ethtypes.EthUint64(et.SumGas().TotalGas),
Output: hex.EncodeToString(et.MsgRct.Return),
},
Subtraces: len(et.Subcalls),
TraceAddress: addr,
Type: callType,
})
for i, call := range et.Subcalls {
buildTraces(traces, append(addr, i), call)
}
}
// TODO: refactor this to be shared code
func (e *EthTrace) getTipsetByBlockNr(ctx context.Context, blkParam string, strict bool) (*types.TipSet, error) {
if blkParam == "earliest" {
return nil, fmt.Errorf("block param \"earliest\" is not supported")
}
head := e.Chain.GetHeaviestTipSet()
switch blkParam {
case "pending":
return head, nil
case "latest":
parent, err := e.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)
}
if abi.ChainEpoch(num) > head.Height()-1 {
return nil, fmt.Errorf("requested a future epoch (beyond 'latest')")
}
ts, err := e.ChainAPI.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(num), head.Key())
if err != nil {
return nil, fmt.Errorf("cannot get tipset at height: %v", num)
}
if strict && ts.Height() != abi.ChainEpoch(num) {
return nil, ErrNullRound
}
return ts, nil
}
}

19
node/modules/trace.go Normal file
View File

@ -0,0 +1,19 @@
package modules
import (
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/node/impl/full"
)
func EthTraceAPI() func(*store.ChainStore, *stmgr.StateManager, full.EthModuleAPI, full.ChainAPI) (*full.EthTrace, error) {
return func(cs *store.ChainStore, sm *stmgr.StateManager, evapi full.EthModuleAPI, chainapi full.ChainAPI) (*full.EthTrace, error) {
return &full.EthTrace{
Chain: cs,
StateManager: sm,
ChainAPI: chainapi,
EthModuleAPI: evapi,
}, nil
}
}