feat_: recalculate route fees with every new block

This commit is contained in:
Sale Djenic 2024-08-29 15:40:30 +02:00 committed by saledjenic
parent 1bb9cbc573
commit 506a76bf9f
4 changed files with 381 additions and 116 deletions

View File

@ -507,6 +507,12 @@ func (api *API) StopSuggestedRoutesAsyncCalculation(ctx context.Context) {
api.router.StopSuggestedRoutesAsyncCalculation() api.router.StopSuggestedRoutesAsyncCalculation()
} }
func (api *API) StopSuggestedRoutesCalculation(ctx context.Context) {
log.Debug("call to StopSuggestedRoutesCalculation")
api.router.StopSuggestedRoutesCalculation()
}
// Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function) // Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function)
func (api *API) GetDerivedAddresses(ctx context.Context, password string, derivedFrom string, paths []string) ([]*DerivedAddress, error) { func (api *API) GetDerivedAddresses(ctx context.Context, password string, derivedFrom string, paths []string) ([]*DerivedAddress, error) {
info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password) info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password)

View File

@ -73,6 +73,14 @@ type Router struct {
feesManager *fees.FeeManager feesManager *fees.FeeManager
pathProcessors map[string]pathprocessor.PathProcessor pathProcessors map[string]pathprocessor.PathProcessor
scheduler *async.Scheduler scheduler *async.Scheduler
activeRoutesMutex sync.Mutex
activeRoutes *SuggestedRoutes
lastInputParamsMutex sync.Mutex
lastInputParams *requests.RouteInputParams
clientsForUpdatesPerChains sync.Map
} }
func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *token.Manager, marketManager *market.Manager, func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *token.Manager, marketManager *market.Manager,
@ -147,12 +155,9 @@ func newSuggestedRoutes(
return suggestedRoutes, allRoutes return suggestedRoutes, allRoutes
} }
func (r *Router) SuggestedRoutesAsync(input *requests.RouteInputParams) { func sendRouterResult(uuid string, result interface{}, err error) {
r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) {
return r.SuggestedRoutes(ctx, input)
}, func(result interface{}, taskType async.TaskType, err error) {
routesResponse := responses.RouterSuggestedRoutes{ routesResponse := responses.RouterSuggestedRoutes{
Uuid: input.Uuid, Uuid: uuid,
} }
if err != nil { if err != nil {
@ -168,20 +173,28 @@ func (r *Router) SuggestedRoutesAsync(input *requests.RouteInputParams) {
} }
signal.SendWalletEvent(signal.SuggestedRoutes, routesResponse) signal.SendWalletEvent(signal.SuggestedRoutes, routesResponse)
}
func (r *Router) SuggestedRoutesAsync(input *requests.RouteInputParams) {
r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) {
return r.SuggestedRoutes(ctx, input)
}, func(result interface{}, taskType async.TaskType, err error) {
sendRouterResult(input.Uuid, result, err)
}) })
} }
func (r *Router) StopSuggestedRoutesAsyncCalculation() { func (r *Router) StopSuggestedRoutesAsyncCalculation() {
r.unsubscribeFeesUpdateAccrossAllChains()
r.scheduler.Stop() r.scheduler.Stop()
} }
func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInputParams) (*SuggestedRoutes, error) { func (r *Router) StopSuggestedRoutesCalculation() {
testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled() r.unsubscribeFeesUpdateAccrossAllChains()
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
} }
input.TestnetMode = testnetMode func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInputParams) (suggestedRoutes *SuggestedRoutes, err error) {
// unsubscribe from updates
r.unsubscribeFeesUpdateAccrossAllChains()
// clear all processors // clear all processors
for _, processor := range r.pathProcessors { for _, processor := range r.pathProcessors {
@ -190,6 +203,29 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInput
} }
} }
r.lastInputParamsMutex.Lock()
r.lastInputParams = input
r.lastInputParamsMutex.Unlock()
defer func() {
r.activeRoutesMutex.Lock()
r.activeRoutes = suggestedRoutes
r.activeRoutesMutex.Unlock()
if suggestedRoutes != nil && err == nil {
// subscribe for updates
for _, path := range suggestedRoutes.Best {
err = r.subscribeForUdates(path.FromChain.ChainID)
}
}
}()
testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled()
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
input.TestnetMode = testnetMode
err = input.Validate() err = input.Validate()
if err != nil { if err != nil {
return nil, errors.CreateErrorResponseFromError(err) return nil, errors.CreateErrorResponseFromError(err)
@ -224,7 +260,7 @@ func (r *Router) SuggestedRoutes(ctx context.Context, input *requests.RouteInput
return nil, errors.CreateErrorResponseFromError(err) return nil, errors.CreateErrorResponseFromError(err)
} }
suggestedRoutes, err := r.resolveRoutes(ctx, input, candidates, balanceMap) suggestedRoutes, err = r.resolveRoutes(ctx, input, candidates, balanceMap)
if err == nil && (suggestedRoutes == nil || len(suggestedRoutes.Best) == 0) { if err == nil && (suggestedRoutes == nil || len(suggestedRoutes.Best) == 0) {
// No best route found, but no error given. // No best route found, but no error given.
@ -657,22 +693,24 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err)
continue continue
} }
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams) approvalRequired, approvalAmountRequired, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams)
if err != nil { if err != nil {
appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err)
continue continue
} }
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees var approvalGasLimit uint64
var l1FeeWei uint64 = 0 if approvalRequired {
// if input.SendType.needL1Fee() { if processorInputParams.TestsMode {
// txInputData, err := pProcessor.PackTxInputData(processorInputParams) approvalGasLimit = processorInputParams.TestApprovalGasEstimation
// if err != nil { } else {
// continue approvalGasLimit, err = r.estimateGasForApproval(processorInputParams, &approvalContractAddress)
// } if err != nil {
appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err)
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData) continue
// } }
}
}
amountOut, err := pProcessor.CalculateAmountOut(processorInputParams) amountOut, err := pProcessor.CalculateAmountOut(processorInputParams)
if err != nil { if err != nil {
@ -687,44 +725,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
estimatedTime += 1 estimatedTime += 1
} }
// calculate ETH fees path := &routes.Path{
ethTotalFees := big.NewInt(0)
txFeeInWei := new(big.Int).Mul(maxFeesPerGas, big.NewInt(int64(gasLimit)))
ethTotalFees.Add(ethTotalFees, txFeeInWei)
txL1FeeInWei := big.NewInt(0)
if l1FeeWei > 0 {
txL1FeeInWei = big.NewInt(int64(l1FeeWei))
ethTotalFees.Add(ethTotalFees, txL1FeeInWei)
}
approvalFeeInWei := big.NewInt(0)
approvalL1FeeInWei := big.NewInt(0)
if approvalRequired {
approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(approvalGasLimit)))
ethTotalFees.Add(ethTotalFees, approvalFeeInWei)
if l1ApprovalFee > 0 {
approvalL1FeeInWei = big.NewInt(int64(l1ApprovalFee))
ethTotalFees.Add(ethTotalFees, approvalL1FeeInWei)
}
}
// calculate required balances (bonder and token fees are already included in the amountIn by Hop bridge (once we include Celar we need to check how they handle the fees))
requiredNativeBalance := big.NewInt(0)
requiredTokenBalance := big.NewInt(0)
if token.IsNative() {
requiredNativeBalance.Add(requiredNativeBalance, amountOption.amount)
if !amountOption.subtractFees {
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
}
} else {
requiredTokenBalance.Add(requiredTokenBalance, amountOption.amount)
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
}
appendPathFn(&routes.Path{
ProcessorName: pProcessor.Name(), ProcessorName: pProcessor.Name(),
FromChain: network, FromChain: network,
ToChain: dest, ToChain: dest,
@ -734,36 +735,28 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp
AmountInLocked: amountOption.locked, AmountInLocked: amountOption.locked,
AmountOut: (*hexutil.Big)(amountOut), AmountOut: (*hexutil.Big)(amountOut),
SuggestedLevelsForMaxFeesPerGas: fetchedFees.MaxFeesLevels, // set params that we don't want to be recalculated with every new block creation
MaxFeesPerGas: (*hexutil.Big)(maxFeesPerGas),
TxBaseFee: (*hexutil.Big)(fetchedFees.BaseFee),
TxPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas),
TxGasAmount: gasLimit, TxGasAmount: gasLimit,
TxBonderFees: (*hexutil.Big)(bonderFees), TxBonderFees: (*hexutil.Big)(bonderFees),
TxTokenFees: (*hexutil.Big)(tokenFees), TxTokenFees: (*hexutil.Big)(tokenFees),
TxFee: (*hexutil.Big)(txFeeInWei),
TxL1Fee: (*hexutil.Big)(txL1FeeInWei),
ApprovalRequired: approvalRequired, ApprovalRequired: approvalRequired,
ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired), ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired),
ApprovalContractAddress: &approvalContractAddress, ApprovalContractAddress: &approvalContractAddress,
ApprovalBaseFee: (*hexutil.Big)(fetchedFees.BaseFee),
ApprovalPriorityFee: (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas),
ApprovalGasAmount: approvalGasLimit, ApprovalGasAmount: approvalGasLimit,
ApprovalFee: (*hexutil.Big)(approvalFeeInWei),
ApprovalL1Fee: (*hexutil.Big)(approvalL1FeeInWei),
TxTotalFee: (*hexutil.Big)(ethTotalFees),
EstimatedTime: estimatedTime, EstimatedTime: estimatedTime,
SubtractFees: amountOption.subtractFees, SubtractFees: amountOption.subtractFees,
RequiredTokenBalance: requiredTokenBalance, }
RequiredNativeBalance: requiredNativeBalance,
}) err = r.cacluateFees(ctx, path, fetchedFees, processorInputParams.TestsMode, processorInputParams.TestApprovalL1Fee)
if err != nil {
appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err)
continue
}
appendPathFn(path)
} }
} }
} }

