Skip to content
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

BTC RBF issue - did not use the original utxo #4149

Open
jenbitcoin opened this issue Dec 4, 2024 · 2 comments
Open

BTC RBF issue - did not use the original utxo #4149

jenbitcoin opened this issue Dec 4, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@jenbitcoin
Copy link

jenbitcoin commented Dec 4, 2024

Describe the bug
The TrustWallet library chooses the best UTXO with bigger amount to spend. The issue happens when we want to replace a transaction. Example, the original input utxos are: [utxo1 (1041 sats), utxo2 (2084 sats)]
and if we want to replace the transaction, and the original utxos are not enough to cover the new fee, we add another utxo [utxo3 (5213 sats)]
When we send the original utxos + the new utxo to replace the transaction, the TW library chose the new utxo and disregard the original utxos. Which means, the app ended up creating a new transaction, not replacing the new one.

To Reproduce
BTC-1 sendAll amount to BTC-2 with [utxo1 (1041 sats), utxo2 (2084 sats)], but set the sequence to 4294967293.
I want to replace, but there's no more balance, so I transfer more funds (5213 sats) to BTC-1.
After I received the new funds, replace the same transaction
send the original UTXOs + additional inputUTXO
[utxo1 (1041 sats), utxo2 (2084 sats), utxo3 (5213 sats)]

Result:
The "replacement" transaction only used utxo3 (5213 sats) -- essentially creating a new transaction and not replacing the old one.

Expected behavior
Is there a way we can force the library to spend the original UTXO first to make sure the tx gets replaced.

Original Transaction:
[txId=a874b98b8b634e18739db549090a175455cd6ebed91a614540b50ce12e4285eb, txRaw=01000000028ab1d8fd56f7de55968624656ee8da22d0f5408e0f36fb144f0ce0106b6b1460000000006a47304402205976722a4cab5d00a720a179f985e95ed5eac6ccf9c664251529f8d1e0b1f76c02207605358cc6fb7bdc82d6e79741aaca76745104630152fe8fbd19e40da2fc78f5012103f854e8b00e451f78360744b72997b0ba44f1bbd9517c75d6857793f5ecabf67afdffffff508b85017432e126c79f1565186e78ed1a3fc7196f2e6ba66ae1d9ffe99aa651000000006b4830450221009f7cc41faf35402fd055ce3d95ce6e9357a966f320d6381f428f472e2ef5b27f02206874c622020551b28214f71c7f24ff712cd2dbfc76143cd9bb4ecfcb80ef2e41012103f854e8b00e451f78360744b72997b0ba44f1bbd9517c75d6857793f5ecabf67afdffffff0139080000000000001976a914f0c61800d9f659449fb00eeaba091bcd5ded578888ac00000000, blockchain=BTC]

Replaced Transaction:
[txId=b1a56c835b1fcf5bb560c191d5a161e2c72c6dd576be98de42ed0a0e58bfa416, txRaw=0100000001d2c9bb30e0365aca5a3c9a3293fee75f37d0c8f3f9b4c9e5b1cc4d52167ba106000000006b4830450221008dcbfd9875953b6c2e35c06acf6876988fba87dd71188d50f49f6784b2661d990220422f94458643fe596da9dfa1c6c0c6b6be280855d35ab28242420575fad9035e012103f854e8b00e451f78360744b72997b0ba44f1bbd9517c75d6857793f5ecabf67afdffffff0239080000000000001976a914f0c61800d9f659449fb00eeaba091bcd5ded578888ac7e090000000000001976a914c125b6c127ca853c5d4a73155f1d4ce525fbd55e88ac00000000, blockchain=BTC]

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
Add any other context about the problem here.

@jenbitcoin jenbitcoin added the bug Something isn't working label Dec 4, 2024
@jenbitcoin jenbitcoin changed the title BTC RBF issue - did not use the passed utxo BTC RBF issue - did not use the original utxo Dec 4, 2024
@satoshiotomakan
Copy link
Collaborator

Hi @jenbitcoin, you can have more control on UTXOs with BitcoinV2 protocol now.
Here's a BRC20 transaction example, but you can adopt for P2TR, P2WPKH, P2PKH transfer:

@Test
fun testSignBrc20Commit() {
// Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1
val privateKeyData = (Numeric.hexStringToByteArray("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"))
val dustSatoshis = 546.toLong()
val txId = Numeric.hexStringToByteArray("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008").reversedArray()
val privateKey = PrivateKey(privateKeyData)
val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data())
val utxo0 = BitcoinV2.Input.newBuilder()
.setOutPoint(BitcoinV2.OutPoint.newBuilder().apply {
hash = ByteString.copyFrom(txId)
vout = 1
})
.setValue(26_400)
.setSighashType(BitcoinSigHashType.ALL.value())
.setScriptBuilder(BitcoinV2.Input.InputBuilder.newBuilder().apply {
p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build()
})
val out0 = BitcoinV2.Output.newBuilder()
.setValue(7_000)
.setBuilder(BitcoinV2.Output.OutputBuilder.newBuilder().apply {
brc20Inscribe = BitcoinV2.Output.OutputBrc20Inscription.newBuilder().apply {
inscribeTo = publicKey
ticker = "oadf"
transferAmount = "20"
}.build()
})
val changeOutput = BitcoinV2.Output.newBuilder()
.setValue(16_400)
.setBuilder(BitcoinV2.Output.OutputBuilder.newBuilder().apply {
p2Wpkh = BitcoinV2.PublicKeyOrHash.newBuilder().setPubkey(publicKey).build()
})
val builder = BitcoinV2.TransactionBuilder.newBuilder()
.setVersion(BitcoinV2.TransactionVersion.V2)
.addInputs(utxo0)
.addOutputs(out0)
.addOutputs(changeOutput)
.setInputSelector(BitcoinV2.InputSelector.UseAll)
.setFixedDustThreshold(dustSatoshis)
val signingInput = BitcoinV2.SigningInput.newBuilder()
.setBuilder(builder)
.addPrivateKeys(ByteString.copyFrom(privateKeyData))
.setChainInfo(BitcoinV2.ChainInfo.newBuilder().apply {
p2PkhPrefix = 0
p2ShPrefix = 5
})
.setDangerousUseFixedSchnorrRng(true)
.build()
val legacySigningInput = Bitcoin.SigningInput.newBuilder().apply {
signingV2 = signingInput
}
val output = AnySigner.sign(legacySigningInput.build(), BITCOIN, SigningOutput.parser())
assertEquals(output.error, SigningError.OK)
assertEquals(output.signingResultV2.error, SigningError.OK)
assertEquals(Numeric.toHexString(output.signingResultV2.encoded.toByteArray()), "0x02000000000101089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e0100000000ffffffff02581b000000000000225120e8b706a97732e705e22ae7710703e7f589ed13c636324461afa443016134cc051040000000000000160014e311b8d6ddff856ce8e9a4e03bc6d4fe5050a83d02483045022100a44aa28446a9a886b378a4a65e32ad9a3108870bd725dc6105160bed4f317097022069e9de36422e4ce2e42b39884aa5f626f8f94194d1013007d5a1ea9220a06dce0121030f209b6ada5edb42c77fd2bc64ad650ae38314c8f451f3e36d80bc8e26f132cb00000000")
assertEquals(Numeric.toHexString(output.signingResultV2.txid.toByteArray()), "0x797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1")
}

The thing is that you can choose the UTXO selection pattern via BitcoinV2.InputSelector


It can be:

  1. BitcoinV2.InputSelector.SelectAscending - Select enough inputs in an ascending order to cover the outputs and fee.
  2. BitcoinV2.InputSelector.SelectDescending - Select enough inputs in an descending order to cover the outputs and fee.
  3. BitcoinV2.InputSelector.InOrder - Select enough inputs in the given order to cover the outputs and fee.
  4. BitcoinV2.InputSelector.UseAll - Use all the given inputs.

You can sort the UTXOs as you wish, and then use BitcoinV2.InputSelector.InOrder

@jenbitcoin
Copy link
Author

jenbitcoin commented Dec 12, 2024

thank you, I will try it out.

UPDATE:

I tried this but for some reasons, it can't find the BitcoinV2OutPoint

I tried to run this file in my local, and this is what I see. Am I missing something? 🤔

Screenshot 2024-12-12 at 5 45 29 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants