Merge pull request #4568 from kittywhiskers/miscports

merge bitcoin#15588...#16475: backports
This commit is contained in:
UdjinM6 2021-12-13 01:15:18 +03:00 committed by GitHub
commit 6af131f825
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 813 additions and 672 deletions

1
.gitignore vendored
View File

@ -91,6 +91,7 @@ libconftest.dylib*
# Compilation and Qt preprocessor part # Compilation and Qt preprocessor part
*.qm *.qm
Makefile Makefile
!depends/Makefile
dash-qt dash-qt
Dash-Qt.app Dash-Qt.app
background.tiff* background.tiff*

View File

@ -21,6 +21,8 @@ bench_bench_dash_SOURCES = \
bench/bls_dkg.cpp \ bench/bls_dkg.cpp \
bench/checkblock.cpp \ bench/checkblock.cpp \
bench/checkqueue.cpp \ bench/checkqueue.cpp \
bench/data.h \
bench/data.cpp \
bench/duplicate_inputs.cpp \ bench/duplicate_inputs.cpp \
bench/ecdsa.cpp \ bench/ecdsa.cpp \
bench/examples.cpp \ bench/examples.cpp \
@ -83,7 +85,7 @@ CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES)
CLEANFILES += $(CLEAN_BITCOIN_BENCH) CLEANFILES += $(CLEAN_BITCOIN_BENCH)
bench/checkblock.cpp: bench/data/block813851.raw.h bench/data.cpp: bench/data/block813851.raw.h
bitcoin_bench: $(BENCH_BINARY) bitcoin_bench: $(BENCH_BINARY)
@ -97,7 +99,7 @@ bench/data/%.raw.h: bench/data/%.raw
@$(MKDIR_P) $(@D) @$(MKDIR_P) $(@D)
@{ \ @{ \
echo "namespace raw_bench{" && \ 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' && \ $(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' && \
echo "};};"; \ echo "};};"; \
} > "$@.new" && mv -f "$@.new" "$@" } > "$@.new" && mv -f "$@.new" "$@"

View File

@ -3,39 +3,34 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bench/bench.h> #include <bench/bench.h>
#include <bench/data.h>
#include <chainparams.h> #include <chainparams.h>
#include <validation.h> #include <validation.h>
#include <streams.h> #include <streams.h>
#include <consensus/validation.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 // 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 // a block off the wire, but before we can relay the block on to peers using
// compact block relay. // compact block relay.
static void DeserializeBlockTest(benchmark::Bench& bench) static void DeserializeBlockTest(benchmark::Bench& bench)
{ {
CDataStream stream((const char*)raw_bench::block813851, CDataStream stream(benchmark::data::block813851, SER_NETWORK, PROTOCOL_VERSION);
(const char*)raw_bench::block813851+sizeof(raw_bench::block813851),
SER_NETWORK, PROTOCOL_VERSION);
char a = '\0'; char a = '\0';
stream.write(&a, 1); // Prevent compaction stream.write(&a, 1); // Prevent compaction
bench.unit("block").run([&] { bench.unit("block").run([&] {
CBlock block; CBlock block;
stream >> block; stream >> block;
bool rewound = stream.Rewind(sizeof(raw_bench::block813851)); bool rewound = stream.Rewind(benchmark::data::block813851.size());
assert(rewound); assert(rewound);
}); });
} }
static void DeserializeAndCheckBlockTest(benchmark::Bench& bench) static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
{ {
CDataStream stream((const char*)raw_bench::block813851, CDataStream stream(benchmark::data::block813851, SER_NETWORK, PROTOCOL_VERSION);
(const char*)raw_bench::block813851+sizeof(raw_bench::block813851),
SER_NETWORK, PROTOCOL_VERSION);
char a = '\0'; char a = '\0';
stream.write(&a, 1); // Prevent compaction stream.write(&a, 1); // Prevent compaction
@ -44,7 +39,7 @@ static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
bench.unit("block").run([&] { bench.unit("block").run([&] {
CBlock block; // Note that CBlock caches its checked state, so we need to recreate it here CBlock block; // Note that CBlock caches its checked state, so we need to recreate it here
stream >> block; stream >> block;
bool rewound = stream.Rewind(sizeof(raw_bench::block813851)); bool rewound = stream.Rewind(benchmark::data::block813851.size());
assert(rewound); assert(rewound);
CValidationState validationState; CValidationState validationState;

14
src/bench/data.cpp Normal file
View 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
View 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

View File

@ -22,6 +22,8 @@
#include <string> #include <string>
constexpr static CAmount DEFAULT_MAX_RAW_TX_FEE{COIN / 10};
bool CCoinJoinEntry::AddScriptSig(const CTxIn& txin) bool CCoinJoinEntry::AddScriptSig(const CTxIn& txin)
{ {
for (auto& txdsin : vecTxDSIn) { for (auto& txdsin : vecTxDSIn) {
@ -370,7 +372,7 @@ bool CCoinJoin::IsCollateralValid(const CTransaction& txCollateral)
{ {
LOCK(cs_main); LOCK(cs_main);
CValidationState validationState; 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"); LogPrint(BCLog::COINJOIN, "CCoinJoin::IsCollateralValid -- didn't pass AcceptToMemoryPool()\n");
return false; return false;
} }

View File

@ -23,6 +23,7 @@
#include <univalue.h> #include <univalue.h>
CCoinJoinServer coinJoinServer; 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) 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); TRY_LOCK(cs_main, lockMain);
CValidationState validationState; CValidationState validationState;
mempool.PrioritiseTransaction(hashTx, 0.1 * COIN); 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"); LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n");
SetNull(); SetNull();
// not much we can do in this case, just notify clients // not much we can do in this case, just notify clients

View File

@ -236,8 +236,8 @@ CAmount CTransactionBuilder::GetFee(unsigned int nBytes) const
if (nRequiredFee > nFeeCalc) { if (nRequiredFee > nFeeCalc) {
nFeeCalc = nRequiredFee; nFeeCalc = nRequiredFee;
} }
if (nFeeCalc > ::maxTxFee) { if (nFeeCalc > pwallet->m_default_max_tx_fee) {
nFeeCalc = ::maxTxFee; nFeeCalc = pwallet->m_default_max_tx_fee;
} }
return nFeeCalc; return nFeeCalc;
} }

View File

@ -37,6 +37,7 @@ void DummyWalletInit::AddWalletOptions() const
"-disablewallet", "-disablewallet",
"-instantsendnotify=<cmd>", "-instantsendnotify=<cmd>",
"-keypool=<n>", "-keypool=<n>",
"-maxtxfee=<amt>",
"-rescan=<mode>", "-rescan=<mode>",
"-salvagewallet", "-salvagewallet",
"-spendzeroconfchange", "-spendzeroconfchange",

View File

@ -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("-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("-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("-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("-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("-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); 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); 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()); fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
if (chainparams.RequireStandard() && !fRequireStandard) if (chainparams.RequireStandard() && !fRequireStandard)
return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString())); return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString()));

View File

@ -11,6 +11,7 @@
#include <interfaces/wallet.h> #include <interfaces/wallet.h>
#include <net.h> #include <net.h>
#include <node/coin.h> #include <node/coin.h>
#include <node/transaction.h>
#include <policy/fees.h> #include <policy/fees.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <primitives/block.h> #include <primitives/block.h>
@ -37,7 +38,7 @@
namespace interfaces { namespace interfaces {
namespace { namespace {
class LockImpl : public Chain::Lock class LockImpl : public Chain::Lock, public UniqueLock<CCriticalSection>
{ {
Optional<int> getHeight() override Optional<int> getHeight() override
{ {
@ -170,16 +171,7 @@ class LockImpl : public Chain::Lock
LockAnnotation lock(::cs_main); LockAnnotation lock(::cs_main);
return CheckFinalTx(tx); 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; using UniqueLock::UniqueLock;
}; };
@ -217,17 +209,11 @@ public:
{ {
m_notifications->BlockDisconnected(*block); m_notifications->BlockDisconnected(*block);
} }
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); } void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
void ResendWalletTransactions(int64_t best_block_time, CConnman*) override
{ {
// `cs_main` is always held when this method is called, so it is safe to m_notifications->UpdatedBlockTip();
// 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);
} }
void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->ChainStateFlushed(locator); }
void NotifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr<const llmq::CChainLockSig>& clsig) override void NotifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr<const llmq::CChainLockSig>& clsig) override
{ {
m_notifications->NotifyChainLock(pindexChainLock, clsig); m_notifications->NotifyChainLock(pindexChainLock, clsig);
@ -284,13 +270,12 @@ class ChainImpl : public Chain
public: public:
std::unique_ptr<Chain::Lock> lock(bool try_lock) override 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 {}; if (try_lock && result && !*result) return {};
// std::move necessary on some compilers due to conversion from // std::move necessary on some compilers due to conversion from
// LockingStateImpl to Lock pointer // LockImpl to Lock pointer
return std::move(result); 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 bool findBlock(const uint256& hash, CBlock* block, int64_t* time, int64_t* time_max) override
{ {
CBlockIndex* index; CBlockIndex* index;
@ -324,10 +309,13 @@ public:
auto it = ::mempool.GetIter(txid); auto it = ::mempool.GetIter(txid);
return it && (*it)->GetCountWithDescendants() > 1; 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); const TransactionError err = BroadcastTransaction(tx, err_string, max_tx_fee, relay, /*wait_callback*/ false);
g_connman->ForEachNode([&inv](CNode* node) { node->PushInventory(inv); }); // 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 void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override
{ {
@ -362,9 +350,9 @@ public:
CFeeRate relayMinFee() override { return ::minRelayTxFee; } CFeeRate relayMinFee() override { return ::minRelayTxFee; }
CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; } CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; }
CFeeRate relayDustFee() override { return ::dustRelayFee; } CFeeRate relayDustFee() override { return ::dustRelayFee; }
CAmount maxTxFee() override { return ::maxTxFee; }
bool getPruneMode() override { return ::fPruneMode; } bool getPruneMode() override { return ::fPruneMode; }
bool p2pEnabled() override { return g_connman != nullptr; } bool p2pEnabled() override { return g_connman != nullptr; }
bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !::ChainstateActive().IsInitialBlockDownload(); }
bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); }
bool shutdownRequested() override { return ShutdownRequested(); } bool shutdownRequested() override { return ShutdownRequested(); }
int64_t getAdjustedTime() override { return GetAdjustedTime(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); }

View File

@ -52,10 +52,6 @@ class Handler;
//! asynchronously //! asynchronously
//! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269). //! (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 //! * The initMessages() and loadWallet() methods which the wallet uses to send
//! notifications to the GUI should go away when GUI and wallet can directly //! notifications to the GUI should go away when GUI and wallet can directly
//! communicate with each other without going through the node //! 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. //! Check if transaction will be final given chain height current time.
virtual bool checkFinalTx(const CTransaction& tx) = 0; 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 //! Return Lock interface. Chain is locked when this is called, and
//! unlocked when the returned interface is freed. //! unlocked when the returned interface is freed.
virtual std::unique_ptr<Lock> lock(bool try_lock = false) = 0; 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 //! Return whether node has the block and optionally return block metadata
//! or contents. //! or contents.
//! //!
@ -184,8 +170,10 @@ public:
//! Check if transaction has descendants in mempool. //! Check if transaction has descendants in mempool.
virtual bool hasDescendantsInMempool(const uint256& txid) = 0; virtual bool hasDescendantsInMempool(const uint256& txid) = 0;
//! Relay transaction. //! Transaction is added to memory pool, if the transaction fee is below the
virtual void relayTransaction(const uint256& txid) = 0; //! 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. //! Calculate mempool ancestor and descendant counts for the given transaction.
virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) = 0; 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. //! Relay dust fee setting (-dustrelayfee), reflecting lowest rate it's economical to spend.
virtual CFeeRate relayDustFee() = 0; 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. //! Check if pruning is enabled.
virtual bool getPruneMode() = 0; virtual bool getPruneMode() = 0;
//! Check if p2p enabled. //! Check if p2p enabled.
virtual bool p2pEnabled() = 0; virtual bool p2pEnabled() = 0;
//! Check if the node is ready to broadcast transactions.
virtual bool isReadyToBroadcast() = 0;
//! Check if in IBD. //! Check if in IBD.
virtual bool isInitialBlockDownload() = 0; virtual bool isInitialBlockDownload() = 0;
@ -256,8 +241,8 @@ public:
virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason) {} virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason) {}
virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {} virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {}
virtual void BlockDisconnected(const CBlock& block) {} virtual void BlockDisconnected(const CBlock& block) {}
virtual void UpdatedBlockTip() {}
virtual void ChainStateFlushed(const CBlockLocator& locator) {} 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 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) {} virtual void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr<const llmq::CInstantSendLock>& islock) {}
}; };

View File

@ -344,7 +344,6 @@ public:
} }
} }
bool getNetworkActive() override { return g_connman && g_connman->GetNetworkActive(); } 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 CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) override
{ {
FeeCalculation fee_calc; FeeCalculation fee_calc;

View File

@ -229,9 +229,6 @@ public:
//! Get network active. //! Get network active.
virtual bool getNetworkActive() = 0; virtual bool getNetworkActive() = 0;
//! Get max tx fee.
virtual CAmount getMaxTxFee() = 0;
//! Estimate smart fee. //! Estimate smart fee.
virtual CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) = 0; virtual CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) = 0;

View File

@ -42,32 +42,6 @@
namespace interfaces { namespace interfaces {
namespace { 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. //! Construct wallet tx struct.
WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx)
{ {
@ -308,7 +282,7 @@ public:
LOCK2(cs_main, m_wallet->cs_wallet); LOCK2(cs_main, m_wallet->cs_wallet);
return m_wallet->ListProTxCoins(outputs); 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, const CCoinControl& coin_control,
bool sign, bool sign,
int& change_pos, int& change_pos,
@ -317,12 +291,28 @@ public:
{ {
auto locked_chain = m_wallet->chain().lock(); auto locked_chain = m_wallet->chain().lock();
LOCK2(mempool.cs, m_wallet->cs_wallet); LOCK2(mempool.cs, m_wallet->cs_wallet);
auto pending = MakeUnique<PendingWalletTxImpl>(*m_wallet); CReserveKey m_key(m_wallet.get());
if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_key, fee, change_pos, CTransactionRef tx;
if (!m_wallet->CreateTransaction(*locked_chain, recipients, tx, m_key, fee, change_pos,
fail_reason, coin_control, sign)) { fail_reason, coin_control, sign)) {
return {}; 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 transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); }
bool abandonTransaction(const uint256& txid) override bool abandonTransaction(const uint256& txid) override
@ -544,6 +534,7 @@ public:
bool hdEnabled() override { return m_wallet->IsHDEnabled(); } bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
bool IsWalletFlagSet(uint64_t flag) override { return m_wallet->IsWalletFlagSet(flag); } bool IsWalletFlagSet(uint64_t flag) override { return m_wallet->IsWalletFlagSet(flag); }
CoinJoin::Client& coinJoin() override { return m_coinjoin; } CoinJoin::Client& coinJoin() override { return m_coinjoin; }
CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }
void remove() override void remove() override
{ {
RemoveWallet(m_wallet); RemoveWallet(m_wallet);

View File

@ -32,7 +32,6 @@ struct CRecipient;
namespace interfaces { namespace interfaces {
class Handler; class Handler;
class PendingWalletTx;
struct WalletAddress; struct WalletAddress;
struct WalletBalances; struct WalletBalances;
struct WalletTx; struct WalletTx;
@ -159,13 +158,19 @@ public:
virtual void listProTxCoins(std::vector<COutPoint>& vOutpts) = 0; virtual void listProTxCoins(std::vector<COutPoint>& vOutpts) = 0;
//! Create transaction. //! 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, const CCoinControl& coin_control,
bool sign, bool sign,
int& change_pos, int& change_pos,
CAmount& fee, CAmount& fee,
std::string& fail_reason) = 0; 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. //! Return whether transaction can be abandoned.
virtual bool transactionCanBeAbandoned(const uint256& txid) = 0; virtual bool transactionCanBeAbandoned(const uint256& txid) = 0;
@ -266,6 +271,9 @@ public:
virtual CoinJoin::Client& coinJoin() = 0; virtual CoinJoin::Client& coinJoin() = 0;
//! Get max tx fee.
virtual CAmount getDefaultMaxTxFee() = 0;
// Remove wallet. // Remove wallet.
virtual void remove() = 0; virtual void remove() = 0;
@ -310,21 +318,6 @@ public:
virtual std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0; 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. //! Information about one wallet address.
struct WalletAddress struct WalletAddress
{ {

View File

@ -237,8 +237,6 @@ namespace {
/** Expiration-time ordered list of (expire time, relay map entry) pairs. */ /** 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::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 struct IteratorComparator
{ {
template<typename I> template<typename I>
@ -1314,8 +1312,6 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB
}); });
connman->WakeMessageHandler(); 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 // Try sending block announcements via headers
// //

View File

@ -13,26 +13,30 @@
#include <future> #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; std::promise<void> promise;
hashTx = tx->GetHash(); uint256 hashTx = tx->GetHash();
bool callback_set = false;
{ // cs_main scope { // cs_main scope
LOCK(cs_main); LOCK(cs_main);
// If the transaction is already confirmed in the chain, don't do anything
// and return early.
CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); CCoinsViewCache &view = ::ChainstateActive().CoinsTip();
bool fHaveChain = false; for (size_t o = 0; o < tx->vout.size(); o++) {
for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, 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 (!mempool.exists(hashTx)) {
if (!fHaveMempool && !fHaveChain) { // Transaction is not already in the mempool. Submit it.
// push to local node and sync with wallets
CValidationState state; CValidationState state;
bool fMissingInputs; bool fMissingInputs;
if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
bypass_limits, highfee)) { bypass_limits, max_tx_fee)) {
if (state.IsInvalid()) { if (state.IsInvalid()) {
err_string = FormatStateMessage(state); err_string = FormatStateMessage(state);
return TransactionError::MEMPOOL_REJECTED; return TransactionError::MEMPOOL_REJECTED;
@ -43,33 +47,37 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx,
err_string = FormatStateMessage(state); err_string = FormatStateMessage(state);
return TransactionError::MEMPOOL_ERROR; 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 // Transaction was accepted to the mempool.
// where a user might call sendrawtransaction with a transaction
// to/from their wallet, immediately call some wallet RPC, and get if (wait_callback) {
// a stale result because callbacks have not yet been processed. // 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] { CallFunctionInValidationInterfaceQueue([&promise] {
promise.set_value(); 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 } // cs_main
if (callback_set) {
// Wait until Validation Interface clients have been notified of the
// transaction entering the mempool.
promise.get_future().wait(); promise.get_future().wait();
if (!g_connman) {
return TransactionError::P2P_DISABLED;
} }
if (relay) {
g_connman->RelayTransaction(*tx); g_connman->RelayTransaction(*tx);
}
return TransactionError::OK; return TransactionError::OK;
} }

View File

@ -11,14 +11,21 @@
#include <util/error.h> #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[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[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 * 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 #endif // BITCOIN_NODE_TRANSACTION_H

View File

@ -411,7 +411,7 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
if (m_coin_control->IsUsingCoinJoin()) { if (m_coin_control->IsUsingCoinJoin()) {
// append number of inputs // append number of inputs
questionString.append("<hr />"); 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)); questionString.append(tr("This transaction will consume %n input(s)", "", nInputs));
// warn about potential privacy issues when spending too many inputs at once // warn about potential privacy issues when spending too many inputs at once
@ -464,7 +464,7 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
accept(); accept();
m_coin_control->UnSelectAll(); m_coin_control->UnSelectAll();
coinControlUpdateLabels(); coinControlUpdateLabels();
Q_EMIT coinsSent(currentTransaction.getWtx()->get().GetHash()); Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash());
} }
fNewRecipientAllowed = true; fNewRecipientAllowed = true;
} }
@ -666,7 +666,7 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn
msgParams.second = CClientUIInterface::MSG_ERROR; msgParams.second = CClientUIInterface::MSG_ERROR;
break; break;
case WalletModel::AbsurdFee: 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; break;
case WalletModel::PaymentRequestExpired: case WalletModel::PaymentRequestExpired:
msgParams.first = tr("Payment request expired."); msgParams.first = tr("Payment request expired.");

View File

@ -270,9 +270,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
} }
// reject absurdly high fee. (This can never happen because the // 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) // belt-and-suspenders check)
if (nFeeRequired > m_node.getMaxTxFee()) if (nFeeRequired > m_wallet->getDefaultMaxTxFee())
return AbsurdFee; return AbsurdFee;
return SendCoinsReturn(OK); return SendCoinsReturn(OK);
@ -312,11 +312,11 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran
auto& newTx = transaction.getWtx(); auto& newTx = transaction.getWtx();
std::string rejectReason; 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)); return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(rejectReason));
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << newTx->get(); ssTx << *newTx;
transaction_array.append(ssTx.data(), ssTx.size()); transaction_array.append(ssTx.data(), ssTx.size());
} }

View File

@ -22,14 +22,14 @@ QList<SendCoinsRecipient> WalletModelTransaction::getRecipients() const
return recipients; return recipients;
} }
std::unique_ptr<interfaces::PendingWalletTx>& WalletModelTransaction::getWtx() CTransactionRef& WalletModelTransaction::getWtx()
{ {
return wtx; return wtx;
} }
unsigned int WalletModelTransaction::getTransactionSize() 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 CAmount WalletModelTransaction::getTransactionFee() const
@ -60,7 +60,7 @@ void WalletModelTransaction::reassignAmounts()
if (out.amount() <= 0) continue; if (out.amount() <= 0) continue;
const unsigned char* scriptStr = (const unsigned char*)out.script().data(); const unsigned char* scriptStr = (const unsigned char*)out.script().data();
CScript scriptPubKey(scriptStr, scriptStr+out.script().size()); 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) { if (txout.scriptPubKey == scriptPubKey) {
subtotal += txout.nValue; subtotal += txout.nValue;
break; break;
@ -72,7 +72,7 @@ void WalletModelTransaction::reassignAmounts()
else // normal recipient (no payment request) else // normal recipient (no payment request)
#endif #endif
{ {
for (const auto& txout : wtx->get().vout) { for (const auto& txout : wtx.get()->vout) {
CScript scriptPubKey = GetScriptForDestination(DecodeDestination(rcp.address.toStdString())); CScript scriptPubKey = GetScriptForDestination(DecodeDestination(rcp.address.toStdString()));
if (txout.scriptPubKey == scriptPubKey) { if (txout.scriptPubKey == scriptPubKey) {
rcp.amount = txout.nValue; rcp.amount = txout.nValue;

View File

@ -16,7 +16,6 @@ class SendCoinsRecipient;
namespace interfaces { namespace interfaces {
class Node; class Node;
class PendingWalletTx;
} }
/** Data model for a walletmodel transaction. */ /** Data model for a walletmodel transaction. */
@ -27,7 +26,7 @@ public:
QList<SendCoinsRecipient> getRecipients() const; QList<SendCoinsRecipient> getRecipients() const;
std::unique_ptr<interfaces::PendingWalletTx>& getWtx(); CTransactionRef& getWtx();
unsigned int getTransactionSize(); unsigned int getTransactionSize();
void setTransactionFee(const CAmount& newFee); void setTransactionFee(const CAmount& newFee);
@ -39,7 +38,7 @@ public:
private: private:
QList<SendCoinsRecipient> recipients; QList<SendCoinsRecipient> recipients;
std::unique_ptr<interfaces::PendingWalletTx> wtx; CTransactionRef wtx;
CAmount fee; CAmount fee;
}; };

View File

@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto // 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 // Copyright (c) 2014-2021 The Dash Core developers
// Distributed under the MIT software license, see the accompanying // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // 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) static UniValue getblockstats(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { const RPCHelpMan help{"getblockstats",
throw std::runtime_error(
RPCHelpMan{"getblockstats",
"\nCompute per block statistics for a given window. All amounts are in duffs.\n" "\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 for some heights with pruning.\n"
"It won't work without -txindex for utxo_size_inc, *fee or *feerate stats.\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\"]'") HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'")
+ HelpExampleRpc("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) { if (g_txindex) {

View File

@ -111,10 +111,12 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "signrawtransactionwithkey", 2, "prevtxs" }, { "signrawtransactionwithkey", 2, "prevtxs" },
{ "signrawtransactionwithwallet", 1, "prevtxs" }, { "signrawtransactionwithwallet", 1, "prevtxs" },
{ "sendrawtransaction", 1, "allowhighfees" }, { "sendrawtransaction", 1, "allowhighfees" },
{ "sendrawtransaction", 1, "maxfeerate" },
{ "sendrawtransaction", 2, "instantsend" }, { "sendrawtransaction", 2, "instantsend" },
{ "sendrawtransaction", 3, "bypasslimits" }, { "sendrawtransaction", 3, "bypasslimits" },
{ "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 0, "rawtxs" },
{ "testmempoolaccept", 1, "allowhighfees" }, { "testmempoolaccept", 1, "allowhighfees" },
{ "testmempoolaccept", 1, "maxfeerate" },
{ "combinerawtransaction", 0, "txs" }, { "combinerawtransaction", 0, "txs" },
{ "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 1, "options" },
{ "walletcreatefundedpsbt", 0, "inputs" }, { "walletcreatefundedpsbt", 0, "inputs" },

View File

@ -584,13 +584,7 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request)
static UniValue setban(const JSONRPCRequest& request) static UniValue setban(const JSONRPCRequest& request)
{ {
std::string strCommand; const RPCHelpMan help{"setban",
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",
"\nAttempts to add or remove an IP/Subnet from the banned list.\n", "\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)"}, {"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\"") + HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"")
+ HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400") + 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) { if (!g_banman) {
throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded");
} }

View File

@ -30,6 +30,7 @@
#include <script/standard.h> #include <script/standard.h>
#include <txmempool.h> #include <txmempool.h>
#include <uint256.h> #include <uint256.h>
#include <util/moneystr.h>
#include <util/validation.h> #include <util/validation.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <validation.h> #include <validation.h>
@ -47,6 +48,11 @@
#include <univalue.h> #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) 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) UniValue sendrawtransaction(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) const RPCHelpMan help{"sendrawtransaction", "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n"
throw std::runtime_error(
RPCHelpMan{"sendrawtransaction",
"\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n"
"\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n", "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n",
{ {
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, {"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"}, {"instantsend", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Deprecated and ignored"},
{"bypasslimits", RPCArg::Type::BOOL, /* default_val */ "false", "Bypass transaction policy limits"}, {"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" "\nAs a JSON-RPC call\n"
+ HelpExampleRpc("sendrawtransaction", "\"signedhex\"") + 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 // parse hex string from parameter
CMutableTransaction mtx; CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) if (!DecodeHexTx(mtx, request.params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
CTransactionRef tx(MakeTransactionRef(std::move(mtx))); CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
bool allowhighfees = false; CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
if (!request.params[1].isNull()) allowhighfees = request.params[1].get_bool();
// 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; bool bypass_limits = false;
if (!request.params[3].isNull()) bypass_limits = request.params[3].get_bool(); if (!request.params[3].isNull()) bypass_limits = request.params[3].get_bool();
const CAmount highfee{allowhighfees ? 0 : ::maxTxFee};
uint256 txid;
std::string err_string; 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) { if (TransactionError::OK != err) {
throw JSONRPCTransactionError(err, err_string); throw JSONRPCTransactionError(err, err_string);
} }
return txid.GetHex(); return tx->GetHash().GetHex();
} }
static UniValue testmempoolaccept(const JSONRPCRequest& request) static UniValue testmempoolaccept(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { const RPCHelpMan help{"testmempoolaccept",
throw std::runtime_error(
RPCHelpMan{"testmempoolaccept",
"\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n" "\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" "\nThis checks if the transaction violates the consensus or policy rules.\n"
"\nSee sendrawtransaction call.\n", "\nSee sendrawtransaction call.\n",
@ -814,7 +832,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, {"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{ RPCResult{
"[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n" "[ (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" "\nAs a JSON-RPC call\n"
+ HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") + 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) { if (request.params[0].get_array().size() != 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now"); 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))); CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const uint256& tx_hash = tx->GetHash(); const uint256& tx_hash = tx->GetHash();
CAmount max_raw_tx_fee = ::maxTxFee; CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
if (!request.params[1].isNull() && request.params[1].get_bool()) { // TODO: temporary migration code for old clients. Remove in v0.20
max_raw_tx_fee = 0; 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); UniValue result(UniValue::VARR);
@ -1375,10 +1406,10 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime"} }, { "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime"} },
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} }, { "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring"} },
{ "rawtransactions", "decodescript", &decodescript, {"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", "combinerawtransaction", &combinerawtransaction, {"txs"} },
{ "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} }, { "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
{ "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} }, { "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees|maxfeerate"} },
{ "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} }, { "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} },
{ "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} }, { "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} },
{ "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} }, { "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },

View File

@ -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 // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // 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; 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 RPCHelpMan::ToString() const
{ {
std::string ret; std::string ret;
@ -418,12 +429,7 @@ std::string RPCHelpMan::ToString() const
ret += m_name; ret += m_name;
bool was_optional{false}; bool was_optional{false};
for (const auto& arg : m_args) { for (const auto& arg : m_args) {
bool optional; const bool optional = arg.IsOptional();
if (arg.m_fallback.which() == 1) {
optional = true;
} else {
optional = RPCArg::Optional::NO != boost::get<RPCArg::Optional>(arg.m_fallback);
}
ret += " "; ret += " ";
if (optional) { if (optional) {
if (!was_optional) ret += "( "; if (!was_optional) ret += "( ";
@ -465,6 +471,15 @@ std::string RPCHelpMan::ToString() const
return ret; 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 RPCArg::ToDescriptionString() const
{ {
std::string ret; std::string ret;

View File

@ -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 // Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -107,7 +107,7 @@ struct RPCArg {
/** Required arg */ /** Required arg */
NO, 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. * `null`. When possible, the default value should be specified.
*/ */
OMITTED_NAMED_ARG, OMITTED_NAMED_ARG,
@ -164,6 +164,8 @@ struct RPCArg {
assert(type == Type::ARR || type == Type::OBJ); assert(type == Type::ARR || type == Type::OBJ);
} }
bool IsOptional() const;
/** /**
* Return the type string of the argument. * Return the type string of the argument.
* Set oneline to allow it to be overridden by a custom oneline type string (m_oneline_description). * 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); RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples);
std::string ToString() const; 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: private:
const std::string m_name; const std::string m_name;

View File

@ -156,7 +156,6 @@ uint256 hashAssumeValid;
arith_uint256 nMinimumChainWork; arith_uint256 nMinimumChainWork;
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
CBlockPolicyEstimator feeEstimator; CBlockPolicyEstimator feeEstimator;
CTxMemPool mempool(&feeEstimator); CTxMemPool mempool(&feeEstimator);

View File

@ -53,12 +53,6 @@ struct LockPoints;
/** Default for -minrelaytxfee, minimum relay fee for transactions */ /** Default for -minrelaytxfee, minimum relay fee for transactions */
static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; 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 */ /** Default for -limitancestorcount, max number of in-mempool ancestors */
static const unsigned int DEFAULT_ANCESTOR_LIMIT = 25; static const unsigned int DEFAULT_ANCESTOR_LIMIT = 25;
/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */ /** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */
@ -146,8 +140,6 @@ extern bool fCheckpointsEnabled;
extern size_t nCoinCacheUsage; extern size_t nCoinCacheUsage;
/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ /** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
extern CFeeRate minRelayTxFee; 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. */ /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */
extern int64_t nMaxTipAge; extern int64_t nMaxTipAge;

View File

@ -25,7 +25,6 @@ struct ValidationInterfaceConnections {
boost::signals2::scoped_connection BlockDisconnected; boost::signals2::scoped_connection BlockDisconnected;
boost::signals2::scoped_connection TransactionRemovedFromMempool; boost::signals2::scoped_connection TransactionRemovedFromMempool;
boost::signals2::scoped_connection ChainStateFlushed; boost::signals2::scoped_connection ChainStateFlushed;
boost::signals2::scoped_connection Broadcast;
boost::signals2::scoped_connection BlockChecked; boost::signals2::scoped_connection BlockChecked;
boost::signals2::scoped_connection NewPoWValidBlock; boost::signals2::scoped_connection NewPoWValidBlock;
boost::signals2::scoped_connection AcceptedBlockHeader; 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 std::shared_ptr<const CBlock> &, const CBlockIndex* pindexDisconnected)> BlockDisconnected;
boost::signals2::signal<void (const CTransactionRef &, MemPoolRemovalReason)> TransactionRemovedFromMempool; boost::signals2::signal<void (const CTransactionRef &, MemPoolRemovalReason)> TransactionRemovedFromMempool;
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed; 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 CBlock&, const CValidationState&)> BlockChecked;
boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock; boost::signals2::signal<void (const CBlockIndex *, const std::shared_ptr<const CBlock>&)> NewPoWValidBlock;
boost::signals2::signal<void (const CBlockIndex *)>AcceptedBlockHeader; 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.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.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.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.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.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)); 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) { void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) {
m_internals->BlockChecked(block, state); m_internals->BlockChecked(block, state);
} }

View File

@ -156,8 +156,6 @@ protected:
* Called on a background thread. * Called on a background thread.
*/ */
virtual void ChainStateFlushed(const CBlockLocator &locator) {} 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. * Notifies listeners of a block validation result.
* If the provided CValidationState IsValid, the provided block * If the provided CValidationState IsValid, the provided block
@ -216,7 +214,6 @@ public:
void NotifyRecoveredSig(const std::shared_ptr<const llmq::CRecoveredSig> &sig); void NotifyRecoveredSig(const std::shared_ptr<const llmq::CRecoveredSig> &sig);
void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff); void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff);
void ChainStateFlushed(const CBlockLocator &); void ChainStateFlushed(const CBlockLocator &);
void Broadcast(int64_t nBestBlockTime, CConnman* connman);
void BlockChecked(const CBlock&, const CValidationState&); void BlockChecked(const CBlock&, const CValidationState&);
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);
}; };

View File

@ -584,7 +584,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
if (fCreate && !Exists(std::string("version"))) { if (fCreate && !Exists(std::string("version"))) {
bool fTmp = fReadOnly; bool fTmp = fReadOnly;
fReadOnly = false; fReadOnly = false;
WriteVersion(CLIENT_VERSION); Write(std::string("version"), CLIENT_VERSION);
fReadOnly = fTmp; fReadOnly = fTmp;
} }
} }

View File

@ -406,17 +406,6 @@ public:
return (ret == 0); 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); bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr);
}; };

View File

@ -22,7 +22,7 @@ CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinC
{ {
CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes); CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes);
// Always obey the maximum // 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) { if (fee_needed > max_tx_fee) {
fee_needed = max_tx_fee; fee_needed = max_tx_fee;
if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE; if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE;

View File

@ -71,6 +71,8 @@ void WalletInit::AddWalletOptions() const
CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET_FEE); 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)", 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); 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)", 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); 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)", 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)) 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.")); 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 (gArgs.IsArgSet("-walletbackupsdir")) {
if (!fs::is_directory(gArgs.GetArg("-walletbackupsdir", ""))) { if (!fs::is_directory(gArgs.GetArg("-walletbackupsdir", ""))) {
InitWarning(strprintf(_("Warning: incorrect parameter %s, path must exist! Using default path."), "-walletbackupsdir")); InitWarning(strprintf(_("Warning: incorrect parameter %s, path must exist! Using default path."), "-walletbackupsdir"));

View File

@ -83,8 +83,9 @@ void StartWallets(CScheduler& scheduler)
pwallet->postInitProcess(); pwallet->postInitProcess();
} }
// Run a thread to flush wallet periodically // Schedule periodic wallet flushes and tx rebroadcasts
scheduler.scheduleEvery(MaybeCompactWalletDB, 500); scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
scheduler.scheduleEvery(MaybeResendWalletTxs, 1000);
if (!fMasternodeMode && CCoinJoinClientOptions::IsEnabled()) { if (!fMasternodeMode && CCoinJoinClientOptions::IsEnabled()) {
scheduler.scheduleEvery(std::bind(&DoCoinJoinMaintenance, std::ref(*g_connman)), 1 * 1000); scheduler.scheduleEvery(std::bind(&DoCoinJoinMaintenance, std::ref(*g_connman)), 1 * 1000);

View File

@ -3013,49 +3013,6 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
return NullUniValue; 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) static UniValue listunspent(const JSONRPCRequest& request)
{ {
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(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 { "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 #endif //ENABLE_MINER
{ "hidden", "instantsendtoaddress", &instantsendtoaddress, {} }, { "hidden", "instantsendtoaddress", &instantsendtoaddress, {} },
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options"} }, { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options"} },
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} },
{ "wallet", "abortrescan", &abortrescan, {} }, { "wallet", "abortrescan", &abortrescan, {} },

View File

@ -369,7 +369,10 @@ public:
int changePos = -1; int changePos = -1;
std::string error; std::string error;
CCoinControl dummy; 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; CValidationState state;
BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state)); BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state));
CMutableTransaction blocktx; CMutableTransaction blocktx;
@ -388,7 +391,6 @@ public:
} }
std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); 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; std::unique_ptr<CWallet> wallet;
}; };
@ -400,8 +402,9 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
// address. // address.
std::map<CTxDestination, std::vector<COutput>> list; std::map<CTxDestination, std::vector<COutput>> list;
{ {
LOCK2(cs_main, wallet->cs_wallet); auto locked_chain = m_chain->lock();
list = wallet->ListCoins(*m_locked_chain); LOCK(wallet->cs_wallet);
list = wallet->ListCoins(*locked_chain);
} }
BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
@ -416,8 +419,9 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
// pubkey. // pubkey.
AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */});
{ {
LOCK2(cs_main, wallet->cs_wallet); auto locked_chain = m_chain->lock();
list = wallet->ListCoins(*m_locked_chain); LOCK(wallet->cs_wallet);
list = wallet->ListCoins(*locked_chain);
} }
BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); 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. // 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; std::vector<COutput> available;
wallet->AvailableCoins(*m_locked_chain, available); wallet->AvailableCoins(*locked_chain, available);
BOOST_CHECK_EQUAL(available.size(), 2U); BOOST_CHECK_EQUAL(available.size(), 2U);
} }
for (const auto& group : list) { 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; std::vector<COutput> available;
wallet->AvailableCoins(*m_locked_chain, available); wallet->AvailableCoins(*locked_chain, available);
BOOST_CHECK_EQUAL(available.size(), 0U); BOOST_CHECK_EQUAL(available.size(), 0U);
} }
// Confirm ListCoins still returns same result as before, despite coins // Confirm ListCoins still returns same result as before, despite coins
// being locked. // being locked.
{ {
LOCK2(cs_main, wallet->cs_wallet); auto locked_chain = m_chain->lock();
list = wallet->ListCoins(*m_locked_chain); LOCK(wallet->cs_wallet);
list = wallet->ListCoins(*locked_chain);
} }
BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);

