-
Notifications
You must be signed in to change notification settings - Fork 8
Add configurable rebalance strategy for bridge pegout #934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v2.6.0
Are you sure you want to change the base?
Changes from 2 commits
92b7cbf
ab5a40a
6364162
dcb1ba4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,7 +92,11 @@ func NewUseCaseRegistry( | |
| liquidityProvider *dataproviders.LocalLiquidityProvider, | ||
| messaging *Messaging, | ||
| mutexes entities.ApplicationMutexes, | ||
| ) *UseCaseRegistry { | ||
| ) (*UseCaseRegistry, error) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if there is no chance this returns an error anymore you should update the signature |
||
| rebalanceStrategy, err := pegout.ParseRebalanceStrategy(env.Pegout.RebalanceStrategy) | ||
volodymyrzahanych marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return &UseCaseRegistry{ | ||
| summariesUseCase: reports.NewSummariesUseCase( | ||
| databaseRegistry.PeginRepository, | ||
|
|
@@ -259,6 +263,7 @@ func NewUseCaseRegistry( | |
| rskRegistry.Wallet, | ||
| rskRegistry.Contracts, | ||
| mutexes.RskWalletMutex(), | ||
| rebalanceStrategy, | ||
| ), | ||
| peginStatusUseCase: pegin.NewStatusUseCase(databaseRegistry.PeginRepository), | ||
| pegoutStatusUseCase: pegout.NewStatusUseCase(databaseRegistry.PegoutRepository), | ||
|
|
@@ -348,7 +353,7 @@ func NewUseCaseRegistry( | |
| messaging.Rpc, | ||
| utils.Scale, | ||
| ), | ||
| } | ||
| }, nil | ||
| } | ||
|
|
||
| func (registry *UseCaseRegistry) GetPeginQuoteUseCase() *pegin.GetQuoteUseCase { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ package pegout | |
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "sync" | ||
|
|
||
| "github.com/rsksmart/liquidity-provider-server/internal/entities" | ||
|
|
@@ -20,12 +21,31 @@ const ( | |
| BridgeConversionGasPrice = 60000000 | ||
| ) | ||
|
|
||
| type RebalanceStrategy string | ||
|
|
||
| const ( | ||
| AllAtOnce RebalanceStrategy = "ALL_AT_ONCE" | ||
| UtxoSplit RebalanceStrategy = "UTXO_SPLIT" | ||
| ) | ||
|
|
||
| func ParseRebalanceStrategy(s string) (RebalanceStrategy, error) { | ||
| switch s { | ||
| case "", string(AllAtOnce): | ||
volodymyrzahanych marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return AllAtOnce, nil | ||
| case string(UtxoSplit): | ||
| return UtxoSplit, nil | ||
| default: | ||
| return "", fmt.Errorf("unknown rebalance strategy: %q", s) | ||
| } | ||
| } | ||
|
|
||
| type BridgePegoutUseCase struct { | ||
| quoteRepository quote.PegoutQuoteRepository | ||
| pegoutProvider liquidity_provider.PegoutLiquidityProvider | ||
| rskWallet blockchain.RootstockWallet | ||
| contracts blockchain.RskContracts | ||
| rskWalletMutex sync.Locker | ||
| strategy RebalanceStrategy | ||
| } | ||
|
|
||
| func NewBridgePegoutUseCase( | ||
|
|
@@ -34,19 +54,21 @@ func NewBridgePegoutUseCase( | |
| rskWallet blockchain.RootstockWallet, | ||
| contracts blockchain.RskContracts, | ||
| rskWalletMutex sync.Locker, | ||
| strategy RebalanceStrategy, | ||
| ) *BridgePegoutUseCase { | ||
| return &BridgePegoutUseCase{ | ||
| quoteRepository: quoteRepository, | ||
| pegoutProvider: pegoutProvider, | ||
| rskWallet: rskWallet, | ||
| contracts: contracts, | ||
| rskWalletMutex: rskWalletMutex, | ||
| strategy: strategy, | ||
| } | ||
| } | ||
|
|
||
| func (useCase *BridgePegoutUseCase) Run(ctx context.Context, watchedQuotes ...quote.WatchedPegoutQuote) error { | ||
| var err error | ||
| var balance, totalValue *entities.Wei | ||
| var totalValue *entities.Wei | ||
|
|
||
| totalValue, err = useCase.calculateTotalToPegout(watchedQuotes) | ||
| if err != nil { | ||
|
|
@@ -66,23 +88,94 @@ func (useCase *BridgePegoutUseCase) Run(ctx context.Context, watchedQuotes ...qu | |
| useCase.rskWalletMutex.Lock() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the mutex should be passed to the handler. Otherwise if you call the function from somewhere else or scan the file without context it can throw false positive alerts for possible race conditions |
||
| defer useCase.rskWalletMutex.Unlock() | ||
|
|
||
| requiredBalance := new(entities.Wei).Add(totalValue, entities.NewWei(BridgeConversionGasLimit*BridgeConversionGasPrice)) | ||
| if balance, err = useCase.rskWallet.GetBalance(ctx); err != nil { | ||
| switch useCase.strategy { | ||
volodymyrzahanych marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| case UtxoSplit: | ||
| return useCase.runUtxoSplit(ctx, totalValue, pegoutConfig, watchedQuotes) | ||
| default: | ||
| return useCase.runAllAtOnce(ctx, totalValue, watchedQuotes) | ||
| } | ||
| } | ||
|
|
||
| func (useCase *BridgePegoutUseCase) checkBalance(ctx context.Context, requiredBalance *entities.Wei) error { | ||
| balance, err := useCase.rskWallet.GetBalance(ctx) | ||
| if err != nil { | ||
| return usecases.WrapUseCaseError(usecases.BridgePegoutId, err) | ||
| } else if balance.Cmp(requiredBalance) < 0 { | ||
| } | ||
| if balance.Cmp(requiredBalance) < 0 { | ||
| return usecases.WrapUseCaseError(usecases.BridgePegoutId, usecases.InsufficientAmountError) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (useCase *BridgePegoutUseCase) runAllAtOnce(ctx context.Context, totalValue *entities.Wei, watchedQuotes []quote.WatchedPegoutQuote) error { | ||
| requiredBalance := new(entities.Wei).Add(totalValue, entities.NewWei(BridgeConversionGasLimit*BridgeConversionGasPrice)) | ||
| if err := useCase.checkBalance(ctx, requiredBalance); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| config := blockchain.NewTransactionConfig(totalValue, BridgeConversionGasLimit, entities.NewWei(BridgeConversionGasPrice)) | ||
| receipt, txErr := useCase.rskWallet.SendRbtc(ctx, config, useCase.contracts.Bridge.GetAddress()) | ||
| if txErr == nil { | ||
| log.Debugf("%s: transaction sent to the bridge successfully (%s)", usecases.BridgePegoutId, receipt.TransactionHash) | ||
| } | ||
|
|
||
| err = useCase.updateQuotes(ctx, receipt, txErr, watchedQuotes) | ||
| if err := useCase.updateQuotes(ctx, receipt, txErr, watchedQuotes); err != nil { | ||
| return usecases.WrapUseCaseError(usecases.BridgePegoutId, err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (useCase *BridgePegoutUseCase) runUtxoSplit( | ||
| ctx context.Context, | ||
| totalValue *entities.Wei, | ||
| pegoutConfig liquidity_provider.PegoutConfiguration, | ||
| watchedQuotes []quote.WatchedPegoutQuote, | ||
| ) error { | ||
| bridgeMin := pegoutConfig.BridgeTransactionMin | ||
| numTxsWei, err := new(entities.Wei).Div(totalValue, bridgeMin) | ||
| if err != nil { | ||
| return usecases.WrapUseCaseError(usecases.BridgePegoutId, err) | ||
| } | ||
| numTxs := numTxsWei.Uint64() | ||
| remainder := new(entities.Wei).Sub(totalValue, new(entities.Wei).Mul(numTxsWei, bridgeMin)) | ||
|
|
||
| gasPerTx := entities.NewWei(BridgeConversionGasLimit * BridgeConversionGasPrice) | ||
| requiredBalance := new(entities.Wei).Add(totalValue, new(entities.Wei).Mul(numTxsWei, gasPerTx)) | ||
| if err := useCase.checkBalance(ctx, requiredBalance); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| bridgeAddress := useCase.contracts.Bridge.GetAddress() | ||
volodymyrzahanych marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // First chunk absorbs the remainder (when N=1, firstChunk == totalValue) | ||
| firstChunk := new(entities.Wei).Add(bridgeMin.Copy(), remainder) | ||
| var receipt blockchain.TransactionReceipt | ||
| var txErr error | ||
|
|
||
| config := blockchain.NewTransactionConfig(firstChunk, BridgeConversionGasLimit, entities.NewWei(BridgeConversionGasPrice)) | ||
| receipt, txErr = useCase.rskWallet.SendRbtc(ctx, config, bridgeAddress) | ||
| if txErr == nil { | ||
| log.Debugf("%s: split tx 1/%d sent to the bridge successfully (%s)", usecases.BridgePegoutId, numTxs, receipt.TransactionHash) | ||
| } else { | ||
| return usecases.WrapUseCaseError( | ||
| usecases.BridgePegoutId, | ||
| useCase.updateQuotes(ctx, receipt, txErr, watchedQuotes), | ||
volodymyrzahanych marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) | ||
| } | ||
|
|
||
| for i := uint64(1); i < numTxs; i++ { | ||
| config = blockchain.NewTransactionConfig(bridgeMin.Copy(), BridgeConversionGasLimit, entities.NewWei(BridgeConversionGasPrice)) | ||
| receipt, txErr = useCase.rskWallet.SendRbtc(ctx, config, bridgeAddress) | ||
| if txErr == nil { | ||
| log.Debugf("%s: split tx %d/%d sent to the bridge successfully (%s)", usecases.BridgePegoutId, i+1, numTxs, receipt.TransactionHash) | ||
| } else { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| if err := useCase.updateQuotes(ctx, receipt, txErr, watchedQuotes); err != nil { | ||
volodymyrzahanych marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return usecases.WrapUseCaseError(usecases.BridgePegoutId, err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.