mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Merge pull request #5360 from vijaydasmp/21_17
backport: bitcoin#18875,11413,16946,17824,18601,18585,20271,19871,20039
This commit is contained in:
commit
b3bdcd05d8
7
doc/release-notes-11413.md
Normal file
7
doc/release-notes-11413.md
Normal 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.
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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); }
|
||||
};
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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, ""); }
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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"},
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user