Merge #5989: backport: Merge bitcoin#21718, 21759, 21872

00e556ac7a Merge bitcoin/bitcoin#21872: net: Sanitize message type for logging (MarcoFalke)
be9781d8b4 Merge bitcoin/bitcoin#21759: wallet: document coin selection code (fanquake)
ea347e6a6e Merge #21718: rpc: Improve error message for getblock invalid datatype. (fanquake)

Pull request description:

  bitcoin backports

Top commit has no ACKs.

Tree-SHA512: 110f139b827ae9e489858c6110bec525e0fe08e7a1d0bf98fccb4e0fa2f532ae1c307eecb97975d02d3252f6c36f2b1b0f10367b6d0cbc8f430ac27e0c938758
This commit is contained in:
pasta 2024-05-19 11:20:08 -05:00
commit cfc40864c1
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
7 changed files with 153 additions and 45 deletions

View File

@ -752,19 +752,19 @@ int V1TransportDeserializer::readHeader(Span<const uint8_t> msg_bytes)
hdrbuf >> hdr; hdrbuf >> hdr;
} }
catch (const std::exception&) { catch (const std::exception&) {
LogPrint(BCLog::NET, "HEADER ERROR - UNABLE TO DESERIALIZE, peer=%d\n", m_node_id); LogPrint(BCLog::NET, "Header error: Unable to deserialize, peer=%d\n", m_node_id);
return -1; return -1;
} }
// Check start string, network magic // Check start string, network magic
if (memcmp(hdr.pchMessageStart, m_chain_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE) != 0) { if (memcmp(hdr.pchMessageStart, m_chain_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE) != 0) {
LogPrint(BCLog::NET, "HEADER ERROR - MESSAGESTART (%s, %u bytes), received %s, peer=%d\n", hdr.GetCommand(), hdr.nMessageSize, HexStr(hdr.pchMessageStart), m_node_id); LogPrint(BCLog::NET, "Header error: Wrong MessageStart %s received, peer=%d\n", HexStr(hdr.pchMessageStart), m_node_id);
return -1; return -1;
} }
// reject messages larger than MAX_SIZE or MAX_PROTOCOL_MESSAGE_LENGTH // reject messages larger than MAX_SIZE or MAX_PROTOCOL_MESSAGE_LENGTH
if (hdr.nMessageSize > MAX_SIZE || hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) { if (hdr.nMessageSize > MAX_SIZE || hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) {
LogPrint(BCLog::NET, "HEADER ERROR - SIZE (%s, %u bytes), peer=%d\n", hdr.GetCommand(), hdr.nMessageSize, m_node_id); LogPrint(BCLog::NET, "Header error: Size too large (%s, %u bytes), peer=%d\n", SanitizeString(hdr.GetCommand()), hdr.nMessageSize, m_node_id);
return -1; return -1;
} }
@ -817,7 +817,7 @@ std::optional<CNetMessage> V1TransportDeserializer::GetMessage(const std::chrono
// Check checksum and header command string // Check checksum and header command string
if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) {
LogPrint(BCLog::NET, "CHECKSUM ERROR (%s, %u bytes), expected %s was %s, peer=%d\n", LogPrint(BCLog::NET, "Header error: Wrong checksum (%s, %u bytes), expected %s was %s, peer=%d\n",
SanitizeString(msg->m_command), msg->m_message_size, SanitizeString(msg->m_command), msg->m_message_size,
HexStr(Span{hash}.first(CMessageHeader::CHECKSUM_SIZE)), HexStr(Span{hash}.first(CMessageHeader::CHECKSUM_SIZE)),
HexStr(hdr.pchChecksum), HexStr(hdr.pchChecksum),
@ -825,8 +825,8 @@ std::optional<CNetMessage> V1TransportDeserializer::GetMessage(const std::chrono
out_err_raw_size = msg->m_raw_message_size; out_err_raw_size = msg->m_raw_message_size;
msg.reset(); msg.reset();
} else if (!hdr.IsCommandValid()) { } else if (!hdr.IsCommandValid()) {
LogPrint(BCLog::NET, "HEADER ERROR - COMMAND (%s, %u bytes), peer=%d\n", LogPrint(BCLog::NET, "Header error: Invalid message type (%s, %u bytes), peer=%d\n",
hdr.GetCommand(), msg->m_message_size, m_node_id); SanitizeString(hdr.GetCommand()), msg->m_message_size, m_node_id);
out_err_raw_size = msg->m_raw_message_size; out_err_raw_size = msg->m_raw_message_size;
msg.reset(); msg.reset();
} }

View File

@ -1242,10 +1242,11 @@ static RPCHelpMan getblock()
int verbosity = 1; int verbosity = 1;
if (!request.params[1].isNull()) { if (!request.params[1].isNull()) {
if(request.params[1].isNum()) if (request.params[1].isBool()) {
verbosity = request.params[1].get_int();
else
verbosity = request.params[1].get_bool() ? 1 : 0; verbosity = request.params[1].get_bool() ? 1 : 0;
} else {
verbosity = request.params[1].get_int();
}
} }
const NodeContext& node = EnsureAnyNodeContext(request.context); const NodeContext& node = EnsureAnyNodeContext(request.context);

View File

@ -15,6 +15,7 @@ static constexpr CAmount MIN_CHANGE{COIN / 100};
//! final minimum change amount after paying for fees //! final minimum change amount after paying for fees
static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2; static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
/** A UTXO under consideration for use in funding a new transaction. */
class CInputCoin { class CInputCoin {
public: public:
CInputCoin(const CTransactionRef& tx, unsigned int i) CInputCoin(const CTransactionRef& tx, unsigned int i)
@ -56,31 +57,58 @@ public:
} }
}; };
/** Parameters for filtering which OutputGroups we may use in coin selection.
* We start by being very selective and requiring multiple confirmations and
* then get more permissive if we cannot fund the transaction. */
struct CoinEligibilityFilter struct CoinEligibilityFilter
{ {
/** Minimum number of confirmations for outputs that we sent to ourselves.
* We may use unconfirmed UTXOs sent from ourselves, e.g. change outputs. */
const int conf_mine; const int conf_mine;
/** Minimum number of confirmations for outputs received from a different
* wallet. We never spend unconfirmed foreign outputs as we cannot rely on these funds yet. */
const int conf_theirs; const int conf_theirs;
/** Maximum number of unconfirmed ancestors aggregated across all UTXOs in an OutputGroup. */
const uint64_t max_ancestors; const uint64_t max_ancestors;
/** Maximum number of descendants that a single UTXO in the OutputGroup may have. */
const uint64_t max_descendants; const uint64_t max_descendants;
const bool m_include_partial_groups{false}; //! Include partial destination groups when avoid_reuse and there are full groups /** When avoid_reuse=true and there are full groups (OUTPUT_GROUP_MAX_ENTRIES), whether or not to use any partial groups.*/
const bool m_include_partial_groups{false};
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {} CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {}
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {} CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {}
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants, bool include_partial) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants), m_include_partial_groups(include_partial) {} CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants, bool include_partial) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants), m_include_partial_groups(include_partial) {}
}; };
/** A group of UTXOs paid to the same output script. */
struct OutputGroup struct OutputGroup
{ {
/** The list of UTXOs contained in this output group. */
std::vector<CInputCoin> m_outputs; std::vector<CInputCoin> m_outputs;
/** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
* our own UTXOs more. */
bool m_from_me{true}; bool m_from_me{true};
/** The total value of the UTXOs in sum. */
CAmount m_value{0}; CAmount m_value{0};
/** The minimum number of confirmations the UTXOs in the group have. Unconfirmed is 0. */
int m_depth{999}; int m_depth{999};
/** The aggregated count of unconfirmed ancestors of all UTXOs in this
* group. Not deduplicated and may overestimate when ancestors are shared. */
size_t m_ancestors{0}; size_t m_ancestors{0};
/** The maximum count of descendants of a single UTXO in this output group. */
size_t m_descendants{0}; size_t m_descendants{0};
/** The value of the UTXOs after deducting the cost of spending them at the effective feerate. */
CAmount effective_value{0}; CAmount effective_value{0};
/** The fee to spend these UTXOs at the effective feerate. */
CAmount fee{0}; CAmount fee{0};
/** The target feerate of the transaction we're trying to build. */
CFeeRate m_effective_feerate{0}; CFeeRate m_effective_feerate{0};
/** The fee to spend these UTXOs at the long term feerate. */
CAmount long_term_fee{0}; CAmount long_term_fee{0};
/** The feerate for spending a created change output eventually (i.e. not urgently, and thus at
* a lower feerate). Calculated using long term fee estimate. This is used to decide whether
* it could be economical to create a change output. */
CFeeRate m_long_term_feerate{0}; CFeeRate m_long_term_feerate{0};
OutputGroup() {} OutputGroup() {}

View File

@ -2877,7 +2877,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
} }
} }
// remove preset inputs from vCoins // remove preset inputs from vCoins so that Coin Selection doesn't pick them.
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
{ {
if (setPresetCoins.count(it->GetInputCoin())) if (setPresetCoins.count(it->GetInputCoin()))
@ -2889,9 +2889,9 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
unsigned int limit_ancestor_count = 0; unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0; unsigned int limit_descendant_count = 0;
chain().getPackageLimits(limit_ancestor_count, limit_descendant_count); chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count); const size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count);
size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count); const size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); const bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
// form groups from remaining coins; note that preset coins will not // form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included // automatically have their associated (same address) coins included
@ -2901,16 +2901,52 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// explicitly shuffling the outputs before processing // explicitly shuffling the outputs before processing
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext()); Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
} }
bool res = value_to_select <= 0 || // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType) || // transaction at a target feerate. If an attempt fails, more attempts may be made using a more
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType) || // permissive CoinEligibilityFilter.
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) || const bool res = [&] {
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) || // Pre-selected inputs already cover the target amount.
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) || if (value_to_select <= 0) return true;
(m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) ||
(m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
// confirmations on outputs received from other wallets and only spend confirmed change.
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) return true;
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) return true;
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
// possible) if we cannot fund the transaction otherwise. We never spend unconfirmed
// outputs received from other wallets.
if (m_spend_zero_conf_change) {
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) return true;
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) {
return true;
}
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) {
return true;
}
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups
// of UTXOs sent to the same address, which are obviously controlled by a single wallet)
// in their entirety.
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) {
return true;
}
// Try with unlimited ancestors/descendants. The transaction will still need to meet
// mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
// OutputGroups use heuristics that may overestimate ancestor/descendant counts.
if (!fRejectLongChains && SelectCoinsMinConf(value_to_select,
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */),
vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType)) {
return true;
}
}
// Coin Selection failed.
return false;
}();
// SelectCoinsMinConf clears setCoinsRet, so add the preset inputs from coin_control to the coinset
util::insert(setCoinsRet, setPresetCoins); util::insert(setCoinsRet, setPresetCoins);
// add preset inputs to the total value selected // add preset inputs to the total value selected

