Skip to content

Conversation

@adamandrews1
Copy link

Previously, combinerawtransaction would silently return the first tx when asked to combine unrelated txs. Now, it will check tx mergeability and throws a descriptive error if tx cannot be merged.

fixes #25980

@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 15, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31298.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK zaidmstrr, theStack, rkrux
Stale ACK 1440000bytes, naiyoma, yuvicc, mossein

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

No conflicts as of last run.

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/33065613881

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@adamandrews1 adamandrews1 marked this pull request as draft November 15, 2024 22:03
@adamandrews1 adamandrews1 force-pushed the combinerawtransaction-check branch 2 times, most recently from 9b24f38 to 192a50b Compare November 15, 2024 22:46
@adamandrews1 adamandrews1 force-pushed the combinerawtransaction-check branch from 192a50b to f19a815 Compare November 20, 2024 02:02
@adamandrews1 adamandrews1 marked this pull request as ready for review November 20, 2024 02:03
@adamandrews1
Copy link
Author

It seems like, with #31249, I should migrate these test changes as well. WIP

@1440000bytes

This comment was marked as abuse.

@adamandrews1
Copy link
Author

Can this use the same approach and error message as combinepsbt?

This is the same functional approach and error message already.
Creating a PSBT involves stripping the scriptSig and scriptWitnesses. Later, the hashes are compared. That's exactly what this change is doing too but the syntax is different. I can align the syntax to make it more clear it is doing the same thing.

@adamandrews1 adamandrews1 force-pushed the combinerawtransaction-check branch from f19a815 to 85c2168 Compare November 20, 2024 23:44
@1440000bytes

This comment was marked as abuse.

Copy link

@1440000bytes 1440000bytes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 85c2168

Copy link
Contributor

@yuvicc yuvicc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK 85c216871a01aab003e1b1aba6246b4178afc929

Below is my full test setup:

Tested on Regtest mode

master@d2136d32bb4764e4035fb892d979c27f037fad93

  • When checked with only 1 raw txn, returns only the txn (no error for missing txns)
  • When checked for mergeability:
    • returns only the first txn when unrelated txns were provided
    • doesn't show descriptive error for any mergeability issue

combinerawtransaction-check@85c216871a01aab003e1b1aba6246b4178afc929

  • When only 1 raw txn provided it returns missing inputs
  • checking for mergeability:
    • shows descriptive error for mergeability issue
      for e.g.
error code: -8
error message:
Transaction 1 not compatible (different transactions)

Gives combine hex when same txns are provided

./build/src/bitcoin-cli combinerawtransaction '["0200000001a0d2e799d40d404da2daf128fc678fdd15f245bdd92c6e1ad15b45f7258a47b90000000000fdffffff01008c86470000000016001483f259c98962117d75154f35cb55cd60b88a257e00000000","0200000001a0d2e799d40d404da2daf128fc678fdd15f245bdd92c6e1ad15b45f7258a47b90000000000fdffffff01008c86470000000016001483f259c98962117d75154f35cb55cd60b88a257e00000000"]'
0200000001a0d2e799d40d404da2daf128fc678fdd15f245bdd92c6e1ad15b45f7258a47b90000000000fdffffff01008c86470000000016001483f259c98962117d75154f35cb55cd60b88a257e00000000

for inputs already spent:

error code: -25
error message:
Input not found or already spent

Note: newly created wallet and txns were used during the test using createrawtransaction rpc in regtest mode

Also, the functional test LGTM:

Temporary test directory at /var/folders/jb/wlbrz0t95vl58wzxjt75wqmh0000gn/T/test_runner_₿_🏃_20241219_004940
Remaining jobs: [rpc_createmultisig.py]
1/1 - rpc_createmultisig.py passed, Duration: 3 s

TEST                  | STATUS    | DURATION

rpc_createmultisig.py | ✓ Passed  | 3 s

ALL                   | ✓ Passed  | 3 s (accumulated) 
Runtime: 3 s

