2021-05-19 11:22:07 +00:00
|
|
|
package gateway
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2023-03-25 07:33:05 +00:00
|
|
|
blocks "github.com/ipfs/go-block-format"
|
2021-08-31 11:26:46 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
2022-04-21 02:48:41 +00:00
|
|
|
"go.opencensus.io/stats"
|
2022-04-20 01:35:19 +00:00
|
|
|
"golang.org/x/time/rate"
|
2021-08-31 11:26:46 +00:00
|
|
|
|
2021-05-19 11:22:07 +00:00
|
|
|
"github.com/filecoin-project/go-address"
|
|
|
|
"github.com/filecoin-project/go-bitfield"
|
2023-01-31 10:00:15 +00:00
|
|
|
"github.com/filecoin-project/go-jsonrpc"
|
2021-05-19 11:22:07 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
2023-08-18 07:28:01 +00:00
|
|
|
verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
|
2021-05-19 11:22:07 +00:00
|
|
|
"github.com/filecoin-project/go-state-types/dline"
|
|
|
|
"github.com/filecoin-project/go-state-types/network"
|
2022-06-14 15:00:51 +00:00
|
|
|
|
2021-05-19 11:22:07 +00:00
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
|
|
"github.com/filecoin-project/lotus/build"
|
|
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
2023-01-05 13:00:34 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
2021-05-19 11:22:07 +00:00
|
|
|
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
2022-09-29 20:46:59 +00:00
|
|
|
_ "github.com/filecoin-project/lotus/lib/sigs/delegated"
|
2021-05-19 11:22:07 +00:00
|
|
|
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
2022-04-21 02:48:41 +00:00
|
|
|
"github.com/filecoin-project/lotus/metrics"
|
2021-05-19 11:22:07 +00:00
|
|
|
"github.com/filecoin-project/lotus/node/impl/full"
|
2023-03-08 16:53:19 +00:00
|
|
|
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
2021-05-19 11:22:07 +00:00
|
|
|
)
|
|
|
|
|
2021-05-19 16:30:06 +00:00
|
|
|
const (
|
|
|
|
DefaultLookbackCap = time.Hour * 24
|
|
|
|
DefaultStateWaitLookbackLimit = abi.ChainEpoch(20)
|
2022-06-01 07:59:13 +00:00
|
|
|
DefaultRateLimitTimeout = time.Second * 5
|
2022-04-20 01:35:19 +00:00
|
|
|
basicRateLimitTokens = 1
|
|
|
|
walletRateLimitTokens = 1
|
|
|
|
chainRateLimitTokens = 2
|
|
|
|
stateRateLimitTokens = 3
|
2021-05-19 16:30:06 +00:00
|
|
|
)
|
|
|
|
|
2021-05-19 11:22:07 +00:00
|
|
|
// TargetAPI defines the API methods that the Node depends on
|
|
|
|
// (to make it easy to mock for tests)
|
|
|
|
type TargetAPI interface {
|
2023-03-24 18:39:15 +00:00
|
|
|
MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error)
|
|
|
|
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
|
|
|
|
MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*api.MiningBaseInfo, error)
|
2023-03-11 07:27:42 +00:00
|
|
|
GasEstimateGasPremium(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error)
|
|
|
|
StateReplay(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error)
|
|
|
|
StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
Version(context.Context) (api.APIVersion, error)
|
2021-11-18 21:55:33 +00:00
|
|
|
ChainGetParentMessages(context.Context, cid.Cid) ([]api.Message, error)
|
|
|
|
ChainGetParentReceipts(context.Context, cid.Cid) ([]*types.MessageReceipt, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error)
|
|
|
|
ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error)
|
|
|
|
ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error)
|
|
|
|
ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error)
|
|
|
|
ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
2021-08-05 17:53:12 +00:00
|
|
|
ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
ChainHasObj(context.Context, cid.Cid) (bool, error)
|
|
|
|
ChainHead(ctx context.Context) (*types.TipSet, error)
|
|
|
|
ChainNotify(context.Context) (<-chan []*api.HeadChange, error)
|
2021-08-27 19:25:43 +00:00
|
|
|
ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
ChainReadObj(context.Context, cid.Cid) ([]byte, error)
|
2022-06-07 03:14:16 +00:00
|
|
|
ChainPutObj(context.Context, blocks.Block) error
|
2021-11-18 21:55:33 +00:00
|
|
|
ChainGetGenesis(context.Context) (*types.TipSet, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error)
|
2023-03-08 16:53:19 +00:00
|
|
|
MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
MpoolPushUntrusted(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error)
|
|
|
|
MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error)
|
|
|
|
MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error)
|
2022-02-16 07:30:07 +00:00
|
|
|
MsigGetVestingSchedule(context.Context, address.Address, types.TipSetKey) (api.MsigVesting, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
MsigGetPending(ctx context.Context, addr address.Address, ts types.TipSetKey) ([]*api.MsigTransaction, error)
|
|
|
|
StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error)
|
2023-03-08 16:53:19 +00:00
|
|
|
StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error)
|
|
|
|
StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error)
|
|
|
|
StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error)
|
2023-08-18 07:28:01 +00:00
|
|
|
StateGetAllocationForPendingDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*verifregtypes.Allocation, error)
|
|
|
|
StateGetAllocation(ctx context.Context, clientAddr address.Address, allocationId verifregtypes.AllocationId, tsk types.TipSetKey) (*verifregtypes.Allocation, error)
|
|
|
|
StateGetAllocations(ctx context.Context, clientAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error)
|
|
|
|
StateGetClaim(ctx context.Context, providerAddr address.Address, claimId verifregtypes.ClaimId, tsk types.TipSetKey) (*verifregtypes.Claim, error)
|
|
|
|
StateGetClaims(ctx context.Context, providerAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error)
|
|
|
|
StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error)
|
|
|
|
StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error)
|
|
|
|
StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error)
|
2023-03-08 16:53:19 +00:00
|
|
|
StateNetworkName(context.Context) (dtypes.NetworkName, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error)
|
|
|
|
StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error)
|
|
|
|
StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error)
|
|
|
|
StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error)
|
|
|
|
StateMinerPower(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error)
|
|
|
|
StateMinerFaults(context.Context, address.Address, types.TipSetKey) (bitfield.BitField, error)
|
|
|
|
StateMinerRecoveries(context.Context, address.Address, types.TipSetKey) (bitfield.BitField, error)
|
2022-04-20 21:34:28 +00:00
|
|
|
StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
StateMinerDeadlines(context.Context, address.Address, types.TipSetKey) ([]api.Deadline, error)
|
|
|
|
StateMinerAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error)
|
|
|
|
StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*dline.Info, error)
|
|
|
|
StateCirculatingSupply(context.Context, types.TipSetKey) (abi.TokenAmount, error)
|
|
|
|
StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error)
|
|
|
|
StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
|
2023-03-14 17:35:26 +00:00
|
|
|
StateVerifierStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
StateVMCirculatingSupplyInternal(context.Context, types.TipSetKey) (api.CirculatingSupply, error)
|
2022-12-13 17:14:03 +00:00
|
|
|
WalletBalance(context.Context, address.Address) (types.BigInt, error)
|
|
|
|
|
2023-02-15 19:17:00 +00:00
|
|
|
EthAddressToFilecoinAddress(ctx context.Context, ethAddress ethtypes.EthAddress) (address.Address, error)
|
2023-02-24 19:19:52 +00:00
|
|
|
FilecoinAddressToEthAddress(ctx context.Context, filecoinAddress address.Address) (ethtypes.EthAddress, error)
|
2023-01-05 13:00:34 +00:00
|
|
|
EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error)
|
|
|
|
EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error)
|
|
|
|
EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error)
|
|
|
|
EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error)
|
|
|
|
EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error)
|
2023-03-14 04:59:16 +00:00
|
|
|
EthGetTransactionByHashLimited(ctx context.Context, txHash *ethtypes.EthHash, limit abi.ChainEpoch) (*ethtypes.EthTx, error)
|
2023-02-02 12:25:50 +00:00
|
|
|
EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error)
|
|
|
|
EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error)
|
2023-06-21 17:36:04 +00:00
|
|
|
EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthUint64, error)
|
2023-03-14 04:59:16 +00:00
|
|
|
EthGetTransactionReceiptLimited(ctx context.Context, txHash ethtypes.EthHash, limit abi.ChainEpoch) (*api.EthTxReceipt, error)
|
2023-01-05 13:00:34 +00:00
|
|
|
EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error)
|
|
|
|
EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error)
|
2023-06-21 17:36:04 +00:00
|
|
|
EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error)
|
|
|
|
EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error)
|
|
|
|
EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBigInt, error)
|
2023-01-05 13:00:34 +00:00
|
|
|
EthChainId(ctx context.Context) (ethtypes.EthUint64, error)
|
2023-04-21 12:02:40 +00:00
|
|
|
EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error)
|
2022-12-13 17:14:03 +00:00
|
|
|
NetVersion(ctx context.Context) (string, error)
|
|
|
|
NetListening(ctx context.Context) (bool, error)
|
2023-01-05 13:00:34 +00:00
|
|
|
EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error)
|
|
|
|
EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error)
|
2023-02-10 18:33:59 +00:00
|
|
|
EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error)
|
2023-01-05 13:00:34 +00:00
|
|
|
EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error)
|
|
|
|
EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error)
|
2023-06-21 17:36:04 +00:00
|
|
|
EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam ethtypes.EthBlockNumberOrHash) (ethtypes.EthBytes, error)
|
2023-01-05 13:00:34 +00:00
|
|
|
EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error)
|
|
|
|
EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error)
|
|
|
|
EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error)
|
|
|
|
EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error)
|
|
|
|
EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error)
|
|
|
|
EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error)
|
|
|
|
EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error)
|
|
|
|
EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error)
|
2023-01-31 10:00:15 +00:00
|
|
|
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
|
2023-01-05 13:00:34 +00:00
|
|
|
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
|
2023-02-02 12:25:50 +00:00
|
|
|
Web3ClientVersion(ctx context.Context) (string, error)
|
2021-05-19 11:22:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ TargetAPI = *new(api.FullNode) // gateway depends on latest
|
|
|
|
|
|
|
|
type Node struct {
|
|
|
|
target TargetAPI
|
2023-01-16 14:28:55 +00:00
|
|
|
subHnd *EthSubHandler
|
2021-05-19 11:22:07 +00:00
|
|
|
lookbackCap time.Duration
|
|
|
|
stateWaitLookbackLimit abi.ChainEpoch
|
2022-04-20 01:35:19 +00:00
|
|
|
rateLimiter *rate.Limiter
|
|
|
|
rateLimitTimeout time.Duration
|
2021-05-19 11:22:07 +00:00
|
|
|
errLookback error
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ api.Gateway = (*Node)(nil)
|
|
|
|
_ full.ChainModuleAPI = (*Node)(nil)
|
|
|
|
_ full.GasModuleAPI = (*Node)(nil)
|
|
|
|
_ full.MpoolModuleAPI = (*Node)(nil)
|
|
|
|
_ full.StateModuleAPI = (*Node)(nil)
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewNode creates a new gateway node.
|
2023-01-16 14:28:55 +00:00
|
|
|
func NewNode(api TargetAPI, sHnd *EthSubHandler, lookbackCap time.Duration, stateWaitLookbackLimit abi.ChainEpoch, rateLimit int64, rateLimitTimeout time.Duration) *Node {
|
2022-04-20 01:35:19 +00:00
|
|
|
var limit rate.Limit
|
|
|
|
if rateLimit == 0 {
|
|
|
|
limit = rate.Inf
|
|
|
|
} else {
|
|
|
|
limit = rate.Every(time.Second / time.Duration(rateLimit))
|
|
|
|
}
|
2021-05-19 11:22:07 +00:00
|
|
|
return &Node{
|
|
|
|
target: api,
|
2023-01-16 14:28:55 +00:00
|
|
|
subHnd: sHnd,
|
2021-05-19 11:22:07 +00:00
|
|
|
lookbackCap: lookbackCap,
|
|
|
|
stateWaitLookbackLimit: stateWaitLookbackLimit,
|
2022-04-20 01:35:19 +00:00
|
|
|
rateLimiter: rate.NewLimiter(limit, stateRateLimitTokens),
|
|
|
|
rateLimitTimeout: rateLimitTimeout,
|
2021-05-19 11:22:07 +00:00
|
|
|
errLookback: fmt.Errorf("lookbacks of more than %s are disallowed", lookbackCap),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gw *Node) checkTipsetKey(ctx context.Context, tsk types.TipSetKey) error {
|
|
|
|
if tsk.IsEmpty() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ts, err := gw.target.ChainGetTipSet(ctx, tsk)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return gw.checkTipset(ts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gw *Node) checkTipset(ts *types.TipSet) error {
|
|
|
|
at := time.Unix(int64(ts.Blocks()[0].Timestamp), 0)
|
|
|
|
if err := gw.checkTimestamp(at); err != nil {
|
|
|
|
return fmt.Errorf("bad tipset: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gw *Node) checkTipsetHeight(ts *types.TipSet, h abi.ChainEpoch) error {
|
2023-03-13 23:36:48 +00:00
|
|
|
if h > ts.Height() {
|
|
|
|
return fmt.Errorf("tipset height in future")
|
|
|
|
}
|
2021-05-19 11:22:07 +00:00
|
|
|
tsBlock := ts.Blocks()[0]
|
|
|
|
heightDelta := time.Duration(uint64(tsBlock.Height-h)*build.BlockDelaySecs) * time.Second
|
|
|
|
timeAtHeight := time.Unix(int64(tsBlock.Timestamp), 0).Add(-heightDelta)
|
|
|
|
|
|
|
|
if err := gw.checkTimestamp(timeAtHeight); err != nil {
|
|
|
|
return fmt.Errorf("bad tipset height: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gw *Node) checkTimestamp(at time.Time) error {
|
|
|
|
if time.Since(at) > gw.lookbackCap {
|
|
|
|
return gw.errLookback
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-20 01:35:19 +00:00
|
|
|
func (gw *Node) limit(ctx context.Context, tokens int) error {
|
|
|
|
ctx2, cancel := context.WithTimeout(ctx, gw.rateLimitTimeout)
|
|
|
|
defer cancel()
|
2022-06-06 23:37:11 +00:00
|
|
|
if perConnLimiter, ok := ctx2.Value(perConnLimiterKey).(*rate.Limiter); ok {
|
2022-05-20 10:40:52 +00:00
|
|
|
err := perConnLimiter.WaitN(ctx2, tokens)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("connection limited. %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-20 01:35:19 +00:00
|
|
|
err := gw.rateLimiter.WaitN(ctx2, tokens)
|
|
|
|
if err != nil {
|
2022-04-21 02:48:41 +00:00
|
|
|
stats.Record(ctx, metrics.RateLimitCount.M(1))
|
|
|
|
return fmt.Errorf("server busy. %w", err)
|
2022-04-20 01:35:19 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|