aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--depends/packages/native_mac_alias.mk9
-rw-r--r--depends/patches/native_mac_alias/python3.patch56
-rw-r--r--doc/build-windows.md7
-rw-r--r--doc/release-notes.md7
-rw-r--r--src/clientversion.cpp2
-rw-r--r--src/init.cpp2
-rw-r--r--src/qt/addresstablemodel.cpp12
-rw-r--r--src/qt/walletmodel.cpp1
-rw-r--r--src/rpc/blockchain.cpp8
-rw-r--r--src/rpc/mining.cpp4
-rw-r--r--src/rpc/rawtransaction.cpp4
-rw-r--r--src/test/util_tests.cpp130
-rw-r--r--src/util.cpp35
-rw-r--r--src/util.h6
-rw-r--r--src/validation.cpp2
-rw-r--r--src/wallet/feebumper.cpp3
-rw-r--r--src/wallet/rpcwallet.cpp5
-rw-r--r--src/wallet/test/wallet_tests.cpp3
-rw-r--r--src/wallet/wallet.cpp9
-rwxr-xr-xtest/functional/feature_nulldummy.py2
-rwxr-xr-xtest/functional/mempool_limit.py10
-rwxr-xr-xtest/functional/mining_prioritisetransaction.py2
-rwxr-xr-xtest/functional/p2p_invalid_tx.py70
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py3
-rw-r--r--test/functional/test_framework/messages.py7
-rwxr-xr-xtest/functional/test_framework/mininode.py153
-rw-r--r--test/functional/test_framework/util.py1
-rwxr-xr-xtest/functional/test_runner.py2
28 files changed, 400 insertions, 155 deletions
diff --git a/depends/packages/native_mac_alias.mk b/depends/packages/native_mac_alias.mk
index 488ec8b59c..306c835656 100644
--- a/depends/packages/native_mac_alias.mk
+++ b/depends/packages/native_mac_alias.mk
@@ -1,14 +1,9 @@
package=native_mac_alias
-$(package)_version=2.0.6
+$(package)_version=2.0.7
$(package)_download_path=https://github.com/al45tair/mac_alias/archive/
$(package)_file_name=v$($(package)_version).tar.gz
-$(package)_sha256_hash=78a3332d9a597eebf09ae652d38ad1e263b28db5c2e6dd725fad357b03b77371
+$(package)_sha256_hash=6f606d3b6bccd2112aeabf1a063f5b5ece87005a5d7e97c8faca23b916e88838
$(package)_install_libdir=$(build_prefix)/lib/python/dist-packages
-$(package)_patches=python3.patch
-
-define $(package)_preprocess_cmds
- patch -p1 < $($(package)_patch_dir)/python3.patch
-endef
define $(package)_build_cmds
python setup.py build
diff --git a/depends/patches/native_mac_alias/python3.patch b/depends/patches/native_mac_alias/python3.patch
deleted file mode 100644
index 6f2f5534a2..0000000000
--- a/depends/patches/native_mac_alias/python3.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-diff -dur a/mac_alias/alias.py b/mac_alias/alias.py
---- a/mac_alias/alias.py
-+++ b/mac_alias/alias.py
-@@ -258,10 +258,10 @@
- alias = Alias()
- alias.appinfo = appinfo
-
-- alias.volume = VolumeInfo (volname.replace('/',':'),
-+ alias.volume = VolumeInfo (volname.decode().replace('/',':'),
- voldate, fstype, disktype,
- volattrs, volfsid)
-- alias.target = TargetInfo (kind, filename.replace('/',':'),
-+ alias.target = TargetInfo (kind, filename.decode().replace('/',':'),
- folder_cnid, cnid,
- crdate, creator_code, type_code)
- alias.target.levels_from = levels_from
-@@ -276,9 +276,9 @@
- b.read(1)
-
- if tag == TAG_CARBON_FOLDER_NAME:
-- alias.target.folder_name = value.replace('/',':')
-+ alias.target.folder_name = value.decode().replace('/',':')
- elif tag == TAG_CNID_PATH:
-- alias.target.cnid_path = struct.unpack(b'>%uI' % (length // 4),
-+ alias.target.cnid_path = struct.unpack('>%uI' % (length // 4),
- value)
- elif tag == TAG_CARBON_PATH:
- alias.target.carbon_path = value
-@@ -313,9 +313,9 @@
- alias.target.creation_date \
- = mac_epoch + datetime.timedelta(seconds=seconds)
- elif tag == TAG_POSIX_PATH:
-- alias.target.posix_path = value
-+ alias.target.posix_path = value.decode()
- elif tag == TAG_POSIX_PATH_TO_MOUNTPOINT:
-- alias.volume.posix_path = value
-+ alias.volume.posix_path = value.decode()
- elif tag == TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE:
- alias.volume.disk_image_alias = Alias.from_bytes(value)
- elif tag == TAG_USER_HOME_LENGTH_PREFIX:
-@@ -467,12 +467,12 @@
-
- b.write(struct.pack(b'>hhQhhQ',
- TAG_HIGH_RES_VOLUME_CREATION_DATE,
-- 8, long(voldate * 65536),
-+ 8, int(voldate * 65536),
- TAG_HIGH_RES_CREATION_DATE,
-- 8, long(crdate * 65536)))
-+ 8, int(crdate * 65536)))
-
- if self.target.cnid_path:
-- cnid_path = struct.pack(b'>%uI' % len(self.target.cnid_path),
-+ cnid_path = struct.pack('>%uI' % len(self.target.cnid_path),
- *self.target.cnid_path)
- b.write(struct.pack(b'>hh', TAG_CNID_PATH,
- len(cnid_path)))
diff --git a/doc/build-windows.md b/doc/build-windows.md
index 8d4afdc817..a10654c7ee 100644
--- a/doc/build-windows.md
+++ b/doc/build-windows.md
@@ -34,10 +34,9 @@ Full instructions to install WSL are available on the above link.
To install WSL on Windows 10 with Fall Creators Update installed (version >= 16215.0) do the following:
1. Enable the Windows Subsystem for Linux feature
- * From Start, search for "Turn Windows features on or off" (type 'turn')
- * Select Windows Subsystem for Linux
- * Click OK
- * Restart if necessary
+ * Open the Windows Features dialog (`OptionalFeatures.exe`)
+ * Enable 'Windows Susbsystem for Linux'
+ * Click 'OK' and restart if necessary
2. Install Ubuntu
* Open Microsoft Store and search for Ubuntu or use [this link](https://www.microsoft.com/store/productId/9NBLGGH4MSV6)
* Click Install
diff --git a/doc/release-notes.md b/doc/release-notes.md
index 0292eaa4d2..528cb81a38 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -56,6 +56,13 @@ frequently tested on them.
Notable changes
===============
+RPC changes
+------------
+
+### Low-level changes
+
+- The `fundrawtransaction` rpc will reject the previously deprecated `reserveChangeKey` option.
+
Credits
=======
diff --git a/src/clientversion.cpp b/src/clientversion.cpp
index 7aa0a8d660..662fbb6e77 100644
--- a/src/clientversion.cpp
+++ b/src/clientversion.cpp
@@ -43,7 +43,7 @@ const std::string CLIENT_NAME("Satoshi");
//! git will put "#define GIT_ARCHIVE 1" on the next line inside archives. $Format:%n#define GIT_ARCHIVE 1$
#ifdef GIT_ARCHIVE
-#define GIT_COMMIT_ID "$Format:%h$"
+#define GIT_COMMIT_ID "$Format:%H$"
#define GIT_COMMIT_DATE "$Format:%cD$"
#endif
diff --git a/src/init.cpp b/src/init.cpp
index b28baba779..895a5358f4 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -680,11 +680,13 @@ void ThreadImport(std::vector<fs::path> vImportFiles)
if (!ActivateBestChain(state, chainparams)) {
LogPrintf("Failed to connect best block");
StartShutdown();
+ return;
}
if (gArgs.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
StartShutdown();
+ return;
}
} // End scope of CImportingNow
if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp
index 74f0db3520..ffb5bff4de 100644
--- a/src/qt/addresstablemodel.cpp
+++ b/src/qt/addresstablemodel.cpp
@@ -393,11 +393,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
}
// Add entry
- {
- LOCK(wallet->cs_wallet);
- wallet->SetAddressBook(DecodeDestination(strAddress), strLabel,
- (type == Send ? "send" : "receive"));
- }
+ wallet->SetAddressBook(DecodeDestination(strAddress), strLabel,
+ (type == Send ? "send" : "receive"));
return QString::fromStdString(strAddress);
}
@@ -411,10 +408,7 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent
// Also refuse to remove receiving addresses.
return false;
}
- {
- LOCK(wallet->cs_wallet);
- wallet->DelAddressBook(DecodeDestination(rec->address.toStdString()));
- }
+ wallet->DelAddressBook(DecodeDestination(rec->address.toStdString()));
return true;
}
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 541114e5fe..34954a6bfa 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -42,6 +42,7 @@ WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *_wallet, O
transactionTableModel(0),
recentRequestsTableModel(0),
cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0),
+ cachedWatchOnlyBalance{0}, cachedWatchUnconfBalance{0}, cachedWatchImmatureBalance{0},
cachedEncryptionStatus(Unencrypted),
cachedNumBlocks(0)
{
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 189da6ae48..f1352a13cf 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1434,7 +1434,7 @@ UniValue preciousblock(const JSONRPCRequest& request)
PreciousBlock(state, Params(), pblockindex);
if (!state.IsValid()) {
- throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
+ throw JSONRPCError(RPC_DATABASE_ERROR, FormatStateMessage(state));
}
return NullUniValue;
@@ -1472,7 +1472,7 @@ UniValue invalidateblock(const JSONRPCRequest& request)
}
if (!state.IsValid()) {
- throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
+ throw JSONRPCError(RPC_DATABASE_ERROR, FormatStateMessage(state));
}
return NullUniValue;
@@ -1509,7 +1509,7 @@ UniValue reconsiderblock(const JSONRPCRequest& request)
ActivateBestChain(state, Params());
if (!state.IsValid()) {
- throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
+ throw JSONRPCError(RPC_DATABASE_ERROR, FormatStateMessage(state));
}
return NullUniValue;
@@ -1563,7 +1563,7 @@ UniValue getchaintxstats(const JSONRPCRequest& request)
pindex = chainActive.Tip();
}
}
-
+
assert(pindex != nullptr);
if (request.params[0].isNull()) {
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index dd74095b62..3f3bfa0cfd 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -264,11 +264,11 @@ static UniValue BIP22ValidationResult(const CValidationState& state)
if (state.IsValid())
return NullUniValue;
- std::string strRejectReason = state.GetRejectReason();
if (state.IsError())
- throw JSONRPCError(RPC_VERIFY_ERROR, strRejectReason);
+ throw JSONRPCError(RPC_VERIFY_ERROR, FormatStateMessage(state));
if (state.IsInvalid())
{
+ std::string strRejectReason = state.GetRejectReason();
if (strRejectReason.empty())
return "rejected";
return strRejectReason;
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 24f2431efc..803cd28649 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -981,12 +981,12 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
nullptr /* plTxnReplaced */, false /* bypass_limits */, nMaxRawTxFee)) {
if (state.IsInvalid()) {
- throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
+ throw JSONRPCError(RPC_TRANSACTION_REJECTED, FormatStateMessage(state));
} else {
if (fMissingInputs) {
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs");
}
- throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason());
+ throw JSONRPCError(RPC_TRANSACTION_ERROR, FormatStateMessage(state));
}
} else {
// If wallet is enabled, ensure that the wallet has been made aware
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 55d60d95e9..4b2da3e219 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -13,6 +13,10 @@
#include <stdint.h>
#include <vector>
+#ifndef WIN32
+#include <sys/types.h>
+#include <sys/wait.h>
+#endif
#include <boost/test/unit_test.hpp>
@@ -603,4 +607,130 @@ BOOST_AUTO_TEST_CASE(test_ParseFixedPoint)
BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount));
}
+static void TestOtherThread(fs::path dirname, std::string lockname, bool *result)
+{
+ *result = LockDirectory(dirname, lockname);
+}
+
+#ifndef WIN32 // Cannot do this test on WIN32 due to lack of fork()
+static constexpr char LockCommand = 'L';
+static constexpr char UnlockCommand = 'U';
+static constexpr char ExitCommand = 'X';
+
+static void TestOtherProcess(fs::path dirname, std::string lockname, int fd)
+{
+ char ch;
+ int rv;
+ while (true) {
+ rv = read(fd, &ch, 1); // Wait for command
+ assert(rv == 1);
+ switch(ch) {
+ case LockCommand:
+ ch = LockDirectory(dirname, lockname);
+ rv = write(fd, &ch, 1);
+ assert(rv == 1);
+ break;
+ case UnlockCommand:
+ ReleaseDirectoryLocks();
+ ch = true; // Always succeeds
+ rv = write(fd, &ch, 1);
+ break;
+ case ExitCommand:
+ close(fd);
+ exit(0);
+ default:
+ assert(0);
+ }
+ }
+}
+#endif
+
+BOOST_AUTO_TEST_CASE(test_LockDirectory)
+{
+ fs::path dirname = fs::temp_directory_path() / fs::unique_path();
+ const std::string lockname = ".lock";
+#ifndef WIN32
+ // Revert SIGCHLD to default, otherwise boost.test will catch and fail on
+ // it: there is BOOST_TEST_IGNORE_SIGCHLD but that only works when defined
+ // at build-time of the boost library
+ void (*old_handler)(int) = signal(SIGCHLD, SIG_DFL);
+
+ // Fork another process for testing before creating the lock, so that we
+ // won't fork while holding the lock (which might be undefined, and is not
+ // relevant as test case as that is avoided with -daemonize).
+ int fd[2];
+ BOOST_CHECK_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), 0);
+ pid_t pid = fork();
+ if (!pid) {
+ BOOST_CHECK_EQUAL(close(fd[1]), 0); // Child: close parent end
+ TestOtherProcess(dirname, lockname, fd[0]);
+ }
+ BOOST_CHECK_EQUAL(close(fd[0]), 0); // Parent: close child end
+#endif
+ // Lock on non-existent directory should fail
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), false);
+
+ fs::create_directories(dirname);
+
+ // Probing lock on new directory should succeed
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true);
+
+ // Persistent lock on new directory should succeed
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true);
+
+ // Another lock on the directory from the same thread should succeed
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true);
+
+ // Another lock on the directory from a different thread within the same process should succeed
+ bool threadresult;
+ std::thread thr(TestOtherThread, dirname, lockname, &threadresult);
+ thr.join();
+ BOOST_CHECK_EQUAL(threadresult, true);
+#ifndef WIN32
+ // Try to aquire lock in child process while we're holding it, this should fail.
+ char ch;
+ BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1);
+ BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1);
+ BOOST_CHECK_EQUAL((bool)ch, false);
+
+ // Give up our lock
+ ReleaseDirectoryLocks();
+ // Probing lock from our side now should succeed, but not hold on to the lock.
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true);
+
+ // Try to acquire the lock in the child process, this should be succesful.
+ BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1);
+ BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1);
+ BOOST_CHECK_EQUAL((bool)ch, true);
+
+ // When we try to probe the lock now, it should fail.
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), false);
+
+ // Unlock the lock in the child process
+ BOOST_CHECK_EQUAL(write(fd[1], &UnlockCommand, 1), 1);
+ BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1);
+ BOOST_CHECK_EQUAL((bool)ch, true);
+
+ // When we try to probe the lock now, it should succeed.
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true);
+
+ // Re-lock the lock in the child process, then wait for it to exit, check
+ // successful return. After that, we check that exiting the process
+ // has released the lock as we would expect by probing it.
+ int processstatus;
+ BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1);
+ BOOST_CHECK_EQUAL(write(fd[1], &ExitCommand, 1), 1);
+ BOOST_CHECK_EQUAL(waitpid(pid, &processstatus, 0), pid);
+ BOOST_CHECK_EQUAL(processstatus, 0);
+ BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true);
+
+ // Restore SIGCHLD
+ signal(SIGCHLD, old_handler);
+ BOOST_CHECK_EQUAL(close(fd[1]), 0); // Close our side of the socketpair
+#endif
+ // Clean up
+ ReleaseDirectoryLocks();
+ fs::remove_all(dirname);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/util.cpp b/src/util.cpp
index 6738bbc6e4..dcf7ed38b1 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -373,20 +373,37 @@ int LogPrintStr(const std::string &str)
return ret;
}
+/** A map that contains all the currently held directory locks. After
+ * successful locking, these will be held here until the global destructor
+ * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks
+ * is called.
+ */
+static std::map<std::string, std::unique_ptr<boost::interprocess::file_lock>> dir_locks;
+/** Mutex to protect dir_locks. */
+static std::mutex cs_dir_locks;
+
bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only)
{
+ std::lock_guard<std::mutex> ulock(cs_dir_locks);
fs::path pathLockFile = directory / lockfile_name;
- FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist.
+
+ // If a lock for this directory already exists in the map, don't try to re-lock it
+ if (dir_locks.count(pathLockFile.string())) {
+ return true;
+ }
+
+ // Create empty lock file if it doesn't exist.
+ FILE* file = fsbridge::fopen(pathLockFile, "a");
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()) {
+ auto lock = MakeUnique<boost::interprocess::file_lock>(pathLockFile.string().c_str());
+ if (!lock->try_lock()) {
return false;
}
- if (probe_only) {
- lock.unlock();
+ if (!probe_only) {
+ // Lock successful and we're not just probing, put it into the map
+ dir_locks.emplace(pathLockFile.string(), std::move(lock));
}
} catch (const boost::interprocess::interprocess_exception& e) {
return error("Error while attempting to lock directory %s: %s", directory.string(), e.what());
@@ -394,6 +411,12 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b
return true;
}
+void ReleaseDirectoryLocks()
+{
+ std::lock_guard<std::mutex> ulock(cs_dir_locks);
+ dir_locks.clear();
+}
+
/** 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 05138a9bfe..9490a5678f 100644
--- a/src/util.h
+++ b/src/util.h
@@ -174,6 +174,12 @@ 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);
+
+/** Release all directory locks. This is used for unit testing only, at runtime
+ * the global destructor will take care of the locks.
+ */
+void ReleaseDirectoryLocks();
+
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 dabf7e279e..e809f66e25 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -712,7 +712,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize);
if (!bypass_limits && mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) {
- return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nFees, mempoolRejectFee));
+ return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nModifiedFees, mempoolRejectFee));
}
// No transactions are allowed below minRelayTxFee except from disconnected blocks
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 5234a69710..9cae660c60 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -274,7 +274,7 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti
CValidationState state;
if (!wallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
// NOTE: CommitTransaction never returns false, so this should never happen.
- errors.push_back(strprintf("The transaction was rejected: %s", state.GetRejectReason()));
+ errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state)));
return Result::WALLET_ERROR;
}
@@ -297,4 +297,3 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti
}
} // namespace feebumper
-
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index b466cf1a81..8f378acd1a 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -435,7 +435,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA
}
CValidationState state;
if (!pwallet->CommitTransaction(wtxNew, reservekey, g_connman.get(), state)) {
- strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason());
+ strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
}
}
@@ -1155,7 +1155,7 @@ UniValue sendmany(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
CValidationState state;
if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state)) {
- strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason());
+ strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state));
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
}
@@ -3129,7 +3129,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
{"change_type", UniValueType(UniValue::VSTR)},
{"includeWatching", UniValueType(UniValue::VBOOL)},
{"lockUnspents", UniValueType(UniValue::VBOOL)},
- {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so.
{"feeRate", UniValueType()}, // will be checked below
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
{"replaceable", UniValueType(UniValue::VBOOL)},
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 161372784b..9db5d63922 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -451,6 +451,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// than or equal to key birthday.
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
{
+ g_address_type = OUTPUT_TYPE_DEFAULT;
+ g_change_type = OUTPUT_TYPE_DEFAULT;
+
// Create two blocks with same timestamp to verify that importwallet rescan
// will pick up both blocks, not just the first.
const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 513819606b..408a01c50b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -3092,7 +3092,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
{
// Broadcast
if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
- LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason());
+ LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state));
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
} else {
wtx.RelayWalletTransaction(connman);
@@ -4179,11 +4179,6 @@ int CMerkleTx::GetBlocksToMaturity() const
bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
{
- // Quick check to avoid re-setting fInMempool to false
- if (mempool.exists(tx->GetHash())) {
- return false;
- }
-
// We must set fInMempool here - while it will be re-set to true by the
// entered-mempool callback, if we did not there would be a race where a
// user could call sendmoney in a loop and hit spurious out of funds errors
@@ -4191,7 +4186,7 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState&
// unavailable as we're not yet aware its in mempool.
bool ret = ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */,
nullptr /* plTxnReplaced */, false /* bypass_limits */, nAbsurdFee);
- fInMempool = ret;
+ fInMempool |= ret;
return ret;
}
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index e4f413cc2a..740c498ce6 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -21,7 +21,7 @@ from test_framework.script import CScript
from io import BytesIO
import time
-NULLDUMMY_ERROR = "64: non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)"
+NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero) (code 64)"
def trueDummy(tx):
scriptSig = CScript(tx.vin[0].scriptSig)
diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py
index e7ce3820d2..7e01663c96 100755
--- a/test/functional/mempool_limit.py
+++ b/test/functional/mempool_limit.py
@@ -50,5 +50,15 @@ class MempoolLimitTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_greater_than(self.nodes[0].getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
+ self.log.info('Create a mempool tx that will not pass mempoolminfee')
+ us0 = utxos.pop()
+ inputs = [{ "txid" : us0["txid"], "vout" : us0["vout"]}]
+ outputs = {self.nodes[0].getnewaddress() : 0.0001}
+ tx = self.nodes[0].createrawtransaction(inputs, outputs)
+ # specifically fund this tx with a fee < mempoolminfee, >= than minrelaytxfee
+ txF = self.nodes[0].fundrawtransaction(tx, {'feeRate': relayfee})
+ txFS = self.nodes[0].signrawtransaction(txF['hex'])
+ assert_raises_rpc_error(-26, "mempool min fee not met, 166 < 411 (code 66)", self.nodes[0].sendrawtransaction, txFS['hex'])
+
if __name__ == '__main__':
MempoolLimitTest().main()
diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py
index 57954ce321..8cea9c2783 100755
--- a/test/functional/mining_prioritisetransaction.py
+++ b/test/functional/mining_prioritisetransaction.py
@@ -120,7 +120,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework):
tx_id = self.nodes[0].decoderawtransaction(tx_hex)["txid"]
# This will raise an exception due to min relay fee not being met
- assert_raises_rpc_error(-26, "66: min relay fee not met", self.nodes[0].sendrawtransaction, tx_hex)
+ assert_raises_rpc_error(-26, "min relay fee not met (code 66)", self.nodes[0].sendrawtransaction, tx_hex)
assert(tx_id not in self.nodes[0].getrawmempool())
# This is a less than 1000-byte transaction, so just set the fee
diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py
index 9c1100e070..64fada38e2 100755
--- a/test/functional/p2p_invalid_tx.py
+++ b/test/functional/p2p_invalid_tx.py
@@ -4,68 +4,50 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test node responses to invalid transactions.
-In this test we connect to one node over p2p, and test tx requests.
-"""
+In this test we connect to one node over p2p, and test tx requests."""
+from test_framework.blocktools import create_block, create_coinbase, create_transaction
+from test_framework.messages import COIN
+from test_framework.mininode import network_thread_start, P2PDataStore
+from test_framework.test_framework import BitcoinTestFramework
-from test_framework.test_framework import ComparisonTestFramework
-from test_framework.comptool import TestManager, TestInstance, RejectResult
-from test_framework.blocktools import *
-import time
+class InvalidTxRequestTest(BitcoinTestFramework):
-
-
-# Use the ComparisonTestFramework with 1 node: only use --testbinary.
-class InvalidTxRequestTest(ComparisonTestFramework):
-
- ''' Can either run this test as 1 node with expected answers, or two and compare them.
- Change the "outcome" variable from each TestInstance object to only do the comparison. '''
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
+ self.extra_args = [["-whitelist=127.0.0.1"]]
def run_test(self):
- test = TestManager(self, self.options.tmpdir)
- test.add_all_connections(self.nodes)
- self.tip = None
- self.block_time = None
+ # Add p2p connection to node0
+ node = self.nodes[0] # convenience reference to the node
+ node.add_p2p_connection(P2PDataStore())
+
network_thread_start()
- test.run()
+ node.p2p.wait_for_verack()
- def get_tests(self):
- if self.tip is None:
- self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
- self.block_time = int(time.time())+1
+ best_block = self.nodes[0].getbestblockhash()
+ tip = int(best_block, 16)
+ best_block_time = self.nodes[0].getblock(best_block)['time']
+ block_time = best_block_time + 1
- '''
- Create a new block with an anyone-can-spend coinbase
- '''
+ self.log.info("Create a new block with an anyone-can-spend coinbase.")
height = 1
- block = create_block(self.tip, create_coinbase(height), self.block_time)
- self.block_time += 1
+ block = create_block(tip, create_coinbase(height), block_time)
+ block_time += 1
block.solve()
# Save the coinbase for later
- self.block1 = block
- self.tip = block.sha256
+ block1 = block
+ tip = block.sha256
height += 1
- yield TestInstance([[block, True]])
+ node.p2p.send_blocks_and_test([block], node, success=True)
- '''
- Now we need that block to mature so we can spend the coinbase.
- '''
- test = TestInstance(sync_every_block=False)
- for i in range(100):
- block = create_block(self.tip, create_coinbase(height), self.block_time)
- block.solve()
- self.tip = block.sha256
- self.block_time += 1
- test.blocks_and_transactions.append([block, True])
- height += 1
- yield test
+ self.log.info("Mature the block.")
+ self.nodes[0].generate(100)
# b'\x64' is OP_NOTIF
# Transaction will be rejected with code 16 (REJECT_INVALID)
- tx1 = create_transaction(self.block1.vtx[0], 0, b'\x64', 50 * COIN - 12000)
- yield TestInstance([[tx1, RejectResult(16, b'mandatory-script-verify-flag-failed')]])
+ tx1 = create_transaction(block1.vtx[0], 0, b'\x64', 50 * COIN - 12000)
+ node.p2p.send_txs_and_test([tx1], node, success=False, reject_code=16, reject_reason=b'mandatory-script-verify-flag-failed (Invalid OP_IF construction)')
# TODO: test further transactions...
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 6b9c9c15b7..4d3be18516 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -181,6 +181,9 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_raises_rpc_error(-3, "Unexpected key foo", self.nodes[2].fundrawtransaction, rawtx, {'foo':'bar'})
+ # reserveChangeKey was deprecated and is now removed
+ assert_raises_rpc_error(-3, "Unexpected key reserveChangeKey", lambda: self.nodes[2].fundrawtransaction(hexstring=rawtx, options={'reserveChangeKey': True}))
+
############################################################
# test a fundrawtransaction with an invalid change address #
############################################################
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index a54a0299c7..46ef7521e0 100644
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -44,6 +44,11 @@ NODE_UNSUPPORTED_SERVICE_BIT_5 = (1 << 5)
NODE_UNSUPPORTED_SERVICE_BIT_7 = (1 << 7)
NODE_NETWORK_LIMITED = (1 << 10)
+MSG_TX = 1
+MSG_BLOCK = 2
+MSG_WITNESS_FLAG = 1 << 30
+MSG_TYPE_MASK = 0xffffffff >> 2
+
# Serialization/deserialization tools
def sha256(s):
return hashlib.new('sha256', s).digest()
@@ -204,8 +209,6 @@ class CAddress():
return "CAddress(nServices=%i ip=%s port=%i)" % (self.nServices,
self.ip, self.port)
-MSG_WITNESS_FLAG = 1<<30
-
class CInv():
typemap = {
0: "Error",
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index 4d9661de37..99d0abc3f9 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -10,7 +10,9 @@ This python code was modified from ArtForz' public domain half-a-node, as
found in the mini-node branch of http://github.com/jgarzik/pynode.
P2PConnection: A low-level connection object to a node's P2P interface
-P2PInterface: A high-level interface object for communicating to a node over P2P"""
+P2PInterface: A high-level interface object for communicating to a node over P2P
+P2PDataStore: A p2p interface class that keeps a store of transactions and blocks
+ and can respond correctly to getdata and getheaders messages"""
import asyncore
from collections import defaultdict
from io import BytesIO
@@ -86,7 +88,7 @@ class P2PConnection(asyncore.dispatcher):
self.network = net
self.disconnect = False
- logger.info('Connecting to Bitcoin Node: %s:%d' % (self.dstaddr, self.dstport))
+ logger.debug('Connecting to Bitcoin Node: %s:%d' % (self.dstaddr, self.dstport))
try:
self.connect((dstaddr, dstport))
@@ -356,10 +358,22 @@ class P2PInterface(P2PConnection):
wait_until(test_function, timeout=timeout, lock=mininode_lock)
def wait_for_getdata(self, timeout=60):
+ """Waits for a getdata message.
+
+ Receiving any getdata message will satisfy the predicate. the last_message["getdata"]
+ value must be explicitly cleared before calling this method, or this will return
+ immediately with success. TODO: change this method to take a hash value and only
+ return true if the correct block/tx has been requested."""
test_function = lambda: self.last_message.get("getdata")
wait_until(test_function, timeout=timeout, lock=mininode_lock)
def wait_for_getheaders(self, timeout=60):
+ """Waits for a getheaders message.
+
+ Receiving any getheaders message will satisfy the predicate. the last_message["getheaders"]
+ value must be explicitly cleared before calling this method, or this will return
+ immediately with success. TODO: change this method to take a hash value and only
+ return true if the correct block header has been requested."""
test_function = lambda: self.last_message.get("getheaders")
wait_until(test_function, timeout=timeout, lock=mininode_lock)
@@ -440,3 +454,138 @@ def network_thread_join(timeout=10):
for thread in network_threads:
thread.join(timeout)
assert not thread.is_alive()
+
+class P2PDataStore(P2PInterface):
+ """A P2P data store class.
+
+ Keeps a block and transaction store and responds correctly to getdata and getheaders requests."""
+
+ def __init__(self):
+ super().__init__()
+ self.reject_code_received = None
+ self.reject_reason_received = None
+ # store of blocks. key is block hash, value is a CBlock object
+ self.block_store = {}
+ self.last_block_hash = ''
+ # store of txs. key is txid, value is a CTransaction object
+ self.tx_store = {}
+ self.getdata_requests = []
+
+ def on_getdata(self, message):
+ """Check for the tx/block in our stores and if found, reply with an inv message."""
+ for inv in message.inv:
+ self.getdata_requests.append(inv.hash)
+ if (inv.type & MSG_TYPE_MASK) == MSG_TX and inv.hash in self.tx_store.keys():
+ self.send_message(msg_tx(self.tx_store[inv.hash]))
+ elif (inv.type & MSG_TYPE_MASK) == MSG_BLOCK and inv.hash in self.block_store.keys():
+ self.send_message(msg_block(self.block_store[inv.hash]))
+ else:
+ logger.debug('getdata message type {} received.'.format(hex(inv.type)))
+
+ def on_getheaders(self, message):
+ """Search back through our block store for the locator, and reply with a headers message if found."""
+
+ locator, hash_stop = message.locator, message.hashstop
+
+ # Assume that the most recent block added is the tip
+ if not self.block_store:
+ return
+
+ headers_list = [self.block_store[self.last_block_hash]]
+ maxheaders = 2000
+ while headers_list[-1].sha256 not in locator.vHave:
+ # Walk back through the block store, adding headers to headers_list
+ # as we go.
+ prev_block_hash = headers_list[-1].hashPrevBlock
+ if prev_block_hash in self.block_store:
+ prev_block_header = self.block_store[prev_block_hash]
+ headers_list.append(prev_block_header)
+ if prev_block_header.sha256 == hash_stop:
+ # if this is the hashstop header, stop here
+ break
+ else:
+ logger.debug('block hash {} not found in block store'.format(hex(prev_block_hash)))
+ break
+
+ # Truncate the list if there are too many headers
+ headers_list = headers_list[:-maxheaders - 1:-1]
+ response = msg_headers(headers_list)
+
+ if response is not None:
+ self.send_message(response)
+
+ def on_reject(self, message):
+ """Store reject reason and code for testing."""
+ self.reject_code_received = message.code
+ self.reject_reason_received = message.reason
+
+ def send_blocks_and_test(self, blocks, rpc, success=True, request_block=True, reject_code=None, reject_reason=None, timeout=60):
+ """Send blocks to test node and test whether the tip advances.
+
+ - add all blocks to our block_store
+ - send a headers message for the final block
+ - the on_getheaders handler will ensure that any getheaders are responded to
+ - if request_block is True: wait for getdata for each of the blocks. The on_getdata handler will
+ ensure that any getdata messages are responded to
+ - if success is True: assert that the node's tip advances to the most recent block
+ - if success is False: assert that the node's tip doesn't advance
+ - if reject_code and reject_reason are set: assert that the correct reject message is received"""
+
+ with mininode_lock:
+ self.reject_code_received = None
+ self.reject_reason_received = None
+
+ for block in blocks:
+ self.block_store[block.sha256] = block
+ self.last_block_hash = block.sha256
+
+ self.send_message(msg_headers([blocks[-1]]))
+
+ if request_block:
+ wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock)
+
+ if success:
+ wait_until(lambda: rpc.getbestblockhash() == blocks[-1].hash, timeout=timeout)
+ else:
+ assert rpc.getbestblockhash() != blocks[-1].hash
+
+ if reject_code is not None:
+ wait_until(lambda: self.reject_code_received == reject_code, lock=mininode_lock)
+ if reject_reason is not None:
+ wait_until(lambda: self.reject_reason_received == reject_reason, lock=mininode_lock)
+
+ def send_txs_and_test(self, txs, rpc, success=True, reject_code=None, reject_reason=None):
+ """Send txs to test node and test whether they're accepted to the mempool.
+
+ - add all txs to our tx_store
+ - send tx messages for all txs
+ - if success is True: assert that the tx is accepted to the mempool
+ - if success is False: assert that the tx is not accepted to the mempool
+ - if reject_code and reject_reason are set: assert that the correct reject message is received."""
+
+ with mininode_lock:
+ self.reject_code_received = None
+ self.reject_reason_received = None
+
+ for tx in txs:
+ self.tx_store[tx.sha256] = tx
+
+ for tx in txs:
+ self.send_message(msg_tx(tx))
+
+ self.sync_with_ping()
+
+ raw_mempool = rpc.getrawmempool()
+ if success:
+ # Check that all txs are now in the mempool
+ for tx in txs:
+ assert tx.hash in raw_mempool, "{} not found in mempool".format(tx.hash)
+ else:
+ # Check that none of the txs are now in the mempool
+ for tx in txs:
+ assert tx.hash not in raw_mempool, "{} tx found in mempool".format(tx.hash)
+
+ if reject_code is not None:
+ wait_until(lambda: self.reject_code_received == reject_code, lock=mininode_lock)
+ if reject_reason is not None:
+ wait_until(lambda: self.reject_reason_received == reject_reason, lock=mininode_lock)
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 7fdc171332..8bf75ca1ae 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -292,6 +292,7 @@ def initialize_datadir(dirname, n):
f.write("port=" + str(p2p_port(n)) + "\n")
f.write("rpcport=" + str(rpc_port(n)) + "\n")
f.write("listenonion=0\n")
+ f.write("bind=127.0.0.1\n")
return datadir
def get_datadir_path(dirname, n):
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 0cf3424c71..945f645eac 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -342,7 +342,7 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove
print('\n============')
print('{}Combined log for {}:{}'.format(BOLD[1], testdir, BOLD[0]))
print('============\n')
- combined_logs, _ = subprocess.Popen([sys.executble, os.path.join(tests_dir, 'combine_logs.py'), '-c', testdir], universal_newlines=True, stdout=subprocess.PIPE).communicate()
+ combined_logs, _ = subprocess.Popen([sys.executable, os.path.join(tests_dir, 'combine_logs.py'), '-c', testdir], universal_newlines=True, stdout=subprocess.PIPE).communicate()
print("\n".join(deque(combined_logs.splitlines(), combined_logs_len)))
print_results(test_results, max_len_name, (int(time.time() - time0)))