Merge pull request #5360 from vijaydasmp/21_17

backport: bitcoin#18875,11413,16946,17824,18601,18585,20271,19871,20039
This commit is contained in:
PastaPastaPasta 2023-07-21 16:03:34 -05:00 committed by GitHub
commit b3bdcd05d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 457 additions and 159 deletions

View File

@ -0,0 +1,7 @@
Updated or changed RPC
----------------------
The `fundrawtransaction`, `sendmany`, `sendtoaddress`, and `walletcreatefundedpsbt`
RPC commands have been updated to include two new fee estimation methods "DASH/kB" and "duff/B".
The target is the fee expressed explicitly in the given form.
In addition, the `estimate_mode` parameter is now case insensitive for all of the above RPC commands.

View File

@ -30,7 +30,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
argsman.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
// Hidden
argsman.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
argsman.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
argsman.AddArg("wipetxes", "Wipe all transactions from a wallet", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
}

View File

@ -563,10 +563,17 @@ struct CNodeState {
*/
bool fSupportsDesiredCmpctVersion;
/** State used to enforce CHAIN_SYNC_TIMEOUT
* Only in effect for outbound, non-manual, full-relay connections, with
* m_protect == false
* Algorithm: if a peer's best known block has less work than our tip,
/** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic.
*
* Both are only in effect for outbound, non-manual, non-protected connections.
* Any peer protected (m_protect = true) is not chosen for eviction. A peer is
* marked as protected if all of these are true:
* - its connection type is IsBlockOnlyConn() == false
* - it gave us a valid connecting header
* - we haven't reached MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT yet
* - it has a better chain than we have
*
* CHAIN_SYNC_TIMEOUT: if a peer's best known block has less work than our tip,
* set a timeout CHAIN_SYNC_TIMEOUT seconds in the future:
* - If at timeout their best known block now has more work than our tip
* when the timeout was set, then either reset the timeout or clear it
@ -576,6 +583,9 @@ struct CNodeState {
* and set a shorter timeout, HEADERS_RESPONSE_TIME seconds in future.
* If their best known block is still behind when that new timeout is
* reached, disconnect.
*
* EXTRA_PEER_CHECK_INTERVAL: after each interval, if we have too many outbound peers,
* drop the outbound one that least recently announced us a new block.
*/
struct ChainSyncTimeoutState {
//! A timeout used for checking whether our peer has sufficiently synced
@ -2466,12 +2476,12 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const std::vector<CBlo
}
}
}
// If this is an outbound full-relay peer, check to see if we should protect
// it from the bad/lagging chain logic.
// Note that outbound block-relay peers are excluded from this protection, and
// thus always subject to eviction under the bad/lagging chain logic.
// See ChainSyncTimeoutState.
if (!pfrom.fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr && pfrom.IsAddrRelayPeer()) {
// If this is an outbound full-relay peer, check to see if we should protect
// it from the bad/lagging chain logic.
// Note that block-relay-only peers are already implicitly protected, so we
// only consider setting m_protect for the full-relay peers.
if (m_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) {
LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom.GetId());
nodestate->m_chain_sync.m_protect = true;

View File

@ -7,8 +7,6 @@
#include <tinyformat.h>
const std::string CURRENCY_UNIT = "DASH";
CFeeRate::CFeeRate(const CAmount& nFeePaid, size_t nBytes_)
{
assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
@ -37,7 +35,10 @@ CAmount CFeeRate::GetFee(size_t nBytes_) const
return nFee;
}
std::string CFeeRate::ToString() const
std::string CFeeRate::ToString(const FeeEstimateMode& fee_estimate_mode) const
{
return strprintf("%d.%08d %s/kB", nSatoshisPerK / COIN, nSatoshisPerK % COIN, CURRENCY_UNIT);
switch (fee_estimate_mode) {
case FeeEstimateMode::DUFF_B: return strprintf("%d.%03d %s/B", nSatoshisPerK / 1000, nSatoshisPerK % 1000, CURRENCY_ATOM);
default: return strprintf("%d.%08d %s/kB", nSatoshisPerK / COIN, nSatoshisPerK % COIN, CURRENCY_UNIT);
}
}

View File

@ -11,7 +11,17 @@
#include <string>
extern const std::string CURRENCY_UNIT;
const std::string CURRENCY_UNIT = "DASH"; // One formatted unit
const std::string CURRENCY_ATOM = "duff"; // One indivisible minimum value unit
/* Used to determine type of fee estimation requested */
enum class FeeEstimateMode {
UNSET, //!< Use default settings based on other criteria
ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
DASH_KB, //!< Use explicit DASH/kB fee given in coin control
DUFF_B, //!< Use explicit duff/B fee given in coin control
};
/**
* Fee rate in satoshis per kilobyte: CAmount / kB
@ -46,7 +56,7 @@ public:
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
friend bool operator!=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK != b.nSatoshisPerK; }
CFeeRate& operator+=(const CFeeRate& a) { nSatoshisPerK += a.nSatoshisPerK; return *this; }
std::string ToString() const;
std::string ToString(const FeeEstimateMode& fee_estimate_mode = FeeEstimateMode::DASH_KB) const;
SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.nSatoshisPerK); }
};

View File

@ -45,13 +45,6 @@ enum class FeeReason {
REQUIRED,
};
/* Used to determine type of fee estimation requested */
enum class FeeEstimateMode {
UNSET, //!< Use default settings based on other criteria
ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
};
/* Used to return detailed information about a feerate bucket */
struct EstimatorBucket
{

View File

@ -121,7 +121,7 @@ void TestGUI(interfaces::Node& node)
wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash());
}
{
WalletRescanReserver reserver(wallet.get());
WalletRescanReserver reserver(*wallet);
reserver.reserve();
CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* start_height */, {} /* max_height */, reserver, true /* fUpdate */);
QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);

