From 1121cb7a91e0dfa4f247273bf89e2e1b47c82628 Mon Sep 17 00:00:00 2001 From: Bue-von-hon Date: Sat, 22 Feb 2025 16:27:23 +0900 Subject: [PATCH] rpc: Support v3 raw transactions creation Added support for creating v3 raw transaction: - Overloaded to include additional parameter Co-authored-by: chungeun-choi Co-authored-by: dongwook-chan Co-authored-by: sean-k1 --- src/policy/policy.h | 1 + src/rpc/client.cpp | 2 ++ src/rpc/rawtransaction.cpp | 3 ++- src/rpc/rawtransaction_util.cpp | 14 +++++++++++++- src/rpc/rawtransaction_util.h | 1 + src/script/script.h | 3 +++ test/functional/rpc_rawtransaction.py | 13 +++++++++---- 7 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/policy/policy.h b/src/policy/policy.h index 2151ec13dd0..1a02f3f7ab5 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -144,6 +144,7 @@ std::vector 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}; static constexpr decltype(CTransaction::version) TX_MAX_STANDARD_VERSION{3}; /** diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 1b711e3c5b1..bfd034d5afe 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -122,6 +122,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createrawtransaction", 1, "outputs" }, { "createrawtransaction", 2, "locktime" }, { "createrawtransaction", 3, "replaceable" }, + { "createrawtransaction", 4, "version" }, { "decoderawtransaction", 1, "iswitness" }, { "signrawtransactionwithkey", 1, "privkeys" }, { "signrawtransactionwithkey", 2, "prevtxs" }, @@ -180,6 +181,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createpsbt", 1, "outputs" }, { "createpsbt", 2, "locktime" }, { "createpsbt", 3, "replaceable" }, + { "createpsbt", 4, "version" }, { "combinepsbt", 0, "txs"}, { "joinpsbts", 0, "txs"}, { "finalizepsbt", 1, "extract"}, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 421656152cb..70d08ce2b01 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -158,6 +158,7 @@ static std::vector 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"}, }; } @@ -433,7 +434,7 @@ static RPCHelpMan createrawtransaction() if (!request.params[3].isNull()) { rbf = request.params[3].get_bool(); } - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, request.params[4]); return EncodeHexTx(CTransaction(rawTx)); }, diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 53f943bb9e0..c773dffc46e 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -143,7 +143,7 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in) } } -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf, const UniValue& version) { CMutableTransaction rawTx; @@ -154,6 +154,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.nLockTime = nLockTime; } + if (!version.isNull()) { + int64_t nVersion = version.getInt(); + if (nVersion < TX_MIN_STANDARD_VERSION || nVersion > TX_MAX_STANDARD_VERSION) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, version out of range(1~3)"); + rawTx.version = nVersion; + } + AddInputs(rawTx, inputs_in, rbf); AddOutputs(rawTx, outputs_in); @@ -164,6 +171,11 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal return rawTx; } +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf) +{ + return ConstructTransaction(inputs_in, outputs_in, locktime, rbf, UniValue(2)); +} + /** Pushes a JSON object for script verification or signing errors to vErrorsRet. */ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::string& strMessage) { diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 40d6bbba873..ed99883b1cf 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -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 rbf); +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf, const UniValue& version); #endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H diff --git a/src/script/script.h b/src/script/script.h index f4579849803..aec6282b1e6 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -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; + template std::vector ToByteVector(const T& in) { diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 18b1fc18967..01d5157cb84 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -93,6 +93,7 @@ class RawTransactionsTest(BitcoinTestFramework): if self.is_specified_wallet_compiled() and not self.options.descriptors: self.import_deterministic_coinbase_privkeys() self.raw_multisig_transaction_legacy_tests() + self.raw_multisig_transaction_legacy_tests(3) self.getrawtransaction_verbosity_tests() @@ -259,7 +260,11 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, []) # Test `createrawtransaction` invalid extra parameters - assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo') + 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(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) # Test `createrawtransaction` invalid `inputs` assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, 'foo', {}) @@ -491,7 +496,7 @@ class RawTransactionsTest(BitcoinTestFramework): 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): self.log.info("Test raw multisig transactions (legacy)") # The traditional multisig workflow does not work with descriptor wallets so these are legacy only. # The multisig workflow with descriptor wallets uses PSBTs and is tested elsewhere, no need to do them here. @@ -552,7 +557,7 @@ class RawTransactionsTest(BitcoinTestFramework): bal = self.nodes[0].getbalance() inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "amount": vout['value']}] outputs = {self.nodes[0].getnewaddress(): 2.19} - rawTx = self.nodes[2].createrawtransaction(inputs, outputs) + rawTx = self.nodes[2].createrawtransaction(inputs, outputs, version=version) rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs) assert_equal(rawTxPartialSigned['complete'], False) # node1 only has one key, can't comp. sign the tx @@ -591,7 +596,7 @@ class RawTransactionsTest(BitcoinTestFramework): bal = self.nodes[0].getbalance() inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "redeemScript": mSigObjValid['hex'], "amount": vout['value']}] outputs = {self.nodes[0].getnewaddress(): 2.19} - rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs) + rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs, version=version) rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs) self.log.debug(rawTxPartialSigned1) assert_equal(rawTxPartialSigned1['complete'], False) # node1 only has one key, can't comp. sign the tx