@zaidmstrr
Copy link
Contributor

Concept ACK

Copy link
Contributor

@naiyoma naiyoma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK 9d31e94

I have tested the changes on regtest and got expected output

bitcoin-cli -regtest combinerawtransaction "[\"$RAW_TX1\", \"$RAW_TX2\"]"
error code: -8
error message:
Transaction 1 not compatible (different transactions)

Copy link
Contributor

@yuvicc yuvicc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-ACK 9d31e94bf584e12269683b74b1c96c7ac883a760

@achow101
Copy link
Member

Are you still working on this?

@adamandrews1 adamandrews1 force-pushed the combinerawtransaction-check branch from 4e31bbe to a26e9fd Compare September 16, 2025 03:32
@adamandrews1
Copy link
Author

@achow101 I've rebased this and updated the comment. No other changes.

Copy link
Contributor

@theStack theStack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

Copy link
Contributor

@rkrux rkrux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK a26e9fd5b5908853d37b81915f5e12a5ad822f86

Thanks for adding the fix.

@mossein
Copy link

mossein commented Dec 6, 2025

Tested ACK a26e9fd5b5908853d37b81915f5e12a5ad822f86

Built on macOS, ran
rpc_createmultisig.py

  • passes. The approach of stripping scriptSigs/scriptWitnesses and comparing txids is clean.

Agree with the minor nits from other reviewers (style changes, error message verbosity).

@adamandrews1
Copy link
Author

Are you still working on this?

Yes, I will produce an additional patch addressing all comments in the coming days.

@adamandrews1 adamandrews1 force-pushed the combinerawtransaction-check branch from a26e9fd to a516fe8 Compare December 8, 2025 15:00
@adamandrews1 adamandrews1 requested a review from achow101 December 8, 2025 15:02
@adamandrews1
Copy link
Author

I've addressed all nits and rebased. Thank you to each of you who left thoughtful commentary.

Copy link
Contributor

@rkrux rkrux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looking quite good.

I noticed that these new cases are added within the do_multisig function that is called several times here:

m_of_n = [(2, 3), (3, 3), (2, 5), (3, 5), (10, 15), (15, 15)]
for (sigs, keys) in m_of_n:
for output_type in ["bech32", "p2sh-segwit", "legacy"]:
self.do_multisig(keys, sigs, output_type)

Given the nature of the bug regarding the un-mergeable transactions, I feel testing this once should be sufficient in which case we can consider adding these cases in a separate function that should help in avoiding testing the fix multiple times. WDYT? (I have not formed a strong opinion on this though.)

Previously, combinerawtransaction would silently return the first tx when
asked to combine unrelated txs. Now, it will check tx mergeability and
throws a descriptive error if tx cannot be merged.
@adamandrews1
Copy link
Author

adamandrews1 commented Dec 29, 2025

Overall looking quite good.

I noticed that these new cases are added within the do_multisig function that is called several times here:

m_of_n = [(2, 3), (3, 3), (2, 5), (3, 5), (10, 15), (15, 15)]
for (sigs, keys) in m_of_n:
for output_type in ["bech32", "p2sh-segwit", "legacy"]:
self.do_multisig(keys, sigs, output_type)

Given the nature of the bug regarding the un-mergeable transactions, I feel testing this once should be sufficient in which case we can consider adding these cases in a separate function that should help in avoiding testing the fix multiple times. WDYT? (I have not formed a strong opinion on this though.)

The tx mergeability constraints are invariant across output_type and n_sig etc, it should be sufficient to test them one time explicitly and disable during all other runs. Probably not a huge runtime cost as-is but there is definitely a fan-out that would adds additional overhead to all callers of do_multisig so got rid of that. Also integrated all other feedback.

@adamandrews1 adamandrews1 force-pushed the combinerawtransaction-check branch from a516fe8 to e3069ec Compare December 29, 2025 18:15
Copy link
Contributor