View File

@ -14,6 +14,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/util/mining.h>
#include <test/util/net.h>
#include <test/util/setup_common.h>
#include <validationinterface.h>
#include <version.h>
@ -59,15 +60,17 @@ void initialize_process_message()
void fuzz_target(const std::vector<uint8_t>& buffer, const std::string& LIMIT_TO_MESSAGE_TYPE)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
ConnmanTestMsg& connman = *(ConnmanTestMsg*)g_setup->m_node.connman.get();
const std::string random_message_type{fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::COMMAND_SIZE).c_str()};
if (!LIMIT_TO_MESSAGE_TYPE.empty() && random_message_type != LIMIT_TO_MESSAGE_TYPE) {
return;
}
CDataStream random_bytes_data_stream{fuzzed_data_provider.ConsumeRemainingBytes<unsigned char>(), SER_NETWORK, PROTOCOL_VERSION};
CNode p2p_node{0, ServiceFlags(NODE_NETWORK | NODE_BLOOM), INVALID_SOCKET, CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK}, 0, 0, CAddress{}, std::string{}, false};
CNode& p2p_node = *std::make_unique<CNode>(0, ServiceFlags(NODE_NETWORK | NODE_BLOOM), INVALID_SOCKET, CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK}, 0, 0, CAddress{}, std::string{}, false).release();
p2p_node.fSuccessfullyConnected = true;
p2p_node.nVersion = PROTOCOL_VERSION;
p2p_node.SetSendVersion(PROTOCOL_VERSION);
connman.AddTestNode(p2p_node);
g_setup->m_node.peerman->InitializeNode(&p2p_node);
try {
g_setup->m_node.peerman->ProcessMessage(p2p_node, random_message_type, random_bytes_data_stream, GetTimeMillis(), std::atomic<bool>{false});
@ -78,6 +81,8 @@ void fuzz_target(const std::vector<uint8_t>& buffer, const std::string& LIMIT_TO
g_setup->m_node.peerman->SendMessages(&p2p_node);
}
SyncWithValidationInterfaceQueue();
LOCK2(::cs_main, g_cs_orphans); // See init.cpp for rationale for implicit locking order requirement
g_setup->m_node.connman->StopNodes();
}
FUZZ_TARGET_INIT(process_message, initialize_process_message) { fuzz_target(buffer, ""); }

View File

@ -77,6 +77,7 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages)
g_setup->m_node.peerman->SendMessages(&random_node);
}
}
connman.ClearTestNodes();
SyncWithValidationInterfaceQueue();
LOCK2(::cs_main, g_cs_orphans); // See init.cpp for rationale for implicit locking order requirement
g_setup->m_node.connman->StopNodes();
}

View File

