diff options
60 files changed, 668 insertions, 603 deletions
diff --git a/.travis.yml b/.travis.yml index 0332a0e204..6c3a13d73d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,13 @@ dist: trusty os: linux language: minimal cache: + ccache: true directories: - depends/built - depends/sdk-sources - $HOME/.ccache +git: + depth: 1 env: global: - MAKEJOBS=-j3 @@ -45,6 +48,7 @@ install: - if [ -n "$PACKAGES" ]; then travis_retry sudo apt-get install --no-install-recommends --no-upgrade -qq $PACKAGES; fi - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then travis_retry pip3 install flake8 --user; fi before_script: + - if [ "$CHECK_DOC" = 1 ]; then git fetch --unshallow; fi - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then contrib/devtools/commit-script-check.sh $TRAVIS_COMMIT_RANGE; fi - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/git-subtree-check.sh src/crypto/ctaes; fi - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/git-subtree-check.sh src/secp256k1; fi @@ -62,13 +66,12 @@ before_script: - if [ "$NEED_XVFB" = 1 ]; then export DISPLAY=:99.0; /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac; fi script: - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then while read LINE; do travis_retry gpg --keyserver hkp://subset.pool.sks-keyservers.net --recv-keys $LINE; done < contrib/verify-commits/trusted-keys; fi - - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then git fetch --unshallow; fi - if [ "$CHECK_DOC" = 1 -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then contrib/verify-commits/verify-commits.sh; fi - export TRAVIS_COMMIT_LOG=`git log --format=fuller -1` - if [ -n "$USE_SHELL" ]; then export CONFIG_SHELL="$USE_SHELL"; fi - OUTDIR=$BASE_OUTDIR/$TRAVIS_PULL_REQUEST/$TRAVIS_JOB_NUMBER-$HOST - BITCOIN_CONFIG_ALL="--disable-dependency-tracking --prefix=$TRAVIS_BUILD_DIR/depends/$HOST --bindir=$OUTDIR/bin --libdir=$OUTDIR/lib" - - if [ -z "$NO_DEPENDS" ]; then depends/$HOST/native/bin/ccache --max-size=$CCACHE_SIZE; fi + - if [ -z "$NO_DEPENDS" ]; then ccache --max-size=$CCACHE_SIZE; fi - test -n "$USE_SHELL" && eval '"$USE_SHELL" -c "./autogen.sh"' || ./autogen.sh - mkdir build && cd build - ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) diff --git a/configure.ac b/configure.ac index 48f104bdca..18f707f0ba 100644 --- a/configure.ac +++ b/configure.ac @@ -1055,7 +1055,7 @@ if test x$system_univalue != xno ; then m4_ifdef( [PKG_CHECK_MODULES], [ - PKG_CHECK_MODULES([UNIVALUE],[libunivalue],[found_univalue=yes],[true]) + PKG_CHECK_MODULES([UNIVALUE],[libunivalue >= 1.0.4],[found_univalue=yes],[true]) ] ) else diff --git a/contrib/devtools/lint-whitespace.sh b/contrib/devtools/lint-whitespace.sh index c5b9408ff2..c5d43043d5 100755 --- a/contrib/devtools/lint-whitespace.sh +++ b/contrib/devtools/lint-whitespace.sh @@ -51,21 +51,26 @@ if showdiff | grep -E -q '^\+.*\s+$'; then echo "The following changes were suspected:" FILENAME="" SEEN=0 + SEENLN=0 while read -r line; do if [[ "$line" =~ ^diff ]]; then FILENAME="$line" SEEN=0 elif [[ "$line" =~ ^@@ ]]; then LINENUMBER="$line" + SEENLN=0 else if [ "$SEEN" -eq 0 ]; then # The first time a file is seen with trailing whitespace, we print the # filename (preceded by a newline). echo echo "$FILENAME" - echo "$LINENUMBER" SEEN=1 fi + if [ "$SEENLN" -eq 0 ]; then + echo "$LINENUMBER" + SEENLN=1 + fi echo "$line" fi done < <(showdiff | grep -E '^(diff --git |@@|\+.*\s+$)') @@ -78,21 +83,26 @@ if showcodediff | perl -nle '$MATCH++ if m{^\+.*\t}; END{exit 1 unless $MATCH>0} echo "The following changes were suspected:" FILENAME="" SEEN=0 + SEENLN=0 while read -r line; do if [[ "$line" =~ ^diff ]]; then FILENAME="$line" SEEN=0 elif [[ "$line" =~ ^@@ ]]; then LINENUMBER="$line" + SEENLN=0 else if [ "$SEEN" -eq 0 ]; then # The first time a file is seen with a tab character, we print the # filename (preceded by a newline). echo echo "$FILENAME" - echo "$LINENUMBER" SEEN=1 fi + if [ "$SEENLN" -eq 0 ]; then + echo "$LINENUMBER" + SEENLN=1 + fi echo "$line" fi done < <(showcodediff | perl -nle 'print if m{^(diff --git |@@|\+.*\t)}') diff --git a/contrib/init/bitcoind.service b/contrib/init/bitcoind.service index ee113d7615..877abafd19 100644 --- a/contrib/init/bitcoind.service +++ b/contrib/init/bitcoind.service @@ -19,7 +19,26 @@ User=bitcoin Type=forking PIDFile=/run/bitcoind/bitcoind.pid Restart=on-failure + +# Hardening measures +#################### + +# Provide a private /tmp and /var/tmp. PrivateTmp=true +# Mount /usr, /boot/ and /etc read-only for the process. +ProtectSystem=full + +# Disallow the process and all of its children to gain +# new privileges through execve(). +NoNewPrivileges=true + +# Use a new /dev namespace only populated with API pseudo devices +# such as /dev/null, /dev/zero and /dev/random. +PrivateDevices=true + +# Deny the creation of writable and executable memory mappings. +MemoryDenyWriteExecute=true + [Install] WantedBy=multi-user.target diff --git a/depends/config.site.in b/depends/config.site.in index 0a4a9c327e..8444dc26f2 100644 --- a/depends/config.site.in +++ b/depends/config.site.in @@ -64,7 +64,6 @@ LDFLAGS="-L$depends_prefix/lib $LDFLAGS" CC="@CC@" CXX="@CXX@" OBJC="${CC}" -CCACHE=$depends_prefix/native/bin/ccache PYTHONPATH=$depends_prefix/native/lib/python/dist-packages:$PYTHONPATH if test -n "@AR@"; then diff --git a/depends/packages/native_biplist.mk b/depends/packages/native_biplist.mk index 3c6e8900f6..5f247e9bf3 100644 --- a/depends/packages/native_biplist.mk +++ b/depends/packages/native_biplist.mk @@ -1,14 +1,9 @@ package=native_biplist -$(package)_version=0.9 -$(package)_download_path=https://pypi.python.org/packages/source/b/biplist +$(package)_version=1.0.3 +$(package)_download_path=https://bitbucket.org/wooster/biplist/downloads $(package)_file_name=biplist-$($(package)_version).tar.gz -$(package)_sha256_hash=b57cadfd26e4754efdf89e9e37de87885f9b5c847b2615688ca04adfaf6ca604 +$(package)_sha256_hash=4c0549764c5fe50b28042ec21aa2e14fe1a2224e239a1dae77d9e7f3932aa4c6 $(package)_install_libdir=$(build_prefix)/lib/python/dist-packages -$(package)_patches=sorted_list.patch - -define $(package)_preprocess_cmds - patch -p1 < $($(package)_patch_dir)/sorted_list.patch -endef define $(package)_build_cmds python setup.py build diff --git a/depends/packages/native_ccache.mk b/depends/packages/native_ccache.mk deleted file mode 100644 index 8f4eb22538..0000000000 --- a/depends/packages/native_ccache.mk +++ /dev/null @@ -1,25 +0,0 @@ -package=native_ccache -$(package)_version=3.4.1 -$(package)_download_path=https://samba.org/ftp/ccache -$(package)_file_name=ccache-$($(package)_version).tar.bz2 -$(package)_sha256_hash=ca5a01fb4868cdb5176c77b8b4a390be7929a6064be80741217e0686f03f8389 - -define $(package)_set_vars -$(package)_config_opts= -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef - -define $(package)_postprocess_cmds - rm -rf lib include -endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 088723ebd0..551c9fa70b 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,5 +1,4 @@ packages:=boost openssl libevent zeromq -native_packages := native_ccache qt_native_packages = native_protobuf qt_packages = qrencode protobuf zlib diff --git a/depends/patches/native_biplist/sorted_list.patch b/depends/patches/native_biplist/sorted_list.patch deleted file mode 100644 index 89abdb1b71..0000000000 --- a/depends/patches/native_biplist/sorted_list.patch +++ /dev/null @@ -1,29 +0,0 @@ ---- a/biplist/__init__.py 2014-10-26 19:03:11.000000000 +0000 -+++ b/biplist/__init__.py 2016-07-19 19:30:17.663521999 +0000 -@@ -541,7 +541,7 @@ - return HashableWrapper(n) - elif isinstance(root, dict): - n = {} -- for key, value in iteritems(root): -+ for key, value in sorted(iteritems(root)): - n[self.wrapRoot(key)] = self.wrapRoot(value) - return HashableWrapper(n) - elif isinstance(root, list): -@@ -616,7 +616,7 @@ - elif isinstance(obj, dict): - size = proc_size(len(obj)) - self.incrementByteCount('dictBytes', incr=1+size) -- for key, value in iteritems(obj): -+ for key, value in sorted(iteritems(obj)): - check_key(key) - self.computeOffsets(key, asReference=True) - self.computeOffsets(value, asReference=True) -@@ -714,7 +714,7 @@ - keys = [] - values = [] - objectsToWrite = [] -- for key, value in iteritems(obj): -+ for key, value in sorted(iteritems(obj)): - keys.append(key) - values.append(value) - for key in keys: diff --git a/doc/build-osx.md b/doc/build-osx.md index 2b84c7cc2c..f19e4f0b8d 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -16,7 +16,7 @@ Then install [Homebrew](https://brew.sh). Dependencies ---------------------- - brew install automake berkeley-db4 libtool boost miniupnpc openssl pkg-config protobuf python3 qt libevent + brew install automake berkeley-db4 libtool boost miniupnpc openssl pkg-config protobuf python qt libevent See [dependencies.md](dependencies.md) for a complete overview. diff --git a/doc/build-windows.md b/doc/build-windows.md index cff4ea9362..0a4136173b 100644 --- a/doc/build-windows.md +++ b/doc/build-windows.md @@ -62,6 +62,8 @@ installing the toolchain will be different. First, install the general dependencies: + sudo apt update + sudo apt upgrade sudo apt install build-essential libtool autotools-dev automake pkg-config bsdmainutils curl git A host toolchain (`build-essential`) is necessary because some dependency diff --git a/doc/dependencies.md b/doc/dependencies.md index 05518bf819..7aa96c4c9b 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -7,7 +7,6 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | --- | --- | --- | --- | --- | --- | | Berkeley DB | [4.8.30](http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | | Boost | [1.64.0](http://www.boost.org/users/download/) | [1.47.0](https://github.com/bitcoin/bitcoin/pull/8920) | No | | | -| ccache | [3.3.6](https://ccache.samba.org/download.html) | | No | | | | Clang | | [3.3+](http://llvm.org/releases/download.html) (C++11 support) | | | | | D-Bus | [1.10.18](https://cgit.freedesktop.org/dbus/dbus/tree/NEWS?h=dbus-1.10) | | No | Yes | | | Expat | [2.2.5](https://libexpat.github.io/) | | No | Yes | | diff --git a/doc/release-notes.md b/doc/release-notes.md index 8fcd2a9163..a79012722f 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -61,7 +61,8 @@ RPC changes ### Low-level changes -- The `fundrawtransaction` rpc will reject the previously deprecated `reserveChangeKey` option. +- The `createrawtransaction` RPC will now accept an array or dictionary (kept for compatibility) for the `outputs` parameter. This means the order of transaction outputs can be specified by the client. +- The `fundrawtransaction` RPC will reject the previously deprecated `reserveChangeKey` option. External wallet files --------------------- @@ -93,6 +94,10 @@ Low-level RPC changes with any `-wallet=<path>` options, there is no change in behavior, and the name of any wallet is just its `<path>` string. +### Logging + +- The log timestamp format is now ISO 8601 (e.g. "2018-02-28T12:34:56Z"). + Credits ======= diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index fcd836fb45..8218e883a6 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -551,7 +551,6 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) // mergedTx will end up with all the signatures; it // starts as a clone of the raw tx: CMutableTransaction mergedTx(txVariants[0]); - bool fComplete = true; CCoinsView viewDummy; CCoinsViewCache view(&viewDummy); @@ -637,7 +636,6 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) CTxIn& txin = mergedTx.vin[i]; const Coin& coin = view.AccessCoin(txin.prevout); if (coin.IsSpent()) { - fComplete = false; continue; } const CScript& prevPubKey = coin.out.scriptPubKey; @@ -652,14 +650,6 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) for (const CTransaction& txv : txVariants) sigdata = CombineSignatures(prevPubKey, MutableTransactionSignatureChecker(&mergedTx, i, amount), sigdata, DataFromTransaction(txv, i)); UpdateTransaction(mergedTx, i, sigdata); - - if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&mergedTx, i, amount))) - fComplete = false; - } - - if (fComplete) { - // do nothing... for now - // perhaps store this for later optional JSON output } tx = mergedTx; diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index 9189c9a8ad..816d854db3 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -21,9 +21,10 @@ namespace Checkpoints { for (const MapCheckpoints::value_type& i : reverse_iterate(checkpoints)) { const uint256& hash = i.second; - BlockMap::const_iterator t = mapBlockIndex.find(hash); - if (t != mapBlockIndex.end()) - return t->second; + CBlockIndex* pindex = LookupBlockIndex(hash); + if (pindex) { + return pindex; + } } return nullptr; } diff --git a/src/checkpoints.h b/src/checkpoints.h index bf935f80a7..564b486393 100644 --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -19,7 +19,7 @@ struct CCheckpointData; namespace Checkpoints { -//! Returns last CBlockIndex* in mapBlockIndex that is a checkpoint +//! Returns last CBlockIndex* that is a checkpoint CBlockIndex* GetLastCheckpoint(const CCheckpointData& data); } //namespace Checkpoints diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index 6cac625abc..fb0d4215a2 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -218,14 +218,10 @@ void HandleError(const leveldb::Status& status) { if (status.ok()) return; - LogPrintf("%s\n", status.ToString()); - if (status.IsCorruption()) - throw dbwrapper_error("Database corrupted"); - if (status.IsIOError()) - throw dbwrapper_error("Database I/O error"); - if (status.IsNotFound()) - throw dbwrapper_error("Database entry missing"); - throw dbwrapper_error("Unknown database error"); + const std::string errmsg = "Fatal LevelDB error: " + status.ToString(); + LogPrintf("%s\n", errmsg); + LogPrintf("You can use -debug=leveldb to get more complete diagnostic messages\n"); + throw dbwrapper_error(errmsg); } const std::vector<unsigned char>& GetObfuscateKey(const CDBWrapper &w) diff --git a/src/init.cpp b/src/init.cpp index 659f97fec6..e0efe5c0a2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1224,7 +1224,7 @@ bool AppInitMain() } if (!fLogTimestamps) - LogPrintf("Startup time: %s\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime())); + LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime())); LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); LogPrintf("Using data directory %s\n", GetDataDir().string()); LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string()); @@ -1424,6 +1424,8 @@ bool AppInitMain() uiInterface.InitMessage(_("Loading block index...")); + LOCK(cs_main); + nStart = GetTimeMillis(); do { try { @@ -1457,8 +1459,9 @@ bool AppInitMain() // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). - if (!mapBlockIndex.empty() && mapBlockIndex.count(chainparams.GetConsensus().hashGenesisBlock) == 0) + if (!mapBlockIndex.empty() && !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); + } // Check for changed -txindex state if (fTxIndex != gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { @@ -1532,16 +1535,13 @@ bool AppInitMain() MIN_BLOCKS_TO_KEEP); } - { - LOCK(cs_main); - CBlockIndex* tip = chainActive.Tip(); - RPCNotifyBlockChange(true, tip); - if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) { - strLoadError = _("The block database contains a block which appears to be from the future. " - "This may be due to your computer's date and time being set incorrectly. " - "Only rebuild the block database if you are sure that your computer's date and time are correct"); - break; - } + CBlockIndex* tip = chainActive.Tip(); + RPCNotifyBlockChange(true, tip); + if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) { + strLoadError = _("The block database contains a block which appears to be from the future. " + "This may be due to your computer's date and time being set incorrectly. " + "Only rebuild the block database if you are sure that your computer's date and time are correct"); + break; } if (!CVerifyDB().VerifyDB(chainparams, pcoinsdbview.get(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), diff --git a/src/net.cpp b/src/net.cpp index 33a60ac96e..53a0a9b180 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2795,7 +2795,7 @@ void CNode::AskFor(const CInv& inv) nRequestTime = it->second; else nRequestTime = 0; - LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime/1000000), id); + LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, FormatISO8601Time(nRequestTime/1000000), id); // Make sure not to reuse time indexes to keep things in the same order int64_t nNow = GetTimeMicros() - 1000000; @@ -469,6 +469,13 @@ public: virtual bool SendMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0; virtual void InitializeNode(CNode* pnode) = 0; virtual void FinalizeNode(NodeId id, bool& update_connection_time) = 0; + +protected: + /** + * Protected destructor so that instances can only be deleted by derived classes. + * If that restriction is no longer desired, this should be made public and virtual. + */ + ~NetEventsInterface() = default; }; enum diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 482a206c8b..f5073fe903 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -374,10 +374,11 @@ void ProcessBlockAvailability(NodeId nodeid) { assert(state != nullptr); if (!state->hashLastUnknownBlock.IsNull()) { - BlockMap::iterator itOld = mapBlockIndex.find(state->hashLastUnknownBlock); - if (itOld != mapBlockIndex.end() && itOld->second->nChainWork > 0) { - if (state->pindexBestKnownBlock == nullptr || itOld->second->nChainWork >= state->pindexBestKnownBlock->nChainWork) - state->pindexBestKnownBlock = itOld->second; + const CBlockIndex* pindex = LookupBlockIndex(state->hashLastUnknownBlock); + if (pindex && pindex->nChainWork > 0) { + if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { + state->pindexBestKnownBlock = pindex; + } state->hashLastUnknownBlock.SetNull(); } } @@ -390,11 +391,12 @@ void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) { ProcessBlockAvailability(nodeid); - BlockMap::iterator it = mapBlockIndex.find(hash); - if (it != mapBlockIndex.end() && it->second->nChainWork > 0) { + const CBlockIndex* pindex = LookupBlockIndex(hash); + if (pindex && pindex->nChainWork > 0) { // An actually better block was announced. - if (state->pindexBestKnownBlock == nullptr || it->second->nChainWork >= state->pindexBestKnownBlock->nChainWork) - state->pindexBestKnownBlock = it->second; + if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { + state->pindexBestKnownBlock = pindex; + } } else { // An unknown block was announced; just assume that the latest one is the best one. state->hashLastUnknownBlock = hash; @@ -1015,7 +1017,7 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) } case MSG_BLOCK: case MSG_WITNESS_BLOCK: - return mapBlockIndex.count(inv.hash); + return LookupBlockIndex(inv.hash) != nullptr; } // Don't know what it is, just say we already got one return true; @@ -1082,11 +1084,10 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus bool need_activate_chain = false; { LOCK(cs_main); - BlockMap::iterator mi = mapBlockIndex.find(inv.hash); - if (mi != mapBlockIndex.end()) - { - if (mi->second->nChainTx && !mi->second->IsValid(BLOCK_VALID_SCRIPTS) && - mi->second->IsValid(BLOCK_VALID_TREE)) { + const CBlockIndex* pindex = LookupBlockIndex(inv.hash); + if (pindex) { + if (pindex->nChainTx && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && + pindex->IsValid(BLOCK_VALID_TREE)) { // If we have the block and all of its parents, but have not yet validated it, // we might be in the middle of connecting it (ie in the unlock of cs_main // before ActivateBestChain but after AcceptBlock). @@ -1102,9 +1103,9 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus } LOCK(cs_main); - BlockMap::iterator mi = mapBlockIndex.find(inv.hash); - if (mi != mapBlockIndex.end()) { - send = BlockRequestAllowed(mi->second, consensusParams); + const CBlockIndex* pindex = LookupBlockIndex(inv.hash); + if (pindex) { + send = BlockRequestAllowed(pindex, consensusParams); if (!send) { LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom->GetId()); } @@ -1112,7 +1113,7 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks // never disconnect whitelisted nodes - if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - mi->second->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->fWhitelisted) + if (send && connman->OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->fWhitelisted) { LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom->GetId()); @@ -1122,7 +1123,7 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold if (send && !pfrom->fWhitelisted && ( - (((pfrom->GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom->GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (chainActive.Tip()->nHeight - mi->second->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) + (((pfrom->GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom->GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (chainActive.Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) )) { LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom->GetId()); @@ -1132,15 +1133,15 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus } // Pruned nodes may have deleted the block, so check whether // it's available before trying to send. - if (send && (mi->second->nStatus & BLOCK_HAVE_DATA)) + if (send && (pindex->nStatus & BLOCK_HAVE_DATA)) { std::shared_ptr<const CBlock> pblock; - if (a_recent_block && a_recent_block->GetHash() == (*mi).second->GetBlockHash()) { + if (a_recent_block && a_recent_block->GetHash() == pindex->GetBlockHash()) { pblock = a_recent_block; } else { // Send block from disk std::shared_ptr<CBlock> pblockRead = std::make_shared<CBlock>(); - if (!ReadBlockFromDisk(*pblockRead, (*mi).second, consensusParams)) + if (!ReadBlockFromDisk(*pblockRead, pindex, consensusParams)) assert(!"cannot load block from disk"); pblock = pblockRead; } @@ -1182,8 +1183,8 @@ void static ProcessGetBlockData(CNode* pfrom, const Consensus::Params& consensus // instead we respond with the full, non-compact block. bool fPeerWantsWitness = State(pfrom->GetId())->fWantsCmpctWitness; int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - if (CanDirectFetch(consensusParams) && mi->second->nHeight >= chainActive.Height() - MAX_CMPCTBLOCK_DEPTH) { - if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == mi->second->GetBlockHash()) { + if (CanDirectFetch(consensusParams) && pindex->nHeight >= chainActive.Height() - MAX_CMPCTBLOCK_DEPTH) { + if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); } else { CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); @@ -1323,7 +1324,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve // don't connect before giving DoS points // - Once a headers message is received that is valid and does connect, // nUnconnectingHeaders gets reset back to 0. - if (mapBlockIndex.find(headers[0].hashPrevBlock) == mapBlockIndex.end() && nCount < MAX_BLOCKS_TO_ANNOUNCE) { + if (!LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { nodestate->nUnconnectingHeaders++; connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), uint256())); LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n", @@ -1353,7 +1354,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve // If we don't have the last header, then they'll have given us // something new (if these headers are valid). - if (mapBlockIndex.find(hashLastBlock) == mapBlockIndex.end()) { + if (!LookupBlockIndex(hashLastBlock)) { received_new_header = true; } } @@ -1369,7 +1370,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve } else { LogPrint(BCLog::NET, "peer=%d: invalid header received\n", pfrom->GetId()); } - if (punish_duplicate_invalid && mapBlockIndex.find(first_invalid_header.GetHash()) != mapBlockIndex.end()) { + if (punish_duplicate_invalid && LookupBlockIndex(first_invalid_header.GetHash())) { // Goal: don't allow outbound peers to use up our outbound // connection slots if they are on incompatible chains. // @@ -2050,13 +2051,13 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LOCK(cs_main); - BlockMap::iterator it = mapBlockIndex.find(req.blockhash); - if (it == mapBlockIndex.end() || !(it->second->nStatus & BLOCK_HAVE_DATA)) { + const CBlockIndex* pindex = LookupBlockIndex(req.blockhash); + if (!pindex || !(pindex->nStatus & BLOCK_HAVE_DATA)) { LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block we don't have", pfrom->GetId()); return true; } - if (it->second->nHeight < chainActive.Height() - MAX_BLOCKTXN_DEPTH) { + if (pindex->nHeight < chainActive.Height() - MAX_BLOCKTXN_DEPTH) { // If an older block is requested (should never happen in practice, // but can happen in tests) send a block response instead of a // blocktxn response. Sending a full block response instead of a @@ -2074,7 +2075,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } CBlock block; - bool ret = ReadBlockFromDisk(block, it->second, chainparams.GetConsensus()); + bool ret = ReadBlockFromDisk(block, pindex, chainparams.GetConsensus()); assert(ret); SendBlockTransactions(block, req, pfrom, connman); @@ -2098,10 +2099,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (locator.IsNull()) { // If locator is null, return the hashStop block - BlockMap::iterator mi = mapBlockIndex.find(hashStop); - if (mi == mapBlockIndex.end()) + pindex = LookupBlockIndex(hashStop); + if (!pindex) { return true; - pindex = (*mi).second; + } if (!BlockRequestAllowed(pindex, chainparams.GetConsensus())) { LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block header that isn't in the main chain\n", __func__, pfrom->GetId()); @@ -2340,14 +2341,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr { LOCK(cs_main); - if (mapBlockIndex.find(cmpctblock.header.hashPrevBlock) == mapBlockIndex.end()) { + if (!LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { // Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers if (!IsInitialBlockDownload()) connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), uint256())); return true; } - if (mapBlockIndex.find(cmpctblock.header.GetHash()) == mapBlockIndex.end()) { + if (!LookupBlockIndex(cmpctblock.header.GetHash())) { received_new_header = true; } } @@ -3329,9 +3330,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic<bool>& interruptM // then send all headers past that one. If we come across any // headers that aren't on chainActive, give up. for (const uint256 &hash : pto->vBlockHashesToAnnounce) { - BlockMap::iterator mi = mapBlockIndex.find(hash); - assert(mi != mapBlockIndex.end()); - const CBlockIndex *pindex = mi->second; + const CBlockIndex* pindex = LookupBlockIndex(hash); + assert(pindex); if (chainActive[pindex->nHeight] != pindex) { // Bail out if we reorged away from this block fRevertToInv = true; @@ -3422,9 +3422,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto, std::atomic<bool>& interruptM // in the past. if (!pto->vBlockHashesToAnnounce.empty()) { const uint256 &hashToAnnounce = pto->vBlockHashesToAnnounce.back(); - BlockMap::iterator mi = mapBlockIndex.find(hashToAnnounce); - assert(mi != mapBlockIndex.end()); - const CBlockIndex *pindex = mi->second; + const CBlockIndex* pindex = LookupBlockIndex(hashToAnnounce); + assert(pindex); // Warn if we're announcing a block that is not on the main chain. // This should be very rare and could be optimized out. diff --git a/src/net_processing.h b/src/net_processing.h index ff1ebc59da..11543129cf 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -35,7 +35,7 @@ static constexpr int64_t EXTRA_PEER_CHECK_INTERVAL = 45; /** Minimum time an outbound-peer-eviction candidate must be connected for, in order to evict, in seconds */ static constexpr int64_t MINIMUM_CONNECT_TIME = 30; -class PeerLogicValidation : public CValidationInterface, public NetEventsInterface { +class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface { private: CConnman* const connman; diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 517aa49e2b..78dc9f81dd 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -21,6 +21,41 @@ #include <QMessageBox> #include <QSortFilterProxyModel> +class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel +{ + const QString m_type; + +public: + AddressBookSortFilterProxyModel(const QString& type, QObject* parent) + : QSortFilterProxyModel(parent) + , m_type(type) + { + setDynamicSortFilter(true); + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); + } + +protected: + bool filterAcceptsRow(int row, const QModelIndex& parent) const + { + auto model = sourceModel(); + auto label = model->index(row, AddressTableModel::Label, parent); + + if (model->data(label, AddressTableModel::TypeRole).toString() != m_type) { + return false; + } + + auto address = model->index(row, AddressTableModel::Address, parent); + + if (filterRegExp().indexIn(model->data(address).toString()) < 0 && + filterRegExp().indexIn(model->data(label).toString()) < 0) { + return false; + } + + return true; + } +}; + AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) : QDialog(parent), ui(new Ui::AddressBookPage), @@ -113,24 +148,12 @@ void AddressBookPage::setModel(AddressTableModel *_model) if(!_model) return; - proxyModel = new QSortFilterProxyModel(this); + auto type = tab == ReceivingTab ? AddressTableModel::Receive : AddressTableModel::Send; + proxyModel = new AddressBookSortFilterProxyModel(type, this); proxyModel->setSourceModel(_model); - proxyModel->setDynamicSortFilter(true); - proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - switch(tab) - { - case ReceivingTab: - // Receive filter - proxyModel->setFilterRole(AddressTableModel::TypeRole); - proxyModel->setFilterFixedString(AddressTableModel::Receive); - break; - case SendingTab: - // Send filter - proxyModel->setFilterRole(AddressTableModel::TypeRole); - proxyModel->setFilterFixedString(AddressTableModel::Send); - break; - } + + connect(ui->searchLineEdit, SIGNAL(textChanged(QString)), proxyModel, SLOT(setFilterWildcard(QString))); + ui->tableView->setModel(proxyModel); ui->tableView->sortByColumn(0, Qt::AscendingOrder); diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 54a43478d1..8877d07330 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -7,6 +7,7 @@ #include <QDialog> +class AddressBookSortFilterProxyModel; class AddressTableModel; class PlatformStyle; @@ -18,7 +19,6 @@ QT_BEGIN_NAMESPACE class QItemSelection; class QMenu; class QModelIndex; -class QSortFilterProxyModel; QT_END_NAMESPACE /** Widget that shows a list of sending or receiving addresses. @@ -53,7 +53,7 @@ private: Mode mode; Tabs tab; QString returnValue; - QSortFilterProxyModel *proxyModel; + AddressBookSortFilterProxyModel *proxyModel; QMenu *contextMenu; QAction *deleteAction; // to be able to explicitly disable it QString newAddressToSelect; diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index 264edeb720..7ac216286c 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -22,6 +22,13 @@ </widget> </item> <item> + <widget class="QLineEdit" name="searchLineEdit"> + <property name="placeholderText"> + <string>Enter address or label to search</string> + </property> + </widget> + </item> + <item> <widget class="QTableView" name="tableView"> <property name="contextMenuPolicy"> <enum>Qt::CustomContextMenu</enum> diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 8ad4fa31f1..4b6fdc8d57 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -770,7 +770,7 @@ bool PaymentServer::verifyExpired(const payments::PaymentDetails& requestDetails { bool fVerified = (requestDetails.has_expires() && (int64_t)requestDetails.expires() < GetTime()); if (fVerified) { - const QString requestExpires = QString::fromStdString(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires())); + const QString requestExpires = QString::fromStdString(FormatISO8601DateTime((int64_t)requestDetails.expires())); qWarning() << QString("PaymentServer::%1: Payment request expired \"%2\".") .arg(__func__) .arg(requestExpires); diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 19cdb0fdea..cc30cf747d 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -167,10 +167,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) // Determine transaction status // Find the block the tx is in - CBlockIndex* pindex = nullptr; - BlockMap::iterator mi = mapBlockIndex.find(wtx.hashBlock); - if (mi != mapBlockIndex.end()) - pindex = (*mi).second; + const CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock); // Sort order, unrecorded transactions sort to the top status.sortKey = strprintf("%010d-%01d-%010u-%03d", diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e7d9d276d7..39ef20c835 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -275,9 +275,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact int nChangePosRet = -1; std::string strFailReason; - CWalletTx *newTx = transaction.getTransaction(); + CTransactionRef& newTx = transaction.getTransaction(); CReserveKey *keyChange = transaction.getPossibleKeyChange(); - bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); + bool fCreated = wallet->CreateTransaction(vecSend, newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && fCreated) transaction.reassignAmounts(nChangePosRet); @@ -309,8 +309,8 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran { LOCK2(cs_main, wallet->cs_wallet); - CWalletTx *newTx = transaction.getTransaction(); + std::vector<std::pair<std::string, std::string>> vOrderForm; for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { if (rcp.paymentRequest.IsInitialized()) @@ -321,22 +321,22 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran } // Store PaymentRequests in wtx.vOrderForm in wallet. - std::string key("PaymentRequest"); std::string value; rcp.paymentRequest.SerializeToString(&value); - newTx->vOrderForm.push_back(make_pair(key, value)); + vOrderForm.emplace_back("PaymentRequest", std::move(value)); } else if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) - newTx->vOrderForm.push_back(make_pair("Message", rcp.message.toStdString())); + vOrderForm.emplace_back("Message", rcp.message.toStdString()); } + CTransactionRef& newTx = transaction.getTransaction(); CReserveKey *keyChange = transaction.getPossibleKeyChange(); CValidationState state; - if(!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), state)) + if (!wallet->CommitTransaction(newTx, {} /* mapValue */, std::move(vOrderForm), {} /* fromAccount */, *keyChange, g_connman.get(), state)) return SendCoinsReturn(TransactionCommitFailed, QString::fromStdString(state.GetRejectReason())); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << *newTx->tx; + ssTx << newTx; transaction_array.append(&(ssTx[0]), ssTx.size()); } diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index 4b2bef2690..4df8a5687e 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -12,12 +12,6 @@ WalletModelTransaction::WalletModelTransaction(const QList<SendCoinsRecipient> & walletTransaction(0), fee(0) { - walletTransaction = new CWalletTx(); -} - -WalletModelTransaction::~WalletModelTransaction() -{ - delete walletTransaction; } QList<SendCoinsRecipient> WalletModelTransaction::getRecipients() const @@ -25,14 +19,14 @@ QList<SendCoinsRecipient> WalletModelTransaction::getRecipients() const return recipients; } -CWalletTx *WalletModelTransaction::getTransaction() const +CTransactionRef& WalletModelTransaction::getTransaction() { return walletTransaction; } unsigned int WalletModelTransaction::getTransactionSize() { - return (!walletTransaction ? 0 : ::GetVirtualTransactionSize(*walletTransaction->tx)); + return (!walletTransaction ? 0 : ::GetVirtualTransactionSize(*walletTransaction)); } CAmount WalletModelTransaction::getTransactionFee() const @@ -62,7 +56,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet) if (out.amount() <= 0) continue; if (i == nChangePosRet) i++; - subtotal += walletTransaction->tx->vout[i].nValue; + subtotal += walletTransaction->vout[i].nValue; i++; } rcp.amount = subtotal; @@ -71,7 +65,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet) { if (i == nChangePosRet) i++; - rcp.amount = walletTransaction->tx->vout[i].nValue; + rcp.amount = walletTransaction->vout[i].nValue; i++; } } diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index cd531dba4b..931e960d18 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -20,11 +20,10 @@ class WalletModelTransaction { public: explicit WalletModelTransaction(const QList<SendCoinsRecipient> &recipients); - ~WalletModelTransaction(); QList<SendCoinsRecipient> getRecipients() const; - CWalletTx *getTransaction() const; + CTransactionRef& getTransaction(); unsigned int getTransactionSize(); void setTransactionFee(const CAmount& newFee); @@ -39,7 +38,7 @@ public: private: QList<SendCoinsRecipient> recipients; - CWalletTx *walletTransaction; + CTransactionRef walletTransaction; std::unique_ptr<CReserveKey> keyChange; CAmount fee; }; diff --git a/src/rest.cpp b/src/rest.cpp index 8cba59dbbc..f47b40343b 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -147,8 +147,7 @@ static bool rest_headers(HTTPRequest* req, headers.reserve(count); { LOCK(cs_main); - BlockMap::const_iterator it = mapBlockIndex.find(hash); - const CBlockIndex *pindex = (it != mapBlockIndex.end()) ? it->second : nullptr; + const CBlockIndex* pindex = LookupBlockIndex(hash); while (pindex != nullptr && chainActive.Contains(pindex)) { headers.push_back(pindex); if (headers.size() == (unsigned long)count) @@ -212,10 +211,11 @@ static bool rest_block(HTTPRequest* req, CBlockIndex* pblockindex = nullptr; { LOCK(cs_main); - if (mapBlockIndex.count(hash) == 0) + pblockindex = LookupBlockIndex(hash); + if (!pblockindex) { return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); + } - pblockindex = mapBlockIndex[hash]; if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index f2a1fd048f..2077723af5 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -709,10 +709,10 @@ UniValue getblockheader(const JSONRPCRequest& request) if (!request.params[1].isNull()) fVerbose = request.params[1].get_bool(); - if (mapBlockIndex.count(hash) == 0) + const CBlockIndex* pblockindex = LookupBlockIndex(hash); + if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); - - CBlockIndex* pblockindex = mapBlockIndex[hash]; + } if (!fVerbose) { @@ -788,12 +788,12 @@ UniValue getblock(const JSONRPCRequest& request) verbosity = request.params[1].get_bool() ? 1 : 0; } - if (mapBlockIndex.count(hash) == 0) + const CBlockIndex* pblockindex = LookupBlockIndex(hash); + if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } CBlock block; - CBlockIndex* pblockindex = mapBlockIndex[hash]; - if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); @@ -858,7 +858,7 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) stats.hashBlock = pcursor->GetBestBlock(); { LOCK(cs_main); - stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight; + stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight; } ss << stats.hashBlock; uint256 prevkey; @@ -1041,8 +1041,7 @@ UniValue gettxout(const JSONRPCRequest& request) } } - BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); - CBlockIndex *pindex = it->second; + const CBlockIndex* pindex = LookupBlockIndex(pcoinsTip->GetBestBlock()); ret.pushKV("bestblock", pindex->GetBlockHash().GetHex()); if (coin.nHeight == MEMPOOL_HEIGHT) { ret.pushKV("confirmations", 0); @@ -1436,10 +1435,10 @@ UniValue preciousblock(const JSONRPCRequest& request) { LOCK(cs_main); - if (mapBlockIndex.count(hash) == 0) + pblockindex = LookupBlockIndex(hash); + if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); - - pblockindex = mapBlockIndex[hash]; + } } CValidationState state; @@ -1472,10 +1471,11 @@ UniValue invalidateblock(const JSONRPCRequest& request) { LOCK(cs_main); - if (mapBlockIndex.count(hash) == 0) + CBlockIndex* pblockindex = LookupBlockIndex(hash); + if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } - CBlockIndex* pblockindex = mapBlockIndex[hash]; InvalidateBlock(state, Params(), pblockindex); } @@ -1510,10 +1510,11 @@ UniValue reconsiderblock(const JSONRPCRequest& request) { LOCK(cs_main); - if (mapBlockIndex.count(hash) == 0) + CBlockIndex* pblockindex = LookupBlockIndex(hash); + if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } - CBlockIndex* pblockindex = mapBlockIndex[hash]; ResetBlockFailureFlags(pblockindex); } @@ -1560,11 +1561,10 @@ UniValue getchaintxstats(const JSONRPCRequest& request) } else { uint256 hash = uint256S(request.params[1].get_str()); LOCK(cs_main); - auto it = mapBlockIndex.find(hash); - if (it == mapBlockIndex.end()) { + pindex = LookupBlockIndex(hash); + if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - pindex = it->second; if (!chainActive.Contains(pindex)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain"); } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 3073a49d0d..0537628763 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -396,9 +396,8 @@ UniValue getblocktemplate(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); uint256 hash = block.GetHash(); - BlockMap::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) { - CBlockIndex *pindex = mi->second; + const CBlockIndex* pindex = LookupBlockIndex(hash); + if (pindex) { if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) return "duplicate"; if (pindex->nStatus & BLOCK_FAILED_MASK) @@ -727,9 +726,8 @@ UniValue submitblock(const JSONRPCRequest& request) bool fBlockPresent = false; { LOCK(cs_main); - BlockMap::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) { - CBlockIndex *pindex = mi->second; + const CBlockIndex* pindex = LookupBlockIndex(hash); + if (pindex) { if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) { return "duplicate"; } @@ -743,9 +741,9 @@ UniValue submitblock(const JSONRPCRequest& request) { LOCK(cs_main); - BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); - if (mi != mapBlockIndex.end()) { - UpdateUncommittedBlockStructures(block, mi->second, Params().GetConsensus()); + const CBlockIndex* pindex = LookupBlockIndex(block.hashPrevBlock); + if (pindex) { + UpdateUncommittedBlockStructures(block, pindex, Params().GetConsensus()); } } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 8dcfb48e9a..20bfd3f355 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -48,9 +48,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) if (!hashBlock.IsNull()) { entry.pushKV("blockhash", hashBlock.GetHex()); - BlockMap::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end() && (*mi).second) { - CBlockIndex* pindex = (*mi).second; + CBlockIndex* pindex = LookupBlockIndex(hashBlock); + if (pindex) { if (chainActive.Contains(pindex)) { entry.pushKV("confirmations", 1 + chainActive.Height() - pindex->nHeight); entry.pushKV("time", pindex->GetBlockTime()); @@ -160,11 +159,10 @@ UniValue getrawtransaction(const JSONRPCRequest& request) if (!request.params[2].isNull()) { uint256 blockhash = ParseHashV(request.params[2], "parameter 3"); - BlockMap::iterator it = mapBlockIndex.find(blockhash); - if (it == mapBlockIndex.end()) { + blockindex = LookupBlockIndex(blockhash); + if (!blockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found"); } - blockindex = it->second; in_active_chain = chainActive.Contains(blockindex); } @@ -238,9 +236,10 @@ UniValue gettxoutproof(const JSONRPCRequest& request) if (!request.params[1].isNull()) { hashBlock = uint256S(request.params[1].get_str()); - if (!mapBlockIndex.count(hashBlock)) + pblockindex = LookupBlockIndex(hashBlock); + if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); - pblockindex = mapBlockIndex[hashBlock]; + } } else { // Loop through txids and try to find which block they're in. Exit loop once a block is found. for (const auto& tx : setTxids) { @@ -257,9 +256,10 @@ UniValue gettxoutproof(const JSONRPCRequest& request) CTransactionRef tx; if (!GetTransaction(oneTxid, tx, Params().GetConsensus(), hashBlock, false) || hashBlock.IsNull()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block"); - if (!mapBlockIndex.count(hashBlock)) + pblockindex = LookupBlockIndex(hashBlock); + if (!pblockindex) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt"); - pblockindex = mapBlockIndex[hashBlock]; + } } CBlock block; @@ -306,8 +306,10 @@ UniValue verifytxoutproof(const JSONRPCRequest& request) LOCK(cs_main); - if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()])) + const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); + if (!pindex || !chainActive.Contains(pindex)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); + } for (const uint256& hash : vMatch) res.push_back(hash.GetHex()); @@ -316,9 +318,10 @@ UniValue verifytxoutproof(const JSONRPCRequest& request) UniValue createrawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { throw std::runtime_error( - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...} ( locktime ) ( replaceable )\n" + // clang-format off + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" "\nCreate a transaction spending the given inputs and creating new outputs.\n" "Outputs can be addresses or data.\n" "Returns hex-encoded raw transaction.\n" @@ -329,18 +332,23 @@ UniValue createrawtransaction(const JSONRPCRequest& request) "1. \"inputs\" (array, required) A json array of json objects\n" " [\n" " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" + " \"txid\":\"id\", (string, required) The transaction id\n" " \"vout\":n, (numeric, required) The output number\n" " \"sequence\":n (numeric, optional) The sequence number\n" " } \n" " ,...\n" " ]\n" - "2. \"outputs\" (object, required) a json object with outputs\n" + "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" + " [\n" " {\n" - " \"address\": x.xxx, (numeric or string, required) The key is the bitcoin address, the numeric value (can be string) is the " + CURRENCY_UNIT + " amount\n" - " \"data\": \"hex\" (string, required) The key is \"data\", the value is hex encoded data\n" - " ,...\n" + " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" + " },\n" + " {\n" + " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" " }\n" + " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.\n" + " ]\n" "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" @@ -348,18 +356,29 @@ UniValue createrawtransaction(const JSONRPCRequest& request) "\"transaction\" (string) hex string of the transaction\n" "\nExamples:\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"") - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"data\\\":\\\"00010203\\\"}\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"address\\\":0.01}\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"data\\\":\\\"00010203\\\"}\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + // clang-format on ); + } - RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ, UniValue::VNUM, UniValue::VBOOL}, true); + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL + }, true + ); if (request.params[0].isNull() || request.params[1].isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); UniValue inputs = request.params[0].get_array(); - UniValue sendTo = request.params[1].get_obj(); + const bool outputs_is_obj = request.params[1].isObject(); + UniValue outputs = outputs_is_obj ? + request.params[1].get_obj() : + request.params[1].get_array(); CMutableTransaction rawTx; @@ -411,11 +430,24 @@ UniValue createrawtransaction(const JSONRPCRequest& request) } std::set<CTxDestination> destinations; - std::vector<std::string> addrList = sendTo.getKeys(); - for (const std::string& name_ : addrList) { - + if (!outputs_is_obj) { + // Translate array of key-value pairs into dict + UniValue outputs_dict = UniValue(UniValue::VOBJ); + for (size_t i = 0; i < outputs.size(); ++i) { + const UniValue& output = outputs[i]; + if (!output.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair not an object as expected"); + } + if (output.size() != 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair must contain exactly one key"); + } + outputs_dict.pushKVs(output); + } + outputs = std::move(outputs_dict); + } + for (const std::string& name_ : outputs.getKeys()) { if (name_ == "data") { - std::vector<unsigned char> data = ParseHexV(sendTo[name_].getValStr(),"Data"); + std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data"); CTxOut out(0, CScript() << OP_RETURN << data); rawTx.vout.push_back(out); @@ -430,7 +462,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request) } CScript scriptPubKey = GetScriptForDestination(destination); - CAmount nAmount = AmountFromValue(sendTo[name_]); + CAmount nAmount = AmountFromValue(outputs[name_]); CTxOut out(nAmount, scriptPubKey); rawTx.vout.push_back(out); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 35401bf876..54995ef000 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -50,12 +50,11 @@ void RPCServer::OnStopped(std::function<void ()> slot) } void RPCTypeCheck(const UniValue& params, - const std::list<UniValue::VType>& typesExpected, + const std::list<UniValueType>& typesExpected, bool fAllowNull) { unsigned int i = 0; - for (UniValue::VType t : typesExpected) - { + for (const UniValueType& t : typesExpected) { if (params.size() <= i) break; @@ -67,10 +66,10 @@ void RPCTypeCheck(const UniValue& params, } } -void RPCTypeCheckArgument(const UniValue& value, UniValue::VType typeExpected) +void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected) { - if (value.type() != typeExpected) { - throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected), uvTypeName(value.type()))); + if (!typeExpected.typeAny && value.type() != typeExpected.type) { + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected.type), uvTypeName(value.type()))); } } diff --git a/src/rpc/server.h b/src/rpc/server.h index 075940cb90..8b32924fbc 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -28,9 +28,9 @@ namespace RPCServer } /** Wrapper for UniValue::VType, which includes typeAny: - * Used to denote don't care type. Only used by RPCTypeCheckObj */ + * Used to denote don't care type. */ struct UniValueType { - explicit UniValueType(UniValue::VType _type) : typeAny(false), type(_type) {} + UniValueType(UniValue::VType _type) : typeAny(false), type(_type) {} UniValueType() : typeAny(true) {} bool typeAny; UniValue::VType type; @@ -69,12 +69,12 @@ bool RPCIsInWarmup(std::string *outStatus); * the right number of arguments are passed, just that any passed are the correct type. */ void RPCTypeCheck(const UniValue& params, - const std::list<UniValue::VType>& typesExpected, bool fAllowNull=false); + const std::list<UniValueType>& typesExpected, bool fAllowNull=false); /** * Type-check one argument; throws JSONRPCError if wrong type given. */ -void RPCTypeCheckArgument(const UniValue& value, UniValue::VType typeExpected); +void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected); /* Check for expected keys/value types in an Object. diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 892e4f2dac..8d9f80ada0 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -52,7 +52,6 @@ BOOST_AUTO_TEST_CASE(rpc_rawparams) BOOST_CHECK_THROW(CallRPC("createrawtransaction"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("createrawtransaction null null"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("createrawtransaction not_array"), std::runtime_error); - BOOST_CHECK_THROW(CallRPC("createrawtransaction [] []"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("createrawtransaction {} {}"), std::runtime_error); BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [] {}")); BOOST_CHECK_THROW(CallRPC("createrawtransaction [] {} extra"), std::runtime_error); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 58f033cd89..84b61bea86 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -164,10 +164,27 @@ BOOST_AUTO_TEST_CASE(util_DateTimeStrFormat) BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0), "1970-01-01 00:00:00"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0x7FFFFFFF), "2038-01-19 03:14:07"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 1317425777), "2011-09-30 23:36:17"); + BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", 1317425777), "2011-09-30T23:36:17Z"); + BOOST_CHECK_EQUAL(DateTimeStrFormat("%H:%M:%SZ", 1317425777), "23:36:17Z"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M", 1317425777), "2011-09-30 23:36"); BOOST_CHECK_EQUAL(DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", 1317425777), "Fri, 30 Sep 2011 23:36:17 +0000"); } +BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime) +{ + BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z"); +} + +BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) +{ + BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30"); +} + +BOOST_AUTO_TEST_CASE(util_FormatISO8601Time) +{ + BOOST_CHECK_EQUAL(FormatISO8601Time(1317425777), "23:36:17Z"); +} + class TestArgsManager : public ArgsManager { public: diff --git a/src/util.cpp b/src/util.cpp index 82c99a3c2f..62cdce3012 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -315,12 +315,14 @@ static std::string LogTimestampStr(const std::string &str, std::atomic_bool *fSt if (*fStartedNewLine) { int64_t nTimeMicros = GetTimeMicros(); - strStamped = DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nTimeMicros/1000000); - if (fLogTimeMicros) - strStamped += strprintf(".%06d", nTimeMicros%1000000); + strStamped = FormatISO8601DateTime(nTimeMicros/1000000); + if (fLogTimeMicros) { + strStamped.pop_back(); + strStamped += strprintf(".%06dZ", nTimeMicros%1000000); + } int64_t mocktime = GetMockTime(); if (mocktime) { - strStamped += " (mocktime: " + DateTimeStrFormat("%Y-%m-%d %H:%M:%S", mocktime) + ")"; + strStamped += " (mocktime: " + FormatISO8601DateTime(mocktime) + ")"; } strStamped += ' ' + str; } else diff --git a/src/utiltime.cpp b/src/utiltime.cpp index e908173135..8a861039b3 100644 --- a/src/utiltime.cpp +++ b/src/utiltime.cpp @@ -85,3 +85,15 @@ std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime) ss << boost::posix_time::from_time_t(nTime); return ss.str(); } + +std::string FormatISO8601DateTime(int64_t nTime) { + return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime); +} + +std::string FormatISO8601Date(int64_t nTime) { + return DateTimeStrFormat("%Y-%m-%d", nTime); +} + +std::string FormatISO8601Time(int64_t nTime) { + return DateTimeStrFormat("%H:%M:%SZ", nTime); +} diff --git a/src/utiltime.h b/src/utiltime.h index 56cc31da67..807c52ffaf 100644 --- a/src/utiltime.h +++ b/src/utiltime.h @@ -27,6 +27,14 @@ void SetMockTime(int64_t nMockTimeIn); int64_t GetMockTime(); void MilliSleep(int64_t n); +/** + * ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date,Time} + * helper functions if possible. + */ std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime); +std::string FormatISO8601DateTime(int64_t nTime); +std::string FormatISO8601Date(int64_t nTime); +std::string FormatISO8601Time(int64_t nTime); + #endif // BITCOIN_UTILTIME_H diff --git a/src/validation.cpp b/src/validation.cpp index 51e40c17b5..f49dc5a155 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -260,12 +260,12 @@ namespace { CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) { + AssertLockHeld(cs_main); + // Find the first block the caller has in the main chain for (const uint256& hash : locator.vHave) { - BlockMap::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) - { - CBlockIndex* pindex = (*mi).second; + CBlockIndex* pindex = LookupBlockIndex(hash); + if (pindex) { if (chain.Contains(pindex)) return pindex; if (pindex->GetAncestor(chain.Height()) == chain.Tip()) { @@ -1267,13 +1267,12 @@ void static InvalidChainFound(CBlockIndex* pindexNew) LogPrintf("%s: invalid block=%s height=%d log2_work=%.8g date=%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, - log(pindexNew->nChainWork.getdouble())/log(2.0), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", - pindexNew->GetBlockTime())); + log(pindexNew->nChainWork.getdouble())/log(2.0), FormatISO8601DateTime(pindexNew->GetBlockTime())); CBlockIndex *tip = chainActive.Tip(); assert (tip); LogPrintf("%s: current best=%s height=%d log2_work=%.8g date=%s\n", __func__, tip->GetBlockHash().ToString(), chainActive.Height(), log(tip->nChainWork.getdouble())/log(2.0), - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", tip->GetBlockTime())); + FormatISO8601DateTime(tip->GetBlockTime())); CheckForkWarningConditions(); } @@ -1317,7 +1316,7 @@ bool CScriptCheck::operator()() { int GetSpendHeight(const CCoinsViewCache& inputs) { LOCK(cs_main); - CBlockIndex* pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second; + CBlockIndex* pindexPrev = LookupBlockIndex(inputs.GetBestBlock()); return pindexPrev->nHeight + 1; } @@ -2229,7 +2228,7 @@ void static UpdateTip(const CBlockIndex *pindexNew, const CChainParams& chainPar LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexNew->GetBlockTime()), + FormatISO8601DateTime(pindexNew->GetBlockTime()), GuessVerificationProgress(chainParams.TxData(), pindexNew), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize()); if (!warningMessages.empty()) LogPrintf(" warning='%s'", boost::algorithm::join(warningMessages, ", ")); @@ -2827,6 +2826,8 @@ bool ResetBlockFailureFlags(CBlockIndex *pindex) { CBlockIndex* CChainState::AddToBlockIndex(const CBlockHeader& block) { + AssertLockHeld(cs_main); + // Check for duplicate uint256 hash = block.GetHash(); BlockMap::iterator it = mapBlockIndex.find(hash); @@ -3273,7 +3274,6 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& BlockMap::iterator miSelf = mapBlockIndex.find(hash); CBlockIndex *pindex = nullptr; if (hash != chainparams.GetConsensus().hashGenesisBlock) { - if (miSelf != mapBlockIndex.end()) { // Block header is already known. pindex = miSelf->second; @@ -3707,6 +3707,8 @@ fs::path GetBlockPosFilename(const CDiskBlockPos &pos, const char *prefix) CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash) { + AssertLockHeld(cs_main); + if (hash.IsNull()) return nullptr; @@ -3834,6 +3836,8 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) bool LoadChainTip(const CChainParams& chainparams) { + AssertLockHeld(cs_main); + if (chainActive.Tip() && chainActive.Tip()->GetBlockHash() == pcoinsTip->GetBestBlock()) return true; if (pcoinsTip->GetBestBlock().IsNull() && mapBlockIndex.size() == 1) { @@ -3847,16 +3851,17 @@ bool LoadChainTip(const CChainParams& chainparams) } // Load pointer to end of best chain - BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); - if (it == mapBlockIndex.end()) + CBlockIndex* pindex = LookupBlockIndex(pcoinsTip->GetBestBlock()); + if (!pindex) { return false; - chainActive.SetTip(it->second); + } + chainActive.SetTip(pindex); g_chainstate.PruneBlockIndexCandidates(); LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n", chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), + FormatISO8601DateTime(chainActive.Tip()->GetBlockTime()), GuessVerificationProgress(chainparams.TxData(), chainActive.Tip())); return true; } @@ -4297,26 +4302,31 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB blkdat >> block; nRewind = blkdat.GetPos(); - // detect out of order blocks, and store them for later uint256 hash = block.GetHash(); - if (hash != chainparams.GetConsensus().hashGenesisBlock && mapBlockIndex.find(block.hashPrevBlock) == mapBlockIndex.end()) { - LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(), - block.hashPrevBlock.ToString()); - if (dbp) - mapBlocksUnknownParent.insert(std::make_pair(block.hashPrevBlock, *dbp)); - continue; - } - - // process in case the block isn't known yet - if (mapBlockIndex.count(hash) == 0 || (mapBlockIndex[hash]->nStatus & BLOCK_HAVE_DATA) == 0) { + { LOCK(cs_main); - CValidationState state; - if (g_chainstate.AcceptBlock(pblock, state, chainparams, nullptr, true, dbp, nullptr)) - nLoaded++; - if (state.IsError()) - break; - } else if (hash != chainparams.GetConsensus().hashGenesisBlock && mapBlockIndex[hash]->nHeight % 1000 == 0) { - LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), mapBlockIndex[hash]->nHeight); + // detect out of order blocks, and store them for later + if (hash != chainparams.GetConsensus().hashGenesisBlock && !LookupBlockIndex(block.hashPrevBlock)) { + LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(), + block.hashPrevBlock.ToString()); + if (dbp) + mapBlocksUnknownParent.insert(std::make_pair(block.hashPrevBlock, *dbp)); + continue; + } + + // process in case the block isn't known yet + CBlockIndex* pindex = LookupBlockIndex(hash); + if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) { + CValidationState state; + if (g_chainstate.AcceptBlock(pblock, state, chainparams, nullptr, true, dbp, nullptr)) { + nLoaded++; + } + if (state.IsError()) { + break; + } + } else if (hash != chainparams.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) { + LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight); + } } // Activate the genesis block so normal node progress can continue @@ -4554,7 +4564,7 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) std::string CBlockFileInfo::ToString() const { - return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, DateTimeStrFormat("%Y-%m-%d", nTimeFirst), DateTimeStrFormat("%Y-%m-%d", nTimeLast)); + return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast)); } CBlockFileInfo* GetBlockFileInfo(size_t n) diff --git a/src/validation.h b/src/validation.h index 99cbfdf1ee..e780f453b2 100644 --- a/src/validation.h +++ b/src/validation.h @@ -428,6 +428,13 @@ public: /** Replay blocks that aren't fully applied to the database. */ bool ReplayBlocks(const CChainParams& params, CCoinsView* view); +inline CBlockIndex* LookupBlockIndex(const uint256& hash) +{ + AssertLockHeld(cs_main); + BlockMap::const_iterator it = mapBlockIndex.find(hash); + return it == mapBlockIndex.end() ? nullptr : it->second; +} + /** Find the last common block between the parameter chain and a locator. */ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator); diff --git a/src/validationinterface.h b/src/validationinterface.h index 56ea698a2e..63097166af 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -56,6 +56,11 @@ void SyncWithValidationInterfaceQueue(); class CValidationInterface { protected: /** + * Protected destructor so that instances can only be deleted by derived classes. + * If that restriction is no longer desired, this should be made public and virtual. + */ + ~CValidationInterface() = default; + /** * Notifies listeners of updated block chain tip * * Called on a background thread. diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 9b85176ca0..82a5017de0 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -235,23 +235,20 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti return result; } - CWalletTx wtxBumped(wallet, MakeTransactionRef(std::move(mtx))); // commit/broadcast the tx + CTransactionRef tx = MakeTransactionRef(std::move(mtx)); + mapValue_t mapValue = oldWtx.mapValue; + mapValue["replaces_txid"] = oldWtx.GetHash().ToString(); + CReserveKey reservekey(wallet); - wtxBumped.mapValue = oldWtx.mapValue; - wtxBumped.mapValue["replaces_txid"] = oldWtx.GetHash().ToString(); - wtxBumped.vOrderForm = oldWtx.vOrderForm; - wtxBumped.strFromAccount = oldWtx.strFromAccount; - wtxBumped.fTimeReceivedIsTxTime = true; - wtxBumped.fFromMe = true; CValidationState state; - if (!wallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) { + if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, oldWtx.strFromAccount, reservekey, g_connman.get(), state)) { // NOTE: CommitTransaction never returns false, so this should never happen. errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state))); return Result::WALLET_ERROR; } - bumped_txid = wtxBumped.GetHash(); + bumped_txid = tx->GetHash(); if (state.IsInvalid()) { // This can happen if the mempool rejected the transaction. Report // what happened in the "errors" response. @@ -259,7 +256,7 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti } // mark the original tx as bumped - if (!wallet->MarkReplaced(oldWtx.GetHash(), wtxBumped.GetHash())) { + if (!wallet->MarkReplaced(oldWtx.GetHash(), bumped_txid)) { // TODO: see if JSON-RPC has a standard way of returning a response // along with an exception. It would be good to return information about // wtxBumped to the caller even if marking the original transaction diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 0edc8d8d66..01125dd618 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -28,10 +28,6 @@ #include <univalue.h> -std::string static EncodeDumpTime(int64_t nTime) { - return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime); -} - int64_t static DecodeDumpTime(const std::string &str) { static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); static const std::locale loc(std::locale::classic(), @@ -354,9 +350,10 @@ UniValue importprunedfunds(const JSONRPCRequest& request) if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { LOCK(cs_main); - - if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()])) + const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); + if (!pindex || !chainActive.Contains(pindex)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); + } std::vector<uint256>::const_iterator it; if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx))==vMatch.end()) { @@ -722,9 +719,9 @@ UniValue dumpwallet(const JSONRPCRequest& request) // produce output file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); - file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime())); + file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); - file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime())); + file << strprintf("# mined on %s\n", FormatISO8601DateTime(chainActive.Tip()->GetBlockTime())); file << "\n"; // add the base58check encoded extended master if the wallet uses HD @@ -741,7 +738,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) } for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; - std::string strTime = EncodeDumpTime(it->first); + std::string strTime = FormatISO8601DateTime(it->first); std::string strAddr; std::string strLabel; CKey key; @@ -769,7 +766,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) // get birth times for scripts with metadata auto it = pwallet->m_script_metadata.find(scriptid); if (it != pwallet->m_script_metadata.end()) { - create_time = EncodeDumpTime(it->second.nCreateTime); + create_time = FormatISO8601DateTime(it->second.nCreateTime); } if(pwallet->GetCScript(scriptid, script)) { file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 457abec1bc..7ad9efff70 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -95,7 +95,7 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) { entry.pushKV("blockhash", wtx.hashBlock.GetHex()); entry.pushKV("blockindex", wtx.nIndex); - entry.pushKV("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()); + entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime()); } else { entry.pushKV("trusted", wtx.IsTrusted()); } @@ -404,7 +404,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request) return ret; } -static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, const CCoinControl& coin_control) +static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue, std::string fromAccount) { CAmount curBalance = pwallet->GetBalance(); @@ -430,16 +430,18 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); - if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { + CTransactionRef tx; + if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } CValidationState state; - if (!pwallet->CommitTransaction(wtxNew, reservekey, g_connman.get(), state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, std::move(fromAccount), reservekey, g_connman.get(), state)) { strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } + return tx; } UniValue sendtoaddress(const JSONRPCRequest& request) @@ -498,11 +500,11 @@ UniValue sendtoaddress(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); // Wallet comments - CWalletTx wtx; + mapValue_t mapValue; if (!request.params[2].isNull() && !request.params[2].get_str().empty()) - wtx.mapValue["comment"] = request.params[2].get_str(); + mapValue["comment"] = request.params[2].get_str(); if (!request.params[3].isNull() && !request.params[3].get_str().empty()) - wtx.mapValue["to"] = request.params[3].get_str(); + mapValue["to"] = request.params[3].get_str(); bool fSubtractFeeFromAmount = false; if (!request.params[4].isNull()) { @@ -527,9 +529,8 @@ UniValue sendtoaddress(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, wtx, coin_control); - - return wtx.GetHash().GetHex(); + CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue), {} /* fromAccount */); + return tx->GetHash().GetHex(); } UniValue listaddressgroupings(const JSONRPCRequest& request) @@ -995,12 +996,11 @@ UniValue sendfrom(const JSONRPCRequest& request) if (!request.params[3].isNull()) nMinDepth = request.params[3].get_int(); - CWalletTx wtx; - wtx.strFromAccount = strAccount; + mapValue_t mapValue; if (!request.params[4].isNull() && !request.params[4].get_str().empty()) - wtx.mapValue["comment"] = request.params[4].get_str(); + mapValue["comment"] = request.params[4].get_str(); if (!request.params[5].isNull() && !request.params[5].get_str().empty()) - wtx.mapValue["to"] = request.params[5].get_str(); + mapValue["to"] = request.params[5].get_str(); EnsureWalletIsUnlocked(pwallet); @@ -1010,9 +1010,8 @@ UniValue sendfrom(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); CCoinControl no_coin_control; // This is a deprecated API - SendMoney(pwallet, dest, nAmount, false, wtx, no_coin_control); - - return wtx.GetHash().GetHex(); + CTransactionRef tx = SendMoney(pwallet, dest, nAmount, false, no_coin_control, std::move(mapValue), std::move(strAccount)); + return tx->GetHash().GetHex(); } @@ -1083,10 +1082,9 @@ UniValue sendmany(const JSONRPCRequest& request) if (!request.params[2].isNull()) nMinDepth = request.params[2].get_int(); - CWalletTx wtx; - wtx.strFromAccount = strAccount; + mapValue_t mapValue; if (!request.params[3].isNull() && !request.params[3].get_str().empty()) - wtx.mapValue["comment"] = request.params[3].get_str(); + mapValue["comment"] = request.params[3].get_str(); UniValue subtractFeeFromAmount(UniValue::VARR); if (!request.params[4].isNull()) @@ -1152,16 +1150,17 @@ UniValue sendmany(const JSONRPCRequest& request) CAmount nFeeRequired = 0; int nChangePosRet = -1; std::string strFailReason; - bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); + CTransactionRef tx; + bool fCreated = pwallet->CreateTransaction(vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; - if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, std::move(strAccount), keyChange, g_connman.get(), state)) { strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } - return wtx.GetHash().GetHex(); + return tx->GetHash().GetHex(); } UniValue addmultisigaddress(const JSONRPCRequest& request) @@ -2043,11 +2042,10 @@ UniValue listsinceblock(const JSONRPCRequest& request) uint256 blockId; blockId.SetHex(request.params[0].get_str()); - BlockMap::iterator it = mapBlockIndex.find(blockId); - if (it == mapBlockIndex.end()) { + paltindex = pindex = LookupBlockIndex(blockId); + if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - paltindex = pindex = it->second; if (chainActive[pindex->nHeight] != pindex) { // the block being asked for is a part of a deactivated chain; // we don't want to depend on its perceived height in the block diff --git a/src/wallet/test/accounting_tests.cpp b/src/wallet/test/accounting_tests.cpp index 7b20bd7b02..aae328d81f 100644 --- a/src/wallet/test/accounting_tests.cpp +++ b/src/wallet/test/accounting_tests.cpp @@ -29,7 +29,7 @@ GetResults(CWallet& wallet, std::map<CAmount, CAccountingEntry>& results) BOOST_AUTO_TEST_CASE(acc_orderupgrade) { std::vector<CWalletTx*> vpwtx; - CWalletTx wtx; + CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); CAccountingEntry ae; std::map<CAmount, CAccountingEntry> results; @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade) wtx.mapValue["comment"] = "z"; m_wallet.AddToWallet(wtx); - vpwtx.push_back(&m_wallet.mapWallet[wtx.GetHash()]); + vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash())); vpwtx[0]->nTimeReceived = (unsigned int)1333333335; vpwtx[0]->nOrderPos = -1; @@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade) wtx.SetTx(MakeTransactionRef(std::move(tx))); } m_wallet.AddToWallet(wtx); - vpwtx.push_back(&m_wallet.mapWallet[wtx.GetHash()]); + vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash())); vpwtx[1]->nTimeReceived = (unsigned int)1333333336; wtx.mapValue["comment"] = "x"; @@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade) wtx.SetTx(MakeTransactionRef(std::move(tx))); } m_wallet.AddToWallet(wtx); - vpwtx.push_back(&m_wallet.mapWallet[wtx.GetHash()]); + vpwtx.push_back(&m_wallet.mapWallet.at(wtx.GetHash())); vpwtx[2]->nTimeReceived = (unsigned int)1333333329; vpwtx[2]->nOrderPos = -1; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index a9e127fe2a..d6b0daf4fe 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -216,7 +216,10 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 if (block) { wtx.SetMerkleBranch(block, 0); } - wallet.AddToWallet(wtx); + { + LOCK(cs_main); + wallet.AddToWallet(wtx); + } LOCK(wallet.cs_wallet); return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart; } @@ -287,23 +290,23 @@ public: CWalletTx& AddTx(CRecipient recipient) { - CWalletTx wtx; + CTransactionRef tx; CReserveKey reservekey(wallet.get()); CAmount fee; int changePos = -1; std::string error; CCoinControl dummy; - BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, reservekey, fee, changePos, error, dummy)); CValidationState state; - BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state)); + BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, {}, reservekey, nullptr, state)); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); - blocktx = CMutableTransaction(*wallet->mapWallet.at(wtx.GetHash()).tx); + blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetHash()).tx); } CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); - auto it = wallet->mapWallet.find(wtx.GetHash()); + auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); it->second.SetMerkleBranch(chainActive.Tip(), 1); return it->second; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0a469d7aed..bd5094085e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -523,7 +523,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran int nMinOrderPos = std::numeric_limits<int>::max(); const CWalletTx* copyFrom = nullptr; for (TxSpends::iterator it = range.first; it != range.second; ++it) { - const CWalletTx* wtx = &mapWallet[it->second]; + const CWalletTx* wtx = &mapWallet.at(it->second); if (wtx->nOrderPos < nMinOrderPos) { nMinOrderPos = wtx->nOrderPos;; copyFrom = wtx; @@ -536,7 +536,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran for (TxSpends::iterator it = range.first; it != range.second; ++it) { const uint256& hash = it->second; - CWalletTx* copyTo = &mapWallet[hash]; + CWalletTx* copyTo = &mapWallet.at(hash); if (copyFrom == copyTo) continue; assert(copyFrom && "Oldest wallet transaction in range assumed to have been found."); if (!copyFrom->IsEquivalentTo(*copyTo)) continue; @@ -1139,11 +1139,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) LOCK2(cs_main, cs_wallet); int conflictconfirms = 0; - if (mapBlockIndex.count(hashBlock)) { - CBlockIndex* pindex = mapBlockIndex[hashBlock]; - if (chainActive.Contains(pindex)) { - conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1); - } + CBlockIndex* pindex = LookupBlockIndex(hashBlock); + if (pindex && chainActive.Contains(pindex)) { + conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1); } // If number of conflict confirms cannot be determined, this means // that the block is still unknown or not yet part of the main chain, @@ -2616,13 +2614,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC LOCK2(cs_main, cs_wallet); CReserveKey reservekey(this); - CWalletTx wtx; - if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + CTransactionRef tx_new; + if (!CreateTransaction(vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } if (nChangePosInOut != -1) { - tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); + tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); // We don't have the normal Create/Commit cycle, and don't want to risk // reusing change, so just remove the key from the keypool here. reservekey.KeepKey(); @@ -2631,11 +2629,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC // Copy output sizes from new transaction; they may have had the fee // subtracted from them. for (unsigned int idx = 0; idx < tx.vout.size(); idx++) { - tx.vout[idx].nValue = wtx.tx->vout[idx].nValue; + tx.vout[idx].nValue = tx_new->vout[idx].nValue; } // Add new txins while keeping original txin scriptSig/order. - for (const CTxIn& txin : wtx.tx->vin) { + for (const CTxIn& txin : tx_new->vin) { if (!coinControl.IsSelected(txin.prevout)) { tx.vin.push_back(txin); @@ -2676,7 +2674,7 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec return g_address_type; } -bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, +bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; @@ -2700,8 +2698,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT return false; } - wtxNew.fTimeReceivedIsTxTime = true; - wtxNew.BindWallet(this); CMutableTransaction txNew; // Discourage fee sniping. @@ -2798,7 +2794,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT nChangePosInOut = nChangePosRequest; txNew.vin.clear(); txNew.vout.clear(); - wtxNew.fFromMe = true; bool fFirst = true; CAmount nValueToSelect = nValue; @@ -3020,11 +3015,11 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } } - // Embed the constructed transaction data in wtxNew. - wtxNew.SetTx(MakeTransactionRef(std::move(txNew))); + // Return the constructed transaction data. + tx = MakeTransactionRef(std::move(txNew)); // Limit size - if (GetTransactionWeight(*wtxNew.tx) >= MAX_STANDARD_TX_WEIGHT) + if (GetTransactionWeight(*tx) >= MAX_STANDARD_TX_WEIGHT) { strFailReason = _("Transaction too large"); return false; @@ -3034,7 +3029,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits LockPoints lp; - CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, false, 0, lp); + CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp); CTxMemPool::setEntries setAncestors; size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000; @@ -3061,10 +3056,18 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT /** * Call after CreateTransaction unless you want to abort */ -bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state) +bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::string fromAccount, CReserveKey& reservekey, CConnman* connman, CValidationState& state) { { LOCK2(cs_main, cs_wallet); + + CWalletTx wtxNew(this, std::move(tx)); + wtxNew.mapValue = std::move(mapValue); + wtxNew.vOrderForm = std::move(orderForm); + wtxNew.strFromAccount = std::move(fromAccount); + wtxNew.fTimeReceivedIsTxTime = true; + wtxNew.fFromMe = true; + LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); { // Take key pair from key pool so it won't be used again @@ -3077,7 +3080,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon // Notify that old coins are spent for (const CTxIn& txin : wtxNew.tx->vin) { - CWalletTx &coin = mapWallet[txin.prevout.hash]; + CWalletTx &coin = mapWallet.at(txin.prevout.hash); coin.BindWallet(this); NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); } @@ -3088,7 +3091,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon // Get the inserted-CWalletTx from mapWallet so that the // fInMempool flag is cached properly - CWalletTx& wtx = mapWallet[wtxNew.GetHash()]; + CWalletTx& wtx = mapWallet.at(wtxNew.GetHash()); if (fBroadcastTransactions) { @@ -3544,7 +3547,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() CTxDestination address; if(!IsMine(txin)) /* If this input isn't mine, ignore it */ continue; - if(!ExtractDestination(mapWallet[txin.prevout.hash].tx->vout[txin.prevout.n].scriptPubKey, address)) + if(!ExtractDestination(mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address)) continue; grouping.insert(address); any_mine = true; @@ -3768,10 +3771,10 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c for (const auto& entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; - BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); - if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) { + CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock); + if (pindex && chainActive.Contains(pindex)) { // ... which are already in a block - int nHeight = blit->second->nHeight; + int nHeight = pindex->nHeight; for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); @@ -3779,7 +3782,7 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c // ... and all their affected keys std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid); if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) - rit->second = blit->second; + rit->second = pindex; } vAffected.clear(); } @@ -3816,7 +3819,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const { unsigned int nTimeSmart = wtx.nTimeReceived; if (!wtx.hashUnset()) { - if (mapBlockIndex.count(wtx.hashBlock)) { + if (const CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock)) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; @@ -3847,7 +3850,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const } } - int64_t blocktime = mapBlockIndex[wtx.hashBlock]->GetBlockTime(); + int64_t blocktime = pindex->GetBlockTime(); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { LogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); @@ -4017,6 +4020,8 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); + LOCK(cs_main); + CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) { @@ -4160,10 +4165,7 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const AssertLockHeld(cs_main); // Find the block it claims to be in - BlockMap::iterator mi = mapBlockIndex.find(hashBlock); - if (mi == mapBlockIndex.end()) - return 0; - CBlockIndex* pindex = (*mi).second; + CBlockIndex* pindex = LookupBlockIndex(hashBlock); if (!pindex || !chainActive.Contains(pindex)) return 0; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f50cde60b9..dc0e8d07d7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -348,11 +348,6 @@ public: mutable CAmount nAvailableWatchCreditCached; mutable CAmount nChangeCached; - CWalletTx() - { - Init(nullptr); - } - CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg)) { Init(pwalletIn); @@ -390,42 +385,36 @@ public: nOrderPos = -1; } - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { - if (ser_action.ForRead()) - Init(nullptr); + template<typename Stream> + void Serialize(Stream& s) const + { char fSpent = false; + mapValue_t mapValueCopy = mapValue; - if (!ser_action.ForRead()) - { - mapValue["fromaccount"] = strFromAccount; - - WriteOrderPos(nOrderPos, mapValue); - - if (nTimeSmart) - mapValue["timesmart"] = strprintf("%u", nTimeSmart); + mapValueCopy["fromaccount"] = strFromAccount; + WriteOrderPos(nOrderPos, mapValueCopy); + if (nTimeSmart) { + mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart); } - READWRITE(*static_cast<CMerkleTx*>(this)); + s << *static_cast<const CMerkleTx*>(this); std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev - READWRITE(vUnused); - READWRITE(mapValue); - READWRITE(vOrderForm); - READWRITE(fTimeReceivedIsTxTime); - READWRITE(nTimeReceived); - READWRITE(fFromMe); - READWRITE(fSpent); - - if (ser_action.ForRead()) - { - strFromAccount = mapValue["fromaccount"]; + s << vUnused << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << fSpent; + } + + template<typename Stream> + void Unserialize(Stream& s) + { + Init(nullptr); + char fSpent; - ReadOrderPos(nOrderPos, mapValue); + s >> *static_cast<CMerkleTx*>(this); + std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev + s >> vUnused >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> fSpent; - nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; - } + strFromAccount = std::move(mapValue["fromaccount"]); + ReadOrderPos(nOrderPos, mapValue); + nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; mapValue.erase("fromaccount"); mapValue.erase("spent"); @@ -592,48 +581,49 @@ public: nEntryNo = 0; } - ADD_SERIALIZE_METHODS; - - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) { + template <typename Stream> + void Serialize(Stream& s) const { int nVersion = s.GetVersion(); - if (!(s.GetType() & SER_GETHASH)) - READWRITE(nVersion); + if (!(s.GetType() & SER_GETHASH)) { + s << nVersion; + } //! Note: strAccount is serialized as part of the key, not here. - READWRITE(nCreditDebit); - READWRITE(nTime); - READWRITE(LIMITED_STRING(strOtherAccount, 65536)); - - if (!ser_action.ForRead()) - { - WriteOrderPos(nOrderPos, mapValue); - - if (!(mapValue.empty() && _ssExtra.empty())) - { - CDataStream ss(s.GetType(), s.GetVersion()); - ss.insert(ss.begin(), '\0'); - ss << mapValue; - ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end()); - strComment.append(ss.str()); - } + s << nCreditDebit << nTime << strOtherAccount; + + mapValue_t mapValueCopy = mapValue; + WriteOrderPos(nOrderPos, mapValueCopy); + + std::string strCommentCopy = strComment; + if (!mapValueCopy.empty() || !_ssExtra.empty()) { + CDataStream ss(s.GetType(), s.GetVersion()); + ss.insert(ss.begin(), '\0'); + ss << mapValueCopy; + ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end()); + strCommentCopy.append(ss.str()); } + s << strCommentCopy; + } - READWRITE(LIMITED_STRING(strComment, 65536)); + template <typename Stream> + void Unserialize(Stream& s) { + int nVersion = s.GetVersion(); + if (!(s.GetType() & SER_GETHASH)) { + s >> nVersion; + } + //! Note: strAccount is serialized as part of the key, not here. + s >> nCreditDebit >> nTime >> LIMITED_STRING(strOtherAccount, 65536) >> LIMITED_STRING(strComment, 65536); size_t nSepPos = strComment.find("\0", 0, 1); - if (ser_action.ForRead()) - { - mapValue.clear(); - if (std::string::npos != nSepPos) - { - CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), s.GetType(), s.GetVersion()); - ss >> mapValue; - _ssExtra = std::vector<char>(ss.begin(), ss.end()); - } - ReadOrderPos(nOrderPos, mapValue); + mapValue.clear(); + if (std::string::npos != nSepPos) { + CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), s.GetType(), s.GetVersion()); + ss >> mapValue; + _ssExtra = std::vector<char>(ss.begin(), ss.end()); } - if (std::string::npos != nSepPos) + ReadOrderPos(nOrderPos, mapValue); + if (std::string::npos != nSepPos) { strComment.erase(nSepPos); + } mapValue.erase("n"); } @@ -980,9 +970,9 @@ public: * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random position */ - bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, + bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); - bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state); + bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::string fromAccount, CReserveKey& reservekey, CConnman* connman, CValidationState& state); void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries); bool AddAccountingEntry(const CAccountingEntry&); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 0b0880a2ba..7f5f3b84b2 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -265,7 +265,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { uint256 hash; ssKey >> hash; - CWalletTx wtx; + CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); ssValue >> wtx; CValidationState state; if (!(CheckTransaction(*wtx.tx, state) && (wtx.GetHash() == hash) && state.IsValid())) @@ -603,7 +603,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) pwallet->UpdateTimeFirstKey(1); for (uint256 hash : wss.vWalletUpgrade) - WriteTx(pwallet->mapWallet[hash]); + WriteTx(pwallet->mapWallet.at(hash)); // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) @@ -664,7 +664,7 @@ DBErrors CWalletDB::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWal uint256 hash; ssKey >> hash; - CWalletTx wtx; + CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef()); ssValue >> wtx; vTxHash.push_back(hash); diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 3ca74ea35e..d1bf9206b2 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -13,7 +13,7 @@ import re import sys # Matches on the date format at the start of the log event -TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}") +TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z") LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event']) diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index edcade63c1..e1f328ba77 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -10,75 +10,63 @@ In this test we connect to one node over p2p, and test block requests: 3) Invalid block with bad coinbase value should be rejected and not re-requested. """ - -from test_framework.test_framework import ComparisonTestFramework -from test_framework.util import * -from test_framework.comptool import TestManager, TestInstance, RejectResult -from test_framework.blocktools import * -from test_framework.mininode import network_thread_start import copy -import time -# Use the ComparisonTestFramework with 1 node: only use --testbinary. -class InvalidBlockRequestTest(ComparisonTestFramework): +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.util import assert_equal - ''' 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. ''' +class InvalidBlockRequestTest(BitcoinTestFramework): 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() + + best_block = node.getblock(node.getbestblockhash()) + tip = int(node.getbestblockhash(), 16) + height = best_block["height"] + 1 + block_time = best_block["time"] + 1 - 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 + self.log.info("Create a new block with an anyone-can-spend coinbase") - ''' - 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.solve() # Save the coinbase for later - self.block1 = block - self.tip = block.sha256 - height += 1 - yield TestInstance([[block, 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 - - ''' - Now we use merkle-root malleability to generate an invalid block with - same blockheader. - Manufacture a block with 3 transactions (coinbase, spend of prior - coinbase, spend of that spend). Duplicate the 3rd transaction to - leave merkle root and blockheader unchanged but invalidate the block. - ''' - block2 = create_block(self.tip, create_coinbase(height), self.block_time) - self.block_time += 1 + block1 = block + tip = block.sha256 + node.p2p.send_blocks_and_test([block1], node, True) + + self.log.info("Mature the block.") + node.generate(100) + + best_block = node.getblock(node.getbestblockhash()) + tip = int(node.getbestblockhash(), 16) + height = best_block["height"] + 1 + block_time = best_block["time"] + 1 + + # Use merkle-root malleability to generate an invalid block with + # same blockheader. + # Manufacture a block with 3 transactions (coinbase, spend of prior + # coinbase, spend of that spend). Duplicate the 3rd transaction to + # leave merkle root and blockheader unchanged but invalidate the block. + self.log.info("Test merkle root malleability.") + + block2 = create_block(tip, create_coinbase(height), block_time) + block_time += 1 # b'0x51' is OP_TRUE - tx1 = create_transaction(self.block1.vtx[0], 0, b'\x51', 50 * COIN) + tx1 = create_transaction(block1.vtx[0], 0, b'\x51', 50 * COIN) tx2 = create_transaction(tx1, 0, b'\x51', 50 * COIN) block2.vtx.extend([tx1, tx2]) @@ -94,24 +82,20 @@ class InvalidBlockRequestTest(ComparisonTestFramework): assert_equal(orig_hash, block2.rehash()) assert(block2_orig.vtx != block2.vtx) - self.tip = block2.sha256 - yield TestInstance([[block2, RejectResult(16, b'bad-txns-duplicate')], [block2_orig, True]]) - height += 1 - - ''' - Make sure that a totally screwed up block is not valid. - ''' - block3 = create_block(self.tip, create_coinbase(height), self.block_time) - self.block_time += 1 - block3.vtx[0].vout[0].nValue = 100 * COIN # Too high! - block3.vtx[0].sha256=None + node.p2p.send_blocks_and_test([block2], node, False, False, 16, b'bad-txns-duplicate') + + self.log.info("Test very broken block.") + + block3 = create_block(tip, create_coinbase(height), block_time) + block_time += 1 + block3.vtx[0].vout[0].nValue = 100 * COIN # Too high! + block3.vtx[0].sha256 = None block3.vtx[0].calc_sha256() block3.hashMerkleRoot = block3.calc_merkle_root() block3.rehash() block3.solve() - yield TestInstance([[block3, RejectResult(16, b'bad-cb-amount')]]) - + node.p2p.send_blocks_and_test([block3], node, False, False, 16, b'bad-cb-amount') if __name__ == '__main__': InvalidBlockRequestTest().main() diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 64fada38e2..69ce529ad6 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -33,12 +33,10 @@ class InvalidTxRequestTest(BitcoinTestFramework): self.log.info("Create a new block with an anyone-can-spend coinbase.") height = 1 block = create_block(tip, create_coinbase(height), block_time) - block_time += 1 block.solve() # Save the coinbase for later block1 = block tip = block.sha256 - height += 1 node.p2p.send_blocks_and_test([block], node, success=True) self.log.info("Mature the block.") @@ -49,7 +47,10 @@ class InvalidTxRequestTest(BitcoinTestFramework): 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... + # Verify valid transaction + tx1 = create_transaction(block1.vtx[0], 0, b'', 50 * COIN - 12000) + node.p2p.send_txs_and_test([tx1], node, success=True) + if __name__ == '__main__': InvalidTxRequestTest().main() diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 16e4f6adb4..5f34b35bfb 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -15,6 +15,7 @@ from test_framework.util import ( assert_raises_rpc_error, connect_nodes_bi, p2p_port, + wait_until, ) class NetTest(BitcoinTestFramework): @@ -47,14 +48,13 @@ class NetTest(BitcoinTestFramework): # the bytes sent/received should change # note ping and pong are 32 bytes each self.nodes[0].ping() - time.sleep(0.1) + wait_until(lambda: (net_totals['totalbytessent'] + 32*2) == self.nodes[0].getnettotals()['totalbytessent'], timeout=1) + wait_until(lambda: (net_totals['totalbytesrecv'] + 32*2) == self.nodes[0].getnettotals()['totalbytesrecv'], timeout=1) + peer_info_after_ping = self.nodes[0].getpeerinfo() - net_totals_after_ping = self.nodes[0].getnettotals() for before, after in zip(peer_info, peer_info_after_ping): assert_equal(before['bytesrecv_per_msg']['pong'] + 32, after['bytesrecv_per_msg']['pong']) assert_equal(before['bytessent_per_msg']['ping'] + 32, after['bytessent_per_msg']['ping']) - assert_equal(net_totals['totalbytesrecv'] + 32*2, net_totals_after_ping['totalbytesrecv']) - assert_equal(net_totals['totalbytessent'] + 32*2, net_totals_after_ping['totalbytessent']) def _test_getnetworkinginfo(self): assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) diff --git a/test/functional/rpc_preciousblock.py b/test/functional/rpc_preciousblock.py index 960cd0ad12..796a2edbef 100755 --- a/test/functional/rpc_preciousblock.py +++ b/test/functional/rpc_preciousblock.py @@ -8,7 +8,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes_bi, - sync_chain, sync_blocks, ) @@ -72,7 +71,7 @@ class PreciousTest(BitcoinTestFramework): assert_equal(self.nodes[0].getbestblockhash(), hashC) self.log.info("Make Node1 prefer block C") self.nodes[1].preciousblock(hashC) - sync_chain(self.nodes[0:2]) # wait because node 1 may not have downloaded hashC + sync_blocks(self.nodes[0:2]) # wait because node 1 may not have downloaded hashC assert_equal(self.nodes[1].getbestblockhash(), hashC) self.log.info("Make Node1 prefer block G again") self.nodes[1].preciousblock(hashG) diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index e074f5bd74..825b897871 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -12,7 +12,12 @@ Test the following RPCs: - getrawtransaction """ +from collections import OrderedDict +from io import BytesIO from test_framework.test_framework import BitcoinTestFramework +from test_framework.messages import ( + CTransaction, +) from test_framework.util import * @@ -43,11 +48,10 @@ class RawTransactionsTest(BitcoinTestFramework): def setup_network(self, split=False): super().setup_network() - connect_nodes_bi(self.nodes,0,2) + connect_nodes_bi(self.nodes, 0, 2) def run_test(self): - - #prepare some coins for multiple *rawtransaction commands + self.log.info('prepare some coins for multiple *rawtransaction commands') self.nodes[2].generate(1) self.sync_all() self.nodes[0].generate(101) @@ -59,10 +63,11 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(5) self.sync_all() - # Test getrawtransaction on genesis block coinbase returns an error + self.log.info('Test getrawtransaction on genesis block coinbase returns an error') block = self.nodes[0].getblock(self.nodes[0].getblockhash(0)) assert_raises_rpc_error(-5, "The genesis block coinbase is not considered an ordinary transaction", self.nodes[0].getrawtransaction, block['merkleroot']) + self.log.info('Check parameter types and required parameters of createrawtransaction') # Test `createrawtransaction` required parameters assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction) assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, []) @@ -83,12 +88,18 @@ class RawTransactionsTest(BitcoinTestFramework): # Test `createrawtransaction` invalid `outputs` address = self.nodes[0].getnewaddress() - assert_raises_rpc_error(-3, "Expected type object", self.nodes[0].createrawtransaction, [], 'foo') + address2 = self.nodes[0].getnewaddress() + assert_raises_rpc_error(-1, "JSON value is not an array as expected", self.nodes[0].createrawtransaction, [], 'foo') + self.nodes[0].createrawtransaction(inputs=[], outputs={}) # Should not throw for backwards compatibility + self.nodes[0].createrawtransaction(inputs=[], outputs=[]) assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'}) assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].createrawtransaction, [], {'foo': 0}) assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].createrawtransaction, [], {address: 'foo'}) assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].createrawtransaction, [], {address: -1}) assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)])) + assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], [{address: 1}, {address: 1}]) + assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}]) + assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']]) # Test `createrawtransaction` invalid `locktime` assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo') @@ -98,9 +109,38 @@ class RawTransactionsTest(BitcoinTestFramework): # Test `createrawtransaction` invalid `replaceable` assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo') - ######################################### - # sendrawtransaction with missing input # - ######################################### + self.log.info('Check that createrawtransaction accepts an array and object as outputs') + tx = CTransaction() + # One output + tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs={address: 99})))) + assert_equal(len(tx.vout), 1) + assert_equal( + bytes_to_hex_str(tx.serialize()), + self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}]), + ) + # Two outputs + tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)]))))) + assert_equal(len(tx.vout), 2) + assert_equal( + bytes_to_hex_str(tx.serialize()), + self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}]), + ) + # Two data outputs + tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([('data', '99'), ('data', '99')]))))) + assert_equal(len(tx.vout), 2) + assert_equal( + bytes_to_hex_str(tx.serialize()), + self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{'data': '99'}, {'data': '99'}]), + ) + # Multiple mixed outputs + tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), ('data', '99'), ('data', '99')]))))) + assert_equal(len(tx.vout), 3) + assert_equal( + bytes_to_hex_str(tx.serialize()), + self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {'data': '99'}, {'data': '99'}]), + ) + + self.log.info('sendrawtransaction with missing input') inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1}] #won't exists outputs = { self.nodes[0].getnewaddress() : 4.998 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) @@ -248,14 +288,14 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = { self.nodes[0].getnewaddress() : 2.19 } rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs) rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs) - self.log.info(rawTxPartialSigned1) + self.log.debug(rawTxPartialSigned1) assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet(rawTx2, inputs) - self.log.info(rawTxPartialSigned2) + self.log.debug(rawTxPartialSigned2) assert_equal(rawTxPartialSigned2['complete'], False) #node2 only has one key, can't comp. sign the tx rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']]) - self.log.info(rawTxComb) + self.log.debug(rawTxComb) self.nodes[2].sendrawtransaction(rawTxComb) rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb) self.sync_all() @@ -273,7 +313,7 @@ class RawTransactionsTest(BitcoinTestFramework): encrawtx = "01000000010000000000000072c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000000ffffffff0100e1f505000000000000000000" decrawtx = self.nodes[0].decoderawtransaction(encrawtx, False) # decode as non-witness transaction assert_equal(decrawtx['vout'][0]['value'], Decimal('1.00000000')) - + # getrawtransaction tests # 1. valid parameters - only supply txid txHash = rawTx["hash"] diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 86c1150abd..8efac9c475 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -24,8 +24,8 @@ from .util import ( check_json_precision, connect_nodes_bi, disconnect_nodes, + get_datadir_path, initialize_datadir, - log_filename, p2p_port, set_node_times, sync_blocks, @@ -358,7 +358,7 @@ class BitcoinTestFramework(): ll = int(self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper() ch.setLevel(ll) # Format logs the same as bitcoind's debug.log with microprecision (so log files can be concatenated and sorted) - formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d000 %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d000Z %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S') formatter.converter = time.gmtime fh.setFormatter(formatter) ch.setFormatter(formatter) @@ -382,7 +382,7 @@ class BitcoinTestFramework(): assert self.num_nodes <= MAX_NODES create_cache = False for i in range(MAX_NODES): - if not os.path.isdir(os.path.join(self.options.cachedir, 'node' + str(i))): + if not os.path.isdir(get_datadir_path(self.options.cachedir, i)): create_cache = True break @@ -391,8 +391,8 @@ class BitcoinTestFramework(): # find and delete old cache directories if any exist for i in range(MAX_NODES): - if os.path.isdir(os.path.join(self.options.cachedir, "node" + str(i))): - shutil.rmtree(os.path.join(self.options.cachedir, "node" + str(i))) + if os.path.isdir(get_datadir_path(self.options.cachedir, i)): + shutil.rmtree(get_datadir_path(self.options.cachedir, i)) # Create cache directories, run bitcoinds: for i in range(MAX_NODES): @@ -430,15 +430,18 @@ class BitcoinTestFramework(): self.stop_nodes() self.nodes = [] self.disable_mocktime() + + def cache_path(n, *paths): + return os.path.join(get_datadir_path(self.options.cachedir, n), "regtest", *paths) + for i in range(MAX_NODES): - os.remove(log_filename(self.options.cachedir, i, "debug.log")) - os.remove(log_filename(self.options.cachedir, i, "wallets/db.log")) - os.remove(log_filename(self.options.cachedir, i, "peers.dat")) - os.remove(log_filename(self.options.cachedir, i, "fee_estimates.dat")) + for entry in os.listdir(cache_path(i)): + if entry not in ['wallets', 'chainstate', 'blocks']: + os.remove(cache_path(i, entry)) for i in range(self.num_nodes): - from_dir = os.path.join(self.options.cachedir, "node" + str(i)) - to_dir = os.path.join(self.options.tmpdir, "node" + str(i)) + from_dir = get_datadir_path(self.options.cachedir, i) + to_dir = get_datadir_path(self.options.tmpdir, i) shutil.copytree(from_dir, to_dir) initialize_datadir(self.options.tmpdir, i) # Overwrite port/rpcport in bitcoin.conf diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index a4b8d5af02..7be695550b 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -328,9 +328,6 @@ def get_auth_cookie(datadir): raise ValueError("No RPC credentials") return user, password -def log_filename(dirname, n_node, logname): - return os.path.join(dirname, "node" + str(n_node), "regtest", logname) - def get_bip9_status(node, key): info = node.getblockchaininfo() return info['bip9_softforks'][key] @@ -370,54 +367,29 @@ def sync_blocks(rpc_connections, *, wait=1, timeout=60): one node already synced to the latest, stable tip, otherwise there's a chance it might return before all nodes are stably synced. """ - # Use getblockcount() instead of waitforblockheight() to determine the - # initial max height because the two RPCs look at different internal global - # variables (chainActive vs latestBlock) and the former gets updated - # earlier. - maxheight = max(x.getblockcount() for x in rpc_connections) - start_time = cur_time = time.time() - while cur_time <= start_time + timeout: - tips = [r.waitforblockheight(maxheight, int(wait * 1000)) for r in rpc_connections] - if all(t["height"] == maxheight for t in tips): - if all(t["hash"] == tips[0]["hash"] for t in tips): - return - raise AssertionError("Block sync failed, mismatched block hashes:{}".format( - "".join("\n {!r}".format(tip) for tip in tips))) - cur_time = time.time() - raise AssertionError("Block sync to height {} timed out:{}".format( - maxheight, "".join("\n {!r}".format(tip) for tip in tips))) - -def sync_chain(rpc_connections, *, wait=1, timeout=60): - """ - Wait until everybody has the same best block - """ - while timeout > 0: + stop_time = time.time() + timeout + while time.time() <= stop_time: best_hash = [x.getbestblockhash() for x in rpc_connections] - if best_hash == [best_hash[0]] * len(best_hash): + if best_hash.count(best_hash[0]) == len(rpc_connections): return time.sleep(wait) - timeout -= wait - raise AssertionError("Chain sync failed: Best block hashes don't match") + raise AssertionError("Block sync timed out:{}".format("".join("\n {!r}".format(b) for b in best_hash))) def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True): """ Wait until everybody has the same transactions in their memory pools """ - while timeout > 0: - pool = set(rpc_connections[0].getrawmempool()) - num_match = 1 - for i in range(1, len(rpc_connections)): - if set(rpc_connections[i].getrawmempool()) == pool: - num_match = num_match + 1 - if num_match == len(rpc_connections): + stop_time = time.time() + timeout + while time.time() <= stop_time: + pool = [set(r.getrawmempool()) for r in rpc_connections] + if pool.count(pool[0]) == len(rpc_connections): if flush_scheduler: for r in rpc_connections: r.syncwithvalidationinterfacequeue() return time.sleep(wait) - timeout -= wait - raise AssertionError("Mempool sync failed") + raise AssertionError("Mempool sync timed out:{}".format("".join("\n {!r}".format(m) for m in pool))) # Transaction/Block functions ############################# |