Skip to content

Commit

Permalink
feat: Add trace filter API
Browse files Browse the repository at this point in the history
  • Loading branch information
simlecode committed Oct 17, 2024
1 parent 372816b commit 81176b4
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 5 deletions.
1 change: 1 addition & 0 deletions app/node/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ func aliasETHAPI(rpcServer *jsonrpc.RPCServer) {
rpcServer.AliasMethod("trace_block", "Filecoin.EthTraceBlock")
rpcServer.AliasMethod("trace_replayBlockTransactions", "Filecoin.EthTraceReplayBlockTransactions")
rpcServer.AliasMethod("trace_transaction", "Filecoin.EthTraceTransaction")
rpcServer.AliasMethod("trace_filter", "Filecoin.EthTraceFilter")

rpcServer.AliasMethod("net_version", "Filecoin.NetVersion")
rpcServer.AliasMethod("net_listening", "Filecoin.NetListening")
Expand Down
4 changes: 4 additions & 0 deletions app/submodule/eth/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ func (e *ethAPIDummy) EthTraceTransaction(ctx context.Context, txHash string) ([
return nil, ErrModuleDisabled
}

func (e *ethAPIDummy) EthTraceFilter(ctx context.Context, filter types.EthTraceFilterCriteria) ([]*types.EthTraceFilterResult, error) {
return nil, ErrModuleDisabled
}

func (e *ethAPIDummy) start(_ context.Context) error {
return nil
}
Expand Down
166 changes: 166 additions & 0 deletions app/submodule/eth/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,172 @@ func (a *ethAPI) EthTraceTransaction(ctx context.Context, txHash string) ([]*typ
return txTraces, nil
}

func (a *ethAPI) EthTraceFilter(ctx context.Context, filter types.EthTraceFilterCriteria) ([]*types.EthTraceFilterResult, error) {
// Define EthBlockNumberFromString as a private function within EthTraceFilter
getEthBlockNumberFromString := func(ctx context.Context, block *string) (types.EthUint64, error) {
head := a.em.chainModule.ChainReader.GetHead()

blockValue := "latest"
if block != nil {
blockValue = *block
}

switch blockValue {
case "earliest":
return 0, fmt.Errorf("block param \"earliest\" is not supported")
case "pending":
return types.EthUint64(head.Height()), nil
case "latest":
parent, err := a.em.chainModule.ChainReader.GetTipSet(ctx, head.Parents())
if err != nil {
return 0, fmt.Errorf("cannot get parent tipset")
}
return types.EthUint64(parent.Height()), nil
case "safe":
latestHeight := head.Height() - 1
safeHeight := latestHeight - types.SafeEpochDelay
return types.EthUint64(safeHeight), nil
default:
blockNum, err := types.EthUint64FromHex(blockValue)
if err != nil {
return 0, fmt.Errorf("cannot parse fromBlock: %w", err)
}
return blockNum, err
}
}

fromBlock, err := getEthBlockNumberFromString(ctx, filter.FromBlock)
if err != nil {
return nil, fmt.Errorf("cannot parse fromBlock: %w", err)
}

toBlock, err := getEthBlockNumberFromString(ctx, filter.ToBlock)
if err != nil {
return nil, fmt.Errorf("cannot parse toBlock: %w", err)
}

var results []*types.EthTraceFilterResult

if filter.Count != nil {
// If filter.Count is specified and it is 0, return an empty result set immediately.
if *filter.Count == 0 {
return []*types.EthTraceFilterResult{}, nil
}

// If filter.Count is specified and is greater than the EthTraceFilterMaxResults config return error
if uint64(*filter.Count) > a.em.cfg.FevmConfig.EthTraceFilterMaxResults {
return nil, fmt.Errorf("invalid response count, requested %d, maximum supported is %d", *filter.Count, a.em.cfg.FevmConfig.EthTraceFilterMaxResults)
}
}

traceCounter := types.EthUint64(0)
for blkNum := fromBlock; blkNum <= toBlock; blkNum++ {
blockTraces, err := a.EthTraceBlock(ctx, strconv.FormatUint(uint64(blkNum), 10))
if err != nil {
return nil, fmt.Errorf("cannot get trace for block %d: %w", blkNum, err)
}

for _, _blockTrace := range blockTraces {
// Create a copy of blockTrace to avoid pointer quirks
blockTrace := *_blockTrace
match, err := matchFilterCriteria(&blockTrace, filter.FromAddress, filter.ToAddress)
if err != nil {
return nil, fmt.Errorf("cannot match filter for block %d: %w", blkNum, err)
}
if !match {
continue
}
traceCounter++
if filter.After != nil && traceCounter <= *filter.After {
continue
}

txTrace := types.EthTraceFilterResult{

Check failure on line 1467 in app/submodule/eth/eth_api.go

View workflow job for this annotation

GitHub Actions / check

S1016: should convert blockTrace (type github.com/filecoin-project/venus/venus-shared/actors/types.EthTraceBlock) to github.com/filecoin-project/venus/venus-shared/actors/types.EthTraceFilterResult instead of using struct literal (gosimple)
EthTrace: blockTrace.EthTrace,
BlockHash: blockTrace.BlockHash,
BlockNumber: blockTrace.BlockNumber,
TransactionHash: blockTrace.TransactionHash,
TransactionPosition: blockTrace.TransactionPosition,
}
results = append(results, &txTrace)

// If Count is specified, limit the results
if filter.Count != nil && types.EthUint64(len(results)) >= *filter.Count {
return results, nil
} else if filter.Count == nil && uint64(len(results)) > a.em.cfg.FevmConfig.EthTraceFilterMaxResults {
return nil, fmt.Errorf("too many results, maximum supported is %d, try paginating requests with After and Count", a.em.cfg.FevmConfig.EthTraceFilterMaxResults)
}
}
}

return results, nil
}

// matchFilterCriteria checks if a trace matches the filter criteria.
func matchFilterCriteria(trace *types.EthTraceBlock, fromDecodedAddresses []types.EthAddress, toDecodedAddresses []types.EthAddress) (bool, error) {
var traceTo types.EthAddress
var traceFrom types.EthAddress

switch trace.Type {
case "call":
action, ok := trace.Action.(*types.EthCallTraceAction)
if !ok {
return false, fmt.Errorf("invalid call trace action")
}
traceTo = action.To
traceFrom = action.From
case "create":
result, okResult := trace.Result.(*types.EthCreateTraceResult)
if !okResult {
return false, fmt.Errorf("invalid create trace result")
}

action, okAction := trace.Action.(*types.EthCreateTraceAction)
if !okAction {
return false, fmt.Errorf("invalid create trace action")
}

if result.Address == nil {
return false, fmt.Errorf("address is nil in create trace result")
}

traceTo = *result.Address
traceFrom = action.From
default:
return false, fmt.Errorf("invalid trace type: %s", trace.Type)
}

// Match FromAddress
if len(fromDecodedAddresses) > 0 {
fromMatch := false
for _, ethAddr := range fromDecodedAddresses {
if traceFrom == ethAddr {
fromMatch = true
break
}
}
if !fromMatch {
return false, nil
}
}

// Match ToAddress
if len(toDecodedAddresses) > 0 {
toMatch := false
for _, ethAddr := range toDecodedAddresses {
if traceTo == ethAddr {
toMatch = true
break
}
}
if !toMatch {
return false, nil
}
}

return true, nil
}

func calculateRewardsAndGasUsed(rewardPercentiles []float64, txGasRewards gasRewardSorter) ([]types.EthBigInt, int64) {
var gasUsedTotal int64
for _, tx := range txGasRewards {
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@ type FevmConfig struct {
// Set to 0 to keep all mappings
EthTxHashMappingLifetimeDays int `json:"ethTxHashMappingLifetimeDays"`

// EthTraceFilterMaxResults sets the maximum results returned per request by trace_filter
EthTraceFilterMaxResults uint64 `json:"ethTraceFilterMaxResults"`

// EthBlkCacheSize specifies the size of the cache used for caching Ethereum blocks.
// This cache enhances the performance of the eth_getBlockByHash RPC call by minimizing the need to access chain state for
// recently requested blocks that are already cached.
Expand All @@ -513,6 +516,7 @@ func newFevmConfig() *FevmConfig {
return &FevmConfig{
EnableEthRPC: false,
EthTxHashMappingLifetimeDays: 0,
EthTraceFilterMaxResults: 500,
EthBlkCacheSize: 500,
Event: EventConfig{
DisableRealTimeFilterAPI: false,
Expand Down
14 changes: 14 additions & 0 deletions venus-devtool/api-gen/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,20 @@ func init() {
addExample(&manifest.Manifest{})
addExample(gpbft.NetworkName("filecoin"))
addExample(gpbft.INITIAL_PHASE)

after := types.EthUint64(0)
count := types.EthUint64(100)

ethTraceFilterCriteria := types.EthTraceFilterCriteria{
FromBlock: pstring("latest"),
ToBlock: pstring("latest"),
FromAddress: types.EthAddressList{ethaddr},
ToAddress: types.EthAddressList{ethaddr},
After: &after,
Count: &count,
}
addExample(&ethTraceFilterCriteria)
addExample(ethTraceFilterCriteria)
}

func ExampleValue(method string, t, parent reflect.Type) interface{} {
Expand Down
3 changes: 1 addition & 2 deletions venus-devtool/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/filecoin-project/go-fil-markets v1.28.3
github.com/filecoin-project/go-jsonrpc v0.6.0
github.com/filecoin-project/go-state-types v0.15.0-rc1
github.com/filecoin-project/lotus v1.30.0-rc1
github.com/filecoin-project/lotus v1.30.0-rc2
github.com/filecoin-project/venus v0.0.0-00010101000000-000000000000
github.com/google/uuid v1.6.0
github.com/ipfs/go-block-format v0.2.0
Expand Down Expand Up @@ -152,7 +152,6 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.59 // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions venus-devtool/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ github.com/filecoin-project/go-statemachine v1.0.3/go.mod h1:jZdXXiHa61n4NmgWFG4
github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI=
github.com/filecoin-project/go-statestore v0.2.0 h1:cRRO0aPLrxKQCZ2UOQbzFGn4WDNdofHZoGPjfNaAo5Q=
github.com/filecoin-project/go-statestore v0.2.0/go.mod h1:8sjBYbS35HwPzct7iT4lIXjLlYyPor80aU7t7a/Kspo=
github.com/filecoin-project/lotus v1.30.0-rc1 h1:cvieumxDhGffmugnvj5Xsvw26XJ9Xy/ck4PltJLmNpg=
github.com/filecoin-project/lotus v1.30.0-rc1/go.mod h1:gXQFTK6OpJIjg2yWnYsf0awszREDffb/X+LPCDmZkwI=
github.com/filecoin-project/lotus v1.30.0-rc2 h1:LLzMnb6dqxN5QHj4IAvDpFPYp8InXY8fvcTGr4uhpnw=
github.com/filecoin-project/lotus v1.30.0-rc2/go.mod h1:gXQFTK6OpJIjg2yWnYsf0awszREDffb/X+LPCDmZkwI=
github.com/filecoin-project/pubsub v1.0.0 h1:ZTmT27U07e54qV1mMiQo4HDr0buo8I1LDHBYLXlsNXM=
github.com/filecoin-project/pubsub v1.0.0/go.mod h1:GkpB33CcUtUNrLPhJgfdy4FDx4OMNR9k+46DHx/Lqrg=
github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao=
Expand Down
41 changes: 41 additions & 0 deletions venus-shared/actors/types/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1126,3 +1126,44 @@ type EthCreateTraceResult struct {
GasUsed EthUint64 `json:"gasUsed"`
Code EthBytes `json:"code"`
}

type EthTraceFilterResult struct {
*EthTrace
BlockHash EthHash `json:"blockHash"`
BlockNumber int64 `json:"blockNumber"`
TransactionHash EthHash `json:"transactionHash"`
TransactionPosition int `json:"transactionPosition"`
}

// EthTraceFilterCriteria defines the criteria for filtering traces.
type EthTraceFilterCriteria struct {
// Interpreted as an epoch (in hex) or one of "latest" for last mined block, "pending" for not yet committed messages.
// Optional, default: "latest".
// Note: "earliest" is not a permitted value.
FromBlock *string `json:"fromBlock,omitempty"`

// Interpreted as an epoch (in hex) or one of "latest" for last mined block, "pending" for not yet committed messages.
// Optional, default: "latest".
// Note: "earliest" is not a permitted value.
ToBlock *string `json:"toBlock,omitempty"`

// Actor address or a list of addresses from which transactions that generate traces should originate.
// Optional, default: nil.
// The JSON decoding must treat a string as equivalent to an array with one value, for example
// "0x8888f1f195afa192cfee86069858" must be decoded as [ "0x8888f1f195afa192cfee86069858" ]
FromAddress EthAddressList `json:"fromAddress,omitempty"`

// Actor address or a list of addresses to which transactions that generate traces are sent.
// Optional, default: nil.
// The JSON decoding must treat a string as equivalent to an array with one value, for example
// "0x8888f1f195afa192cfee86069858" must be decoded as [ "0x8888f1f195afa192cfee86069858" ]
ToAddress EthAddressList `json:"toAddress,omitempty"`

// After specifies the offset for pagination of trace results. The number of traces to skip before returning results.
// Optional, default: nil.
After *EthUint64 `json:"after,omitempty"`

// Limits the number of traces returned.
// Optional, default: all traces.
Count *EthUint64 `json:"count,omitempty"`
}
2 changes: 2 additions & 0 deletions venus-shared/api/chain/v1/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ type IETH interface {
EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*types.EthTraceReplayBlockTransaction, error) //perm:read
// Implmements OpenEthereum-compatible API method trace_transaction
EthTraceTransaction(ctx context.Context, txHash string) ([]*types.EthTraceTransaction, error) //perm:read
// Implements OpenEthereum-compatible API method trace_filter
EthTraceFilter(ctx context.Context, filter types.EthTraceFilterCriteria) ([]*types.EthTraceFilterResult, error) //perm:read
}

type IETHEvent interface {
Expand Down
45 changes: 45 additions & 0 deletions venus-shared/api/chain/v1/method.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ curl http://<ip>:<port>/rpc/v1 -X POST -H "Content-Type: application/json" -H "
* [EthSendRawTransaction](#ethsendrawtransaction)
* [EthSyncing](#ethsyncing)
* [EthTraceBlock](#ethtraceblock)
* [EthTraceFilter](#ethtracefilter)
* [EthTraceReplayBlockTransactions](#ethtracereplayblocktransactions)
* [EthTraceTransaction](#ethtracetransaction)
* [FilecoinAddressToEthAddress](#filecoinaddresstoethaddress)
Expand Down Expand Up @@ -3203,6 +3204,50 @@ Response:
]
```

### EthTraceFilter
Implements OpenEthereum-compatible API method trace_filter


Perms: read

Inputs:
```json
[
{
"fromBlock": "latest",
"toBlock": "latest",
"fromAddress": [
"0x5cbeecf99d3fdb3f25e309cc264f240bb0664031"
],
"toAddress": [
"0x5cbeecf99d3fdb3f25e309cc264f240bb0664031"
],
"after": "0x0",
"count": "0x64"
}
]
```

Response:
```json
[
{
"type": "string value",
"error": "string value",
"subtraces": 123,
"traceAddress": [
123
],
"action": {},
"result": {},
"blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707",
"blockNumber": 9,
"transactionHash": "0x0707070707070707070707070707070707070707070707070707070707070707",
"transactionPosition": 123
}
]
```

### EthTraceReplayBlockTransactions
Replays all transactions in a block returning the requested traces for each transaction

Expand Down
15 changes: 15 additions & 0 deletions venus-shared/api/chain/v1/mock/mock_fullnode.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 81176b4

Please sign in to comment.