@ -6,11 +6,16 @@
#include <util/fees.h>
#include <policy/fees.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <map>
#include <string>
#include <vector>
#include <utility>
std::string StringForFeeReason(FeeReason reason) {
std::string StringForFeeReason(FeeReason reason)
{
static const std::map<FeeReason, std::string> fee_reason_strings = {
{FeeReason::NONE, "None"},
{FeeReason::HALF_ESTIMATE, "Half Target 60% Threshold"},
@ -29,16 +34,31 @@ std::string StringForFeeReason(FeeReason reason) {
return reason_string->second;
}
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode) {
static const std::map<std::string, FeeEstimateMode> fee_modes = {
{"UNSET", FeeEstimateMode::UNSET},
{"ECONOMICAL", FeeEstimateMode::ECONOMICAL},
{"CONSERVATIVE", FeeEstimateMode::CONSERVATIVE},
const std::vector<std::pair<std::string, FeeEstimateMode>>& FeeModeMap()
{
static const std::vector<std::pair<std::string, FeeEstimateMode>> FEE_MODES = {
{"unset", FeeEstimateMode::UNSET},
{"economical", FeeEstimateMode::ECONOMICAL},
{"conservative", FeeEstimateMode::CONSERVATIVE},
{(CURRENCY_UNIT + "/kB"), FeeEstimateMode::DASH_KB},
{(CURRENCY_ATOM + "/B"), FeeEstimateMode::DUFF_B},
};
auto mode = fee_modes.find(mode_string);
if (mode == fee_modes.end()) return false;
fee_estimate_mode = mode->second;
return true;
return FEE_MODES;
}
std::string FeeModes(const std::string& delimiter)
{
return Join(FeeModeMap(), delimiter, [&](const std::pair<std::string, FeeEstimateMode>& i) { return i.first; });
}
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode)
{
auto searchkey = ToUpper(mode_string);
for (const auto& pair : FeeModeMap()) {
if (ToUpper(pair.first) == searchkey) {
fee_estimate_mode = pair.second;
return true;
}
}
return false;
}

View File

@ -12,5 +12,6 @@ enum class FeeReason;
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
std::string StringForFeeReason(FeeReason reason);
std::string FeeModes(const std::string& delimiter);
#endif // BITCOIN_UTIL_FEES_H

View File

@ -106,7 +106,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
EnsureLegacyScriptPubKeyMan(*wallet, true);
WalletBatch batch(pwallet->GetDBHandle());
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
bool fRescan = true;
{
LOCK(pwallet->cs_wallet);
@ -231,7 +231,7 @@ UniValue importaddress(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
}
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (fRescan && !reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@ -419,7 +419,7 @@ UniValue importpubkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
}
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (fRescan && !reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@ -488,7 +488,7 @@ UniValue importwallet(const JSONRPCRequest& request)
}
WalletBatch batch(pwallet->GetDBHandle());
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@ -780,7 +780,7 @@ UniValue importelectrumwallet(const JSONRPCRequest& request)
spk_man.UpdateTimeFirstKey(nTimeBegin);
pwallet->WalletLogPrintf("Rescanning %i blocks\n", tip_height - nStartHeight + 1);
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@ -1519,7 +1519,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
}
}
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (fRescan && !reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}

View File

@ -51,6 +51,8 @@ using interfaces::FoundBlock;
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
static const uint32_t WALLET_DASH_KB_TO_DUFF_B = COIN / 1000; // 1 duff / B = 0.00001 DASH / kB
static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) {
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
@ -190,6 +192,7 @@ static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniVa
entry.pushKV(item.first, item.second);
}
static std::string LabelFromValue(const UniValue& value)
{
std::string label = value.get_str();
@ -198,6 +201,40 @@ static std::string LabelFromValue(const UniValue& value)
return label;
}
/**
* Update coin control with fee estimation based on the given parameters
*
* @param[in] pwallet Wallet pointer
* @param[in,out] cc Coin control which is to be updated
* @param[in] estimate_mode String value (e.g. "ECONOMICAL")
* @param[in] estimate_param Parameter (blocks to confirm, explicit fee rate, etc)
* @throws a JSONRPCError if estimate_mode is unknown, or if estimate_param is missing when required
*/
static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const UniValue& estimate_mode, const UniValue& estimate_param)
{
if (!estimate_mode.isNull()) {
if (!FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
if (cc.m_fee_mode == FeeEstimateMode::DASH_KB || cc.m_fee_mode == FeeEstimateMode::DUFF_B) {
if (estimate_param.isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Selected estimate_mode requires a fee rate");
}
CAmount fee_rate = AmountFromValue(estimate_param);
if (cc.m_fee_mode == FeeEstimateMode::DUFF_B) {
fee_rate /= WALLET_DASH_KB_TO_DUFF_B;
}
cc.m_feerate = CFeeRate(fee_rate);
} else if (!estimate_param.isNull()) {
cc.m_confirm_target = ParseConfirmTarget(estimate_param, pwallet->chain().estimateMaxBlocks());
}
}
UniValue getnewaddress(const JSONRPCRequest& request)
{
RPCHelpMan{"getnewaddress",
@ -371,11 +408,9 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
" The recipient will receive less amount of Dash than you enter in the amount field."},
{"use_is", RPCArg::Type::BOOL, /* default */ "false", "Deprecated and ignored"},
{"use_cj", RPCArg::Type::BOOL, /* default */ "false", "Use CoinJoin funds only"},
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\""},
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
" dirty if they have previously been used in a transaction."},
},
@ -386,6 +421,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"seans outpost\"")
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" true")
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 0.00002 " + (CURRENCY_UNIT + "/kB"))
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 2 " + (CURRENCY_ATOM + "/B"))
+ HelpExampleRpc("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\"")
},
}.Check(request);
@ -424,20 +461,13 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
coin_control.UseCoinJoin(request.params[6].get_bool());
}
if (!request.params[7].isNull()) {
coin_control.m_confirm_target = ParseConfirmTarget(request.params[7], pwallet->chain().estimateMaxBlocks());
}
if (!request.params[8].isNull()) {
if (!FeeModeFromString(request.params[8].get_str(), coin_control.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[9]);
// We also enable partial spend avoidance if reuse avoidance is set.
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
SetFeeEstimateMode(pwallet, coin_control, request.params[8], request.params[7]);
EnsureWalletIsUnlocked(pwallet);
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
@ -852,11 +882,9 @@ static UniValue sendmany(const JSONRPCRequest& request)
},
{"use_is", RPCArg::Type::BOOL, /* default */ "false", "Deprecated and ignored"},
{"use_cj", RPCArg::Type::BOOL, /* default */ "false", "Use CoinJoin funds only"},
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\""},
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
},
RPCResult{
RPCResult::Type::STR_HEX, "txid", "The transaction id for the send. Only 1 transaction is created regardless of\n"
@ -902,15 +930,7 @@ static UniValue sendmany(const JSONRPCRequest& request)
coin_control.UseCoinJoin(request.params[7].get_bool());
}
if (!request.params[8].isNull()) {
coin_control.m_confirm_target = ParseConfirmTarget(request.params[8], pwallet->chain().estimateMaxBlocks());
}
if (!request.params[9].isNull()) {
if (!FeeModeFromString(request.params[9].get_str(), coin_control.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
SetFeeEstimateMode(pwallet, coin_control, request.params[9], request.params[8]);
if (coin_control.IsUsingCoinJoin()) {
mapValue["DS"] = "1";
@ -2732,7 +2752,7 @@ static UniValue upgradetohd(const JSONRPCRequest& request)
// If you are generating new mnemonic it is assumed that the addresses have never gotten a transaction before, so you don't need to rescan for transactions
bool rescan = request.params[3].isNull() ? !generate_mnemonic : request.params[3].get_bool();
if (rescan) {
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@ -3254,26 +3274,21 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
if (options.exists("feeRate"))
{
if (options.exists("conf_target")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
}
if (options.exists("estimate_mode")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
}
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
coinControl.fOverrideFeeRate = true;
}
if (options.exists("subtractFeeFromOutputs"))
subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array();
if (options.exists("conf_target")) {
if (options.exists("feeRate")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
}
coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"], pwallet->chain().estimateMaxBlocks());
}
if (options.exists("estimate_mode")) {
if (options.exists("feeRate")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
}
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
}
}
SetFeeEstimateMode(pwallet, coinControl, options["estimate_mode"], options["conf_target"]);
}
} else {
// if options is null and not a bool
@ -3337,11 +3352,9 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
},
},
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\""},
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
},
"options"},
},
@ -3505,7 +3518,7 @@ static UniValue rescanblockchain(const JSONRPCRequest& request)
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@ -3577,7 +3590,7 @@ static UniValue wipewallettxes(const JSONRPCRequest& request)
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
WalletRescanReserver reserver(pwallet);
WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort rescan or wait.");
}
@ -4081,10 +4094,8 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
},
},
{"conf_target", RPCArg::Type::NUM, /* default */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
" \"UNSET\"\n"
" \"ECONOMICAL\"\n"
" \"CONSERVATIVE\""},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
},
"options"},
{"bip32derivs", RPCArg::Type::BOOL, /* default */ "true", "Include BIP 32 derivation paths for public keys if we know them"},