View File

@ -1473,6 +1473,10 @@ void CWallet::BlockDisconnected(const CBlock& block) {
fAnonymizableTallyCachedNonDenom = false; fAnonymizableTallyCachedNonDenom = false;
} }
void CWallet::UpdatedBlockTip()
{
m_best_block_time = GetTime();
}
void CWallet::BlockUntilSyncedToCurrentChain() { void CWallet::BlockUntilSyncedToCurrentChain() {
@ -2261,8 +2265,7 @@ void CWallet::ReacceptWalletTransactions()
std::map<int64_t, CWalletTx*> mapSorted; std::map<int64_t, CWalletTx*> mapSorted;
// Sort pending wallet transactions based on their initial wallet insertion order // 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; const uint256& wtxid = item.first;
CWalletTx& wtx = item.second; CWalletTx& wtx = item.second;
assert(wtx.GetHash() == wtxid); assert(wtx.GetHash() == wtxid);
@ -2277,27 +2280,31 @@ void CWallet::ReacceptWalletTransactions()
// Try to add wallet transactions to memory pool // Try to add wallet transactions to memory pool
for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
CWalletTx& wtx = *(item.second); CWalletTx& wtx = *(item.second);
CValidationState state; std::string unused_err_string;
wtx.AcceptToMemoryPool(*locked_chain, state); 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()); // Can't relay if wallet is not broadcasting
if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain(locked_chain) == 0) if (!pwallet->GetBroadcastTransactions()) return false;
{ // Don't relay abandoned transactions
CValidationState state; if (isAbandoned()) return false;
/* GetDepthInMainChain already catches known conflicts. */ // Submit transaction to mempool for relay
if (InMempool() || AcceptToMemoryPool(locked_chain, state)) { pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString());
pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); // We must set fInMempool here - while it will be re-set to true by the
if (pwallet->chain().p2pEnabled()) { // entered-mempool callback, if we did not there would be a race where a
pwallet->chain().relayTransaction(GetHash()); // user could call sendmoney in a loop and hit spurious out of funds errors
return true; // 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
return false; // 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 std::set<uint256> CWalletTx::GetConflicts() const
@ -2593,57 +2600,62 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
return CTransaction(tx1) == CTransaction(tx2); 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 // Do this infrequently and randomly to avoid giving away
// that these are our transactions. // that these are our transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) if (GetTime() < nNextResend || !fBroadcastTransactions) return;
return;
bool fFirst = (nNextResend == 0); bool fFirst = (nNextResend == 0);
nNextResend = GetTime() + GetRand(30 * 60); nNextResend = GetTime() + GetRand(30 * 60);
if (fFirst) if (fFirst) return;
return;
// Only do it if there's been a new block since last time // Only do it if there's been a new block since last time
if (nBestBlockTime < nLastResend) if (m_best_block_time < nLastResend) return;
return;
nLastResend = GetTime(); nLastResend = GetTime();
// Rebroadcast unconfirmed txes older than 5 minutes before the last int submitted_tx_count = 0;
// block was found:
std::vector<uint256> relayed = ResendWalletTransactionsBefore(locked_chain, nBestBlockTime-5*60); { // locked_chain, mempool.cs and cs_wallet scope
if (!relayed.empty()) auto locked_chain = chain().lock();
WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); 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 /** @} */ // end of mapWallet
void MaybeResendWalletTxs()
{
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
pwallet->ResendWalletTransactions();
}
}
/** @defgroup Actions /** @defgroup Actions
@ -3053,7 +3065,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
utxo_pool.push_back(group); utxo_pool.push_back(group);
} }
bnb_used = false; 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) if (fBroadcastTransactions)
{ {
// Broadcast std::string err_string;
if (!wtx.AcceptToMemoryPool(*locked_chain, state)) { if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) {
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); 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. // 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")); 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()); walletInstance->ChainStateFlushed(locked_chain->getTipLocator());
// Try to create wallet backup right after new wallet was created // 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; 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_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); 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; fInternal = fInternalIn;
} }
CWalletKey::CWalletKey(int64_t nExpires)
{
nTimeCreated = (nExpires ? GetTime() : 0);
nTimeExpires = nExpires;
}
void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock)
{ {
// Update the tx's hashBlock // Update the tx's hashBlock
@ -5508,18 +5535,6 @@ bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const
return GetBlocksToMaturity(locked_chain) > 0; 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> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const {
std::vector<OutputGroup> groups; std::vector<OutputGroup> groups;
std::map<CTxDestination, OutputGroup> gmap; std::map<CTxDestination, OutputGroup> gmap;

View File

@ -74,6 +74,12 @@ static const bool DEFAULT_AVOIDPARTIALSPENDS = false;
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6;
static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_WALLETBROADCAST = true;
static const bool DEFAULT_DISABLE_WALLET = false; 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 //! if set, all keys will be derived by using BIP39/BIP44
static const bool DEFAULT_USE_HD_WALLET = false; static const bool DEFAULT_USE_HD_WALLET = false;
@ -520,11 +526,8 @@ public:
int64_t GetTxTime() const; int64_t GetTxTime() const;
// RelayWalletTransaction may only be called if fBroadcastTransactions! // Pass this transaction to node for mempool insertion and relay to peers if flag set to true
bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain); bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, 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);
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation // 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 struct CoinSelectionParams
{ {
bool use_bnb = true; bool use_bnb = true;
@ -657,6 +638,8 @@ private:
int64_t nNextResend = 0; int64_t nNextResend = 0;
int64_t nLastResend = 0; int64_t nLastResend = 0;
bool fBroadcastTransactions = false; 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 bool fAnonymizableTallyCached = false;
mutable std::vector<CompactTallyItem> vecAnonymizableTallyCached; mutable std::vector<CompactTallyItem> vecAnonymizableTallyCached;
@ -983,6 +966,7 @@ public:
void TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) override; void TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) override;
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const CBlock& block) override; void BlockDisconnected(const CBlock& block) override;
void UpdatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
struct ScanResult { struct ScanResult {
@ -1003,9 +987,7 @@ public:
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate); ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override; void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override;
void ReacceptWalletTransactions(); void ReacceptWalletTransactions();
void ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) override; void ResendWalletTransactions();
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!
std::vector<uint256> ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime);
struct Balance { struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) 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_fallback_fee{DEFAULT_FALLBACK_FEE};
CFeeRate m_discard_rate{DEFAULT_DISCARD_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(); bool NewKeyPool();
size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -1284,6 +1268,12 @@ public:
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; 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. */ /** A key allocated from the key pool. */
class CReserveKey final : public CReserveScript class CReserveKey final : public CReserveScript
{ {

View File

@ -22,50 +22,80 @@
#include <atomic> #include <atomic>
#include <string> #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 // WalletBatch
// //
bool WalletBatch::WriteName(const std::string& strAddress, const std::string& strName) 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) bool WalletBatch::EraseName(const std::string& strAddress)
{ {
// This should only be used for sending addresses, never for receiving addresses, // 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. // 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) 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) 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) 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) 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) 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) 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; 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(), vchPubKey.begin(), vchPubKey.end());
vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.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, bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,
const std::vector<unsigned char>& vchCryptedSecret, const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta) const CKeyMetadata &keyMeta)
{ {
if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) { if (!WriteIC(std::make_pair(DBKeys::KEYMETA, vchPubKey), keyMeta)) {
return false; 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; return false;
} }
EraseIC(std::make_pair(std::string("key"), vchPubKey)); EraseIC(std::make_pair(DBKeys::KEY, vchPubKey));
EraseIC(std::make_pair(std::string("wkey"), vchPubKey));
return true; return true;
} }
bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) 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) 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) 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 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) 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 false;
} }
return EraseIC(std::make_pair(std::string("watchs"), dest)); return EraseIC(std::make_pair(DBKeys::WATCHS, dest));
} }
bool WalletBatch::WriteBestBlock(const CBlockLocator& locator) bool WalletBatch::WriteBestBlock(const CBlockLocator& locator)
{ {
WriteIC(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan WriteIC(DBKeys::BESTBLOCK, CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
return WriteIC(std::string("bestblock_nomerkle"), locator); return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, locator);
} }
bool WalletBatch::ReadBestBlock(CBlockLocator& locator) bool WalletBatch::ReadBestBlock(CBlockLocator& locator)
{ {
if (m_batch.Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true; if (m_batch.Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true;
return m_batch.Read(std::string("bestblock_nomerkle"), locator); return m_batch.Read(DBKeys::BESTBLOCK_NOMERKLE, locator);
} }
bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext) 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) 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) 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) 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) bool WalletBatch::WriteMinVersion(int nVersion)
{ {
return WriteIC(std::string("minversion"), nVersion); return WriteIC(DBKeys::MINVERSION, nVersion);
} }
bool WalletBatch::ReadCoinJoinSalt(uint256& salt, bool fLegacy) bool WalletBatch::ReadCoinJoinSalt(uint256& salt, bool fLegacy)
{ {
// TODO: Remove legacy checks after few major releases // 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) 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) 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}; unsigned int m_unknown_records{0};
bool fIsEncrypted{false}; bool fIsEncrypted{false};
bool fAnyUnordered{false}; bool fAnyUnordered{false};
int nFileVersion{0};
std::vector<uint256> vWalletUpgrade; std::vector<uint256> vWalletUpgrade;
CWalletScanState() { CWalletScanState() {
@ -200,20 +228,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
// Taking advantage of the fact that pair serialization // Taking advantage of the fact that pair serialization
// is just the two items serialized one after the other // is just the two items serialized one after the other
ssKey >> strType; ssKey >> strType;
if (strType == "name") if (strType == DBKeys::NAME) {
{
std::string strAddress; std::string strAddress;
ssKey >> strAddress; ssKey >> strAddress;
ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].name;
} } else if (strType == DBKeys::PURPOSE) {
else if (strType == "purpose")
{
std::string strAddress; std::string strAddress;
ssKey >> strAddress; ssKey >> strAddress;
ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose; ssValue >> pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose;
} } else if (strType == DBKeys::TX) {
else if (strType == "tx")
{
uint256 hash; uint256 hash;
ssKey >> hash; ssKey >> hash;
CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
@ -247,9 +270,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
wss.fAnyUnordered = true; wss.fAnyUnordered = true;
pwallet->LoadToWallet(wtx); pwallet->LoadToWallet(wtx);
} } else if (strType == DBKeys::WATCHS) {
else if (strType == "watchs")
{
wss.nWatchKeys++; wss.nWatchKeys++;
CScript script; CScript script;
ssKey >> script; ssKey >> script;
@ -257,9 +278,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssValue >> fYes; ssValue >> fYes;
if (fYes == '1') if (fYes == '1')
pwallet->LoadWatchOnly(script); pwallet->LoadWatchOnly(script);
} } else if (strType == DBKeys::KEY) {
else if (strType == "key" || strType == "wkey")
{
CPubKey vchPubKey; CPubKey vchPubKey;
ssKey >> vchPubKey; ssKey >> vchPubKey;
if (!vchPubKey.IsValid()) if (!vchPubKey.IsValid())
@ -271,20 +290,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
CPrivKey pkey; CPrivKey pkey;
uint256 hash; uint256 hash;
if (strType == "key")
{
wss.nKeys++; wss.nKeys++;
ssValue >> pkey; ssValue >> pkey;
} else {
CWalletKey wkey;
ssValue >> wkey;
pkey = wkey.vchPrivKey;
}
// 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 // ... 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. // 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. // remaining backwards-compatible.
try try
{ {
@ -321,9 +333,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: LoadKey failed"; strErr = "Error reading wallet database: LoadKey failed";
return false; return false;
} }
} } else if (strType == DBKeys::MASTER_KEY) {
else if (strType == "mkey")
{
unsigned int nID; unsigned int nID;
ssKey >> nID; ssKey >> nID;
CMasterKey kMasterKey; CMasterKey kMasterKey;
@ -336,9 +346,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
pwallet->mapMasterKeys[nID] = kMasterKey; pwallet->mapMasterKeys[nID] = kMasterKey;
if (pwallet->nMasterKeyMaxID < nID) if (pwallet->nMasterKeyMaxID < nID)
pwallet->nMasterKeyMaxID = nID; pwallet->nMasterKeyMaxID = nID;
} } else if (strType == DBKeys::CRYPTED_KEY) {
else if (strType == "ckey")
{
CPubKey vchPubKey; CPubKey vchPubKey;
ssKey >> vchPubKey; ssKey >> vchPubKey;
if (!vchPubKey.IsValid()) if (!vchPubKey.IsValid())
@ -356,27 +364,21 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false; return false;
} }
wss.fIsEncrypted = true; wss.fIsEncrypted = true;
} } else if (strType == DBKeys::KEYMETA) {
else if (strType == "keymeta")
{
CPubKey vchPubKey; CPubKey vchPubKey;
ssKey >> vchPubKey; ssKey >> vchPubKey;
CKeyMetadata keyMeta; CKeyMetadata keyMeta;
ssValue >> keyMeta; ssValue >> keyMeta;
wss.nKeyMeta++; wss.nKeyMeta++;
pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta); pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
} } else if (strType == DBKeys::WATCHMETA) {
else if (strType == "watchmeta")
{
CScript script; CScript script;
ssKey >> script; ssKey >> script;
CKeyMetadata keyMeta; CKeyMetadata keyMeta;
ssValue >> keyMeta; ssValue >> keyMeta;
wss.nKeyMeta++; wss.nKeyMeta++;
pwallet->LoadScriptMetadata(CScriptID(script), keyMeta); pwallet->LoadScriptMetadata(CScriptID(script), keyMeta);
} } else if (strType == DBKeys::DEFAULTKEY) {
else if (strType == "defaultkey")
{
// We don't want or need the default key, but if there is one set, // 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 // we want to make sure that it is valid so that we can detect corruption
CPubKey vchPubKey; CPubKey vchPubKey;
@ -385,23 +387,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: Default Key corrupt"; strErr = "Error reading wallet database: Default Key corrupt";
return false; return false;
} }
} } else if (strType == DBKeys::POOL) {
else if (strType == "pool")
{
int64_t nIndex; int64_t nIndex;
ssKey >> nIndex; ssKey >> nIndex;
CKeyPool keypool; CKeyPool keypool;
ssValue >> keypool; ssValue >> keypool;
pwallet->LoadKeyPool(nIndex, keypool); pwallet->LoadKeyPool(nIndex, keypool);
} } else if (strType == DBKeys::CSCRIPT) {
else if (strType == "version")
{
ssValue >> wss.nFileVersion;
if (wss.nFileVersion == 10300)
wss.nFileVersion = 300;
}
else if (strType == "cscript")
{
uint160 hash; uint160 hash;
ssKey >> hash; ssKey >> hash;
CScript script; CScript script;
@ -411,21 +403,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: LoadCScript failed"; strErr = "Error reading wallet database: LoadCScript failed";
return false; return false;
} }
} } else if (strType == DBKeys::ORDERPOSNEXT) {
else if (strType == "orderposnext")
{
ssValue >> pwallet->nOrderPosNext; ssValue >> pwallet->nOrderPosNext;
} } else if (strType == DBKeys::DESTDATA) {
else if (strType == "destdata")
{
std::string strAddress, strKey, strValue; std::string strAddress, strKey, strValue;
ssKey >> strAddress; ssKey >> strAddress;
ssKey >> strKey; ssKey >> strKey;
ssValue >> strValue; ssValue >> strValue;
pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue); pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue);
} } else if (strType == DBKeys::HDCHAIN) {
else if (strType == "hdchain")
{
CHDChain chain; CHDChain chain;
ssValue >> chain; ssValue >> chain;
if (!pwallet->SetHDChainSingle(chain, true)) if (!pwallet->SetHDChainSingle(chain, true))
@ -433,9 +419,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: SetHDChain failed"; strErr = "Error reading wallet database: SetHDChain failed";
return false; return false;
} }
} } else if (strType == DBKeys::CRYPTED_HDCHAIN) {
else if (strType == "chdchain")
{
CHDChain chain; CHDChain chain;
ssValue >> chain; ssValue >> chain;
if (!pwallet->SetCryptedHDChainSingle(chain, true)) if (!pwallet->SetCryptedHDChainSingle(chain, true))
@ -443,9 +427,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: SetHDCryptedChain failed"; strErr = "Error reading wallet database: SetHDCryptedChain failed";
return false; return false;
} }
} } else if (strType == DBKeys::HDPUBKEY) {
else if (strType == "hdpubkey")
{
wss.nHDPubKeys++; wss.nHDPubKeys++;
CPubKey vchPubKey; CPubKey vchPubKey;
ssKey >> vchPubKey; ssKey >> vchPubKey;
@ -463,7 +445,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: LoadHDPubKey failed"; strErr = "Error reading wallet database: LoadHDPubKey failed";
return false; return false;
} }
} else if (strType == "gobject") { } else if (strType == DBKeys::G_OBJECT) {
uint256 nObjectHash; uint256 nObjectHash;
CGovernanceObject obj; CGovernanceObject obj;
ssKey >> nObjectHash; ssKey >> nObjectHash;
@ -478,16 +460,18 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Invalid governance object: LoadGovernanceObject"; strErr = "Invalid governance object: LoadGovernanceObject";
return false; return false;
} }
} else if (strType == "flags") { } else if (strType == DBKeys::FLAGS) {
uint64_t flags; uint64_t flags;
ssValue >> flags; ssValue >> flags;
if (!pwallet->SetWalletFlags(flags, true)) { if (!pwallet->SetWalletFlags(flags, true)) {
strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found"; strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found";
return false; return false;
} }
} } else if (strType == DBKeys::OLD_KEY) {
else if (strType != "bestblock" && strType != "bestblock_nomerkle" && strErr = "Found unsupported 'wkey' record, try loading with version 0.17";
strType != "minversion" && strType != "acentry"){ return false;
} else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE &&
strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY && strType != DBKeys::VERSION) {
wss.m_unknown_records++; wss.m_unknown_records++;
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -506,9 +490,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
bool WalletBatch::IsKeyType(const std::string& strType) bool WalletBatch::IsKeyType(const std::string& strType)
{ {
return (strType== "key" || strType == "wkey" || return (strType == DBKeys::KEY ||
strType == "mkey" || strType == "ckey" || strType == DBKeys::MASTER_KEY || strType == DBKeys::CRYPTED_KEY ||
strType == "hdchain" || strType == "chdchain"); strType == DBKeys::HDCHAIN || strType == DBKeys::CRYPTED_HDCHAIN);
} }
DBErrors WalletBatch::LoadWallet(CWallet* pwallet) DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
@ -522,8 +506,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
try { try {
int nMinVersion = 0; int nMinVersion = 0;
if (m_batch.Read((std::string)"minversion", nMinVersion)) if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) {
{
if (nMinVersion > FEATURE_LATEST) if (nMinVersion > FEATURE_LATEST)
return DBErrors::TOO_NEW; return DBErrors::TOO_NEW;
pwallet->LoadMinVersion(nMinVersion); pwallet->LoadMinVersion(nMinVersion);
@ -557,15 +540,15 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
{ {
// losing keys is considered a catastrophic error, anything else // losing keys is considered a catastrophic error, anything else
// we assume the user can live with: // we assume the user can live with:
if (IsKeyType(strType) || strType == "defaultkey") { if (IsKeyType(strType) || strType == DBKeys::DEFAULTKEY) {
result = DBErrors::CORRUPT; result = DBErrors::CORRUPT;
} else if(strType == "flags") { } else if (strType == DBKeys::FLAGS) {
// reading the wallet flags can only fail if unknown flags are present // reading the wallet flags can only fail if unknown flags are present
result = DBErrors::TOO_NEW; result = DBErrors::TOO_NEW;
} else { } else {
// Leave other errors alone, if we try to fix them we might make things worse. // 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. 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: // Rescan if there is a bad transaction record:
gArgs.SoftSetBoolArg("-rescan", true); gArgs.SoftSetBoolArg("-rescan", true);
} }
@ -590,7 +573,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
if (result != DBErrors::LOAD_OK) if (result != DBErrors::LOAD_OK)
return result; 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", 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, wss.nKeys, wss.nCKeys, wss.nKeys + wss.nCKeys,
@ -604,11 +592,11 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
WriteTx(pwallet->mapWallet.at(hash)); WriteTx(pwallet->mapWallet.at(hash));
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: // 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; return DBErrors::NEED_REWRITE;
if (wss.nFileVersion < CLIENT_VERSION) // Update if (last_client < CLIENT_VERSION) // Update
WriteVersion(CLIENT_VERSION); m_batch.Write(DBKeys::VERSION, CLIENT_VERSION);
if (wss.fAnyUnordered) if (wss.fAnyUnordered)
result = pwallet->ReorderTransactions(); result = pwallet->ReorderTransactions();
@ -622,8 +610,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW
try { try {
int nMinVersion = 0; int nMinVersion = 0;
if (m_batch.Read((std::string)"minversion", nMinVersion)) if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) {
{
if (nMinVersion > FEATURE_LATEST) if (nMinVersion > FEATURE_LATEST)
return DBErrors::TOO_NEW; return DBErrors::TOO_NEW;
} }
@ -652,7 +639,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CW
std::string strType; std::string strType;
ssKey >> strType; ssKey >> strType;
if (strType == "tx") { if (strType == DBKeys::TX) {
uint256 hash; uint256 hash;
ssKey >> hash; ssKey >> hash;
@ -783,8 +770,10 @@ bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, C
fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue, fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue,
dummyWss, strType, strErr); dummyWss, strType, strErr);
} }
if (!IsKeyType(strType) && strType != "hdpubkey") if (!IsKeyType(strType) && strType != DBKeys::HDCHAIN) {
return false; return false;
}
if (!fReadOK) if (!fReadOK)
{ {
LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, strErr); 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) 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) 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) bool WalletBatch::WriteHDChain(const CHDChain& chain)
{ {
return WriteIC(std::string("hdchain"), chain); return WriteIC(DBKeys::HDCHAIN, chain);
} }
bool WalletBatch::WriteCryptedHDChain(const CHDChain& chain) bool WalletBatch::WriteCryptedHDChain(const CHDChain& chain)
{ {
if (!WriteIC(std::string("chdchain"), chain)) if (!WriteIC(DBKeys::CRYPTED_HDCHAIN, chain))
return false; return false;
EraseIC(std::string("hdchain")); EraseIC(DBKeys::HDCHAIN);
return true; return true;
} }
bool WalletBatch::WriteHDPubKey(const CHDPubKey& hdPubKey, const CKeyMetadata& keyMeta) 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 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) bool WalletBatch::WriteWalletFlags(const uint64_t flags)
{ {
return WriteIC(std::string("flags"), flags); return WriteIC(DBKeys::FLAGS, flags);
} }
bool WalletBatch::TxnBegin() bool WalletBatch::TxnBegin()
@ -856,13 +845,3 @@ bool WalletBatch::TxnAbort()
{ {
return m_batch.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);
}

View File

@ -55,6 +55,36 @@ enum class DBErrors
NEED_REWRITE 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 class CKeyMetadata
{ {
public: public:
@ -192,10 +222,6 @@ public:
bool TxnCommit(); bool TxnCommit();
//! Abort current transaction //! Abort current transaction
bool TxnAbort(); bool TxnAbort();
//! Read wallet version
bool ReadVersion(int& nVersion);
//! Write wallet version
bool WriteVersion(int nVersion);
private: private:
BerkeleyBatch m_batch; BerkeleyBatch m_batch;
WalletDatabase& m_database; WalletDatabase& m_database;

View File

@ -140,7 +140,7 @@ class AddressIndexTest(BitcoinTestFramework):
tx.rehash() tx.rehash()
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex()) 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.nodes[0].generate(1)
self.sync_all() self.sync_all()
@ -170,7 +170,7 @@ class AddressIndexTest(BitcoinTestFramework):
tx.vout = [CTxOut(amount, scriptPubKey2)] tx.vout = [CTxOut(amount, scriptPubKey2)]
tx.rehash() tx.rehash()
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex()) 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.nodes[0].generate(1)
self.sync_all() self.sync_all()
balance1 = self.nodes[1].getaddressbalance(address2) balance1 = self.nodes[1].getaddressbalance(address2)
@ -184,7 +184,7 @@ class AddressIndexTest(BitcoinTestFramework):
tx.rehash() tx.rehash()
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex()) 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.nodes[0].generate(1)
self.sync_all() self.sync_all()
@ -265,7 +265,7 @@ class AddressIndexTest(BitcoinTestFramework):
tx.vout = [CTxOut(amount, scriptPubKey3)] tx.vout = [CTxOut(amount, scriptPubKey3)]
tx.rehash() tx.rehash()
signed_tx = self.nodes[2].signrawtransactionwithwallet(tx.serialize().hex()) 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) self.bump_mocktime(2)
tx2 = CTransaction() tx2 = CTransaction()
@ -279,7 +279,7 @@ class AddressIndexTest(BitcoinTestFramework):
] ]
tx2.rehash() tx2.rehash()
signed_tx2 = self.nodes[2].signrawtransactionwithwallet(tx2.serialize().hex()) 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) self.bump_mocktime(2)
mempool = self.nodes[2].getaddressmempool({"addresses": [address3]}) mempool = self.nodes[2].getaddressmempool({"addresses": [address3]})
@ -306,7 +306,7 @@ class AddressIndexTest(BitcoinTestFramework):
tx.rehash() tx.rehash()
self.nodes[2].importprivkey(privKey3) self.nodes[2].importprivkey(privKey3)
signed_tx3 = self.nodes[2].signrawtransactionwithwallet(tx.serialize().hex()) 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) self.bump_mocktime(2)
mempool3 = self.nodes[2].getaddressmempool({"addresses": [address3]}) mempool3 = self.nodes[2].getaddressmempool({"addresses": [address3]})
@ -338,7 +338,7 @@ class AddressIndexTest(BitcoinTestFramework):
tx.rehash() tx.rehash()
self.nodes[0].importprivkey(privkey1) self.nodes[0].importprivkey(privkey1)
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex()) 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() self.sync_all()
mempool_deltas = self.nodes[2].getaddressmempool({"addresses": [address1]}) mempool_deltas = self.nodes[2].getaddressmempool({"addresses": [address1]})