View File

@ -10,43 +10,46 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/contracts" "github.com/status-im/status-go/contracts"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle" gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
"github.com/status-im/status-go/contracts/ierc20" "github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common" walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/fees"
"github.com/status-im/status-go/services/wallet/router/pathprocessor" "github.com/status-im/status-go/services/wallet/router/pathprocessor"
routs "github.com/status-im/status-go/services/wallet/router/routes"
"github.com/status-im/status-go/services/wallet/router/sendtype" "github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/token"
) )
func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) ( func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
bool, *big.Int, uint64, uint64, error) { bool, *big.Int, error) {
if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() { if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() {
return false, nil, 0, 0, nil return false, nil, nil
} }
if params.FromToken.IsNative() { if params.FromToken.IsNative() {
return false, nil, 0, 0, nil return false, nil, nil
} }
contractMaker, err := contracts.NewContractMaker(r.rpcClient) contractMaker, err := contracts.NewContractMaker(r.rpcClient)
if err != nil { if err != nil {
return false, nil, 0, 0, err return false, nil, err
} }
contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address) contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address)
if err != nil { if err != nil {
return false, nil, 0, 0, err return false, nil, err
} }
if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress { if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress {
return false, nil, 0, 0, nil return false, nil, nil
} }
if params.TestsMode { if params.TestsMode {
return true, params.AmountIn, params.TestApprovalGasEstimation, params.TestApprovalL1Fee, nil return true, params.AmountIn, nil
} }
allowance, err := contract.Allowance(&bind.CallOpts{ allowance, err := contract.Allowance(&bind.CallOpts{
@ -54,45 +57,65 @@ func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType
}, params.FromAddr, *approvalContractAddress) }, params.FromAddr, *approvalContractAddress)
if err != nil { if err != nil {
return false, nil, 0, 0, err return false, nil, err
} }
if allowance.Cmp(params.AmountIn) >= 0 { if allowance.Cmp(params.AmountIn) >= 0 {
return false, nil, 0, 0, nil return false, nil, nil
} }
ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID) return true, params.AmountIn, nil
if err != nil { }
return false, nil, 0, 0, err
func (r *Router) packApprovalInputData(amountIn *big.Int, approvalContractAddress *common.Address) ([]byte, error) {
if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress {
return []byte{}, nil
} }
erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
if err != nil { if err != nil {
return false, nil, 0, 0, err return []byte{}, err
} }
data, err := erc20ABI.Pack("approve", approvalContractAddress, params.AmountIn) return erc20ABI.Pack("approve", approvalContractAddress, amountIn)
}
func (r *Router) estimateGasForApproval(params pathprocessor.ProcessorInputParams, approvalContractAddress *common.Address) (uint64, error) {
data, err := r.packApprovalInputData(params.AmountIn, approvalContractAddress)
if err != nil { if err != nil {
return false, nil, 0, 0, err return 0, err
} }
estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{ ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID)
if err != nil {
return 0, err
}
return ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
From: params.FromAddr, From: params.FromAddr,
To: &params.FromToken.Address, To: &params.FromToken.Address,
Value: pathprocessor.ZeroBigIntValue, Value: pathprocessor.ZeroBigIntValue,
Data: data, Data: data,
}) })
if err != nil {
return false, nil, 0, 0, err
} }
// fetching l1 fee func (r *Router) calculateApprovalL1Fee(amountIn *big.Int, chainID uint64, approvalContractAddress *common.Address) (uint64, error) {
data, err := r.packApprovalInputData(amountIn, approvalContractAddress)
if err != nil {
return 0, err
}
ethClient, err := r.rpcClient.EthClient(chainID)
if err != nil {
return 0, err
}
var l1Fee uint64 var l1Fee uint64
oracleContractAddress, err := gaspriceoracle.ContractAddress(params.FromChain.ChainID) oracleContractAddress, err := gaspriceoracle.ContractAddress(chainID)
if err == nil { if err == nil {
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient) oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
if err != nil { if err != nil {
return false, nil, 0, 0, err return 0, err
} }
callOpt := &bind.CallOpts{} callOpt := &bind.CallOpts{}
@ -101,7 +124,7 @@ func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType
l1Fee = l1FeeResult.Uint64() l1Fee = l1FeeResult.Uint64()
} }
return true, params.AmountIn, estimate, l1Fee, nil return l1Fee, nil
} }
func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) { func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
@ -136,3 +159,96 @@ func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.To
return r.tokenManager.GetBalance(ctx, client, account, token.Address) return r.tokenManager.GetBalance(ctx, client, account, token.Address)
} }
func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees *fees.SuggestedFees, testsMode bool, testApprovalL1Fee uint64) (err error) {
var (
l1ApprovalFee uint64
)
if path.ApprovalRequired {
if testsMode {
l1ApprovalFee = testApprovalL1Fee
} else {
l1ApprovalFee, err = r.calculateApprovalL1Fee(path.AmountIn.ToInt(), path.FromChain.ChainID, path.ApprovalContractAddress)
if err != nil {
return err
}
}
}
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
var l1FeeWei uint64 = 0
// if input.SendType.needL1Fee() {
// txInputData, err := pProcessor.PackTxInputData(processorInputParams)
// if err != nil {
// continue
// }
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
// }
r.lastInputParamsMutex.Lock()
gasFeeMode := r.lastInputParams.GasFeeMode
r.lastInputParamsMutex.Unlock()
maxFeesPerGas := fetchedFees.FeeFor(gasFeeMode)
// calculate ETH fees
ethTotalFees := big.NewInt(0)
txFeeInWei := new(big.Int).Mul(maxFeesPerGas, big.NewInt(int64(path.TxGasAmount)))
ethTotalFees.Add(ethTotalFees, txFeeInWei)
txL1FeeInWei := big.NewInt(0)
if l1FeeWei > 0 {
txL1FeeInWei = big.NewInt(int64(l1FeeWei))
ethTotalFees.Add(ethTotalFees, txL1FeeInWei)
}
approvalFeeInWei := big.NewInt(0)
approvalL1FeeInWei := big.NewInt(0)
if path.ApprovalRequired {
approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(path.ApprovalGasAmount)))
ethTotalFees.Add(ethTotalFees, approvalFeeInWei)
if l1ApprovalFee > 0 {
approvalL1FeeInWei = big.NewInt(int64(l1ApprovalFee))
ethTotalFees.Add(ethTotalFees, approvalL1FeeInWei)
}
}
// calculate required balances (bonder and token fees are already included in the amountIn by Hop bridge (once we include Celar we need to check how they handle the fees))
requiredNativeBalance := big.NewInt(0)
requiredTokenBalance := big.NewInt(0)
if path.FromToken.IsNative() {
requiredNativeBalance.Add(requiredNativeBalance, path.AmountIn.ToInt())
if !path.SubtractFees {
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
}
} else {
requiredTokenBalance.Add(requiredTokenBalance, path.AmountIn.ToInt())
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
}
// set the values
path.SuggestedLevelsForMaxFeesPerGas = fetchedFees.MaxFeesLevels
path.MaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.TxPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
path.TxFee = (*hexutil.Big)(txFeeInWei)
path.TxL1Fee = (*hexutil.Big)(txL1FeeInWei)
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.ApprovalPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
path.ApprovalFee = (*hexutil.Big)(approvalFeeInWei)
path.ApprovalL1Fee = (*hexutil.Big)(approvalL1FeeInWei)
path.TxTotalFee = (*hexutil.Big)(ethTotalFees)
path.RequiredTokenBalance = requiredTokenBalance
path.RequiredNativeBalance = requiredNativeBalance
return nil
}

