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 // Returns the client version
Web3ClientVersion(ctx context.Context) (string, error) //perm:read 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 // CreateBackup creates node backup onder the specified file name. The
// method requires that the lotus daemon is running with the // method requires that the lotus daemon is running with the
// LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that // 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) EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
Web3ClientVersion(ctx context.Context) (string, 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_subscribe", "Filecoin.EthSubscribe")
as.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe") 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_version", "Filecoin.NetVersion")
as.AliasMethod("net_listening", "Filecoin.NetListening") 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) 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. // Version mocks base method.
func (m *MockFullNode) Version(arg0 context.Context) (api.APIVersion, error) { func (m *MockFullNode) Version(arg0 context.Context) (api.APIVersion, error) {
m.ctrl.T.Helper() 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"` 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"` WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `perm:"read"`
WalletDefaultAddress func(p0 context.Context) (address.Address, error) `perm:"write"` 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) `` 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) `` Version func(p0 context.Context) (APIVersion, error) ``
WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, 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 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) { func (s *FullNodeStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) {
if s.Internal.WalletBalance == nil { if s.Internal.WalletBalance == nil {
return *new(types.BigInt), ErrNotSupported 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 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) { func (s *GatewayStruct) Version(p0 context.Context) (APIVersion, error) {
if s.Internal.Version == nil { if s.Internal.Version == nil {
return *new(APIVersion), ErrNotSupported return *new(APIVersion), ErrNotSupported

View File

@ -2289,7 +2289,7 @@ func (t *GasTrace) UnmarshalCBOR(r io.Reader) (err error) {
return nil return nil
} }
var lengthBufMessageTrace = []byte{134} var lengthBufMessageTrace = []byte{137}
func (t *MessageTrace) MarshalCBOR(w io.Writer) error { func (t *MessageTrace) MarshalCBOR(w io.Writer) error {
if t == nil { if t == nil {
@ -2343,6 +2343,23 @@ func (t *MessageTrace) MarshalCBOR(w io.Writer) error {
return err 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 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") 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") 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.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 return nil
} }

View File

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

View File

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

View File

@ -287,6 +287,9 @@
* [SyncUnmarkAllBad](#SyncUnmarkAllBad) * [SyncUnmarkAllBad](#SyncUnmarkAllBad)
* [SyncUnmarkBad](#SyncUnmarkBad) * [SyncUnmarkBad](#SyncUnmarkBad)
* [SyncValidateTipset](#SyncValidateTipset) * [SyncValidateTipset](#SyncValidateTipset)
* [Trace](#Trace)
* [TraceBlock](#TraceBlock)
* [TraceReplayBlockTransactions](#TraceReplayBlockTransactions)
* [Wallet](#Wallet) * [Wallet](#Wallet)
* [WalletBalance](#WalletBalance) * [WalletBalance](#WalletBalance)
* [WalletDefaultAddress](#WalletDefaultAddress) * [WalletDefaultAddress](#WalletDefaultAddress)
@ -6312,7 +6315,12 @@ Response:
"Value": "0", "Value": "0",
"Method": 1, "Method": 1,
"Params": "Ynl0ZSBhcnJheQ==", "Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42 "ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}, },
"MsgRct": { "MsgRct": {
"ExitCode": 0, "ExitCode": 0,
@ -6336,7 +6344,12 @@ Response:
"Value": "0", "Value": "0",
"Method": 1, "Method": 1,
"Params": "Ynl0ZSBhcnJheQ==", "Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42 "ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}, },
"MsgRct": { "MsgRct": {
"ExitCode": 0, "ExitCode": 0,
@ -6542,7 +6555,12 @@ Response:
"Value": "0", "Value": "0",
"Method": 1, "Method": 1,
"Params": "Ynl0ZSBhcnJheQ==", "Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42 "ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}, },
"MsgRct": { "MsgRct": {
"ExitCode": 0, "ExitCode": 0,
@ -6566,7 +6584,12 @@ Response:
"Value": "0", "Value": "0",
"Method": 1, "Method": 1,
"Params": "Ynl0ZSBhcnJheQ==", "Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42 "ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}, },
"MsgRct": { "MsgRct": {
"ExitCode": 0, "ExitCode": 0,
@ -8061,7 +8084,12 @@ Response:
"Value": "0", "Value": "0",
"Method": 1, "Method": 1,
"Params": "Ynl0ZSBhcnJheQ==", "Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42 "ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}, },
"MsgRct": { "MsgRct": {
"ExitCode": 0, "ExitCode": 0,
@ -8085,7 +8113,12 @@ Response:
"Value": "0", "Value": "0",
"Method": 1, "Method": 1,
"Params": "Ynl0ZSBhcnJheQ==", "Params": "Ynl0ZSBhcnJheQ==",
"ParamsCodec": 42 "ParamsCodec": 42,
"GasLimit": 42,
"ReadOnly": true,
"CodeCid": {
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
}
}, },
"MsgRct": { "MsgRct": {
"ExitCode": 0, "ExitCode": 0,
@ -8790,6 +8823,44 @@ Inputs:
Response: `true` 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 ## Wallet

View File

@ -144,6 +144,8 @@ type TargetAPI interface {
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
Web3ClientVersion(ctx context.Context) (string, 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 var _ TargetAPI = *new(api.FullNode) // gateway depends on latest

View File

@ -21,14 +21,6 @@ import (
"github.com/filecoin-project/lotus/chain/types/ethtypes" "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) { func (gw *Node) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) {
// gateway provides public API, so it can't hold user accounts // gateway provides public API, so it can't hold user accounts
return []ethtypes.EthAddress{}, nil return []ethtypes.EthAddress{}, nil
@ -582,6 +574,38 @@ func (gw *Node) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionI
return ok, nil 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 var EthMaxFiltersPerConn = 16 // todo make this configurable
func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) { 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(stmgr.StateManagerAPI), rpcstmgr.NewRPCStateManager),
Override(new(full.EthModuleAPI), From(new(api.Gateway))), Override(new(full.EthModuleAPI), From(new(api.Gateway))),
Override(new(full.EthEventAPI), 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 // Full node API / service startup
@ -270,10 +271,12 @@ func ConfigFullNode(c interface{}) Option {
If(cfg.Fevm.EnableEthRPC, If(cfg.Fevm.EnableEthRPC,
Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm)), Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm)),
Override(new(full.EthEventAPI), modules.EthEventAPI(cfg.Fevm)), Override(new(full.EthEventAPI), modules.EthEventAPI(cfg.Fevm)),
Override(new(full.EthTraceAPI), modules.EthTraceAPI()),
), ),
If(!cfg.Fevm.EnableEthRPC, If(!cfg.Fevm.EnableEthRPC,
Override(new(full.EthModuleAPI), &full.EthModuleDummy{}), Override(new(full.EthModuleAPI), &full.EthModuleDummy{}),
Override(new(full.EthEventAPI), &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.SyncAPI
full.RaftAPI full.RaftAPI
full.EthAPI full.EthAPI
full.EthTraceAPI
DS dtypes.MetadataDS DS dtypes.MetadataDS
NetworkName dtypes.NetworkName NetworkName dtypes.NetworkName

View File

@ -178,5 +178,14 @@ func (e *EthModuleDummy) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubs
return false, ErrModuleDisabled 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 _ EthModuleAPI = &EthModuleDummy{}
var _ EthEventAPI = &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
}
}