View File

@ -129,7 +129,7 @@ class BIP65Test(BitcoinTestFramework):
# First we show that this tx is valid except for CLTV by getting it # First we show that this tx is valid except for CLTV by getting it
# rejected from the mempool for exactly that reason. # 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. # Now we verify that a block with this transaction is also invalid.
block.vtx.append(spendtx) block.vtx.append(spendtx)

View File

@ -118,7 +118,7 @@ class BIP66Test(BitcoinTestFramework):
# First we show that this tx is valid except for DERSIG by getting it # First we show that this tx is valid except for DERSIG by getting it
# rejected from the mempool for exactly that reason. # 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. # Now we verify that a block with this transaction is also invalid.
block.vtx.append(spendtx) block.vtx.append(spendtx)

View File

@ -63,7 +63,7 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
# the ScriptSig that will satisfy the ScriptPubKey. # the ScriptSig that will satisfy the ScriptPubKey.
for inp in tx.vin: for inp in tx.vin:
inp.scriptSig = SCRIPT_SIG[inp.prevout.n] 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": 0, "amount": total_in - amount - fee})
unconflist.append({"txid": txid, "vout": 1, "amount": amount}) unconflist.append({"txid": txid, "vout": 1, "amount": amount})
@ -93,7 +93,7 @@ def split_inputs(from_node, txins, txouts, initial_split=False):
else: else:
tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]] tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]]
completetx = ToHex(tx) 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": 0, "amount": half_change})
txouts.append({"txid": txid, "vout": 1, "amount": rem_change}) txouts.append({"txid": txid, "vout": 1, "amount": rem_change})