View File

@ -193,6 +193,7 @@ bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key
bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
bool keyFail = false;
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
WalletBatch batch(m_storage.GetDatabase());
for (; mi != mapCryptedKeys.end(); ++mi)
{
const CPubKey &vchPubKey = (*mi).second.first;
@ -206,6 +207,10 @@ bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key
keyPass = true;
if (fDecryptionThoroughlyChecked)
break;
else {
// Rewrite these encrypted keys with checksums
batch.WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
}
}
if (keyPass && keyFail)
{
@ -923,8 +928,13 @@ bool LegacyScriptPubKeyMan::GetPubKeyInner(const CKeyID &address, CPubKey& vchPu
return GetWatchPubKey(address, vchPubKeyOut);
}
bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret, bool checksum_valid)
{
// Set fDecryptionThoroughlyChecked to false when the checksum is invalid
if (!checksum_valid) {
fDecryptionThoroughlyChecked = false;
}
return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
}

View File

@ -226,7 +226,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
{
private:
//! keeps track of whether Unlock has run a thorough check before
bool fDecryptionThoroughlyChecked = false;
bool fDecryptionThoroughlyChecked = true;
using WatchOnlySet = std::set<CScript>;
using WatchKeyMap = std::map<CKeyID, CPubKey>;
@ -371,7 +371,7 @@ public:
//! Adds an encrypted key to the store, and saves it to disk.
bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
//! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret, bool checksum_valid);
void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
//! Adds a CScript to the store
bool LoadCScript(const CScript& redeemScript);

View File

@ -138,7 +138,7 @@ public:
LOCK2(wallet->cs_wallet, cs_main);
wallet->GetLegacyScriptPubKeyMan()->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
WalletRescanReserver reserver(wallet.get());
WalletRescanReserver reserver(*wallet);
reserver.reserve();
CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, true /* fUpdate */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);

