aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/seeds/README.md4
-rw-r--r--doc/benchmarking.md49
-rw-r--r--doc/release-notes.md35
-rw-r--r--src/Makefile.am2
-rw-r--r--src/chainparams.cpp26
-rw-r--r--src/chainparams.h11
-rw-r--r--src/init.cpp28
-rw-r--r--src/net.cpp27
-rw-r--r--src/protocol.h10
-rw-r--r--src/qt/addresstablemodel.cpp6
-rw-r--r--src/qt/addresstablemodel.h4
-rw-r--r--src/qt/editaddressdialog.cpp5
-rw-r--r--src/qt/forms/receivecoinsdialog.ui121
-rw-r--r--src/qt/guiutil.cpp12
-rw-r--r--src/qt/guiutil.h2
-rw-r--r--src/qt/receivecoinsdialog.cpp17
-rw-r--r--src/qt/receiverequestdialog.cpp8
-rw-r--r--src/qt/test/wallettests.cpp4
-rw-r--r--src/qt/walletmodel.cpp5
-rw-r--r--src/qt/walletmodel.h4
-rw-r--r--src/rpc/blockchain.cpp17
-rw-r--r--src/rpc/misc.cpp122
-rw-r--r--src/rpc/util.cpp68
-rw-r--r--src/rpc/util.h19
-rw-r--r--src/util.cpp22
-rw-r--r--src/util.h1
-rw-r--r--src/validation.cpp8
-rw-r--r--src/wallet/db.cpp48
-rw-r--r--src/wallet/db.h2
-rw-r--r--src/wallet/init.cpp12
-rw-r--r--src/wallet/rpcdump.cpp374
-rw-r--r--src/wallet/rpcwallet.cpp124
-rw-r--r--src/wallet/test/wallet_tests.cpp12
-rw-r--r--src/wallet/wallet.cpp75
-rw-r--r--src/wallet/wallet.h47
-rw-r--r--src/wallet/walletutil.cpp2
-rw-r--r--test/README.md2
-rwxr-xr-xtest/functional/address_types.py16
-rwxr-xr-xtest/functional/bitcoin_cli.py2
-rwxr-xr-xtest/functional/bumpfee.py2
-rwxr-xr-xtest/functional/deprecated_rpc.py6
-rwxr-xr-xtest/functional/fundrawtransaction.py6
-rwxr-xr-xtest/functional/importmulti.py8
-rwxr-xr-xtest/functional/listtransactions.py3
-rwxr-xr-xtest/functional/multiwallet.py20
-rwxr-xr-xtest/functional/nulldummy.py4
-rwxr-xr-xtest/functional/p2p-compactblocks.py2
-rwxr-xr-xtest/functional/rawtransactions.py13
-rwxr-xr-xtest/functional/segwit.py32
-rwxr-xr-xtest/functional/test_framework/test_framework.py12
-rwxr-xr-xtest/functional/test_framework/test_node.py20
-rw-r--r--test/functional/test_framework/util.py5
-rwxr-xr-xtest/functional/test_runner.py6
-rwxr-xr-xtest/functional/wallet-accounts.py2
-rwxr-xr-xtest/functional/wallet-dump.py4
-rwxr-xr-xtest/functional/wallet-encryption.py19
-rwxr-xr-xtest/functional/zapwallettxes.py2
57 files changed, 988 insertions, 531 deletions
diff --git a/contrib/seeds/README.md b/contrib/seeds/README.md
index 139c03181f..6dc277f298 100644
--- a/contrib/seeds/README.md
+++ b/contrib/seeds/README.md
@@ -4,7 +4,9 @@ Utility to generate the seeds.txt list that is compiled into the client
(see [src/chainparamsseeds.h](/src/chainparamsseeds.h) and other utilities in [contrib/seeds](/contrib/seeds)).
Be sure to update `PATTERN_AGENT` in `makeseeds.py` to include the current version,
-and remove old versions as necessary.
+and remove old versions as necessary (at a minimum when GetDesireableServiceFlags
+changes its default return value, as those are the services which seeds are added
+to addrman with).
The seeds compiled into the release are created from sipa's DNS seed data, like this:
diff --git a/doc/benchmarking.md b/doc/benchmarking.md
index 0ba75afcda..99d36cb226 100644
--- a/doc/benchmarking.md
+++ b/doc/benchmarking.md
@@ -4,24 +4,49 @@ Benchmarking
Bitcoin Core has an internal benchmarking framework, with benchmarks
for cryptographic algorithms such as SHA1, SHA256, SHA512 and RIPEMD160. As well as the rolling bloom filter.
+Running
+---------------------
After compiling bitcoin-core, the benchmarks can be run with:
-`src/bench/bench_bitcoin`
+
+ src/bench/bench_bitcoin
The output will look similar to:
```
-#Benchmark,count,min,max,average
-RIPEMD160,448,0.001245033173334,0.002638196945190,0.002461894814457
-RollingBloom-refresh,1,0.000635000000000,0.000635000000000,0.000635000000000
-RollingBloom-refresh,1,0.000108000000000,0.000108000000000,0.000108000000000
-RollingBloom-refresh,1,0.000107000000000,0.000107000000000,0.000107000000000
-RollingBloom-refresh,1,0.000204000000000,0.000204000000000,0.000204000000000
-SHA1,640,0.000909024336207,0.001938136418660,0.001843086257577
-SHA256,256,0.002209486499909,0.008500099182129,0.004300644621253
-SHA512,384,0.001319904176016,0.002813005447388,0.002615700786312
-Sleep100ms,10,0.205592155456543,0.210056066513062,0.104166316986084
-Trig,67108864,0.000000014997003,0.000000015448112,0.000000015188842
+# Benchmark, evals, iterations, total, min, max, median
+Base58CheckEncode, 5, 320000, 120.772, 7.49351e-05, 7.59374e-05, 7.54759e-05
+Base58Decode, 5, 800000, 122.833, 3.0467e-05, 3.11732e-05, 3.06304e-05
+Base58Encode, 5, 470000, 137.094, 5.81061e-05, 5.85109e-05, 5.84462e-05
+BenchLockedPool, 5, 530, 34.2023, 0.0128247, 0.0129613, 0.0129026
+CCheckQueueSpeedPrevectorJob, 5, 1400, 26.1762, 0.00365048, 0.00388629, 0.00367108
+CCoinsCaching, 5, 170000, 48.1074, 5.60229e-05, 5.72316e-05, 5.66214e-05
+CoinSelection, 5, 650, 34.6426, 0.0105801, 0.0107699, 0.010664
+DeserializeAndCheckBlockTest, 5, 160, 39.2084, 0.0483662, 0.0494199, 0.0490138
+DeserializeBlockTest, 5, 130, 23.8129, 0.0357731, 0.0373763, 0.0365858
+FastRandom_1bit, 5, 440000000, 38.1609, 1.72974e-08, 1.73882e-08, 1.73478e-08
+FastRandom_32bit, 5, 110000000, 72.8237, 1.29992e-07, 1.37014e-07, 1.30115e-07
+MempoolEviction, 5, 41000, 89.8883, 0.000432748, 0.000446857, 0.000438483
+PrevectorClear, 5, 5600, 47.9229, 0.00169952, 0.0017455, 0.00170315
+PrevectorDestructor, 5, 5700, 44.5498, 0.0015561, 0.00156977, 0.00156469
+RIPEMD160, 5, 440, 135.988, 0.0615496, 0.062268, 0.0617779
+RollingBloom, 5, 1500000, 36.5109, 4.80961e-06, 4.97463e-06, 4.85811e-06
+SHA1, 5, 570, 51.808, 0.018065, 0.0182623, 0.0181865
+SHA256, 5, 340, 8.31841, 0.00483231, 0.00499803, 0.00485486
+SHA256_32b, 5, 4700000, 10.469, 4.43441e-07, 4.47611e-07, 4.45223e-07
+SHA512, 5, 330, 33.3408, 0.02017, 0.0202554, 0.0201921
+SipHash_32b, 5, 40000000, 38.7088, 1.91103e-07, 1.96998e-07, 1.93792e-07
+Sleep100ms, 5, 10, 5.01062, 0.100131, 0.100368, 0.100147
+Trig, 5, 12000000, 5.95494, 9.78115e-08, 1.04354e-07, 9.80682e-08
+VerifyScriptBench, 5, 6300, 9.02493, 0.000285566, 0.000288433, 0.000286175
```
+Help
+---------------------
+`-?` will print a list of options and exit:
+
+ src/bench/bench_bitcoin -?
+
+Notes
+---------------------
More benchmarks are needed for, in no particular order:
- Script Validation
- CCoinDBView caching
diff --git a/doc/release-notes.md b/doc/release-notes.md
index d92666da72..29377d4a1d 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -75,19 +75,26 @@ There is a checkbox to mark the transaction as final.
The RPC default remains unchanged: to use RBF, launch with `-walletrbf=1` or
use the `replaceable` argument for individual transactions.
-Custom wallet directories
----------------------
-The ability to specify a directory other than the default data directory in which to store
-wallets has been added. An existing directory can be specified using the `-walletdir=<dir>`
-argument. Wallets loaded via `-wallet` arguments must be in this wallet directory. Care should be taken
-when choosing a wallet directory location, as if it becomes unavailable during operation,
-funds may be lost.
-
-Default wallet directory change
---------------------------
-On new installations (if the data directory doesn't exist), wallets will now be stored in a
-new `wallets/` subdirectory inside the data directory. If this `wallets/` subdirectory
-doesn't exist (i.e. on existing nodes), the current datadir root is used instead, as it was.
+Wallets directory configuration (`-walletdir`)
+----------------------------------------------
+
+Bitcoin Core now has more flexibility in where the wallets directory can be
+located. Previously wallet database files were stored at the top level of the
+bitcoin data directory. The behavior is now:
+
+- For new installations (where the data directory doesn't already exist),
+ wallets will now be stored in a new `wallets/` subdirectory inside the data
+ directory by default.
+- For existing nodes (where the data directory already exists), wallets will be
+ stored in the data directory root by default. If a `wallets/` subdirectory
+ already exists in the data directory root, then wallets will be stored in the
+ `wallets/` subdirectory by default.
+- The location of the wallets directory can be overridden by specifying a
+ `-walletdir=<path>` option where `<path>` can be an absolute path to a
+ directory or directory symlink.
+
+Care should be taken when choosing the wallets directory location, as if it
+becomes unavailable during operation, funds may be lost.
Low-level RPC changes
----------------------
@@ -97,6 +104,8 @@ Low-level RPC changes
* `getwalletinfo`
* `getmininginfo`
- The wallet RPC `getreceivedbyaddress` will return an error if called with an address not in the wallet.
+- The wallet RPC `addwitnessaddress` was deprecated and will be removed in version 0.17,
+ set the `address_type` argument of `getnewaddress`, or option `-addresstype=[bech32|p2sh-segwit]` instead.
Changed command-line options
-----------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 4b65774fc6..4fbd605d9e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -133,6 +133,7 @@ BITCOIN_CORE_H = \
rpc/safemode.h \
rpc/server.h \
rpc/register.h \
+ rpc/util.h \
scheduler.h \
script/sigcache.h \
script/sign.h \
@@ -352,6 +353,7 @@ libbitcoin_util_a_SOURCES = \
fs.cpp \
random.cpp \
rpc/protocol.cpp \
+ rpc/util.cpp \
support/cleanse.cpp \
sync.cpp \
threadinterrupt.cpp \
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 97a6c346ab..39757baf9e 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -124,13 +124,17 @@ public:
assert(consensus.hashGenesisBlock == uint256S("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"));
assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
- // Note that of those with the service bits flag, most only support a subset of possible options
- vSeeds.emplace_back("seed.bitcoin.sipa.be", true); // Pieter Wuille, only supports x1, x5, x9, and xd
- vSeeds.emplace_back("dnsseed.bluematt.me", true); // Matt Corallo, only supports x9
- vSeeds.emplace_back("dnsseed.bitcoin.dashjr.org", false); // Luke Dashjr
- vSeeds.emplace_back("seed.bitcoinstats.com", true); // Christian Decker, supports x1 - xf
- vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch", true); // Jonas Schnelli, only supports x1, x5, x9, and xd
- vSeeds.emplace_back("seed.btc.petertodd.org", true); // Peter Todd, only supports x1, x5, x9, and xd
+ // Note that of those which support the service bits prefix, most only support a subset of
+ // possible options.
+ // This is fine at runtime as we'll fall back to using them as a oneshot if they dont support the
+ // service bits we want, but we should get them updated to support all service bits wanted by any
+ // release ASAP to avoid it where possible.
+ vSeeds.emplace_back("seed.bitcoin.sipa.be"); // Pieter Wuille, only supports x1, x5, x9, and xd
+ vSeeds.emplace_back("dnsseed.bluematt.me"); // Matt Corallo, only supports x9
+ vSeeds.emplace_back("dnsseed.bitcoin.dashjr.org"); // Luke Dashjr
+ vSeeds.emplace_back("seed.bitcoinstats.com"); // Christian Decker, supports x1 - xf
+ vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch"); // Jonas Schnelli, only supports x1, x5, x9, and xd
+ vSeeds.emplace_back("seed.btc.petertodd.org"); // Peter Todd, only supports x1, x5, x9, and xd
base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,0);
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,5);
@@ -229,10 +233,10 @@ public:
vFixedSeeds.clear();
vSeeds.clear();
// nodes with support for servicebits filtering should be at the top
- vSeeds.emplace_back("testnet-seed.bitcoin.jonasschnelli.ch", true);
- vSeeds.emplace_back("seed.tbtc.petertodd.org", true);
- vSeeds.emplace_back("seed.testnet.bitcoin.sprovoost.nl", true);
- vSeeds.emplace_back("testnet-seed.bluematt.me", false);
+ vSeeds.emplace_back("testnet-seed.bitcoin.jonasschnelli.ch");
+ vSeeds.emplace_back("seed.tbtc.petertodd.org");
+ vSeeds.emplace_back("seed.testnet.bitcoin.sprovoost.nl");
+ vSeeds.emplace_back("testnet-seed.bluematt.me"); // Just a static list of stable node(s), only supports x9
base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);
diff --git a/src/chainparams.h b/src/chainparams.h
index ecfe1a0ac3..d478da9891 100644
--- a/src/chainparams.h
+++ b/src/chainparams.h
@@ -14,12 +14,6 @@
#include <memory>
#include <vector>
-struct CDNSSeedData {
- std::string host;
- bool supportsServiceBitsFiltering;
- CDNSSeedData(const std::string &strHost, bool supportsServiceBitsFilteringIn) : host(strHost), supportsServiceBitsFiltering(supportsServiceBitsFilteringIn) {}
-};
-
struct SeedSpec6 {
uint8_t addr[16];
uint16_t port;
@@ -71,7 +65,8 @@ public:
bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; }
/** Return the BIP70 network string (main, test or regtest) */
std::string NetworkIDString() const { return strNetworkID; }
- const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; }
+ /** Return the list of hostnames to look up for DNS seeds */
+ const std::vector<std::string>& DNSSeeds() const { return vSeeds; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP() const { return bech32_hrp; }
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
@@ -85,7 +80,7 @@ protected:
CMessageHeader::MessageStartChars pchMessageStart;
int nDefaultPort;
uint64_t nPruneAfterHeight;
- std::vector<CDNSSeedData> vSeeds;
+ std::vector<std::string> vSeeds;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string bech32_hrp;
std::string strNetworkID;
diff --git a/src/init.cpp b/src/init.cpp
index 7215e87359..d1d733af94 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1143,23 +1143,10 @@ bool AppInitParameterInteraction()
static bool LockDataDirectory(bool probeOnly)
{
- std::string strDataDir = GetDataDir().string();
-
// Make sure only a single Bitcoin process is using the data directory.
- fs::path pathLockFile = GetDataDir() / ".lock";
- FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist.
- if (file) fclose(file);
-
- try {
- static boost::interprocess::file_lock lock(pathLockFile.string().c_str());
- if (!lock.try_lock()) {
- return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), strDataDir, _(PACKAGE_NAME)));
- }
- if (probeOnly) {
- lock.unlock();
- }
- } catch(const boost::interprocess::interprocess_exception& e) {
- return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running.") + " %s.", strDataDir, _(PACKAGE_NAME), e.what()));
+ fs::path datadir = GetDataDir();
+ if (!LockDirectory(datadir, ".lock", probeOnly)) {
+ return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), datadir.string(), _(PACKAGE_NAME)));
}
return true;
}
@@ -1223,6 +1210,15 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string());
LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD);
+ // Warn about relative -datadir path.
+ if (gArgs.IsArgSet("-datadir") && !fs::path(gArgs.GetArg("-datadir", "")).is_absolute()) {
+ LogPrintf("Warning: relative datadir option '%s' specified, which will be interpreted relative to the "
+ "current working directory '%s'. This is fragile, because if bitcoin is started in the future "
+ "from a different location, it will be unable to locate the current data files. There could "
+ "also be data loss if bitcoin is started while in a temporary directory.\n",
+ gArgs.GetArg("-datadir", ""), fs::current_path().string());
+ }
+
InitSignatureCache();
InitScriptExecutionCache();
diff --git a/src/net.cpp b/src/net.cpp
index 682f47bff2..8111390749 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -136,7 +136,7 @@ static std::vector<CAddress> convertSeed6(const std::vector<SeedSpec6> &vSeedsIn
for (const auto& seed_in : vSeedsIn) {
struct in6_addr ip;
memcpy(&ip, seed_in.addr, sizeof(ip));
- CAddress addr(CService(ip, seed_in.port), NODE_NETWORK);
+ CAddress addr(CService(ip, seed_in.port), GetDesirableServiceFlags(NODE_NONE));
addr.nTime = GetTime() - GetRand(nOneWeek) - nOneWeek;
vSeedsOut.push_back(addr);
}
@@ -1577,19 +1577,6 @@ void MapPort(bool)
-static std::string GetDNSHost(const CDNSSeedData& data, ServiceFlags* requiredServiceBits)
-{
- //use default host for non-filter-capable seeds or if we use the default service bits (NODE_NETWORK)
- if (!data.supportsServiceBitsFiltering || *requiredServiceBits == NODE_NETWORK) {
- *requiredServiceBits = NODE_NETWORK;
- return data.host;
- }
-
- // See chainparams.cpp, most dnsseeds only support one or two possible servicebits hostnames
- return strprintf("x%x.%s", *requiredServiceBits, data.host);
-}
-
-
void CConnman::ThreadDNSAddressSeed()
{
// goal: only query DNS seeds if address need is acute
@@ -1612,22 +1599,22 @@ void CConnman::ThreadDNSAddressSeed()
}
}
- const std::vector<CDNSSeedData> &vSeeds = Params().DNSSeeds();
+ const std::vector<std::string> &vSeeds = Params().DNSSeeds();
int found = 0;
LogPrintf("Loading addresses from DNS seeds (could take a while)\n");
- for (const CDNSSeedData &seed : vSeeds) {
+ for (const std::string &seed : vSeeds) {
if (interruptNet) {
return;
}
if (HaveNameProxy()) {
- AddOneShot(seed.host);
+ AddOneShot(seed);
} else {
std::vector<CNetAddr> vIPs;
std::vector<CAddress> vAdd;
ServiceFlags requiredServiceBits = GetDesirableServiceFlags(NODE_NONE);
- std::string host = GetDNSHost(seed, &requiredServiceBits);
+ std::string host = strprintf("x%x.%s", requiredServiceBits, seed);
CNetAddr resolveSource;
if (!resolveSource.SetInternal(host)) {
continue;
@@ -1643,6 +1630,10 @@ void CConnman::ThreadDNSAddressSeed()
found++;
}
addrman.Add(vAdd, resolveSource);
+ } else {
+ // We now avoid directly using results from DNS Seeds which do not support service bit filtering,
+ // instead using them as a oneshot to get nodes with our desired service bits.
+ AddOneShot(seed);
}
}
}
diff --git a/src/protocol.h b/src/protocol.h
index 452b5b28ce..43d8ac067a 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -291,7 +291,15 @@ enum ServiceFlags : uint64_t {
* unless they set NODE_NETWORK_LIMITED and we are out of IBD, in which
* case NODE_NETWORK_LIMITED suffices).
*
- * Thus, generally, avoid calling with peerServices == NODE_NONE.
+ * Thus, generally, avoid calling with peerServices == NODE_NONE, unless
+ * state-specific flags must absolutely be avoided. When called with
+ * peerServices == NODE_NONE, the returned desirable service flags are
+ * guaranteed to not change dependant on state - ie they are suitable for
+ * use when describing peers which we know to be desirable, but for which
+ * we do not have a confirmed set of service flags.
+ *
+ * If the NODE_NONE return value is changed, contrib/seeds/makeseeds.py
+ * should be updated appropriately to filter for the same nodes.
*/
static ServiceFlags GetDesirableServiceFlags(ServiceFlags services) {
return ServiceFlags(NODE_NETWORK | NODE_WITNESS);
diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp
index a2521a1e9e..74f0db3520 100644
--- a/src/qt/addresstablemodel.cpp
+++ b/src/qt/addresstablemodel.cpp
@@ -341,7 +341,7 @@ void AddressTableModel::updateEntry(const QString &address,
priv->updateEntry(address, label, isMine, purpose, status);
}
-QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address)
+QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type)
{
std::string strLabel = label.toStdString();
std::string strAddress = address.toStdString();
@@ -384,8 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
return QString();
}
}
- wallet->LearnRelatedScripts(newKey, g_address_type);
- strAddress = EncodeDestination(GetDestinationForKey(newKey, g_address_type));
+ wallet->LearnRelatedScripts(newKey, address_type);
+ strAddress = EncodeDestination(GetDestinationForKey(newKey, address_type));
}
else
{
diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h
index d04b95ebae..11439e25d5 100644
--- a/src/qt/addresstablemodel.h
+++ b/src/qt/addresstablemodel.h
@@ -8,6 +8,8 @@
#include <QAbstractTableModel>
#include <QStringList>
+enum OutputType : int;
+
class AddressTablePriv;
class WalletModel;
@@ -61,7 +63,7 @@ public:
/* Add an address to the model.
Returns the added address on success, and an empty string otherwise.
*/
- QString addRow(const QString &type, const QString &label, const QString &address);
+ QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type);
/* Look up label for address in address book, if not found return empty string.
*/
diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp
index 6b7df2f2b9..a945fc6aa0 100644
--- a/src/qt/editaddressdialog.cpp
+++ b/src/qt/editaddressdialog.cpp
@@ -11,6 +11,8 @@
#include <QDataWidgetMapper>
#include <QMessageBox>
+extern OutputType g_address_type;
+
EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent) :
QDialog(parent),
ui(new Ui::EditAddressDialog),
@@ -77,7 +79,8 @@ bool EditAddressDialog::saveCurrentRow()
address = model->addRow(
mode == NewSendingAddress ? AddressTableModel::Send : AddressTableModel::Receive,
ui->labelEdit->text(),
- ui->addressEdit->text());
+ ui->addressEdit->text(),
+ g_address_type);
break;
case EditReceivingAddress:
case EditSendingAddress:
diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui
index 58f030ebf0..09fb435a58 100644
--- a/src/qt/forms/receivecoinsdialog.ui
+++ b/src/qt/forms/receivecoinsdialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>776</width>
- <height>364</height>
+ <height>396</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
@@ -28,6 +28,22 @@
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QGridLayout" name="gridLayout">
+ <item row="5" column="0">
+ <widget class="QLabel" name="label">
+ <property name="toolTip">
+ <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Amount:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>reqAmount</cstring>
+ </property>
+ </widget>
+ </item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="toolTip">
@@ -51,13 +67,6 @@
</property>
</widget>
</item>
- <item row="6" column="2">
- <widget class="QLineEdit" name="reqMessage">
- <property name="toolTip">
- <string>An optional message to attach to the payment request, which will be displayed when the request is opened. Note: The message will not be sent with the payment over the Bitcoin network.</string>
- </property>
- </widget>
- </item>
<item row="2" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
@@ -81,32 +90,10 @@
</property>
</widget>
</item>
- <item row="5" column="0">
- <widget class="QLabel" name="label">
- <property name="toolTip">
- <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string>
- </property>
- <property name="text">
- <string>&amp;Amount:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>reqAmount</cstring>
- </property>
- </widget>
- </item>
- <item row="5" column="2">
- <widget class="BitcoinAmountField" name="reqAmount">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
+ <item row="6" column="2">
+ <widget class="QLineEdit" name="reqMessage">
<property name="toolTip">
- <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string>
+ <string>An optional message to attach to the payment request, which will be displayed when the request is opened. Note: The message will not be sent with the payment over the Bitcoin network.</string>
</property>
</widget>
</item>
@@ -174,6 +161,73 @@
</property>
</widget>
</item>
+ <item row="5" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="BitcoinAmountField" name="reqAmount">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>1000</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useBech32">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>1000</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="toolTip">
+ <string>Bech32 addresses (BIP-173) are cheaper to spend from and offer better protection against typos. When unchecked a P2SH wrapped SegWit address will be created, compatible with older wallets.</string>
+ </property>
+ <property name="text">
+ <string>Generate Bech32 address</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</item>
</layout>
@@ -306,6 +360,7 @@
<tabstops>
<tabstop>reqLabel</tabstop>
<tabstop>reqAmount</tabstop>
+ <tabstop>useBech32</tabstop>
<tabstop>reqMessage</tabstop>
<tabstop>receiveButton</tabstop>
<tabstop>clearButton</tabstop>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 670d6108db..558d4f108c 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -995,6 +995,18 @@ QString formatBytes(uint64_t bytes)
return QString(QObject::tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024);
}
+qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) {
+ while(font_size >= minPointSize) {
+ font.setPointSizeF(font_size);
+ QFontMetrics fm(font);
+ if (fm.width(text) < width) {
+ break;
+ }
+ font_size -= 0.5;
+ }
+ return font_size;
+}
+
void ClickableLabel::mouseReleaseEvent(QMouseEvent *event)
{
Q_EMIT clicked(event->pos());
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index ad0e22ccd6..71a69483f5 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -201,6 +201,8 @@ namespace GUIUtil
QString formatBytes(uint64_t bytes);
+ qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize = 4, qreal startPointSize = 14);
+
class ClickableLabel : public QLabel
{
Q_OBJECT
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index e07f2b74ee..7fd5285467 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -2,6 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <wallet/wallet.h>
+
#include <qt/receivecoinsdialog.h>
#include <qt/forms/ui_receivecoinsdialog.h>
@@ -91,6 +93,15 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model)
SLOT(recentRequestsView_selectionChanged(QItemSelection, QItemSelection)));
// Last 2 columns are set by the columnResizingFixer, when the table geometry is ready.
columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this);
+
+ // configure bech32 checkbox, disable if launched with legacy as default:
+ if (model->getDefaultAddressType() == OUTPUT_TYPE_BECH32) {
+ ui->useBech32->setCheckState(Qt::Checked);
+ } else {
+ ui->useBech32->setCheckState(Qt::Unchecked);
+ }
+
+ ui->useBech32->setVisible(model->getDefaultAddressType() != OUTPUT_TYPE_LEGACY);
}
}
@@ -133,7 +144,11 @@ void ReceiveCoinsDialog::on_receiveButton_clicked()
QString address;
QString label = ui->reqLabel->text();
/* Generate new receiving address */
- address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "");
+ OutputType address_type = model->getDefaultAddressType();
+ if (address_type != OUTPUT_TYPE_LEGACY) {
+ address_type = ui->useBech32->isChecked() ? OUTPUT_TYPE_BECH32 : OUTPUT_TYPE_P2SH_SEGWIT;
+ }
+ address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type);
SendCoinsRecipient info(address, label,
ui->reqAmount->value(), ui->reqMessage->text());
ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp
index 209397ca0c..d4cb0e5ba2 100644
--- a/src/qt/receiverequestdialog.cpp
+++ b/src/qt/receiverequestdialog.cpp
@@ -183,9 +183,13 @@ void ReceiveRequestDialog::update()
QPainter painter(&qrAddrImage);
painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE));
QFont font = GUIUtil::fixedPitchFont();
- font.setPixelSize(12);
- painter.setFont(font);
QRect paddedRect = qrAddrImage.rect();
+
+ // calculate ideal font size
+ qreal font_size = GUIUtil::calculateIdealFontSize(paddedRect.width() - 20, info.address, font);
+ font.setPointSizeF(font_size);
+
+ painter.setFont(font);
paddedRect.setHeight(QR_IMAGE_SIZE+12);
painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, info.address);
painter.end();
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index a270e5de59..cd49292138 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -169,7 +169,9 @@ void TestGUI()
}
{
LOCK(cs_main);
- wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true);
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
}
wallet.SetBroadcastTransactions(true);
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 494b46905d..4d7e977fcb 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -733,6 +733,11 @@ bool WalletModel::hdEnabled() const
return wallet->IsHDEnabled();
}
+OutputType WalletModel::getDefaultAddressType() const
+{
+ return g_address_type;
+}
+
int WalletModel::getDefaultConfirmTarget() const
{
return nTxConfirmTarget;
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index 7d7e4f160e..9e13de79be 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -15,6 +15,8 @@
#include <QObject>
+enum OutputType : int;
+
class AddressTableModel;
class OptionsModel;
class PlatformStyle;
@@ -214,6 +216,8 @@ public:
bool hdEnabled() const;
+ OutputType getDefaultAddressType() const;
+
int getDefaultConfirmTarget() const;
private:
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 1b2c71c4a4..346672e45a 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -24,6 +24,7 @@
#include <util.h>
#include <utilstrencodings.h>
#include <hash.h>
+#include <validationinterface.h>
#include <warnings.h>
#include <stdint.h>
@@ -323,6 +324,21 @@ UniValue waitforblockheight(const JSONRPCRequest& request)
return ret;
}
+UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() > 0) {
+ throw std::runtime_error(
+ "syncwithvalidationinterfacequeue\n"
+ "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n"
+ "\nExamples:\n"
+ + HelpExampleCli("syncwithvalidationinterfacequeue","")
+ + HelpExampleRpc("syncwithvalidationinterfacequeue","")
+ );
+ }
+ SyncWithValidationInterfaceQueue();
+ return NullUniValue;
+}
+
UniValue getdifficulty(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
@@ -1628,6 +1644,7 @@ static const CRPCCommand commands[] =
{ "hidden", "waitfornewblock", &waitfornewblock, {"timeout"} },
{ "hidden", "waitforblock", &waitforblock, {"blockhash","timeout"} },
{ "hidden", "waitforblockheight", &waitforblockheight, {"height","timeout"} },
+ { "hidden", "syncwithvalidationinterfacequeue", &syncwithvalidationinterfacequeue, {} },
};
void RegisterBlockchainRPCCommands(CRPCTable &t)
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index 95a27d474b..c11dda22c4 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -15,6 +15,7 @@
#include <netbase.h>
#include <rpc/blockchain.h>
#include <rpc/server.h>
+#include <rpc/util.h>
#include <timedata.h>
#include <util.h>
#include <utilstrencodings.h>
@@ -254,88 +255,21 @@ UniValue validateaddress(const JSONRPCRequest& request)
// Needed even with !ENABLE_WALLET, to pass (ignored) pointers around
class CWallet;
-/**
- * Used by addmultisigaddress / createmultisig:
- */
-CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params)
-{
- int nRequired = params[0].get_int();
- const UniValue& keys = params[1].get_array();
-
- // Gather public keys
- if (nRequired < 1)
- throw std::runtime_error("a multisignature address must require at least one key to redeem");
- if ((int)keys.size() < nRequired)
- throw std::runtime_error(
- strprintf("not enough keys supplied "
- "(got %u keys, but need at least %d to redeem)", keys.size(), nRequired));
- if (keys.size() > 16)
- throw std::runtime_error("Number of addresses involved in the multisignature address creation > 16\nReduce the number");
- std::vector<CPubKey> pubkeys;
- pubkeys.resize(keys.size());
- for (unsigned int i = 0; i < keys.size(); i++)
- {
- const std::string& ks = keys[i].get_str();
-#ifdef ENABLE_WALLET
- // Case 1: Bitcoin address and we have full public key:
- CTxDestination dest = DecodeDestination(ks);
- if (pwallet && IsValidDestination(dest)) {
- CKeyID key = GetKeyForDestination(*pwallet, dest);
- if (key.IsNull()) {
- throw std::runtime_error(strprintf("%s does not refer to a key", ks));
- }
- CPubKey vchPubKey;
- if (!pwallet->GetPubKey(key, vchPubKey)) {
- throw std::runtime_error(strprintf("no full public key for address %s", ks));
- }
- if (!vchPubKey.IsFullyValid())
- throw std::runtime_error(" Invalid public key: "+ks);
- pubkeys[i] = vchPubKey;
- }
-
- // Case 2: hex public key
- else
-#endif
- if (IsHex(ks))
- {
- CPubKey vchPubKey(ParseHex(ks));
- if (!vchPubKey.IsFullyValid())
- throw std::runtime_error(" Invalid public key: "+ks);
- pubkeys[i] = vchPubKey;
- }
- else
- {
- throw std::runtime_error(" Invalid public key: "+ks);
- }
- }
- CScript result = GetScriptForMultisig(nRequired, pubkeys);
-
- if (result.size() > MAX_SCRIPT_ELEMENT_SIZE)
- throw std::runtime_error(
- strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE));
-
- return result;
-}
-
UniValue createmultisig(const JSONRPCRequest& request)
{
-#ifdef ENABLE_WALLET
- CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
-#else
- CWallet * const pwallet = nullptr;
-#endif
-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 2)
{
std::string msg = "createmultisig nrequired [\"key\",...]\n"
"\nCreates a multi-signature address with n signature of m keys required.\n"
"It returns a json object with the address and redeemScript.\n"
-
+ "DEPRECATION WARNING: Using addresses with createmultisig is deprecated. Clients must\n"
+ "transition to using addmultisigaddress to create multisig addresses with addresses known\n"
+ "to the wallet before upgrading to v0.17. To use the deprecated functionality, start bitcoind with -deprecatedrpc=createmultisig\n"
"\nArguments:\n"
- "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
- "2. \"keys\" (string, required) A json array of keys which are bitcoin addresses or hex-encoded public keys\n"
+ "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
+ "2. \"keys\" (string, required) A json array of hex-encoded public keys\n"
" [\n"
- " \"key\" (string) bitcoin address or hex-encoded public key\n"
+ " \"key\" (string) The hex-encoded public key\n"
" ,...\n"
" ]\n"
@@ -346,16 +280,37 @@ UniValue createmultisig(const JSONRPCRequest& request)
"}\n"
"\nExamples:\n"
- "\nCreate a multisig address from 2 addresses\n"
- + HelpExampleCli("createmultisig", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") +
+ "\nCreate a multisig address from 2 public keys\n"
+ + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") +
"\nAs a json rpc call\n"
- + HelpExampleRpc("createmultisig", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"")
+ + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"")
;
throw std::runtime_error(msg);
}
+ int required = request.params[0].get_int();
+
+ // Get the public keys
+ const UniValue& keys = request.params[1].get_array();
+ std::vector<CPubKey> pubkeys;
+ for (unsigned int i = 0; i < keys.size(); ++i) {
+ if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
+ pubkeys.push_back(HexToPubKey(keys[i].get_str()));
+ } else {
+#ifdef ENABLE_WALLET
+ CWallet* const pwallet = GetWalletForJSONRPCRequest(request);
+ if (IsDeprecatedRPCEnabled("createmultisig") && EnsureWalletIsAvailable(pwallet, false)) {
+ pubkeys.push_back(AddrToPubKey(pwallet, keys[i].get_str()));
+ } else
+#endif
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\nNote that from v0.16, createmultisig no longer accepts addresses."
+ " Clients must transition to using addmultisigaddress to create multisig addresses with addresses known to the wallet before upgrading to v0.17."
+ " To use the deprecated functionality, start bitcoind with -deprecatedrpc=createmultisig", keys[i].get_str()));
+ }
+ }
+
// Construct using pay-to-script-hash:
- CScript inner = _createmultisig_redeemScript(pwallet, request.params);
+ CScript inner = CreateMultisigRedeemscript(required, pubkeys);
CScriptID innerID(inner);
UniValue result(UniValue::VOBJ);
@@ -668,6 +623,18 @@ UniValue echo(const JSONRPCRequest& request)
return request.params;
}
+static UniValue getinfo_deprecated(const JSONRPCRequest& request)
+{
+ throw JSONRPCError(RPC_METHOD_NOT_FOUND,
+ "getinfo\n"
+ "\nThis call was removed in version 0.16.0. Use the appropriate fields from:\n"
+ "- getblockchaininfo: blocks, difficulty, chain\n"
+ "- getnetworkinfo: version, protocolversion, timeoffset, connections, proxy, relayfee, warnings\n"
+ "- getwalletinfo: balance, keypoololdest, keypoolsize, paytxfee, unlocked_until, walletversion\n"
+ "\nbitcoin-cli has the option -getinfo to collect and format these in the old format."
+ );
+}
+
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
@@ -682,6 +649,7 @@ static const CRPCCommand commands[] =
{ "hidden", "setmocktime", &setmocktime, {"timestamp"}},
{ "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
{ "hidden", "echojson", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
+ { "hidden", "getinfo", &getinfo_deprecated, {}},
};
void RegisterMiscRPCCommands(CRPCTable &t)
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
new file mode 100644
index 0000000000..09ded4e46e
--- /dev/null
+++ b/src/rpc/util.cpp
@@ -0,0 +1,68 @@
+// Copyright (c) 2017 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <base58.h>
+#include <keystore.h>
+#include <pubkey.h>
+#include <rpc/protocol.h>
+#include <rpc/util.h>
+#include <tinyformat.h>
+#include <utilstrencodings.h>
+
+// Converts a hex string to a public key if possible
+CPubKey HexToPubKey(const std::string& hex_in)
+{
+ if (!IsHex(hex_in)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in);
+ }
+ CPubKey vchPubKey(ParseHex(hex_in));
+ if (!vchPubKey.IsFullyValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in);
+ }
+ return vchPubKey;
+}
+
+// Retrieves a public key for an address from the given CKeyStore
+CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in)
+{
+ CTxDestination dest = DecodeDestination(addr_in);
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address: " + addr_in);
+ }
+ CKeyID key = GetKeyForDestination(*keystore, dest);
+ if (key.IsNull()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s does not refer to a key", addr_in));
+ }
+ CPubKey vchPubKey;
+ if (!keystore->GetPubKey(key, vchPubKey)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("no full public key for address %s", addr_in));
+ }
+ if (!vchPubKey.IsFullyValid()) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet contains an invalid public key");
+ }
+ return vchPubKey;
+}
+
+// Creates a multisig redeemscript from a given list of public keys and number required.
+CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys)
+{
+ // Gather public keys
+ if (required < 1) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "a multisignature address must require at least one key to redeem");
+ }
+ if ((int)pubkeys.size() < required) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("not enough keys supplied (got %u keys, but need at least %d to redeem)", pubkeys.size(), required));
+ }
+ if (pubkeys.size() > 16) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Number of keys involved in the multisignature address creation > 16\nReduce the number");
+ }
+
+ CScript result = GetScriptForMultisig(required, pubkeys);
+
+ if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE)));
+ }
+
+ return result;
+}
diff --git a/src/rpc/util.h b/src/rpc/util.h
new file mode 100644
index 0000000000..568a4260ba
--- /dev/null
+++ b/src/rpc/util.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2017 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_RPC_UTIL_H
+#define BITCOIN_RPC_UTIL_H
+
+#include <string>
+#include <vector>
+
+class CKeyStore;
+class CPubKey;
+class CScript;
+
+CPubKey HexToPubKey(const std::string& hex_in);
+CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in);
+CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys);
+
+#endif // BITCOIN_RPC_UTIL_H
diff --git a/src/util.cpp b/src/util.cpp
index 150bc503df..80eed24ffd 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -72,6 +72,7 @@
#include <boost/algorithm/string/case_conv.hpp> // for to_lower()
#include <boost/algorithm/string/predicate.hpp> // for startswith() and endswith()
+#include <boost/interprocess/sync/file_lock.hpp>
#include <boost/program_options/detail/config_file.hpp>
#include <boost/thread.hpp>
#include <openssl/crypto.h>
@@ -375,6 +376,27 @@ int LogPrintStr(const std::string &str)
return ret;
}
+bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only)
+{
+ fs::path pathLockFile = directory / lockfile_name;
+ FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist.
+ if (file) fclose(file);
+
+ try {
+ static std::map<std::string, boost::interprocess::file_lock> locks;
+ boost::interprocess::file_lock& lock = locks.emplace(pathLockFile.string(), pathLockFile.string().c_str()).first->second;
+ if (!lock.try_lock()) {
+ return false;
+ }
+ if (probe_only) {
+ lock.unlock();
+ }
+ } catch (const boost::interprocess::interprocess_exception& e) {
+ return error("Error while attempting to lock directory %s: %s", directory.string(), e.what());
+ }
+ return true;
+}
+
/** Interpret string as boolean, for argument parsing */
static bool InterpretBool(const std::string& strValue)
{
diff --git a/src/util.h b/src/util.h
index 6a0d6a31e7..277b4c66af 100644
--- a/src/util.h
+++ b/src/util.h
@@ -173,6 +173,7 @@ bool TruncateFile(FILE *file, unsigned int length);
int RaiseFileDescriptorLimit(int nMinFD);
void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length);
bool RenameOver(fs::path src, fs::path dest);
+bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false);
bool TryCreateDirectories(const fs::path& p);
fs::path GetDefaultDataDir();
const fs::path &GetDataDir(bool fNetSpecific = true);
diff --git a/src/validation.cpp b/src/validation.cpp
index 14d60bb269..8cee0dfac3 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1121,7 +1121,13 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus:
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
{
- if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), consensusParams))
+ CDiskBlockPos blockPos;
+ {
+ LOCK(cs_main);
+ blockPos = pindex->GetBlockPos();
+ }
+
+ if (!ReadBlockFromDisk(block, blockPos, consensusParams))
return false;
if (block.GetHash() != pindex->GetBlockHash())
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 35ff0e1eec..23c6279128 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -95,7 +95,7 @@ void CDBEnv::Close()
EnvShutdown();
}
-bool CDBEnv::Open(const fs::path& pathIn)
+bool CDBEnv::Open(const fs::path& pathIn, bool retry)
{
if (fDbEnvInit)
return true;
@@ -103,6 +103,11 @@ bool CDBEnv::Open(const fs::path& pathIn)
boost::this_thread::interruption_point();
strPath = pathIn.string();
+ if (!LockDirectory(pathIn, ".walletlock")) {
+ LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath);
+ return false;
+ }
+
fs::path pathLogDir = pathIn / "database";
TryCreateDirectories(pathLogDir);
fs::path pathErrorFile = pathIn / "db.log";
@@ -134,7 +139,24 @@ bool CDBEnv::Open(const fs::path& pathIn)
S_IRUSR | S_IWUSR);
if (ret != 0) {
dbenv->close(0);
- return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
+ LogPrintf("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
+ if (retry) {
+ // try moving the database env out of the way
+ fs::path pathDatabaseBak = pathIn / strprintf("database.%d.bak", GetTime());
+ try {
+ fs::rename(pathLogDir, pathDatabaseBak);
+ LogPrintf("Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string());
+ } catch (const fs::filesystem_error&) {
+ // failure is ok (well, not really, but it's not worse than what we started with)
+ }
+ // try opening it again one more time
+ if (!Open(pathIn, false)) {
+ // if it still fails, it probably means we can't even create the database env
+ return false;
+ }
+ } else {
+ return false;
+ }
}
fDbEnvInit = true;
@@ -269,25 +291,11 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle
return false;
}
- if (!bitdb.Open(walletDir))
- {
- // try moving the database env out of the way
- fs::path pathDatabase = walletDir / "database";
- fs::path pathDatabaseBak = walletDir / strprintf("database.%d.bak", GetTime());
- try {
- fs::rename(pathDatabase, pathDatabaseBak);
- LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
- } catch (const fs::filesystem_error&) {
- // failure is ok (well, not really, but it's not worse than what we started with)
- }
-
- // try again
- if (!bitdb.Open(walletDir)) {
- // if it still fails, it probably means we can't even create the database env
- errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
- return false;
- }
+ if (!bitdb.Open(walletDir, true)) {
+ errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
+ return false;
}
+
return true;
}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index c6f317927f..787135e400 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -68,7 +68,7 @@ public:
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
- bool Open(const fs::path& path);
+ bool Open(const fs::path& path, bool retry = 0);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string& strFile);
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 7d61d708a1..c7f19bc90a 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -207,11 +207,15 @@ bool VerifyWallets()
return true;
}
- if (gArgs.IsArgSet("-walletdir") && !fs::is_directory(GetWalletDir())) {
- if (fs::exists(fs::system_complete(gArgs.GetArg("-walletdir", "")))) {
- return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), gArgs.GetArg("-walletdir", "").c_str()));
+ if (gArgs.IsArgSet("-walletdir")) {
+ fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
+ if (!fs::exists(wallet_dir)) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string()));
+ } else if (!fs::is_directory(wallet_dir)) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string()));
+ } else if (!wallet_dir.is_absolute()) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string()));
}
- return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), gArgs.GetArg("-walletdir", "").c_str()));
}
LogPrintf("Using wallet directory %s\n", GetWalletDir().string());
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 645e776b68..0b021f9fe0 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -86,7 +86,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
"1. \"privkey\" (string, required) The private key (see dumpprivkey)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"\nExamples:\n"
"\nDump a private key\n"
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
@@ -101,61 +102,65 @@ UniValue importprivkey(const JSONRPCRequest& request)
);
- LOCK2(cs_main, pwallet->cs_wallet);
-
- EnsureWalletIsUnlocked(pwallet);
-
- std::string strSecret = request.params[0].get_str();
- std::string strLabel = "";
- if (!request.params[1].isNull())
- strLabel = request.params[1].get_str();
-
- // Whether to perform rescan after import
+ WalletRescanReserver reserver(pwallet);
bool fRescan = true;
- if (!request.params[2].isNull())
- fRescan = request.params[2].get_bool();
-
- if (fRescan && fPruneMode)
- throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- CBitcoinSecret vchSecret;
- bool fGood = vchSecret.SetString(strSecret);
+ EnsureWalletIsUnlocked(pwallet);
- if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
+ std::string strSecret = request.params[0].get_str();
+ std::string strLabel = "";
+ if (!request.params[1].isNull())
+ strLabel = request.params[1].get_str();
- CKey key = vchSecret.GetKey();
- if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
+ // Whether to perform rescan after import
+ if (!request.params[2].isNull())
+ fRescan = request.params[2].get_bool();
- CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
- CKeyID vchAddress = pubkey.GetID();
- {
- pwallet->MarkDirty();
+ if (fRescan && fPruneMode)
+ throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
- // We don't know which corresponding address will be used; label them all
- for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
- pwallet->SetAddressBook(dest, strLabel, "receive");
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
- // Don't throw error in case a key is already there
- if (pwallet->HaveKey(vchAddress)) {
- return NullUniValue;
- }
+ CBitcoinSecret vchSecret;
+ bool fGood = vchSecret.SetString(strSecret);
- pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
+ if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
- if (!pwallet->AddKeyPubKey(key, pubkey)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
- }
- pwallet->LearnAllRelatedScripts(pubkey);
+ CKey key = vchSecret.GetKey();
+ if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
- // whenever a key is imported, we need to scan the whole chain
- pwallet->UpdateTimeFirstKey(1);
+ CPubKey pubkey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubkey));
+ CKeyID vchAddress = pubkey.GetID();
+ {
+ pwallet->MarkDirty();
+ // We don't know which corresponding address will be used; label them all
+ for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
+ pwallet->SetAddressBook(dest, strLabel, "receive");
+ }
- if (fRescan) {
- pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
+ // Don't throw error in case a key is already there
+ if (pwallet->HaveKey(vchAddress)) {
+ return NullUniValue;
+ }
+
+ // whenever a key is imported, we need to scan the whole chain
+ pwallet->UpdateTimeFirstKey(1);
+ pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
+
+ if (!pwallet->AddKeyPubKey(key, pubkey)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
+ }
+ pwallet->LearnAllRelatedScripts(pubkey);
}
}
+ if (fRescan) {
+ pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
+ }
return NullUniValue;
}
@@ -237,7 +242,8 @@ UniValue importaddress(const JSONRPCRequest& request)
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"If you have the full public key, you should call importpubkey instead of this.\n"
"\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
"as change, and not show up in many RPCs.\n"
@@ -263,29 +269,35 @@ UniValue importaddress(const JSONRPCRequest& request)
if (fRescan && fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
// Whether to import a p2sh version, too
bool fP2SH = false;
if (!request.params[3].isNull())
fP2SH = request.params[3].get_bool();
- LOCK2(cs_main, pwallet->cs_wallet);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- CTxDestination dest = DecodeDestination(request.params[0].get_str());
- if (IsValidDestination(dest)) {
- if (fP2SH) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (IsValidDestination(dest)) {
+ if (fP2SH) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
+ }
+ ImportAddress(pwallet, dest, strLabel);
+ } else if (IsHex(request.params[0].get_str())) {
+ std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
+ ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
+ } else {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
}
- ImportAddress(pwallet, dest, strLabel);
- } else if (IsHex(request.params[0].get_str())) {
- std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
- ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
- } else {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
}
-
if (fRescan)
{
- pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
+ pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
pwallet->ReacceptWalletTransactions();
}
@@ -406,7 +418,8 @@ UniValue importpubkey(const JSONRPCRequest& request)
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"\nExamples:\n"
"\nImport a public key with rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
@@ -429,6 +442,11 @@ UniValue importpubkey(const JSONRPCRequest& request)
if (fRescan && fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
if (!IsHex(request.params[0].get_str()))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
@@ -436,17 +454,18 @@ UniValue importpubkey(const JSONRPCRequest& request)
if (!pubKey.IsFullyValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
- LOCK2(cs_main, pwallet->cs_wallet);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
- ImportAddress(pwallet, dest, strLabel);
+ for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
+ ImportAddress(pwallet, dest, strLabel);
+ }
+ ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
+ pwallet->LearnAllRelatedScripts(pubKey);
}
- ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
- pwallet->LearnAllRelatedScripts(pubKey);
-
if (fRescan)
{
- pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
+ pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
pwallet->ReacceptWalletTransactions();
}
@@ -479,91 +498,98 @@ UniValue importwallet(const JSONRPCRequest& request)
if (fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
- LOCK2(cs_main, pwallet->cs_wallet);
+ WalletRescanReserver reserver(pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
- EnsureWalletIsUnlocked(pwallet);
+ int64_t nTimeBegin = 0;
+ bool fGood = true;
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- std::ifstream file;
- file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
- if (!file.is_open())
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
+ EnsureWalletIsUnlocked(pwallet);
- int64_t nTimeBegin = chainActive.Tip()->GetBlockTime();
+ std::ifstream file;
+ file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
+ if (!file.is_open()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
+ }
+ nTimeBegin = chainActive.Tip()->GetBlockTime();
- bool fGood = true;
+ int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
+ file.seekg(0, file.beg);
- int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
- file.seekg(0, file.beg);
-
- pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
- while (file.good()) {
- pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
- std::string line;
- std::getline(file, line);
- if (line.empty() || line[0] == '#')
- continue;
-
- std::vector<std::string> vstr;
- boost::split(vstr, line, boost::is_any_of(" "));
- if (vstr.size() < 2)
- continue;
- CBitcoinSecret vchSecret;
- if (vchSecret.SetString(vstr[0])) {
- CKey key = vchSecret.GetKey();
- CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
- CKeyID keyid = pubkey.GetID();
- if (pwallet->HaveKey(keyid)) {
- LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
+ pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
+ while (file.good()) {
+ pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
+ std::string line;
+ std::getline(file, line);
+ if (line.empty() || line[0] == '#')
continue;
- }
- int64_t nTime = DecodeDumpTime(vstr[1]);
- std::string strLabel;
- bool fLabel = true;
- for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
- if (boost::algorithm::starts_with(vstr[nStr], "#"))
- break;
- if (vstr[nStr] == "change=1")
- fLabel = false;
- if (vstr[nStr] == "reserve=1")
- fLabel = false;
- if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
- strLabel = DecodeDumpString(vstr[nStr].substr(6));
- fLabel = true;
- }
- }
- LogPrintf("Importing %s...\n", EncodeDestination(keyid));
- if (!pwallet->AddKeyPubKey(key, pubkey)) {
- fGood = false;
+
+ std::vector<std::string> vstr;
+ boost::split(vstr, line, boost::is_any_of(" "));
+ if (vstr.size() < 2)
continue;
+ CBitcoinSecret vchSecret;
+ if (vchSecret.SetString(vstr[0])) {
+ CKey key = vchSecret.GetKey();
+ CPubKey pubkey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubkey));
+ CKeyID keyid = pubkey.GetID();
+ if (pwallet->HaveKey(keyid)) {
+ LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
+ continue;
+ }
+ int64_t nTime = DecodeDumpTime(vstr[1]);
+ std::string strLabel;
+ bool fLabel = true;
+ for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
+ if (boost::algorithm::starts_with(vstr[nStr], "#"))
+ break;
+ if (vstr[nStr] == "change=1")
+ fLabel = false;
+ if (vstr[nStr] == "reserve=1")
+ fLabel = false;
+ if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
+ strLabel = DecodeDumpString(vstr[nStr].substr(6));
+ fLabel = true;
+ }
+ }
+ LogPrintf("Importing %s...\n", EncodeDestination(keyid));
+ if (!pwallet->AddKeyPubKey(key, pubkey)) {
+ fGood = false;
+ continue;
+ }
+ pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
+ if (fLabel)
+ pwallet->SetAddressBook(keyid, strLabel, "receive");
+ nTimeBegin = std::min(nTimeBegin, nTime);
+ } else if(IsHex(vstr[0])) {
+ std::vector<unsigned char> vData(ParseHex(vstr[0]));
+ CScript script = CScript(vData.begin(), vData.end());
+ if (pwallet->HaveCScript(script)) {
+ LogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
+ continue;
+ }
+ if(!pwallet->AddCScript(script)) {
+ LogPrintf("Error importing script %s\n", vstr[0]);
+ fGood = false;
+ continue;
+ }
+ int64_t birth_time = DecodeDumpTime(vstr[1]);
+ if (birth_time > 0) {
+ pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time;
+ nTimeBegin = std::min(nTimeBegin, birth_time);
+ }
}
- pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
- if (fLabel)
- pwallet->SetAddressBook(keyid, strLabel, "receive");
- nTimeBegin = std::min(nTimeBegin, nTime);
- } else if(IsHex(vstr[0])) {
- std::vector<unsigned char> vData(ParseHex(vstr[0]));
- CScript script = CScript(vData.begin(), vData.end());
- if (pwallet->HaveCScript(script)) {
- LogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
- continue;
- }
- if(!pwallet->AddCScript(script)) {
- LogPrintf("Error importing script %s\n", vstr[0]);
- fGood = false;
- continue;
- }
- int64_t birth_time = DecodeDumpTime(vstr[1]);
- if (birth_time > 0) {
- pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time;
- nTimeBegin = std::min(nTimeBegin, birth_time);
- }
}
+ file.close();
+ pwallet->ShowProgress("", 100); // hide progress dialog in GUI
+ pwallet->UpdateTimeFirstKey(nTimeBegin);
}
- file.close();
- pwallet->ShowProgress("", 100); // hide progress dialog in GUI
- pwallet->UpdateTimeFirstKey(nTimeBegin);
- pwallet->RescanFromTime(nTimeBegin, false /* update */);
+ pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */);
pwallet->MarkDirty();
if (!fGood)
@@ -685,7 +711,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
file << "\n";
- // add the base58check encoded extended master if the wallet uses HD
+ // add the base58check encoded extended master if the wallet uses HD
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
if (!masterKeyID.IsNull())
{
@@ -1110,6 +1136,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
" {\n"
" \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n"
" }\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n"
"\nExamples:\n" +
HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
"{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
@@ -1135,49 +1163,55 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
}
}
- LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked(pwallet);
-
- // Verify all timestamps are present before importing any keys.
- const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
- for (const UniValue& data : requests.getValues()) {
- GetImportTimestamp(data, now);
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
+ int64_t now = 0;
bool fRunScan = false;
- const int64_t minimumTimestamp = 1;
int64_t nLowestTimestamp = 0;
-
- if (fRescan && chainActive.Tip()) {
- nLowestTimestamp = chainActive.Tip()->GetBlockTime();
- } else {
- fRescan = false;
- }
-
UniValue response(UniValue::VARR);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
- for (const UniValue& data : requests.getValues()) {
- const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
- const UniValue result = ProcessImport(pwallet, data, timestamp);
- response.push_back(result);
-
- if (!fRescan) {
- continue;
+ // Verify all timestamps are present before importing any keys.
+ now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
+ for (const UniValue& data : requests.getValues()) {
+ GetImportTimestamp(data, now);
}
- // If at least one request was successful then allow rescan.
- if (result["success"].get_bool()) {
- fRunScan = true;
+ const int64_t minimumTimestamp = 1;
+
+ if (fRescan && chainActive.Tip()) {
+ nLowestTimestamp = chainActive.Tip()->GetBlockTime();
+ } else {
+ fRescan = false;
}
- // Get the lowest timestamp.
- if (timestamp < nLowestTimestamp) {
- nLowestTimestamp = timestamp;
+ for (const UniValue& data : requests.getValues()) {
+ const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
+ const UniValue result = ProcessImport(pwallet, data, timestamp);
+ response.push_back(result);
+
+ if (!fRescan) {
+ continue;
+ }
+
+ // If at least one request was successful then allow rescan.
+ if (result["success"].get_bool()) {
+ fRunScan = true;
+ }
+
+ // Get the lowest timestamp.
+ if (timestamp < nLowestTimestamp) {
+ nLowestTimestamp = timestamp;
+ }
}
}
-
if (fRescan && fRunScan && requests.size()) {
- int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, true /* update */);
+ int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
pwallet->ReacceptWalletTransactions();
if (scannedTime > nLowestTimestamp) {
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 59653721c3..6849671dcf 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -18,6 +18,7 @@
#include <rpc/mining.h>
#include <rpc/safemode.h>
#include <rpc/server.h>
+#include <rpc/util.h>
#include <script/sign.h>
#include <timedata.h>
#include <util.h>
@@ -144,7 +145,7 @@ UniValue getnewaddress(const JSONRPCRequest& request)
"so payments received with the address will be credited to 'account'.\n"
"\nArguments:\n"
"1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n"
- "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -addresstype.\n"
+ "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
"\"address\" (string) The new bitcoin address\n"
"\nExamples:\n"
@@ -242,7 +243,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
"\nReturns a new Bitcoin address, for receiving change.\n"
"This is for use with raw transactions, NOT normal use.\n"
"\nArguments:\n"
- "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -changetype.\n"
+ "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
"\nResult:\n"
"\"address\" (string) The address\n"
"\nExamples:\n"
@@ -1161,9 +1162,6 @@ UniValue sendmany(const JSONRPCRequest& request)
return wtx.GetHash().GetHex();
}
-// Defined in rpc/misc.cpp
-extern CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params);
-
UniValue addmultisigaddress(const JSONRPCRequest& request)
{
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -1181,16 +1179,22 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
"If 'account' is specified (DEPRECATED), assign address to that account.\n"
"\nArguments:\n"
- "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
- "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
+ "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
+ "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
" [\n"
- " \"address\" (string) bitcoin address or hex-encoded public key\n"
+ " \"address\" (string) bitcoin address or hex-encoded public key\n"
" ...,\n"
" ]\n"
- "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
+ "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
"\nResult:\n"
- "\"address\" (string) A bitcoin address associated with the keys.\n"
+ "{\n"
+ " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n"
+ " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n"
+ "}\n"
+ "\nResult (DEPRECATED. To see this result in v0.16 instead, please start bitcoind with -deprecatedrpc=addmultisigaddress).\n"
+ " clients should transition to the new output api before upgrading to v0.17.\n"
+ "\"address\" (string) A bitcoin address associated with the keys.\n"
"\nExamples:\n"
"\nAdd a multisig address from 2 addresses\n"
@@ -1207,14 +1211,34 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
if (!request.params[2].isNull())
strAccount = AccountFromValue(request.params[2]);
+ int required = request.params[0].get_int();
+
+ // Get the public keys
+ const UniValue& keys_or_addrs = request.params[1].get_array();
+ std::vector<CPubKey> pubkeys;
+ for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) {
+ if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) {
+ pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
+ } else {
+ pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str()));
+ }
+ }
+
// Construct using pay-to-script-hash:
- CScript inner = _createmultisig_redeemScript(pwallet, request.params);
+ CScript inner = CreateMultisigRedeemscript(required, pubkeys);
pwallet->AddCScript(inner);
-
CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type);
-
pwallet->SetAddressBook(dest, strAccount, "send");
- return EncodeDestination(dest);
+
+ // Return old style interface
+ if (IsDeprecatedRPCEnabled("addmultisigaddress")) {
+ return EncodeDestination(dest);
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("address", EncodeDestination(dest));
+ result.pushKV("redeemScript", HexStr(inner.begin(), inner.end()));
+ return result;
}
class Witnessifier : public boost::static_visitor<bool>
@@ -1285,7 +1309,8 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
{
std::string msg = "addwitnessaddress \"address\" ( p2sh )\n"
- "\nAdd a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n"
+ "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"
+ "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n"
"It returns the witness script.\n"
"\nArguments:\n"
@@ -1299,6 +1324,12 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
throw std::runtime_error(msg);
}
+ if (!IsDeprecatedRPCEnabled("addwitnessaddress")) {
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. "
+ "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n"
+ "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n");
+ }
+
{
LOCK(cs_main);
if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !gArgs.GetBoolArg("-walletprematurewitness", false)) {
@@ -2283,7 +2314,8 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
"This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
"\nArguments:\n"
"1. \"passphrase\" (string, required) The wallet passphrase\n"
- "2. timeout (numeric, required) The time to keep the decryption key in seconds.\n"
+ "2. timeout (numeric, required) The time to keep the decryption key in seconds. Limited to at most 1073741824 (2^30) seconds.\n"
+ " Any value greater than 1073741824 seconds will be set to 1073741824 seconds.\n"
"\nNote:\n"
"Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
"time that overrides the old one.\n"
@@ -2312,6 +2344,17 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
strWalletPass = request.params[0].get_str().c_str();
+ // Get the timeout
+ int64_t nSleepTime = request.params[1].get_int64();
+ // Timeout cannot be negative, otherwise it will relock immediately
+ if (nSleepTime < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
+ }
+ // Clamp timeout to 2^30 seconds
+ if (nSleepTime > (int64_t)1 << 30) {
+ nSleepTime = (int64_t)1 << 30;
+ }
+
if (strWalletPass.length() > 0)
{
if (!pwallet->Unlock(strWalletPass)) {
@@ -2325,7 +2368,6 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
pwallet->TopUpKeyPool();
- int64_t nSleepTime = request.params[1].get_int64();
pwallet->nRelockTime = GetTime() + nSleepTime;
RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime);
@@ -3398,30 +3440,41 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
);
}
- LOCK2(cs_main, pwallet->cs_wallet);
+ WalletRescanReserver reserver(pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
- CBlockIndex *pindexStart = chainActive.Genesis();
+ CBlockIndex *pindexStart = nullptr;
CBlockIndex *pindexStop = nullptr;
- if (!request.params[0].isNull()) {
- pindexStart = chainActive[request.params[0].get_int()];
- if (!pindexStart) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
- }
- }
+ CBlockIndex *pChainTip = nullptr;
+ {
+ LOCK(cs_main);
+ pindexStart = chainActive.Genesis();
+ pChainTip = chainActive.Tip();
- if (!request.params[1].isNull()) {
- pindexStop = chainActive[request.params[1].get_int()];
- if (!pindexStop) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
+ if (!request.params[0].isNull()) {
+ pindexStart = chainActive[request.params[0].get_int()];
+ if (!pindexStart) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
+ }
}
- else if (pindexStop->nHeight < pindexStart->nHeight) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
+
+ if (!request.params[1].isNull()) {
+ pindexStop = chainActive[request.params[1].get_int()];
+ if (!pindexStop) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
+ }
+ else if (pindexStop->nHeight < pindexStart->nHeight) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
+ }
}
}
// We can't rescan beyond non-pruned blocks, stop and throw an error
if (fPruneMode) {
- CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip();
+ LOCK(cs_main);
+ CBlockIndex *block = pindexStop ? pindexStop : pChainTip;
while (block && block->nHeight >= pindexStart->nHeight) {
if (!(block->nStatus & BLOCK_HAVE_DATA)) {
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
@@ -3430,18 +3483,17 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
}
}
- CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true);
+ CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true);
if (!stopBlock) {
if (pwallet->IsAbortingRescan()) {
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
}
// if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
- stopBlock = pindexStop ? pindexStop : chainActive.Tip();
+ stopBlock = pindexStop ? pindexStop : pChainTip;
}
else {
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
}
-
UniValue response(UniValue::VOBJ);
response.pushKV("start_height", pindexStart->nHeight);
response.pushKV("stop_height", stopBlock->nHeight);
@@ -3468,7 +3520,7 @@ static const CRPCCommand commands[] =
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
{ "wallet", "abortrescan", &abortrescan, {} },
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account"} },
- { "wallet", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
+ { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 7b3c283f37..7e0881afd7 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -384,7 +384,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
{
CWallet wallet;
AddKey(wallet, coinbaseKey);
- BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr));
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
}
@@ -397,7 +399,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
{
CWallet wallet;
AddKey(wallet, coinbaseKey);
- BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr));
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
}
@@ -608,7 +612,9 @@ public:
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
- wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr);
+ WalletRescanReserver reserver(wallet.get());
+ reserver.reserve();
+ wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver);
}
~ListCoinsTestingSetup()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index c804338952..7fb26900e1 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1612,19 +1612,20 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
* @return Earliest timestamp that could be successfully scanned from. Timestamp
* returned will be higher than startTime if relevant blocks could not be read.
*/
-int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
+int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update)
{
- AssertLockHeld(cs_main);
- AssertLockHeld(cs_wallet);
-
// Find starting block. May be null if nCreateTime is greater than the
// highest blockchain timestamp, in which case there is nothing that needs
// to be scanned.
- CBlockIndex* const startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
- LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
+ CBlockIndex* startBlock = nullptr;
+ {
+ LOCK(cs_main);
+ startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
+ LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
+ }
if (startBlock) {
- const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update);
+ const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
if (failedBlock) {
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
}
@@ -1643,12 +1644,17 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
*
* If pindexStop is not a nullptr, the scan will stop at the block-index
* defined by pindexStop
+ *
+ * Caller needs to make sure pindexStop (and the optional pindexStart) are on
+ * the main chain after to the addition of any new keys you want to detect
+ * transactions for.
*/
-CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate)
+CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate)
{
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
+ assert(reserver.isReserved());
if (pindexStop) {
assert(pindexStop->nHeight >= pindexStart->nHeight);
}
@@ -1656,24 +1662,42 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
CBlockIndex* pindex = pindexStart;
CBlockIndex* ret = nullptr;
{
- LOCK2(cs_main, cs_wallet);
fAbortRescan = false;
- fScanningWallet = true;
-
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
- double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
- double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
+ CBlockIndex* tip = nullptr;
+ double dProgressStart;
+ double dProgressTip;
+ {
+ LOCK(cs_main);
+ tip = chainActive.Tip();
+ dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
+ dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
+ }
while (pindex && !fAbortRescan)
{
- if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
- ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((GuessVerificationProgress(chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) {
+ double gvp = 0;
+ {
+ LOCK(cs_main);
+ gvp = GuessVerificationProgress(chainParams.TxData(), pindex);
+ }
+ ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ }
if (GetTime() >= nNow + 60) {
nNow = GetTime();
+ LOCK(cs_main);
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
}
CBlock block;
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
+ LOCK2(cs_main, cs_wallet);
+ if (pindex && !chainActive.Contains(pindex)) {
+ // Abort scan if current block is no longer active, to prevent
+ // marking transactions as coming from the wrong block.
+ ret = pindex;
+ break;
+ }
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
}
@@ -1683,14 +1707,20 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
if (pindex == pindexStop) {
break;
}
- pindex = chainActive.Next(pindex);
+ {
+ LOCK(cs_main);
+ pindex = chainActive.Next(pindex);
+ if (tip != chainActive.Tip()) {
+ tip = chainActive.Tip();
+ // in case the tip has changed, update progress max
+ dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
+ }
+ }
}
if (pindex && fAbortRescan) {
LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
}
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
-
- fScanningWallet = false;
}
return ret;
}
@@ -4031,7 +4061,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
}
nStart = GetTimeMillis();
- walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true);
+ {
+ WalletRescanReserver reserver(walletInstance);
+ if (!reserver.reserve()) {
+ InitError(_("Failed to rescan the wallet during initialization"));
+ return nullptr;
+ }
+ walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
+ }
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
walletInstance->SetBestChain(chainActive.GetLocator());
walletInstance->dbw->IncrementUpdateCounter();
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index cc350853ea..e8f536634e 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -99,7 +99,7 @@ enum WalletFeature
FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version
};
-enum OutputType
+enum OutputType : int
{
OUTPUT_TYPE_NONE,
OUTPUT_TYPE_LEGACY,
@@ -659,6 +659,7 @@ private:
};
+class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime
/**
* A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
* and provides the ability to create new transactions.
@@ -668,7 +669,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
private:
static std::atomic<bool> fFlushScheduled;
std::atomic<bool> fAbortRescan;
- std::atomic<bool> fScanningWallet;
+ std::atomic<bool> fScanningWallet; //controlled by WalletRescanReserver
+ std::mutex mutexScanning;
+ friend class WalletRescanReserver;
+
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
@@ -945,8 +949,8 @@ public:
void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
- int64_t RescanFromTime(int64_t startTime, bool update);
- CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate = false);
+ int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
+ CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false);
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
@@ -1265,4 +1269,39 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType);
/** Get all destinations (potentially) supported by the wallet for the given key. */
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);
+/** RAII object to check and reserve a wallet rescan */
+class WalletRescanReserver
+{
+private:
+ CWalletRef m_wallet;
+ bool m_could_reserve;
+public:
+ explicit WalletRescanReserver(CWalletRef w) : m_wallet(w), m_could_reserve(false) {}
+
+ bool reserve()
+ {
+ assert(!m_could_reserve);
+ std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
+ if (m_wallet->fScanningWallet) {
+ return false;
+ }
+ m_wallet->fScanningWallet = true;
+ m_could_reserve = true;
+ return true;
+ }
+
+ bool isReserved() const
+ {
+ return (m_could_reserve && m_wallet->fScanningWallet);
+ }
+
+ ~WalletRescanReserver()
+ {
+ std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
+ if (m_could_reserve) {
+ m_wallet->fScanningWallet = false;
+ }
+ }
+};
+
#endif // BITCOIN_WALLET_WALLET_H
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index f15e5de1e2..7c97b668ae 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -9,7 +9,7 @@ fs::path GetWalletDir()
fs::path path;
if (gArgs.IsArgSet("-walletdir")) {
- path = fs::system_complete(gArgs.GetArg("-walletdir", ""));
+ path = gArgs.GetArg("-walletdir", "");
if (!fs::is_directory(path)) {
// If the path specified doesn't exist, we return the deliberately
// invalid empty string.
diff --git a/test/README.md b/test/README.md
index 868eb667ae..b59c8db4e5 100644
--- a/test/README.md
+++ b/test/README.md
@@ -8,7 +8,7 @@ There are currently two sets of tests in this directory:
- [functional](/test/functional) which test the functionality of
bitcoind and bitcoin-qt by interacting with them through the RPC and P2P
interfaces.
-- [util](test/util) which tests the bitcoin utilities, currently only
+- [util](/test/util) which tests the bitcoin utilities, currently only
bitcoin-tx.
The util tests are run as part of `make check` target. The functional
diff --git a/test/functional/address_types.py b/test/functional/address_types.py
index 32c561aae3..38a3425214 100755
--- a/test/functional/address_types.py
+++ b/test/functional/address_types.py
@@ -171,14 +171,14 @@ class AddressTypeTest(BitcoinTestFramework):
# addmultisigaddress with at least 1 uncompressed key should return a legacy address.
for node in range(4):
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2]), True, 'legacy')
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2]), True, 'legacy')
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2]), True, 'legacy')
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2])['address'], True, 'legacy')
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2])['address'], True, 'legacy')
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2])['address'], True, 'legacy')
# addmultisigaddress with all compressed keys should return the appropriate address type (even when the keys are not ours).
- self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'legacy')
- self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'p2sh-segwit')
- self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'p2sh-segwit')
- self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'bech32')
+ self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'legacy')
+ self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
+ self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
+ self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'bech32')
for explicit_type, multisig, from_node in itertools.product([False, True], [False, True], range(4)):
address_type = None
@@ -209,7 +209,7 @@ class AddressTypeTest(BitcoinTestFramework):
else:
addr1 = self.nodes[to_node].getnewaddress()
addr2 = self.nodes[to_node].getnewaddress()
- address = self.nodes[to_node].addmultisigaddress(2, [addr1, addr2])
+ address = self.nodes[to_node].addmultisigaddress(2, [addr1, addr2])['address']
# Do some sanity checking on the created address
if address_type is not None:
diff --git a/test/functional/bitcoin_cli.py b/test/functional/bitcoin_cli.py
index d1cd3b3620..d8c80ab34f 100755
--- a/test/functional/bitcoin_cli.py
+++ b/test/functional/bitcoin_cli.py
@@ -39,7 +39,7 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)
self.log.info("Compare responses from `bitcoin-cli -getinfo` and the RPCs data is retrieved from.")
- cli_get_info = self.nodes[0].cli().send_cli('-getinfo')
+ cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
wallet_info = self.nodes[0].getwalletinfo()
network_info = self.nodes[0].getnetworkinfo()
blockchain_info = self.nodes[0].getblockchaininfo()
diff --git a/test/functional/bumpfee.py b/test/functional/bumpfee.py
index 1e5620736b..2cd4127854 100755
--- a/test/functional/bumpfee.py
+++ b/test/functional/bumpfee.py
@@ -33,7 +33,7 @@ class BumpFeeTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
- self.extra_args = [["-prematurewitness", "-walletprematurewitness", "-walletrbf={}".format(i)]
+ self.extra_args = [["-prematurewitness", "-walletprematurewitness", "-deprecatedrpc=addwitnessaddress", "-walletrbf={}".format(i)]
for i in range(self.num_nodes)]
def run_test(self):
diff --git a/test/functional/deprecated_rpc.py b/test/functional/deprecated_rpc.py
index 19fd24edb9..d6f25158ef 100755
--- a/test/functional/deprecated_rpc.py
+++ b/test/functional/deprecated_rpc.py
@@ -10,7 +10,7 @@ class DeprecatedRpcTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
- self.extra_args = [[], ["-deprecatedrpc=estimatefee"]]
+ self.extra_args = [[], ["-deprecatedrpc=estimatefee", "-deprecatedrpc=createmultisig"]]
def run_test(self):
self.log.info("estimatefee: Shows deprecated message")
@@ -19,5 +19,9 @@ class DeprecatedRpcTest(BitcoinTestFramework):
self.log.info("Using -deprecatedrpc=estimatefee bypasses the error")
self.nodes[1].estimatefee(1)
+ self.log.info("Make sure that -deprecatedrpc=createmultisig allows it to take addresses")
+ assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, [self.nodes[0].getnewaddress()])
+ self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
+
if __name__ == '__main__':
DeprecatedRpcTest().main()
diff --git a/test/functional/fundrawtransaction.py b/test/functional/fundrawtransaction.py
index 4739e73c39..b3d6549229 100755
--- a/test/functional/fundrawtransaction.py
+++ b/test/functional/fundrawtransaction.py
@@ -358,7 +358,7 @@ class RawTransactionsTest(BitcoinTestFramework):
addr1Obj = self.nodes[1].validateaddress(addr1)
addr2Obj = self.nodes[1].validateaddress(addr2)
- mSigObj = self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ mSigObj = self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
inputs = []
outputs = {mSigObj:1.1}
@@ -391,7 +391,7 @@ class RawTransactionsTest(BitcoinTestFramework):
addr4Obj = self.nodes[1].validateaddress(addr4)
addr5Obj = self.nodes[1].validateaddress(addr5)
- mSigObj = self.nodes[1].addmultisigaddress(4, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey']])
+ mSigObj = self.nodes[1].addmultisigaddress(4, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey']])['address']
inputs = []
outputs = {mSigObj:1.1}
@@ -418,7 +418,7 @@ class RawTransactionsTest(BitcoinTestFramework):
addr1Obj = self.nodes[2].validateaddress(addr1)
addr2Obj = self.nodes[2].validateaddress(addr2)
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
# send 1.2 BTC to msig addr
diff --git a/test/functional/importmulti.py b/test/functional/importmulti.py
index ab4ca48e48..be9be83839 100755
--- a/test/functional/importmulti.py
+++ b/test/functional/importmulti.py
@@ -228,7 +228,7 @@ class ImportMultiTest (BitcoinTestFramework):
sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
- multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']])
+ multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']])
self.nodes[1].generate(100)
transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00)
self.nodes[1].generate(1)
@@ -255,7 +255,7 @@ class ImportMultiTest (BitcoinTestFramework):
sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
- multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']])
+ multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']])
self.nodes[1].generate(100)
transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00)
self.nodes[1].generate(1)
@@ -282,7 +282,7 @@ class ImportMultiTest (BitcoinTestFramework):
sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
- multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']])
+ multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']])
self.nodes[1].generate(100)
transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00)
self.nodes[1].generate(1)
@@ -309,7 +309,7 @@ class ImportMultiTest (BitcoinTestFramework):
sig_address_1 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
sig_address_3 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
- multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['address'], sig_address_2['address'], sig_address_3['pubkey']])
+ multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']])
self.nodes[1].generate(100)
transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00)
self.nodes[1].generate(1)
diff --git a/test/functional/listtransactions.py b/test/functional/listtransactions.py
index cbed065928..ba71ac0967 100755
--- a/test/functional/listtransactions.py
+++ b/test/functional/listtransactions.py
@@ -81,7 +81,8 @@ class ListTransactionsTest(BitcoinTestFramework):
{"category":"receive","amount":Decimal("0.44")},
{"txid":txid, "account" : "toself"} )
- multisig = self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
+ pubkey = self.nodes[1].validateaddress(self.nodes[1].getnewaddress())['pubkey']
+ multisig = self.nodes[1].createmultisig(1, [pubkey])
self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
self.nodes[1].generate(1)
diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py
index 12d9e9f48d..b07e451667 100755
--- a/test/functional/multiwallet.py
+++ b/test/functional/multiwallet.py
@@ -15,8 +15,8 @@ from test_framework.util import assert_equal, assert_raises_rpc_error
class MultiWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
- self.num_nodes = 1
- self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3', '-wallet=w']]
+ self.num_nodes = 2
+ self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3', '-wallet=w'], []]
self.supports_cli = True
def run_test(self):
@@ -28,7 +28,11 @@ class MultiWalletTest(BitcoinTestFramework):
assert_equal(set(node.listwallets()), {"w1", "w2", "w3", "w"})
- self.stop_node(0)
+ self.stop_nodes()
+
+ self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
+ self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
+ self.assert_start_raises_init_error(0, ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir())
# should not initialize if there are duplicate wallets
self.assert_start_raises_init_error(0, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.')
@@ -59,19 +63,21 @@ class MultiWalletTest(BitcoinTestFramework):
assert_equal(set(node.listwallets()), {"w4", "w5"})
w5 = wallet("w5")
w5.generate(1)
- self.stop_node(0)
# now if wallets/ exists again, but the rootdir is specified as the walletdir, w4 and w5 should still be loaded
os.rename(wallet_dir2, wallet_dir())
- self.start_node(0, ['-wallet=w4', '-wallet=w5', '-walletdir=' + data_dir()])
+ self.restart_node(0, ['-wallet=w4', '-wallet=w5', '-walletdir=' + data_dir()])
assert_equal(set(node.listwallets()), {"w4", "w5"})
w5 = wallet("w5")
w5_info = w5.getwalletinfo()
assert_equal(w5_info['immature_balance'], 50)
- self.stop_node(0)
+ competing_wallet_dir = os.path.join(self.options.tmpdir, 'competing_walletdir')
+ os.mkdir(competing_wallet_dir)
+ self.restart_node(0, ['-walletdir='+competing_wallet_dir])
+ self.assert_start_raises_init_error(1, ['-walletdir='+competing_wallet_dir], 'Error initializing wallet database environment')
- self.start_node(0, self.extra_args[0])
+ self.restart_node(0, self.extra_args[0])
w1 = wallet("w1")
w2 = wallet("w2")
diff --git a/test/functional/nulldummy.py b/test/functional/nulldummy.py
index 110b7a655e..fac620bc8d 100755
--- a/test/functional/nulldummy.py
+++ b/test/functional/nulldummy.py
@@ -42,11 +42,11 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.setup_clean_chain = True
# This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through
# normal segwit activation here (and don't use the default always-on behaviour).
- self.extra_args = [['-whitelist=127.0.0.1', '-walletprematurewitness', '-vbparams=segwit:0:999999999999', '-addresstype=legacy']]
+ self.extra_args = [['-whitelist=127.0.0.1', '-walletprematurewitness', '-vbparams=segwit:0:999999999999', '-addresstype=legacy', "-deprecatedrpc=addwitnessaddress"]]
def run_test(self):
self.address = self.nodes[0].getnewaddress()
- self.ms_address = self.nodes[0].addmultisigaddress(1,[self.address])
+ self.ms_address = self.nodes[0].addmultisigaddress(1,[self.address])['address']
self.wit_address = self.nodes[0].addwitnessaddress(self.address)
self.wit_ms_address = self.nodes[0].addwitnessaddress(self.ms_address)
diff --git a/test/functional/p2p-compactblocks.py b/test/functional/p2p-compactblocks.py
index e98ae31a89..d9f461a049 100755
--- a/test/functional/p2p-compactblocks.py
+++ b/test/functional/p2p-compactblocks.py
@@ -95,7 +95,7 @@ class CompactBlocksTest(BitcoinTestFramework):
self.num_nodes = 2
# This test was written assuming SegWit is activated using BIP9 at height 432 (3x confirmation window).
# TODO: Rewrite this test to support SegWit being always active.
- self.extra_args = [["-vbparams=segwit:0:0"], ["-vbparams=segwit:0:999999999999", "-txindex"]]
+ self.extra_args = [["-vbparams=segwit:0:0"], ["-vbparams=segwit:0:999999999999", "-txindex", "-deprecatedrpc=addwitnessaddress"]]
self.utxos = []
def build_block_on_tip(self, node, segwit=False):
diff --git a/test/functional/rawtransactions.py b/test/functional/rawtransactions.py
index fe749adb49..d39d86b310 100755
--- a/test/functional/rawtransactions.py
+++ b/test/functional/rawtransactions.py
@@ -145,7 +145,12 @@ class RawTransactionsTest(BitcoinTestFramework):
addr1Obj = self.nodes[2].validateaddress(addr1)
addr2Obj = self.nodes[2].validateaddress(addr2)
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ # Tests for createmultisig and addmultisigaddress
+ assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"])
+ self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # createmultisig can only take public keys
+ assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) # addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here.
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
#use balance deltas instead of absolute values
bal = self.nodes[2].getbalance()
@@ -168,7 +173,7 @@ class RawTransactionsTest(BitcoinTestFramework):
addr2Obj = self.nodes[2].validateaddress(addr2)
addr3Obj = self.nodes[2].validateaddress(addr3)
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address']
txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
decTx = self.nodes[0].gettransaction(txId)
@@ -213,8 +218,8 @@ class RawTransactionsTest(BitcoinTestFramework):
addr1Obj = self.nodes[1].validateaddress(addr1)
addr2Obj = self.nodes[2].validateaddress(addr2)
- self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+ self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address']
mSigObjValid = self.nodes[2].validateaddress(mSigObj)
txId = self.nodes[0].sendtoaddress(mSigObj, 2.2)
diff --git a/test/functional/segwit.py b/test/functional/segwit.py
index 7d5c760ad9..931ebdea39 100755
--- a/test/functional/segwit.py
+++ b/test/functional/segwit.py
@@ -40,9 +40,9 @@ class SegWitTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 3
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
- self.extra_args = [["-walletprematurewitness", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"],
- ["-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"],
- ["-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"]]
+ self.extra_args = [["-walletprematurewitness", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"],
+ ["-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"],
+ ["-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"]]
def setup_network(self):
super().setup_network()
@@ -95,7 +95,7 @@ class SegWitTest(BitcoinTestFramework):
for i in range(3):
newaddress = self.nodes[i].getnewaddress()
self.pubkey.append(self.nodes[i].validateaddress(newaddress)["pubkey"])
- multiaddress = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]])
+ multiaddress = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]])['address']
multiscript = CScript([OP_1, hex_str_to_bytes(self.pubkey[-1]), OP_1, OP_CHECKMULTISIG])
p2sh_addr = self.nodes[i].addwitnessaddress(newaddress)
bip173_addr = self.nodes[i].addwitnessaddress(newaddress, False)
@@ -290,19 +290,19 @@ class SegWitTest(BitcoinTestFramework):
solvable_anytime = [] # These outputs should be solvable after importpubkey
unseen_anytime = [] # These outputs should never be seen
- uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]]))
- uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]]))
- compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]]))
- uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], uncompressed_solvable_address[0]]))
- compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]]))
- compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], compressed_solvable_address[1]]))
+ uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]])['address'])
+ uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]])['address'])
+ compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]])['address'])
+ uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], uncompressed_solvable_address[0]])['address'])
+ compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])['address'])
+ compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], compressed_solvable_address[1]])['address'])
unknown_address = ["mtKKyoHabkk6e4ppT7NaM7THqPUt7AzPrT", "2NDP3jLWAFT8NDAiUa9qiE6oBt2awmMq7Dx"]
# Test multisig_without_privkey
# We have 2 public keys without private keys, use addmultisigaddress to add to wallet.
# Money sent to P2SH of multisig of this should only be seen after importaddress with the BASE58 P2SH address.
- multisig_without_privkey_address = self.nodes[0].addmultisigaddress(2, [pubkeys[3], pubkeys[4]])
+ multisig_without_privkey_address = self.nodes[0].addmultisigaddress(2, [pubkeys[3], pubkeys[4]])['address']
script = CScript([OP_2, hex_str_to_bytes(pubkeys[3]), hex_str_to_bytes(pubkeys[4]), OP_2, OP_CHECKMULTISIG])
solvable_after_importaddress.append(CScript([OP_HASH160, hash160(script), OP_EQUAL]))
@@ -463,11 +463,11 @@ class SegWitTest(BitcoinTestFramework):
solvable_anytime = [] # These outputs should be solvable after importpubkey
unseen_anytime = [] # These outputs should never be seen
- uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]]))
- uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]]))
- compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]]))
- uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], uncompressed_solvable_address[0]]))
- compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]]))
+ uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]])['address'])
+ uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]])['address'])
+ compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]])['address'])
+ uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], uncompressed_solvable_address[0]])['address'])
+ compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])['address'])
premature_witaddress = []
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 5d4f8e6720..f8d66def64 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -220,18 +220,18 @@ class BitcoinTestFramework():
for i in range(num_nodes):
self.nodes.append(TestNode(i, self.options.tmpdir, extra_args[i], rpchost, timewait=timewait, binary=binary[i], stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, use_cli=self.options.usecli))
- def start_node(self, i, extra_args=None, stderr=None):
+ def start_node(self, i, *args, **kwargs):
"""Start a bitcoind"""
node = self.nodes[i]
- node.start(extra_args, stderr)
+ node.start(*args, **kwargs)
node.wait_for_rpc_connection()
if self.options.coveragedir is not None:
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
- def start_nodes(self, extra_args=None):
+ def start_nodes(self, extra_args=None, *args, **kwargs):
"""Start multiple bitcoinds"""
if extra_args is None:
@@ -239,7 +239,7 @@ class BitcoinTestFramework():
assert_equal(len(extra_args), self.num_nodes)
try:
for i, node in enumerate(self.nodes):
- node.start(extra_args[i])
+ node.start(extra_args[i], *args, **kwargs)
for node in self.nodes:
node.wait_for_rpc_connection()
except:
@@ -271,10 +271,10 @@ class BitcoinTestFramework():
self.stop_node(i)
self.start_node(i, extra_args)
- def assert_start_raises_init_error(self, i, extra_args=None, expected_msg=None):
+ def assert_start_raises_init_error(self, i, extra_args=None, expected_msg=None, *args, **kwargs):
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
try:
- self.start_node(i, extra_args, stderr=log_stderr)
+ self.start_node(i, extra_args, stderr=log_stderr, *args, **kwargs)
self.stop_node(i)
except Exception as e:
assert 'bitcoind exited' in str(e) # node must have shutdown
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 589a8f3969..1054e6d028 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -81,13 +81,13 @@ class TestNode():
assert self.rpc_connected and self.rpc is not None, "Error: no RPC connection"
return getattr(self.rpc, name)
- def start(self, extra_args=None, stderr=None):
+ def start(self, extra_args=None, stderr=None, *args, **kwargs):
"""Start the node."""
if extra_args is None:
extra_args = self.extra_args
if stderr is None:
stderr = self.stderr
- self.process = subprocess.Popen(self.args + extra_args, stderr=stderr)
+ self.process = subprocess.Popen(self.args + extra_args, stderr=stderr, *args, **kwargs)
self.running = True
self.log.debug("bitcoind started, waiting for RPC to come up")
@@ -213,16 +213,16 @@ class TestNodeCLI():
"""Interface to bitcoin-cli for an individual node"""
def __init__(self, binary, datadir):
- self.args = []
+ self.options = []
self.binary = binary
self.datadir = datadir
self.input = None
self.log = logging.getLogger('TestFramework.bitcoincli')
- def __call__(self, *args, input=None):
- # TestNodeCLI is callable with bitcoin-cli command-line args
+ def __call__(self, *options, input=None):
+ # TestNodeCLI is callable with bitcoin-cli command-line options
cli = TestNodeCLI(self.binary, self.datadir)
- cli.args = [str(arg) for arg in args]
+ cli.options = [str(o) for o in options]
cli.input = input
return cli
@@ -238,16 +238,18 @@ class TestNodeCLI():
results.append(dict(error=e))
return results
- def send_cli(self, command, *args, **kwargs):
+ def send_cli(self, command=None, *args, **kwargs):
"""Run bitcoin-cli command. Deserializes returned string as python object."""
pos_args = [str(arg) for arg in args]
named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()]
assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call"
- p_args = [self.binary, "-datadir=" + self.datadir] + self.args
+ p_args = [self.binary, "-datadir=" + self.datadir] + self.options
if named_args:
p_args += ["-named"]
- p_args += [command] + pos_args + named_args
+ if command is not None:
+ p_args += [command]
+ p_args += pos_args + named_args
self.log.debug("Running bitcoin-cli command: %s" % command)
process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
cli_stdout, cli_stderr = process.communicate(input=self.input)
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 664dc6d0af..7fdc171332 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -390,7 +390,7 @@ def sync_chain(rpc_connections, *, wait=1, timeout=60):
timeout -= wait
raise AssertionError("Chain sync failed: Best block hashes don't match")
-def sync_mempools(rpc_connections, *, wait=1, timeout=60):
+def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True):
"""
Wait until everybody has the same transactions in their memory
pools
@@ -402,6 +402,9 @@ def sync_mempools(rpc_connections, *, wait=1, timeout=60):
if set(rpc_connections[i].getrawmempool()) == pool:
num_match = num_match + 1
if num_match == len(rpc_connections):
+ if flush_scheduler:
+ for r in rpc_connections:
+ r.syncwithvalidationinterfacequeue()
return
time.sleep(wait)
timeout -= wait
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 72ad300e7e..b8b6ee98bf 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -313,9 +313,9 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove
# Populate cache
try:
subprocess.check_output([tests_dir + 'create_cache.py'] + flags + ["--tmpdir=%s/cache" % tmpdir])
- except Exception as e:
- print(e.output)
- raise e
+ except subprocess.CalledProcessError as e:
+ sys.stdout.buffer.write(e.output)
+ raise
#Run Tests
job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags)
diff --git a/test/functional/wallet-accounts.py b/test/functional/wallet-accounts.py
index 287436877f..ecd1cfc82b 100755
--- a/test/functional/wallet-accounts.py
+++ b/test/functional/wallet-accounts.py
@@ -126,7 +126,7 @@ class WalletAccountsTest(BitcoinTestFramework):
addresses = []
for x in range(10):
addresses.append(node.getnewaddress())
- multisig_address = node.addmultisigaddress(5, addresses, account.name)
+ multisig_address = node.addmultisigaddress(5, addresses, account.name)['address']
account.add_address(multisig_address)
account.verify(node)
node.sendfrom("", multisig_address, 50)
diff --git a/test/functional/wallet-dump.py b/test/functional/wallet-dump.py
index 8b81c81ba8..77f90ffb81 100755
--- a/test/functional/wallet-dump.py
+++ b/test/functional/wallet-dump.py
@@ -69,7 +69,7 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old):
class WalletDumpTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.extra_args = [["-keypool=90", "-addresstype=legacy"]]
+ self.extra_args = [["-keypool=90", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"]]
def setup_network(self, split=False):
# Use 1 minute timeout because the initial getnewaddress RPC can take
@@ -94,7 +94,7 @@ class WalletDumpTest(BitcoinTestFramework):
# Test scripts dump by adding a P2SH witness and a 1-of-1 multisig address
witness_addr = self.nodes[0].addwitnessaddress(addrs[0]["address"], True)
- multisig_addr = self.nodes[0].addmultisigaddress(1, [addrs[1]["address"]])
+ multisig_addr = self.nodes[0].addmultisigaddress(1, [addrs[1]["address"]])["address"]
script_addrs = [witness_addr, multisig_addr]
# dump unencrypted wallet
diff --git a/test/functional/wallet-encryption.py b/test/functional/wallet-encryption.py
index 452e8ec291..3c927ee484 100755
--- a/test/functional/wallet-encryption.py
+++ b/test/functional/wallet-encryption.py
@@ -10,6 +10,8 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
+ assert_greater_than,
+ assert_greater_than_or_equal,
)
class WalletEncryptionTest(BitcoinTestFramework):
@@ -56,6 +58,23 @@ class WalletEncryptionTest(BitcoinTestFramework):
assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", self.nodes[0].walletpassphrase, passphrase, 10)
self.nodes[0].walletpassphrase(passphrase2, 10)
assert_equal(privkey, self.nodes[0].dumpprivkey(address))
+ self.nodes[0].walletlock()
+
+ # Test timeout bounds
+ assert_raises_rpc_error(-8, "Timeout cannot be negative.", self.nodes[0].walletpassphrase, passphrase2, -10)
+ # Check the timeout
+ # Check a time less than the limit
+ expected_time = int(time.time()) + (1 << 30) - 600
+ self.nodes[0].walletpassphrase(passphrase2, (1 << 30) - 600)
+ actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
+ assert_greater_than_or_equal(actual_time, expected_time)
+ assert_greater_than(expected_time + 5, actual_time) # 5 second buffer
+ # Check a time greater than the limit
+ expected_time = int(time.time()) + (1 << 30) - 1
+ self.nodes[0].walletpassphrase(passphrase2, (1 << 33))
+ actual_time = self.nodes[0].getwalletinfo()['unlocked_until']
+ assert_greater_than_or_equal(actual_time, expected_time)
+ assert_greater_than(expected_time + 5, actual_time) # 5 second buffer
if __name__ == '__main__':
WalletEncryptionTest().main()
diff --git a/test/functional/zapwallettxes.py b/test/functional/zapwallettxes.py
index 0471f2afb3..08afb87894 100755
--- a/test/functional/zapwallettxes.py
+++ b/test/functional/zapwallettxes.py
@@ -10,7 +10,7 @@
transactions are still available.
- restart node 0 with zapwallettxes and persistmempool, and verify that both
the confirmed and the unconfirmed transactions are still available.
-- restart node 0 with just zapwallettxed and verify that the confirmed
+- restart node 0 with just zapwallettxes and verify that the confirmed
transactions are still available, but that the unconfirmed transaction has
been zapped.
"""