From 59cf85ab5504c24d953097ed9c270499a79ea1cc Mon Sep 17 00:00:00 2001 From: Jamie Ford Date: Mon, 25 Nov 2024 13:47:53 +1100 Subject: [PATCH] refactor: use mutex instead of locking --- bouncer/shared/send_btc.ts | 67 ++++++++++++++++++++++++--------- bouncer/tests/btc_vault_swap.ts | 35 ++++------------- 2 files changed, 56 insertions(+), 46 deletions(-) diff --git a/bouncer/shared/send_btc.ts b/bouncer/shared/send_btc.ts index 95aad18f5f..57da971383 100644 --- a/bouncer/shared/send_btc.ts +++ b/bouncer/shared/send_btc.ts @@ -12,32 +12,63 @@ export const btcClient = new Client({ }); export async function selectInputs(amount: number) { - return btcClientMutex.runExclusive(async () => { - // List unspent UTXOs - const utxos = await btcClient.listUnspent(); + // List unspent UTXOs + const utxos = await btcClient.listUnspent(); + + // Find a UTXO with enough funds + const utxo = utxos.find((u) => u.amount >= amount); + if (!utxo) throw new Error('Insufficient funds'); + // TODO: be able to select more than one UTXO + + const change = utxo.amount - amount; - // Find a UTXO with enough funds - const utxo = utxos.find((u) => u.amount >= amount); - if (!utxo) throw new Error('Insufficient funds'); - // TODO: be able to select more than one UTXO + // Prepare the transaction inputs + const inputs = [ + { + txid: utxo.txid, + vout: utxo.vout, + }, + ]; - // Lock the selected UTXO to prevent it from being used in another transaction - await btcClient.lockUnspent(false, [{ txid: utxo.txid, vout: utxo.vout }]); + return { + inputs, + change, + }; +} + +export async function sendVaultTransaction( + nulldataUtxo: string, + amountBtc: number, + depositAddress: string, + refundAddress: string, +) { + return btcClientMutex.runExclusive(async () => { + const feeBtc = 0.00001; + const { inputs, change } = await selectInputs(Number(amountBtc) + feeBtc); - const change = utxo.amount - amount; + // The `createRawTransaction` function will add the op codes, so we have to remove them here. + const nullDataWithoutOpCodes = nulldataUtxo.replace('0x', '').substring(4); - // Prepare the transaction inputs - const inputs = [ + const outputs = [ { - txid: utxo.txid, - vout: utxo.vout, + [depositAddress]: amountBtc, + }, + { + data: nullDataWithoutOpCodes, + }, + { + [refundAddress]: change, }, ]; - return { - inputs, - change, - }; + const rawTx = await btcClient.createRawTransaction(inputs, outputs, 0, false); + const signedTx = await btcClient.signRawTransactionWithWallet(rawTx); + const txid = await btcClient.sendRawTransaction(signedTx.hex); + + if (!txid) { + throw new Error('Broadcast failed'); + } + return txid as string; }); } diff --git a/bouncer/tests/btc_vault_swap.ts b/bouncer/tests/btc_vault_swap.ts index d3d72dfd5a..b93ceb18da 100644 --- a/bouncer/tests/btc_vault_swap.ts +++ b/bouncer/tests/btc_vault_swap.ts @@ -1,11 +1,10 @@ import assert from 'assert'; import { ExecutableTest } from '../shared/executable_test'; -import { BTC_ENDPOINT, selectInputs, waitForBtcTransaction, btcClient } from '../shared/send_btc'; +import { BTC_ENDPOINT, waitForBtcTransaction, sendVaultTransaction } from '../shared/send_btc'; import { amountToFineAmount, Asset, assetDecimals, - btcClientMutex, createStateChainKeypair, newAddress, observeBalanceIncrease, @@ -51,9 +50,6 @@ async function buildAndSendBtcVaultSwap( affiliates.push({ account: affiliateAddress, bps: commissionBps }); } - const feeBtc = 0.00001; - const { inputs, change } = await selectInputs(Number(depositAmountBtc) + feeBtc); - const vaultSwapDetails = (await chainflip.rpc( `cf_get_vault_swap_details`, broker.address, @@ -71,32 +67,15 @@ async function buildAndSendBtcVaultSwap( testBtcVaultSwap.debugLog('nulldata_utxo:', vaultSwapDetails.nulldata_utxo); testBtcVaultSwap.debugLog('deposit_address:', vaultSwapDetails.deposit_address); - // The `createRawTransaction` function will add the op codes, so we have to remove them here. - const nullDataWithoutOpCodes = vaultSwapDetails.nulldata_utxo.replace('0x', '').substring(4); - - const outputs = [ - { - [vaultSwapDetails.deposit_address]: depositAmountBtc, - }, - { - data: nullDataWithoutOpCodes, - }, - { - [refundAddress]: change, - }, - ]; - - const rawTx = await btcClient.createRawTransaction(inputs, outputs, 0, false); - const signedTx = await btcClient.signRawTransactionWithWallet(rawTx); - const txid = await btcClientMutex.runExclusive(async () => - btcClient.sendRawTransaction(signedTx.hex), + const txid = await sendVaultTransaction( + vaultSwapDetails.nulldata_utxo, + depositAmountBtc, + vaultSwapDetails.deposit_address, + refundAddress, ); - if (!txid) { - throw new Error('Broadcast failed'); - } testBtcVaultSwap.log('Broadcast successful, txid:', txid); - await waitForBtcTransaction(txid as string); + await waitForBtcTransaction(txid); testBtcVaultSwap.debugLog('Transaction confirmed'); }