diff --git a/doc/release-notes-32896.md b/doc/release-notes-32896.md new file mode 100644 index 00000000000..a577e81f1c9 --- /dev/null +++ b/doc/release-notes-32896.md @@ -0,0 +1,19 @@ +Updated RPCs +------------ +The following RPCs now contain a `version` parameter that allows +the user to create transactions of any standard version number (1-3): +- `createrawtransaction` +- `createpsbt` +- `send` +- `sendall` +- `walletcreatefundedpsbt` + +Wallet +------ +Support has been added for spending TRUC transactions received by the +wallet, as well as creating TRUC transactions. The wallet ensures that +TRUC policy rules are being met. The wallet will throw an error if the +user is trying to spend TRUC utxos with utxos of other versions. +Additionally, the wallet will treat unconfirmed TRUC sibling +transactions as mempool conflicts. The wallet will also ensure that +transactions spending TRUC utxos meet the required size restrictions. diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index fdb6bc5f341..3da6cb7489b 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -98,7 +98,7 @@ bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType) bool IsStandardTx(const CTransaction& tx, const std::optional& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason) { - if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < 1) { + if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < TX_MIN_STANDARD_VERSION) { reason = "version"; return false; } diff --git a/src/policy/policy.h b/src/policy/policy.h index ce8bfc6aef9..23993dd705e 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -145,6 +145,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/policy/truc_policy.h b/src/policy/truc_policy.h index dbc77696c67..acd8ae56b1c 100644 --- a/src/policy/truc_policy.h +++ b/src/policy/truc_policy.h @@ -28,8 +28,10 @@ static constexpr unsigned int TRUC_ANCESTOR_LIMIT{2}; /** Maximum sigop-adjusted virtual size of all v3 transactions. */ static constexpr int64_t TRUC_MAX_VSIZE{10000}; +static constexpr int64_t TRUC_MAX_WEIGHT{TRUC_MAX_VSIZE * WITNESS_SCALE_FACTOR}; /** Maximum sigop-adjusted virtual size of a tx which spends from an unconfirmed TRUC transaction. */ static constexpr int64_t TRUC_CHILD_MAX_VSIZE{1000}; +static constexpr int64_t TRUC_CHILD_MAX_WEIGHT{TRUC_CHILD_MAX_VSIZE * WITNESS_SCALE_FACTOR}; // These limits are within the default ancestor/descendant limits. static_assert(TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE <= DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1000); static_assert(TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE <= DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1000); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 0a33ae732a4..1fbe62d3d38 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -119,6 +119,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" }, @@ -167,6 +168,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "walletcreatefundedpsbt", 3, "solving_data"}, { "walletcreatefundedpsbt", 3, "max_tx_weight"}, { "walletcreatefundedpsbt", 4, "bip32derivs" }, + { "walletcreatefundedpsbt", 5, "version" }, { "walletprocesspsbt", 1, "sign" }, { "walletprocesspsbt", 3, "bip32derivs" }, { "walletprocesspsbt", 4, "finalize" }, @@ -177,6 +179,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"}, @@ -213,6 +216,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "send", 4, "replaceable"}, { "send", 4, "solving_data"}, { "send", 4, "max_tx_weight"}, + { "send", 5, "version"}, { "sendall", 0, "recipients" }, { "sendall", 1, "conf_target" }, { "sendall", 3, "fee_rate"}, @@ -230,6 +234,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendall", 4, "conf_target"}, { "sendall", 4, "replaceable"}, { "sendall", 4, "solving_data"}, + { "sendall", 4, "version"}, { "simulaterawtransaction", 0, "rawtxs" }, { "simulaterawtransaction", 1, "options" }, { "simulaterawtransaction", 1, "include_watchonly"}, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index c87741a5389..b7f458ea294 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -53,6 +53,8 @@ using node::GetTransaction; using node::NodeContext; using node::PSBTAnalysis; +static constexpr decltype(CTransaction::version) DEFAULT_RAWTX_VERSION{CTransaction::CURRENT_VERSION}; + static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS) @@ -158,6 +160,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{DEFAULT_RAWTX_VERSION}, "Transaction version"}, }; } @@ -437,7 +440,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, self.Arg("version")); return EncodeHexTx(CTransaction(rawTx)); }, @@ -1679,7 +1682,7 @@ static RPCHelpMan createpsbt() 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, self.Arg("version")); // Make a blank psbt PartiallySignedTransaction psbtx; diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 17a111ae0bc..2fba6c03bef 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -143,7 +144,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 uint32_t version) { CMutableTransaction rawTx; @@ -154,6 +155,11 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.nLockTime = nLockTime; } + if (version < TX_MIN_STANDARD_VERSION || version > TX_MAX_STANDARD_VERSION) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, version out of range(%d~%d)", TX_MIN_STANDARD_VERSION, TX_MAX_STANDARD_VERSION)); + } + rawTx.version = version; + AddInputs(rawTx, inputs_in, rbf); AddOutputs(rawTx, outputs_in); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 40d6bbba873..33017e91879 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -53,6 +53,6 @@ std::vector> ParseOutputs(const UniValue& out 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 uint32_t version); #endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 5da02b4df4e..0604dec2dc9 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -731,6 +731,7 @@ TMPL_INST(CheckRequiredOrDefault, const UniValue&, *CHECK_NONFATAL(maybe_arg);); TMPL_INST(CheckRequiredOrDefault, bool, CHECK_NONFATAL(maybe_arg)->get_bool();); TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt();); TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt();); +TMPL_INST(CheckRequiredOrDefault, uint32_t, CHECK_NONFATAL(maybe_arg)->getInt();); TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str();); bool RPCHelpMan::IsValidNumArgs(size_t num_args) const diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index d7075bc8d6e..6431dc86951 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -21,6 +21,8 @@ namespace wallet { const int DEFAULT_MIN_DEPTH = 0; const int DEFAULT_MAX_DEPTH = 9999999; +const int DEFAULT_WALLET_TX_VERSION = CTransaction::CURRENT_VERSION; + //! Default for -avoidpartialspends static constexpr bool DEFAULT_AVOIDPARTIALSPENDS = false; @@ -109,10 +111,10 @@ public: int m_max_depth = DEFAULT_MAX_DEPTH; //! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs FlatSigningProvider m_external_provider; + //! Version + uint32_t m_version = DEFAULT_WALLET_TX_VERSION; //! Locktime std::optional m_locktime; - //! Version - std::optional m_version; //! Caps weight of resulting tx std::optional m_max_tx_weight{std::nullopt}; diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 08889c8e060..c86765c4a50 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -174,6 +174,8 @@ struct CoinSelectionParams { * 1) Received from other wallets, 2) replacing other txs, 3) that have been replaced. */ bool m_include_unsafe_inputs = false; + /** The version of the transaction we are trying to create. */ + uint32_t m_version{CTransaction::CURRENT_VERSION}; /** The maximum weight for this transaction. */ std::optional m_max_tx_weight{std::nullopt}; diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 8d120cc38aa..dd7c9172d98 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include