Skip to content

Commit

Permalink
Make findWalletForRedemption use determineWalletMainUtxo function
Browse files Browse the repository at this point in the history
The `findWalletForRedemption` function should rely on `determineWalletMainUtxo`
wallet while seeking for current wallet main UTXO. Otherwise, it may not
find the main UTXO in case there is a wallet state drift between Bitcoin
and Ethereum chains.
  • Loading branch information
lukasz-zimnoch committed Jul 11, 2023
1 parent 4994672 commit 10ab6da
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 114 deletions.
57 changes: 13 additions & 44 deletions typescript/src/redemption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import {
UnspentTransactionOutput,
Client as BitcoinClient,
TransactionHash,
encodeToBitcoinAddress,
} from "./bitcoin"
import { Bridge, Identifier, TBTCToken } from "./chain"
import { assembleTransactionProof } from "./proof"
import { WalletState } from "./wallet"
import { determineWalletMainUtxo, WalletState } from "./wallet"
import { BitcoinNetwork } from "./bitcoin-network"
import { Hex } from "./hex"

Expand Down Expand Up @@ -445,7 +444,7 @@ export async function findWalletForRedemption(

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

// Wallet must be in Live state.
Expand All @@ -458,18 +457,20 @@ export async function findWalletForRedemption(
continue
}

if (
mainUtxoHash.equals(
Hex.from(
"0x0000000000000000000000000000000000000000000000000000000000000000"
)
)
) {
// Wallet must have a main UTXO that can be determined.
const mainUtxo = await determineWalletMainUtxo(
walletPublicKeyHash,
bridge,
bitcoinClient,
bitcoinNetwork
)
if (!mainUtxo) {
console.debug(
`Main utxo not set for wallet public ` +
`key hash(${walletPublicKeyHash.toString()}). ` +
`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 bridge.pendingRedemptions(
Expand All @@ -489,38 +490,6 @@ export async function findWalletForRedemption(
continue
}

const walletBitcoinAddress = encodeToBitcoinAddress(
wallet.walletPublicKeyHash.toString(),
true,
bitcoinNetwork
)

// TODO: In case a wallet is working on something (e.g. redemption) and a
// Bitcoin transaction was already submitted by the wallet to the bitcoin
// chain (new utxo returned from bitcoin client), but proof hasn't been
// submitted yet to the Bridge (old main utxo returned from the Bridge) the
// `findWalletForRedemption` function will not find such a wallet. To cover
// this case, we should take, for example, the last 5 transactions made by
// the wallet into account. We will address this issue in a follow-up work.
const utxos = await bitcoinClient.findAllUnspentTransactionOutputs(
walletBitcoinAddress
)

// We need to find correct utxo- utxo components must point to the recent
// main UTXO of the given wallet, as currently known on the chain.
const mainUtxo = utxos.find((utxo) =>
mainUtxoHash.equals(bridge.buildUtxoHash(utxo))
)

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 walletBTCBalance = mainUtxo.value.sub(pendingRedemptionsValue)

// Save the max possible redemption amount.
Expand Down
92 changes: 61 additions & 31 deletions typescript/test/data/redemption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
UnspentTransactionOutput,
TransactionMerkleBranch,
TransactionHash,
createOutputScriptFromAddress,
} from "../../src/bitcoin"
import { RedemptionRequest } from "../../src/redemption"
import { Address } from "../../src/ethereum"
import { Hex } from "../../src"
import { BitcoinTransaction, Hex } from "../../src"
import { WalletState } from "../../src/wallet"

/**
Expand Down Expand Up @@ -668,13 +669,14 @@ export const redemptionProof: RedemptionProofTestData = {
},
}

interface FindWalletForRedemptionWaleltData {
interface FindWalletForRedemptionWalletData {
data: {
state: WalletState
mainUtxoHash: Hex
walletPublicKey: Hex
btcAddress: string
utxos: UnspentTransactionOutput[]
mainUtxo: UnspentTransactionOutput
transactions: BitcoinTransaction[]
pendingRedemptionsValue: BigNumber
}
event: {
Expand All @@ -687,10 +689,10 @@ interface FindWalletForRedemptionWaleltData {
}

export const findWalletForRedemptionData: {
liveWallet: FindWalletForRedemptionWaleltData
walletWithoutUtxo: FindWalletForRedemptionWaleltData
nonLiveWallet: FindWalletForRedemptionWaleltData
walletWithPendingRedemption: FindWalletForRedemptionWaleltData
liveWallet: FindWalletForRedemptionWalletData
walletWithoutUtxo: FindWalletForRedemptionWalletData
nonLiveWallet: FindWalletForRedemptionWalletData
walletWithPendingRedemption: FindWalletForRedemptionWalletData
pendingRedemption: RedemptionRequest
} = {
liveWallet: {
Expand All @@ -703,13 +705,28 @@ export const findWalletForRedemptionData: {
"0x028ed84936be6a9f594a2dcc636d4bebf132713da3ce4dac5c61afbf8bbb47d6f7"
),
btcAddress: "tb1qqwm566yn44rdlhgph8sw8vecta8uutg79afuja",
utxos: [
mainUtxo: {
transactionHash: Hex.from(
"0x5b6d040eb06b3de1a819890d55d251112e55c31db4a3f5eb7cfacf519fad7adb"
),
outputIndex: 0,
value: BigNumber.from("791613461"),
},
transactions: [
{
transactionHash: Hex.from(
"0x5b6d040eb06b3de1a819890d55d251112e55c31db4a3f5eb7cfacf519fad7adb"
),
outputIndex: 0,
value: BigNumber.from("791613461"),
inputs: [], // not relevant
outputs: [
{
outputIndex: 0,
value: BigNumber.from("791613461"),
scriptPubKey: createOutputScriptFromAddress(
"tb1qqwm566yn44rdlhgph8sw8vecta8uutg79afuja"
),
},
],
},
],
pendingRedemptionsValue: BigNumber.from(0),
Expand Down Expand Up @@ -740,15 +757,14 @@ export const findWalletForRedemptionData: {
"0x030fbbae74e6d85342819e719575949a1349e975b69fb382e9fef671a3a74efc52"
),
btcAddress: "tb1qkct7r24k4wutnsun84rvp3qsyt8yfpvqz89d2y",
utxos: [
{
transactionHash: Hex.from(
"0x0000000000000000000000000000000000000000000000000000000000000000"
),
outputIndex: 0,
value: BigNumber.from("0"),
},
],
mainUtxo: {
transactionHash: Hex.from(
"0x0000000000000000000000000000000000000000000000000000000000000000"
),
outputIndex: 0,
value: BigNumber.from("0"),
},
transactions: [],
pendingRedemptionsValue: BigNumber.from(0),
},
event: {
Expand Down Expand Up @@ -778,15 +794,14 @@ export const findWalletForRedemptionData: {
"0x02633b102417009ae55103798f4d366dfccb081dcf20025088b9bf10a8e15d8ded"
),
btcAddress: "tb1qf6jvyd680ncf9dtr5znha9ql5jmw84lupwwuf6",
utxos: [
{
transactionHash: Hex.from(
"0x0000000000000000000000000000000000000000000000000000000000000000"
),
outputIndex: 0,
value: BigNumber.from("0"),
},
],
mainUtxo: {
transactionHash: Hex.from(
"0x0000000000000000000000000000000000000000000000000000000000000000"
),
outputIndex: 0,
value: BigNumber.from("0"),
},
transactions: [],
pendingRedemptionsValue: BigNumber.from(0),
},
event: {
Expand Down Expand Up @@ -815,13 +830,28 @@ export const findWalletForRedemptionData: {
"0x02ab193a63b3523bfab77d3645d11da10722393687458c4213b350b7e08f50b7ee"
),
btcAddress: "tb1qx2xejtjltdcau5dpks8ucszkhxdg3fj88404lh",
utxos: [
mainUtxo: {
transactionHash: Hex.from(
"0x81c4884a8c2fccbeb57745a5e59f895a9c1bb8fc42eecc82269100a1a46bbb85"
),
outputIndex: 0,
value: BigNumber.from("3370000"), // 0.0337 BTC
},
transactions: [
{
transactionHash: Hex.from(
"0x81c4884a8c2fccbeb57745a5e59f895a9c1bb8fc42eecc82269100a1a46bbb85"
),
outputIndex: 0,
value: BigNumber.from("3370000"), // 0.0337 BTC
inputs: [], // not relevant
outputs: [
{
outputIndex: 0,
value: BigNumber.from("3370000"), // 0.0337 BTC
scriptPubKey: createOutputScriptFromAddress(
"tb1qx2xejtjltdcau5dpks8ucszkhxdg3fj88404lh"
),
},
],
},
],
pendingRedemptionsValue: BigNumber.from(2370000), // 0.0237 BTC
Expand Down
50 changes: 11 additions & 39 deletions typescript/test/redemption.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { BigNumberish, BigNumber } from "ethers"
import { BitcoinNetwork } from "../src/bitcoin-network"
import { Wallet } from "../src/wallet"
import { MockTBTCToken } from "./utils/mock-tbtc-token"
import { BitcoinTransaction } from "../src"

chai.use(chaiAsPromised)

Expand Down Expand Up @@ -1490,9 +1491,9 @@ describe("Redemption", () => {
(wallet) => wallet.event
)

const walletsUnspentTransacionOutputs = new Map<
const walletsTransactionHistory = new Map<
string,
UnspentTransactionOutput[]
BitcoinTransaction[]
>()

walletsOrder.forEach((wallet) => {
Expand All @@ -1501,11 +1502,11 @@ describe("Redemption", () => {
mainUtxoHash,
walletPublicKey,
btcAddress,
utxos,
transactions,
pendingRedemptionsValue,
} = wallet.data

walletsUnspentTransacionOutputs.set(btcAddress, utxos)
walletsTransactionHistory.set(btcAddress, transactions)
bridge.setWallet(
wallet.event.walletPublicKeyHash.toPrefixedString(),
{
Expand All @@ -1517,8 +1518,7 @@ describe("Redemption", () => {
)
})

bitcoinClient.unspentTransactionOutputs =
walletsUnspentTransacionOutputs
bitcoinClient.transactionHistory = walletsTransactionHistory
})

context(
Expand All @@ -1545,29 +1545,13 @@ describe("Redemption", () => {
})
})

it("should get wallet data details", () => {
const bridgeWalletDetailsLogs = bridge.walletsLog

const wallets = Array.from(walletsOrder)
// Remove last live wallet.
wallets.pop()

expect(bridgeWalletDetailsLogs.length).to.eql(wallets.length)

wallets.forEach((wallet, index) => {
expect(bridgeWalletDetailsLogs[index].walletPublicKeyHash).to.eql(
wallet.event.walletPublicKeyHash.toPrefixedString()
)
})
})

it("should return the wallet data that can handle redemption request", () => {
const expectedWalletData =
findWalletForRedemptionData.walletWithPendingRedemption.data

expect(result).to.deep.eq({
walletPublicKey: expectedWalletData.walletPublicKey.toString(),
mainUtxo: expectedWalletData.utxos[0],
mainUtxo: expectedWalletData.mainUtxo,
})
})
}
Expand All @@ -1579,8 +1563,7 @@ describe("Redemption", () => {
const amount = BigNumber.from("10000000000") // 1 000 BTC
const expectedMaxAmount = walletsOrder
.map((wallet) => wallet.data)
.map((wallet) => wallet.utxos)
.flat()
.map((wallet) => wallet.mainUtxo)
.map((utxo) => utxo.value)
.sort((a, b) => (b.gt(a) ? 0 : -1))[0]

Expand Down Expand Up @@ -1647,24 +1630,13 @@ describe("Redemption", () => {
})
})

it("should get wallet data details", () => {
const bridgeWalletDetailsLogs = bridge.walletsLog

expect(bridgeWalletDetailsLogs.length).to.eql(walletsOrder.length)
walletsOrder.forEach((wallet, index) => {
expect(bridgeWalletDetailsLogs[index].walletPublicKeyHash).to.eql(
wallet.event.walletPublicKeyHash.toPrefixedString()
)
})
})

it("should skip the wallet for which there is a pending redemption request to the same redeemer output script and return the wallet data that can handle redemption request", () => {
const expectedWalletData =
findWalletForRedemptionData.liveWallet.data

expect(result).to.deep.eq({
walletPublicKey: expectedWalletData.walletPublicKey.toString(),
mainUtxo: expectedWalletData.utxos[0],
mainUtxo: expectedWalletData.mainUtxo,
})
})
}
Expand All @@ -1676,7 +1648,7 @@ describe("Redemption", () => {
beforeEach(async () => {
const wallet =
findWalletForRedemptionData.walletWithPendingRedemption
const walletBTCBalance = wallet.data.utxos[0].value
const walletBTCBalance = wallet.data.mainUtxo.value

const amount: BigNumber = walletBTCBalance
.sub(wallet.data.pendingRedemptionsValue)
Expand All @@ -1699,7 +1671,7 @@ describe("Redemption", () => {

expect(result).to.deep.eq({
walletPublicKey: expectedWalletData.walletPublicKey.toString(),
mainUtxo: expectedWalletData.utxos[0],
mainUtxo: expectedWalletData.mainUtxo,
})
})
}
Expand Down

0 comments on commit 10ab6da

Please sign in to comment.