View File

@ -393,7 +393,7 @@ public:
CTransactionRef tx; CTransactionRef tx;
/* New transactions start as UNCONFIRMED. At BlockConnected, /** New transactions start as UNCONFIRMED. At BlockConnected,
* they will transition to CONFIRMED. In case of reorg, at BlockDisconnected, * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected,
* they roll back to UNCONFIRMED. If we detect a conflicting transaction at * they roll back to UNCONFIRMED. If we detect a conflicting transaction at
* block connection, we update conflicted tx and its dependencies as CONFLICTED. * block connection, we update conflicted tx and its dependencies as CONFLICTED.
@ -408,7 +408,7 @@ public:
ABANDONED ABANDONED
}; };
/* Confirmation includes tx status and a triplet of {block height/block hash/tx index in block} /** Confirmation includes tx status and a triplet of {block height/block hash/tx index in block}
* at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned. * at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned.
* Meaning of these fields changes with CONFLICTED state where they instead point to block hash * Meaning of these fields changes with CONFLICTED state where they instead point to block hash
* and block height of the deepest conflicting tx. * and block height of the deepest conflicting tx.
@ -517,7 +517,7 @@ public:
CAmount GetAnonymizedCredit(const CCoinControl* coinControl = nullptr) const; CAmount GetAnonymizedCredit(const CCoinControl* coinControl = nullptr) const;
CAmount GetDenominatedCredit(bool unconfirmed, bool fUseCache=true) const; CAmount GetDenominatedCredit(bool unconfirmed, bool fUseCache=true) const;
// Get the marginal bytes if spending the specified output from this transaction /** Get the marginal bytes if spending the specified output from this transaction */
int GetSpendSize(unsigned int out, bool use_max_sig = false) const int GetSpendSize(unsigned int out, bool use_max_sig = false) const
{ {
return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig); return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig);
@ -531,7 +531,7 @@ public:
return (GetDebit(filter) > 0); return (GetDebit(filter) > 0);
} }
// True if only scriptSigs are different /** True if only scriptSigs are different */
bool IsEquivalentTo(const CWalletTx& tx) const; bool IsEquivalentTo(const CWalletTx& tx) const;
bool InMempool() const; bool InMempool() const;
@ -541,7 +541,7 @@ public:
bool CanBeResent() const; bool CanBeResent() const;
// Pass this transaction to node for mempool insertion and relay to peers if flag set to true /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */
bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay); bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay);
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
@ -623,7 +623,15 @@ class COutput
{ {
public: public:
const CWalletTx *tx; const CWalletTx *tx;
/** Index in tx->vout. */
int i; int i;
/**
* Depth in block chain.
* If > 0: the tx is on chain and has this many confirmations.
* If = 0: the tx is waiting confirmation.
* If < 0: a conflicting tx is on chain and has this many confirmations. */
int nDepth; int nDepth;
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */ /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
@ -663,15 +671,23 @@ public:
} }
}; };
/** Parameters for one iteration of Coin Selection. */
struct CoinSelectionParams struct CoinSelectionParams
{ {
/** Toggles use of Branch and Bound instead of Knapsack solver. */
bool use_bnb = true; bool use_bnb = true;
/** Size of a change output in bytes, determined by the output type. */
size_t change_output_size = 0; size_t change_output_size = 0;
/** Size of the input to spend a change output in virtual bytes. */
size_t change_spend_size = 0; size_t change_spend_size = 0;
/** The targeted feerate of the transaction being built. */
CFeeRate effective_fee = CFeeRate(0); CFeeRate effective_fee = CFeeRate(0);
size_t tx_noinputs_size = 0; size_t tx_noinputs_size = 0;
//! Indicate that we are subtracting the fee from outputs /** Indicate that we are subtracting the fee from outputs */
bool m_subtract_fee_outputs = false; bool m_subtract_fee_outputs = false;
/** When true, always spend all (up to OUTPUT_GROUP_MAX_ENTRIES) or none of the outputs
* associated with the same address. This helps reduce privacy leaks resulting from address
* reuse. Dust outputs are not eligible to be added to output groups and thus not considered. */
bool m_avoid_partial_spends = false; bool m_avoid_partial_spends = false;
CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size, bool avoid_partial) : CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size, bool avoid_partial) :
@ -709,6 +725,8 @@ private:
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE}; int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
int64_t nNextResend = 0; int64_t nNextResend = 0;
/** Whether this wallet will submit newly created transactions to the node's mempool and
* prompt rebroadcasts (see ResendWalletTransactions()). */
bool fBroadcastTransactions = false; bool fBroadcastTransactions = false;
// Local time that the tip block was received. Used to schedule wallet rebroadcasts. // Local time that the tip block was received. Used to schedule wallet rebroadcasts.
std::atomic<int64_t> m_best_block_time {0}; std::atomic<int64_t> m_best_block_time {0};
@ -746,10 +764,10 @@ private:
*/ */
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, WalletBatch& batch, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, WalletBatch& batch, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ /** Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx); void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx);
/* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */ /** Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */
void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -758,6 +776,7 @@ private:
* Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */ * Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */
void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, WalletBatch& batch, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, WalletBatch& batch, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** WalletFlags set on this wallet. */
std::atomic<uint64_t> m_wallet_flags{0}; std::atomic<uint64_t> m_wallet_flags{0};
bool SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose); bool SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose);
@ -809,7 +828,7 @@ private:
*/ */
void InitCoinJoinSalt(); void InitCoinJoinSalt();
/* Height of last block processed is used by wallet to know depth of transactions /** Height of last block processed is used by wallet to know depth of transactions
* without relying on Chain interface beyond asynchronous updates. For safety, we * without relying on Chain interface beyond asynchronous updates. For safety, we
* initialize it to -1. Height is a pointer on node's tip and doesn't imply * initialize it to -1. Height is a pointer on node's tip and doesn't imply
* that the wallet has scanned sequentially all blocks up to this one. * that the wallet has scanned sequentially all blocks up to this one.
@ -826,7 +845,7 @@ private:
bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign, int nExtraPayloadSize); bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign, int nExtraPayloadSize);
public: public:
/* /**
* Main wallet lock. * Main wallet lock.
* This lock protects all the fields added by CWallet. * This lock protects all the fields added by CWallet.
*/ */
@ -840,8 +859,11 @@ public:
/** /**
* Select a set of coins such that nValueRet >= nTargetValue and at least * Select a set of coins such that nValueRet >= nTargetValue and at least
* all coins from coinControl are selected; Never select unconfirmed coins * all coins from coin_control are selected; never select unconfirmed coins if they are not ours
* if they are not ours * param@[out] setCoinsRet Populated with inputs including pre-selected inputs from
* coin_control and Coin Selection if successful.
* param@[out] nValueRet Total value of selected coins including pre-selected ones
* from coin_control and Coin Selection if successful.
*/ */
bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet,
const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -881,6 +903,8 @@ public:
void UpdateProgress(const std::string& title, int nProgress) override; void UpdateProgress(const std::string& title, int nProgress) override;
/** Map from txid to CWalletTx for all transactions this wallet is
* interested in, including received and sent transactions. */
std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet);
typedef std::multimap<int64_t, CWalletTx*> TxItems; typedef std::multimap<int64_t, CWalletTx*> TxItems;
@ -892,6 +916,10 @@ public:
std::map<CTxDestination, CAddressBookData> m_address_book GUARDED_BY(cs_wallet); std::map<CTxDestination, CAddressBookData> m_address_book GUARDED_BY(cs_wallet);
const CAddressBookData* FindAddressBookEntry(const CTxDestination&, bool allow_change = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); const CAddressBookData* FindAddressBookEntry(const CTxDestination&, bool allow_change = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Set of Coins owned by this wallet that we won't try to spend from. A
* Coin may be locked if it has already been used to fund a transaction
* that hasn't confirmed yet. We wouldn't consider the Coin spent already,
* but also shouldn't try to use it again. */
std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet);
int64_t nKeysLeftSinceAutoBackup; int64_t nKeysLeftSinceAutoBackup;
@ -933,6 +961,11 @@ public:
* small change; This method is stochastic for some inputs and upon * small change; This method is stochastic for some inputs and upon
* completion the coin set and corresponding actual target value is * completion the coin set and corresponding actual target value is
* assembled * assembled
* param@[in] coins Set of UTXOs to consider. These will be categorized into
* OutputGroups and filtered using eligibility_filter before
* selecting coins.
* param@[out] setCoinsRet Populated with the coins selected if successful.
* param@[out] nValueRet Used to return the total value of selected coins.
*/ */
bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used, CoinType nCoinType = CoinType::ALL_COINS) const; bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used, CoinType nCoinType = CoinType::ALL_COINS) const;
@ -1069,9 +1102,9 @@ public:
* calling CreateTransaction(); * calling CreateTransaction();
*/ */
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl);
// Fetch the inputs and sign with SIGHASH_ALL. /** Fetch the inputs and sign with SIGHASH_ALL. */
bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// Sign the tx given the input coins and sighash. /** Sign the tx given the input coins and sighash. */
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const; bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const;
@ -1128,6 +1161,8 @@ public:
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE}; CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET}; unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET};
/** Allow Coin Selection to pick unconfirmed UTXOs that were sent from our own wallet if it
* cannot fund the transaction otherwise. */
bool m_spend_zero_conf_change{DEFAULT_SPEND_ZEROCONF_CHANGE}; bool m_spend_zero_conf_change{DEFAULT_SPEND_ZEROCONF_CHANGE};
bool m_allow_fallback_fee{true}; //!< will be false if -fallbackfee=0 bool m_allow_fallback_fee{true}; //!< will be false if -fallbackfee=0
CFeeRate m_min_fee{DEFAULT_TRANSACTION_MINFEE}; //!< Override with -mintxfee CFeeRate m_min_fee{DEFAULT_TRANSACTION_MINFEE}; //!< Override with -mintxfee
@ -1137,7 +1172,12 @@ public:
* Override with -fallbackfee * Override with -fallbackfee
*/ */
CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE}; CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE};
/** If the cost to spend a change output at this feerate is greater than the value of the
* output itself, just drop it to fees. */
CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE}; CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE};
/** The maximum fee amount we're willing to pay to prioritize partial spend avoidance. */
CAmount m_max_aps_fee{DEFAULT_MAX_AVOIDPARTIALSPEND_FEE}; //!< note: this is absolute fee, not fee rate CAmount m_max_aps_fee{DEFAULT_MAX_AVOIDPARTIALSPEND_FEE}; //!< note: this is absolute fee, not fee rate
/** Absolute maximum transaction fee (in satoshis) used by default for the wallet */ /** Absolute maximum transaction fee (in satoshis) used by default for the wallet */
CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE}; CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE};
@ -1477,10 +1517,10 @@ public:
}; };
// Calculate the size of the transaction assuming all signatures are max size /** Calculate the size of the transaction assuming all signatures are max size
// Use DummySignatureCreator, which inserts 71 byte signatures everywhere. * Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should * NOTE: this requires that all inputs must be in mapWallet (eg the tx should
// be IsAllFromMe). * be IsAllFromMe). */
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);