@rkrux rkrux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tx mergeability constraints are invariant across output_type and n_sig etc, it should be sufficient to test them one time explicitly and disable during all other runs. Probably not a huge runtime cost as-is but there is definitely a fan-out that would adds additional overhead to all callers of do_multisig so got rid of that. Also integrated all other feedback.

Thanks for addressing all the feedback! I had a different approach in mind that creates a separate function for these checks but the latest implementation also looks fine to me.

Code is lgtm e3069ec

Shared 2 nits that could be addressed at the moment I believe.

Comment on lines +625 to +634
Txid txid{};
for (unsigned int k{0}; k < tx_variants_copy.size(); ++k) {
// Remove all scriptSigs and scriptWitnesses from inputs
for (CTxIn& input : tx_variants_copy[k].vin) {
input.scriptSig.clear();
input.scriptWitness.SetNull();
}
if (k == 0) {
txid = tx_variants_copy[k].GetHash();
} else if (txid != tx_variants_copy[k].GetHash()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - this is set only once for the first transaction:

diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 1de4d200e8..d0038f25ad 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -622,7 +622,7 @@ static RPCHelpMan combinerawtransaction()
 
     { // Test Tx relation for mergeability. Strip scriptSigs and scriptWitnesses to facilitate txId comparison
         std::vector<CMutableTransaction> tx_variants_copy(txVariants);
-        Txid txid{};
+        Txid first_txid{};
         for (unsigned int k{0}; k < tx_variants_copy.size(); ++k) {
             // Remove all scriptSigs and scriptWitnesses from inputs
             for (CTxIn& input : tx_variants_copy[k].vin) {
@@ -630,8 +630,8 @@ static RPCHelpMan combinerawtransaction()
                 input.scriptWitness.SetNull();
             }
             if (k == 0) {
-                txid = tx_variants_copy[k].GetHash();
-            } else if (txid != tx_variants_copy[k].GetHash()) {
+                first_txid = tx_variants_copy[k].GetHash();
+            } else if (first_txid != tx_variants_copy[k].GetHash()) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction number %d not compatible with first transaction", k+1));
             }
         }

out_addr2 = getnewdestination('bech32')[2]
unmergeable_transaction_args = [
([[{"txid": tx["txid"], "vout": tx["sent_vout"]}], [{out_addr: outval * 2}]], {}), # similar transaction but with different amount to send
([[{"txid": tx["txid"], "vout": tx["sent_vout"]}], [{out_addr: outval}]], {"locktime": 0, "replaceable": True, "version": 1}), # similar transaction but with different version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because non-positional arguments are used here for version, rest 2 (locktime and replaceable) are not required:

diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index fa0f668922..199193cbbd 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -162,7 +162,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
             out_addr2 = getnewdestination('bech32')[2]
             unmergeable_transaction_args = [
                 ([[{"txid": tx["txid"], "vout": tx["sent_vout"]}], [{out_addr: outval * 2}]], {}), # similar transaction but with different amount to send
-                ([[{"txid": tx["txid"], "vout": tx["sent_vout"]}], [{out_addr: outval}]], {"locktime": 0, "replaceable": True, "version": 1}), # similar transaction but with different version
+                ([[{"txid": tx["txid"], "vout": tx["sent_vout"]}], [{out_addr: outval}]], {"version": 1}), # similar transaction but with different version
                 ([[{"txid": tx["txid"], "vout": tx["sent_vout"]}], [{out_addr: outval}]], {"locktime": 1}), # similar transaction but with different locktime
                 ([[{"txid": tx["txid"], "vout": tx["sent_vout"]}], [{out_addr2: outval}]], {}), # similar transaction but with different output address (scriptPubKey)
                 ([[{"txid": tx["txid"], "vout": tx["sent_vout"], "sequence": 1}], [{out_addr: outval}]], {}), # similar transaction but with different sequence number
(END)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

combinerawtransaction confusing with distinct transactions