Skip to content

Conversation

Bue-von-hon
Copy link
Contributor

@Bue-von-hon Bue-von-hon commented Feb 22, 2025

Added support for creating v3 raw transaction:

  • Overloaded to include additional parameter

this resolves #31348

@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 22, 2025

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/31936.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK glozow, 1440000bytes
Approach NACK achow101
Stale ACK adamandrews1

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32966 (Silent Payments: Receiving by Eunovo)
  • #32896 (wallet, rpc: add v3 transaction creation and wallet support by ishaanam)
  • #28201 (Silent Payments: sending by josibake)
  • #21283 (Implement BIP 370 PSBTv2 by achow101)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@Bue-von-hon
Copy link
Contributor Author

@rkrux @theStack @ytrezq PTAL

@maflcko
Copy link
Member

maflcko commented Feb 22, 2025

You'll have to add tests for any new feature and make sure the tests pass

Copy link
Member

@glozow glozow 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. Thanks for your PR, but it needs to pass CI and new features need tests to be considered (please read CONTRIBUTING.md). Perhaps a functional test in rpc_rawtransaction.py.

if (!version.isNull()) {
int64_t nVersion = version.getInt<int64_t>();
if (nVersion < 0 || nVersion > TRANSACTION_VERSION_MAX)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, version out of range");
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps include what the min and max are.

Copy link
Contributor

Choose a reason for hiding this comment

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

Added variable to denote min, max value of the version.

if (nVersion < TX_MIN_STANDARD_VERSION || nVersion > TX_MAX_STANDARD_VERSION)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, version out of range(1~3)");

@@ -63,6 +63,9 @@ static constexpr int64_t VALIDATION_WEIGHT_PER_SIGOP_PASSED{50};
// How much weight budget is added to the witness size (Tapscript only, see BIP 342).
static constexpr int64_t VALIDATION_WEIGHT_OFFSET{50};

// Maximum transaction version
static const int32_t TRANSACTION_VERSION_MAX = 3;
Copy link
Member

Choose a reason for hiding this comment

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

Why aren't we using TX_MAX_STANDARD_VERSION everywhere?

Copy link
Contributor

Choose a reason for hiding this comment

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

@glozow
Are you implying that we should use the variable instead of constant values in the test code?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this seems to be redundant with TX_MAX_STANDARD_VERSION. It also appears to be unused, did you maybe forget to delete it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed it.

@@ -154,6 +154,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
rawTx.nLockTime = nLockTime;
}

if (!version.isNull()) {
int64_t nVersion = version.getInt<int64_t>();
Copy link
Member

Choose a reason for hiding this comment

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

Why int64_t? version has type uint32_t.

Copy link
Contributor

Choose a reason for hiding this comment

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

@glozow
Thanks for the feedback. int64_t was my bad. However version field of raw transaction seems to be int32_t type according to bitcoint developer doc.. The doc explicitly states that the field is signed. Should I used unsigned instead?

Choose a reason for hiding this comment

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

it's weird that it's signed, but I think we should use the same type as the docs

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use uint32_t because that is what is used in both CTransaction and CMutableTransaction.

const uint32_t version;

uint32_t version;

Those docs might be outdated because this change was done recently: #29325

Copy link
Contributor

Choose a reason for hiding this comment

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

These docs are indeed outdated, last update was 4 years back: https://github.com/bitcoin-dot-org/developer.bitcoin.org. Only the code in this repo should be treated as source of truth.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

replaced it.

@@ -122,6 +122,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createrawtransaction", 1, "outputs" },
{ "createrawtransaction", 2, "locktime" },
{ "createrawtransaction", 3, "replaceable" },
{ "createrawtransaction", 4, "version" },
Copy link
Member

Choose a reason for hiding this comment

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

Probably best not to have this be a positional parameter.

Copy link
Contributor

Choose a reason for hiding this comment

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

@luke-jr
We had to add it because without it, RPC method vaildation does not pass.

Copy link
Member

Choose a reason for hiding this comment

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

No, the issue isn't this one line of code, but the overall addition of the parameter

Choose a reason for hiding this comment

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

I agree think it's a bit too important for a 4th positional arg

Copy link
Contributor

Choose a reason for hiding this comment

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

@luke-jr @adamandrews1
So should I remove the line? If I should, could you advise which code should be updated to avoid failure of the aforementioned validation?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am inclining towards not adding a positional argument for this. Mostly because this is not something the user would use frequently and instead would let the defaults take over. I think using an optional options object here (used in many RPCs) would suffice for this use case.
@glozow WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Related, see #31936 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

This is needed as the arg is non-string type. My understanding is that @luke-jr is concept nacking the PR. I don't really understand the comments about the positionalness of this arg?

@Bue-von-hon Bue-von-hon force-pushed the create-v3-rawtransaction branch from 8a4348d to db71e16 Compare March 1, 2025 07:07
@chungeun-choi
Copy link
Contributor

@glozow @luke-jr PTAL

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.

Thank you for working on this, please mention in the PR description that it closes the issue #31348.

Comment on lines 262 to 266
assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 2, 'foo')

# Test `createrawtransaction` invalid version parameters
assert_raises_rpc_error(-8, "Invalid parameter, version out of range(0~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, -1)
assert_raises_rpc_error(-8, "Invalid parameter, version out of range(0~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, 4)
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO we should add a test wherein a v3 transaction is successfully created, signed, and broadcast to the network. ATM, only the erroneous cases are covered.

Copy link
Contributor

Choose a reason for hiding this comment

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

@rkrux
Utilized existing raw_multisig_transaction_legacy_tests but with version 3 raw transaction. This seems to cover all the domain you specified. Could you pls take a look?

self.raw_multisig_transaction_legacy_tests(3)

Copy link
Contributor

Choose a reason for hiding this comment

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

@dongwook-chan
Copy link
Contributor

dongwook-chan commented Mar 1, 2025

@glozow @luke-jr @rkrux

While updating the code, I noticed a couple of potential improvements.

  1. No range validation tests for locktime argument in createrawtransaction RPC.
    I added range validation tests for new version argument. Should we extend this to locktime as well?

  2. createpsbt RPC still creates only v2 raw transactions even though it shares same RPCHelpMan with createrawtransaction.
    Should it support v3 raw transactions too?

@DrahtBot DrahtBot removed the CI failed label Mar 1, 2025
@rkrux
Copy link
Contributor

rkrux commented Mar 1, 2025

Re: #31936 (comment)

  1. There seems to be couple functional tests related to locktime out of range here:

    assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1)
    assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, 4294967296)

  2. Good point, I think it should but in a separate PR because along with it, we'd need to ensure that other PSBT related RPCs such as walletcreatefundedpsbt among others work with v3 transactions as well. There's a PSBT_GLOBAL_TX_VERSION field as well in the PSBT that might need to be set to 3, so I believe best to be in a separate PR.

This above point makes me realise that related to my previous comment #31936 (comment), we can add a more comprehensive functional test for raw transaction related RPCs in this PR that creates a v3 raw transaction createrawtransaction, funds a raw transaction fundrawtransaction, decodes it decoderawtransaction, signs it signrawtransactionwithwallet, and then sends it sendrawtransaction. This would ensure that the integration of v3 with all the related transaction RPCs gel together.

@rkrux
Copy link
Contributor

rkrux commented Mar 1, 2025

There's a PSBT_GLOBAL_TX_VERSION field as well in the PSBT that might need to be set to 3

An update to my previous comment: #31936 (comment)

This can only done when PSBT V2 is implemented #21283, so no need for this at the moment.

@1440000bytes

This comment was marked as abuse.

@adamandrews1
Copy link

adamandrews1 commented Mar 6, 2025

Concept ACK
Note: May need to revisit #31298 after this merges

@@ -54,5 +54,6 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);

/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
Copy link
Member

Choose a reason for hiding this comment

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

not sure about default args. It would be better to explicitly enumerate the call sites and just put the default there, if needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment outdated? I don't understand where are the default args here that are referred to in the above comment.

Copy link
Member

Choose a reason for hiding this comment

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

