Add new tracing API
This commit is contained in:
parent
c4214e23bf
commit
1b0f54a61b
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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{}),
|
||||
),
|
||||
),
|
||||
|
||||
|
@ -36,6 +36,7 @@ type FullNodeAPI struct {
|
||||
full.SyncAPI
|
||||
full.RaftAPI
|
||||
full.EthAPI
|
||||
full.EthTraceAPI
|
||||
|
||||
DS dtypes.MetadataDS
|
||||
NetworkName dtypes.NetworkName
|
||||
|
@ -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
273
node/impl/full/trace.go
Normal 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
19
node/modules/trace.go
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user