View File

@ -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]") 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)] 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)) 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.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") 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) test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, 47)
trueDummy(test2tx) 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.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) 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) test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, 46)
test6txs=[CTransaction(test4tx)] test6txs=[CTransaction(test4tx)]
trueDummy(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.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]") self.log.info("Test 6: NULLDUMMY compliant transactions should be accepted to mempool and in block after activation [432]")
for i in test6txs: 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) self.block_submit(self.nodes[0], test6txs, True)

View File

@ -76,7 +76,7 @@ class SpentIndexTest(BitcoinTestFramework):
tx.rehash() tx.rehash()
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex()) 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.nodes[0].generate(1)
self.sync_all() self.sync_all()
@ -111,7 +111,7 @@ class SpentIndexTest(BitcoinTestFramework):
tx2.rehash() tx2.rehash()
self.nodes[0].importprivkey(privkey) self.nodes[0].importprivkey(privkey)
signed_tx2 = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex()) 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 # Check the mempool index
self.sync_all() self.sync_all()

View File

@ -59,7 +59,7 @@ class TxIndexTest(BitcoinTestFramework):
tx.rehash() tx.rehash()
signed_tx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex()) 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.nodes[0].generate(1)
self.sync_all() self.sync_all()

View File

@ -70,7 +70,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], inputs=[{'txid': coin['txid'], 'vout': coin['vout']}],
outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}],
))['hex'] ))['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) node.generate(1)
self.mempool_size = 0 self.mempool_size = 0
self.check_mempool_result( self.check_mempool_result(
@ -103,9 +103,9 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.check_mempool_result( self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': True}], result_expected=[{'txid': tx.rehash(), 'allowed': True}],
rawtxs=[tx.serialize().hex()], 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.mempool_size += 1
self.log.info('A transaction in the mempool') 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') self.log.info('A transaction that conflicts with an unconfirmed tx')
# Send the transaction that replaces the mempool transaction and opts out of replaceability # 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 # take original raw_tx_0
tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0)))
tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee
@ -139,7 +139,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
self.check_mempool_result( self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict'}], result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict'}],
rawtxs=[tx.serialize().hex()], rawtxs=[tx.serialize().hex()],
allowhighfees=True, maxfeerate=0,
) )
self.log.info('A transaction with missing inputs, that never existed') 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.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 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'] 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 # 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( raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[ inputs=[
@ -164,7 +164,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
], ],
outputs=[{node.getnewaddress(): 0.1}] outputs=[{node.getnewaddress(): 0.1}]
))['hex'] ))['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) node.generate(1)
self.mempool_size = 0 self.mempool_size = 0
# Now see if we can add the coins back to the utxo set by sending the exact txs again # 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( self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final'}], result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final'}],
rawtxs=[tx.serialize().hex()], rawtxs=[tx.serialize().hex()],
allowhighfees=True, maxfeerate=0,
) )