View File

@ -30,7 +30,7 @@ VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte len
class msg_unrecognized: class msg_unrecognized:
"""Nonsensical message. Modeled after similar types in test_framework.messages.""" """Nonsensical message. Modeled after similar types in test_framework.messages."""
msgtype = b'badmsg' msgtype = b'badmsg\x01'
def __init__(self, *, str_data): def __init__(self, *, str_data):
self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data
@ -81,7 +81,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_magic_bytes(self): def test_magic_bytes(self):
self.log.info("Test message with invalid magic bytes disconnects peer") self.log.info("Test message with invalid magic bytes disconnects peer")
conn = self.nodes[0].add_p2p_connection(P2PDataStore()) conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['HEADER ERROR - MESSAGESTART (badmsg, 2 bytes), received ffffffff']): with self.nodes[0].assert_debug_log(['Header error: Wrong MessageStart ffffffff received']):
msg = conn.build_message(msg_unrecognized(str_data="d")) msg = conn.build_message(msg_unrecognized(str_data="d"))
# modify magic bytes # modify magic bytes
msg = b'\xff' * 4 + msg[4:] msg = b'\xff' * 4 + msg[4:]
@ -92,7 +92,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_checksum(self): def test_checksum(self):
self.log.info("Test message with invalid checksum logs an error") self.log.info("Test message with invalid checksum logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore()) conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['CHECKSUM ERROR (badmsg, 2 bytes), expected 78df0a04 was ffffffff']): with self.nodes[0].assert_debug_log(['Header error: Wrong checksum (badmsg, 2 bytes), expected 78df0a04 was ffffffff']):
msg = conn.build_message(msg_unrecognized(str_data="d")) msg = conn.build_message(msg_unrecognized(str_data="d"))
# Checksum is after start bytes (4B), message type (12B), len (4B) # Checksum is after start bytes (4B), message type (12B), len (4B)
cut_len = 4 + 12 + 4 cut_len = 4 + 12 + 4
@ -117,7 +117,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
def test_msgtype(self): def test_msgtype(self):
self.log.info("Test message with invalid message type logs an error") self.log.info("Test message with invalid message type logs an error")
conn = self.nodes[0].add_p2p_connection(P2PDataStore()) conn = self.nodes[0].add_p2p_connection(P2PDataStore())
with self.nodes[0].assert_debug_log(['HEADER ERROR - COMMAND']): with self.nodes[0].assert_debug_log(['Header error: Invalid message type']):
msg = msg_unrecognized(str_data="d") msg = msg_unrecognized(str_data="d")
msg = conn.build_message(msg) msg = conn.build_message(msg)
# Modify msgtype # Modify msgtype

View File

@ -442,6 +442,9 @@ class BlockchainTest(BitcoinTestFramework):
self.log.info("Test getblock with verbosity 2 still works with pruned Undo data") self.log.info("Test getblock with verbosity 2 still works with pruned Undo data")
datadir = get_datadir_path(self.options.tmpdir, 0) datadir = get_datadir_path(self.options.tmpdir, 0)
self.log.info("Test that getblock with invalid verbosity type returns proper error message")
assert_raises_rpc_error(-1, "JSON value is not an integer as expected", node.getblock, blockhash, "2")
def move_block_file(old, new): def move_block_file(old, new):
old_path = os.path.join(datadir, self.chain, 'blocks', old) old_path = os.path.join(datadir, self.chain, 'blocks', old)
new_path = os.path.join(datadir, self.chain, 'blocks', new) new_path = os.path.join(datadir, self.chain, 'blocks', new)