From 6e0fdc7321e399ec2fdc2874bb930b4cc956348d Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Sat, 27 Oct 2018 07:17:59 -0400 Subject: [PATCH 1/9] Merge #14571: [tests] Test that nodes respond to getdata with notfound fa78a2fc67 [tests] Test that nodes respond to getdata with notfound (MarcoFalke) Pull request description: If a node has not announced a tx at all, then it should respond to getdata messages for that tx with notfound, to avoid leaking tx origination privacy. In the future this could be adjusted such that a node responds with notfound when a tx has not been announced to us, but that seems to be a more involved change. See e.g. https://github.com/jnewbery/bitcoin/commits/pr14220.1 Tree-SHA512: 6244afa5bd5d8fec9b89dfc02c9958bc370195145a0f3715f33200d6cf73a376c94193d44bf4523867196e6591c53ede8f9b6a77cb296b48c114a117b8c8b1fa --- test/functional/p2p_leak_tx.py | 57 ++++++++++++++++++++++ test/functional/test_framework/messages.py | 17 +++++++ test/functional/test_framework/mininode.py | 40 ++++++++++++++- test/functional/test_runner.py | 1 + 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100755 test/functional/p2p_leak_tx.py diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py new file mode 100755 index 0000000000000..dc4d475b2d894 --- /dev/null +++ b/test/functional/p2p_leak_tx.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test that we don't leak txs to inbound peers that we haven't yet announced to""" + +from test_framework.messages import msg_getdata, CInv +from test_framework.mininode import P2PDataStore +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + + +class P2PNode(P2PDataStore): + def on_inv(self, msg): + pass + + +class P2PLeakTxTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + gen_node = self.nodes[0] # The block and tx generating node + gen_node.generate(1) + + inbound_peer = self.nodes[0].add_p2p_connection(P2PNode()) # An "attacking" inbound peer + + MAX_REPEATS = 100 + self.log.info("Running test up to {} times.".format(MAX_REPEATS)) + for i in range(MAX_REPEATS): + self.log.info('Run repeat {}'.format(i + 1)) + txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 0.01) + + want_tx = msg_getdata() + want_tx.inv.append(CInv(t=1, h=int(txid, 16))) + inbound_peer.last_message.pop('notfound', None) + inbound_peer.send_message(want_tx) + inbound_peer.sync_with_ping() + + if inbound_peer.last_message.get('notfound'): + self.log.debug('tx {} was not yet announced to us.'.format(txid)) + self.log.debug("node has responded with a notfound message. End test.") + assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16)) + inbound_peer.last_message.pop('notfound') + break + else: + self.log.debug('tx {} was already announced to us. Try test again.'.format(txid)) + assert int(txid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv] + + +if __name__ == '__main__': + P2PLeakTxTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 6acd1f6da615d..e07a5318c24bd 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1397,6 +1397,23 @@ def serialize(self): def __repr__(self): return "msg_mempool()" +class msg_notfound: + __slots__ = ("vec", ) + command = b"notfound" + + def __init__(self, vec=None): + self.vec = vec or [] + + def deserialize(self, f): + self.vec = deser_vector(f, CInv) + + def serialize(self): + return ser_vector(self.vec) + + def __repr__(self): + return "msg_notfound(vec=%s)" % (repr(self.vec)) + + class msg_sendheaders(): command = b"sendheaders" diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 47ae5fe9c5fa3..b591fe0ba6542 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -23,7 +23,42 @@ import time import threading -from test_framework.messages import CBlockHeader, MIN_VERSION_SUPPORTED, msg_addr, msg_addrv2, msg_block, msg_blocktxn, msg_clsig, msg_cmpctblock, msg_getaddr, msg_getblocks, msg_getblocktxn, msg_getdata, msg_getheaders, msg_getmnlistd, msg_headers, msg_inv, msg_islock, msg_mempool, msg_mnlistdiff, msg_ping, msg_pong, msg_qdata, msg_qgetdata, msg_reject, msg_sendaddrv2, msg_sendcmpct, msg_sendheaders, msg_tx, msg_verack, msg_version, MY_SUBVERSION, NODE_NETWORK, sha256 +from test_framework.messages import ( + CBlockHeader, + MIN_VERSION_SUPPORTED, + msg_addr, + msg_addrv2, + msg_block, + msg_blocktxn, + msg_clsig, + msg_cmpctblock, + msg_getaddr, + msg_getblocks, + msg_getblocktxn, + msg_getdata, + msg_getheaders, + msg_getmnlistd, + msg_headers, + msg_inv, + msg_islock, + msg_mempool, + msg_mnlistdiff, + msg_notfound, + msg_ping, + msg_pong, + msg_qdata, + msg_qgetdata, + msg_reject, + msg_sendaddrv2, + msg_sendcmpct, + msg_sendheaders, + msg_tx, + msg_verack, + msg_version, + MY_SUBVERSION, + NODE_NETWORK, + sha256, +) from test_framework.util import wait_until MSG_TX = 1 @@ -62,7 +97,7 @@ b"govsync": None, b"islock": msg_islock, b"mnlistdiff": msg_mnlistdiff, - b"notfound": None, + b"notfound": msg_notfound, b"qfcommit": None, b"qsendrecsigs": None, b"qgetdata": msg_qgetdata, @@ -338,6 +373,7 @@ def on_getdata(self, message): pass def on_getheaders(self, message): pass def on_headers(self, message): pass def on_mempool(self, message): pass + def on_notfound(self, message): pass def on_pong(self, message): pass def on_reject(self, message): pass def on_sendaddrv2(self, message): pass diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index b3875ecd8b3f0..9cc23dd292f18 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -154,6 +154,7 @@ 'feature_versionbits_warning.py', 'rpc_preciousblock.py', 'wallet_importprunedfunds.py', + 'p2p_leak_tx.py', 'rpc_signmessage.py', 'feature_nulldummy.py', 'mempool_accept.py', From 14af9c67b616c7f29eab7e2692c2c214c23ca200 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Sat, 27 Oct 2018 10:38:40 -0400 Subject: [PATCH 2/9] Merge #13783: validation: Pass tx pool reference into CheckSequenceLocks fa511e8dad Pass tx pool reference into CheckSequenceLocks (MarcoFalke) Pull request description: `CheckSequenceLocks` is called from ATMP and the member function `CTxMemPool::removeForReorg` without passing in the tx pool object that is used in those function's scope and instead using the global `::mempool` instance. This fix should be refactoring only, since currently there is only one (global) tx pool in normal operation. Though, it fixes hard to track down issues in future settings where more than one mempool exists at a time. (E.g. for tests, rpc or p2p tx relay purposes) Tree-SHA512: f0804588c7d29bb6ff05ec14f22a16422b89ab31ae714f38cd07f811d7dc7907bfd14e799c4c1c3121144ff22711019bbe9212b39e2fd4531936a4119950fa49 --- src/test/miner_tests.cpp | 4 ++-- src/txmempool.cpp | 2 +- src/validation.cpp | 8 ++++---- src/validation.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index f4c399823e567..300fb7a01f7cb 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -85,8 +85,8 @@ static CBlockIndex CreateBlockIndex(int nHeight) static bool TestSequenceLocks(const CTransaction &tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - LOCK(mempool.cs); - return CheckSequenceLocks(tx, flags); + LOCK(::mempool.cs); + return CheckSequenceLocks(::mempool, tx, flags); } // Test suite for ancestor feerate transaction selection. diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 588e404bd48db..588a384a75a7c 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -760,7 +760,7 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem const CTransaction& tx = it->GetTx(); LockPoints lp = it->GetLockPoints(); bool validLP = TestLockPointValidity(&lp); - if (!CheckFinalTx(tx, flags) || !CheckSequenceLocks(tx, flags, &lp, validLP)) { + if (!CheckFinalTx(tx, flags) || !CheckSequenceLocks(*this, tx, flags, &lp, validLP)) { // Note if CheckSequenceLocks fails the LockPoints may still be invalid // So it's critical that we remove the tx and not depend on the LockPoints. txToRemove.insert(it); diff --git a/src/validation.cpp b/src/validation.cpp index 5a4e68fd98801..121eee6d06368 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -379,10 +379,10 @@ bool TestLockPointValidity(const LockPoints* lp) return true; } -bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool useExistingLockPoints) +bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp, bool useExistingLockPoints) { AssertLockHeld(cs_main); - AssertLockHeld(mempool.cs); + AssertLockHeld(pool.cs); CBlockIndex* tip = chainActive.Tip(); assert(tip != nullptr); @@ -405,7 +405,7 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool } else { // pcoinsTip contains the UTXO set for chainActive.Tip() - CCoinsViewMemPool viewMemPool(pcoinsTip.get(), mempool); + CCoinsViewMemPool viewMemPool(pcoinsTip.get(), pool); std::vector prevheights; prevheights.resize(tx.vin.size()); for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { @@ -738,7 +738,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // be mined yet. // Must keep pool.cs for this unless we change CheckSequenceLocks to take a // CoinsViewCache instead of create its own - if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) + if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); CAmount nFees = 0; diff --git a/src/validation.h b/src/validation.h index 7cf36c8993d27..5df9f9b801606 100644 --- a/src/validation.h +++ b/src/validation.h @@ -365,7 +365,7 @@ bool TestLockPointValidity(const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_mai * * See consensus/consensus.h for flag definitions. */ -bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** * Closure representing one script verification From 6d4984b0a7ed3c94b82770630aae1101878491ff Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Fri, 2 Nov 2018 15:04:55 -0400 Subject: [PATCH 3/9] Merge #14630: test_runner: Remove travis specific code fa43626611 test_runner: Remove travis specific code (MarcoFalke) Pull request description: The tests are no longer run on travis, but in a docker, developer machines or a windows vm. The code was essentially dead for months now. Fix that by explicitly passing in `--ci` to the test runner on our docker and appveyor windows vm. Tree-SHA512: 5d48693c03e8eb27536658ccf9ba738fe93a72abd4b72c80caac084b5b2cdffa77a1031a671eeefe70b71d63500f55917803d4be54d01849722afdccb700a9e6 --- test/functional/test_runner.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 9cc23dd292f18..d921e2466dfc0 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -52,9 +52,6 @@ TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 -# 30 minutes represented in seconds -TRAVIS_TIMEOUT_DURATION = 30 * 60 - BASE_SCRIPTS = [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel @@ -348,12 +345,12 @@ def main(): jobs=args.jobs, enable_coverage=args.coverage, args=passon_args, - runs_ci=args.ci, combined_logs_len=args.combinedlogslen, - failfast=args.failfast + failfast=args.failfast, + runs_ci=args.ci, ) -def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, runs_ci=False, combined_logs_len=0,failfast=False): +def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0,failfast=False, runs_ci=False): args = args or [] # Warn if dashd is already running (unix only) @@ -395,7 +392,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= tmpdir=tmpdir, test_list=test_list, flags=flags, - timeout_duration=TRAVIS_TIMEOUT_DURATION if runs_ci else float('inf'), # in seconds + timeout_duration=30 * 60 if runs_ci else float('inf'), # in seconds ) start_time = time.time() test_results = [] @@ -606,6 +603,7 @@ def check_script_list(*, src_dir, fail_on_warn): # On travis this warning is an error to prevent merging incomplete commits into master sys.exit(1) + class RPCCoverage(): """ Coverage reporting utilities for test_runner. From fe24157964237a593c85d5b249a06cc40a239c9f Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Fri, 2 Nov 2018 17:41:06 -0400 Subject: [PATCH 4/9] Merge #14528: travis: Compile once on xenial fa4bcaf82a travis: Compile once on xenial (MarcoFalke) Pull request description: Currently we only build on bionic (since that is also the current gitian environment). However, building on the current and previous Ubuntu LTS should be supported with only system packages and without depends. Tree-SHA512: bf5725cfb1be09220510d53010c7b7deb20051a9995e39fe5e83505c63db09ac877a41b896c97b253052fefea58ca0a9b6d9c5962a7ac4b258782c476d6ee7c0 --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index e97cdcaa6e6bd..0361562aa0928 100644 --- a/.travis.yml +++ b/.travis.yml @@ -253,6 +253,15 @@ after_success: DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1" GOAL="install" BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-debug CXXFLAGS=\"-g0 -O2\"" +# x86_64 Linux (xenial, no depends, only system libs) + - stage: test + env: >- + HOST=x86_64-unknown-linux-gnu + DOCKER_NAME_TAG=ubuntu:16.04 + PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" + NO_DEPENDS=1 + GOAL="install" + BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER" # x86_64 Linux (no depends, only system libs) - stage: test env: >- From ae6f3840c99a5821237c5ed695051a2664e3fcbc Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Mon, 5 Nov 2018 12:49:41 +0100 Subject: [PATCH 5/9] Merge #14554: qt: Remove unused `adjustedTime` parameter 04972fefd12c2c764b5f2afee87228e8d90f2448 Remove unused `adjustedTime` parameter (Hennadii Stepanov) Pull request description: After merging #13622 the `adjustedTime` parameter in the `updateStatus` function is unused. Tree-SHA512: 1d0e03e7343f076ee0032fb721f8ba50571d579958001aab372a43e45b4de24c2bf3bd18c245071cbd69f61ef38182e19666c6f936d55c9085b73c848ba62626 # Conflicts: # src/interfaces/wallet.cpp # src/interfaces/wallet.h # src/qt/transactionrecord.cpp # src/qt/transactionrecord.h # src/qt/transactiontablemodel.cpp --- src/interfaces/wallet.cpp | 8 ++------ src/interfaces/wallet.h | 6 ++---- src/qt/transactiondesc.cpp | 7 +++---- src/qt/transactiondesc.h | 2 +- src/qt/transactionrecord.cpp | 2 +- src/qt/transactionrecord.h | 2 +- src/qt/transactiontablemodel.cpp | 5 ++--- 7 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index bccc967e45887..d866bf6891922 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -342,8 +342,7 @@ class WalletImpl : public Wallet return result; } bool tryGetTxStatus(const uint256& txid, - interfaces::WalletTxStatus& tx_status, - int64_t& adjusted_time) override + interfaces::WalletTxStatus& tx_status) override { TRY_LOCK(::cs_main, locked_chain); if (!locked_chain) { @@ -357,7 +356,6 @@ class WalletImpl : public Wallet if (mi == m_wallet.mapWallet.end()) { return false; } - adjusted_time = GetAdjustedTime(); tx_status = MakeWalletTxStatus(mi->second); return true; } @@ -365,14 +363,12 @@ class WalletImpl : public Wallet WalletTxStatus& tx_status, WalletOrderForm& order_form, bool& in_mempool, - int& num_blocks, - int64_t& adjusted_time) override + int& num_blocks) override { LOCK2(::cs_main, m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { num_blocks = ::chainActive.Height(); - adjusted_time = GetAdjustedTime(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; tx_status = MakeWalletTxStatus(mi->second); diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index f107e7f581ce5..d681f49a6ba6f 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -183,16 +183,14 @@ class Wallet //! Try to get updated status for a particular transaction, if possible without blocking. virtual bool tryGetTxStatus(const uint256& txid, - WalletTxStatus& tx_status, - int64_t& adjusted_time) = 0; + WalletTxStatus& tx_status) = 0; //! Get transaction details. virtual WalletTx getWalletTxDetails(const uint256& txid, WalletTxStatus& tx_status, WalletOrderForm& order_form, bool& in_mempool, - int& num_blocks, - int64_t& adjusted_time) = 0; + int& num_blocks) = 0; // Get the number of coinjoin rounds an output went through virtual int getRealOutpointCoinJoinRounds(const COutPoint& outpoint) = 0; diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 5c60101e40f4a..9f60b60754666 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -21,7 +21,7 @@ #include #include -QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks, int64_t adjustedTime) +QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks) { if (!status.is_final) { @@ -61,11 +61,10 @@ QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const i QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord *rec, int unit) { int numBlocks; - int64_t adjustedTime; interfaces::WalletTxStatus status; interfaces::WalletOrderForm orderForm; bool inMempool; - interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks, adjustedTime); + interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks); QString strHTML; @@ -77,7 +76,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall CAmount nDebit = wtx.debit; CAmount nNet = nCredit - nDebit; - strHTML += "" + tr("Status") + ": " + FormatTxStatus(wtx, status, inMempool, numBlocks, adjustedTime); + strHTML += "" + tr("Status") + ": " + FormatTxStatus(wtx, status, inMempool, numBlocks); strHTML += "
"; strHTML += "" + tr("Date") + ": " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "
"; diff --git a/src/qt/transactiondesc.h b/src/qt/transactiondesc.h index cb8453cb81e95..5492dc388ecdd 100644 --- a/src/qt/transactiondesc.h +++ b/src/qt/transactiondesc.h @@ -29,7 +29,7 @@ class TransactionDesc: public QObject private: TransactionDesc() {} - static QString FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks, int64_t adjustedTime); + static QString FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks); }; #endif // BITCOIN_QT_TRANSACTIONDESC_H diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index a7fbf5e4a9327..6d7384fad5ee2 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -252,7 +252,7 @@ QList TransactionRecord::decomposeTransaction(interfaces::Wal return parts; } -void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t adjustedTime, int chainLockHeight) +void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int chainLockHeight) { // Determine transaction status diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 1470856ad0fac..f66452e6bd904 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -161,7 +161,7 @@ class TransactionRecord /** Update status from core wallet tx. */ - void updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t adjustedTime, int chainLockHeight); + void updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int chainLockHeight); /** Return whether a status update is needed. */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 906c002e74324..24cfd20249295 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -200,9 +200,8 @@ class TransactionTablePriv // try to update the status of this transaction from the wallet. // Otherwise, simply re-use the cached status. interfaces::WalletTxStatus wtx; - int64_t adjustedTime; - if (rec->statusUpdateNeeded(numBlocks, parent->getChainLockHeight()) && wallet.tryGetTxStatus(rec->hash, wtx, adjustedTime)) { - rec->updateStatus(wtx, numBlocks, adjustedTime, parent->getChainLockHeight()); + if (rec->statusUpdateNeeded(numBlocks, parent->getChainLockHeight()) && wallet.tryGetTxStatus(rec->hash, wtx)) { + rec->updateStatus(wtx, numBlocks, parent->getChainLockHeight()); } return rec; } From 65c030f7acbcbe54abfabc6f6eb341816ee315ca Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Thu, 1 Nov 2018 17:36:41 +0100 Subject: [PATCH 6/9] Merge #14377: check that a separator is found for psbt inputs, outputs, and global map 4fb3388db95f408566e43ebb9736842cfbff0a7d check that a separator is found for psbt inputs, outputs, and global map (Andrew Chow) Pull request description: Currently it doesn't make sure that a separator was found so PSBTs missing a trailing separator would still pass. This fixes that and adds a test case for it. It really only makes sense to check for the separator for the output maps as if an input or global map was missing a separator, the fields following it would be interpreted as belonging to the previous input or global map. However I have added the check for those two anyways to be consistent. Tree-SHA512: 50c0c08e201ba02494b369a4d36ddb73e6634eb5a4e4e201c4ef38fd2dbeea2c642b8a04d50c91615da61ecbfade37309e47431368f4b1064539c42015766b50 --- src/script/sign.h | 30 +++++++++++++++++++++++++++--- test/functional/data/rpc_psbt.json | 3 ++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/script/sign.h b/src/script/sign.h index 431f4fa6e4a08..13bb02dec9508 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -276,6 +276,7 @@ struct PSBTInput template inline void Unserialize(Stream& s) { // Read loop + bool found_sep = false; while(!s.empty()) { // Read std::vector key; @@ -283,7 +284,10 @@ struct PSBTInput // the key is empty if that was actually a separator byte // This is a special case for key lengths 0 as those are not allowed (except for separator) - if (key.empty()) return; + if (key.empty()) { + found_sep = true; + break; + } // First byte of key is the type unsigned char type = key[0]; @@ -366,6 +370,10 @@ struct PSBTInput break; } } + + if (!found_sep) { + throw std::ios_base::failure("Separator is missing at the end of an input map"); + } } template @@ -412,6 +420,7 @@ struct PSBTOutput template inline void Unserialize(Stream& s) { // Read loop + bool found_sep = false; while(!s.empty()) { // Read std::vector key; @@ -419,7 +428,10 @@ struct PSBTOutput // the key is empty if that was actually a separator byte // This is a special case for key lengths 0 as those are not allowed (except for separator) - if (key.empty()) return; + if (key.empty()) { + found_sep = true; + break; + } // First byte of key is the type unsigned char type = key[0]; @@ -454,6 +466,10 @@ struct PSBTOutput } } } + + if (!found_sep) { + throw std::ios_base::failure("Separator is missing at the end of an output map"); + } } template @@ -528,6 +544,7 @@ struct PartiallySignedTransaction } // Read global data + bool found_sep = false; while(!s.empty()) { // Read std::vector key; @@ -535,7 +552,10 @@ struct PartiallySignedTransaction // the key is empty if that was actually a separator byte // This is a special case for key lengths 0 as those are not allowed (except for separator) - if (key.empty()) break; + if (key.empty()) { + found_sep = true; + break; + } // First byte of key is the type unsigned char type = key[0]; @@ -573,6 +593,10 @@ struct PartiallySignedTransaction } } + if (!found_sep) { + throw std::ios_base::failure("Separator is missing at the end of the global map"); + } + // Make sure that we got an unsigned tx if (!tx) { throw std::ios_base::failure("No unsigned transcation was provided"); diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index c19b8ed09a879..49da0cf4effad 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -16,7 +16,8 @@ "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" ], "valid" : [ "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA", From 2f59f766d6a8341e8ab5533333e355d5f3efcb66 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Sun, 4 Nov 2018 17:22:35 -0500 Subject: [PATCH 7/9] Merge #14410: rpcwallet: 'ischange' field for 'getaddressinfo' RPC 14a06525b2 tests: add test for 'getaddressinfo' RPC result 'ischange' field (whythat) 93d1aa9abc rpcwallet: add 'ischange' field to 'getaddressinfo' response (whythat) Pull request description: Implementation of proposal in #14396. This introduces `CWallet::IsChange(CScript&)` method and replaces original `CWallet::IsChange(CTxOut&)` method with overloaded version that delegates to the new method with *txout*'s `scriptPubKey`. In this way `TODO` note from the original method can still be addressed in a single place. Tree-SHA512: ef5dbc82d76b4b9b2fa6a70abc3385a677c55021f79e187ee2f392ee32bc6b406191f4129acae5c17b0206e72b6712e7e0cad574a4bbd966871c2e656c45e041 # Conflicts: # doc/release-notes-14282.md # src/wallet/rpcwallet.cpp --- src/wallet/rpcwallet.cpp | 2 ++ src/wallet/wallet.cpp | 9 +++++++-- src/wallet/wallet.h | 1 + test/functional/wallet_basic.py | 18 +++++++++++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7dd1249971579..aab81c103741c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4165,6 +4165,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " \"ismine\" : true|false, (boolean) If the address is yours or not\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" " \"isscript\" : true|false, (boolean) If the key is a script\n" + " \"ischange\" : true|false, (boolean) If the address was used for change output\n" " \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata\n" " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n" " \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n" @@ -4222,6 +4223,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) ret.pushKV("account", pwallet->mapAddressBook[dest].name); } } + ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); const CKeyMetadata* meta = nullptr; const CKeyID *key_id = boost::get(&dest); if (key_id != nullptr && !key_id->IsNull()) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e37e2ddc9a15b..62919f41d18bd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1717,6 +1717,11 @@ CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) cons } bool CWallet::IsChange(const CTxOut& txout) const +{ + return IsChange(txout.scriptPubKey); +} + +bool CWallet::IsChange(const CScript& script) const { // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a script that is ours, but is not in the address book @@ -1725,10 +1730,10 @@ bool CWallet::IsChange(const CTxOut& txout) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - if (::IsMine(*this, txout.scriptPubKey)) + if (::IsMine(*this, script)) { CTxDestination address; - if (!ExtractDestination(txout.scriptPubKey, address)) + if (!ExtractDestination(script, address)) return true; LOCK(cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 20af192b6e2c8..6d775d02fe95b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1164,6 +1164,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface isminetype IsMine(const CTxOut& txout) const; CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const; bool IsChange(const CTxOut& txout) const; + bool IsChange(const CScript& script) const; CAmount GetChange(const CTxOut& txout) const; bool IsMine(const CTransaction& tx) const; /** should probably be renamed to IsRelevantToMe */ diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 8f9515f97fdc7..844c0fb5e0ed3 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -496,7 +496,7 @@ def run_test(self): # Verify nothing new in wallet assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999))) - # Test getaddressinfo. Note that these addresses are taken from disablewallet.py + # Test getaddressinfo on external address. Note that these addresses are taken from disablewallet.py assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy") address_info = self.nodes[0].getaddressinfo("yjQ5gLvGRtmq1cwc4kePLCrzQ8GVCh9Gaz") assert_equal(address_info['address'], "yjQ5gLvGRtmq1cwc4kePLCrzQ8GVCh9Gaz") @@ -504,6 +504,22 @@ def run_test(self): assert not address_info["ismine"] assert not address_info["iswatchonly"] assert not address_info["isscript"] + assert not address_info["ischange"] + + # Test getaddressinfo 'ischange' field on change address. + self.nodes[0].generate(1) + destination = self.nodes[1].getnewaddress() + txid = self.nodes[0].sendtoaddress(destination, 0.123) + tx = self.nodes[0].decoderawtransaction(self.nodes[0].getrawtransaction(txid)) + output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]] + assert len(output_addresses) > 1 + for address in output_addresses: + ischange = self.nodes[0].getaddressinfo(address)['ischange'] + assert_equal(ischange, address != destination) + if ischange: + change = address + self.nodes[0].setlabel(change, 'foobar') + assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False) if __name__ == '__main__': WalletTest().main() From e74e8a74f4fa26d3cba2ce7e40732bee1d0c84ab Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Mon, 5 Nov 2018 13:18:32 +0100 Subject: [PATCH 8/9] Merge #14628: Trivial: Rename misleading 'defaultPort' to 'rpc_port' 4ed730802f9ec3d65477a29a318dd78216ef7085 scripted-diff: Rename misleading 'defaultPort' to 'http_port' (Murray Nesbitt) Pull request description: `defaultPort` in `HTTPBindAddresses()` is misleadingly named. `defaultPort ` suggests a constant, not something that might be overridden by `-rpcport`. Tree-SHA512: f6ae8bdc2b4a4f503e44df9efdec32c854d2dede87714399f53791d50cce6bc41c46b01d1583cfc0e3e4777c244e1c74443fa39d9da50a45e53af265b74a17d1 --- src/httpserver.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 0c56d43070356..a02855f18be10 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -301,13 +301,13 @@ static bool ThreadHTTP(struct event_base* base) /** Bind HTTP server to specified addresses */ static bool HTTPBindAddresses(struct evhttp* http) { - int defaultPort = gArgs.GetArg("-rpcport", BaseParams().RPCPort()); + int http_port = gArgs.GetArg("-rpcport", BaseParams().RPCPort()); std::vector > endpoints; // Determine what addresses to bind to if (!(gArgs.IsArgSet("-rpcallowip") && gArgs.IsArgSet("-rpcbind"))) { // Default to loopback if not allowing external IPs - endpoints.push_back(std::make_pair("::1", defaultPort)); - endpoints.push_back(std::make_pair("127.0.0.1", defaultPort)); + endpoints.push_back(std::make_pair("::1", http_port)); + endpoints.push_back(std::make_pair("127.0.0.1", http_port)); if (gArgs.IsArgSet("-rpcallowip")) { LogPrintf("WARNING: option -rpcallowip was specified without -rpcbind; this doesn't usually make sense\n"); } @@ -316,11 +316,14 @@ static bool HTTPBindAddresses(struct evhttp* http) } } else if (gArgs.IsArgSet("-rpcbind")) { // Specific bind address for (const std::string& strRPCBind : gArgs.GetArgs("-rpcbind")) { - int port = defaultPort; + int port = http_port; std::string host; SplitHostPort(strRPCBind, port, host); endpoints.push_back(std::make_pair(host, port)); } + } else { // No specific bind address specified, bind to any + endpoints.push_back(std::make_pair("::", http_port)); + endpoints.push_back(std::make_pair("0.0.0.0", http_port)); } // Bind addresses From 1018267b10806c91293e4d5bc9bea321a54279a8 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Mon, 5 Nov 2018 13:46:29 -0500 Subject: [PATCH 9/9] Merge #14660: trivial: Don't translate help texts b6022149ec trivial: Don't translate in help text (ken2812221) Pull request description: Tree-SHA512: 05a92b3ac77d00e7bf8c62a0461c9801306e924ac408eae58b0e091eae1c7d54cf46a7a862355fb9aa50b26b505f2298ace6f7b8d294ad38578bdca4d8738343 --- src/wallet/init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 5ad6ffb9cae57..1397b56679a63 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -64,7 +64,7 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit(); void WalletInit::AddWalletOptions() const { - gArgs.AddArg("-avoidpartialspends", strprintf(_("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)"), DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); + gArgs.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)", DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); gArgs.AddArg("-createwalletbackups=", strprintf("Number of automatic wallet backups (default: %u)", nWalletBackups), false, OptionsCategory::WALLET); gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", false, OptionsCategory::WALLET); gArgs.AddArg("-instantsendnotify=", "Execute command when a wallet InstantSend transaction is successfully locked (%s in cmd is replaced by TxID)", false, OptionsCategory::WALLET);