Is this comment outdated? I don't understand where are the default args here that are referred to in the above comment.

In C++, function overloading can be used as a tool to set default values for function parameters. This is done here, by setting the default value in the function implementation, as opposed to in the function signature.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see this comment referred to the new implementation of this older function caused by the introduction of function overloading here. Yeah, I am not in favour of this as well, seem unnecessary for just a version param, the function signature defaults can suffice.

Copy link
Member

Choose a reason for hiding this comment

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

I was trying to say in #31936 (comment) that a default doesn't make sense at all here (in any way), because:

  • Every call site that omits the default has the same fallback, so changing the default in the future will make review harder.
  • Every call site needs to document the default in the RPC help anyway, so simply using the default from the docs is self-consistent and self-documenting and avoids the need for a default here

Finally, default values in C++ function signatures or function overloads are problematic, when more defaults are added in the future, because there is no way to skip over one default value and only specify the other one. Modern languages, such as Rust don't support this at all, or Python allows for named args.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, I like these points quite a lot. Summarising below, please correct me if I misunderstand:

We can avoid using defaults here completely by letting the corresponding version default trickle down from the RPC help into these functions here.

As I checked in the call sites of CreateTransaction, this^ requires other transaction creation RPCs to accept a version param as well such as createpsbt, walletcreatefundedpsbt, send, sendall, which seems consistent with an earlier suggestion here #31936 (comment).

This increases the scope of the PR a bit but I feel it's worth doing these changes as these RPCs are quite related to each other in terms of functionality. I can correspondingly update the issue description as well.

This also helps to make a case for using the options argument in a previously open ended question here #31936 (comment) because most of the above mentioned RPCs accept an option param wherein the version option can be added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is truly amazing content. I wrote a blog post on this topic.

@@ -122,6 +122,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createrawtransaction", 1, "outputs" },
{ "createrawtransaction", 2, "locktime" },
{ "createrawtransaction", 3, "replaceable" },
{ "createrawtransaction", 4, "version" },
Copy link
Contributor

Choose a reason for hiding this comment

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

I am inclining towards not adding a positional argument for this. Mostly because this is not something the user would use frequently and instead would let the defaults take over. I think using an optional options object here (used in many RPCs) would suffice for this use case.
@glozow WDYT?

@@ -154,6 +154,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
rawTx.nLockTime = nLockTime;
}

if (!version.isNull()) {
int64_t nVersion = version.getInt<int64_t>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use uint32_t because that is what is used in both CTransaction and CMutableTransaction.

const uint32_t version;

uint32_t version;

Those docs might be outdated because this change was done recently: #29325

@@ -491,7 +496,7 @@ def transaction_version_number_tests(self):
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0xffffffff)

def raw_multisig_transaction_legacy_tests(self):
def raw_multisig_transaction_legacy_tests(self, version=2):
Copy link
Contributor

Choose a reason for hiding this comment

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

Although this works but there seems to be a note here regarding legacy wallets, which are being deprecated. I think a more suitable place could be in the getrawtransaction_verbosity_tests that use create_self_transfer(), which already accepts a version argument in the tests - just need to pass down 3.

Also, testing the value of the transaction version after being mined would complete the all-round testing of this functionality for which only adding a new field for version in the getrawtransaction_verbosity_tests subtest should suffice here:

@@ -180,6 +181,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createpsbt", 1, "outputs" },
{ "createpsbt", 2, "locktime" },
{ "createpsbt", 3, "replaceable" },
{ "createpsbt", 4, "version" },
Copy link
Contributor

Choose a reason for hiding this comment

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

If accepting a new argument can't be avoided for this PSBT RPC because of the same CreateTxDoc() being reused underneath, then I think we can explore updating functional tests for PSBT in this PR itself because createpsbt RPC uses the same ConstructTransaction() as well.

rpc_psbt.py is the file that would need to be updated. Using a similar approach like done in the rpc_rawtransaction would be preferred that is reusing bunch of the existing testing methods.

@@ -54,5 +54,6 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);

/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment outdated? I don't understand where are the default args here that are referred to in the above comment.

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.

