Files
lotus/gateway/proxy_eth.go
Rod Vagg 8feaa02de0 Revert "feat: fix GetBlock for null rounds by returning nil (#12529)"
This reverts commit c6745545041939b97962e376ee2593609bb3d5b5.
2024-10-28 15:17:44 +11:00

809 lines
23 KiB
Go

package gateway
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/policy"
"github.com/filecoin-project/lotus/chain/events/filter"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
)
var ErrTooManyFilters = errors.New("too many subscriptions and filters per connection")
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
}
func (gw *Node) EthAddressToFilecoinAddress(ctx context.Context, ethAddress ethtypes.EthAddress) (address.Address, error) {
return gw.target.EthAddressToFilecoinAddress(ctx, ethAddress)
}
func (gw *Node) FilecoinAddressToEthAddress(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthAddress, error) {
// validate params
_, err := jsonrpc.DecodeParams[ethtypes.FilecoinAddressToEthAddressParams](params)
if err != nil {
return ethtypes.EthAddress{}, xerrors.Errorf("decoding params: %w", err)
}
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthAddress{}, err
}
return gw.target.FilecoinAddressToEthAddress(ctx, params)
}
func (gw *Node) EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) {
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
return 0, err
}
return gw.target.EthBlockNumber(ctx)
}
func (gw *Node) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return 0, err
}
head, err := gw.target.ChainHead(ctx)
if err != nil {
return 0, err
}
if err := gw.checkTipsetHeight(head, abi.ChainEpoch(blkNum)); err != nil {
return 0, err
}
return gw.target.EthGetBlockTransactionCountByNumber(ctx, blkNum)
}
func (gw *Node) tskByEthHash(ctx context.Context, blkHash ethtypes.EthHash) (types.TipSetKey, error) {
tskCid := blkHash.ToCid()
tskBlk, err := gw.target.ChainReadObj(ctx, tskCid)
if err != nil {
return types.EmptyTSK, err
}
tsk := new(types.TipSetKey)
if err := tsk.UnmarshalCBOR(bytes.NewReader(tskBlk)); err != nil {
return types.EmptyTSK, xerrors.Errorf("cannot unmarshal block into tipset key: %w", err)
}
return *tsk, nil
}
func (gw *Node) checkBlkHash(ctx context.Context, blkHash ethtypes.EthHash) error {
tsk, err := gw.tskByEthHash(ctx, blkHash)
if err != nil {
return err
}
return gw.checkTipsetKey(ctx, tsk)
}
func (gw *Node) checkEthBlockParam(ctx context.Context, blkParam ethtypes.EthBlockNumberOrHash, lookback ethtypes.EthUint64) error {
// first check if its a predefined block or a block number
if blkParam.PredefinedBlock != nil || blkParam.BlockNumber != nil {
head, err := gw.target.ChainHead(ctx)
if err != nil {
return err
}
var num ethtypes.EthUint64
if blkParam.PredefinedBlock != nil {
if *blkParam.PredefinedBlock == "earliest" {
return xerrors.New("block param \"earliest\" is not supported")
} else if *blkParam.PredefinedBlock == "pending" || *blkParam.PredefinedBlock == "latest" {
// Head is always ok.
if lookback == 0 {
return nil
}
if lookback <= ethtypes.EthUint64(head.Height()) {
num = ethtypes.EthUint64(head.Height()) - lookback
}
}
} else {
num = *blkParam.BlockNumber
}
return gw.checkTipsetHeight(head, abi.ChainEpoch(num))
}
// otherwise its a block hash
if blkParam.BlockHash != nil {
return gw.checkBlkHash(ctx, *blkParam.BlockHash)
}
return xerrors.New("invalid block param")
}
func (gw *Node) checkBlkParam(ctx context.Context, blkParam string, lookback ethtypes.EthUint64) error {
if blkParam == "earliest" {
// also not supported in node impl
return xerrors.New("block param \"earliest\" is not supported")
}
head, err := gw.target.ChainHead(ctx)
if err != nil {
return err
}
var num ethtypes.EthUint64
switch blkParam {
case "pending", "latest":
// Head is always ok.
if lookback == 0 {
return nil
}
// Can't look beyond 0 anyways.
if lookback > ethtypes.EthUint64(head.Height()) {
break
}
num = ethtypes.EthUint64(head.Height()) - lookback
case "safe":
num = ethtypes.EthUint64(head.Height()) - lookback - ethtypes.EthUint64(ethtypes.SafeEpochDelay)
case "finalized":
num = ethtypes.EthUint64(head.Height()) - lookback - ethtypes.EthUint64(policy.ChainFinality)
default:
if err := num.UnmarshalJSON([]byte(`"` + blkParam + `"`)); err != nil {
return fmt.Errorf("cannot parse block number: %v", err)
}
}
return gw.checkTipsetHeight(head, abi.ChainEpoch(num))
}
func (gw *Node) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) {
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
return 0, err
}
return gw.target.EthGetBlockTransactionCountByHash(ctx, blkHash)
}
func (gw *Node) EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthBlock{}, err
}
if err := gw.checkBlkHash(ctx, blkHash); err != nil {
return ethtypes.EthBlock{}, err
}
return gw.target.EthGetBlockByHash(ctx, blkHash, fullTxInfo)
}
func (gw *Node) EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthBlock{}, err
}
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
return ethtypes.EthBlock{}, err
}
return gw.target.EthGetBlockByNumber(ctx, blkNum, fullTxInfo)
}
func (gw *Node) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (*ethtypes.EthTx, error) {
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
return nil, err
}
if err := gw.checkBlkHash(ctx, blkHash); err != nil {
return nil, err
}
return gw.target.EthGetTransactionByBlockHashAndIndex(ctx, blkHash, txIndex)
}
func (gw *Node) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum string, txIndex ethtypes.EthUint64) (*ethtypes.EthTx, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
return nil, err
}
return gw.target.EthGetTransactionByBlockNumberAndIndex(ctx, blkNum, txIndex)
}
func (gw *Node) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) {
return gw.target.EthGetTransactionByHashLimited(ctx, txHash, api.LookbackNoLimit)
}
func (gw *Node) EthGetTransactionByHashLimited(ctx context.Context, txHash *ethtypes.EthHash, limit abi.ChainEpoch) (*ethtypes.EthTx, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if limit == api.LookbackNoLimit {
limit = gw.maxMessageLookbackEpochs
}
if gw.maxMessageLookbackEpochs != api.LookbackNoLimit && limit > gw.maxMessageLookbackEpochs {
limit = gw.maxMessageLookbackEpochs
}
return gw.target.EthGetTransactionByHashLimited(ctx, txHash, limit)
}
func (gw *Node) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
return gw.target.EthGetTransactionHashByCid(ctx, cid)
}
func (gw *Node) EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) {
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
return nil, err
}
return gw.target.EthGetMessageCidByTransactionHash(ctx, txHash)
}
func (gw *Node) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthUint64, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return 0, err
}
if err := gw.checkEthBlockParam(ctx, blkParam, 0); err != nil {
return 0, err
}
return gw.target.EthGetTransactionCount(ctx, sender, blkParam)
}
func (gw *Node) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) {
return gw.EthGetTransactionReceiptLimited(ctx, txHash, api.LookbackNoLimit)
}
func (gw *Node) EthGetTransactionReceiptLimited(ctx context.Context, txHash ethtypes.EthHash, limit abi.ChainEpoch) (*api.EthTxReceipt, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if limit == api.LookbackNoLimit {
limit = gw.maxMessageLookbackEpochs
}
if gw.maxMessageLookbackEpochs != api.LookbackNoLimit && limit > gw.maxMessageLookbackEpochs {
limit = gw.maxMessageLookbackEpochs
}
return gw.target.EthGetTransactionReceiptLimited(ctx, txHash, limit)
}
func (gw *Node) EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if err := gw.checkEthBlockParam(ctx, blkParam, 0); err != nil {
return nil, err
}
return gw.target.EthGetCode(ctx, address, blkParam)
}
func (gw *Node) EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if err := gw.checkEthBlockParam(ctx, blkParam, 0); err != nil {
return nil, err
}
return gw.target.EthGetStorageAt(ctx, address, position, blkParam)
}
func (gw *Node) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBigInt, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthBigInt(big.Zero()), err
}
if err := gw.checkEthBlockParam(ctx, blkParam, 0); err != nil {
return ethtypes.EthBigInt(big.Zero()), err
}
return gw.target.EthGetBalance(ctx, address, blkParam)
}
func (gw *Node) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) {
if err := gw.limit(ctx, basicRateLimitTokens); err != nil {
return 0, err
}
return gw.target.EthChainId(ctx)
}
func (gw *Node) EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) {
if err := gw.limit(ctx, basicRateLimitTokens); err != nil {
return ethtypes.EthSyncingResult{}, err
}
return gw.target.EthSyncing(ctx)
}
func (gw *Node) NetVersion(ctx context.Context) (string, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return "", err
}
return gw.target.NetVersion(ctx)
}
func (gw *Node) NetListening(ctx context.Context) (bool, error) {
if err := gw.limit(ctx, basicRateLimitTokens); err != nil {
return false, err
}
return gw.target.NetListening(ctx)
}
func (gw *Node) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return 0, err
}
return gw.target.EthProtocolVersion(ctx)
}
func (gw *Node) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) {
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
return ethtypes.EthBigInt(big.Zero()), err
}
return gw.target.EthGasPrice(ctx)
}
var EthFeeHistoryMaxBlockCount = 128 // this seems to be expensive; todo: figure out what is a good number that works with everything
func (gw *Node) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) {
params, err := jsonrpc.DecodeParams[ethtypes.EthFeeHistoryParams](p)
if err != nil {
return ethtypes.EthFeeHistory{}, xerrors.Errorf("decoding params: %w", err)
}
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthFeeHistory{}, err
}
if err := gw.checkBlkParam(ctx, params.NewestBlkNum, params.BlkCount); err != nil {
return ethtypes.EthFeeHistory{}, err
}
if params.BlkCount > ethtypes.EthUint64(EthFeeHistoryMaxBlockCount) {
return ethtypes.EthFeeHistory{}, xerrors.New("block count too high")
}
return gw.target.EthFeeHistory(ctx, p)
}
func (gw *Node) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) {
if err := gw.limit(ctx, chainRateLimitTokens); err != nil {
return ethtypes.EthBigInt(big.Zero()), err
}
return gw.target.EthMaxPriorityFeePerGas(ctx)
}
func (gw *Node) EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthUint64, error) {
// validate params
_, err := jsonrpc.DecodeParams[ethtypes.EthEstimateGasParams](p)
if err != nil {
return ethtypes.EthUint64(0), xerrors.Errorf("decoding params: %w", err)
}
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return 0, err
}
// todo limit gas? to what?
return gw.target.EthEstimateGas(ctx, p)
}
func (gw *Node) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if err := gw.checkEthBlockParam(ctx, blkParam, 0); err != nil {
return nil, err
}
// todo limit gas? to what?
return gw.target.EthCall(ctx, tx, blkParam)
}
func (gw *Node) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthHash{}, err
}
// push the message via the untrusted variant which uses MpoolPushUntrusted
return gw.target.EthSendRawTransactionUntrusted(ctx, rawTx)
}
func (gw *Node) EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if filter.FromBlock != nil {
if err := gw.checkBlkParam(ctx, *filter.FromBlock, 0); err != nil {
return nil, err
}
}
if filter.ToBlock != nil {
if err := gw.checkBlkParam(ctx, *filter.ToBlock, 0); err != nil {
return nil, err
}
}
if filter.BlockHash != nil {
if err := gw.checkBlkHash(ctx, *filter.BlockHash); err != nil {
return nil, err
}
}
return gw.target.EthGetLogs(ctx, filter)
}
func (gw *Node) EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
ft, err := getStatefulTracker(ctx)
if err != nil {
return nil, xerrors.Errorf("EthGetFilterChanges not supported: %w", err)
}
if !ft.hasFilter(id) {
return nil, filter.ErrFilterNotFound
}
return gw.target.EthGetFilterChanges(ctx, id)
}
func (gw *Node) EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
ft, err := getStatefulTracker(ctx)
if err != nil {
return nil, xerrors.Errorf("EthGetFilterLogs not supported: %w", err)
}
if !ft.hasFilter(id) {
return nil, nil
}
return gw.target.EthGetFilterLogs(ctx, id)
}
func (gw *Node) EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthFilterID{}, err
}
return gw.addUserFilterLimited(ctx, "EthNewFilter", func() (ethtypes.EthFilterID, error) {
return gw.target.EthNewFilter(ctx, filter)
})
}
func (gw *Node) EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthFilterID{}, err
}
return gw.addUserFilterLimited(ctx, "EthNewBlockFilter", func() (ethtypes.EthFilterID, error) {
return gw.target.EthNewBlockFilter(ctx)
})
}
func (gw *Node) EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthFilterID{}, err
}
return gw.addUserFilterLimited(ctx, "EthNewPendingTransactionFilter", func() (ethtypes.EthFilterID, error) {
return gw.target.EthNewPendingTransactionFilter(ctx)
})
}
func (gw *Node) EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return false, err
}
// check if the filter belongs to this connection
ft, err := getStatefulTracker(ctx)
if err != nil {
return false, xerrors.Errorf("EthUninstallFilter not supported: %w", err)
}
ft.lk.Lock()
defer ft.lk.Unlock()
if _, ok := ft.userFilters[id]; !ok {
return false, nil
}
ok, err := gw.target.EthUninstallFilter(ctx, id)
if err != nil {
// don't delete the filter, it's "stuck" so should still count towards the limit
log.Warnf("error uninstalling filter: %v", err)
return false, err
}
delete(ft.userFilters, id)
return ok, nil
}
func (gw *Node) EthSubscribe(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
// validate params
_, err := jsonrpc.DecodeParams[ethtypes.EthSubscribeParams](p)
if err != nil {
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("decoding params: %w", err)
}
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return ethtypes.EthSubscriptionID{}, err
}
if gw.subHnd == nil {
return ethtypes.EthSubscriptionID{}, xerrors.New("EthSubscribe not supported: subscription support not enabled")
}
ethCb, ok := jsonrpc.ExtractReverseClient[api.EthSubscriberMethods](ctx)
if !ok {
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("EthSubscribe not supported: connection doesn't support callbacks")
}
ft, err := getStatefulTracker(ctx)
if err != nil {
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("EthSubscribe not supported: %w", err)
}
ft.lk.Lock()
defer ft.lk.Unlock()
if len(ft.userSubscriptions)+len(ft.userFilters) >= gw.ethMaxFiltersPerConn {
return ethtypes.EthSubscriptionID{}, ErrTooManyFilters
}
sub, err := gw.target.EthSubscribe(ctx, p)
if err != nil {
return ethtypes.EthSubscriptionID{}, err
}
err = gw.subHnd.AddSub(ctx, sub, func(ctx context.Context, response *ethtypes.EthSubscriptionResponse) error {
outParam, err := json.Marshal(response)
if err != nil {
return err
}
return ethCb.EthSubscription(ctx, outParam)
})
if err != nil {
return ethtypes.EthSubscriptionID{}, err
}
ft.userSubscriptions[sub] = func() {
if _, err := gw.target.EthUnsubscribe(ctx, sub); err != nil {
log.Warnf("error unsubscribing after connection end: %v", err)
}
}
return sub, err
}
func (gw *Node) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return false, err
}
// check if the filter belongs to this connection
ft, err := getStatefulTracker(ctx)
if err != nil {
return false, xerrors.Errorf("EthUnsubscribe not supported: %w", err)
}
ft.lk.Lock()
defer ft.lk.Unlock()
if _, ok := ft.userSubscriptions[id]; !ok {
return false, nil
}
ok, err := gw.target.EthUnsubscribe(ctx, id)
if err != nil {
// don't delete the subscription, it's "stuck" so should still count towards the limit
log.Warnf("error unsubscribing: %v", err)
return false, err
}
delete(ft.userSubscriptions, id)
if gw.subHnd != nil {
gw.subHnd.RemoveSub(id)
}
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) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
return nil, err
}
return gw.target.EthTraceBlock(ctx, blkNum)
}
func (gw *Node) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
return nil, err
}
return gw.target.EthTraceReplayBlockTransactions(ctx, blkNum, traceTypes)
}
func (gw *Node) EthTraceTransaction(ctx context.Context, txHash string) ([]*ethtypes.EthTraceTransaction, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
return gw.target.EthTraceTransaction(ctx, txHash)
}
func (gw *Node) EthTraceFilter(ctx context.Context, filter ethtypes.EthTraceFilterCriteria) ([]*ethtypes.EthTraceFilterResult, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if filter.ToBlock != nil {
if err := gw.checkBlkParam(ctx, *filter.ToBlock, 0); err != nil {
return nil, err
}
}
if filter.FromBlock != nil {
if err := gw.checkBlkParam(ctx, *filter.FromBlock, 0); err != nil {
return nil, err
}
}
return gw.target.EthTraceFilter(ctx, filter)
}
func (gw *Node) EthGetBlockReceipts(ctx context.Context, blkParam ethtypes.EthBlockNumberOrHash) ([]*api.EthTxReceipt, error) {
return gw.EthGetBlockReceiptsLimited(ctx, blkParam, api.LookbackNoLimit)
}
func (gw *Node) EthGetBlockReceiptsLimited(ctx context.Context, blkParam ethtypes.EthBlockNumberOrHash, limit abi.ChainEpoch) ([]*api.EthTxReceipt, error) {
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
return nil, err
}
if limit == api.LookbackNoLimit {
limit = gw.maxMessageLookbackEpochs
}
if gw.maxMessageLookbackEpochs != api.LookbackNoLimit && limit > gw.maxMessageLookbackEpochs {
limit = gw.maxMessageLookbackEpochs
}
return gw.target.EthGetBlockReceiptsLimited(ctx, blkParam, limit)
}
func (gw *Node) addUserFilterLimited(
ctx context.Context,
callName string,
install func() (ethtypes.EthFilterID, error),
) (ethtypes.EthFilterID, error) {
ft, err := getStatefulTracker(ctx)
if err != nil {
return ethtypes.EthFilterID{}, xerrors.Errorf("%s not supported: %w", callName, err)
}
ft.lk.Lock()
defer ft.lk.Unlock()
if len(ft.userSubscriptions)+len(ft.userFilters) >= gw.ethMaxFiltersPerConn {
return ethtypes.EthFilterID{}, ErrTooManyFilters
}
id, err := install()
if err != nil {
return id, err
}
ft.userFilters[id] = func() {
if _, err := gw.target.EthUninstallFilter(ctx, id); err != nil {
log.Warnf("error uninstalling filter after connection end: %v", err)
}
}
return id, nil
}
func getStatefulTracker(ctx context.Context) (*statefulCallTracker, error) {
if jsonrpc.GetConnectionType(ctx) != jsonrpc.ConnectionTypeWS {
return nil, xerrors.New("stateful methods are only available on websocket connections")
}
if ct, ok := ctx.Value(statefulCallTrackerKey).(*statefulCallTracker); !ok {
return nil, xerrors.New("stateful tracking is not available for this call")
} else {
return ct, nil
}
}
type cleanup func()
type statefulCallTracker struct {
lk sync.Mutex
userFilters map[ethtypes.EthFilterID]cleanup
userSubscriptions map[ethtypes.EthSubscriptionID]cleanup
}
func (ft *statefulCallTracker) cleanup() {
ft.lk.Lock()
defer ft.lk.Unlock()
for _, cleanup := range ft.userFilters {
cleanup()
}
for _, cleanup := range ft.userSubscriptions {
cleanup()
}
}
func (ft *statefulCallTracker) hasFilter(id ethtypes.EthFilterID) bool {
ft.lk.Lock()
defer ft.lk.Unlock()
_, ok := ft.userFilters[id]
return ok
}
// called per request (ws connection)
func newStatefulCallTracker() *statefulCallTracker {
return &statefulCallTracker{
userFilters: make(map[ethtypes.EthFilterID]cleanup),
userSubscriptions: make(map[ethtypes.EthSubscriptionID]cleanup),
}
}