View File

@ -101,7 +101,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
@ -120,7 +120,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
@ -146,7 +146,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
@ -171,7 +171,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
@ -581,7 +581,7 @@ public:
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
WalletRescanReserver reserver(wallet.get());
WalletRescanReserver reserver(*wallet);
reserver.reserve();
CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
@ -715,7 +715,7 @@ public:
wallet->LoadWallet(firstRun);
AddWallet(wallet);
AddKey(*wallet, coinbaseKey);
WalletRescanReserver reserver(wallet.get());
WalletRescanReserver reserver(*wallet);
reserver.reserve();
{
LOCK(wallet->cs_wallet);
@ -1160,7 +1160,7 @@ BOOST_FIXTURE_TEST_CASE(select_coins_grouped_by_addresses, ListCoinsTestingSetup
}
// Reveal the mined tx, it should conflict with the one we have in the wallet already.
WalletRescanReserver reserver(wallet.get());
WalletRescanReserver reserver(*wallet);
reserver.reserve();
auto result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);

View File

@ -2860,6 +2860,13 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
++it;
}
unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0;
chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
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);
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
// form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included
if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
@ -2868,14 +2875,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// explicitly shuffling the outputs before processing
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
}
std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends);
unsigned int limit_ancestor_count;
unsigned int 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);
size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends, max_ancestors);
bool res = value_to_select <= 0 ||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used, nCoinType) ||
@ -4791,7 +4791,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
{
WalletRescanReserver reserver(walletInstance.get());
WalletRescanReserver reserver(*walletInstance);
if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
error = _("Failed to rescan the wallet during initialization");
return nullptr;
@ -5150,32 +5150,49 @@ bool CWalletTx::IsImmatureCoinBase() const
return GetBlocksToMaturity() > 0;
}
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const {
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const {
std::vector<OutputGroup> groups;
std::map<CTxDestination, OutputGroup> gmap;
CTxDestination dst;
std::set<CTxDestination> full_groups;
for (const auto& output : outputs) {
if (output.fSpendable) {
CTxDestination dst;
CInputCoin input_coin = output.GetInputCoin();
size_t ancestors, descendants;
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) {
// Limit output groups to no more than 10 entries, to protect
// against inadvertently creating a too-large transaction
// when using -avoidpartialspends
if (gmap[dst].m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
groups.push_back(gmap[dst]);
gmap.erase(dst);
auto it = gmap.find(dst);
if (it != gmap.end()) {
// Limit output groups to no more than OUTPUT_GROUP_MAX_ENTRIES
// number of entries, to protect against inadvertently creating
// a too-large transaction when using -avoidpartialspends to
// prevent breaking consensus or surprising users with a very
// high amount of fees.
if (it->second.m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
groups.push_back(it->second);
it->second = OutputGroup{};
full_groups.insert(dst);
}
it->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
} else {
gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
}
gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
} else {
groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
}
}
}
if (!single_coin) {
for (const auto& it : gmap) groups.push_back(it.second);
for (auto& it : gmap) {
auto& group = it.second;
if (full_groups.count(it.first) > 0) {
// Make this unattractive as we want coin selection to avoid it if possible
group.m_ancestors = max_ancestors - 1;
}
groups.push_back(group);
}
}
return groups;
}

View File

@ -920,7 +920,7 @@ public:
bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const;
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const;
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -1371,35 +1371,36 @@ void MaybeResendWalletTxs();
class WalletRescanReserver
{
private:
CWallet* m_wallet;
CWallet& m_wallet;
bool m_could_reserve;
public:
explicit WalletRescanReserver(CWallet* w) : m_wallet(w), m_could_reserve(false) {}
explicit WalletRescanReserver(CWallet& w) : m_wallet(w), m_could_reserve(false) {}
bool reserve()
{
assert(!m_could_reserve);
if (m_wallet->fScanningWallet.exchange(true)) {
if (m_wallet.fScanningWallet.exchange(true)) {
return false;
}
m_wallet->m_scanning_start = GetTimeMillis();
m_wallet->m_scanning_progress = 0;
m_wallet->fAbortRescan = false;
m_wallet.m_scanning_start = GetTimeMillis();
m_wallet.m_scanning_progress = 0;
m_wallet.fAbortRescan = false;
m_could_reserve = true;
return true;
}
bool isReserved() const
{
return (m_could_reserve && m_wallet->fScanningWallet);
return (m_could_reserve && m_wallet.fScanningWallet);
}
~WalletRescanReserver()
{
if (m_could_reserve) {
m_wallet->fScanningWallet = false;
m_wallet.fScanningWallet = false;
}
}
};
// Calculate the size of the transaction assuming all signatures are max size

View File

@ -124,8 +124,19 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
return false;
}
if (!WriteIC(std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey), vchCryptedSecret, false)) {
return false;
// Compute a checksum of the encrypted key
uint256 checksum = Hash(vchCryptedSecret.begin(), vchCryptedSecret.end());
const auto key = std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey);
if (!WriteIC(key, std::make_pair(vchCryptedSecret, checksum), false)) {
// It may already exist, so try writing just the checksum
std::vector<unsigned char> val;
if (!m_batch->Read(key, val)) {
return false;
}
if (!WriteIC(key, std::make_pair(val, checksum), true)) {
return false;
}
}
EraseIC(std::make_pair(DBKeys::KEY, vchPubKey));
return true;
@ -371,9 +382,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
std::vector<unsigned char> vchPrivKey;
ssValue >> vchPrivKey;
// Get the checksum and check it
bool checksum_valid = false;
if (!ssValue.eof()) {
uint256 checksum;
ssValue >> checksum;
if ((checksum_valid = Hash(vchPrivKey.begin(), vchPrivKey.end()) != checksum)) {
strErr = "Error reading wallet database: Crypted key corrupt";
return false;
}
}
wss.nCKeys++;
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey))
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey, checksum_valid))
{
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed";
return false;

View File

@ -4,6 +4,8 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mempool acceptance of raw transactions."""
from decimal import Decimal
from io import BytesIO
import math
@ -82,10 +84,10 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
)
self.log.info('A transaction not in the mempool')
fee = 0.00000700
fee = Decimal('0.000007')
raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later
outputs=[{node.getnewaddress(): 0.3 - fee}],
outputs=[{node.getnewaddress(): Decimal('0.3') - fee}],
))['hex']
tx = CTransaction()
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))

View File

@ -586,16 +586,16 @@ class CBlock(CBlockHeader):
__slots__ = ("vtx",)
def __init__(self, header=None):
super(CBlock, self).__init__(header)
super().__init__(header)
self.vtx = []
def deserialize(self, f):
super(CBlock, self).deserialize(f)
super().deserialize(f)
self.vtx = deser_vector(f, CTransaction)
def serialize(self):
r = b""
r += super(CBlock, self).serialize()
r += super().serialize()
r += ser_vector(self.vtx)
return r

View File

@ -96,7 +96,7 @@ class CScriptOp(int):
return _opcode_instances[n]
except IndexError:
assert len(_opcode_instances) == n
_opcode_instances.append(super(CScriptOp, cls).__new__(cls, n))
_opcode_instances.append(super().__new__(cls, n))
return _opcode_instances[n]
# Populate opcode instance table
@ -373,7 +373,7 @@ class CScriptTruncatedPushDataError(CScriptInvalidError):
"""Invalid pushdata due to truncation"""
def __init__(self, msg, data):
self.data = data
super(CScriptTruncatedPushDataError, self).__init__(msg)
super().__init__(msg)
# This is used, eg, for blockchain heights in coinbase scripts (bip34)
@ -459,14 +459,14 @@ class CScript(bytes):
def __new__(cls, value=b''):
if isinstance(value, bytes) or isinstance(value, bytearray):
return super(CScript, cls).__new__(cls, value)
return super().__new__(cls, value)
else:
def coerce_iterable(iterable):
for instance in iterable:
yield cls.__coerce_instance(instance)
# Annoyingly on both python2 and python3 bytes.join() always
# returns a bytes instance even when subclassed.
return super(CScript, cls).__new__(cls, b''.join(coerce_iterable(value)))
return super().__new__(cls, b''.join(coerce_iterable(value)))
def raw_iter(self):
"""Raw iteration

View File

@ -89,11 +89,15 @@ class AvoidReuseTest(BitcoinTestFramework):
self.nodes[0].generate(110)
self.sync_all()
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
self.test_fund_send_fund_senddirty()
self.test_sending_from_reused_address_without_avoid_reuse()
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
self.test_fund_send_fund_send()
self.test_sending_from_reused_address_fails()
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
self.test_getbalances_used()
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
self.test_full_destination_group_is_preferred()
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
self.test_all_destination_groups_are_used()
def test_persistence(self):
'''Test that wallet files persist the avoid_reuse flag.'''
@ -137,13 +141,13 @@ class AvoidReuseTest(BitcoinTestFramework):
# Unload temp wallet
self.nodes[1].unloadwallet(tempwallet)
def test_fund_send_fund_senddirty(self):
def test_sending_from_reused_address_without_avoid_reuse(self):
'''
Test the same as test_fund_send_fund_send, except send the 10 BTC with
Test the same as test_sending_from_reused_address_fails, except send the 10 BTC with
the avoid_reuse flag set to false. This means the 10 BTC send should succeed,
where it fails in test_fund_send_fund_send.
where it fails in test_sending_from_reused_address_fails.
'''
self.log.info("Test fund send fund send dirty")
self.log.info("Test sending from reused address with avoid_reuse=false")
fundaddr = self.nodes[1].getnewaddress()
retaddr = self.nodes[0].getnewaddress()
@ -188,7 +192,7 @@ class AvoidReuseTest(BitcoinTestFramework):
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001)
def test_fund_send_fund_send(self):
def test_sending_from_reused_address_fails(self):
'''
Test the simple case where [1] generates a new address A, then
[0] sends 10 BTC to A.
@ -197,7 +201,7 @@ class AvoidReuseTest(BitcoinTestFramework):
[1] tries to spend 10 BTC (fails; dirty).
[1] tries to spend 4 BTC (succeeds; change address sufficient)
'''
self.log.info("Test fund send fund send")
self.log.info("Test sending from reused address fails")
fundaddr = self.nodes[1].getnewaddress(label="")
retaddr = self.nodes[0].getnewaddress()
@ -276,5 +280,66 @@ class AvoidReuseTest(BitcoinTestFramework):
assert_unspent(self.nodes[1], total_count=2, total_sum=6, reused_count=1, reused_sum=1)
assert_balances(self.nodes[1], mine={"used": 1, "trusted": 5})
def test_full_destination_group_is_preferred(self):
'''
Test the case where [1] only has 11 outputs of 1 BTC in the same reused
address and tries to send a small payment of 0.5 BTC. The wallet
should use 10 outputs from the reused address as inputs and not a
single 1 BTC input, in order to join several outputs from the reused
address.
'''
self.log.info("Test that full destination groups are preferred in coin selection")
# Node under test should be empty
assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0)
new_addr = self.nodes[1].getnewaddress()
ret_addr = self.nodes[0].getnewaddress()
# Send 11 outputs of 1 BTC to the same, reused address in the wallet
for _ in range(11):
self.nodes[0].sendtoaddress(new_addr, 1)
self.nodes[0].generate(1)
self.sync_all()
# Sending a transaction that is smaller than each one of the
# available outputs
txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=0.5)
inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"]
# The transaction should use 10 inputs exactly
assert_equal(len(inputs), 10)
def test_all_destination_groups_are_used(self):
'''
Test the case where [1] only has 22 outputs of 1 BTC in the same reused
address and tries to send a payment of 20.5 BTC. The wallet
should use all 22 outputs from the reused address as inputs.
'''
self.log.info("Test that all destination groups are used")
# Node under test should be empty
assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0)
new_addr = self.nodes[1].getnewaddress()
ret_addr = self.nodes[0].getnewaddress()
# Send 22 outputs of 1 BTC to the same, reused address in the wallet
for _ in range(22):
self.nodes[0].sendtoaddress(new_addr, 1)
self.nodes[0].generate(1)
self.sync_all()
# Sending a transaction that needs to use the full groups
# of 10 inputs but also the incomplete group of 2 inputs.
txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=20.5)
inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"]
# The transaction should use 22 inputs exactly
assert_equal(len(inputs), 22)
if __name__ == '__main__':
AvoidReuseTest().main()