Answering open comments.

@@ -54,5 +54,6 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);

/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, I like these points quite a lot. Summarising below, please correct me if I misunderstand:

We can avoid using defaults here completely by letting the corresponding version default trickle down from the RPC help into these functions here.

As I checked in the call sites of CreateTransaction, this^ requires other transaction creation RPCs to accept a version param as well such as createpsbt, walletcreatefundedpsbt, send, sendall, which seems consistent with an earlier suggestion here #31936 (comment).

This increases the scope of the PR a bit but I feel it's worth doing these changes as these RPCs are quite related to each other in terms of functionality. I can correspondingly update the issue description as well.

This also helps to make a case for using the options argument in a previously open ended question here #31936 (comment) because most of the above mentioned RPCs accept an option param wherein the version option can be added.

@maflcko
Copy link
Member

maflcko commented Mar 31, 2025

Are you still working on this?

@Bue-von-hon
Copy link
Contributor Author

Are you still working on this?

sure!

@@ -63,6 +63,9 @@ static constexpr int64_t VALIDATION_WEIGHT_PER_SIGOP_PASSED{50};
// How much weight budget is added to the witness size (Tapscript only, see BIP 342).
static constexpr int64_t VALIDATION_WEIGHT_OFFSET{50};

// Maximum transaction version
static const int32_t TRANSACTION_VERSION_MAX = 3;
Copy link
Member

Choose a reason for hiding this comment

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

Yes, this seems to be redundant with TX_MAX_STANDARD_VERSION. It also appears to be unused, did you maybe forget to delete it?

@@ -144,6 +144,7 @@ std::vector<uint32_t> GetDust(const CTransaction& tx, CFeeRate dust_relay_rate);
// Changing the default transaction version requires a two step process: first
// adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later
// allowing the new transaction version in the wallet/RPC.
static constexpr decltype(CTransaction::version) TX_MIN_STANDARD_VERSION{1};
Copy link
Member

Choose a reason for hiding this comment

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

concept ack introducing this constant. I think it would be best to have it in a separate commit and replace the magic number in policy.cpp with this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure about the meaning of 'separate commit,' but I replaced the magic number in the policy.cpp file with a constant value.

Copy link
Member

Choose a reason for hiding this comment

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

I mean putting the changes assigning 1 to this constant in one commit, and then the version parameter addition in a second commit.

@@ -158,6 +158,7 @@ static std::vector<RPCArg> CreateTxDoc()
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
{"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "Marks this transaction as BIP125-replaceable.\n"
"Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
{"version", RPCArg::Type::NUM, RPCArg::Default{2}, "Transaction version"},
Copy link
Member

Choose a reason for hiding this comment

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

Should use CURRENT_VERSION instead of magic number

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fix it.

@@ -122,6 +122,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createrawtransaction", 1, "outputs" },
{ "createrawtransaction", 2, "locktime" },
{ "createrawtransaction", 3, "replaceable" },
{ "createrawtransaction", 4, "version" },
Copy link
Member

Choose a reason for hiding this comment

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

This is needed as the arg is non-string type. My understanding is that @luke-jr is concept nacking the PR. I don't really understand the comments about the positionalness of this arg?

@fanquake fanquake marked this pull request as draft April 1, 2025 11:21
@Bue-von-hon Bue-von-hon force-pushed the create-v3-rawtransaction branch from 1121cb7 to 3722e10 Compare April 5, 2025 11:34
Bue-von-hon and others added 2 commits April 26, 2025 20:43
Added support for creating v3 raw transaction:
- Overloaded  to include additional parameter

Co-authored-by: chungeun-choi <cucuridas@gmail.com>
Co-authored-by: dongwook-chan <dongwook.chan@gmail.com>
Co-authored-by: sean-k1 <uhs2000@naver.com>
@Bue-von-hon Bue-von-hon force-pushed the create-v3-rawtransaction branch from d3e3ea7 to a43237d Compare April 26, 2025 12:06
@Bue-von-hon Bue-von-hon marked this pull request as ready for review April 26, 2025 12:29
@Bue-von-hon
Copy link
Contributor Author

@Bue-von-hon
Copy link
Contributor Author

Copy link

@adamandrews1 adamandrews1 left a comment

Choose a reason for hiding this comment

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

utACK 63d9b9e

Suggested some improvements, but not blocking ones IMO

if (!version.isNull()) {
uint32_t nVersion = version.getInt<uint32_t>();
if (nVersion < TX_MIN_STANDARD_VERSION || nVersion > TX_MAX_STANDARD_VERSION)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, version out of range(1~3)");

Choose a reason for hiding this comment

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

This RPC error should be a formatted string so that it remains accurate as TX_MIN_STANDARD_VERSION and TX_MAX_STANDARD_VERSION get updated.


# Test `createrawtransaction` invalid version parameters
assert_raises_rpc_error(-8, "Invalid parameter, version out of range(1~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, 0)
assert_raises_rpc_error(-8, "Invalid parameter, version out of range(1~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, 4)

Choose a reason for hiding this comment

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

The final parameter (tx version) for this test should be set to TX_MAX_STANDARD_VERSION + 1 or at least to a number with headroom like 999.

Comment on lines 342 to 344
rawtx_v3 = self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)]), version=3)
tx = tx_from_hex(rawtx_v3)
assert_equal(tx.version, 3)

