From e6573c639d07e6f349ef731c6bdc064c702c0033 Mon Sep 17 00:00:00 2001 From: dwasse Date: Wed, 8 May 2024 11:52:07 -0500 Subject: [PATCH] RFQ: add decimals cache (#2502) * Feat: add decimalsCache, refactor decimal fetching * Cleanup: comments * Fix: build * Fix: use concurrent map --- services/rfq/relayer/service/chainindexer.go | 98 +++++++++----------- services/rfq/relayer/service/handlers.go | 8 +- services/rfq/relayer/service/relayer.go | 3 + 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/services/rfq/relayer/service/chainindexer.go b/services/rfq/relayer/service/chainindexer.go index f900964eee..3b1e7b7acb 100644 --- a/services/rfq/relayer/service/chainindexer.go +++ b/services/rfq/relayer/service/chainindexer.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" @@ -119,10 +120,10 @@ func (r *Relayer) runChainIndexer(ctx context.Context, chainID int) (err error) return nil } -const ethDecimals = 18 +var ethDecimals uint8 = 18 // getDecimals gets the decimals for the origin and dest tokens. -func (r *Relayer) getDecimals(parentCtx context.Context, bridgeTx fastbridge.IFastBridgeBridgeTransaction) (_ *decimalsRes, err error) { +func (r *Relayer) getDecimalsFromBridgeTx(parentCtx context.Context, bridgeTx fastbridge.IFastBridgeBridgeTransaction) (originDecimals *uint8, destDecimals *uint8, err error) { ctx, span := r.metrics.Tracer().Start(parentCtx, "getDecimals", trace.WithAttributes( attribute.String("sender", bridgeTx.OriginSender.String()), )) @@ -131,75 +132,66 @@ func (r *Relayer) getDecimals(parentCtx context.Context, bridgeTx fastbridge.IFa metrics.EndSpanWithErr(span, err) }() - // TODO: add a cache for decimals. - var originERC20, destERC20 *ierc20.IERC20 - res := decimalsRes{} - - if bridgeTx.OriginToken == chain.EthAddress { - res.originDecimals = ethDecimals - } else { - // TODO: cleanup duplication, but keep paralellism. - // this is a bit of a pain since it deals w/ 3 different fields, but shouldn't take too long - originClient, err := r.client.GetChainClient(ctx, int(bridgeTx.OriginChainId)) + // fetch the token decimals in parallel + g, ctx := errgroup.WithContext(ctx) + g.Go(func() error { + originDecimals, err = r.getDecimals(ctx, bridgeTx.OriginToken, bridgeTx.OriginChainId) if err != nil { - return nil, fmt.Errorf("could not get origin client: %w", err) + return fmt.Errorf("could not get origin decimals: %w", err) } - originERC20, err = ierc20.NewIERC20(bridgeTx.OriginToken, originClient) + return nil + }) + g.Go(func() error { + destDecimals, err = r.getDecimals(ctx, bridgeTx.DestToken, bridgeTx.DestChainId) if err != nil { - return nil, fmt.Errorf("could not get origin token") + return fmt.Errorf("could not get dest decimals: %w", err) } + return nil + }) + err = g.Wait() + if err != nil { + return nil, nil, fmt.Errorf("could not get decimals: %w", err) } - if bridgeTx.DestToken == chain.EthAddress { - res.destDecimals = ethDecimals - } else { - destClient, err := r.client.GetChainClient(ctx, int(bridgeTx.DestChainId)) - if err != nil { - return nil, fmt.Errorf("could not get dest client: %w", err) - } - destERC20, err = ierc20.NewIERC20(bridgeTx.DestToken, destClient) - if err != nil { - return nil, fmt.Errorf("could not get dest token") - } - } + return originDecimals, destDecimals, nil +} - // return early if both dest and origin are ETH. - if originERC20 == nil && destERC20 == nil { - return &res, nil +// getDecimals gets the decimals for a token on a chain. +// It will attempt to load a result from the cache first. +// The cache will be updated if the result is fetched from RPC. +func (r *Relayer) getDecimals(ctx context.Context, addr common.Address, chainID uint32) (decimals *uint8, err error) { + // attempt to load decimal from cache + key := getDecimalsKey(addr, chainID) + decimals, ok := r.decimalsCache.Load(key) + if ok { + return decimals, nil } - // do rpc calls to fetch the erc20 decimals. - g, ctx := errgroup.WithContext(ctx) - if originERC20 != nil { - g.Go(func() error { - res.originDecimals, err = originERC20.Decimals(&bind.CallOpts{Context: ctx}) - if err != nil { - return fmt.Errorf("could not get dest decimals: %w", err) - } - return nil - }) + if addr == chain.EthAddress { + return ðDecimals, nil } - if destERC20 != nil { - g.Go(func() error { - res.destDecimals, err = destERC20.Decimals(&bind.CallOpts{Context: ctx}) - if err != nil { - return fmt.Errorf("could not get origin decimals: %w", err) - } - return nil - }) + // fetch decimals from RPC + client, err := r.client.GetChainClient(ctx, int(chainID)) + if err != nil { + return nil, fmt.Errorf("could not get client for chain %d: %w", chainID, err) } - - err = g.Wait() + erc20, err := ierc20.NewIERC20(addr, client) + if err != nil { + return nil, fmt.Errorf("could not get token at %s: %w", addr.String(), err) + } + dec, err := erc20.Decimals(&bind.CallOpts{Context: ctx}) if err != nil { return nil, fmt.Errorf("could not get decimals: %w", err) } - return &res, nil + // update the cache + r.decimalsCache.Store(key, &dec) + return &dec, nil } -type decimalsRes struct { - originDecimals, destDecimals uint8 +func getDecimalsKey(addr common.Address, chainID uint32) string { + return fmt.Sprintf("%s-%d", addr.Hex(), chainID) } func (r *Relayer) handleDepositClaimed(ctx context.Context, event *fastbridge.FastBridgeBridgeDepositClaimed, chainID int) error { diff --git a/services/rfq/relayer/service/handlers.go b/services/rfq/relayer/service/handlers.go index cda68a0ae0..3b0ec386f4 100644 --- a/services/rfq/relayer/service/handlers.go +++ b/services/rfq/relayer/service/handlers.go @@ -59,22 +59,22 @@ func (r *Relayer) handleBridgeRequestedLog(parentCtx context.Context, req *fastb } // TODO: you can just pull these out of inventory. If they don't exist mark as invalid. - decimals, err := r.getDecimals(ctx, bridgeTx) + originDecimals, destDecimals, err := r.getDecimalsFromBridgeTx(ctx, bridgeTx) // can't use errors.is here if err != nil && strings.Contains(err.Error(), "no contract code at given address") { logger.Warnf("invalid token, skipping") return nil } - if err != nil { + if err != nil || originDecimals == nil || destDecimals == nil { return fmt.Errorf("could not get decimals: %w", err) } err = r.db.StoreQuoteRequest(ctx, reldb.QuoteRequest{ BlockNumber: req.Raw.BlockNumber, RawRequest: req.Request, - OriginTokenDecimals: decimals.originDecimals, - DestTokenDecimals: decimals.destDecimals, + OriginTokenDecimals: *originDecimals, + DestTokenDecimals: *destDecimals, TransactionID: req.TransactionId, Sender: req.Sender, Transaction: bridgeTx, diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go index fb6b25652c..340b0b15b0 100644 --- a/services/rfq/relayer/service/relayer.go +++ b/services/rfq/relayer/service/relayer.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ipfs/go-log" "github.com/jellydator/ttlcache/v3" + "github.com/puzpuzpuz/xsync/v2" "github.com/synapsecns/sanguine/core/dbcommon" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/ethergo/listener" @@ -45,6 +46,7 @@ type Relayer struct { submitter submitter.TransactionSubmitter signer signer.Signer claimCache *ttlcache.Cache[common.Hash, bool] + decimalsCache *xsync.MapOf[string, *uint8] } var logger = log.Logger("relayer") @@ -126,6 +128,7 @@ func NewRelayer(ctx context.Context, metricHandler metrics.Handler, cfg relconfi quoter: q, metrics: metricHandler, claimCache: cache, + decimalsCache: xsync.NewMapOf[*uint8](), cfg: cfg, inventory: im, submitter: sm,