View File

@ -31,13 +31,13 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
b = [ self.nodes[0].getblockhash(n) for n in range(1, 4) ] 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 ] 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_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 = []
blocks.extend(self.nodes[0].generate(1)) 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_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)) blocks.extend(self.nodes[0].generate(1))

View File

@ -177,5 +177,10 @@ class GetblockstatsTest(BitcoinTestFramework):
assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats, assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats,
hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') 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__': if __name__ == '__main__':
GetblockstatsTest().main() GetblockstatsTest().main()

View File

@ -234,11 +234,7 @@ class RawTransactionsTest(BitcoinTestFramework):
txDetails = self.nodes[0].gettransaction(txId, True) txDetails = self.nodes[0].gettransaction(txId, True)
rawTx = self.nodes[0].decoderawtransaction(txDetails['hex']) rawTx = self.nodes[0].decoderawtransaction(txDetails['hex'])
vout = False vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000'))
for outpoint in rawTx['vout']:
if outpoint['value'] == Decimal('2.20000000'):
vout = outpoint
break
bal = self.nodes[0].getbalance() bal = self.nodes[0].getbalance()
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}] inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}]
@ -279,11 +275,7 @@ class RawTransactionsTest(BitcoinTestFramework):
txDetails = self.nodes[0].gettransaction(txId, True) txDetails = self.nodes[0].gettransaction(txId, True)
rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex']) rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex'])
vout = False vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000'))
for outpoint in rawTx2['vout']:
if outpoint['value'] == Decimal('2.20000000'):
vout = outpoint
break
bal = self.nodes[0].getbalance() bal = self.nodes[0].getbalance()
inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}] 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) decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0x7fff) 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__': if __name__ == '__main__':
RawTransactionsTest().main() RawTransactionsTest().main()