Choose a reason for hiding this comment

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

Shouldn't this test be repeated for each valid tx.version in the range [TX_MIN_STANDARD_VERSION, TX_MAX_STANDARD_VERSION]?

@DrahtBot DrahtBot requested a review from glozow May 1, 2025 16:57
@achow101
Copy link
Member

achow101 commented May 3, 2025

Approach NACK

While I agree that we should support v3 transactions, I think this implementation is incomplete without any v3 support in the wallet's coin selection. As it is now, this appears to enable a massive footgun - users can use createrawtransaction to create a v3 transaction, fund it with fundrawtransaction, and then wind up with a v3 transaction that they cannot broadcast. This is because the topological restrictions of v3 transactions are not being taken into account during coin selection, it is possible that the wallet will select inputs which are incompatible with a v3 transaction, and therefore produce a transaction that won't be broadcast.

I expect that supporting sending v3 transactions from Bitcoin Core will actual be a significantly more involved project. While I appreciate your enthusiasm, I think it would be better to leave the implementation of this feature to experienced contributors.

@Bue-von-hon
Copy link
Contributor Author

@adamandrews1 @achow101
As per the comments, we'll hand over the task to a more suitable developer.
Instead, we plan to proceed with issue-26756 (we're a developer group based in South-Korea).
This issue involves aligning the behavior of the AvailableCoins method with the getbalance method, which seems well-suited to our expertise.
If you believe this issue isn’t appropriate for us, we’d appreciate it if you could suggest alternative issues.

P.S. We’ve incorporated all code review feedback. Please review when convenient.

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Task lint: https://github.com/bitcoin/bitcoin/runs/41977639266
LLM reason (✨ experimental): The CI failure is due to the lint-locale-dependence.py check detecting the use of std::to_string.

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.

@maflcko
Copy link
Member

maflcko commented May 20, 2025

users can use createrawtransaction to create a v3 transaction, fund it with fundrawtransaction, and then wind up with a v3 transaction that they cannot broadcast.

createrawtransaction doesn't do other policy checks either, such as OP_RETURN checks, so one could argue this is fine as well.

Though, CI is failing on this pull.

@achow101
Copy link
Member

createrawtransaction doesn't do other policy checks either, such as OP_RETURN checks, so one could argue this is fine as well.

fundrawtransaction does though, and that was my point. This PR doesn't touch fundrawtransaction (or rather the wallet's coin selection code) and hence this is incomplete.

@maflcko
Copy link
Member

maflcko commented May 21, 2025

createrawtransaction doesn't do other policy checks either, such as OP_RETURN checks, so one could argue this is fine as well.

fundrawtransaction does though, and that was my point.

I don't think fundrawtransaction checks for OP_RETURN. At least locally, the following passes for me:

createrawtransaction '[]' '[{"data":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]'

