aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/test/wallet_tests.cpp53
-rw-r--r--src/wallet/wallet.cpp154
-rw-r--r--src/wallet/wallet.h21
-rw-r--r--src/wallet/walletdb.cpp45
-rw-r--r--src/wallet/walletdb.h3
5 files changed, 160 insertions, 116 deletions
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 420761ad0d..42b67fac39 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -453,4 +453,57 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN);
}
+static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime)
+{
+ CMutableTransaction tx;
+ tx.nLockTime = lockTime;
+ SetMockTime(mockTime);
+ CBlockIndex* block = nullptr;
+ if (blockTime > 0) {
+ auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex);
+ assert(inserted.second);
+ const uint256& hash = inserted.first->first;
+ block = inserted.first->second;
+ block->nTime = blockTime;
+ block->phashBlock = &hash;
+ }
+
+ CWalletTx wtx(&wallet, MakeTransactionRef(tx));
+ if (block) {
+ wtx.SetMerkleBranch(block, 0);
+ }
+ wallet.AddToWallet(wtx);
+ return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
+}
+
+// Simple test to verify assignment of CWalletTx::nSmartTime value. Could be
+// expanded to cover more corner cases of smart time logic.
+BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
+{
+ CWallet wallet;
+
+ // New transaction should use clock time if lower than block time.
+ BOOST_CHECK_EQUAL(AddTx(wallet, 1, 100, 120), 100);
+
+ // Test that updating existing transaction does not change smart time.
+ BOOST_CHECK_EQUAL(AddTx(wallet, 1, 200, 220), 100);
+
+ // New transaction should use clock time if there's no block time.
+ BOOST_CHECK_EQUAL(AddTx(wallet, 2, 300, 0), 300);
+
+ // New transaction should use block time if lower than clock time.
+ BOOST_CHECK_EQUAL(AddTx(wallet, 3, 420, 400), 400);
+
+ // New transaction should use latest entry time if higher than
+ // min(block time, clock time).
+ BOOST_CHECK_EQUAL(AddTx(wallet, 4, 500, 390), 400);
+
+ // If there are future entries, new transaction should use time of the
+ // newest entry that is no more than 300 seconds ahead of the clock time.
+ BOOST_CHECK_EQUAL(AddTx(wallet, 5, 50, 600), 300);
+
+ // Reset mock time for other tests.
+ SetMockTime(0);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 02af1bf10f..dc3e0e28d1 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -20,6 +20,7 @@
#include "primitives/transaction.h"
#include "script/script.h"
#include "script/sign.h"
+#include "scheduler.h"
#include "timedata.h"
#include "txmempool.h"
#include "util.h"
@@ -39,7 +40,6 @@ CWallet* pwalletMain = NULL;
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
-bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS;
bool fWalletRbf = DEFAULT_WALLET_RBF;
const char * DEFAULT_WALLET_DAT = "wallet.dat";
@@ -869,51 +869,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(&walletdb);
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
-
- wtx.nTimeSmart = wtx.nTimeReceived;
- if (!wtxIn.hashUnset())
- {
- if (mapBlockIndex.count(wtxIn.hashBlock))
- {
- int64_t latestNow = wtx.nTimeReceived;
- int64_t latestEntry = 0;
- {
- // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
- int64_t latestTolerated = latestNow + 300;
- const TxItems & txOrdered = wtxOrdered;
- for (TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
- {
- CWalletTx *const pwtx = (*it).second.first;
- if (pwtx == &wtx)
- continue;
- CAccountingEntry *const pacentry = (*it).second.second;
- int64_t nSmartTime;
- if (pwtx)
- {
- nSmartTime = pwtx->nTimeSmart;
- if (!nSmartTime)
- nSmartTime = pwtx->nTimeReceived;
- }
- else
- nSmartTime = pacentry->nTime;
- if (nSmartTime <= latestTolerated)
- {
- latestEntry = nSmartTime;
- if (nSmartTime > latestNow)
- latestNow = nSmartTime;
- break;
- }
- }
- }
-
- int64_t blocktime = mapBlockIndex[wtxIn.hashBlock]->GetBlockTime();
- wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
- }
- else
- LogPrintf("AddToWallet(): found %s in block %s not in index\n",
- wtxIn.GetHash().ToString(),
- wtxIn.hashBlock.ToString());
- }
+ wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(hash);
}
@@ -2422,7 +2378,6 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
CAmount nValueToSelect = nValue;
if (nSubtractFeeFromAmount == 0)
nValueToSelect += nFeeRet;
- double dPriority = 0;
// vouts to the payees
for (const auto& recipient : vecSend)
{
@@ -2463,19 +2418,6 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
strFailReason = _("Insufficient funds");
return false;
}
- for (const auto& pcoin : setCoins)
- {
- CAmount nCredit = pcoin.first->tx->vout[pcoin.second].nValue;
- //The coin age after the next block (depth+1) is used instead of the current,
- //reflecting an assumption the user would accept a bit more delay for
- //a chance at a free transaction.
- //But mempool inputs might still be in the mempool, so their age stays 0
- int age = pcoin.first->GetDepthInMainChain();
- assert(age >= 0);
- if (age != 0)
- age += 1;
- dPriority += (double)nCredit * age;
- }
const CAmount nChange = nValueIn - nValueToSelect;
if (nChange > 0)
@@ -2587,7 +2529,6 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
unsigned int nBytes = GetVirtualTransactionSize(txNew);
CTransaction txNewConst(txNew);
- dPriority = txNewConst.ComputePriority(dPriority, nBytes);
// Remove scriptSigs to eliminate the fee calculation dummy signatures
for (auto& vin : txNew.vin) {
@@ -2600,16 +2541,6 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (coinControl && coinControl->nConfirmTarget > 0)
currentConfirmationTarget = coinControl->nConfirmTarget;
- // Can we complete this as a free transaction?
- if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)
- {
- // Not enough fee: enough priority?
- double dPriorityNeeded = mempool.estimateSmartPriority(currentConfirmationTarget);
- // Require at least hard-coded AllowFree.
- if (dPriority >= dPriorityNeeded && AllowFree(dPriority))
- break;
- }
-
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, mempool);
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
@@ -2698,7 +2629,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
LockPoints lp;
- CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, 0, 0, false, 0, lp);
+ CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, false, 0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
@@ -3471,6 +3402,71 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c
mapKeyBirth[it->first] = it->second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off
}
+/**
+ * Compute smart timestamp for a transaction being added to the wallet.
+ *
+ * Logic:
+ * - If sending a transaction, assign its timestamp to the current time.
+ * - If receiving a transaction outside a block, assign its timestamp to the
+ * current time.
+ * - If receiving a block with a future timestamp, assign all its (not already
+ * known) transactions' timestamps to the current time.
+ * - If receiving a block with a past timestamp, before the most recent known
+ * transaction (that we care about), assign all its (not already known)
+ * transactions' timestamps to the same timestamp as that most-recent-known
+ * transaction.
+ * - If receiving a block with a past timestamp, but after the most recent known
+ * transaction, assign all its (not already known) transactions' timestamps to
+ * the block time.
+ *
+ * For more information see CWalletTx::nTimeSmart,
+ * https://bitcointalk.org/?topic=54527, or
+ * https://github.com/bitcoin/bitcoin/pull/1393.
+ */
+unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
+{
+ unsigned int nTimeSmart = wtx.nTimeReceived;
+ if (!wtx.hashUnset()) {
+ if (mapBlockIndex.count(wtx.hashBlock)) {
+ int64_t latestNow = wtx.nTimeReceived;
+ int64_t latestEntry = 0;
+
+ // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
+ int64_t latestTolerated = latestNow + 300;
+ const TxItems& txOrdered = wtxOrdered;
+ for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
+ CWalletTx* const pwtx = it->second.first;
+ if (pwtx == &wtx) {
+ continue;
+ }
+ CAccountingEntry* const pacentry = it->second.second;
+ int64_t nSmartTime;
+ if (pwtx) {
+ nSmartTime = pwtx->nTimeSmart;
+ if (!nSmartTime) {
+ nSmartTime = pwtx->nTimeReceived;
+ }
+ } else {
+ nSmartTime = pacentry->nTime;
+ }
+ if (nSmartTime <= latestTolerated) {
+ latestEntry = nSmartTime;
+ if (nSmartTime > latestNow) {
+ latestNow = nSmartTime;
+ }
+ break;
+ }
+ }
+
+ int64_t blocktime = mapBlockIndex[wtx.hashBlock]->GetBlockTime();
+ nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
+ } else {
+ LogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
+ }
+ }
+ return nTimeSmart;
+}
+
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
if (boost::get<CNoDestination>(&dest))
@@ -3526,8 +3522,6 @@ std::string CWallet::GetWalletHelpString(bool showDebug)
CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK())));
strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions on startup"));
strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet on startup"));
- if (showDebug)
- strUsage += HelpMessageOpt("-sendfreetransactions", strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), DEFAULT_SEND_FREE_TRANSACTIONS));
strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE));
strUsage += HelpMessageOpt("-txconfirmtarget=<n>", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET));
strUsage += HelpMessageOpt("-usehd", _("Use hierarchical deterministic key generation (HD) after BIP32. Only has effect during wallet creation/first start") + " " + strprintf(_("(default: %u)"), DEFAULT_USE_HD_WALLET));
@@ -3754,17 +3748,17 @@ bool CWallet::InitLoadWallet()
return true;
}
-std::atomic<bool> CWallet::fFlushThreadRunning(false);
+std::atomic<bool> CWallet::fFlushScheduled(false);
-void CWallet::postInitProcess(boost::thread_group& threadGroup)
+void CWallet::postInitProcess(CScheduler& scheduler)
{
// Add wallet transactions that aren't already in a block to mempool
// Do this here as mempool requires genesis block to be loaded
ReacceptWalletTransactions();
// Run a thread to flush wallet periodically
- if (!CWallet::fFlushThreadRunning.exchange(true)) {
- threadGroup.create_thread(ThreadFlushWalletDB);
+ if (!CWallet::fFlushScheduled.exchange(true)) {
+ scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
}
}
@@ -3848,12 +3842,8 @@ bool CWallet::ParameterInteraction()
}
nTxConfirmTarget = GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
bSpendZeroConfChange = GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
- fSendFreeTransactions = GetBoolArg("-sendfreetransactions", DEFAULT_SEND_FREE_TRANSACTIONS);
fWalletRbf = GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
- if (fSendFreeTransactions && GetArg("-limitfreerelay", DEFAULT_LIMITFREERELAY) <= 0)
- return InitError("Creation of free transactions with their relay disabled is not supported.");
-
return true;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 65ad3e0095..9f89c89bc2 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -29,7 +29,6 @@
#include <vector>
#include <boost/shared_ptr.hpp>
-#include <boost/thread.hpp>
extern CWallet* pwalletMain;
@@ -39,7 +38,6 @@ extern CWallet* pwalletMain;
extern CFeeRate payTxFee;
extern unsigned int nTxConfirmTarget;
extern bool bSpendZeroConfChange;
-extern bool fSendFreeTransactions;
extern bool fWalletRbf;
static const unsigned int DEFAULT_KEYPOOL_SIZE = 100;
@@ -57,16 +55,12 @@ static const CAmount MIN_CHANGE = CENT;
static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2;
//! Default for -spendzeroconfchange
static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
-//! Default for -sendfreetransactions
-static const bool DEFAULT_SEND_FREE_TRANSACTIONS = false;
//! Default for -walletrejectlongchains
static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false;
//! -txconfirmtarget default
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6;
//! -walletrbf default
static const bool DEFAULT_WALLET_RBF = false;
-//! Largest (in bytes) free transaction we're willing to create
-static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
static const bool DEFAULT_WALLETBROADCAST = true;
static const bool DEFAULT_DISABLE_WALLET = false;
//! if set, all keys will be derived by using BIP32
@@ -79,6 +73,7 @@ class CCoinControl;
class COutput;
class CReserveKey;
class CScript;
+class CScheduler;
class CTxMemPool;
class CWalletTx;
@@ -285,6 +280,15 @@ public:
std::vector<std::pair<std::string, std::string> > vOrderForm;
unsigned int fTimeReceivedIsTxTime;
unsigned int nTimeReceived; //!< time received by this node
+ /**
+ * Stable timestamp that never changes, and reflects the order a transaction
+ * was added to the wallet. Timestamp is based on the block time for a
+ * transaction added as part of a block, or else the time when the
+ * transaction was received if it wasn't part of a block, with the timestamp
+ * adjusted in both cases so timestamp order matches the order transactions
+ * were added to the wallet. More details can be found in
+ * CWallet::ComputeTimeSmart().
+ */
unsigned int nTimeSmart;
/**
* From me flag is set to 1 for transactions that were created by the wallet
@@ -593,7 +597,7 @@ private:
class CWallet : public CCryptoKeyStore, public CValidationInterface
{
private:
- static std::atomic<bool> fFlushThreadRunning;
+ static std::atomic<bool> fFlushScheduled;
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
@@ -800,6 +804,7 @@ public:
bool EncryptWallet(const SecureString& strWalletPassphrase);
void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const;
+ unsigned int ComputeTimeSmart(const CWalletTx& wtx) const;
/**
* Increment the next transaction order id
@@ -1001,7 +1006,7 @@ public:
* Wallet post-init setup
* Gives the wallet a chance to register repetitive tasks and complete post-init tasks
*/
- void postInitProcess(boost::thread_group& threadGroup);
+ void postInitProcess(CScheduler& scheduler);
/* Wallets parameter interaction */
static bool ParameterInteraction();
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index f894a365ac..5ba9f150a8 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -777,38 +777,33 @@ DBErrors CWalletDB::ZapWalletTx(vector<CWalletTx>& vWtx)
return DB_LOAD_OK;
}
-void ThreadFlushWalletDB()
+void MaybeCompactWalletDB()
{
- // Make this thread recognisable as the wallet flushing thread
- RenameThread("bitcoin-wallet");
-
- static bool fOneThread;
- if (fOneThread)
+ static std::atomic<bool> fOneThread;
+ if (fOneThread.exchange(true)) {
return;
- fOneThread = true;
- if (!GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET))
+ }
+ if (!GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
return;
+ }
- unsigned int nLastSeen = CWalletDB::GetUpdateCounter();
- unsigned int nLastFlushed = CWalletDB::GetUpdateCounter();
- int64_t nLastWalletUpdate = GetTime();
- while (true)
- {
- MilliSleep(500);
+ static unsigned int nLastSeen = CWalletDB::GetUpdateCounter();
+ static unsigned int nLastFlushed = CWalletDB::GetUpdateCounter();
+ static int64_t nLastWalletUpdate = GetTime();
- if (nLastSeen != CWalletDB::GetUpdateCounter())
- {
- nLastSeen = CWalletDB::GetUpdateCounter();
- nLastWalletUpdate = GetTime();
- }
+ if (nLastSeen != CWalletDB::GetUpdateCounter())
+ {
+ nLastSeen = CWalletDB::GetUpdateCounter();
+ nLastWalletUpdate = GetTime();
+ }
- if (nLastFlushed != CWalletDB::GetUpdateCounter() && GetTime() - nLastWalletUpdate >= 2)
- {
- const std::string& strFile = pwalletMain->strWalletFile;
- if (CDB::PeriodicFlush(strFile))
- nLastFlushed = CWalletDB::GetUpdateCounter();
- }
+ if (nLastFlushed != CWalletDB::GetUpdateCounter() && GetTime() - nLastWalletUpdate >= 2)
+ {
+ const std::string& strFile = pwalletMain->strWalletFile;
+ if (CDB::PeriodicFlush(strFile))
+ nLastFlushed = CWalletDB::GetUpdateCounter();
}
+ fOneThread = false;
}
//
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 0d32df1578..4d7dfb727e 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -193,6 +193,7 @@ private:
void operator=(const CWalletDB&);
};
-void ThreadFlushWalletDB();
+//! Compacts BDB state so that wallet.dat is self-contained (if there are changes)
+void MaybeCompactWalletDB();
#endif // BITCOIN_WALLET_WALLETDB_H