View File

@ -0,0 +1,150 @@
package router
import (
"context"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/rpc/chain"
walletCommon "github.com/status-im/status-go/services/wallet/common"
)
var (
newBlockCheckIntervalMainnet = 3 * time.Second
newBlockCheckIntervalOptimism = 1 * time.Second
newBlockCheckIntervalArbitrum = 200 * time.Millisecond
feeRecalculationTimeout = 5 * time.Minute
)
type fetchingLastBlock struct {
client chain.ClientInterface
lastBlock uint64
closeCh chan struct{}
}
func (r *Router) subscribeForUdates(chainID uint64) error {
if _, ok := r.clientsForUpdatesPerChains.Load(chainID); ok {
return nil
}
ethClient, err := r.rpcClient.EthClient(chainID)
if err != nil {
log.Error("Failed to get eth client", "error", err)
return err
}
flb := fetchingLastBlock{
client: ethClient,
lastBlock: 0,
closeCh: make(chan struct{}),
}
r.clientsForUpdatesPerChains.Store(chainID, flb)
r.startTimeoutForUpdates(flb.closeCh)
var ticker *time.Ticker
switch chainID {
case walletCommon.EthereumMainnet,
walletCommon.EthereumSepolia:
ticker = time.NewTicker(newBlockCheckIntervalMainnet)
case walletCommon.OptimismMainnet,
walletCommon.OptimismSepolia:
ticker = time.NewTicker(newBlockCheckIntervalOptimism)
case walletCommon.ArbitrumMainnet,
walletCommon.ArbitrumSepolia:
ticker = time.NewTicker(newBlockCheckIntervalArbitrum)
}
ctx, cancelCtx := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ticker.C:
var blockNumber uint64
blockNumber, err := ethClient.BlockNumber(ctx)
if err != nil {
log.Error("Failed to get block number", "error", err)
continue
}
val, ok := r.clientsForUpdatesPerChains.Load(chainID)
if !ok {
log.Error("Failed to get fetchingLastBlock", "chain", chainID)
continue
}
flbLoaded, ok := val.(fetchingLastBlock)
if !ok {
log.Error("Failed to get fetchingLastBlock", "chain", chainID)
continue
}
if blockNumber > flbLoaded.lastBlock {
flbLoaded.lastBlock = blockNumber
r.clientsForUpdatesPerChains.Store(chainID, flbLoaded)
fees, err := r.feesManager.SuggestedFees(ctx, chainID)
if err != nil {
log.Error("Failed to get suggested fees", "error", err)
continue
}
r.lastInputParamsMutex.Lock()
uuid := r.lastInputParams.Uuid
r.lastInputParamsMutex.Unlock()
r.activeRoutesMutex.Lock()
if r.activeRoutes != nil && r.activeRoutes.Best != nil && len(r.activeRoutes.Best) > 0 {
for _, path := range r.activeRoutes.Best {
err = r.cacluateFees(ctx, path, fees, false, 0)
if err != nil {
log.Error("Failed to calculate fees", "error", err)
continue
}
}
sendRouterResult(uuid, r.activeRoutes, nil)
}
r.activeRoutesMutex.Unlock()
}
case <-flb.closeCh:
ticker.Stop()
cancelCtx()
return
}
}
}()
return nil
}
func (r *Router) startTimeoutForUpdates(closeCh chan struct{}) {
dedlineTicker := time.NewTicker(feeRecalculationTimeout)
go func() {
for {
select {
case <-dedlineTicker.C:
r.unsubscribeFeesUpdateAccrossAllChains()
return
case <-closeCh:
dedlineTicker.Stop()
return
}
}
}()
}
func (r *Router) unsubscribeFeesUpdateAccrossAllChains() {
r.clientsForUpdatesPerChains.Range(func(key, value interface{}) bool {
flb, ok := value.(fetchingLastBlock)
if !ok {
log.Error("Failed to get fetchingLastBlock", "chain", key)
return false
}
close(flb.closeCh)
r.clientsForUpdatesPerChains.Delete(key)
return true
})
}