mirror of
https://github.com/dashpay/dash.git
synced 2024-12-24 11:32:46 +01:00
Merge pull request #4568 from kittywhiskers/miscports
merge bitcoin#15588...#16475: backports
This commit is contained in:
commit
6af131f825
1
.gitignore
vendored
1
.gitignore
vendored
@ -91,6 +91,7 @@ libconftest.dylib*
|
||||
# Compilation and Qt preprocessor part
|
||||
*.qm
|
||||
Makefile
|
||||
!depends/Makefile
|
||||
dash-qt
|
||||
Dash-Qt.app
|
||||
background.tiff*
|
||||
|
@ -21,6 +21,8 @@ bench_bench_dash_SOURCES = \
|
||||
bench/bls_dkg.cpp \
|
||||
bench/checkblock.cpp \
|
||||
bench/checkqueue.cpp \
|
||||
bench/data.h \
|
||||
bench/data.cpp \
|
||||
bench/duplicate_inputs.cpp \
|
||||
bench/ecdsa.cpp \
|
||||
bench/examples.cpp \
|
||||
@ -83,7 +85,7 @@ CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES)
|
||||
|
||||
CLEANFILES += $(CLEAN_BITCOIN_BENCH)
|
||||
|
||||
bench/checkblock.cpp: bench/data/block813851.raw.h
|
||||
bench/data.cpp: bench/data/block813851.raw.h
|
||||
|
||||
bitcoin_bench: $(BENCH_BINARY)
|
||||
|
||||
@ -97,7 +99,7 @@ bench/data/%.raw.h: bench/data/%.raw
|
||||
@$(MKDIR_P) $(@D)
|
||||
@{ \
|
||||
echo "namespace raw_bench{" && \
|
||||
echo "static unsigned const char $(*F)[] = {" && \
|
||||
echo "static unsigned const char $(*F)_raw[] = {" && \
|
||||
$(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' && \
|
||||
echo "};};"; \
|
||||
} > "$@.new" && mv -f "$@.new" "$@"
|
||||
|
@ -3,39 +3,34 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <bench/bench.h>
|
||||
#include <bench/data.h>
|
||||
|
||||
#include <chainparams.h>
|
||||
#include <validation.h>
|
||||
#include <streams.h>
|
||||
#include <consensus/validation.h>
|
||||
|
||||
#include <bench/data/block813851.raw.h>
|
||||
|
||||
// These are the two major time-sinks which happen after we have fully received
|
||||
// a block off the wire, but before we can relay the block on to peers using
|
||||
// compact block relay.
|
||||
|
||||
static void DeserializeBlockTest(benchmark::Bench& bench)
|
||||
{
|
||||
CDataStream stream((const char*)raw_bench::block813851,
|
||||
(const char*)raw_bench::block813851+sizeof(raw_bench::block813851),
|
||||
SER_NETWORK, PROTOCOL_VERSION);
|
||||
CDataStream stream(benchmark::data::block813851, SER_NETWORK, PROTOCOL_VERSION);
|
||||
char a = '\0';
|
||||
stream.write(&a, 1); // Prevent compaction
|
||||
|
||||
bench.unit("block").run([&] {
|
||||
CBlock block;
|
||||
stream >> block;
|
||||
bool rewound = stream.Rewind(sizeof(raw_bench::block813851));
|
||||
bool rewound = stream.Rewind(benchmark::data::block813851.size());
|
||||
assert(rewound);
|
||||
});
|
||||
}
|
||||
|
||||
static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
|
||||
{
|
||||
CDataStream stream((const char*)raw_bench::block813851,
|
||||
(const char*)raw_bench::block813851+sizeof(raw_bench::block813851),
|
||||
SER_NETWORK, PROTOCOL_VERSION);
|
||||
CDataStream stream(benchmark::data::block813851, SER_NETWORK, PROTOCOL_VERSION);
|
||||
char a = '\0';
|
||||
stream.write(&a, 1); // Prevent compaction
|
||||
|
||||
@ -44,7 +39,7 @@ static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
|
||||
bench.unit("block").run([&] {
|
||||
CBlock block; // Note that CBlock caches its checked state, so we need to recreate it here
|
||||
stream >> block;
|
||||
bool rewound = stream.Rewind(sizeof(raw_bench::block813851));
|
||||
bool rewound = stream.Rewind(benchmark::data::block813851.size());
|
||||
assert(rewound);
|
||||
|
||||
CValidationState validationState;
|
||||
|
14
src/bench/data.cpp
Normal file
14
src/bench/data.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2019 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <bench/data.h>
|
||||
|
||||
namespace benchmark {
|
||||
namespace data {
|
||||
|
||||
#include <bench/data/block813851.raw.h>
|
||||
const std::vector<uint8_t> block813851{raw_bench::block813851_raw, raw_bench::block813851_raw + sizeof(raw_bench::block813851_raw) / sizeof(raw_bench::block813851_raw[0])};
|
||||
|
||||
} // namespace data
|
||||
} // namespace benchmark
|
19
src/bench/data.h
Normal file
19
src/bench/data.h
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2019 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_BENCH_DATA_H
|
||||
#define BITCOIN_BENCH_DATA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace benchmark {
|
||||
namespace data {
|
||||
|
||||
extern const std::vector<uint8_t> block813851;
|
||||
|
||||
} // namespace data
|
||||
} // namespace benchmark
|
||||
|
||||
#endif // BITCOIN_BENCH_DATA_H
|
@ -22,6 +22,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
constexpr static CAmount DEFAULT_MAX_RAW_TX_FEE{COIN / 10};
|
||||
|
||||
bool CCoinJoinEntry::AddScriptSig(const CTxIn& txin)
|
||||
{
|
||||
for (auto& txdsin : vecTxDSIn) {
|
||||
@ -370,7 +372,7 @@ bool CCoinJoin::IsCollateralValid(const CTransaction& txCollateral)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CValidationState validationState;
|
||||
if (!AcceptToMemoryPool(mempool, validationState, MakeTransactionRef(txCollateral), nullptr /* pfMissingInputs */, false /* bypass_limits */, maxTxFee /* nAbsurdFee */, true /* fDryRun */)) {
|
||||
if (!AcceptToMemoryPool(mempool, validationState, MakeTransactionRef(txCollateral), nullptr /* pfMissingInputs */, false /* bypass_limits */, DEFAULT_MAX_RAW_TX_FEE /* nAbsurdFee */, true /* fDryRun */)) {
|
||||
LogPrint(BCLog::COINJOIN, "CCoinJoin::IsCollateralValid -- didn't pass AcceptToMemoryPool()\n");
|
||||
return false;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <univalue.h>
|
||||
|
||||
CCoinJoinServer coinJoinServer;
|
||||
constexpr static CAmount DEFAULT_MAX_RAW_TX_FEE{COIN / 10};
|
||||
|
||||
void CCoinJoinServer::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman, bool enable_bip61)
|
||||
{
|
||||
@ -341,7 +342,7 @@ void CCoinJoinServer::CommitFinalTransaction(CConnman& connman)
|
||||
TRY_LOCK(cs_main, lockMain);
|
||||
CValidationState validationState;
|
||||
mempool.PrioritiseTransaction(hashTx, 0.1 * COIN);
|
||||
if (!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, nullptr /* pfMissingInputs */, false /* bypass_limits */, maxTxFee /* nAbsurdFee */)) {
|
||||
if (!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, nullptr /* pfMissingInputs */, false /* bypass_limits */, DEFAULT_MAX_RAW_TX_FEE /* nAbsurdFee */)) {
|
||||
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n");
|
||||
SetNull();
|
||||
// not much we can do in this case, just notify clients
|
||||
|
@ -236,8 +236,8 @@ CAmount CTransactionBuilder::GetFee(unsigned int nBytes) const
|
||||
if (nRequiredFee > nFeeCalc) {
|
||||
nFeeCalc = nRequiredFee;
|
||||
}
|
||||
if (nFeeCalc > ::maxTxFee) {
|
||||
nFeeCalc = ::maxTxFee;
|
||||
if (nFeeCalc > pwallet->m_default_max_tx_fee) {
|
||||
nFeeCalc = pwallet->m_default_max_tx_fee;
|
||||
}
|
||||
return nFeeCalc;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ void DummyWalletInit::AddWalletOptions() const
|
||||
"-disablewallet",
|
||||
"-instantsendnotify=<cmd>",
|
||||
"-keypool=<n>",
|
||||
"-maxtxfee=<amt>",
|
||||
"-rescan=<mode>",
|
||||
"-salvagewallet",
|
||||
"-spendzeroconfchange",
|
||||
|
18
src/init.cpp
18
src/init.cpp
@ -685,8 +685,6 @@ void SetupServerArgs()
|
||||
gArgs.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
|
||||
gArgs.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
|
||||
gArgs.AddArg("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
|
||||
gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction or raw transaction; setting this too low may abort large transactions (default: %s)",
|
||||
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
||||
gArgs.AddArg("-minsporkkeys=<n>", "Overrides minimum spork signers to change spork value. Only useful for regtest and devnet. Using this on mainnet or testnet will ban you.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
||||
gArgs.AddArg("-printpriority", strprintf("Log transaction fee per kB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
|
||||
gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -daemon. To disable logging to file, set -nodebuglogfile)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
||||
@ -1496,22 +1494,6 @@ bool AppInitParameterInteraction()
|
||||
dustRelayFee = CFeeRate(n);
|
||||
}
|
||||
|
||||
// This is required by both the wallet and node
|
||||
if (gArgs.IsArgSet("-maxtxfee"))
|
||||
{
|
||||
CAmount nMaxFee = 0;
|
||||
if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee))
|
||||
return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")));
|
||||
if (nMaxFee > HIGH_MAX_TX_FEE)
|
||||
InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
|
||||
maxTxFee = nMaxFee;
|
||||
if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee)
|
||||
{
|
||||
return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
|
||||
gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
|
||||
if (chainparams.RequireStandard() && !fRequireStandard)
|
||||
return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString()));
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <interfaces/wallet.h>
|
||||
#include <net.h>
|
||||
#include <node/coin.h>
|
||||
#include <node/transaction.h>
|
||||
#include <policy/fees.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/block.h>
|
||||
@ -37,7 +38,7 @@
|
||||
namespace interfaces {
|
||||
namespace {
|
||||
|
||||
class LockImpl : public Chain::Lock
|
||||
class LockImpl : public Chain::Lock, public UniqueLock<CCriticalSection>
|
||||
{
|
||||
Optional<int> getHeight() override
|
||||
{
|
||||
@ -170,16 +171,7 @@ class LockImpl : public Chain::Lock
|
||||
LockAnnotation lock(::cs_main);
|
||||
return CheckFinalTx(tx);
|
||||
}
|
||||
bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
return AcceptToMemoryPool(::mempool, state, tx, nullptr /* missing inputs */,
|
||||
false /* bypass limits */, absurd_fee);
|
||||
}
|
||||
};
|
||||
|
||||
class LockingStateImpl : public LockImpl, public UniqueLock<CCriticalSection>
|
||||
{
|
||||
using UniqueLock::UniqueLock;
|
||||
};
|
||||
|
||||
@ -217,17 +209,11 @@ public:
|
||||
{
|
||||
m_notifications->BlockDisconnected(*block);
|
||||
}
|
||||
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
|
||||
void ResendWalletTransactions(int64_t best_block_time, CConnman*) override
|
||||
void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
|
||||
{
|
||||
// `cs_main` is always held when this method is called, so it is safe to
|
||||
// call `assumeLocked`. This is awkward, and the `assumeLocked` method
|
||||
// should be able to be removed entirely if `ResendWalletTransactions`
|
||||
// is replaced by a wallet timer as suggested in
|
||||
// https://github.com/bitcoin/bitcoin/issues/15619
|
||||
auto locked_chain = m_chain.assumeLocked();
|
||||
m_notifications->ResendWalletTransactions(*locked_chain, best_block_time);
|
||||
m_notifications->UpdatedBlockTip();
|
||||
}
|
||||
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
|
||||
void NotifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr<const llmq::CChainLockSig>& clsig) override
|
||||
{
|
||||
m_notifications->NotifyChainLock(pindexChainLock, clsig);
|
||||
@ -284,13 +270,12 @@ class ChainImpl : public Chain
|
||||
public:
|
||||
std::unique_ptr<Chain::Lock> lock(bool try_lock) override
|
||||
{
|
||||
auto result = MakeUnique<LockingStateImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock);
|
||||
auto result = MakeUnique<LockImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock);
|
||||
if (try_lock && result && !*result) return {};
|
||||
// std::move necessary on some compilers due to conversion from
|
||||
// LockingStateImpl to Lock pointer
|
||||
// LockImpl to Lock pointer
|
||||
return std::move(result);
|
||||
}
|
||||
std::unique_ptr<Chain::Lock> assumeLocked() override { return MakeUnique<LockImpl>(); }
|
||||
bool findBlock(const uint256& hash, CBlock* block, int64_t* time, int64_t* time_max) override
|
||||
{
|
||||
CBlockIndex* index;
|
||||
@ -324,10 +309,13 @@ public:
|
||||
auto it = ::mempool.GetIter(txid);
|
||||
return it && (*it)->GetCountWithDescendants() > 1;
|
||||
}
|
||||
void relayTransaction(const uint256& txid) override
|
||||
bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) override
|
||||
{
|
||||
CInv inv(CCoinJoin::GetDSTX(txid) ? MSG_DSTX : MSG_TX, txid);
|
||||
g_connman->ForEachNode([&inv](CNode* node) { node->PushInventory(inv); });
|
||||
const TransactionError err = BroadcastTransaction(tx, err_string, max_tx_fee, relay, /*wait_callback*/ false);
|
||||
// Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures.
|
||||
// Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures
|
||||
// that Chain clients do not need to know about.
|
||||
return TransactionError::OK == err;
|
||||
}
|
||||
void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override
|
||||
{
|
||||
@ -362,9 +350,9 @@ public:
|
||||
CFeeRate relayMinFee() override { return ::minRelayTxFee; }
|
||||
CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; }
|
||||
CFeeRate relayDustFee() override { return ::dustRelayFee; }
|
||||
CAmount maxTxFee() override { return ::maxTxFee; }
|
||||
bool getPruneMode() override { return ::fPruneMode; }
|
||||
bool p2pEnabled() override { return g_connman != nullptr; }
|
||||
bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !::ChainstateActive().IsInitialBlockDownload(); }
|
||||
bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); }
|
||||
bool shutdownRequested() override { return ShutdownRequested(); }
|
||||
int64_t getAdjustedTime() override { return GetAdjustedTime(); }
|
||||
|
@ -52,10 +52,6 @@ class Handler;
|
||||
//! asynchronously
|
||||
//! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269).
|
||||
//!
|
||||
//! * The relayTransactions() and submitToMemoryPool() methods could be replaced
|
||||
//! with a higher-level broadcastTransaction method
|
||||
//! (https://github.com/bitcoin/bitcoin/pull/14978#issuecomment-459373984).
|
||||
//!
|
||||
//! * The initMessages() and loadWallet() methods which the wallet uses to send
|
||||
//! notifications to the GUI should go away when GUI and wallet can directly
|
||||
//! communicate with each other without going through the node
|
||||
@ -145,22 +141,12 @@ public:
|
||||
|
||||
//! Check if transaction will be final given chain height current time.
|
||||
virtual bool checkFinalTx(const CTransaction& tx) = 0;
|
||||
|
||||
//! Add transaction to memory pool if the transaction fee is below the
|
||||
//! amount specified by absurd_fee. Returns false if the transaction
|
||||
//! could not be added due to the fee or for another reason.
|
||||
virtual bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) = 0;
|
||||
};
|
||||
|
||||
//! Return Lock interface. Chain is locked when this is called, and
|
||||
//! unlocked when the returned interface is freed.
|
||||
virtual std::unique_ptr<Lock> lock(bool try_lock = false) = 0;
|
||||
|
||||
//! Return Lock interface assuming chain is already locked. This
|
||||
//! method is temporary and is only used in a few places to avoid changing
|
||||
//! behavior while code is transitioned to use the Chain::Lock interface.
|
||||
virtual std::unique_ptr<Lock> assumeLocked() = 0;
|
||||
|
||||
//! Return whether node has the block and optionally return block metadata
|
||||
//! or contents.
|
||||
//!
|
||||
@ -184,8 +170,10 @@ public:
|
||||
//! Check if transaction has descendants in mempool.
|
||||
virtual bool hasDescendantsInMempool(const uint256& txid) = 0;
|
||||
|
||||
//! Relay transaction.
|
||||
virtual void relayTransaction(const uint256& txid) = 0;
|
||||
//! Transaction is added to memory pool, if the transaction fee is below the
|
||||
//! amount specified by max_tx_fee, and broadcast to all peers if relay is set to true.
|
||||
//! Return false if the transaction could not be added due to the fee or for another reason.
|
||||
virtual bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) = 0;
|
||||
|
||||
//! Calculate mempool ancestor and descendant counts for the given transaction.
|
||||
virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) = 0;
|
||||
@ -211,18 +199,15 @@ public:
|
||||
//! Relay dust fee setting (-dustrelayfee), reflecting lowest rate it's economical to spend.
|
||||
virtual CFeeRate relayDustFee() = 0;
|
||||
|
||||
//! Node max tx fee setting (-maxtxfee).
|
||||
//! This could be replaced by a per-wallet max fee, as proposed at
|
||||
//! https://github.com/bitcoin/bitcoin/issues/15355
|
||||
//! But for the time being, wallets call this to access the node setting.
|
||||
virtual CAmount maxTxFee() = 0;
|
||||
|
||||
//! Check if pruning is enabled.
|
||||
virtual bool getPruneMode() = 0;
|
||||
|
||||
//! Check if p2p enabled.
|
||||
virtual bool p2pEnabled() = 0;
|
||||
|
||||
//! Check if the node is ready to broadcast transactions.
|
||||
virtual bool isReadyToBroadcast() = 0;
|
||||
|
||||
//! Check if in IBD.
|
||||
virtual bool isInitialBlockDownload() = 0;
|
||||
|
||||
@ -256,8 +241,8 @@ public:
|
||||
virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason) {}
|
||||
virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {}
|
||||
virtual void BlockDisconnected(const CBlock& block) {}
|
||||
virtual void UpdatedBlockTip() {}
|
||||
virtual void ChainStateFlushed(const CBlockLocator& locator) {}
|
||||
virtual void ResendWalletTransactions(Lock& locked_chain, int64_t best_block_time) {}
|
||||
virtual void NotifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr<const llmq::CChainLockSig>& clsig) {}
|
||||
virtual void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr<const llmq::CInstantSendLock>& islock) {}
|
||||
};
|
||||
|
@ -344,7 +344,6 @@ public:
|
||||
}
|
||||
}
|
||||
bool getNetworkActive() override { return g_connman && g_connman->GetNetworkActive(); }
|
||||
CAmount getMaxTxFee() override { return ::maxTxFee; }
|
||||
CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) override
|
||||
{
|
||||
FeeCalculation fee_calc;
|
||||
|
@ -229,9 +229,6 @@ public:
|
||||
//! Get network active.
|
||||
virtual bool getNetworkActive() = 0;
|
||||
|
||||
//! Get max tx fee.
|
||||
virtual CAmount getMaxTxFee() = 0;
|
||||
|
||||
//! Estimate smart fee.
|
||||
virtual CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) = 0;
|
||||
|
||||
|
@ -42,32 +42,6 @@
|
||||
namespace interfaces {
|
||||
namespace {
|
||||
|
||||
class PendingWalletTxImpl : public PendingWalletTx
|
||||
{
|
||||
public:
|
||||
explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet), m_key(&wallet) {}
|
||||
|
||||
const CTransaction& get() override { return *m_tx; }
|
||||
|
||||
bool commit(WalletValueMap value_map,
|
||||
WalletOrderForm order_form,
|
||||
std::string& reject_reason) override
|
||||
{
|
||||
auto locked_chain = m_wallet.chain().lock();
|
||||
LOCK2(mempool.cs, m_wallet.cs_wallet);
|
||||
CValidationState state;
|
||||
if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_key, state)) {
|
||||
reject_reason = state.GetRejectReason();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CTransactionRef m_tx;
|
||||
CWallet& m_wallet;
|
||||
CReserveKey m_key;
|
||||
};
|
||||
|
||||
//! Construct wallet tx struct.
|
||||
WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx)
|
||||
{
|
||||
@ -308,7 +282,7 @@ public:
|
||||
LOCK2(cs_main, m_wallet->cs_wallet);
|
||||
return m_wallet->ListProTxCoins(outputs);
|
||||
}
|
||||
std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients,
|
||||
CTransactionRef createTransaction(const std::vector<CRecipient>& recipients,
|
||||
const CCoinControl& coin_control,
|
||||
bool sign,
|
||||
int& change_pos,
|
||||
@ -317,12 +291,28 @@ public:
|
||||
{
|
||||
auto locked_chain = m_wallet->chain().lock();
|
||||
LOCK2(mempool.cs, m_wallet->cs_wallet);
|
||||
auto pending = MakeUnique<PendingWalletTxImpl>(*m_wallet);
|
||||
if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_key, fee, change_pos,
|
||||
CReserveKey m_key(m_wallet.get());
|
||||
CTransactionRef tx;
|
||||
if (!m_wallet->CreateTransaction(*locked_chain, recipients, tx, m_key, fee, change_pos,
|
||||
fail_reason, coin_control, sign)) {
|
||||
return {};
|
||||
}
|
||||
return std::move(pending);
|
||||
return tx;
|
||||
}
|
||||
bool commitTransaction(CTransactionRef tx,
|
||||
WalletValueMap value_map,
|
||||
WalletOrderForm order_form,
|
||||
std::string& reject_reason) override
|
||||
{
|
||||
auto locked_chain = m_wallet->chain().lock();
|
||||
LOCK2(mempool.cs, m_wallet->cs_wallet);
|
||||
CReserveKey m_key(m_wallet.get());
|
||||
CValidationState state;
|
||||
if (!m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form), m_key, state)) {
|
||||
reject_reason = state.GetRejectReason();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); }
|
||||
bool abandonTransaction(const uint256& txid) override
|
||||
@ -544,6 +534,7 @@ public:
|
||||
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
|
||||
bool IsWalletFlagSet(uint64_t flag) override { return m_wallet->IsWalletFlagSet(flag); }
|
||||
CoinJoin::Client& coinJoin() override { return m_coinjoin; }
|
||||
CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }
|
||||
void remove() override
|
||||
{
|
||||
RemoveWallet(m_wallet);
|
||||
|
@ -32,7 +32,6 @@ struct CRecipient;
|
||||
namespace interfaces {
|
||||
|
||||
class Handler;
|
||||
class PendingWalletTx;
|
||||
struct WalletAddress;
|
||||
struct WalletBalances;
|
||||
struct WalletTx;
|
||||
@ -159,13 +158,19 @@ public:
|
||||
virtual void listProTxCoins(std::vector<COutPoint>& vOutpts) = 0;
|
||||
|
||||
//! Create transaction.
|
||||
virtual std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients,
|
||||
virtual CTransactionRef createTransaction(const std::vector<CRecipient>& recipients,
|
||||
const CCoinControl& coin_control,
|
||||
bool sign,
|
||||
int& change_pos,
|
||||
CAmount& fee,
|
||||
std::string& fail_reason) = 0;
|
||||
|
||||
//! Commit transaction.
|
||||
virtual bool commitTransaction(CTransactionRef tx,
|
||||
WalletValueMap value_map,
|
||||
WalletOrderForm order_form,
|
||||
std::string& reject_reason) = 0;
|
||||
|
||||
//! Return whether transaction can be abandoned.
|
||||
virtual bool transactionCanBeAbandoned(const uint256& txid) = 0;
|
||||
|
||||
@ -266,6 +271,9 @@ public:
|
||||
|
||||
virtual CoinJoin::Client& coinJoin() = 0;
|
||||
|
||||
//! Get max tx fee.
|
||||
virtual CAmount getDefaultMaxTxFee() = 0;
|
||||
|
||||
// Remove wallet.
|
||||
virtual void remove() = 0;
|
||||
|
||||
@ -310,21 +318,6 @@ public:
|
||||
virtual std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0;
|
||||
};
|
||||
|
||||
//! Tracking object returned by CreateTransaction and passed to CommitTransaction.
|
||||
class PendingWalletTx
|
||||
{
|
||||
public:
|
||||
virtual ~PendingWalletTx() {}
|
||||
|
||||
//! Get transaction data.
|
||||
virtual const CTransaction& get() = 0;
|
||||
|
||||
//! Send pending transaction and commit to wallet.
|
||||
virtual bool commit(WalletValueMap value_map,
|
||||
WalletOrderForm order_form,
|
||||
std::string& reject_reason) = 0;
|
||||
};
|
||||
|
||||
//! Information about one wallet address.
|
||||
struct WalletAddress
|
||||
{
|
||||
|
@ -237,8 +237,6 @@ namespace {
|
||||
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */
|
||||
std::deque<std::pair<int64_t, MapRelay::iterator>> vRelayExpiration GUARDED_BY(cs_main);
|
||||
|
||||
std::atomic<int64_t> nTimeBestReceived(0); // Used only to inform the wallet of when we last received a block
|
||||
|
||||
struct IteratorComparator
|
||||
{
|
||||
template<typename I>
|
||||
@ -1314,8 +1312,6 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB
|
||||
});
|
||||
connman->WakeMessageHandler();
|
||||
}
|
||||
|
||||
nTimeBestReceived = GetTime();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4381,21 +4377,6 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
|
||||
}
|
||||
}
|
||||
|
||||
// Resend wallet transactions that haven't gotten in a block yet
|
||||
// Except during reindex, importing and IBD, when old wallet
|
||||
// transactions become unconfirmed and spams other nodes.
|
||||
if (!fReindex && !fImporting && !::ChainstateActive().IsInitialBlockDownload())
|
||||
{
|
||||
static int64_t nLastBroadcastTime = 0;
|
||||
// HACK: Call this only once every few seconds. SendMessages is called once per peer, which makes this signal very expensive
|
||||
// The proper solution would be to move this out of here, but this is not worth the effort right now as bitcoin#15632 will later do this.
|
||||
// Luckily, the Broadcast signal is not used for anything else then CWallet::ResendWalletTransactionsBefore.
|
||||
if (nNow - nLastBroadcastTime >= 5000000) {
|
||||
GetMainSignals().Broadcast(nTimeBestReceived, connman);
|
||||
nLastBroadcastTime = nNow;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Try sending block announcements via headers
|
||||
//
|
||||
|
@ -13,26 +13,30 @@
|
||||
|
||||
#include <future>
|
||||
|
||||
TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, std::string& err_string, const CAmount& highfee, const bool bypass_limits)
|
||||
TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback, bool bypass_limits)
|
||||
{
|
||||
assert(g_connman);
|
||||
std::promise<void> promise;
|
||||
hashTx = tx->GetHash();
|
||||
uint256 hashTx = tx->GetHash();
|
||||
bool callback_set = false;
|
||||
|
||||
{ // cs_main scope
|
||||
LOCK(cs_main);
|
||||
// If the transaction is already confirmed in the chain, don't do anything
|
||||
// and return early.
|
||||
CCoinsViewCache &view = ::ChainstateActive().CoinsTip();
|
||||
bool fHaveChain = false;
|
||||
for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
|
||||
for (size_t o = 0; o < tx->vout.size(); o++) {
|
||||
const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o));
|
||||
fHaveChain = !existingCoin.IsSpent();
|
||||
// IsSpent doesnt mean the coin is spent, it means the output doesnt' exist.
|
||||
// So if the output does exist, then this transaction exists in the chain.
|
||||
if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN;
|
||||
}
|
||||
bool fHaveMempool = mempool.exists(hashTx);
|
||||
if (!fHaveMempool && !fHaveChain) {
|
||||
// push to local node and sync with wallets
|
||||
if (!mempool.exists(hashTx)) {
|
||||
// Transaction is not already in the mempool. Submit it.
|
||||
CValidationState state;
|
||||
bool fMissingInputs;
|
||||
if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
|
||||
bypass_limits, highfee)) {
|
||||
bypass_limits, max_tx_fee)) {
|
||||
if (state.IsInvalid()) {
|
||||
err_string = FormatStateMessage(state);
|
||||
return TransactionError::MEMPOOL_REJECTED;
|
||||
@ -43,33 +47,37 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx,
|
||||
err_string = FormatStateMessage(state);
|
||||
return TransactionError::MEMPOOL_ERROR;
|
||||
}
|
||||
} else {
|
||||
// If wallet is enabled, ensure that the wallet has been made aware
|
||||
// of the new transaction prior to returning. This prevents a race
|
||||
// where a user might call sendrawtransaction with a transaction
|
||||
// to/from their wallet, immediately call some wallet RPC, and get
|
||||
// a stale result because callbacks have not yet been processed.
|
||||
}
|
||||
|
||||
// Transaction was accepted to the mempool.
|
||||
|
||||
if (wait_callback) {
|
||||
// For transactions broadcast from outside the wallet, make sure
|
||||
// that the wallet has been notified of the transaction before
|
||||
// continuing.
|
||||
//
|
||||
// This prevents a race where a user might call sendrawtransaction
|
||||
// with a transaction to/from their wallet, immediately call some
|
||||
// wallet RPC, and get a stale result because callbacks have not
|
||||
// yet been processed.
|
||||
CallFunctionInValidationInterfaceQueue([&promise] {
|
||||
promise.set_value();
|
||||
});
|
||||
callback_set = true;
|
||||
}
|
||||
} else if (fHaveChain) {
|
||||
return TransactionError::ALREADY_IN_CHAIN;
|
||||
} else {
|
||||
// Make sure we don't block forever if re-sending
|
||||
// a transaction already in mempool.
|
||||
promise.set_value();
|
||||
}
|
||||
|
||||
} // cs_main
|
||||
|
||||
promise.get_future().wait();
|
||||
|
||||
if (!g_connman) {
|
||||
return TransactionError::P2P_DISABLED;
|
||||
if (callback_set) {
|
||||
// Wait until Validation Interface clients have been notified of the
|
||||
// transaction entering the mempool.
|
||||
promise.get_future().wait();
|
||||
}
|
||||
|
||||
g_connman->RelayTransaction(*tx);
|
||||
if (relay) {
|
||||
g_connman->RelayTransaction(*tx);
|
||||
}
|
||||
|
||||
return TransactionError::OK;
|
||||
}
|
||||
|
@ -11,14 +11,21 @@
|
||||
#include <util/error.h>
|
||||
|
||||
/**
|
||||
* Broadcast a transaction
|
||||
* Submit a transaction to the mempool and (optionally) relay it to all P2P peers.
|
||||
*
|
||||
* Mempool submission can be synchronous (will await mempool entry notification
|
||||
* over the CValidationInterface) or asynchronous (will submit and not wait for
|
||||
* notification), depending on the value of wait_callback. wait_callback MUST
|
||||
* NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid
|
||||
* deadlock.
|
||||
*
|
||||
* @param[in] tx the transaction to broadcast
|
||||
* @param[out] &txid the txid of the transaction, if successfully broadcast
|
||||
* @param[out] &err_string reference to std::string to fill with error string if available
|
||||
* @param[in] highfee Reject txs with fees higher than this (if 0, accept any fee)
|
||||
* @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee)
|
||||
* @param[in] relay flag if both mempool insertion and p2p relay are requested
|
||||
* @param[in] wait_callback, wait until callbacks have been processed to avoid stale result due to a sequentially RPC.
|
||||
* return error
|
||||
*/
|
||||
[[nodiscard]] TransactionError BroadcastTransaction(CTransactionRef tx, uint256& txid, std::string& err_string, const CAmount& highfee, const bool bypass_limits = false);
|
||||
[[nodiscard]] TransactionError BroadcastTransaction(CTransactionRef tx, std::string& err_string, const CAmount& highfee, bool relay, bool wait_callback, bool bypass_limits = false);
|
||||
|
||||
#endif // BITCOIN_NODE_TRANSACTION_H
|
||||
|
@ -411,7 +411,7 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
|
||||
if (m_coin_control->IsUsingCoinJoin()) {
|
||||
// append number of inputs
|
||||
questionString.append("<hr />");
|
||||
int nInputs = currentTransaction.getWtx()->get().vin.size();
|
||||
int nInputs = currentTransaction.getWtx()->vin.size();
|
||||
questionString.append(tr("This transaction will consume %n input(s)", "", nInputs));
|
||||
|
||||
// warn about potential privacy issues when spending too many inputs at once
|
||||
@ -464,7 +464,7 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
|
||||
accept();
|
||||
m_coin_control->UnSelectAll();
|
||||
coinControlUpdateLabels();
|
||||
Q_EMIT coinsSent(currentTransaction.getWtx()->get().GetHash());
|
||||
Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash());
|
||||
}
|
||||
fNewRecipientAllowed = true;
|
||||
}
|
||||
@ -666,7 +666,7 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn
|
||||
msgParams.second = CClientUIInterface::MSG_ERROR;
|
||||
break;
|
||||
case WalletModel::AbsurdFee:
|
||||
msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->node().getMaxTxFee()));
|
||||
msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
|
||||
break;
|
||||
case WalletModel::PaymentRequestExpired:
|
||||
msgParams.first = tr("Payment request expired.");
|
||||
|
@ -270,9 +270,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
|
||||
}
|
||||
|
||||
// reject absurdly high fee. (This can never happen because the
|
||||
// wallet caps the fee at maxTxFee. This merely serves as a
|
||||
// wallet caps the fee at m_default_max_tx_fee. This merely serves as a
|
||||
// belt-and-suspenders check)
|
||||
if (nFeeRequired > m_node.getMaxTxFee())
|
||||
if (nFeeRequired > m_wallet->getDefaultMaxTxFee())
|
||||
return AbsurdFee;
|
||||
|
||||
return SendCoinsReturn(OK);
|
||||
@ -312,11 +312,11 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran
|
||||
|
||||
auto& newTx = transaction.getWtx();
|
||||
std::string rejectReason;
|
||||
if (!newTx->commit(std::move(mapValue), std::move(vOrderForm), rejectReason))
|
||||
if (!wallet().commitTransaction(newTx, std::move(mapValue), std::move(vOrderForm), rejectReason))
|
||||
return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(rejectReason));
|
||||
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << newTx->get();
|
||||
ssTx << *newTx;
|
||||
transaction_array.append(ssTx.data(), ssTx.size());
|
||||
}
|
||||
|
||||
|
@ -22,14 +22,14 @@ QList<SendCoinsRecipient> WalletModelTransaction::getRecipients() const
|
||||
return recipients;
|
||||
}
|
||||
|
||||
std::unique_ptr<interfaces::PendingWalletTx>& WalletModelTransaction::getWtx()
|
||||
CTransactionRef& WalletModelTransaction::getWtx()
|
||||
{
|
||||
return wtx;
|
||||
}
|
||||
|
||||
unsigned int WalletModelTransaction::getTransactionSize()
|
||||
{
|
||||
return wtx ? ::GetSerializeSize(wtx->get(), SER_NETWORK, PROTOCOL_VERSION) : 0;
|
||||
return wtx != nullptr ? ::GetSerializeSize(*wtx, SER_NETWORK, PROTOCOL_VERSION) : 0;
|
||||
}
|
||||
|
||||
CAmount WalletModelTransaction::getTransactionFee() const
|
||||
@ -60,7 +60,7 @@ void WalletModelTransaction::reassignAmounts()
|
||||
if (out.amount() <= 0) continue;
|
||||
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
|
||||
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
|
||||
for (const auto& txout : wtx->get().vout) {
|
||||
for (const auto& txout : wtx.get()->vout) {
|
||||
if (txout.scriptPubKey == scriptPubKey) {
|
||||
subtotal += txout.nValue;
|
||||
break;
|
||||
@ -72,7 +72,7 @@ void WalletModelTransaction::reassignAmounts()
|
||||
else // normal recipient (no payment request)
|
||||
#endif
|
||||
{
|
||||
for (const auto& txout : wtx->get().vout) {
|
||||
for (const auto& txout : wtx.get()->vout) {
|
||||
CScript scriptPubKey = GetScriptForDestination(DecodeDestination(rcp.address.toStdString()));
|
||||
if (txout.scriptPubKey == scriptPubKey) {
|
||||
rcp.amount = txout.nValue;
|
||||
|
@ -16,7 +16,6 @@ class SendCoinsRecipient;
|
||||
|
||||
namespace interfaces {
|
||||
class Node;
|
||||
class PendingWalletTx;
|
||||
}
|
||||
|
||||
/** Data model for a walletmodel transaction. */
|
||||
@ -27,7 +26,7 @@ public:
|
||||
|
||||
QList<SendCoinsRecipient> getRecipients() const;
|
||||
|
||||
std::unique_ptr<interfaces::PendingWalletTx>& getWtx();
|
||||
CTransactionRef& getWtx();
|
||||
unsigned int getTransactionSize();
|
||||
|
||||
void setTransactionFee(const CAmount& newFee);
|
||||
@ -39,7 +38,7 @@ public:
|
||||
|
||||
private:
|
||||
QList<SendCoinsRecipient> recipients;
|
||||
std::unique_ptr<interfaces::PendingWalletTx> wtx;
|
||||
CTransactionRef wtx;
|
||||
CAmount fee;
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2015 The Bitcoin Core developers
|
||||
// Copyright (c) 2009-2019 The Bitcoin Core developers
|
||||
// Copyright (c) 2014-2021 The Dash Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
@ -2026,9 +2026,7 @@ static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t)
|
||||
|
||||
static UniValue getblockstats(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) {
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"getblockstats",
|
||||
const RPCHelpMan help{"getblockstats",
|
||||
"\nCompute per block statistics for a given window. All amounts are in duffs.\n"
|
||||
"It won't work for some heights with pruning.\n"
|
||||
"It won't work without -txindex for utxo_size_inc, *fee or *feerate stats.\n",
|
||||
@ -2080,7 +2078,9 @@ static UniValue getblockstats(const JSONRPCRequest& request)
|
||||
HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'")
|
||||
+ HelpExampleRpc("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'")
|
||||
},
|
||||
}.ToString());
|
||||
};
|
||||
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
|
||||
throw std::runtime_error(help.ToString());
|
||||
}
|
||||
|
||||
if (g_txindex) {
|
||||
|
@ -111,10 +111,12 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "signrawtransactionwithkey", 2, "prevtxs" },
|
||||
{ "signrawtransactionwithwallet", 1, "prevtxs" },
|
||||
{ "sendrawtransaction", 1, "allowhighfees" },
|
||||
{ "sendrawtransaction", 1, "maxfeerate" },
|
||||
{ "sendrawtransaction", 2, "instantsend" },
|
||||
{ "sendrawtransaction", 3, "bypasslimits" },
|
||||
{ "testmempoolaccept", 0, "rawtxs" },
|
||||
{ "testmempoolaccept", 1, "allowhighfees" },
|
||||
{ "testmempoolaccept", 1, "maxfeerate" },
|
||||
{ "combinerawtransaction", 0, "txs" },
|
||||
{ "fundrawtransaction", 1, "options" },
|
||||
{ "walletcreatefundedpsbt", 0, "inputs" },
|
||||
|
@ -584,13 +584,7 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request)
|
||||
|
||||
static UniValue setban(const JSONRPCRequest& request)
|
||||
{
|
||||
std::string strCommand;
|
||||
if (!request.params[1].isNull())
|
||||
strCommand = request.params[1].get_str();
|
||||
if (request.fHelp || request.params.size() < 2 ||
|
||||
(strCommand != "add" && strCommand != "remove"))
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"setban",
|
||||
const RPCHelpMan help{"setban",
|
||||
"\nAttempts to add or remove an IP/Subnet from the banned list.\n",
|
||||
{
|
||||
{"subnet", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP/Subnet (see getpeerinfo for nodes IP) with an optional netmask (default is /32 = single IP)"},
|
||||
@ -604,7 +598,13 @@ static UniValue setban(const JSONRPCRequest& request)
|
||||
+ HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"")
|
||||
+ HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400")
|
||||
},
|
||||
}.ToString());
|
||||
};
|
||||
std::string strCommand;
|
||||
if (!request.params[1].isNull())
|
||||
strCommand = request.params[1].get_str();
|
||||
if (request.fHelp || !help.IsValidNumArgs(request.params.size()) || (strCommand != "add" && strCommand != "remove")) {
|
||||
throw std::runtime_error(help.ToString());
|
||||
}
|
||||
if (!g_banman) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded");
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <script/standard.h>
|
||||
#include <txmempool.h>
|
||||
#include <uint256.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <util/validation.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <validation.h>
|
||||
@ -47,6 +48,11 @@
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
/** High fee for sendrawtransaction and testmempoolaccept.
|
||||
* By default, transaction with a fee higher than this will be rejected by the
|
||||
* RPCs. This can be overriden with the maxfeerate argument.
|
||||
*/
|
||||
constexpr static CAmount DEFAULT_MAX_RAW_TX_FEE{COIN / 10};
|
||||
|
||||
void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
|
||||
{
|
||||
@ -751,14 +757,11 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
|
||||
|
||||
UniValue sendrawtransaction(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"sendrawtransaction",
|
||||
"\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n"
|
||||
const RPCHelpMan help{"sendrawtransaction", "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n"
|
||||
"\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n",
|
||||
{
|
||||
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
|
||||
{"allowhighfees", RPCArg::Type::BOOL, /* default */ "false", "Allow high fees"},
|
||||
{"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
|
||||
{"instantsend", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Deprecated and ignored"},
|
||||
{"bypasslimits", RPCArg::Type::BOOL, /* default_val */ "false", "Bypass transaction policy limits"},
|
||||
},
|
||||
@ -775,35 +778,50 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
|
||||
},
|
||||
}.ToString());
|
||||
};
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL});
|
||||
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
|
||||
throw std::runtime_error(help.ToString());
|
||||
}
|
||||
|
||||
RPCTypeCheck(request.params, {
|
||||
UniValue::VSTR,
|
||||
UniValueType(), // NUM or BOOL, checked later
|
||||
UniValue::VBOOL
|
||||
});
|
||||
|
||||
// parse hex string from parameter
|
||||
CMutableTransaction mtx;
|
||||
if (!DecodeHexTx(mtx, request.params[0].get_str()))
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
||||
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
|
||||
bool allowhighfees = false;
|
||||
if (!request.params[1].isNull()) allowhighfees = request.params[1].get_bool();
|
||||
CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
|
||||
|
||||
// TODO: temporary migration code for old clients. Remove in v0.20
|
||||
if (request.params[1].isBool()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
|
||||
} else if (request.params[1].isNum()) {
|
||||
CFeeRate fr(AmountFromValue(request.params[1]));
|
||||
max_raw_tx_fee = fr.GetFee(GetVirtualTransactionSize(*tx));
|
||||
} else if (!request.params[1].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
|
||||
}
|
||||
|
||||
bool bypass_limits = false;
|
||||
if (!request.params[3].isNull()) bypass_limits = request.params[3].get_bool();
|
||||
const CAmount highfee{allowhighfees ? 0 : ::maxTxFee};
|
||||
uint256 txid;
|
||||
std::string err_string;
|
||||
const TransactionError err = BroadcastTransaction(tx, txid, err_string, highfee, bypass_limits);
|
||||
AssertLockNotHeld(cs_main);
|
||||
const TransactionError err = BroadcastTransaction(tx, err_string, max_raw_tx_fee, /* relay */ true, /* wait_callback */ true, bypass_limits);
|
||||
if (TransactionError::OK != err) {
|
||||
throw JSONRPCTransactionError(err, err_string);
|
||||
}
|
||||
|
||||
return txid.GetHex();
|
||||
return tx->GetHash().GetHex();
|
||||
}
|
||||
|
||||
static UniValue testmempoolaccept(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"testmempoolaccept",
|
||||
const RPCHelpMan help{"testmempoolaccept",
|
||||
"\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n"
|
||||
"\nThis checks if the transaction violates the consensus or policy rules.\n"
|
||||
"\nSee sendrawtransaction call.\n",
|
||||
@ -814,7 +832,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
|
||||
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
|
||||
},
|
||||
},
|
||||
{"allowhighfees", RPCArg::Type::BOOL, /* default */ "false", "Allow high fees"},
|
||||
{"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
|
||||
},
|
||||
RPCResult{
|
||||
"[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n"
|
||||
@ -836,10 +854,17 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]")
|
||||
},
|
||||
}.ToString());
|
||||
};
|
||||
|
||||
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
|
||||
throw std::runtime_error(help.ToString());
|
||||
}
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VBOOL});
|
||||
RPCTypeCheck(request.params, {
|
||||
UniValue::VARR,
|
||||
UniValueType(), // NUM or BOOL, checked later
|
||||
});
|
||||
|
||||
if (request.params[0].get_array().size() != 1) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now");
|
||||
}
|
||||
@ -851,9 +876,15 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
|
||||
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
|
||||
const uint256& tx_hash = tx->GetHash();
|
||||
|
||||
CAmount max_raw_tx_fee = ::maxTxFee;
|
||||
if (!request.params[1].isNull() && request.params[1].get_bool()) {
|
||||
max_raw_tx_fee = 0;
|
||||
CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
|
||||
// TODO: temporary migration code for old clients. Remove in v0.20
|
||||
if (request.params[1].isBool()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
|
||||
} else if (request.params[1].isNum()) {
|
||||
CFeeRate fr(AmountFromValue(request.params[1]));
|
||||
max_raw_tx_fee = fr.GetFee(GetVirtualTransactionSize(*tx));
|
||||
} else if (!request.params[1].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VARR);
|
||||
@ -1375,10 +1406,10 @@ static const CRPCCommand commands[] =
|
||||
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime"} },
|
||||
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} },
|
||||
{ "rawtransactions", "decodescript", &decodescript, {"hexstring"} },
|
||||
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees","instantsend","bypasslimits"} },
|
||||
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees|maxfeerate","instantsend","bypasslimits"} },
|
||||
{ "rawtransactions", "combinerawtransaction", &combinerawtransaction, {"txs"} },
|
||||
{ "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
|
||||
{ "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} },
|
||||
{ "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees|maxfeerate"} },
|
||||
{ "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} },
|
||||
{ "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} },
|
||||
{ "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2017 The Bitcoin Core developers
|
||||
// Copyright (c) 2017-2019 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@ -410,6 +410,17 @@ std::string RPCExamples::ToDescriptionString() const
|
||||
return m_examples.empty() ? m_examples : "\nExamples:\n" + m_examples;
|
||||
}
|
||||
|
||||
bool RPCHelpMan::IsValidNumArgs(size_t num_args) const
|
||||
{
|
||||
size_t num_required_args = 0;
|
||||
for (size_t n = m_args.size(); n > 0; --n) {
|
||||
if (!m_args.at(n - 1).IsOptional()) {
|
||||
num_required_args = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return num_required_args <= num_args && num_args <= m_args.size();
|
||||
}
|
||||
std::string RPCHelpMan::ToString() const
|
||||
{
|
||||
std::string ret;
|
||||
@ -418,12 +429,7 @@ std::string RPCHelpMan::ToString() const
|
||||
ret += m_name;
|
||||
bool was_optional{false};
|
||||
for (const auto& arg : m_args) {
|
||||
bool optional;
|
||||
if (arg.m_fallback.which() == 1) {
|
||||
optional = true;
|
||||
} else {
|
||||
optional = RPCArg::Optional::NO != boost::get<RPCArg::Optional>(arg.m_fallback);
|
||||
}
|
||||
const bool optional = arg.IsOptional();
|
||||
ret += " ";
|
||||
if (optional) {
|
||||
if (!was_optional) ret += "( ";
|
||||
@ -465,6 +471,15 @@ std::string RPCHelpMan::ToString() const
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool RPCArg::IsOptional() const
|
||||
{
|
||||
if (m_fallback.which() == 1) {
|
||||
return true;
|
||||
} else {
|
||||
return RPCArg::Optional::NO != boost::get<RPCArg::Optional>(m_fallback);
|
||||
}
|
||||
}
|
||||
|
||||
std::string RPCArg::ToDescriptionString() const
|
||||
{
|
||||
std::string ret;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2017 The Bitcoin Core developers
|
||||
// Copyright (c) 2017-2019 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@ -107,7 +107,7 @@ struct RPCArg {
|
||||
/** Required arg */
|
||||
NO,
|
||||
/**
|
||||
* Optinal arg that is a named argument and has a default value of
|
||||
* Optional arg that is a named argument and has a default value of
|
||||
* `null`. When possible, the default value should be specified.
|
||||
*/
|
||||
OMITTED_NAMED_ARG,
|
||||
@ -164,6 +164,8 @@ struct RPCArg {
|
||||
assert(type == Type::ARR || type == Type::OBJ);
|
||||
}
|
||||
|
||||
bool IsOptional() const;
|
||||
|
||||
/**
|
||||
* Return the type string of the argument.
|
||||
* Set oneline to allow it to be overridden by a custom oneline type string (m_oneline_description).
|
||||
@ -239,6 +241,8 @@ public:
|
||||
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples);
|
||||
|
||||
std::string ToString() const;
|
||||
/** If the supplied number of args is neither too small nor too high */
|
||||
bool IsValidNumArgs(size_t num_args) const;
|
||||
|
||||
private:
|
||||
const std::string m_name;
|
||||
|
@ -156,7 +156,6 @@ uint256 hashAssumeValid;
|
||||
arith_uint256 nMinimumChainWork;
|
||||
|
||||
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
|
||||
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
|
||||
|
||||
CBlockPolicyEstimator feeEstimator;
|
||||
CTxMemPool mempool(&feeEstimator);
|
||||
|
@ -53,12 +53,6 @@ struct LockPoints;
|
||||
|
||||
/** Default for -minrelaytxfee, minimum relay fee for transactions */
|
||||
static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000;
|
||||
//! -maxtxfee default
|
||||
static const CAmount DEFAULT_TRANSACTION_MAXFEE = COIN / 10;
|
||||
//! Discourage users to set fees higher than this amount (in duffs) per kB
|
||||
static const CAmount HIGH_TX_FEE_PER_KB = COIN / 100;
|
||||
//! -maxtxfee will warn if called with a higher fee than this amount (in duffs)
|
||||
static const CAmount HIGH_MAX_TX_FEE = 100 * HIGH_TX_FEE_PER_KB;
|
||||
/** Default for -limitancestorcount, max number of in-mempool ancestors */
|
||||
static const unsigned int DEFAULT_ANCESTOR_LIMIT = 25;
|
||||
/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */
|
||||
@ -146,8 +140,6 @@ extern bool fCheckpointsEnabled;
|
||||
extern size_t nCoinCacheUsage;
|
||||
/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
|
||||
extern CFeeRate minRelayTxFee;
|
||||
/** Absolute maximum transaction fee (in duffs) used by wallet and mempool (rejects high fee in sendrawtransaction) */
|
||||
extern CAmount maxTxFee;
|
||||
/** If the tip is older than this (in seconds), the node is considered to be in initial block download. */
|
||||
extern int64_t nMaxTipAge;
|
||||
|
||||
|
@ -25,7 +25,6 @@ struct ValidationInterfaceConnections {
|
||||
boost::signals2::scoped_connection BlockDisconnected;
|
||||
boost::signals2::scoped_connection TransactionRemovedFromMempool;
|
||||
boost::signals2::scoped_connection ChainStateFlushed;
|
||||
boost::signals2::scoped_connection Broadcast;
|
||||
boost::signals2::scoped_connection BlockChecked;
|
||||
boost::signals2::scoped_connection NewPoWValidBlock;
|
||||
boost::signals2::scoped_connection AcceptedBlockHeader;
|
||||
@ -48,7 +47,6 @@ struct MainSignalsInstance {
|
||||
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex* pindexDisconnected)> BlockDisconnected;
|
||||
boost::signals2::signal<void (const CTransactionRef &, MemPoolRemovalReason)> TransactionRemovedFromMempool;
|
||||
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed;
|
||||
boost::signals2::signal<void (int64_t nBestBlockTime, CConnman* connman)> Broadcast;
|
||||
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
|
||||
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;
|
||||
boost::signals2::signal<void (const CBlockIndex *)>AcceptedBlockHeader;
|
||||
@ -122,7 +120,6 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
|
||||
conns.NotifyChainLock = g_signals.m_internals->NotifyChainLock.connect(std::bind(&CValidationInterface::NotifyChainLock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
|
||||
conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1, std::placeholders::_2));
|
||||
conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1));
|
||||
conns.Broadcast = g_signals.m_internals->Broadcast.connect(std::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, std::placeholders::_1, std::placeholders::_2));
|
||||
conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2));
|
||||
conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2));
|
||||
conns.NotifyGovernanceObject = g_signals.m_internals->NotifyGovernanceObject.connect(std::bind(&CValidationInterface::NotifyGovernanceObject, pwalletIn, std::placeholders::_1));
|
||||
@ -205,10 +202,6 @@ void CMainSignals::ChainStateFlushed(const CBlockLocator &locator) {
|
||||
});
|
||||
}
|
||||
|
||||
void CMainSignals::Broadcast(int64_t nBestBlockTime, CConnman* connman) {
|
||||
m_internals->Broadcast(nBestBlockTime, connman);
|
||||
}
|
||||
|
||||
void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) {
|
||||
m_internals->BlockChecked(block, state);
|
||||
}
|
||||
|
@ -156,8 +156,6 @@ protected:
|
||||
* Called on a background thread.
|
||||
*/
|
||||
virtual void ChainStateFlushed(const CBlockLocator &locator) {}
|
||||
/** Tells listeners to broadcast their data. */
|
||||
virtual void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) {}
|
||||
/**
|
||||
* Notifies listeners of a block validation result.
|
||||
* If the provided CValidationState IsValid, the provided block
|
||||
@ -216,7 +214,6 @@ public:
|
||||
void NotifyRecoveredSig(const std::shared_ptr<const llmq::CRecoveredSig> &sig);
|
||||
void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff);
|
||||
void ChainStateFlushed(const CBlockLocator &);
|
||||
void Broadcast(int64_t nBestBlockTime, CConnman* connman);
|
||||
void BlockChecked(const CBlock&, const CValidationState&);
|
||||
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);
|
||||
};
|
||||
|
@ -584,7 +584,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
|
||||
if (fCreate && !Exists(std::string("version"))) {
|
||||
bool fTmp = fReadOnly;
|
||||
fReadOnly = false;
|
||||
WriteVersion(CLIENT_VERSION);
|
||||
Write(std::string("version"), CLIENT_VERSION);
|
||||
fReadOnly = fTmp;
|
||||
}
|
||||
}
|
||||
|
@ -406,17 +406,6 @@ public:
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
bool ReadVersion(int& nVersion)
|
||||
{
|
||||
nVersion = 0;
|
||||
return Read(std::string("version"), nVersion);
|
||||
}
|
||||
|
||||
bool WriteVersion(int nVersion)
|
||||
{
|
||||
return Write(std::string("version"), nVersion);
|
||||
}
|
||||
|
||||
bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr);
|
||||
};
|
||||
|
||||
|
@ -22,7 +22,7 @@ CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinC
|
||||
{
|
||||
CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes);
|
||||
// Always obey the maximum
|
||||
const CAmount max_tx_fee = wallet.chain().maxTxFee();
|
||||
const CAmount max_tx_fee = wallet.m_default_max_tx_fee;
|
||||
if (fee_needed > max_tx_fee) {
|
||||
fee_needed = max_tx_fee;
|
||||
if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE;
|
||||
|
@ -71,6 +71,8 @@ void WalletInit::AddWalletOptions() const
|
||||
CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET_FEE);
|
||||
gArgs.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)",
|
||||
CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET_FEE);
|
||||
gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)",
|
||||
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
||||
gArgs.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)",
|
||||
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET_FEE);
|
||||
gArgs.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)",
|
||||
@ -165,10 +167,6 @@ bool WalletInit::ParameterInteraction() const
|
||||
if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false))
|
||||
return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again."));
|
||||
|
||||
if (::minRelayTxFee.GetFeePerK() > HIGH_TX_FEE_PER_KB)
|
||||
InitWarning(AmountHighWarn("-minrelaytxfee") + " " +
|
||||
_("The wallet will avoid paying less than the minimum relay fee."));
|
||||
|
||||
if (gArgs.IsArgSet("-walletbackupsdir")) {
|
||||
if (!fs::is_directory(gArgs.GetArg("-walletbackupsdir", ""))) {
|
||||
InitWarning(strprintf(_("Warning: incorrect parameter %s, path must exist! Using default path."), "-walletbackupsdir"));
|
||||
|
@ -83,8 +83,9 @@ void StartWallets(CScheduler& scheduler)
|
||||
pwallet->postInitProcess();
|
||||
}
|
||||
|
||||
// Run a thread to flush wallet periodically
|
||||
// Schedule periodic wallet flushes and tx rebroadcasts
|
||||
scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
|
||||
scheduler.scheduleEvery(MaybeResendWalletTxs, 1000);
|
||||
|
||||
if (!fMasternodeMode && CCoinJoinClientOptions::IsEnabled()) {
|
||||
scheduler.scheduleEvery(std::bind(&DoCoinJoinMaintenance, std::ref(*g_connman)), 1 * 1000);
|
||||
|
@ -3013,49 +3013,6 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
static UniValue resendwallettransactions(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
if (request.fHelp || request.params.size() != 0)
|
||||
throw std::runtime_error(
|
||||
RPCHelpMan{"resendwallettransactions",
|
||||
"Immediately re-broadcast unconfirmed wallet transactions to all peers.\n"
|
||||
"Intended only for testing; the wallet code periodically re-broadcasts\n"
|
||||
"automatically.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
"Returns an RPC error if -walletbroadcast is set to false.\n"
|
||||
"Returns array of transaction ids that were re-broadcast.\n"
|
||||
},
|
||||
RPCExamples{""},
|
||||
}.ToString()
|
||||
);
|
||||
|
||||
if (!pwallet->chain().p2pEnabled())
|
||||
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
|
||||
|
||||
auto locked_chain = pwallet->chain().lock();
|
||||
LOCK2(mempool.cs, pwallet->cs_wallet);
|
||||
|
||||
if (!pwallet->GetBroadcastTransactions()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast");
|
||||
}
|
||||
|
||||
std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(*locked_chain, GetTime());
|
||||
UniValue result(UniValue::VARR);
|
||||
for (const uint256& txid : txids)
|
||||
{
|
||||
result.push_back(txid.ToString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static UniValue listunspent(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
@ -4205,7 +4162,6 @@ static const CRPCCommand commands[] =
|
||||
{ "hidden", "generate", &generate, {"nblocks","maxtries"} }, // Hidden as it isn't functional, just an error to let people know if miner isn't compiled
|
||||
#endif //ENABLE_MINER
|
||||
{ "hidden", "instantsendtoaddress", &instantsendtoaddress, {} },
|
||||
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
|
||||
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options"} },
|
||||
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
|
||||
{ "wallet", "abortrescan", &abortrescan, {} },
|
||||
|
@ -369,7 +369,10 @@ public:
|
||||
int changePos = -1;
|
||||
std::string error;
|
||||
CCoinControl dummy;
|
||||
BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy));
|
||||
{
|
||||
auto locked_chain = m_chain->lock();
|
||||
BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy));
|
||||
}
|
||||
CValidationState state;
|
||||
BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state));
|
||||
CMutableTransaction blocktx;
|
||||
@ -388,7 +391,6 @@ public:
|
||||
}
|
||||
|
||||
std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain();
|
||||
std::unique_ptr<interfaces::Chain::Lock> m_locked_chain = m_chain->assumeLocked(); // Temporary. Removed in upcoming lock cleanup
|
||||
std::unique_ptr<CWallet> wallet;
|
||||
};
|
||||
|
||||
@ -400,8 +402,9 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
|
||||
// address.
|
||||
std::map<CTxDestination, std::vector<COutput>> list;
|
||||
{
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
list = wallet->ListCoins(*m_locked_chain);
|
||||
auto locked_chain = m_chain->lock();
|
||||
LOCK(wallet->cs_wallet);
|
||||
list = wallet->ListCoins(*locked_chain);
|
||||
}
|
||||
BOOST_CHECK_EQUAL(list.size(), 1U);
|
||||
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
|
||||
@ -416,8 +419,9 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
|
||||
// pubkey.
|
||||
AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */});
|
||||
{
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
list = wallet->ListCoins(*m_locked_chain);
|
||||
auto locked_chain = m_chain->lock();
|
||||
LOCK(wallet->cs_wallet);
|
||||
list = wallet->ListCoins(*locked_chain);
|
||||
}
|
||||
BOOST_CHECK_EQUAL(list.size(), 1U);
|
||||
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
|
||||
@ -425,9 +429,10 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
|
||||
|
||||
// Lock both coins. Confirm number of available coins drops to 0.
|
||||
{
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
auto locked_chain = m_chain->lock();
|
||||
LOCK(wallet->cs_wallet);
|
||||
std::vector<COutput> available;
|
||||
wallet->AvailableCoins(*m_locked_chain, available);
|
||||
wallet->AvailableCoins(*locked_chain, available);
|
||||
BOOST_CHECK_EQUAL(available.size(), 2U);
|
||||
}
|
||||
for (const auto& group : list) {
|
||||
@ -437,16 +442,18 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
|
||||
}
|
||||
}
|
||||
{
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
auto locked_chain = m_chain->lock();
|
||||
LOCK(wallet->cs_wallet);
|
||||
std::vector<COutput> available;
|
||||
wallet->AvailableCoins(*m_locked_chain, available);
|
||||
wallet->AvailableCoins(*locked_chain, available);
|
||||
BOOST_CHECK_EQUAL(available.size(), 0U);
|
||||
}
|
||||
// Confirm ListCoins still returns same result as before, despite coins
|
||||
// being locked.
|
||||
{
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
list = wallet->ListCoins(*m_locked_chain);
|
||||
auto locked_chain = m_chain->lock();
|
||||
LOCK(wallet->cs_wallet);
|
||||
list = wallet->ListCoins(*locked_chain);
|
||||
}
|
||||
BOOST_CHECK_EQUAL(list.size(), 1U);
|
||||
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
|
||||
|
@ -1473,6 +1473,10 @@ void CWallet::BlockDisconnected(const CBlock& block) {
|
||||
fAnonymizableTallyCachedNonDenom = false;
|
||||
}
|
||||
|
||||
void CWallet::UpdatedBlockTip()
|
||||
{
|
||||
m_best_block_time = GetTime();
|
||||
}
|
||||
|
||||
|
||||
void CWallet::BlockUntilSyncedToCurrentChain() {
|
||||
@ -2261,8 +2265,7 @@ void CWallet::ReacceptWalletTransactions()
|
||||
std::map<int64_t, CWalletTx*> mapSorted;
|
||||
|
||||
// Sort pending wallet transactions based on their initial wallet insertion order
|
||||
for (std::pair<const uint256, CWalletTx>& item : mapWallet)
|
||||
{
|
||||
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
|
||||
const uint256& wtxid = item.first;
|
||||
CWalletTx& wtx = item.second;
|
||||
assert(wtx.GetHash() == wtxid);
|
||||
@ -2277,27 +2280,31 @@ void CWallet::ReacceptWalletTransactions()
|
||||
// Try to add wallet transactions to memory pool
|
||||
for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
|
||||
CWalletTx& wtx = *(item.second);
|
||||
CValidationState state;
|
||||
wtx.AcceptToMemoryPool(*locked_chain, state);
|
||||
std::string unused_err_string;
|
||||
wtx.SubmitMemoryPoolAndRelay(unused_err_string, false, *locked_chain);
|
||||
}
|
||||
}
|
||||
|
||||
bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain)
|
||||
bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain)
|
||||
{
|
||||
assert(pwallet->GetBroadcastTransactions());
|
||||
if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain(locked_chain) == 0)
|
||||
{
|
||||
CValidationState state;
|
||||
/* GetDepthInMainChain already catches known conflicts. */
|
||||
if (InMempool() || AcceptToMemoryPool(locked_chain, state)) {
|
||||
pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString());
|
||||
if (pwallet->chain().p2pEnabled()) {
|
||||
pwallet->chain().relayTransaction(GetHash());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// Can't relay if wallet is not broadcasting
|
||||
if (!pwallet->GetBroadcastTransactions()) return false;
|
||||
// Don't relay abandoned transactions
|
||||
if (isAbandoned()) return false;
|
||||
// Submit transaction to mempool for relay
|
||||
pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString());
|
||||
// We must set fInMempool here - while it will be re-set to true by the
|
||||
// entered-mempool callback, if we did not there would be a race where a
|
||||
// user could call sendmoney in a loop and hit spurious out of funds errors
|
||||
// because we think that this newly generated transaction's change is
|
||||
// unavailable as we're not yet aware that it is in the mempool.
|
||||
//
|
||||
// Irrespective of the failure reason, un-marking fInMempool
|
||||
// out-of-order is incorrect - it should be unmarked when
|
||||
// TransactionRemovedFromMempool fires.
|
||||
bool ret = pwallet->chain().broadcastTransaction(tx, err_string, pwallet->m_default_max_tx_fee, relay);
|
||||
fInMempool |= ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::set<uint256> CWalletTx::GetConflicts() const
|
||||
@ -2593,57 +2600,62 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
|
||||
return CTransaction(tx1) == CTransaction(tx2);
|
||||
}
|
||||
|
||||
std::vector<uint256> CWallet::ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime)
|
||||
// Rebroadcast transactions from the wallet. We do this on a random timer
|
||||
// to slightly obfuscate which transactions come from our wallet.
|
||||
//
|
||||
// Ideally, we'd only resend transactions that we think should have been
|
||||
// mined in the most recent block. Any transaction that wasn't in the top
|
||||
// blockweight of transactions in the mempool shouldn't have been mined,
|
||||
// and so is probably just sitting in the mempool waiting to be confirmed.
|
||||
// Rebroadcasting does nothing to speed up confirmation and only damages
|
||||
// privacy.
|
||||
void CWallet::ResendWalletTransactions()
|
||||
{
|
||||
std::vector<uint256> result;
|
||||
// During reindex, importing and IBD, old wallet transactions become
|
||||
// unconfirmed. Don't resend them as that would spam other nodes.
|
||||
if (!chain().isReadyToBroadcast()) return;
|
||||
|
||||
LOCK2(mempool.cs, cs_wallet);
|
||||
|
||||
// Sort them in chronological order
|
||||
std::multimap<unsigned int, CWalletTx*> mapSorted;
|
||||
for (std::pair<const uint256, CWalletTx>& item : mapWallet)
|
||||
{
|
||||
CWalletTx& wtx = item.second;
|
||||
// Don't rebroadcast if newer than nTime:
|
||||
if (wtx.nTimeReceived > nTime)
|
||||
continue;
|
||||
mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx));
|
||||
}
|
||||
for (const std::pair<const unsigned int, CWalletTx*>& item : mapSorted)
|
||||
{
|
||||
CWalletTx& wtx = *item.second;
|
||||
if (wtx.RelayWalletTransaction(locked_chain))
|
||||
result.push_back(wtx.GetHash());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime)
|
||||
{
|
||||
// Do this infrequently and randomly to avoid giving away
|
||||
// that these are our transactions.
|
||||
if (GetTime() < nNextResend || !fBroadcastTransactions)
|
||||
return;
|
||||
if (GetTime() < nNextResend || !fBroadcastTransactions) return;
|
||||
bool fFirst = (nNextResend == 0);
|
||||
nNextResend = GetTime() + GetRand(30 * 60);
|
||||
if (fFirst)
|
||||
return;
|
||||
if (fFirst) return;
|
||||
|
||||
// Only do it if there's been a new block since last time
|
||||
if (nBestBlockTime < nLastResend)
|
||||
return;
|
||||
if (m_best_block_time < nLastResend) return;
|
||||
nLastResend = GetTime();
|
||||
|
||||
// Rebroadcast unconfirmed txes older than 5 minutes before the last
|
||||
// block was found:
|
||||
std::vector<uint256> relayed = ResendWalletTransactionsBefore(locked_chain, nBestBlockTime-5*60);
|
||||
if (!relayed.empty())
|
||||
WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size());
|
||||
int submitted_tx_count = 0;
|
||||
|
||||
{ // locked_chain, mempool.cs and cs_wallet scope
|
||||
auto locked_chain = chain().lock();
|
||||
LOCK2(mempool.cs, cs_wallet);
|
||||
|
||||
// Relay transactions
|
||||
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
|
||||
CWalletTx& wtx = item.second;
|
||||
// only rebroadcast unconfirmed txes older than 5 minutes before the
|
||||
// last block was found
|
||||
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
|
||||
std::string unused_err_string;
|
||||
if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true, *locked_chain)) ++submitted_tx_count;
|
||||
}
|
||||
} // locked_chain, mempool.cs and cs_wallet
|
||||
|
||||
if (submitted_tx_count > 0) {
|
||||
WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count);
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */ // end of mapWallet
|
||||
|
||||
|
||||
void MaybeResendWalletTxs()
|
||||
{
|
||||
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
|
||||
pwallet->ResendWalletTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @defgroup Actions
|
||||
@ -3053,7 +3065,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
|
||||
utxo_pool.push_back(group);
|
||||
}
|
||||
bnb_used = false;
|
||||
return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet, nCoinType == CoinType::ONLY_FULLY_MIXED, maxTxFee);
|
||||
return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet, nCoinType == CoinType::ONLY_FULLY_MIXED, m_default_max_tx_fee);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3937,12 +3949,10 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
|
||||
|
||||
if (fBroadcastTransactions)
|
||||
{
|
||||
// Broadcast
|
||||
if (!wtx.AcceptToMemoryPool(*locked_chain, state)) {
|
||||
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state));
|
||||
std::string err_string;
|
||||
if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) {
|
||||
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string);
|
||||
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
|
||||
} else {
|
||||
wtx.RelayWalletTransaction(*locked_chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5009,7 +5019,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||
return error(_("Unable to generate initial keys"));
|
||||
}
|
||||
|
||||
auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup
|
||||
auto locked_chain = chain.lock();
|
||||
walletInstance->ChainStateFlushed(locked_chain->getTipLocator());
|
||||
|
||||
// Try to create wallet backup right after new wallet was created
|
||||
@ -5107,6 +5117,29 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (gArgs.IsArgSet("-maxtxfee"))
|
||||
{
|
||||
CAmount nMaxFee = 0;
|
||||
if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
|
||||
chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")));
|
||||
return nullptr;
|
||||
}
|
||||
if (nMaxFee > HIGH_MAX_TX_FEE) {
|
||||
chain.initWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
|
||||
}
|
||||
if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) {
|
||||
chain.initError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
|
||||
gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString()));
|
||||
return nullptr;
|
||||
}
|
||||
walletInstance->m_default_max_tx_fee = nMaxFee;
|
||||
}
|
||||
|
||||
if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB)
|
||||
chain.initWarning(AmountHighWarn("-minrelaytxfee") + " " +
|
||||
_("The wallet will avoid paying less than the minimum relay fee."));
|
||||
|
||||
walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
|
||||
walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
|
||||
|
||||
@ -5448,12 +5481,6 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool fInternalIn)
|
||||
fInternal = fInternalIn;
|
||||
}
|
||||
|
||||
CWalletKey::CWalletKey(int64_t nExpires)
|
||||
{
|
||||
nTimeCreated = (nExpires ? GetTime() : 0);
|
||||
nTimeExpires = nExpires;
|
||||
}
|
||||
|
||||
void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock)
|
||||
{
|
||||
// Update the tx's hashBlock
|
||||
@ -5508,18 +5535,6 @@ bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const
|
||||
return GetBlocksToMaturity(locked_chain) > 0;
|
||||
}
|
||||
|
||||
bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state)
|
||||
{
|
||||
// We must set fInMempool here - while it will be re-set to true by the
|
||||
// entered-mempool callback, if we did not there would be a race where a
|
||||
// user could call sendmoney in a loop and hit spurious out of funds errors
|
||||
// because we think that this newly generated transaction's change is
|
||||
// unavailable as we're not yet aware that it is in the mempool.
|
||||
bool ret = locked_chain.submitToMemoryPool(tx, pwallet->chain().maxTxFee(), state);
|
||||
fInMempool |= ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const {
|
||||
std::vector<OutputGroup> groups;
|
||||
std::map<CTxDestination, OutputGroup> gmap;
|
||||
|
@ -74,6 +74,12 @@ static const bool DEFAULT_AVOIDPARTIALSPENDS = false;
|
||||
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6;
|
||||
static const bool DEFAULT_WALLETBROADCAST = true;
|
||||
static const bool DEFAULT_DISABLE_WALLET = false;
|
||||
//! -maxtxfee default
|
||||
static const CAmount DEFAULT_TRANSACTION_MAXFEE = COIN / 10;
|
||||
//! Discourage users to set fees higher than this amount (in duffs) per kB
|
||||
static const CAmount HIGH_TX_FEE_PER_KB = COIN / 100;
|
||||
//! -maxtxfee will warn if called with a higher fee than this amount (in duffs)
|
||||
static const CAmount HIGH_MAX_TX_FEE = 100 * HIGH_TX_FEE_PER_KB;
|
||||
|
||||
//! if set, all keys will be derived by using BIP39/BIP44
|
||||
static const bool DEFAULT_USE_HD_WALLET = false;
|
||||
@ -520,11 +526,8 @@ public:
|
||||
|
||||
int64_t GetTxTime() const;
|
||||
|
||||
// RelayWalletTransaction may only be called if fBroadcastTransactions!
|
||||
bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain);
|
||||
|
||||
/** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
|
||||
bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state);
|
||||
// Pass this transaction to node for mempool insertion and relay to peers if flag set to true
|
||||
bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain);
|
||||
|
||||
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
|
||||
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
|
||||
@ -598,28 +601,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/** Private key that includes an expiration date in case it never gets used. */
|
||||
class CWalletKey
|
||||
{
|
||||
public:
|
||||
CPrivKey vchPrivKey;
|
||||
int64_t nTimeCreated;
|
||||
int64_t nTimeExpires;
|
||||
std::string strComment;
|
||||
// todo: add something to note what created it (user, getnewaddress, change)
|
||||
// maybe should have a map<string, string> property map
|
||||
|
||||
explicit CWalletKey(int64_t nExpires=0);
|
||||
|
||||
SERIALIZE_METHODS(CWalletKey, obj)
|
||||
{
|
||||
int nVersion = s.GetVersion();
|
||||
if (!(s.GetType() & SER_GETHASH))
|
||||
READWRITE(nVersion);
|
||||
READWRITE(obj.vchPrivKey, obj.nTimeCreated, obj.nTimeExpires, LIMITED_STRING(obj.strComment, 65536));
|
||||
}
|
||||
};
|
||||
|
||||
struct CoinSelectionParams
|
||||
{
|
||||
bool use_bnb = true;
|
||||
@ -657,6 +638,8 @@ private:
|
||||
int64_t nNextResend = 0;
|
||||
int64_t nLastResend = 0;
|
||||
bool fBroadcastTransactions = false;
|
||||
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
|
||||
std::atomic<int64_t> m_best_block_time {0};
|
||||
|
||||
mutable bool fAnonymizableTallyCached = false;
|
||||
mutable std::vector<CompactTallyItem> vecAnonymizableTallyCached;
|
||||
@ -983,6 +966,7 @@ public:
|
||||
void TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) override;
|
||||
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
|
||||
void BlockDisconnected(const CBlock& block) override;
|
||||
void UpdatedBlockTip() override;
|
||||
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
|
||||
|
||||
struct ScanResult {
|
||||
@ -1003,9 +987,7 @@ public:
|
||||
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
|
||||
void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override;
|
||||
void ReacceptWalletTransactions();
|
||||
void ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) override;
|
||||
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!
|
||||
std::vector<uint256> ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime);
|
||||
void ResendWalletTransactions();
|
||||
struct Balance {
|
||||
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
|
||||
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
|
||||
@ -1064,6 +1046,8 @@ public:
|
||||
*/
|
||||
CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE};
|
||||
CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE};
|
||||
/** Absolute maximum transaction fee (in satoshis) used by default for the wallet */
|
||||
CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE};
|
||||
|
||||
bool NewKeyPool();
|
||||
size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
@ -1284,6 +1268,12 @@ public:
|
||||
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called periodically by the schedule thread. Prompts individual wallets to resend
|
||||
* their transactions. Actual rebroadcast schedule is managed by the wallets themselves.
|
||||
*/
|
||||
void MaybeResendWalletTxs();
|
||||
|
||||
/** A key allocated from the key pool. */
|
||||
class CReserveKey final : public CReserveScript
|
||||
{
|
||||
|
@ -22,50 +22,80 @@
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
namespace DBKeys {
|
||||
const std::string ACENTRY{"acentry"};
|
||||
const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"};
|
||||
const std::string BESTBLOCK{"bestblock"};
|
||||
const std::string CRYPTED_KEY{"ckey"};
|
||||
const std::string CRYPTED_HDCHAIN{"chdchain"};
|
||||
const std::string COINJOIN_SALT{"cj_salt"};
|
||||
const std::string CSCRIPT{"cscript"};
|
||||
const std::string DEFAULTKEY{"defaultkey"};
|
||||
const std::string DESTDATA{"destdata"};
|
||||
const std::string FLAGS{"flags"};
|
||||
const std::string G_OBJECT{"g_object"};
|
||||
const std::string HDCHAIN{"hdchain"};
|
||||
const std::string HDPUBKEY{"hdpubkey"};
|
||||
const std::string KEYMETA{"keymeta"};
|
||||
const std::string KEY{"key"};
|
||||
const std::string MASTER_KEY{"mkey"};
|
||||
const std::string MINVERSION{"minversion"};
|
||||
const std::string NAME{"name"};
|
||||
const std::string OLD_KEY{"wkey"};
|
||||
const std::string ORDERPOSNEXT{"orderposnext"};
|
||||
const std::string POOL{"pool"};
|
||||
const std::string PURPOSE{"purpose"};
|
||||
const std::string PRIVATESEND_SALT{"ps_salt"};
|
||||
const std::string TX{"tx"};
|
||||
const std::string VERSION{"version"};
|
||||
const std::string WATCHMETA{"watchmeta"};
|
||||
const std::string WATCHS{"watchs"};
|
||||
} // namespace DBKeys
|
||||
|
||||
//
|
||||
// WalletBatch
|
||||
//
|
||||
|
||||
bool WalletBatch::WriteName(const std::string& strAddress, const std::string& strName)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("name"), strAddress), strName);
|
||||
return WriteIC(std::make_pair(DBKeys::NAME, strAddress), strName);
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseName(const std::string& strAddress)
|
||||
{
|
||||
// This should only be used for sending addresses, never for receiving addresses,
|
||||
// receiving addresses must always have an address book entry if they're not change return.
|
||||
return EraseIC(std::make_pair(std::string("name"), strAddress));
|
||||
return EraseIC(std::make_pair(DBKeys::NAME, strAddress));
|
||||
}
|
||||
|
||||
bool WalletBatch::WritePurpose(const std::string& strAddress, const std::string& strPurpose)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose);
|
||||
return WriteIC(std::make_pair(DBKeys::PURPOSE, strAddress), strPurpose);
|
||||
}
|
||||
|
||||
bool WalletBatch::ErasePurpose(const std::string& strAddress)
|
||||
{
|
||||
return EraseIC(std::make_pair(std::string("purpose"), strAddress));
|
||||
return EraseIC(std::make_pair(DBKeys::PURPOSE, strAddress));
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteTx(const CWalletTx& wtx)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("tx"), wtx.GetHash()), wtx);
|
||||
return WriteIC(std::make_pair(DBKeys::TX, wtx.GetHash()), wtx);
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseTx(uint256 hash)
|
||||
{
|
||||
return EraseIC(std::make_pair(std::string("tx"), hash));
|
||||
return EraseIC(std::make_pair(DBKeys::TX, hash));
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteKeyMeta(const CPubKey& vchPubKey, const CKeyMetadata& keyMeta)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, true);
|
||||
return WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta, true);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
|
||||
{
|
||||
if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) {
|
||||
if (!WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -75,102 +105,101 @@ bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey,
|
||||
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
|
||||
vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());
|
||||
|
||||
return WriteIC(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
|
||||
return WriteIC(std::make_pair(DBKeys::KEY, vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
const CKeyMetadata &keyMeta)
|
||||
{
|
||||
if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) {
|
||||
if (!WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WriteIC(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) {
|
||||
if (!WriteIC(std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey), vchCryptedSecret, false)) {
|
||||
return false;
|
||||
}
|
||||
EraseIC(std::make_pair(std::string("key"), vchPubKey));
|
||||
EraseIC(std::make_pair(std::string("wkey"), vchPubKey));
|
||||
EraseIC(std::make_pair(DBKeys::KEY, vchPubKey));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
|
||||
return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteCScript(const uint160& hash, const CScript& redeemScript)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("cscript"), hash), redeemScript, false);
|
||||
return WriteIC(std::make_pair(DBKeys::CSCRIPT, hash), redeemScript, false);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta)
|
||||
{
|
||||
if (!WriteIC(std::make_pair(std::string("watchmeta"), dest), keyMeta)) {
|
||||
if (!WriteIC(std::make_pair(DBKeys::WATCHMETA, dest), keyMeta)) {
|
||||
return false;
|
||||
}
|
||||
return WriteIC(std::make_pair(std::string("watchs"), dest), '1');
|
||||
return WriteIC(std::make_pair(DBKeys::WATCHS, dest), '1');
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseWatchOnly(const CScript &dest)
|
||||
{
|
||||
if (!EraseIC(std::make_pair(std::string("watchmeta"), dest))) {
|
||||
if (!EraseIC(std::make_pair(DBKeys::WATCHMETA, dest))) {
|
||||
return false;
|
||||
}
|
||||
return EraseIC(std::make_pair(std::string("watchs"), dest));
|
||||
return EraseIC(std::make_pair(DBKeys::WATCHS, dest));
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteBestBlock(const CBlockLocator& locator)
|
||||
{
|
||||
WriteIC(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
|
||||
return WriteIC(std::string("bestblock_nomerkle"), locator);
|
||||
WriteIC(DBKeys::BESTBLOCK, CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
|
||||
return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, locator);
|
||||
}
|
||||
|
||||
bool WalletBatch::ReadBestBlock(CBlockLocator& locator)
|
||||
{
|
||||
if (m_batch.Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true;
|
||||
return m_batch.Read(std::string("bestblock_nomerkle"), locator);
|
||||
if (m_batch.Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true;
|
||||
return m_batch.Read(DBKeys::BESTBLOCK_NOMERKLE, locator);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext)
|
||||
{
|
||||
return WriteIC(std::string("orderposnext"), nOrderPosNext);
|
||||
return WriteIC(DBKeys::ORDERPOSNEXT, nOrderPosNext);
|
||||
}
|
||||
|
||||
bool WalletBatch::ReadPool(int64_t nPool, CKeyPool& keypool)
|
||||
{
|
||||
return m_batch.Read(std::make_pair(std::string("pool"), nPool), keypool);
|
||||
return m_batch.Read(std::make_pair(DBKeys::POOL, nPool), keypool);
|
||||
}
|
||||
|
||||
bool WalletBatch::WritePool(int64_t nPool, const CKeyPool& keypool)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("pool"), nPool), keypool);
|
||||
return WriteIC(std::make_pair(DBKeys::POOL, nPool), keypool);
|
||||
}
|
||||
|
||||
bool WalletBatch::ErasePool(int64_t nPool)
|
||||
{
|
||||
return EraseIC(std::make_pair(std::string("pool"), nPool));
|
||||
return EraseIC(std::make_pair(DBKeys::POOL, nPool));
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteMinVersion(int nVersion)
|
||||
{
|
||||
return WriteIC(std::string("minversion"), nVersion);
|
||||
return WriteIC(DBKeys::MINVERSION, nVersion);
|
||||
}
|
||||
|
||||
bool WalletBatch::ReadCoinJoinSalt(uint256& salt, bool fLegacy)
|
||||
{
|
||||
// TODO: Remove legacy checks after few major releases
|
||||
return m_batch.Read(std::string(fLegacy ? "ps_salt" : "cj_salt"), salt);
|
||||
return m_batch.Read(std::string(fLegacy ? DBKeys::PRIVATESEND_SALT : DBKeys::COINJOIN_SALT), salt);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteCoinJoinSalt(const uint256& salt)
|
||||
{
|
||||
return WriteIC(std::string("cj_salt"), salt);
|
||||
return WriteIC(DBKeys::COINJOIN_SALT, salt);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteGovernanceObject(const CGovernanceObject& obj)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("gobject"), obj.GetHash()), obj, false);
|
||||
return WriteIC(std::make_pair(DBKeys::G_OBJECT, obj.GetHash()), obj, false);
|
||||
}
|
||||
|
||||
|
||||
@ -184,7 +213,6 @@ public:
|
||||
unsigned int m_unknown_records{0};
|
||||
bool fIsEncrypted{false};
|
||||
bool fAnyUnordered{false};
|
||||
int nFileVersion{0};
|
||||
std::vector<uint256> vWalletUpgrade;
|
||||
|
||||
CWalletScanState() {
|
||||
@ -200,20 +228,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
// Taking advantage of the fact that pair serialization
|
||||
// is just the two items serialized one after the other
|
||||
ssKey >> strType;
|
||||
if (strType == "name")
|
||||
{
|
||||
if (strType == DBKeys::NAME) {
|
||||
std::string strAddress;
|
||||
ssKey >> strAddress;
|
||||
ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name;
|
||||
}
|
||||
else if (strType == "purpose")
|
||||
{
|
||||
} else if (strType == DBKeys::PURPOSE) {
|
||||
std::string strAddress;
|
||||
ssKey >> strAddress;
|
||||
ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose;
|
||||
}
|
||||
else if (strType == "tx")
|
||||
{
|
||||
} else if (strType == DBKeys::TX) {
|
||||
uint256 hash;
|
||||
ssKey >> hash;
|
||||
CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
|
||||
@ -247,9 +270,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
wss.fAnyUnordered = true;
|
||||
|
||||
pwallet->LoadToWallet(wtx);
|
||||
}
|
||||
else if (strType == "watchs")
|
||||
{
|
||||
} else if (strType == DBKeys::WATCHS) {
|
||||
wss.nWatchKeys++;
|
||||
CScript script;
|
||||
ssKey >> script;
|
||||
@ -257,9 +278,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
ssValue >> fYes;
|
||||
if (fYes == '1')
|
||||
pwallet->LoadWatchOnly(script);
|
||||
}
|
||||
else if (strType == "key" || strType == "wkey")
|
||||
{
|
||||
} else if (strType == DBKeys::KEY) {
|
||||
CPubKey vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
if (!vchPubKey.IsValid())
|
||||
@ -271,20 +290,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
CPrivKey pkey;
|
||||
uint256 hash;
|
||||
|
||||
if (strType == "key")
|
||||
{
|
||||
wss.nKeys++;
|
||||
ssValue >> pkey;
|
||||
} else {
|
||||
CWalletKey wkey;
|
||||
ssValue >> wkey;
|
||||
pkey = wkey.vchPrivKey;
|
||||
}
|
||||
wss.nKeys++;
|
||||
ssValue >> pkey;
|
||||
|
||||
// Old wallets store keys as "key" [pubkey] => [privkey]
|
||||
// Old wallets store keys as DBKeys::KEY [pubkey] => [privkey]
|
||||
// ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key
|
||||
// using EC operations as a checksum.
|
||||
// Newer wallets store keys as "key"[pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while
|
||||
// Newer wallets store keys as DBKeys::KEY [pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while
|
||||
// remaining backwards-compatible.
|
||||
try
|
||||
{
|
||||
@ -321,9 +333,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
strErr = "Error reading wallet database: LoadKey failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "mkey")
|
||||
{
|
||||
} else if (strType == DBKeys::MASTER_KEY) {
|
||||
unsigned int nID;
|
||||
ssKey >> nID;
|
||||
CMasterKey kMasterKey;
|
||||
@ -336,9 +346,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
pwallet->mapMasterKeys[nID] = kMasterKey;
|
||||
if (pwallet->nMasterKeyMaxID < nID)
|
||||
pwallet->nMasterKeyMaxID = nID;
|
||||
}
|
||||
else if (strType == "ckey")
|
||||
{
|
||||
} else if (strType == DBKeys::CRYPTED_KEY) {
|
||||
CPubKey vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
if (!vchPubKey.IsValid())
|
||||
@ -356,27 +364,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
return false;
|
||||
}
|
||||
wss.fIsEncrypted = true;
|
||||
}
|
||||
else if (strType == "keymeta")
|
||||
{
|
||||
} else if (strType == DBKeys::KEYMETA) {
|
||||
CPubKey vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
CKeyMetadata keyMeta;
|
||||
ssValue >> keyMeta;
|
||||
wss.nKeyMeta++;
|
||||
pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
|
||||
}
|
||||
else if (strType == "watchmeta")
|
||||
{
|
||||
} else if (strType == DBKeys::WATCHMETA) {
|
||||
CScript script;
|
||||
ssKey >> script;
|
||||
CKeyMetadata keyMeta;
|
||||
ssValue >> keyMeta;
|
||||
wss.nKeyMeta++;
|
||||
pwallet->LoadScriptMetadata(CScriptID(script), keyMeta);
|
||||
}
|
||||
else if (strType == "defaultkey")
|
||||
{
|
||||
} else if (strType == DBKeys::DEFAULTKEY) {
|
||||
// We don't want or need the default key, but if there is one set,
|
||||
// we want to make sure that it is valid so that we can detect corruption
|
||||
CPubKey vchPubKey;
|
||||
@ -385,23 +387,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
strErr = "Error reading wallet database: Default Key corrupt";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "pool")
|
||||
{
|
||||
} else if (strType == DBKeys::POOL) {
|
||||
int64_t nIndex;
|
||||
ssKey >> nIndex;
|
||||
CKeyPool keypool;
|
||||
ssValue >> keypool;
|
||||
pwallet->LoadKeyPool(nIndex, keypool);
|
||||
}
|
||||
else if (strType == "version")
|
||||
{
|
||||
ssValue >> wss.nFileVersion;
|
||||
if (wss.nFileVersion == 10300)
|
||||
wss.nFileVersion = 300;
|
||||
}
|
||||
else if (strType == "cscript")
|
||||
{
|
||||
} else if (strType == DBKeys::CSCRIPT) {
|
||||
uint160 hash;
|
||||
ssKey >> hash;
|
||||
CScript script;
|
||||
@ -411,21 +403,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
strErr = "Error reading wallet database: LoadCScript failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "orderposnext")
|
||||
{
|
||||
} else if (strType == DBKeys::ORDERPOSNEXT) {
|
||||
ssValue >> pwallet->nOrderPosNext;
|
||||
}
|
||||
else if (strType == "destdata")
|
||||
{
|
||||
} else if (strType == DBKeys::DESTDATA) {
|
||||
std::string strAddress, strKey, strValue;
|
||||
ssKey >> strAddress;
|
||||
ssKey >> strKey;
|
||||
ssValue >> strValue;
|
||||
pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue);
|
||||
}
|
||||
else if (strType == "hdchain")
|
||||
{
|
||||
} else if (strType == DBKeys::HDCHAIN) {
|
||||
CHDChain chain;
|
||||
ssValue >> chain;
|
||||
if (!pwallet->SetHDChainSingle(chain, true))
|
||||
@ -433,9 +419,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
strErr = "Error reading wallet database: SetHDChain failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "chdchain")
|
||||
{
|
||||
} else if (strType == DBKeys::CRYPTED_HDCHAIN) {
|
||||
CHDChain chain;
|
||||
ssValue >> chain;
|
||||
if (!pwallet->SetCryptedHDChainSingle(chain, true))
|
||||
@ -443,9 +427,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
strErr = "Error reading wallet database: SetHDCryptedChain failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "hdpubkey")
|
||||
{
|
||||
} else if (strType == DBKeys::HDPUBKEY) {
|
||||
wss.nHDPubKeys++;
|
||||
CPubKey vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
@ -463,7 +445,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
strErr = "Error reading wallet database: LoadHDPubKey failed";
|
||||
return false;
|
||||
}
|
||||
} else if (strType == "gobject") {
|
||||
} else if (strType == DBKeys::G_OBJECT) {
|
||||
uint256 nObjectHash;
|
||||
CGovernanceObject obj;
|
||||
ssKey >> nObjectHash;
|
||||
@ -478,16 +460,18 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
strErr = "Invalid governance object: LoadGovernanceObject";
|
||||
return false;
|
||||
}
|
||||
} else if (strType == "flags") {
|
||||
} else if (strType == DBKeys::FLAGS) {
|
||||
uint64_t flags;
|
||||
ssValue >> flags;
|
||||
if (!pwallet->SetWalletFlags(flags, true)) {
|
||||
strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType != "bestblock" && strType != "bestblock_nomerkle" &&
|
||||
strType != "minversion" && strType != "acentry"){
|
||||
} else if (strType == DBKeys::OLD_KEY) {
|
||||
strErr = "Found unsupported 'wkey' record, try loading with version 0.17";
|
||||
return false;
|
||||
} else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE &&
|
||||
strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY && strType != DBKeys::VERSION) {
|
||||
wss.m_unknown_records++;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
@ -506,9 +490,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
|
||||
bool WalletBatch::IsKeyType(const std::string& strType)
|
||||
{
|
||||
return (strType== "key" || strType == "wkey" ||
|
||||
strType == "mkey" || strType == "ckey" ||
|
||||
strType == "hdchain" || strType == "chdchain");
|
||||
return (strType == DBKeys::KEY ||
|
||||
strType == DBKeys::MASTER_KEY || strType == DBKeys::CRYPTED_KEY ||
|
||||
strType == DBKeys::HDCHAIN || strType == DBKeys::CRYPTED_HDCHAIN);
|
||||
}
|
||||
|
||||
DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||
@ -522,8 +506,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||
LOCK(pwallet->cs_wallet);
|
||||
try {
|
||||
int nMinVersion = 0;
|
||||
if (m_batch.Read((std::string)"minversion", nMinVersion))
|
||||
{
|
||||
if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) {
|
||||
if (nMinVersion > FEATURE_LATEST)
|
||||
return DBErrors::TOO_NEW;
|
||||
pwallet->LoadMinVersion(nMinVersion);
|
||||
@ -557,15 +540,15 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||
{
|
||||
// losing keys is considered a catastrophic error, anything else
|
||||
// we assume the user can live with:
|
||||
if (IsKeyType(strType) || strType == "defaultkey") {
|
||||
if (IsKeyType(strType) || strType == DBKeys::DEFAULTKEY) {
|
||||
result = DBErrors::CORRUPT;
|
||||
} else if(strType == "flags") {
|
||||
} else if (strType == DBKeys::FLAGS) {
|
||||
// reading the wallet flags can only fail if unknown flags are present
|
||||
result = DBErrors::TOO_NEW;
|
||||
} else {
|
||||
// Leave other errors alone, if we try to fix them we might make things worse.
|
||||
fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
|
||||
if (strType == "tx")
|
||||
if (strType == DBKeys::TX)
|
||||
// Rescan if there is a bad transaction record:
|
||||
gArgs.SoftSetBoolArg("-rescan", true);
|
||||
}
|
||||
@ -590,7 +573,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||
if (result != DBErrors::LOAD_OK)
|
||||
return result;
|
||||
|
||||
pwallet->WalletLogPrintf("nFileVersion = %d\n", wss.nFileVersion);
|
||||
// Last client version to open this wallet, was previously the file version number
|
||||
int last_client = CLIENT_VERSION;
|
||||
m_batch.Read(DBKeys::VERSION, last_client);
|
||||
|
||||
int wallet_version = pwallet->GetVersion();
|
||||
pwallet->WalletLogPrintf("Wallet File Version = %d\n", wallet_version > 0 ? wallet_version : last_client);
|
||||
|
||||
pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u total; Watch scripts: %u; HD PubKeys: %u; Metadata: %u; Unknown wallet records: %u\n",
|
||||
wss.nKeys, wss.nCKeys, wss.nKeys + wss.nCKeys,
|
||||
@ -604,11 +592,11 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||
WriteTx(pwallet->mapWallet.at(hash));
|
||||
|
||||
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
|
||||
if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000))
|
||||
if (wss.fIsEncrypted && (last_client == 40000 || last_client == 50000))
|
||||
return DBErrors::NEED_REWRITE;
|
||||
|
||||
if (wss.nFileVersion < CLIENT_VERSION) // Update
|
||||
WriteVersion(CLIENT_VERSION);
|
||||
if (last_client < CLIENT_VERSION) // Update
|
||||
m_batch.Write(DBKeys::VERSION, CLIENT_VERSION);
|
||||
|
||||
if (wss.fAnyUnordered)
|
||||
result = pwallet->ReorderTransactions();
|
||||
@ -622,8 +610,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW
|
||||
|
||||
try {
|
||||
int nMinVersion = 0;
|
||||
if (m_batch.Read((std::string)"minversion", nMinVersion))
|
||||
{
|
||||
if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) {
|
||||
if (nMinVersion > FEATURE_LATEST)
|
||||
return DBErrors::TOO_NEW;
|
||||
}
|
||||
@ -652,7 +639,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW
|
||||
|
||||
std::string strType;
|
||||
ssKey >> strType;
|
||||
if (strType == "tx") {
|
||||
if (strType == DBKeys::TX) {
|
||||
uint256 hash;
|
||||
ssKey >> hash;
|
||||
|
||||
@ -783,8 +770,10 @@ bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, C
|
||||
fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue,
|
||||
dummyWss, strType, strErr);
|
||||
}
|
||||
if (!IsKeyType(strType) && strType != "hdpubkey")
|
||||
if (!IsKeyType(strType) && strType != DBKeys::HDCHAIN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fReadOK)
|
||||
{
|
||||
LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, strErr);
|
||||
@ -806,40 +795,40 @@ bool WalletBatch::VerifyDatabaseFile(const fs::path& wallet_path, std::string& w
|
||||
|
||||
bool WalletBatch::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
|
||||
{
|
||||
return WriteIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
|
||||
return WriteIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)), value);
|
||||
}
|
||||
|
||||
bool WalletBatch::EraseDestData(const std::string &address, const std::string &key)
|
||||
{
|
||||
return EraseIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
|
||||
return EraseIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)));
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteHDChain(const CHDChain& chain)
|
||||
{
|
||||
return WriteIC(std::string("hdchain"), chain);
|
||||
return WriteIC(DBKeys::HDCHAIN, chain);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteCryptedHDChain(const CHDChain& chain)
|
||||
{
|
||||
if (!WriteIC(std::string("chdchain"), chain))
|
||||
if (!WriteIC(DBKeys::CRYPTED_HDCHAIN, chain))
|
||||
return false;
|
||||
|
||||
EraseIC(std::string("hdchain"));
|
||||
EraseIC(DBKeys::HDCHAIN);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteHDPubKey(const CHDPubKey& hdPubKey, const CKeyMetadata& keyMeta)
|
||||
{
|
||||
if (!WriteIC(std::make_pair(std::string("keymeta"), hdPubKey.extPubKey.pubkey), keyMeta, false))
|
||||
if (!WriteIC(std::make_pair(DBKeys::KEYMETA, hdPubKey.extPubKey.pubkey), keyMeta, false))
|
||||
return false;
|
||||
|
||||
return WriteIC(std::make_pair(std::string("hdpubkey"), hdPubKey.extPubKey.pubkey), hdPubKey, false);
|
||||
return WriteIC(std::make_pair(DBKeys::HDPUBKEY, hdPubKey.extPubKey.pubkey), hdPubKey, false);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteWalletFlags(const uint64_t flags)
|
||||
{
|
||||
return WriteIC(std::string("flags"), flags);
|
||||
return WriteIC(DBKeys::FLAGS, flags);
|
||||
}
|
||||
|
||||
bool WalletBatch::TxnBegin()
|
||||
@ -856,13 +845,3 @@ bool WalletBatch::TxnAbort()
|
||||
{
|
||||
return m_batch.TxnAbort();
|
||||
}
|
||||
|
||||
bool WalletBatch::ReadVersion(int& nVersion)
|
||||
{
|
||||
return m_batch.ReadVersion(nVersion);
|
||||
}
|
||||
|
||||
bool WalletBatch::WriteVersion(int nVersion)
|
||||
{
|
||||
return m_batch.WriteVersion(nVersion);
|
||||
}
|
||||
|
@ -55,6 +55,36 @@ enum class DBErrors
|
||||
NEED_REWRITE
|
||||
};
|
||||
|
||||
namespace DBKeys {
|
||||
extern const std::string ACENTRY;
|
||||
extern const std::string BESTBLOCK;
|
||||
extern const std::string BESTBLOCK_NOMERKLE;
|
||||
extern const std::string CRYPTED_HDCHAIN;
|
||||
extern const std::string CRYPTED_KEY;
|
||||
extern const std::string COINJOIN_SALT;
|
||||
extern const std::string CSCRIPT;
|
||||
extern const std::string DEFAULTKEY;
|
||||
extern const std::string DESTDATA;
|
||||
extern const std::string FLAGS;
|
||||
extern const std::string G_OBJECT;
|
||||
extern const std::string HDCHAIN;
|
||||
extern const std::string HDPUBKEY;
|
||||
extern const std::string KEY;
|
||||
extern const std::string KEYMETA;
|
||||
extern const std::string MASTER_KEY;
|
||||
extern const std::string MINVERSION;
|
||||
extern const std::string NAME;
|
||||
extern const std::string OLD_KEY;
|
||||
extern const std::string ORDERPOSNEXT;
|
||||
extern const std::string POOL;
|
||||
extern const std::string PURPOSE;
|
||||
extern const std::string PRIVATESEND_SALT;
|
||||
extern const std::string TX;
|
||||
extern const std::string VERSION;
|
||||
extern const std::string WATCHMETA;
|
||||
extern const std::string WATCHS;
|
||||
} // namespace DBKeys
|
||||
|
||||
class CKeyMetadata
|
||||
{
|
||||
public:
|
||||
@ -192,10 +222,6 @@ public:
|
||||
bool TxnCommit();
|
||||
//! Abort current transaction
|
||||
bool TxnAbort();
|
||||
//! Read wallet version
|
||||
bool ReadVersion(int& nVersion);
|
||||
//! Write wallet version
|
||||
bool WriteVersion(int nVersion);
|
||||
private:
|
||||
BerkeleyBatch m_batch;
|
||||
WalletDatabase& m_database;
|
||||
|
@ -140,7 +140,7 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
tx.rehash()
|
||||
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True)
|
||||
sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], 0)
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
@ -170,7 +170,7 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
tx.vout = [CTxOut(amount, scriptPubKey2)]
|
||||
tx.rehash()
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
spending_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True)
|
||||
spending_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], 0)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
balance1 = self.nodes[1].getaddressbalance(address2)
|
||||
@ -184,7 +184,7 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
tx.rehash()
|
||||
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True)
|
||||
sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], 0)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
@ -265,7 +265,7 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
tx.vout = [CTxOut(amount, scriptPubKey3)]
|
||||
tx.rehash()
|
||||
signed_tx = self.nodes[2].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
memtxid1 = self.nodes[2].sendrawtransaction(signed_tx["hex"], True)
|
||||
memtxid1 = self.nodes[2].sendrawtransaction(signed_tx["hex"], 0)
|
||||
self.bump_mocktime(2)
|
||||
|
||||
tx2 = CTransaction()
|
||||
@ -279,7 +279,7 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
]
|
||||
tx2.rehash()
|
||||
signed_tx2 = self.nodes[2].signrawtransactionwithwallet(tx2.serialize().hex())
|
||||
memtxid2 = self.nodes[2].sendrawtransaction(signed_tx2["hex"], True)
|
||||
memtxid2 = self.nodes[2].sendrawtransaction(signed_tx2["hex"], 0)
|
||||
self.bump_mocktime(2)
|
||||
|
||||
mempool = self.nodes[2].getaddressmempool({"addresses": [address3]})
|
||||
@ -306,7 +306,7 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
tx.rehash()
|
||||
self.nodes[2].importprivkey(privKey3)
|
||||
signed_tx3 = self.nodes[2].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
self.nodes[2].sendrawtransaction(signed_tx3["hex"], True)
|
||||
self.nodes[2].sendrawtransaction(signed_tx3["hex"], 0)
|
||||
self.bump_mocktime(2)
|
||||
|
||||
mempool3 = self.nodes[2].getaddressmempool({"addresses": [address3]})
|
||||
@ -338,7 +338,7 @@ class AddressIndexTest(BitcoinTestFramework):
|
||||
tx.rehash()
|
||||
self.nodes[0].importprivkey(privkey1)
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
self.nodes[0].sendrawtransaction(signed_tx["hex"], True)
|
||||
self.nodes[0].sendrawtransaction(signed_tx["hex"], 0)
|
||||
|
||||
self.sync_all()
|
||||
mempool_deltas = self.nodes[2].getaddressmempool({"addresses": [address1]})
|
||||
|
@ -129,7 +129,7 @@ class BIP65Test(BitcoinTestFramework):
|
||||
|
||||
# First we show that this tx is valid except for CLTV by getting it
|
||||
# rejected from the mempool for exactly that reason.
|
||||
assert_raises_rpc_error(-26, 'non-mandatory-script-verify-flag (Negative locktime) (code 64)', self.nodes[0].sendrawtransaction, spendtx.serialize().hex(), True)
|
||||
assert_raises_rpc_error(-26, 'non-mandatory-script-verify-flag (Negative locktime) (code 64)', self.nodes[0].sendrawtransaction, spendtx.serialize().hex(), 0)
|
||||
|
||||
# Now we verify that a block with this transaction is also invalid.
|
||||
block.vtx.append(spendtx)
|
||||
|
@ -118,7 +118,7 @@ class BIP66Test(BitcoinTestFramework):
|
||||
|
||||
# First we show that this tx is valid except for DERSIG by getting it
|
||||
# rejected from the mempool for exactly that reason.
|
||||
assert_raises_rpc_error(-26, 'non-mandatory-script-verify-flag (Non-canonical DER signature) (code 64)', self.nodes[0].sendrawtransaction, spendtx.serialize().hex(), True)
|
||||
assert_raises_rpc_error(-26, 'non-mandatory-script-verify-flag (Non-canonical DER signature) (code 64)', self.nodes[0].sendrawtransaction, spendtx.serialize().hex(), 0)
|
||||
|
||||
# Now we verify that a block with this transaction is also invalid.
|
||||
block.vtx.append(spendtx)
|
||||
|
@ -63,7 +63,7 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
|
||||
# the ScriptSig that will satisfy the ScriptPubKey.
|
||||
for inp in tx.vin:
|
||||
inp.scriptSig = SCRIPT_SIG[inp.prevout.n]
|
||||
txid = from_node.sendrawtransaction(ToHex(tx), True)
|
||||
txid = from_node.sendrawtransaction(hexstring=ToHex(tx), maxfeerate=0)
|
||||
unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee})
|
||||
unconflist.append({"txid": txid, "vout": 1, "amount": amount})
|
||||
|
||||
@ -93,7 +93,7 @@ def split_inputs(from_node, txins, txouts, initial_split=False):
|
||||
else:
|
||||
tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]]
|
||||
completetx = ToHex(tx)
|
||||
txid = from_node.sendrawtransaction(completetx, True)
|
||||
txid = from_node.sendrawtransaction(hexstring=completetx, maxfeerate=0)
|
||||
txouts.append({"txid": txid, "vout": 0, "amount": half_change})
|
||||
txouts.append({"txid": txid, "vout": 1, "amount": rem_change})
|
||||
|
||||
|
@ -60,15 +60,15 @@ class NULLDUMMYTest(BitcoinTestFramework):
|
||||
|
||||
self.log.info("Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [430]")
|
||||
test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, 49)]
|
||||
txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize().hex(), True)
|
||||
txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize().hex(), 0)
|
||||
test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, 48))
|
||||
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize().hex(), True)
|
||||
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize().hex(), 0)
|
||||
self.block_submit(self.nodes[0], test1txs, True)
|
||||
|
||||
self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation")
|
||||
test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, 47)
|
||||
trueDummy(test2tx)
|
||||
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize().hex(), True)
|
||||
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize().hex(), 0)
|
||||
|
||||
self.log.info("Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [431]")
|
||||
self.block_submit(self.nodes[0], [test2tx], True)
|
||||
@ -77,12 +77,12 @@ class NULLDUMMYTest(BitcoinTestFramework):
|
||||
test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, 46)
|
||||
test6txs=[CTransaction(test4tx)]
|
||||
trueDummy(test4tx)
|
||||
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize().hex(), True)
|
||||
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize().hex(), 0)
|
||||
self.block_submit(self.nodes[0], [test4tx])
|
||||
|
||||
self.log.info("Test 6: NULLDUMMY compliant transactions should be accepted to mempool and in block after activation [432]")
|
||||
for i in test6txs:
|
||||
self.nodes[0].sendrawtransaction(i.serialize().hex(), True)
|
||||
self.nodes[0].sendrawtransaction(i.serialize().hex(), 0)
|
||||
self.block_submit(self.nodes[0], test6txs, True)
|
||||
|
||||
|
||||
|
@ -76,7 +76,7 @@ class SpentIndexTest(BitcoinTestFramework):
|
||||
tx.rehash()
|
||||
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True)
|
||||
txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], 0)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
@ -111,7 +111,7 @@ class SpentIndexTest(BitcoinTestFramework):
|
||||
tx2.rehash()
|
||||
self.nodes[0].importprivkey(privkey)
|
||||
signed_tx2 = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())
|
||||
txid2 = self.nodes[0].sendrawtransaction(signed_tx2["hex"], True)
|
||||
txid2 = self.nodes[0].sendrawtransaction(signed_tx2["hex"], 0)
|
||||
|
||||
# Check the mempool index
|
||||
self.sync_all()
|
||||
|
@ -59,7 +59,7 @@ class TxIndexTest(BitcoinTestFramework):
|
||||
tx.rehash()
|
||||
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())
|
||||
txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True)
|
||||
txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], 0)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
|
@ -70,7 +70,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||
inputs=[{'txid': coin['txid'], 'vout': coin['vout']}],
|
||||
outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}],
|
||||
))['hex']
|
||||
txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, allowhighfees=True)
|
||||
txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0)
|
||||
node.generate(1)
|
||||
self.mempool_size = 0
|
||||
self.check_mempool_result(
|
||||
@ -103,9 +103,9 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||
self.check_mempool_result(
|
||||
result_expected=[{'txid': tx.rehash(), 'allowed': True}],
|
||||
rawtxs=[tx.serialize().hex()],
|
||||
allowhighfees=True,
|
||||
maxfeerate=0,
|
||||
)
|
||||
node.sendrawtransaction(hexstring=raw_tx_final, allowhighfees=True)
|
||||
node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0)
|
||||
self.mempool_size += 1
|
||||
|
||||
self.log.info('A transaction in the mempool')
|
||||
@ -131,7 +131,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||
|
||||
self.log.info('A transaction that conflicts with an unconfirmed tx')
|
||||
# Send the transaction that replaces the mempool transaction and opts out of replaceability
|
||||
# node.sendrawtransaction(hexstring=tx.serialize().hex(), allowhighfees=True)
|
||||
# node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
|
||||
# take original raw_tx_0
|
||||
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
|
||||
tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee
|
||||
@ -139,7 +139,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||
self.check_mempool_result(
|
||||
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict'}],
|
||||
rawtxs=[tx.serialize().hex()],
|
||||
allowhighfees=True,
|
||||
maxfeerate=0,
|
||||
)
|
||||
|
||||
self.log.info('A transaction with missing inputs, that never existed')
|
||||
@ -155,7 +155,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
|
||||
tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend
|
||||
raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex']
|
||||
txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, allowhighfees=True)
|
||||
txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0)
|
||||
# Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them
|
||||
raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction(
|
||||
inputs=[
|
||||
@ -164,7 +164,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||
],
|
||||
outputs=[{node.getnewaddress(): 0.1}]
|
||||
))['hex']
|
||||
txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, allowhighfees=True)
|
||||
txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0)
|
||||
node.generate(1)
|
||||
self.mempool_size = 0
|
||||
# Now see if we can add the coins back to the utxo set by sending the exact txs again
|
||||
@ -313,7 +313,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||
self.check_mempool_result(
|
||||
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final'}],
|
||||
rawtxs=[tx.serialize().hex()],
|
||||
allowhighfees=True,
|
||||
maxfeerate=0,
|
||||
)
|
||||
|
||||
|
||||
|
@ -31,13 +31,13 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
|
||||
b = [ self.nodes[0].getblockhash(n) for n in range(1, 4) ]
|
||||
coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ]
|
||||
spends1_raw = [ create_raw_transaction(self.nodes[0], txid, node0_address, 500) for txid in coinbase_txids ]
|
||||
spends1_id = [ self.nodes[0].sendrawtransaction(tx, False, False, True) for tx in spends1_raw ]
|
||||
spends1_id = [ self.nodes[0].sendrawtransaction(tx, 0, False, True) for tx in spends1_raw ]
|
||||
|
||||
blocks = []
|
||||
blocks.extend(self.nodes[0].generate(1))
|
||||
|
||||
spends2_raw = [ create_raw_transaction(self.nodes[0], txid, node0_address, 499.99) for txid in spends1_id ]
|
||||
spends2_id = [ self.nodes[0].sendrawtransaction(tx, False, False, True) for tx in spends2_raw ]
|
||||
spends2_id = [ self.nodes[0].sendrawtransaction(tx, 0, False, True) for tx in spends2_raw ]
|
||||
|
||||
blocks.extend(self.nodes[0].generate(1))
|
||||
|
||||
|
@ -177,5 +177,10 @@ class GetblockstatsTest(BitcoinTestFramework):
|
||||
assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats,
|
||||
hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f')
|
||||
|
||||
# Invalid number of args
|
||||
assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats, '00', 1, 2)
|
||||
assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
GetblockstatsTest().main()
|
||||
|
@ -234,11 +234,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
|
||||
txDetails = self.nodes[0].gettransaction(txId, True)
|
||||
rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
|
||||
vout = False
|
||||
for outpoint in rawTx['vout']:
|
||||
if outpoint['value'] == Decimal('2.20000000'):
|
||||
vout = outpoint
|
||||
break
|
||||
vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000'))
|
||||
|
||||
bal = self.nodes[0].getbalance()
|
||||
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}]
|
||||
@ -279,11 +275,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
|
||||
txDetails = self.nodes[0].gettransaction(txId, True)
|
||||
rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
|
||||
vout = False
|
||||
for outpoint in rawTx2['vout']:
|
||||
if outpoint['value'] == Decimal('2.20000000'):
|
||||
vout = outpoint
|
||||
break
|
||||
vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000'))
|
||||
|
||||
bal = self.nodes[0].getbalance()
|
||||
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}]
|
||||
@ -375,5 +367,30 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
|
||||
assert_equal(decrawtx['version'], 0x7fff)
|
||||
|
||||
self.log.info('sendrawtransaction/testmempoolaccept with maxfeerate')
|
||||
|
||||
txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
|
||||
rawTx = self.nodes[0].getrawtransaction(txId, True)
|
||||
vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1.00000000'))
|
||||
|
||||
self.sync_all()
|
||||
inputs = [{ "txid" : txId, "vout" : vout['n'] }]
|
||||
outputs = { self.nodes[0].getnewaddress() : Decimal("0.99999000") } # 1000 sat fee
|
||||
rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
|
||||
rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx)
|
||||
assert_equal(rawTxSigned['complete'], True)
|
||||
# 1000 sat fee, ~200 b transaction, fee rate should land around 5 sat/b = 0.00005000 BTC/kB
|
||||
# Thus, testmempoolaccept should reject
|
||||
testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']], 0.00001000)[0]
|
||||
assert_equal(testres['allowed'], False)
|
||||
assert_equal(testres['reject-reason'], '256: absurdly-high-fee')
|
||||
# and sendrawtransaction should throw
|
||||
assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
|
||||
# And below calls should both succeed
|
||||
testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate=0.00007000)[0]
|
||||
assert_equal(testres['allowed'], True)
|
||||
self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate=0.00007000)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
RawTransactionsTest().main()
|
||||
|
@ -572,7 +572,7 @@ def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
|
||||
|
||||
rawtx = from_node.createrawtransaction(inputs, outputs)
|
||||
signresult = from_node.signrawtransactionwithwallet(rawtx)
|
||||
txid = from_node.sendrawtransaction(signresult["hex"], True)
|
||||
txid = from_node.sendrawtransaction(signresult["hex"], 0)
|
||||
|
||||
return (txid, signresult["hex"], fee)
|
||||
|
||||
@ -646,7 +646,7 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
|
||||
tx.vout.append(txout)
|
||||
newtx = tx.serialize().hex()
|
||||
signresult = node.signrawtransactionwithwallet(newtx, None, "NONE")
|
||||
txid = node.sendrawtransaction(signresult["hex"], True)
|
||||
txid = node.sendrawtransaction(signresult["hex"], 0)
|
||||
txids.append(txid)
|
||||
return txids
|
||||
|
||||
|
@ -178,6 +178,7 @@ BASE_SCRIPTS = [
|
||||
'wallet_importprunedfunds.py',
|
||||
'p2p_leak_tx.py',
|
||||
'rpc_signmessage.py',
|
||||
'wallet_balance.py',
|
||||
'feature_nulldummy.py',
|
||||
'mempool_accept.py',
|
||||
'mempool_expiry.py',
|
||||
|
133
test/functional/wallet_balance.py
Executable file
133
test/functional/wallet_balance.py
Executable file
@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2018 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test the wallet balance RPC methods."""
|
||||
from decimal import Decimal
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
|
||||
RANDOM_COINBASE_ADDRESS = 'ycwedq2f3sz2Yf9JqZsBCQPxp18WU3Hp4J'
|
||||
|
||||
def create_transactions(node, address, amt, fees):
|
||||
# Create and sign raw transactions from node to address for amt.
|
||||
# Creates a transaction for each fee and returns an array
|
||||
# of the raw transactions.
|
||||
utxos = node.listunspent(0)
|
||||
|
||||
# Create transactions
|
||||
inputs = []
|
||||
ins_total = 0
|
||||
for utxo in utxos:
|
||||
inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]})
|
||||
ins_total += utxo['amount']
|
||||
if ins_total > amt:
|
||||
break
|
||||
|
||||
txs = []
|
||||
for fee in fees:
|
||||
outputs = {address: amt, node.getrawchangeaddress(): ins_total - amt - fee}
|
||||
raw_tx = node.createrawtransaction(inputs, outputs, 0)
|
||||
raw_tx = node.signrawtransactionwithwallet(raw_tx)
|
||||
txs.append(raw_tx)
|
||||
|
||||
return txs
|
||||
|
||||
class WalletTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 2
|
||||
self.setup_clean_chain = True
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
# Check that nodes don't own any UTXOs
|
||||
assert_equal(len(self.nodes[0].listunspent()), 0)
|
||||
assert_equal(len(self.nodes[1].listunspent()), 0)
|
||||
|
||||
self.log.info("Mining one block for each node")
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
self.nodes[1].generate(1)
|
||||
self.nodes[1].generatetoaddress(100, RANDOM_COINBASE_ADDRESS)
|
||||
self.sync_all()
|
||||
|
||||
assert_equal(self.nodes[0].getbalance(), 500)
|
||||
assert_equal(self.nodes[1].getbalance(), 500)
|
||||
|
||||
self.log.info("Test getbalance with different arguments")
|
||||
assert_equal(self.nodes[0].getbalance("*"), 500)
|
||||
assert_equal(self.nodes[0].getbalance("*", 1), 500)
|
||||
assert_equal(self.nodes[0].getbalance("*", 1, True), 500)
|
||||
assert_equal(self.nodes[0].getbalance(minconf=1), 500)
|
||||
|
||||
# Send 490 BTC from 0 to 1 and 960 BTC from 1 to 0.
|
||||
txs = create_transactions(self.nodes[0], self.nodes[1].getnewaddress(), 490 , [Decimal('0.01')])
|
||||
self.nodes[0].sendrawtransaction(txs[0]['hex'])
|
||||
self.nodes[1].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation
|
||||
|
||||
self.sync_all()
|
||||
txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), 960, [Decimal('0.01'), Decimal('0.02')])
|
||||
self.nodes[1].sendrawtransaction(txs[0]['hex'])
|
||||
self.nodes[0].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation
|
||||
self.sync_all()
|
||||
|
||||
# First argument of getbalance must be set to "*"
|
||||
assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", self.nodes[1].getbalance, "")
|
||||
|
||||
self.log.info("Test getbalance and getunconfirmedbalance with unconfirmed inputs")
|
||||
|
||||
# getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions
|
||||
assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send
|
||||
assert_equal(self.nodes[1].getbalance(), Decimal('29.99')) # change from node 1's send
|
||||
# Same with minconf=0
|
||||
assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99'))
|
||||
assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('29.99'))
|
||||
# getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
|
||||
# TODO: fix getbalance tracking of coin spentness depth
|
||||
assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0'))
|
||||
assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0'))
|
||||
# getunconfirmedbalance
|
||||
assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('960')) # output of node 1's spend
|
||||
assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent
|
||||
|
||||
# Node 1 bumps the transaction fee and resends
|
||||
# self.nodes[1].sendrawtransaction(txs[1]['hex']) # disabled, no RBF in Dash
|
||||
self.sync_all()
|
||||
|
||||
self.log.info("Test getbalance and getunconfirmedbalance with conflicted unconfirmed inputs")
|
||||
|
||||
assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('960')) # output of node 1's send
|
||||
assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('960'))
|
||||
assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) # Doesn't include output of node 0's send since it was spent
|
||||
assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0'))
|
||||
|
||||
self.nodes[1].generatetoaddress(1, RANDOM_COINBASE_ADDRESS)
|
||||
self.sync_all()
|
||||
|
||||
# balances are correct after the transactions are confirmed
|
||||
assert_equal(self.nodes[0].getbalance(), Decimal('969.99')) # node 1's send plus change from node 0's send
|
||||
assert_equal(self.nodes[1].getbalance(), Decimal('29.99')) # change from node 0's send
|
||||
|
||||
# Send total balance away from node 1
|
||||
txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), Decimal('29.98'), [Decimal('0.01')])
|
||||
self.nodes[1].sendrawtransaction(txs[0]['hex'])
|
||||
self.nodes[1].generatetoaddress(2, RANDOM_COINBASE_ADDRESS)
|
||||
self.sync_all()
|
||||
|
||||
# getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago
|
||||
# TODO: fix getbalance tracking of coin spentness depth
|
||||
# getbalance with minconf=3 should still show the old balance
|
||||
assert_equal(self.nodes[1].getbalance(minconf=3), Decimal('0'))
|
||||
|
||||
# getbalance with minconf=2 will show the new balance.
|
||||
assert_equal(self.nodes[1].getbalance(minconf=2), Decimal('0'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletTest().main()
|
@ -65,15 +65,6 @@ class WalletTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[1].getbalance(), 500)
|
||||
assert_equal(self.nodes[2].getbalance(), 0)
|
||||
|
||||
# Check getbalance with different arguments
|
||||
assert_equal(self.nodes[0].getbalance("*"), 500)
|
||||
assert_equal(self.nodes[0].getbalance("*", 1), 500)
|
||||
assert_equal(self.nodes[0].getbalance("*", 1, True), 500)
|
||||
assert_equal(self.nodes[0].getbalance(minconf=1), 500)
|
||||
|
||||
# first argument of getbalance must be excluded or set to "*"
|
||||
assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", self.nodes[0].getbalance, "")
|
||||
|
||||
# Check that only first and second nodes have UTXOs
|
||||
utxos = self.nodes[0].listunspent()
|
||||
assert_equal(len(utxos), 1)
|
||||
@ -174,8 +165,8 @@ class WalletTest(BitcoinTestFramework):
|
||||
totalfee += fee_per_input
|
||||
|
||||
# Have node 1 (miner) send the transactions
|
||||
self.nodes[1].sendrawtransaction(txns_to_send[0]["hex"])
|
||||
self.nodes[1].sendrawtransaction(txns_to_send[1]["hex"])
|
||||
self.nodes[1].sendrawtransaction(txns_to_send[0]["hex"], 0)
|
||||
self.nodes[1].sendrawtransaction(txns_to_send[1]["hex"], 0)
|
||||
|
||||
# Have node1 mine a block to confirm transactions:
|
||||
self.nodes[1].generate(1)
|
||||
@ -222,27 +213,9 @@ class WalletTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[2].getbalance(), node_2_bal)
|
||||
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('100'), fee_per_byte, count_bytes(self.nodes[2].gettransaction(txid)['hex']))
|
||||
|
||||
# Test ResendWalletTransactions:
|
||||
# Create a couple of transactions, then start up a fourth
|
||||
# node (nodes[3]) and ask nodes[0] to rebroadcast.
|
||||
# EXPECT: nodes[3] should have those transactions in its mempool.
|
||||
txid1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
|
||||
txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||
self.sync_mempools(self.nodes[0:2])
|
||||
|
||||
self.start_node(3)
|
||||
connect_nodes_bi(self.nodes, 0, 3)
|
||||
self.sync_blocks()
|
||||
|
||||
relayed = self.nodes[0].resendwallettransactions()
|
||||
assert_equal(set(relayed), {txid1, txid2})
|
||||
self.sync_mempools()
|
||||
|
||||
assert txid1 in self.nodes[3].getrawmempool()
|
||||
|
||||
# Exercise balance rpcs
|
||||
assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], 1)
|
||||
assert_equal(self.nodes[0].getunconfirmedbalance(), 1)
|
||||
self.sync_all()
|
||||
|
||||
# check if we can list zero value tx as available coins
|
||||
# 1. create raw_tx
|
||||
|
@ -2,31 +2,84 @@
|
||||
# Copyright (c) 2017 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test resendwallettransactions RPC."""
|
||||
"""Test that the wallet resends transactions periodically."""
|
||||
from collections import defaultdict
|
||||
import time
|
||||
|
||||
from test_framework.blocktools import create_block, create_coinbase
|
||||
from test_framework.messages import ToHex
|
||||
from test_framework.mininode import P2PInterface, mininode_lock
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||
from test_framework.util import assert_equal, wait_until
|
||||
|
||||
class P2PStoreTxInvs(P2PInterface):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.tx_invs_received = defaultdict(int)
|
||||
|
||||
def on_inv(self, message):
|
||||
# Store how many times invs have been received for each tx.
|
||||
for i in message.inv:
|
||||
if i.type == 1:
|
||||
# save txid
|
||||
self.tx_invs_received[i.hash] += 1
|
||||
|
||||
class ResendWalletTransactionsTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
self.extra_args = [['--walletbroadcast=false']]
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
# Should raise RPC_WALLET_ERROR (-4) if walletbroadcast is disabled.
|
||||
assert_raises_rpc_error(-4, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast", self.nodes[0].resendwallettransactions)
|
||||
node = self.nodes[0] # alias
|
||||
|
||||
# Should return an empty array if there aren't unconfirmed wallet transactions.
|
||||
self.stop_node(0)
|
||||
self.start_node(0, extra_args=[])
|
||||
assert_equal(self.nodes[0].resendwallettransactions(), [])
|
||||
node.add_p2p_connection(P2PStoreTxInvs())
|
||||
|
||||
# Should return an array with the unconfirmed wallet transaction.
|
||||
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||
assert_equal(self.nodes[0].resendwallettransactions(), [txid])
|
||||
self.log.info("Create a new transaction and wait until it's broadcast")
|
||||
txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16)
|
||||
|
||||
# Wallet rebroadcast is first scheduled 1 sec after startup (see
|
||||
# nNextResend in ResendWalletTransactions()). Sleep for just over a
|
||||
# second to be certain that it has been called before the first
|
||||
# setmocktime call below.
|
||||
time.sleep(1.1)
|
||||
|
||||
# Can take a few seconds due to transaction trickling
|
||||
def wait_p2p():
|
||||
self.bump_mocktime(1)
|
||||
return node.p2p.tx_invs_received[txid] >= 1
|
||||
wait_until(wait_p2p, lock=mininode_lock)
|
||||
|
||||
# Add a second peer since txs aren't rebroadcast to the same peer (see filterInventoryKnown)
|
||||
node.add_p2p_connection(P2PStoreTxInvs())
|
||||
|
||||
self.log.info("Create a block")
|
||||
# Create and submit a block without the transaction.
|
||||
# Transactions are only rebroadcast if there has been a block at least five minutes
|
||||
# after the last time we tried to broadcast. Use mocktime and give an extra minute to be sure.
|
||||
block_time = self.mocktime + 6 * 60
|
||||
node.setmocktime(block_time)
|
||||
block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockchaininfo()['blocks']), block_time)
|
||||
block.nVersion = 3
|
||||
block.rehash()
|
||||
block.solve()
|
||||
node.submitblock(ToHex(block))
|
||||
|
||||
# Transaction should not be rebroadcast
|
||||
node.p2ps[1].sync_with_ping()
|
||||
assert_equal(node.p2ps[1].tx_invs_received[txid], 0)
|
||||
|
||||
self.log.info("Transaction should be rebroadcast after 30 minutes")
|
||||
# Use mocktime and give an extra 5 minutes to be sure.
|
||||
rebroadcast_time = self.mocktime + 41 * 60
|
||||
node.setmocktime(rebroadcast_time)
|
||||
self.mocktime = rebroadcast_time
|
||||
|
||||
def wait_p2p_1():
|
||||
self.bump_mocktime(1)
|
||||
return node.p2ps[1].tx_invs_received[txid] >= 1
|
||||
wait_until(wait_p2p_1, lock=mininode_lock)
|
||||
|
||||
if __name__ == '__main__':
|
||||
ResendWalletTransactionsTest().main()
|
||||
|
Loading…
Reference in New Issue
Block a user