Skip to content

Commit

Permalink
Retrieve wallet states concurrently in redemption service (#828)
Browse files Browse the repository at this point in the history
## Description
This PR updates the `findWalletForRedemption` function to retrieve all
wallet states concurrently by using `Promise.all` rather than reading
them one by one. This change reduces the redemption processing time from
several minutes (6–7 minutes) down to approximately 15-20 seconds.

## Key Changes
- Retrieve wallet states concurrently using `Promise.all`.
- After retrieval, iterate over the assembled data to select the
appropriate wallet.
- Preserve the existing logic for determining suitability based on live
status, pending redemptions, and sufficient balance.

## Impact
- Dramatically improved performance and user experience.
- Significantly shortened wait times and reduced risk of timeouts.
  • Loading branch information
evandrosaturnino authored Feb 24, 2025
2 parents c8744ce + 9d7f27d commit 9a76db1
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 91 deletions.
18 changes: 9 additions & 9 deletions typescript/api-reference/classes/RedemptionsService.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Service exposing features related to tBTC v2 redemptions.

#### Defined in

[services/redemptions/redemptions-service.ts:31](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L31)
[services/redemptions/redemptions-service.ts:32](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L32)

## Properties

Expand All @@ -53,7 +53,7 @@ Bitcoin client handle.

#### Defined in

[services/redemptions/redemptions-service.ts:29](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L29)
[services/redemptions/redemptions-service.ts:30](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L30)

___

Expand All @@ -65,7 +65,7 @@ Handle to tBTC contracts.

#### Defined in

[services/redemptions/redemptions-service.ts:25](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L25)
[services/redemptions/redemptions-service.ts:26](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L26)

## Methods

Expand All @@ -92,7 +92,7 @@ Object containing:

#### Defined in

[services/redemptions/redemptions-service.ts:132](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L132)
[services/redemptions/redemptions-service.ts:133](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L133)

___

Expand All @@ -119,7 +119,7 @@ Promise holding the wallet main UTXO or undefined value.

#### Defined in

[services/redemptions/redemptions-service.ts:302](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L302)
[services/redemptions/redemptions-service.ts:326](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L326)

___

Expand All @@ -145,7 +145,7 @@ Promise with the wallet details needed to request a redemption.

#### Defined in

[services/redemptions/redemptions-service.ts:181](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L181)
[services/redemptions/redemptions-service.ts:182](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L182)

___

Expand Down Expand Up @@ -176,7 +176,7 @@ Throws an error if no redemption request exists for the given

#### Defined in

[services/redemptions/redemptions-service.ts:414](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L414)
[services/redemptions/redemptions-service.ts:438](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L438)

___

Expand Down Expand Up @@ -205,7 +205,7 @@ Object containing:

#### Defined in

[services/redemptions/redemptions-service.ts:49](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L49)
[services/redemptions/redemptions-service.ts:50](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L50)

___

Expand Down Expand Up @@ -237,4 +237,4 @@ Object containing:

#### Defined in

[services/redemptions/redemptions-service.ts:88](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L88)
[services/redemptions/redemptions-service.ts:89](https://github.com/keep-network/tbtc-v2/blob/main/typescript/src/services/redemptions/redemptions-service.ts#L89)
188 changes: 106 additions & 82 deletions typescript/src/services/redemptions/redemptions-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
RedemptionRequest,
TBTCContracts,
Wallet,
WalletState,
} from "../../lib/contracts"
import {
Expand Down Expand Up @@ -195,100 +196,123 @@ export class RedemptionsService {
}
| undefined = undefined
let maxAmount = BigNumber.from(0)
let liveWalletsCounter = 0

const bitcoinNetwork = await this.bitcoinClient.getNetwork()

for (const wallet of wallets) {
const { walletPublicKeyHash } = wallet
const { state, walletPublicKey, pendingRedemptionsValue } =
await this.tbtcContracts.bridge.wallets(walletPublicKeyHash)

// Wallet must be in Live state.
if (state !== WalletState.Live || !walletPublicKey) {
console.debug(
`Wallet is not in Live state ` +
`(wallet public key hash: ${walletPublicKeyHash.toString()}). ` +
`Continue the loop execution to the next wallet...`
)
continue
}
liveWalletsCounter++

// Wallet must have a main UTXO that can be determined.
const mainUtxo = await this.determineWalletMainUtxo(
walletPublicKeyHash,
bitcoinNetwork
const bridgeWalletsByWalletPublicKeyHash: Promise<Wallet>[] = []
for (let index = 0; index < wallets.length; index++) {
const { walletPublicKeyHash } = wallets[index]
bridgeWalletsByWalletPublicKeyHash.push(
this.tbtcContracts.bridge.wallets(walletPublicKeyHash)
)
if (!mainUtxo) {
console.debug(
`Could not find matching UTXO on chains ` +
`for wallet public key hash (${walletPublicKeyHash.toString()}). ` +
`Continue the loop execution to the next wallet...`
)
continue
}

const pendingRedemption =
await this.tbtcContracts.bridge.pendingRedemptions(
walletPublicKey,
redeemerOutputScript
)

if (pendingRedemption.requestedAt != 0) {
console.debug(
`There is a pending redemption request from this wallet to the ` +
`same Bitcoin address. Given wallet public key hash` +
`(${walletPublicKeyHash.toString()}) and redeemer output script ` +
`(${redeemerOutputScript.toString()}) pair can be used for only one ` +
`pending request at the same time. ` +
`Continue the loop execution to the next wallet...`
)
continue
}

const walletBTCBalance = mainUtxo.value.sub(pendingRedemptionsValue)
}

// Save the max possible redemption amount.
maxAmount = walletBTCBalance.gt(maxAmount) ? walletBTCBalance : maxAmount
let walletPublicKeyHashIndex = 0

if (walletBTCBalance.gte(amount)) {
walletData = {
// Using Promise.all, we retrieve all wallet states at once,
// significantly improving overall performance.
return Promise.all(bridgeWalletsByWalletPublicKeyHash).then(
async (bridgeWallets) => {
let liveWalletsCounter = 0
for (const {
state,
walletPublicKey,
mainUtxo,
pendingRedemptionsValue,
} of bridgeWallets) {
const { walletPublicKeyHash } = wallets[walletPublicKeyHashIndex]
walletPublicKeyHashIndex++

// Wallet must be in Live state.
if (state !== WalletState.Live || !walletPublicKey) {
console.debug(
`Wallet is not in Live state ` +
`(wallet public key hash: ${walletPublicKeyHash.toString()}). ` +
`Continue the loop execution to the next wallet...`
)
continue
}
liveWalletsCounter++

// Wallet must have a main UTXO that can be determined.
const mainUtxo = await this.determineWalletMainUtxo(
walletPublicKeyHash,
bitcoinNetwork
)
if (!mainUtxo) {
console.debug(
`Could not find matching UTXO on chains ` +
`for wallet public key hash (${walletPublicKeyHash.toString()}). ` +
`Continue the loop execution to the next wallet...`
)
continue
}

const pendingRedemption =
await this.tbtcContracts.bridge.pendingRedemptions(
walletPublicKey,
redeemerOutputScript
)

if (pendingRedemption.requestedAt != 0) {
console.debug(
`There is a pending redemption request from this wallet to the ` +
`same Bitcoin address. Given wallet public key hash` +
`(${walletPublicKeyHash.toString()}) and redeemer output script ` +
`(${redeemerOutputScript.toString()}) pair can be used for only one ` +
`pending request at the same time. ` +
`Continue the loop execution to the next wallet...`
)
continue
}

const walletBTCBalance = mainUtxo.value.sub(pendingRedemptionsValue)

// Save the max possible redemption amount.
maxAmount = walletBTCBalance.gt(maxAmount)
? walletBTCBalance
: maxAmount

if (walletBTCBalance.gte(amount)) {
walletData = {
walletPublicKey,
mainUtxo,
}

break
}

console.debug(
`The wallet (${walletPublicKeyHash.toString()})` +
`cannot handle the redemption request. ` +
`Continue the loop execution to the next wallet...`
)
}

break
}

console.debug(
`The wallet (${walletPublicKeyHash.toString()})` +
`cannot handle the redemption request. ` +
`Continue the loop execution to the next wallet...`
)
}

if (liveWalletsCounter === 0) {
throw new Error("Currently, there are no live wallets in the network.")
}
if (liveWalletsCounter === 0) {
throw new Error(
"Currently, there are no live wallets in the network."
)
}

// Cover a corner case when the user requested redemption for all live wallets
// in the network using the same Bitcoin address.
if (!walletData && liveWalletsCounter > 0 && maxAmount.eq(0)) {
throw new Error(
"All live wallets in the network have the pending redemption for a given Bitcoin address. " +
"Please use another Bitcoin address."
)
}
// Cover a corner case when the user requested redemption for all live wallets
// in the network using the same Bitcoin address.
if (!walletData && liveWalletsCounter > 0 && maxAmount.eq(0)) {
throw new Error(
"All live wallets in the network have the pending redemption for a given Bitcoin address. " +
"Please use another Bitcoin address."
)
}

if (!walletData)
throw new Error(
`Could not find a wallet with enough funds. Maximum redemption amount is ${maxAmount} Satoshi ( ${maxAmount.div(
BigNumber.from(1e8)
)} BTC ) .`
)
if (!walletData)
throw new Error(
`Could not find a wallet with enough funds. Maximum redemption amount is ${maxAmount} Satoshi ( ${maxAmount.div(
BigNumber.from(1e8)
)} BTC ) .`
)

return walletData
return walletData
}
)
}

/**
Expand Down

0 comments on commit 9a76db1

Please sign in to comment.