mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-25 21:41:36 +02:00
wallet: limit v3 tx weight in coin selection
This commit is contained in:
@@ -28,8 +28,10 @@ static constexpr unsigned int TRUC_ANCESTOR_LIMIT{2};
|
|||||||
|
|
||||||
/** Maximum sigop-adjusted virtual size of all v3 transactions. */
|
/** Maximum sigop-adjusted virtual size of all v3 transactions. */
|
||||||
static constexpr int64_t TRUC_MAX_VSIZE{10000};
|
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. */
|
/** 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_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.
|
// 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_ANCESTOR_SIZE_LIMIT_KVB * 1000);
|
||||||
static_assert(TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE <= DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1000);
|
static_assert(TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE <= DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1000);
|
||||||
|
@@ -174,6 +174,8 @@ struct CoinSelectionParams {
|
|||||||
* 1) Received from other wallets, 2) replacing other txs, 3) that have been replaced.
|
* 1) Received from other wallets, 2) replacing other txs, 3) that have been replaced.
|
||||||
*/
|
*/
|
||||||
bool m_include_unsafe_inputs = false;
|
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. */
|
/** The maximum weight for this transaction. */
|
||||||
std::optional<int> m_max_tx_weight{std::nullopt};
|
std::optional<int> m_max_tx_weight{std::nullopt};
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#include <key_io.h>
|
#include <key_io.h>
|
||||||
#include <node/types.h>
|
#include <node/types.h>
|
||||||
#include <policy/policy.h>
|
#include <policy/policy.h>
|
||||||
|
#include <policy/truc_policy.h>
|
||||||
#include <rpc/rawtransaction_util.h>
|
#include <rpc/rawtransaction_util.h>
|
||||||
#include <rpc/util.h>
|
#include <rpc/util.h>
|
||||||
#include <script/script.h>
|
#include <script/script.h>
|
||||||
@@ -717,6 +718,12 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
|
|||||||
coinControl.m_max_tx_weight = options["max_tx_weight"].getInt<int>();
|
coinControl.m_max_tx_weight = options["max_tx_weight"].getInt<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tx.version == TRUC_VERSION) {
|
||||||
|
if (!coinControl.m_max_tx_weight.has_value() || coinControl.m_max_tx_weight.value() > TRUC_MAX_WEIGHT) {
|
||||||
|
coinControl.m_max_tx_weight = TRUC_MAX_WEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (recipients.empty())
|
if (recipients.empty())
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
|
||||||
|
|
||||||
@@ -1316,6 +1323,7 @@ RPCHelpMan send()
|
|||||||
if (options.exists("max_tx_weight")) {
|
if (options.exists("max_tx_weight")) {
|
||||||
coin_control.m_max_tx_weight = options["max_tx_weight"].getInt<int>();
|
coin_control.m_max_tx_weight = options["max_tx_weight"].getInt<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
SetOptionsInputWeights(options["inputs"], options);
|
SetOptionsInputWeights(options["inputs"], options);
|
||||||
// Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
|
// Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
|
||||||
// This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
|
// This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
|
||||||
@@ -1455,6 +1463,12 @@ RPCHelpMan sendall()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (coin_control.m_version == TRUC_VERSION) {
|
||||||
|
coin_control.m_max_tx_weight = TRUC_MAX_WEIGHT;
|
||||||
|
} else {
|
||||||
|
coin_control.m_max_tx_weight = MAX_STANDARD_TX_WEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
const bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf};
|
const bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf};
|
||||||
|
|
||||||
FeeCalculation fee_calc_out;
|
FeeCalculation fee_calc_out;
|
||||||
@@ -1497,6 +1511,10 @@ RPCHelpMan sendall()
|
|||||||
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
|
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// we are spending an unconfirmed TRUC transaction, so lower max weight
|
||||||
|
if (output.depth == 0 && coin_control.m_version == TRUC_VERSION) {
|
||||||
|
coin_control.m_max_tx_weight = TRUC_CHILD_MAX_WEIGHT;
|
||||||
|
}
|
||||||
CTxIn input(output.outpoint.hash, output.outpoint.n, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL);
|
CTxIn input(output.outpoint.hash, output.outpoint.n, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL);
|
||||||
rawTx.vin.push_back(input);
|
rawTx.vin.push_back(input);
|
||||||
total_input_value += output.txout.nValue;
|
total_input_value += output.txout.nValue;
|
||||||
@@ -1529,7 +1547,7 @@ RPCHelpMan sendall()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If this transaction is too large, e.g. because the wallet has many UTXOs, it will be rejected by the node's mempool.
|
// If this transaction is too large, e.g. because the wallet has many UTXOs, it will be rejected by the node's mempool.
|
||||||
if (tx_size.weight > MAX_STANDARD_TX_WEIGHT) {
|
if (tx_size.weight > coin_control.m_max_tx_weight) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Transaction too large.");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Transaction too large.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -925,11 +925,17 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
|
|||||||
// If no solution is found, return the first detailed error (if any).
|
// If no solution is found, return the first detailed error (if any).
|
||||||
// future: add "error level" so the worst one can be picked instead.
|
// future: add "error level" so the worst one can be picked instead.
|
||||||
std::vector<util::Result<SelectionResult>> res_detailed_errors;
|
std::vector<util::Result<SelectionResult>> res_detailed_errors;
|
||||||
|
CoinSelectionParams updated_selection_params = coin_selection_params;
|
||||||
for (const auto& select_filter : ordered_filters) {
|
for (const auto& select_filter : ordered_filters) {
|
||||||
auto it = filtered_groups.find(select_filter.filter);
|
auto it = filtered_groups.find(select_filter.filter);
|
||||||
if (it == filtered_groups.end()) continue;
|
if (it == filtered_groups.end()) continue;
|
||||||
|
if (updated_selection_params.m_version == TRUC_VERSION && (select_filter.filter.conf_mine == 0 || select_filter.filter.conf_theirs == 0)) {
|
||||||
|
if (updated_selection_params.m_max_tx_weight > (TRUC_CHILD_MAX_WEIGHT)) {
|
||||||
|
updated_selection_params.m_max_tx_weight = TRUC_CHILD_MAX_WEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (auto res{AttemptSelection(wallet.chain(), value_to_select, it->second,
|
if (auto res{AttemptSelection(wallet.chain(), value_to_select, it->second,
|
||||||
coin_selection_params, select_filter.allow_mixed_output_types)}) {
|
updated_selection_params, select_filter.allow_mixed_output_types)}) {
|
||||||
return res; // result found
|
return res; // result found
|
||||||
} else {
|
} else {
|
||||||
// If any specific error message appears here, then something particularly wrong might have happened.
|
// If any specific error message appears here, then something particularly wrong might have happened.
|
||||||
@@ -1046,6 +1052,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
|
|||||||
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
|
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
|
||||||
coin_selection_params.m_include_unsafe_inputs = coin_control.m_include_unsafe_inputs;
|
coin_selection_params.m_include_unsafe_inputs = coin_control.m_include_unsafe_inputs;
|
||||||
coin_selection_params.m_max_tx_weight = coin_control.m_max_tx_weight.value_or(MAX_STANDARD_TX_WEIGHT);
|
coin_selection_params.m_max_tx_weight = coin_control.m_max_tx_weight.value_or(MAX_STANDARD_TX_WEIGHT);
|
||||||
|
coin_selection_params.m_version = coin_control.m_version;
|
||||||
int minimum_tx_weight = MIN_STANDARD_TX_NONWITNESS_SIZE * WITNESS_SCALE_FACTOR;
|
int minimum_tx_weight = MIN_STANDARD_TX_NONWITNESS_SIZE * WITNESS_SCALE_FACTOR;
|
||||||
if (coin_selection_params.m_max_tx_weight.value() < minimum_tx_weight || coin_selection_params.m_max_tx_weight.value() > MAX_STANDARD_TX_WEIGHT) {
|
if (coin_selection_params.m_max_tx_weight.value() < minimum_tx_weight || coin_selection_params.m_max_tx_weight.value() > MAX_STANDARD_TX_WEIGHT) {
|
||||||
return util::Error{strprintf(_("Maximum transaction weight must be between %d and %d"), minimum_tx_weight, MAX_STANDARD_TX_WEIGHT)};
|
return util::Error{strprintf(_("Maximum transaction weight must be between %d and %d"), minimum_tx_weight, MAX_STANDARD_TX_WEIGHT)};
|
||||||
|
Reference in New Issue
Block a user