View File

@ -572,7 +572,7 @@ def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
rawtx = from_node.createrawtransaction(inputs, outputs) rawtx = from_node.createrawtransaction(inputs, outputs)
signresult = from_node.signrawtransactionwithwallet(rawtx) signresult = from_node.signrawtransactionwithwallet(rawtx)
txid = from_node.sendrawtransaction(signresult["hex"], True) txid = from_node.sendrawtransaction(signresult["hex"], 0)
return (txid, signresult["hex"], fee) return (txid, signresult["hex"], fee)
@ -646,7 +646,7 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
tx.vout.append(txout) tx.vout.append(txout)
newtx = tx.serialize().hex() newtx = tx.serialize().hex()
signresult = node.signrawtransactionwithwallet(newtx, None, "NONE") signresult = node.signrawtransactionwithwallet(newtx, None, "NONE")
txid = node.sendrawtransaction(signresult["hex"], True) txid = node.sendrawtransaction(signresult["hex"], 0)
txids.append(txid) txids.append(txid)
return txids return txids

View File

@ -178,6 +178,7 @@ BASE_SCRIPTS = [
'wallet_importprunedfunds.py', 'wallet_importprunedfunds.py',
'p2p_leak_tx.py', 'p2p_leak_tx.py',
'rpc_signmessage.py', 'rpc_signmessage.py',
'wallet_balance.py',
'feature_nulldummy.py', 'feature_nulldummy.py',
'mempool_accept.py', 'mempool_accept.py',
'mempool_expiry.py', 'mempool_expiry.py',

133
test/functional/wallet_balance.py Executable file
View 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()

View File

@ -65,15 +65,6 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getbalance(), 500) assert_equal(self.nodes[1].getbalance(), 500)
assert_equal(self.nodes[2].getbalance(), 0) 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 # Check that only first and second nodes have UTXOs
utxos = self.nodes[0].listunspent() utxos = self.nodes[0].listunspent()
assert_equal(len(utxos), 1) assert_equal(len(utxos), 1)
@ -174,8 +165,8 @@ class WalletTest(BitcoinTestFramework):
totalfee += fee_per_input totalfee += fee_per_input
# Have node 1 (miner) send the transactions # Have node 1 (miner) send the transactions
self.nodes[1].sendrawtransaction(txns_to_send[0]["hex"]) self.nodes[1].sendrawtransaction(txns_to_send[0]["hex"], 0)
self.nodes[1].sendrawtransaction(txns_to_send[1]["hex"]) self.nodes[1].sendrawtransaction(txns_to_send[1]["hex"], 0)
# Have node1 mine a block to confirm transactions: # Have node1 mine a block to confirm transactions:
self.nodes[1].generate(1) self.nodes[1].generate(1)
@ -222,27 +213,9 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal) 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'])) 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) self.start_node(3)
connect_nodes_bi(self.nodes, 0, 3) connect_nodes_bi(self.nodes, 0, 3)
self.sync_blocks() self.sync_all()
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)
# check if we can list zero value tx as available coins # check if we can list zero value tx as available coins
# 1. create raw_tx # 1. create raw_tx

View File

@ -2,31 +2,84 @@
# Copyright (c) 2017 The Bitcoin Core developers # Copyright (c) 2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # 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.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): class ResendWalletTransactionsTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 1
self.extra_args = [['--walletbroadcast=false']]
def skip_test_if_missing_module(self): def skip_test_if_missing_module(self):
self.skip_if_no_wallet() self.skip_if_no_wallet()
def run_test(self): def run_test(self):
# Should raise RPC_WALLET_ERROR (-4) if walletbroadcast is disabled. node = self.nodes[0] # alias
assert_raises_rpc_error(-4, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast", self.nodes[0].resendwallettransactions)
# Should return an empty array if there aren't unconfirmed wallet transactions. node.add_p2p_connection(P2PStoreTxInvs())
self.stop_node(0)
self.start_node(0, extra_args=[])
assert_equal(self.nodes[0].resendwallettransactions(), [])
# Should return an array with the unconfirmed wallet transaction. self.log.info("Create a new transaction and wait until it's broadcast")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16)
assert_equal(self.nodes[0].resendwallettransactions(), [txid])
# 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__': if __name__ == '__main__':
ResendWalletTransactionsTest().main() ResendWalletTransactionsTest().main()