aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/fuzzing.md8
-rw-r--r--src/Makefile.qttest.include3
-rw-r--r--src/fs.h2
-rw-r--r--src/net.cpp2
-rw-r--r--src/netbase.cpp3
-rw-r--r--src/policy/policy.cpp8
-rw-r--r--src/qt/coincontroldialog.cpp4
-rw-r--r--src/qt/forms/sendcoinsdialog.ui13
-rw-r--r--src/qt/sendcoinsdialog.cpp24
-rw-r--r--src/qt/test/wallettests.cpp10
-rw-r--r--src/qt/walletmodel.cpp60
-rw-r--r--src/random.cpp33
-rw-r--r--src/random.h7
-rw-r--r--src/rpc/blockchain.cpp22
-rw-r--r--src/scheduler.cpp6
-rw-r--r--src/test/addrman_tests.cpp10
-rw-r--r--src/test/test_bitcoin_fuzzy.cpp23
-rw-r--r--src/validation.cpp6
-rw-r--r--src/validation.h3
-rw-r--r--src/versionbits.cpp36
-rw-r--r--src/versionbits.h10
-rw-r--r--src/wallet/coincontrol.h3
-rw-r--r--src/wallet/rpcdump.cpp9
-rw-r--r--src/wallet/test/wallet_tests.cpp163
-rw-r--r--src/wallet/wallet.cpp104
-rw-r--r--src/wallet/wallet.h18
-rwxr-xr-xtest/functional/bip9-softforks.py51
-rwxr-xr-xtest/functional/test_framework/test_framework.py13
-rwxr-xr-xtest/functional/test_runner.py29
29 files changed, 539 insertions, 144 deletions
diff --git a/doc/fuzzing.md b/doc/fuzzing.md
index bf3ad17861..5dedcb51c8 100644
--- a/doc/fuzzing.md
+++ b/doc/fuzzing.md
@@ -32,6 +32,13 @@ We disable ccache because we don't want to pollute the ccache with instrumented
objects, and similarly don't want to use non-instrumented cached objects linked
in.
+The fuzzing can be sped up significantly (~200x) by using `afl-clang-fast` and
+`afl-clang-fast++` in place of `afl-gcc` and `afl-g++` when compiling. When
+compiling using `afl-clang-fast`/`afl-clang-fast++` the resulting
+`test_bitcoin_fuzzy` binary will be instrumented in such a way that the AFL
+features "persistent mode" and "deferred forkserver" can be used. See
+https://github.com/mcarpenter/afl/tree/master/llvm_mode for details.
+
Preparing fuzzing
------------------
@@ -63,4 +70,3 @@ $AFLPATH/afl-fuzz -i ${AFLIN} -o ${AFLOUT} -m52 -- test/test_bitcoin_fuzzy
You may have to change a few kernel parameters to test optimally - `afl-fuzz`
will print an error and suggestion if so.
-
diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include
index 948e13a9e1..391b9ebdf6 100644
--- a/src/Makefile.qttest.include
+++ b/src/Makefile.qttest.include
@@ -46,7 +46,8 @@ qt_test_test_bitcoin_qt_SOURCES = \
if ENABLE_WALLET
qt_test_test_bitcoin_qt_SOURCES += \
qt/test/paymentservertests.cpp \
- qt/test/wallettests.cpp
+ qt/test/wallettests.cpp \
+ wallet/test/wallet_test_fixture.cpp
endif
nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP)
diff --git a/src/fs.h b/src/fs.h
index 585cbf9c38..abb4be254b 100644
--- a/src/fs.h
+++ b/src/fs.h
@@ -21,4 +21,4 @@ namespace fsbridge {
FILE *freopen(const fs::path& p, const char *mode, FILE *stream);
};
-#endif
+#endif // BITCOIN_FS_H
diff --git a/src/net.cpp b/src/net.cpp
index ded6f1099a..198d8f5fff 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -148,7 +148,7 @@ static std::vector<CAddress> convertSeed6(const std::vector<SeedSpec6> &vSeedsIn
// one by discovery.
CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices)
{
- CAddress ret(CService(CNetAddr(),GetListenPort()), NODE_NONE);
+ CAddress ret(CService(CNetAddr(),GetListenPort()), nLocalServices);
CService addr;
if (GetLocal(addr, paddrPeer))
{
diff --git a/src/netbase.cpp b/src/netbase.cpp
index 2584f571ee..32557dd179 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -668,13 +668,14 @@ std::string NetworkErrorString(int err)
std::string NetworkErrorString(int err)
{
char buf[256];
- const char *s = buf;
buf[0] = 0;
/* Too bad there are two incompatible implementations of the
* thread-safe strerror. */
+ const char *s;
#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */
s = strerror_r(err, buf, sizeof(buf));
#else /* POSIX variant always returns message in buffer */
+ s = buf;
if (strerror_r(err, buf, sizeof(buf)))
buf[0] = 0;
#endif
diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp
index 2b19a6714b..f4fffd6578 100644
--- a/src/policy/policy.cpp
+++ b/src/policy/policy.cpp
@@ -15,7 +15,7 @@
#include <boost/foreach.hpp>
-CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee)
+CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
{
// "Dust" is defined in terms of dustRelayFee,
// which has units satoshis-per-kilobyte.
@@ -44,12 +44,12 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee)
nSize += (32 + 4 + 1 + 107 + 4); // the 148 mentioned above
}
- return 3 * dustRelayFee.GetFee(nSize);
+ return 3 * dustRelayFeeIn.GetFee(nSize);
}
-bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee)
+bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
{
- return (txout.nValue < GetDustThreshold(txout, dustRelayFee));
+ return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn));
}
/**
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index 2a331d4fae..135cf6f701 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -513,8 +513,6 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
// Fee
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, ::mempool, ::feeEstimator);
- if (nPayFee > 0 && coinControl->nMinimumTotalFee > nPayFee)
- nPayFee = coinControl->nMinimumTotalFee;
if (nPayAmount > 0)
{
@@ -573,7 +571,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Bytes
l7->setText(fDust ? tr("yes") : tr("no")); // Dust
l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); // Change
- if (nPayFee > 0 && (coinControl->nMinimumTotalFee < nPayFee))
+ if (nPayFee > 0)
{
l3->setText(ASYMP_UTF8 + l3->text());
l4->setText(ASYMP_UTF8 + l4->text());
diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui
index 52256ca5c4..89f9c25d14 100644
--- a/src/qt/forms/sendcoinsdialog.ui
+++ b/src/qt/forms/sendcoinsdialog.ui
@@ -862,19 +862,6 @@
</widget>
</item>
<item>
- <widget class="QRadioButton" name="radioCustomAtLeast">
- <property name="toolTip">
- <string>If the custom fee is set to 1000 satoshis and the transaction is only 250 bytes, then &quot;per kilobyte&quot; only pays 250 satoshis in fee, while &quot;total at least&quot; pays 1000 satoshis. For transactions bigger than a kilobyte both pay by kilobyte.</string>
- </property>
- <property name="text">
- <string>total at least</string>
- </property>
- <attribute name="buttonGroup">
- <string notr="true">groupCustomFee</string>
- </attribute>
- </widget>
- </item>
- <item>
<widget class="BitcoinAmountField" name="customFee"/>
</item>
<item>
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 8dcb0fd016..272ab9486a 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -109,7 +109,6 @@ SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *p
ui->groupFee->setId(ui->radioCustomFee, 1);
ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
ui->groupCustomFee->setId(ui->radioCustomPerKilobyte, 0);
- ui->groupCustomFee->setId(ui->radioCustomAtLeast, 1);
ui->groupCustomFee->button((int)std::max(0, std::min(1, settings.value("nCustomFeeRadio").toInt())))->setChecked(true);
ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool());
@@ -606,7 +605,6 @@ void SendCoinsDialog::updateFeeSectionControls()
ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
ui->radioCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
- ui->radioCustomAtLeast ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked() && CoinControlDialog::coinControl->HasSelected());
ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
}
@@ -617,19 +615,12 @@ void SendCoinsDialog::updateGlobalFeeVariables()
int nConfirmTarget = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
payTxFee = CFeeRate(0);
- // set nMinimumTotalFee to 0 to not accidentally pay a custom fee
- CoinControlDialog::coinControl->nMinimumTotalFee = 0;
-
// show the estimated required time for confirmation
ui->confirmationTargetLabel->setText(GUIUtil::formatDurationStr(nConfirmTarget * Params().GetConsensus().nPowTargetSpacing) + " / " + tr("%n block(s)", "", nConfirmTarget));
}
else
{
payTxFee = CFeeRate(ui->customFee->value());
-
- // if user has selected to set a minimum absolute fee, pass the value to coincontrol
- // set nMinimumTotalFee to 0 in case of user has selected that the fee is per KB
- CoinControlDialog::coinControl->nMinimumTotalFee = ui->radioCustomAtLeast->isChecked() ? ui->customFee->value() : 0;
}
}
@@ -828,21 +819,6 @@ void SendCoinsDialog::coinControlUpdateLabels()
if (!model || !model->getOptionsModel())
return;
- if (model->getOptionsModel()->getCoinControlFeatures())
- {
- // enable minimum absolute fee UI controls
- ui->radioCustomAtLeast->setVisible(true);
-
- // only enable the feature if inputs are selected
- ui->radioCustomAtLeast->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked() &&CoinControlDialog::coinControl->HasSelected());
- }
- else
- {
- // in case coin control is disabled (=default), hide minimum absolute fee UI controls
- ui->radioCustomAtLeast->setVisible(false);
- return;
- }
-
// set pay amounts
CoinControlDialog::payAmounts.clear();
CoinControlDialog::fSubtractFeeFromAmount = false;
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index a0dce3d997..798d333d63 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -65,7 +65,6 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
}
return {};
}
-}
//! Simple qt wallet tests.
//
@@ -80,7 +79,7 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
// src/qt/test/test_bitcoin-qt -platform xcb # Linux
// src/qt/test/test_bitcoin-qt -platform windows # Windows
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
-void WalletTests::walletTests()
+void TestSendCoins()
{
// Set up wallet and chain with 101 blocks (1 mature block for spending).
TestChain100Setup test;
@@ -117,3 +116,10 @@ void WalletTests::walletTests()
bitdb.Flush(true);
bitdb.Reset();
}
+
+}
+
+void WalletTests::walletTests()
+{
+ TestSendCoins();
+}
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 37af8abb38..33b407ae57 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -68,14 +68,7 @@ CAmount WalletModel::getBalance(const CCoinControl *coinControl) const
{
if (coinControl)
{
- CAmount nBalance = 0;
- std::vector<COutput> vCoins;
- wallet->AvailableCoins(vCoins, true, coinControl);
- BOOST_FOREACH(const COutput& out, vCoins)
- if(out.fSpendable)
- nBalance += out.tx->tx->vout[out.i].nValue;
-
- return nBalance;
+ return wallet->GetAvailableBalance(coinControl);
}
return wallet->GetBalance();
@@ -600,38 +593,11 @@ bool WalletModel::isSpent(const COutPoint& outpoint) const
// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address)
void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const
{
- std::vector<COutput> vCoins;
- wallet->AvailableCoins(vCoins);
-
- LOCK2(cs_main, wallet->cs_wallet); // ListLockedCoins, mapWallet
- std::vector<COutPoint> vLockedCoins;
- wallet->ListLockedCoins(vLockedCoins);
-
- // add locked coins
- BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins)
- {
- if (!wallet->mapWallet.count(outpoint.hash)) continue;
- int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
- if (nDepth < 0) continue;
- COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true /* spendable */, true /* solvable */, true /* safe */);
- if (outpoint.n < out.tx->tx->vout.size() && wallet->IsMine(out.tx->tx->vout[outpoint.n]) == ISMINE_SPENDABLE)
- vCoins.push_back(out);
- }
-
- BOOST_FOREACH(const COutput& out, vCoins)
- {
- COutput cout = out;
-
- while (wallet->IsChange(cout.tx->tx->vout[cout.i]) && cout.tx->tx->vin.size() > 0 && wallet->IsMine(cout.tx->tx->vin[0]))
- {
- if (!wallet->mapWallet.count(cout.tx->tx->vin[0].prevout.hash)) break;
- cout = COutput(&wallet->mapWallet[cout.tx->tx->vin[0].prevout.hash], cout.tx->tx->vin[0].prevout.n, 0 /* depth */, true /* spendable */, true /* solvable */, true /* safe */);
+ for (auto& group : wallet->ListCoins()) {
+ auto& resultGroup = mapCoins[QString::fromStdString(CBitcoinAddress(group.first).ToString())];
+ for (auto& coin : group.second) {
+ resultGroup.emplace_back(std::move(coin));
}
-
- CTxDestination address;
- if(!out.fSpendable || !ExtractDestination(cout.tx->tx->vout[cout.i].scriptPubKey, address))
- continue;
- mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())].push_back(out);
}
}
@@ -661,11 +627,7 @@ void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests)
{
- LOCK(wallet->cs_wallet);
- BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
- BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item2, item.second.destdata)
- if (item2.first.size() > 2 && item2.first.substr(0,2) == "rr") // receive request
- vReceiveRequests.push_back(item2.second);
+ vReceiveRequests = wallet->GetDestValues("rr"); // receive request
}
bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest)
@@ -685,11 +647,7 @@ bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t
bool WalletModel::transactionCanBeAbandoned(uint256 hash) const
{
- LOCK2(cs_main, wallet->cs_wallet);
- const CWalletTx *wtx = wallet->GetWalletTx(hash);
- if (!wtx || wtx->isAbandoned() || wtx->GetDepthInMainChain() > 0 || wtx->InMempool())
- return false;
- return true;
+ return wallet->TransactionCanBeAbandoned(hash);
}
bool WalletModel::abandonTransaction(uint256 hash) const
@@ -702,9 +660,7 @@ bool WalletModel::transactionSignalsRBF(uint256 hash) const
{
LOCK2(cs_main, wallet->cs_wallet);
const CWalletTx *wtx = wallet->GetWalletTx(hash);
- if (wtx && SignalsOptInRBF(*wtx))
- return true;
- return false;
+ return wtx && SignalsOptInRBF(*wtx);
}
bool WalletModel::bumpFee(uint256 hash)
diff --git a/src/random.cpp b/src/random.cpp
index 3b9df3edaa..de7553c825 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -203,10 +203,43 @@ void GetRandBytes(unsigned char* buf, int num)
}
}
+static void AddDataToRng(void* data, size_t len);
+
+void RandAddSeedSleep()
+{
+ int64_t nPerfCounter1 = GetPerformanceCounter();
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ int64_t nPerfCounter2 = GetPerformanceCounter();
+
+ // Combine with and update state
+ AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1));
+ AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2));
+
+ memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1));
+ memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2));
+}
+
+
static std::mutex cs_rng_state;
static unsigned char rng_state[32] = {0};
static uint64_t rng_counter = 0;
+static void AddDataToRng(void* data, size_t len) {
+ CSHA512 hasher;
+ hasher.Write((const unsigned char*)&len, sizeof(len));
+ hasher.Write((const unsigned char*)data, len);
+ unsigned char buf[64];
+ {
+ std::unique_lock<std::mutex> lock(cs_rng_state);
+ hasher.Write(rng_state, sizeof(rng_state));
+ hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter));
+ ++rng_counter;
+ hasher.Finalize(buf);
+ memcpy(rng_state, buf + 32, 32);
+ }
+ memory_cleanse(buf, 64);
+}
+
void GetStrongRandBytes(unsigned char* out, int num)
{
assert(num <= 32);
diff --git a/src/random.h b/src/random.h
index 9551e1c461..6a63d57429 100644
--- a/src/random.h
+++ b/src/random.h
@@ -24,6 +24,13 @@ int GetRandInt(int nMax);
uint256 GetRandHash();
/**
+ * Add a little bit of randomness to the output of GetStrongRangBytes.
+ * This sleeps for a millisecond, so should only be called when there is
+ * no other work to be done.
+ */
+void RandAddSeedSleep();
+
+/**
* Function to gather random data from multiple sources, failing whenever any
* of those source fail to provide a result.
*/
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 9d72a23e6d..b4b160aac9 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -187,7 +187,7 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex)
latestblock.hash = pindex->GetBlockHash();
latestblock.height = pindex->nHeight;
}
- cond_blockchange.notify_all();
+ cond_blockchange.notify_all();
}
UniValue waitfornewblock(const JSONRPCRequest& request)
@@ -1074,6 +1074,17 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse
rv.push_back(Pair("startTime", consensusParams.vDeployments[id].nStartTime));
rv.push_back(Pair("timeout", consensusParams.vDeployments[id].nTimeout));
rv.push_back(Pair("since", VersionBitsTipStateSinceHeight(consensusParams, id)));
+ if (THRESHOLD_STARTED == thresholdState)
+ {
+ UniValue statsUV(UniValue::VOBJ);
+ BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id);
+ statsUV.push_back(Pair("period", statsStruct.period));
+ statsUV.push_back(Pair("threshold", statsStruct.threshold));
+ statsUV.push_back(Pair("elapsed", statsStruct.elapsed));
+ statsUV.push_back(Pair("count", statsStruct.count));
+ statsUV.push_back(Pair("possible", statsStruct.possible));
+ rv.push_back(Pair("statistics", statsUV));
+ }
return rv;
}
@@ -1119,7 +1130,14 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
" \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n"
" \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
" \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n"
- " \"since\": xx (numeric) height of the first block to which the status applies\n"
+ " \"since\": xx, (numeric) height of the first block to which the status applies\n"
+ " \"statistics\": { (object) numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)\n"
+ " \"period\": xx, (numeric) the length in blocks of the BIP9 signalling period \n"
+ " \"threshold\": xx, (numeric) the number of blocks with the version bit set required to activate the feature \n"
+ " \"elapsed\": xx, (numeric) the number of blocks elapsed since the beginning of the current period \n"
+ " \"count\": xx, (numeric) the number of blocks with the version bit set in the current period \n"
+ " \"possible\": xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold \n"
+ " }\n"
" }\n"
" }\n"
"}\n"
diff --git a/src/scheduler.cpp b/src/scheduler.cpp
index 0c1cfa2718..923ba2c231 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -4,6 +4,7 @@
#include "scheduler.h"
+#include "random.h"
#include "reverselock.h"
#include <assert.h>
@@ -39,6 +40,11 @@ void CScheduler::serviceQueue()
// is called.
while (!shouldStop()) {
try {
+ if (!shouldStop() && taskQueue.empty()) {
+ reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
+ // Use this chance to get a tiny bit more entropy
+ RandAddSeedSleep();
+ }
while (!shouldStop() && taskQueue.empty()) {
// Wait until there is something to do.
newTaskScheduled.wait(lock);
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index 3812490ec0..dc5372a070 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -104,10 +104,14 @@ BOOST_AUTO_TEST_CASE(addrman_simple)
// Test: New table has one addr and we add a diff addr we should
- // have two addrs.
+ // have at least one addr.
+ // Note that addrman's size cannot be tested reliably after insertion, as
+ // hash collisions may occur. But we can always be sure of at least one
+ // success.
+
CService addr2 = ResolveService("250.1.1.2", 8333);
BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source));
- BOOST_CHECK_EQUAL(addrman.size(), 2);
+ BOOST_CHECK(addrman.size() >= 1);
// Test: AddrMan::Clear() should empty the new table.
addrman.Clear();
@@ -120,7 +124,7 @@ BOOST_AUTO_TEST_CASE(addrman_simple)
vAddr.push_back(CAddress(ResolveService("250.1.1.3", 8333), NODE_NONE));
vAddr.push_back(CAddress(ResolveService("250.1.1.4", 8333), NODE_NONE));
BOOST_CHECK(addrman.Add(vAddr, source));
- BOOST_CHECK_EQUAL(addrman.size(), 2);
+ BOOST_CHECK(addrman.size() >= 1);
}
BOOST_AUTO_TEST_CASE(addrman_ports)
diff --git a/src/test/test_bitcoin_fuzzy.cpp b/src/test/test_bitcoin_fuzzy.cpp
index c4983f6f5c..e11e46bb02 100644
--- a/src/test/test_bitcoin_fuzzy.cpp
+++ b/src/test/test_bitcoin_fuzzy.cpp
@@ -59,9 +59,8 @@ bool read_stdin(std::vector<char> &data) {
return length==0;
}
-int main(int argc, char **argv)
+int do_fuzz()
{
- ECCVerifyHandle globalVerifyHandle;
std::vector<char> buffer;
if (!read_stdin(buffer)) return 0;
@@ -256,3 +255,23 @@ int main(int argc, char **argv)
return 0;
}
+int main(int argc, char **argv)
+{
+ ECCVerifyHandle globalVerifyHandle;
+#ifdef __AFL_INIT
+ // Enable AFL deferred forkserver mode. Requires compilation using
+ // afl-clang-fast++. See fuzzing.md for details.
+ __AFL_INIT();
+#endif
+
+#ifdef __AFL_LOOP
+ // Enable AFL persistent mode. Requires compilation using afl-clang-fast++.
+ // See fuzzing.md for details.
+ while (__AFL_LOOP(1000)) {
+ do_fuzz();
+ }
+ return 0;
+#else
+ return do_fuzz();
+#endif
+}
diff --git a/src/validation.cpp b/src/validation.cpp
index 77cd5985ac..ac16af3ee7 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -3934,6 +3934,12 @@ ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::D
return VersionBitsState(chainActive.Tip(), params, pos, versionbitscache);
}
+BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos)
+{
+ LOCK(cs_main);
+ return VersionBitsStatistics(chainActive.Tip(), params, pos);
+}
+
int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos)
{
LOCK(cs_main);
diff --git a/src/validation.h b/src/validation.h
index 743a2973f8..b19c3ff4f7 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -339,6 +339,9 @@ std::string FormatStateMessage(const CValidationState &state);
/** Get the BIP9 state for a given deployment at the current tip. */
ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos);
+/** Get the numerical statistics for the BIP9 state for a given deployment at the current tip. */
+BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos);
+
/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */
int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos);
diff --git a/src/versionbits.cpp b/src/versionbits.cpp
index 8a7cce7485..80786233f5 100644
--- a/src/versionbits.cpp
+++ b/src/versionbits.cpp
@@ -3,7 +3,6 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "versionbits.h"
-
#include "consensus/params.h"
const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = {
@@ -105,6 +104,36 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex*
return state;
}
+// return the numerical statistics of blocks signalling the specified BIP9 condition in this current period
+BIP9Stats AbstractThresholdConditionChecker::GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const
+{
+ BIP9Stats stats;
+
+ stats.period = Period(params);
+ stats.threshold = Threshold(params);
+
+ if (pindex == NULL)
+ return stats;
+
+ // Find beginning of period
+ const CBlockIndex* pindexEndOfPrevPeriod = pindex->GetAncestor(pindex->nHeight - ((pindex->nHeight + 1) % stats.period));
+ stats.elapsed = pindex->nHeight - pindexEndOfPrevPeriod->nHeight;
+
+ // Count from current block to beginning of period
+ int count = 0;
+ const CBlockIndex* currentIndex = pindex;
+ while (pindexEndOfPrevPeriod->nHeight != currentIndex->nHeight){
+ if (Condition(currentIndex, params))
+ count++;
+ currentIndex = currentIndex->pprev;
+ }
+
+ stats.count = count;
+ stats.possible = (stats.period - stats.threshold ) >= (stats.elapsed - count);
+
+ return stats;
+}
+
int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const
{
const ThresholdState initialState = GetStateFor(pindexPrev, params, cache);
@@ -167,6 +196,11 @@ ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::
return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, cache.caches[pos]);
}
+BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos)
+{
+ return VersionBitsConditionChecker(pos).GetStateStatisticsFor(pindexPrev, params);
+}
+
int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache)
{
return VersionBitsConditionChecker(pos).GetStateSinceHeightFor(pindexPrev, params, cache.caches[pos]);
diff --git a/src/versionbits.h b/src/versionbits.h
index 7a929266aa..f1d31ea0af 100644
--- a/src/versionbits.h
+++ b/src/versionbits.h
@@ -37,6 +37,14 @@ struct BIP9DeploymentInfo {
bool gbt_force;
};
+struct BIP9Stats {
+ int period;
+ int threshold;
+ int elapsed;
+ int count;
+ bool possible;
+};
+
extern const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[];
/**
@@ -51,6 +59,7 @@ protected:
virtual int Threshold(const Consensus::Params& params) const =0;
public:
+ BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindex, const Consensus::Params& params) const;
// Note that the functions below take a pindexPrev as input: they compute information for block B based on its parent.
ThresholdState GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const;
int GetStateSinceHeightFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const;
@@ -64,6 +73,7 @@ struct VersionBitsCache
};
ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache);
+BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos);
int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache);
uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos);
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index 2aa26fb00a..cb4719ae90 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -18,8 +18,6 @@ public:
bool fAllowOtherInputs;
//! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
bool fAllowWatchOnly;
- //! Minimum absolute fee (not per kilobyte)
- CAmount nMinimumTotalFee;
//! Override estimated feerate
bool fOverrideFeeRate;
//! Feerate to use if overrideFeeRate is true
@@ -40,7 +38,6 @@ public:
fAllowOtherInputs = false;
fAllowWatchOnly = false;
setSelected.clear();
- nMinimumTotalFee = 0;
nFeeRate = CFeeRate(0);
fOverrideFeeRate = false;
nConfirmTarget = 0;
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 82708dab26..d46cf69efb 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -536,14 +536,11 @@ UniValue importwallet(const JSONRPCRequest& request)
}
file.close();
pwallet->ShowProgress("", 100); // hide progress dialog in GUI
-
- CBlockIndex *pindex = chainActive.Tip();
- while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - TIMESTAMP_WINDOW)
- pindex = pindex->pprev;
-
pwallet->UpdateTimeFirstKey(nTimeBegin);
- LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1);
+ CBlockIndex *pindex = chainActive.FindEarliestAtLeast(nTimeBegin - TIMESTAMP_WINDOW);
+
+ LogPrintf("Rescanning last %i blocks\n", pindex ? chainActive.Height() - pindex->nHeight + 1 : 0);
pwallet->ScanForWalletTransactions(pindex);
pwallet->MarkDirty();
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 8eeba72a06..5c7359fdce 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -9,6 +9,7 @@
#include <utility>
#include <vector>
+#include "consensus/validation.h"
#include "rpc/server.h"
#include "test/test_bitcoin.h"
#include "validation.h"
@@ -19,6 +20,8 @@
#include <univalue.h>
extern UniValue importmulti(const JSONRPCRequest& request);
+extern UniValue dumpwallet(const JSONRPCRequest& request);
+extern UniValue importwallet(const JSONRPCRequest& request);
// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
#define RUN_TESTS 100
@@ -437,6 +440,66 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
}
}
+// Verify importwallet RPC starts rescan at earliest block with timestamp
+// greater or equal than key birthday. Previously there was a bug where
+// importwallet RPC would start the scan at the latest block with timestamp less
+// than or equal to key birthday.
+BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
+{
+ CWallet *pwalletMainBackup = ::pwalletMain;
+ LOCK(cs_main);
+
+ // Create two blocks with same timestamp to verify that importwallet rescan
+ // will pick up both blocks, not just the first.
+ const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5;
+ SetMockTime(BLOCK_TIME);
+ coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+ coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+
+ // Set key birthday to block time increased by the timestamp window, so
+ // rescan will start at the block time.
+ const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW;
+ SetMockTime(KEY_TIME);
+ coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+
+ // Import key into wallet and call dumpwallet to create backup file.
+ {
+ CWallet wallet;
+ LOCK(wallet.cs_wallet);
+ wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
+ wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
+
+ JSONRPCRequest request;
+ request.params.setArray();
+ request.params.push_back("wallet.backup");
+ ::pwalletMain = &wallet;
+ ::dumpwallet(request);
+ }
+
+ // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
+ // were scanned, and no prior blocks were scanned.
+ {
+ CWallet wallet;
+
+ JSONRPCRequest request;
+ request.params.setArray();
+ request.params.push_back("wallet.backup");
+ ::pwalletMain = &wallet;
+ ::importwallet(request);
+
+ BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3);
+ BOOST_CHECK_EQUAL(coinbaseTxns.size(), 103);
+ for (size_t i = 0; i < coinbaseTxns.size(); ++i) {
+ bool found = wallet.GetWalletTx(coinbaseTxns[i].GetHash());
+ bool expected = i >= 100;
+ BOOST_CHECK_EQUAL(found, expected);
+ }
+ }
+
+ SetMockTime(0);
+ ::pwalletMain = pwalletMainBackup;
+}
+
// Check that GetImmatureCredit() returns a newly calculated value instead of
// the cached value after a MarkDirty() call.
//
@@ -515,4 +578,104 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
SetMockTime(0);
}
+BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
+{
+ CTxDestination dest = CKeyID();
+ pwalletMain->AddDestData(dest, "misc", "val_misc");
+ pwalletMain->AddDestData(dest, "rr0", "val_rr0");
+ pwalletMain->AddDestData(dest, "rr1", "val_rr1");
+
+ auto values = pwalletMain->GetDestValues("rr");
+ BOOST_CHECK_EQUAL(values.size(), 2);
+ BOOST_CHECK_EQUAL(values[0], "val_rr0");
+ BOOST_CHECK_EQUAL(values[1], "val_rr1");
+}
+
+class ListCoinsTestingSetup : public TestChain100Setup
+{
+public:
+ ListCoinsTestingSetup()
+ {
+ CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ ::bitdb.MakeMock();
+ wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat"))));
+ bool firstRun;
+ wallet->LoadWallet(firstRun);
+ LOCK(wallet->cs_wallet);
+ wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
+ wallet->ScanForWalletTransactions(chainActive.Genesis());
+ }
+
+ ~ListCoinsTestingSetup()
+ {
+ wallet.reset();
+ ::bitdb.Flush(true);
+ ::bitdb.Reset();
+ }
+
+ CWalletTx& AddTx(CRecipient recipient)
+ {
+ CWalletTx wtx;
+ CReserveKey reservekey(wallet.get());
+ CAmount fee;
+ int changePos = -1;
+ std::string error;
+ BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error));
+ CValidationState state;
+ BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
+ auto it = wallet->mapWallet.find(wtx.GetHash());
+ BOOST_CHECK(it != wallet->mapWallet.end());
+ CreateAndProcessBlock({CMutableTransaction(*it->second.tx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ it->second.SetMerkleBranch(chainActive.Tip(), 1);
+ return it->second;
+ }
+
+ std::unique_ptr<CWallet> wallet;
+};
+
+BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
+{
+ std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
+ LOCK(wallet->cs_wallet);
+
+ // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
+ // address.
+ auto list = wallet->ListCoins();
+ BOOST_CHECK_EQUAL(list.size(), 1);
+ BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(list.begin()->second.size(), 1);
+
+ // Check initial balance from one mature coinbase transaction.
+ BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance());
+
+ // Add a transaction creating a change address, and confirm ListCoins still
+ // returns the coin associated with the change address underneath the
+ // coinbaseKey pubkey, even though the change address has a different
+ // pubkey.
+ AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */});
+ list = wallet->ListCoins();
+ BOOST_CHECK_EQUAL(list.size(), 1);
+ BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
+
+ // Lock both coins. Confirm number of available coins drops to 0.
+ std::vector<COutput> available;
+ wallet->AvailableCoins(available);
+ BOOST_CHECK_EQUAL(available.size(), 2);
+ for (const auto& group : list) {
+ for (const auto& coin : group.second) {
+ wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
+ }
+ }
+ wallet->AvailableCoins(available);
+ BOOST_CHECK_EQUAL(available.size(), 0);
+
+ // Confirm ListCoins still returns same result as before, despite coins
+ // being locked.
+ list = wallet->ListCoins();
+ BOOST_CHECK_EQUAL(list.size(), 1);
+ BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 993776252b..bc98435249 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -982,6 +982,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
return false;
}
+bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
+{
+ LOCK2(cs_main, cs_wallet);
+ const CWalletTx* wtx = GetWalletTx(hashTx);
+ return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 && !wtx->InMempool();
+}
+
bool CWallet::AbandonTransaction(const uint256& hashTx)
{
LOCK2(cs_main, cs_wallet);
@@ -1977,6 +1984,21 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, cons
return balance;
}
+CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
+{
+ LOCK2(cs_main, cs_wallet);
+
+ CAmount balance = 0;
+ std::vector<COutput> vCoins;
+ AvailableCoins(vCoins, true, coinControl);
+ for (const COutput& out : vCoins) {
+ if (out.fSpendable) {
+ balance += out.tx->tx->vout[out.i].nValue;
+ }
+ }
+ return balance;
+}
+
void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const
{
vCoins.clear();
@@ -2088,6 +2110,69 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
}
}
+std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
+{
+ // TODO: Add AssertLockHeld(cs_wallet) here.
+ //
+ // Because the return value from this function contains pointers to
+ // CWalletTx objects, callers to this function really should acquire the
+ // cs_wallet lock before calling it. However, the current caller doesn't
+ // acquire this lock yet. There was an attempt to add the missing lock in
+ // https://github.com/bitcoin/bitcoin/pull/10340, but that change has been
+ // postponed until after https://github.com/bitcoin/bitcoin/pull/10244 to
+ // avoid adding some extra complexity to the Qt code.
+
+ std::map<CTxDestination, std::vector<COutput>> result;
+
+ std::vector<COutput> availableCoins;
+ AvailableCoins(availableCoins);
+
+ LOCK2(cs_main, cs_wallet);
+ for (auto& coin : availableCoins) {
+ CTxDestination address;
+ if (coin.fSpendable &&
+ ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) {
+ result[address].emplace_back(std::move(coin));
+ }
+ }
+
+ std::vector<COutPoint> lockedCoins;
+ ListLockedCoins(lockedCoins);
+ for (const auto& output : lockedCoins) {
+ auto it = mapWallet.find(output.hash);
+ if (it != mapWallet.end()) {
+ int depth = it->second.GetDepthInMainChain();
+ if (depth >= 0 && output.n < it->second.tx->vout.size() &&
+ IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) {
+ CTxDestination address;
+ if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) {
+ result[address].emplace_back(
+ &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const
+{
+ const CTransaction* ptx = &tx;
+ int n = output;
+ while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) {
+ const COutPoint& prevout = ptx->vin[0].prevout;
+ auto it = mapWallet.find(prevout.hash);
+ if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n ||
+ !IsMine(it->second.tx->vout[prevout.n])) {
+ break;
+ }
+ ptx = it->second.tx.get();
+ n = prevout.n;
+ }
+ return ptx->vout[n];
+}
+
static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
@@ -2635,9 +2720,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
currentConfirmationTarget = coinControl->nConfirmTarget;
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator);
- if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
- nFeeNeeded = coinControl->nMinimumTotalFee;
- }
if (coinControl && coinControl->fOverrideFeeRate)
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
@@ -3410,7 +3492,7 @@ bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
return (setLockedCoins.count(outpt) > 0);
}
-void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts)
+void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
{
AssertLockHeld(cs_wallet); // setLockedCoins
for (std::set<COutPoint>::iterator it = setLockedCoins.begin();
@@ -3611,6 +3693,20 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st
return false;
}
+std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
+{
+ LOCK(cs_wallet);
+ std::vector<std::string> values;
+ for (const auto& address : mapAddressBook) {
+ for (const auto& data : address.second.destdata) {
+ if (!data.first.compare(0, prefix.size(), prefix)) {
+ values.emplace_back(data.second);
+ }
+ }
+ }
+ return values;
+}
+
std::string CWallet::GetWalletHelpString(bool showDebug)
{
std::string strUsage = HelpMessageGroup(_("Wallet options:"));
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 69f51b3f64..11b2f7a663 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -821,6 +821,16 @@ public:
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = NULL, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const;
/**
+ * Return list of available coins and locked coins grouped by non-change output address.
+ */
+ std::map<CTxDestination, std::vector<COutput>> ListCoins() const;
+
+ /**
+ * Find non-change parent output.
+ */
+ const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const;
+
+ /**
* Shuffle and select coins until nTargetValue is reached while avoiding
* small change; This method is stochastic for some inputs and upon
* completion the coin set and corresponding actual target value is
@@ -834,7 +844,7 @@ public:
void LockCoin(const COutPoint& output);
void UnlockCoin(const COutPoint& output);
void UnlockAllCoins();
- void ListLockedCoins(std::vector<COutPoint>& vOutpts);
+ void ListLockedCoins(std::vector<COutPoint>& vOutpts) const;
/*
* Rescan abort properties
@@ -873,6 +883,8 @@ public:
bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
//! Look up a destination data tuple in the store, return true if found false otherwise
bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const;
+ //! Get all destination values matching a prefix.
+ std::vector<std::string> GetDestValues(const std::string& prefix) const;
//! Adds a watch-only address to the store, and saves it to disk.
bool AddWatchOnly(const CScript& dest, int64_t nCreateTime);
@@ -917,6 +929,7 @@ public:
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const;
CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const;
+ CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
/**
* Insert additional inputs into the transaction by
@@ -1066,6 +1079,9 @@ public:
/** Set whether this wallet broadcasts transactions. */
void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; }
+ /** Return whether transaction can be abandoned */
+ bool TransactionCanBeAbandoned(const uint256& hashTx) const;
+
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
bool AbandonTransaction(const uint256& hashTx);
diff --git a/test/functional/bip9-softforks.py b/test/functional/bip9-softforks.py
index 1b2dff63d2..fff47fcca9 100755
--- a/test/functional/bip9-softforks.py
+++ b/test/functional/bip9-softforks.py
@@ -99,12 +99,43 @@ class BIP9SoftForksTest(ComparisonTestFramework):
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
assert_equal(self.get_bip9_status(bipName)['since'], 144)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
assert_equal(tmpl['vbavailable'][bipName], bitno)
assert_equal(tmpl['vbrequired'], 0)
assert(tmpl['version'] & activated_version)
+ # Test 1-A
+ # check stats after max number of "signalling not" blocks such that LOCKED_IN still possible this period
+ test_blocks = self.generate_blocks(36, 4, test_blocks) # 0x00000004 (signalling not)
+ test_blocks = self.generate_blocks(10, activated_version) # 0x20000001 (signalling ready)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 46)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
+
+ # Test 1-B
+ # check stats after one additional "signalling not" block -- LOCKED_IN no longer possible this period
+ test_blocks = self.generate_blocks(1, 4, test_blocks) # 0x00000004 (signalling not)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 47)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], False)
+
+ # Test 1-C
+ # finish period with "ready" blocks, but soft fork will still fail to advance to LOCKED_IN
+ test_blocks = self.generate_blocks(97, activated_version) # 0x20000001 (signalling ready)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
+ assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
+ assert_equal(self.get_bip9_status(bipName)['status'], 'started')
+
# Test 2
# Fail to achieve LOCKED_IN 100 out of 144 signal bit 1
# using a variety of bits to simulate multiple parallel softforks
@@ -116,6 +147,8 @@ class BIP9SoftForksTest(ComparisonTestFramework):
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
assert_equal(self.get_bip9_status(bipName)['since'], 144)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
assert_equal(tmpl['vbavailable'][bipName], bitno)
@@ -125,14 +158,24 @@ class BIP9SoftForksTest(ComparisonTestFramework):
# Test 3
# 108 out of 144 signal bit 1 to achieve LOCKED_IN
# using a variety of bits to simulate multiple parallel softforks
- test_blocks = self.generate_blocks(58, activated_version) # 0x20000001 (signalling ready)
+ test_blocks = self.generate_blocks(57, activated_version) # 0x20000001 (signalling ready)
test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not)
test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready)
test_blocks = self.generate_blocks(10, 4, test_blocks) # 0x20010000 (signalling not)
yield TestInstance(test_blocks, sync_every_block=False)
+ # check counting stats and "possible" flag before last block of this period achieves LOCKED_IN...
+ assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 143)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 107)
+ assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
+ assert_equal(self.get_bip9_status(bipName)['status'], 'started')
+
+ # ...continue with Test 3
+ test_blocks = self.generate_blocks(1, activated_version) # 0x20000001 (signalling ready)
+ yield TestInstance(test_blocks, sync_every_block=False)
+
assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
- assert_equal(self.get_bip9_status(bipName)['since'], 432)
+ assert_equal(self.get_bip9_status(bipName)['since'], 576)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
@@ -142,7 +185,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
- assert_equal(self.get_bip9_status(bipName)['since'], 432)
+ assert_equal(self.get_bip9_status(bipName)['since'], 576)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
@@ -168,7 +211,7 @@ class BIP9SoftForksTest(ComparisonTestFramework):
yield TestInstance([[block, True]])
assert_equal(self.get_bip9_status(bipName)['status'], 'active')
- assert_equal(self.get_bip9_status(bipName)['since'], 576)
+ assert_equal(self.get_bip9_status(bipName)['since'], 720)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName in tmpl['rules'])
assert(bipName not in tmpl['vbavailable'])
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 8d8139e4e4..4b5b311385 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -109,8 +109,7 @@ class BitcoinTestFramework(object):
help="Source directory containing bitcoind/bitcoin-cli (default: %default)")
parser.add_option("--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__))+"/../../cache"),
help="Directory for caching pregenerated datadirs")
- parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
- help="Root directory for datadirs")
+ parser.add_option("--tmpdir", dest="tmpdir", help="Root directory for datadirs")
parser.add_option("-l", "--loglevel", dest="loglevel", default="INFO",
help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console. Note that logs at all levels are always written to the test_framework.log file in the temporary test directory.")
parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",
@@ -124,9 +123,6 @@ class BitcoinTestFramework(object):
self.add_options(parser)
(self.options, self.args) = parser.parse_args()
- # backup dir variable for removal at cleanup
- self.options.root, self.options.tmpdir = self.options.tmpdir, self.options.tmpdir + '/' + str(self.options.port_seed)
-
if self.options.coveragedir:
enable_coverage(self.options.coveragedir)
@@ -137,7 +133,10 @@ class BitcoinTestFramework(object):
check_json_precision()
# Set up temp directory and start logging
- os.makedirs(self.options.tmpdir, exist_ok=False)
+ if self.options.tmpdir:
+ os.makedirs(self.options.tmpdir, exist_ok=False)
+ else:
+ self.options.tmpdir = tempfile.mkdtemp(prefix="test")
self._start_logging()
success = False
@@ -167,8 +166,6 @@ class BitcoinTestFramework(object):
if not self.options.nocleanup and not self.options.noshutdown and success:
self.log.info("Cleaning up")
shutil.rmtree(self.options.tmpdir)
- if not os.listdir(self.options.root):
- os.rmdir(self.options.root)
else:
self.log.warning("Not cleaning up dir %s" % self.options.tmpdir)
if os.getenv("PYTHON_DEBUG", ""):
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 6174ca1d88..b2aee7c739 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -16,6 +16,7 @@ For a description of arguments recognized by test scripts, see
import argparse
import configparser
+import datetime
import os
import time
import shutil
@@ -170,6 +171,7 @@ def main():
parser.add_argument('--jobs', '-j', type=int, default=4, help='how many test scripts to run in parallel. Default=4.')
parser.add_argument('--keepcache', '-k', action='store_true', help='the default behavior is to flush the cache directory on startup. --keepcache retains the cache from the previous testrun.')
parser.add_argument('--quiet', '-q', action='store_true', help='only print results summary and failure logs')
+ parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs")
args, unknown_args = parser.parse_known_args()
# args to be passed on always start with two dashes; tests are the remaining unknown args
@@ -187,6 +189,12 @@ def main():
logging_level = logging.INFO if args.quiet else logging.DEBUG
logging.basicConfig(format='%(message)s', level=logging_level)
+ # Create base test directory
+ tmpdir = "%s/bitcoin_test_runner_%s" % (args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))
+ os.makedirs(tmpdir)
+
+ logging.debug("Temporary test directory at %s" % tmpdir)
+
enable_wallet = config["components"].getboolean("ENABLE_WALLET")
enable_utils = config["components"].getboolean("ENABLE_UTILS")
enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND")
@@ -247,9 +255,9 @@ def main():
if not args.keepcache:
shutil.rmtree("%s/test/cache" % config["environment"]["BUILDDIR"], ignore_errors=True)
- run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"], config["environment"]["EXEEXT"], args.jobs, args.coverage, passon_args)
+ run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"], config["environment"]["EXEEXT"], tmpdir, args.jobs, args.coverage, passon_args)
-def run_tests(test_list, src_dir, build_dir, exeext, jobs=1, enable_coverage=False, args=[]):
+def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_coverage=False, args=[]):
# Warn if bitcoind is already running (unix only)
try:
if subprocess.check_output(["pidof", "bitcoind"]) is not None:
@@ -280,10 +288,10 @@ def run_tests(test_list, src_dir, build_dir, exeext, jobs=1, enable_coverage=Fal
if len(test_list) > 1 and jobs > 1:
# Populate cache
- subprocess.check_output([tests_dir + 'create_cache.py'] + flags)
+ subprocess.check_output([tests_dir + 'create_cache.py'] + flags + ["--tmpdir=%s/cache" % tmpdir])
#Run Tests
- job_queue = TestHandler(jobs, tests_dir, test_list, flags)
+ job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags)
time0 = time.time()
test_results = []
@@ -310,6 +318,10 @@ def run_tests(test_list, src_dir, build_dir, exeext, jobs=1, enable_coverage=Fal
logging.debug("Cleaning up coverage data")
coverage.cleanup()
+ # Clear up the temp directory if all subdirectories are gone
+ if not os.listdir(tmpdir):
+ os.rmdir(tmpdir)
+
all_passed = all(map(lambda test_result: test_result.was_successful, test_results))
sys.exit(not all_passed)
@@ -337,10 +349,11 @@ class TestHandler:
Trigger the testscrips passed in via the list.
"""
- def __init__(self, num_tests_parallel, tests_dir, test_list=None, flags=None):
+ def __init__(self, num_tests_parallel, tests_dir, tmpdir, test_list=None, flags=None):
assert(num_tests_parallel >= 1)
self.num_jobs = num_tests_parallel
self.tests_dir = tests_dir
+ self.tmpdir = tmpdir
self.test_list = test_list
self.flags = flags
self.num_running = 0
@@ -355,13 +368,15 @@ class TestHandler:
# Add tests
self.num_running += 1
t = self.test_list.pop(0)
- port_seed = ["--portseed={}".format(len(self.test_list) + self.portseed_offset)]
+ portseed = len(self.test_list) + self.portseed_offset
+ portseed_arg = ["--portseed={}".format(portseed)]
log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16)
log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16)
test_argv = t.split()
+ tmpdir = ["--tmpdir=%s/%s_%s" % (self.tmpdir, re.sub(".py$", "", test_argv[0]), portseed)]
self.jobs.append((t,
time.time(),
- subprocess.Popen([self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + port_seed,
+ subprocess.Popen([self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + portseed_arg + tmpdir,
universal_newlines=True,
stdout=log_stdout,
stderr=log_stderr),