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: >- 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 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; } 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/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 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); 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/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", 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..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 @@ -154,6 +151,7 @@ 'feature_versionbits_warning.py', 'rpc_preciousblock.py', 'wallet_importprunedfunds.py', + 'p2p_leak_tx.py', 'rpc_signmessage.py', 'feature_nulldummy.py', 'mempool_accept.py', @@ -347,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) @@ -394,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 = [] @@ -605,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. 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()