fundrawtransaction 02000000000100000000000000007c6a4c79aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000 '{"fee_rate":"1.2"}'

The following sendrawtransaction fails with scriptpubkey (code -26).

Co-authored-by: chungeun-choi <cucuridas@gmail.com>
Co-authored-by: sean-k1 <uhs2000@naver.com>
@sean-k1
Copy link
Contributor

sean-k1 commented May 31, 2025

@achow101 @maflcko
we fixed the lint error.
There seems to be a lot of discussion, let me know when it's clear.

@sean-k1
Copy link
Contributor

sean-k1 commented Jul 8, 2025

@achow101 @maflcko
PTAL

@maflcko
Copy link
Member

maflcko commented Jul 8, 2025

ref #32896

@maflcko
Copy link
Member

maflcko commented Jul 8, 2025

PTAL

Not sure what you are asking for. Feedback has been provided, and the burden to address it and reply to it is on the pull request author. I can't make it go away by looking at it more.

Apart from the prior feedback, you'd also have to squash your commits according to https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#squashing-commits into atomic units (https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#committing-patches)

@maflcko
Copy link
Member

maflcko commented Jul 14, 2025

Closing for now, due to inactivity. The given feedback hasn't bee addressed over a long time and at this point it may be best to focus review on #32896.

@maflcko maflcko closed this Jul 14, 2025
glozow added a commit that referenced this pull request Aug 19, 2025
…port

5c8bf7b doc: add release notes for version 3 transactions (ishaanam)
4ef8065 test: add truc wallet tests (ishaanam)
5d932e1 test: extract `bulk_vout` from `bulk_tx` so it can be used by wallet tests (ishaanam)
2cb473d rpc: Support version 3 transaction creation (Bue-von-hon)
4c20343 rpc: Add transaction min standard version parameter (Bue-von-hon)
c5a2d08 wallet: don't return utxos from multiple truc txs in AvailableCoins (ishaanam)
da8748a wallet: limit v3 tx weight in coin selection (ishaanam)
85c5410 wallet: mark unconfirmed v3 siblings as mempool conflicts (ishaanam)
0804fc3 wallet: throw error at conflicting tx versions in pre-selected inputs (ishaanam)
cc15522 wallet: set m_version in coin control to default value (ishaanam)
2e96176  wallet: don't include unconfirmed v3 txs with children in available coins (ishaanam)
ec2676b wallet: unconfirmed ancestors and descendants are always truc (ishaanam)

Pull request description:

  This PR Implements the following:
  - If creating a v3 transaction, `AvailableCoins` doesn't return unconfirmed v2 utxos (and vice versa)
  - `AvailableCoins` doesn't return an unconfirmed v3 utxo if its transaction already has a child
  - If a v3 transaction is kicked out of the mempool by a sibling, mark the sibling as a mempool conflict
  - Throw an error if pre-selected inputs are of the wrong transaction version
  - Allow setting version to 3 manually in `createrawtransaction` (uses commits from #31936)
  - Limits a v3 transaction weight in coin selection

  Closes #31348

  To-Do:
  - [x] Test a v3 sibling conflict kicking out one of our transactions from the mempool
  - [x] Implement separate size limit for TRUC children
  - [x] Test that we can't fund a v2 transaction when everything is v3 unconfirmed
  - [x] Test a v3 sibling conflict being removed from the mempool
  - [x] Test limiting v3 transaction weight in coin selection
  - [x] Simplify tests
  - [x] Add documentation
  - [x] Test that user-input max weight is not overwritten by truc max weight
  - [x] Test v3 in RPCs other than `createrawtransaction`

ACKs for top commit:
  glozow:
    reACK 5c8bf7b
  achow101:
    ACK 5c8bf7b
  rkrux:
    ACK 5c8bf7b

Tree-SHA512: da8aea51c113e193dd0b442eff765bd6b8dc0e5066272d3e52190a223c903f48788795f32c554f268af0d2607b5b8c3985c648879cb176c65540837c05d0abb5
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.

Add support for creating v3 raw transactions in createrawtransaction RPC