View File

@ -223,7 +223,60 @@ class WalletTest(BitcoinTestFramework):
self.start_node(3)
self.connect_nodes(0, 3)
self.sync_all()
# Sendmany with explicit fee (DASH/kB)
# Throw if no conf_target provided
assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
self.nodes[2].sendmany,
amounts={ address: 10 },
estimate_mode='dash/kB')
# Throw if negative feerate
assert_raises_rpc_error(-3, "Amount out of range",
self.nodes[2].sendmany,
amounts={ address: 10 },
conf_target=-1,
estimate_mode='dash/kB')
fee_per_kb = 0.0002500
explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
txid = self.nodes[2].sendmany(
amounts={ address: 10 },
conf_target=fee_per_kb,
estimate_mode='dash/kB',
)
self.nodes[2].generate(1)
self.sync_all(self.nodes[0:3])
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), explicit_fee_per_byte, count_bytes(self.nodes[2].gettransaction(txid)['hex']))
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal += Decimal('10')
assert_equal(self.nodes[0].getbalance(), node_0_bal)
# Sendmany with explicit fee (DUFF/B)
# Throw if no conf_target provided
assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
self.nodes[2].sendmany,
amounts={ address: 10 },
estimate_mode='duff/b')
# Throw if negative feerate
assert_raises_rpc_error(-3, "Amount out of range",
self.nodes[2].sendmany,
amounts={ address: 10 },
conf_target=-1,
estimate_mode='duff/b')
fee_duff_per_b = 2
fee_per_kb = fee_duff_per_b / 100000.0
explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
txid = self.nodes[2].sendmany(
amounts={ address: 10 },
conf_target=fee_duff_per_b,
estimate_mode='duff/b',
)
self.nodes[2].generate(1)
self.sync_all(self.nodes[0:3])
balance = self.nodes[2].getbalance()
node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_per_byte, count_bytes(self.nodes[2].gettransaction(txid)['hex']))
assert_equal(balance, node_2_bal)
node_0_bal += Decimal('10')
assert_equal(self.nodes[0].getbalance(), node_0_bal)
# check if we can list zero value tx as available coins
# 1. create raw_tx
@ -349,6 +402,74 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
# send with explicit dash/kb fee
self.log.info("test explicit fee (sendtoaddress as dash/kb)")
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
prebalance = self.nodes[2].getbalance()
assert prebalance > 2
address = self.nodes[1].getnewaddress()
# Throw if no conf_target provided
assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
self.nodes[2].sendtoaddress,
address=address,
amount=1.0,
estimate_mode='dash/Kb')
# Throw if negative feerate
assert_raises_rpc_error(-3, "Amount out of range",
self.nodes[2].sendtoaddress,
address=address,
amount=1.0,
conf_target=-1,
estimate_mode='dash/kb')
txid = self.nodes[2].sendtoaddress(
address=address,
amount=1.0,
conf_target=0.00002500,
estimate_mode='dash/kb',
)
tx_size = count_bytes(self.nodes[2].gettransaction(txid)['hex'])
self.sync_all(self.nodes[0:3])
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
postbalance = self.nodes[2].getbalance()
fee = prebalance - postbalance - Decimal('1')
assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
# send with explicit duff/b fee
self.sync_all(self.nodes[0:3])
self.log.info("test explicit fee (sendtoaddress as duff/b)")
self.nodes[0].generate(1)
prebalance = self.nodes[2].getbalance()
assert prebalance > 2
address = self.nodes[1].getnewaddress()
# Throw if no conf_target provided
assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
self.nodes[2].sendtoaddress,
address=address,
amount=1.0,
estimate_mode='duff/b')
# Throw if negative feerate
assert_raises_rpc_error(-3, "Amount out of range",
self.nodes[2].sendtoaddress,
address=address,
amount=1.0,
conf_target=-1,
estimate_mode='duff/b')
txid = self.nodes[2].sendtoaddress(
address=address,
amount=1.0,
conf_target=2,
estimate_mode='duff/B',
)
tx_size = count_bytes(self.nodes[2].gettransaction(txid)['hex'])
self.sync_all(self.nodes[0:3])
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
postbalance = self.nodes[2].getbalance()
fee = prebalance - postbalance - Decimal('1')
assert_fee_amount(fee, tx_size, Decimal('0.00002000'))
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)

View File

@ -25,7 +25,7 @@ class TxnMallTest(BitcoinTestFramework):
def setup_network(self):
# Start with split network:
super(TxnMallTest, self).setup_network()
super().setup_network()
self.disconnect_nodes(1, 2)
def run_test(self):