Added support for creating v3 raw transaction:
- Overloaded to include additional parameter
this resolves #31348
Added support for creating v3 raw transaction:
this resolves #31348
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31936.
See the guideline for information on the review process.
Type | Reviewers |
---|---|
Concept ACK | glozow, 1440000bytes, adamandrews1 |
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Reviewers, this pull request conflicts with the following ones:
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.
153@@ -154,6 +154,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
154 rawTx.nLockTime = nLockTime;
155 }
156
157+ if (!version.isNull()) {
158+ int64_t nVersion = version.getInt<int64_t>();
159+ if (nVersion < 0 || nVersion > TRANSACTION_VERSION_MAX)
160+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, version out of range");
62@@ -63,6 +63,9 @@ static constexpr int64_t VALIDATION_WEIGHT_PER_SIGOP_PASSED{50};
63 // How much weight budget is added to the witness size (Tapscript only, see BIP 342).
64 static constexpr int64_t VALIDATION_WEIGHT_OFFSET{50};
65
66+// Maximum transaction version
67+static const int32_t TRANSACTION_VERSION_MAX = 3;
TX_MAX_STANDARD_VERSION
everywhere?
153@@ -154,6 +154,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
154 rawTx.nLockTime = nLockTime;
155 }
156
157+ if (!version.isNull()) {
158+ int64_t nVersion = version.getInt<int64_t>();
int64_t
? version
has type uint32_t
.
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?
Let’s use uint32_t
because that is what is used in both CTransaction
and CMutableTransaction
.
https://github.com/bitcoin/bitcoin/blob/72c150dfe7619c2b6e2fd06a1fa431b545a3a225/src/primitives/transaction.h#L308
Those docs might be outdated because this change was done recently: https://github.com/bitcoin/bitcoin/pull/29325
121@@ -122,6 +122,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
122 { "createrawtransaction", 1, "outputs" },
123 { "createrawtransaction", 2, "locktime" },
124 { "createrawtransaction", 3, "replaceable" },
125+ { "createrawtransaction", 4, "version" },
options
object here (used in many RPCs) would suffice for this use case.
@glozow WDYT?
258@@ -259,7 +259,11 @@ def createrawtransaction_tests(self):
259 assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [])
260
261 # Test `createrawtransaction` invalid extra parameters
262- assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo')
263+ assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 2, 'foo')
264+
265+ # Test `createrawtransaction` invalid version parameters
266+ assert_raises_rpc_error(-8, "Invalid parameter, version out of range(0~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, -1)
267+ assert_raises_rpc_error(-8, "Invalid parameter, version out of range(0~3)", self.nodes[0].createrawtransaction, [], {}, 0, False, 4)
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?
https://github.com/bitcoin/bitcoin/blob/1121cb7a91e0dfa4f247273bf89e2e1b47c82628/test/functional/rpc_rawtransaction.py#L96
While updating the code, I noticed a couple of potential improvements.
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?
createpsbt
RPC still creates only v2 raw transactions even though it shares same RPCHelpMan
with createrawtransaction
.
Should it support v3 raw transactions too?
Re: #31936 (comment)
There seems to be couple functional tests related to locktime out of range here: https://github.com/bitcoin/bitcoin/blob/3c1f72a36700271c7c1293383549c3be29f28edb/test/functional/rpc_rawtransaction.py#L312-L313
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 (review), 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.
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.
153@@ -154,6 +154,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
154 rawTx.nLockTime = nLockTime;
155 }
156
157+ if (!version.isNull()) {
158+ int64_t nVersion = version.getInt<int64_t>();
159+ if (nVersion < 0 || nVersion > TX_MAX_STANDARD_VERSION)
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>
53@@ -54,5 +54,6 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in);
54
55 /** Create a transaction from univalue parameters */
56 CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf);
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.
version
param, the function signature defaults can suffice.
I was trying to say in #31936 (review) that a default doesn’t make sense at all here (in any way), because:
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.
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 (review).
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 (review) because most of the above mentioned RPCs accept an option
param wherein the version
option can be added.
495@@ -491,7 +496,7 @@ def transaction_version_number_tests(self):
496 decrawtx = self.nodes[0].decoderawtransaction(rawtx)
497 assert_equal(decrawtx['version'], 0xffffffff)
498
499- def raw_multisig_transaction_legacy_tests(self):
500+ def raw_multisig_transaction_legacy_tests(self, version=2):
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: https://github.com/bitcoin/bitcoin/blob/199d47d9629b603d5a23b96c5260c68add00ccb3/test/functional/rpc_rawtransaction.py#L187
180@@ -180,6 +181,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
181 { "createpsbt", 1, "outputs" },
182 { "createpsbt", 2, "locktime" },
183 { "createpsbt", 3, "replaceable" },
184+ { "createpsbt", 4, "version" },
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.
Are you still working on this?
sure!