diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index c15151bae..fec8e0bfc 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -1,393 +1,387 @@ // Copyright (c) 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. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace interfaces { namespace { - class LockImpl : public Chain::Lock { + class LockImpl : public Chain::Lock, public UniqueLock { Optional getHeight() override { int height = ::ChainActive().Height(); if (height >= 0) { return height; } return nullopt; } Optional getBlockHeight(const BlockHash &hash) override { CBlockIndex *block = LookupBlockIndex(hash); if (block && ::ChainActive().Contains(block)) { return block->nHeight; } return nullopt; } int getBlockDepth(const BlockHash &hash) override { const Optional tip_height = getHeight(); const Optional height = getBlockHeight(hash); return tip_height && height ? *tip_height - *height + 1 : 0; } BlockHash getBlockHash(int height) override { CBlockIndex *block = ::ChainActive()[height]; assert(block != nullptr); return block->GetBlockHash(); } int64_t getBlockTime(int height) override { CBlockIndex *block = ::ChainActive()[height]; assert(block != nullptr); return block->GetBlockTime(); } int64_t getBlockMedianTimePast(int height) override { CBlockIndex *block = ::ChainActive()[height]; assert(block != nullptr); return block->GetMedianTimePast(); } bool haveBlockOnDisk(int height) override { CBlockIndex *block = ::ChainActive()[height]; return block && (block->nStatus.hasData() != 0) && block->nTx > 0; } Optional findFirstBlockWithTime(int64_t time, BlockHash *hash) override { CBlockIndex *block = ::ChainActive().FindEarliestAtLeast(time); if (block) { if (hash) { *hash = block->GetBlockHash(); } return block->nHeight; } return nullopt; } Optional findFirstBlockWithTimeAndHeight(int64_t time, int height) override { // TODO: Could update CChain::FindEarliestAtLeast() to take a height // parameter and use it with std::lower_bound() to make this // implementation more efficient and allow combining // findFirstBlockWithTime and findFirstBlockWithTimeAndHeight into // one method. for (CBlockIndex *block = ::ChainActive()[height]; block; block = ::ChainActive().Next(block)) { if (block->GetBlockTime() >= time) { return block->nHeight; } } return nullopt; } Optional findPruned(int start_height, Optional stop_height) override { if (::fPruneMode) { CBlockIndex *block = stop_height ? ::ChainActive()[*stop_height] : ::ChainActive().Tip(); while (block && block->nHeight >= start_height) { if (block->nStatus.hasData() == 0) { return block->nHeight; } block = block->pprev; } } return nullopt; } Optional findFork(const BlockHash &hash, Optional *height) override { const CBlockIndex *block = LookupBlockIndex(hash); const CBlockIndex *fork = block ? ::ChainActive().FindFork(block) : nullptr; if (height) { if (block) { *height = block->nHeight; } else { height->reset(); } } if (fork) { return fork->nHeight; } return nullopt; } bool isPotentialTip(const BlockHash &hash) override { if (::ChainActive().Tip()->GetBlockHash() == hash) { return true; } CBlockIndex *block = LookupBlockIndex(hash); return block && block->GetAncestor(::ChainActive().Height()) == ::ChainActive().Tip(); } CBlockLocator getTipLocator() override { return ::ChainActive().GetLocator(); } Optional findLocatorFork(const CBlockLocator &locator) override { LockAnnotation lock(::cs_main); if (CBlockIndex *fork = FindForkInGlobalIndex(::ChainActive(), locator)) { return fork->nHeight; } return nullopt; } bool contextualCheckTransactionForCurrentBlock( const Consensus::Params ¶ms, const CTransaction &tx, CValidationState &state) override { LockAnnotation lock(::cs_main); return ContextualCheckTransactionForCurrentBlock(params, tx, state); } bool submitToMemoryPool(const Config &config, CTransactionRef tx, Amount absurd_fee, CValidationState &state) override { LockAnnotation lock(::cs_main); return AcceptToMemoryPool(config, ::g_mempool, state, tx, nullptr /* missing inputs */, false /* bypass limits */, absurd_fee); } - }; - class LockingStateImpl : public LockImpl, - public UniqueLock { using UniqueLock::UniqueLock; }; class NotificationsHandlerImpl : public Handler, CValidationInterface { public: explicit NotificationsHandlerImpl(Chain &chain, Chain::Notifications ¬ifications) : m_chain(chain), m_notifications(¬ifications) { RegisterValidationInterface(this); } ~NotificationsHandlerImpl() override { disconnect(); } void disconnect() override { if (m_notifications) { m_notifications = nullptr; UnregisterValidationInterface(this); } } void TransactionAddedToMempool(const CTransactionRef &tx) override { m_notifications->TransactionAddedToMempool(tx); } void TransactionRemovedFromMempool(const CTransactionRef &tx) override { m_notifications->TransactionRemovedFromMempool(tx); } void BlockConnected( const std::shared_ptr &block, const CBlockIndex *index, const std::vector &tx_conflicted) override { m_notifications->BlockConnected(*block, tx_conflicted); } void BlockDisconnected(const std::shared_ptr &block) override { m_notifications->BlockDisconnected(*block); } void UpdatedBlockTip(const CBlockIndex *index, const CBlockIndex *fork_index, bool is_ibd) override { m_notifications->UpdatedBlockTip(); } void ChainStateFlushed(const CBlockLocator &locator) override { m_notifications->ChainStateFlushed(locator); } Chain &m_chain; Chain::Notifications *m_notifications; }; class RpcHandlerImpl : public Handler { public: RpcHandlerImpl(const CRPCCommand &command) : m_command(command), m_wrapped_command(&command) { m_command.actor = [this](Config &config, const JSONRPCRequest &request, UniValue &result, bool last_handler) { if (!m_wrapped_command) { return false; } try { return m_wrapped_command->actor(config, request, result, last_handler); } catch (const UniValue &e) { // If this is not the last handler and a wallet not found // exception was thrown, return false so the next handler // can try to handle the request. Otherwise, reraise the // exception. if (!last_handler) { const UniValue &code = e["code"]; if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) { return false; } } throw; } }; ::tableRPC.appendCommand(m_command.name, &m_command); } void disconnect() override final { if (m_wrapped_command) { m_wrapped_command = nullptr; ::tableRPC.removeCommand(m_command.name, &m_command); } } ~RpcHandlerImpl() override { disconnect(); } CRPCCommand m_command; const CRPCCommand *m_wrapped_command; }; class ChainImpl : public Chain { public: std::unique_ptr lock(bool try_lock) override { - auto result = std::make_unique( + auto result = std::make_unique( ::cs_main, "cs_main", __FILE__, __LINE__, try_lock); if (try_lock && result && !*result) { return {}; } return result; } - std::unique_ptr assumeLocked() override { - return std::make_unique(); - } bool findBlock(const BlockHash &hash, CBlock *block, int64_t *time, int64_t *time_max) override { CBlockIndex *index; { LOCK(cs_main); index = LookupBlockIndex(hash); if (!index) { return false; } if (time) { *time = index->GetBlockTime(); } if (time_max) { *time_max = index->GetBlockTimeMax(); } } if (block && !ReadBlockFromDisk(*block, index, Params().GetConsensus())) { block->SetNull(); } return true; } void findCoins(std::map &coins) override { return FindCoins(coins); } double guessVerificationProgress(const BlockHash &block_hash) override { LOCK(cs_main); return GuessVerificationProgress(Params().TxData(), LookupBlockIndex(block_hash)); } bool hasDescendantsInMempool(const TxId &txid) override { LOCK(::g_mempool.cs); auto it = ::g_mempool.GetIter(txid); return it && (*it)->GetCountWithDescendants() > 1; } void relayTransaction(const TxId &txid) override { CInv inv(MSG_TX, txid); g_connman->ForEachNode( [&inv](CNode *node) { node->PushInventory(inv); }); } void getTransactionAncestry(const TxId &txid, size_t &ancestors, size_t &descendants) override { ::g_mempool.GetTransactionAncestry(txid, ancestors, descendants); } bool checkChainLimits(const CTransactionRef &tx) override { LockPoints lp; CTxMemPoolEntry entry(tx, Amount(), 0, 0, false, 0, lp); CTxMemPool::setEntries ancestors; auto limit_ancestor_count = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); auto limit_ancestor_size = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; auto limit_descendant_count = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); auto limit_descendant_size = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; std::string unused_error_string; LOCK(::g_mempool.cs); return ::g_mempool.CalculateMemPoolAncestors( entry, ancestors, limit_ancestor_count, limit_ancestor_size, limit_descendant_count, limit_descendant_size, unused_error_string); } CFeeRate estimateFee() const override { return ::g_mempool.estimateFee(); } CFeeRate relayMinFee() override { return ::minRelayTxFee; } CFeeRate relayDustFee() override { return ::dustRelayFee; } Amount maxTxFee() override { return ::maxTxFee; } bool getPruneMode() override { return ::fPruneMode; } bool p2pEnabled() override { return g_connman != nullptr; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } void initMessage(const std::string &message) override { ::uiInterface.InitMessage(message); } void initWarning(const std::string &message) override { InitWarning(message); } void initError(const std::string &message) override { InitError(message); } void loadWallet(std::unique_ptr wallet) override { ::uiInterface.LoadWallet(wallet); } void showProgress(const std::string &title, int progress, bool resume_possible) override { ::uiInterface.ShowProgress(title, progress, resume_possible); } std::unique_ptr handleNotifications(Notifications ¬ifications) override { return std::make_unique(*this, notifications); } void waitForNotifications() override { SyncWithValidationInterfaceQueue(); } std::unique_ptr handleRpc(const CRPCCommand &command) override { return std::make_unique(command); } }; } // namespace std::unique_ptr MakeChain() { return std::make_unique(); } } // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index a3dff99d8..936bf5392 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -1,321 +1,316 @@ // Copyright (c) 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. #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H #include #include #include #include #include #include #include #include struct BlockHash; class CBlock; struct CBlockLocator; class CChainParams; class Coin; class Config; class CRPCCommand; class CScheduler; class CValidationState; namespace Consensus { struct Params; } namespace interfaces { class Handler; class Wallet; //! Interface giving clients (wallet processes, maybe other analysis tools in //! the future) ability to access to the chain state, receive notifications, //! estimate fees, and submit transactions. //! //! TODO: Current chain methods are too low level, exposing too much of the //! internal workings of the bitcoin node, and not being very convenient to use. //! Chain methods should be cleaned up and simplified over time. Examples: //! //! * The Chain::lock() method, which lets clients delay chain tip updates //! should be removed when clients are able to respond to updates //! asynchronously //! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269). //! //! * The isPotentialTip() and waitForNotifications() methods are too low-level //! and should be replaced with a higher level //! waitForNotificationsUpTo(block_hash) method that the wallet can call //! instead //! (https://github.com/bitcoin/bitcoin/pull/10973#discussion_r266995234). //! //! * The relayTransactions() and submitToMemoryPool() methods could be replaced //! with a higher-level broadcastTransaction method //! (https://github.com/bitcoin/bitcoin/pull/14978#issuecomment-459373984). //! //! * The initMessages() and loadWallet() methods which the wallet uses to send //! notifications to the GUI should go away when GUI and wallet can directly //! communicate with each other without going through the node //! (https://github.com/bitcoin/bitcoin/pull/15288#discussion_r253321096). class Chain { public: virtual ~Chain() {} //! Interface for querying locked chain state, used by legacy code that //! assumes state won't change between calls. New code should avoid using //! the Lock interface and instead call higher-level Chain methods //! that return more information so the chain doesn't need to stay locked //! between calls. class Lock { public: virtual ~Lock() {} //! Get current chain height, not including genesis block (returns 0 if //! chain only contains genesis block, nullopt if chain does not contain //! any blocks). virtual Optional getHeight() = 0; //! Get block height above genesis block. Returns 0 for genesis block, //! 1 for following block, and so on. Returns nullopt for a block not //! included in the current chain. virtual Optional getBlockHeight(const BlockHash &hash) = 0; //! Get block depth. Returns 1 for chain tip, 2 for preceding block, and //! so on. Returns 0 for a block not included in the current chain. virtual int getBlockDepth(const BlockHash &hash) = 0; //! Get block hash. Height must be valid or this function will abort. virtual BlockHash getBlockHash(int height) = 0; //! Get block time. Height must be valid or this function will abort. virtual int64_t getBlockTime(int height) = 0; //! Get block median time past. Height must be valid or this function //! will abort. virtual int64_t getBlockMedianTimePast(int height) = 0; //! Check that the block is available on disk (i.e. has not been //! pruned), and contains transactions. virtual bool haveBlockOnDisk(int height) = 0; //! Return height of the first block in the chain with timestamp equal //! or greater than the given time, or nullopt if there is no block with //! a high enough timestamp. Also return the block hash as an optional //! output parameter (to avoid the cost of a second lookup in case this //! information is needed.) virtual Optional findFirstBlockWithTime(int64_t time, BlockHash *hash) = 0; //! Return height of the first block in the chain with timestamp equal //! or greater than the given time and height equal or greater than the //! given height, or nullopt if there is no such block. //! //! Calling this with height 0 is equivalent to calling //! findFirstBlockWithTime, but less efficient because it requires a //! linear instead of a binary search. virtual Optional findFirstBlockWithTimeAndHeight(int64_t time, int height) = 0; //! Return height of last block in the specified range which is pruned, //! or nullopt if no block in the range is pruned. Range is inclusive. virtual Optional findPruned(int start_height = 0, Optional stop_height = nullopt) = 0; //! Return height of the highest block on the chain that is an ancestor //! of the specified block, or nullopt if no common ancestor is found. //! Also return the height of the specified block as an optional output //! parameter (to avoid the cost of a second hash lookup in case this //! information is desired). virtual Optional findFork(const BlockHash &hash, Optional *height) = 0; //! Return true if block hash points to the current chain tip, or to a //! possible descendant of the current chain tip that isn't currently //! connected. virtual bool isPotentialTip(const BlockHash &hash) = 0; //! Get locator for the current chain tip. virtual CBlockLocator getTipLocator() = 0; //! Return height of the latest block common to locator and chain, which //! is guaranteed to be an ancestor of the block used to create the //! locator. virtual Optional findLocatorFork(const CBlockLocator &locator) = 0; //! Check if transaction will be final given chain height current time. virtual bool contextualCheckTransactionForCurrentBlock( const Consensus::Params ¶ms, const CTransaction &tx, CValidationState &state) = 0; //! Add transaction to memory pool if the transaction fee is below the //! amount specified by absurd_fee (as a safeguard). */ virtual bool submitToMemoryPool(const Config &config, CTransactionRef tx, Amount absurd_fee, CValidationState &state) = 0; }; //! Return Lock interface. Chain is locked when this is called, and //! unlocked when the returned interface is freed. virtual std::unique_ptr lock(bool try_lock = false) = 0; - //! Return Lock interface assuming chain is already locked. This - //! method is temporary and is only used in a few places to avoid changing - //! behavior while code is transitioned to use the Chain::Lock interface. - virtual std::unique_ptr assumeLocked() = 0; - //! Return whether node has the block and optionally return block metadata //! or contents. //! //! If a block pointer is provided to retrieve the block contents, and the //! block exists but doesn't have data (for example due to pruning), the //! block will be empty and all fields set to null. virtual bool findBlock(const BlockHash &hash, CBlock *block = nullptr, int64_t *time = nullptr, int64_t *max_time = nullptr) = 0; //! Look up unspent output information. Returns coins in the mempool and in //! the current chain UTXO set. Iterates through all the keys in the map and //! populates the values. virtual void findCoins(std::map &coins) = 0; //! Estimate fraction of total transactions verified if blocks up to //! the specified block hash are verified. virtual double guessVerificationProgress(const BlockHash &block_hash) = 0; //! Check if transaction has descendants in mempool. virtual bool hasDescendantsInMempool(const TxId &txid) = 0; //! Calculate mempool ancestor and descendant counts for the given //! transaction. virtual void getTransactionAncestry(const TxId &txid, size_t &ancestors, size_t &descendants) = 0; //! Relay transaction. virtual void relayTransaction(const TxId &txid) = 0; //! Check if transaction will pass the mempool's chain limits. virtual bool checkChainLimits(const CTransactionRef &tx) = 0; //! Estimate fee virtual CFeeRate estimateFee() const = 0; //! Relay current minimum fee (from -minrelaytxfee settings). virtual CFeeRate relayMinFee() = 0; //! Relay dust fee setting (-dustrelayfee), reflecting lowest rate it's //! economical to spend. virtual CFeeRate relayDustFee() = 0; //! Get node max tx fee setting (-maxtxfee). //! This could be replaced by a per-wallet max fee, as proposed at //! https://github.com/bitcoin/bitcoin/issues/15355 //! But for the time being, wallets call this to access the node setting. virtual Amount maxTxFee() = 0; //! Check if pruning is enabled. virtual bool getPruneMode() = 0; //! Check if p2p enabled. virtual bool p2pEnabled() = 0; //! Check if the node is ready to broadcast transactions. virtual bool isReadyToBroadcast() = 0; //! Check if in IBD. virtual bool isInitialBlockDownload() = 0; //! Check if shutdown requested. virtual bool shutdownRequested() = 0; //! Get adjusted time. virtual int64_t getAdjustedTime() = 0; //! Send init message. virtual void initMessage(const std::string &message) = 0; //! Send init warning. virtual void initWarning(const std::string &message) = 0; //! Send init error. virtual void initError(const std::string &message) = 0; //! Send wallet load notification to the GUI. virtual void loadWallet(std::unique_ptr wallet) = 0; //! Send progress indicator. virtual void showProgress(const std::string &title, int progress, bool resume_possible) = 0; //! Chain notifications. class Notifications { public: virtual ~Notifications() {} virtual void TransactionAddedToMempool(const CTransactionRef &tx) {} virtual void TransactionRemovedFromMempool(const CTransactionRef &ptx) { } virtual void BlockConnected(const CBlock &block, const std::vector &tx_conflicted) {} virtual void BlockDisconnected(const CBlock &block) {} virtual void UpdatedBlockTip() {} virtual void ChainStateFlushed(const CBlockLocator &locator) {} }; //! Register handler for notifications. virtual std::unique_ptr handleNotifications(Notifications ¬ifications) = 0; //! Wait for pending notifications to be handled. virtual void waitForNotifications() = 0; //! Register handler for RPC. Command is not copied, so reference //! needs to remain valid until Handler is disconnected. virtual std::unique_ptr handleRpc(const CRPCCommand &command) = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for //! monitoring and analysis in the future). class ChainClient { public: virtual ~ChainClient() {} //! Register rpcs. virtual void registerRpcs() = 0; //! Check for errors before loading. virtual bool verify(const CChainParams &chainParams) = 0; //! Load saved state. virtual bool load(const CChainParams &chainParams) = 0; //! Start client execution and provide a scheduler. virtual void start(CScheduler &scheduler) = 0; //! Save state to disk. virtual void flush() = 0; //! Shut down client. virtual void stop() = 0; }; //! Return implementation of Chain interface. std::unique_ptr MakeChain(); //! Return implementation of ChainClient interface for a wallet client. This //! function will be undefined in builds where ENABLE_WALLET is false. //! //! Currently, wallets are the only chain clients. But in the future, other //! types of chain clients could be added, such as tools for monitoring, //! analysis, or fee estimation. These clients need to expose their own //! MakeXXXClient functions returning their implementations of the ChainClient //! interface. std::unique_ptr MakeWalletClient(Chain &chain, std::vector wallet_filenames); } // namespace interfaces #endif // BITCOIN_INTERFACES_CHAIN_H diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 2c329c9bb..cdbfb1960 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -1,491 +1,496 @@ // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static void AddKey(CWallet &wallet, const CKey &key) { LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(key, key.GetPubKey()); } BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Verify ScanForWalletTransactions accommodates a null start block. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( BlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), Amount::zero()); } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block // file. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } // Prune the remaining block file. PruneOneBlockFile(newTip->GetBlockPos().nFile); UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions scans no blocks. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), Amount::zero()); } } BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { std::shared_ptr wallet = std::make_shared( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); UniValue key; key.setObject(); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); key.pushKV("timestamp", 0); key.pushKV("internal", UniValue(true)); keys.push_back(key); key.clear(); key.setObject(); CKey futureKey; futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); JSONRPCRequest request; request.params.setArray(); request.params.push_back(keys); UniValue response = importmulti(GetConfig(), request); BOOST_CHECK_EQUAL( response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":" "\"Rescan failed for key with creation timestamp %d. " "There was an error reading a block from time %d, which " "is after or within %d seconds of key creation, and " "could contain transactions pertaining to the key. As a " "result, transactions and coins using this key may not " "appear in the wallet. This error could be caused by " "pruning or data corruption (see bitcoind log for " "details) and could be dealt with by downloading and " "rescanning the relevant blocks (see -reindex and " "-rescan options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); RemoveWallet(wallet); } } // Verify importwallet RPC starts rescan at earliest block with timestamp // greater or equal than key birthday. Previously there was a bug where // importwallet RPC would start the scan at the latest block with timestamp less // than or equal to key birthday. BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. const int64_t BLOCK_TIME = ::ChainActive().Tip()->GetBlockTimeMax() + 5; SetMockTime(BLOCK_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); // Set key birthday to block time increased by the timestamp window, so // rescan will start at the block time. const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW; SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); auto locked_chain = chain->lock(); std::string backup_file = (GetDataDir() / "wallet.backup").string(); // Import key into wallet and call dumpwallet to create backup file. { std::shared_ptr wallet = std::make_shared( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); JSONRPCRequest request; request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); ::dumpwallet(GetConfig(), request); RemoveWallet(wallet); } // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { std::shared_ptr wallet = std::make_shared( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); ::importwallet(GetConfig(), request); RemoveWallet(wallet); LOCK(wallet->cs_wallet); BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U); BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U); for (size_t i = 0; i < m_coinbase_txns.size(); ++i) { bool found = wallet->GetWalletTx(m_coinbase_txns[i]->GetId()); bool expected = i >= 100; BOOST_CHECK_EQUAL(found, expected); } } } // Check that GetImmatureCredit() returns a newly calculated value instead of // the cached value after a MarkDirty() call. // // This is a regression test written to verify a bugfix for the immature credit // function. Similar tests probably should be written for the other credit and // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { auto chain = interfaces::MakeChain(); CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); auto locked_chain = chain->lock(); LOCK(wallet.cs_wallet); wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash(); wtx.nIndex = 0; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), Amount::zero()); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50 * COIN); } static int64_t AddTx(CWallet &wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) { CMutableTransaction tx; tx.nLockTime = lockTime; SetMockTime(mockTime); CBlockIndex *block = nullptr; if (blockTime > 0) { LockAnnotation lock(::cs_main); auto locked_chain = wallet.chain().lock(); auto inserted = mapBlockIndex.emplace(BlockHash(GetRandHash()), new CBlockIndex); assert(inserted.second); const BlockHash &hash = inserted.first->first; block = inserted.first->second; block->nTime = blockTime; block->phashBlock = &hash; } CWalletTx wtx(&wallet, MakeTransactionRef(tx)); if (block) { wtx.SetMerkleBranch(block->GetBlockHash(), 0); } { LOCK(cs_main); wallet.AddToWallet(wtx); } LOCK(wallet.cs_wallet); return wallet.mapWallet.at(wtx.GetId()).nTimeSmart; } // Simple test to verify assignment of CWalletTx::nSmartTime value. Could be // expanded to cover more corner cases of smart time logic. BOOST_AUTO_TEST_CASE(ComputeTimeSmart) { // New transaction should use clock time if lower than block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 100, 120), 100); // Test that updating existing transaction does not change smart time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 200, 220), 100); // New transaction should use clock time if there's no block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 2, 300, 0), 300); // New transaction should use block time if lower than clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 3, 420, 400), 400); // New transaction should use latest entry time if higher than // min(block time, clock time). BOOST_CHECK_EQUAL(AddTx(m_wallet, 4, 500, 390), 400); // If there are future entries, new transaction should use time of the // newest entry that is no more than 300 seconds ahead of the clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 5, 50, 600), 300); // Reset mock time for other tests. SetMockTime(0); } BOOST_AUTO_TEST_CASE(LoadReceiveRequests) { CTxDestination dest = CKeyID(); LOCK(m_wallet.cs_wallet); m_wallet.AddDestData(dest, "misc", "val_misc"); m_wallet.AddDestData(dest, "rr0", "val_rr0"); m_wallet.AddDestData(dest, "rr1", "val_rr1"); auto values = m_wallet.GetDestValues("rr"); BOOST_CHECK_EQUAL(values.size(), 2U); BOOST_CHECK_EQUAL(values[0], "val_rr0"); BOOST_CHECK_EQUAL(values[1], "val_rr1"); } class ListCoinsTestingSetup : public TestChain100Setup { public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); wallet = std::make_unique(Params(), *m_chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions( ::ChainActive().Genesis()->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, ::ChainActive().Height()); BOOST_CHECK(result.last_failed_block.IsNull()); } ~ListCoinsTestingSetup() { wallet.reset(); } CWalletTx &AddTx(CRecipient recipient) { CTransactionRef tx; CReserveKey reservekey(wallet.get()); Amount fee; int changePos = -1; std::string error; CCoinControl dummy; - BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, - reservekey, fee, changePos, error, - dummy)); + { + auto locked_chain = m_chain->lock(); + BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, + tx, reservekey, fee, + changePos, error, dummy)); + } CValidationState state; BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state)); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetId()).tx); } CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetId()); BOOST_CHECK(it != wallet->mapWallet.end()); it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } std::unique_ptr m_chain = interfaces::MakeChain(); - // Temporary. Removed in upcoming lock cleanup - std::unique_ptr m_locked_chain = - m_chain->assumeLocked(); std::unique_ptr wallet; }; BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString(); // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey // address. std::map> list; { - LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(*m_locked_chain); + auto locked_chain = m_chain->lock(); + LOCK(wallet->cs_wallet); + list = wallet->ListCoins(*locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U); // Check initial balance from one mature coinbase transaction. BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance()); // Add a transaction creating a change address, and confirm ListCoins still // returns the coin associated with the change address underneath the // coinbaseKey pubkey, even though the change address has a different // pubkey. AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { - LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(*m_locked_chain); + auto locked_chain = m_chain->lock(); + LOCK(wallet->cs_wallet); + list = wallet->ListCoins(*locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); // Lock both coins. Confirm number of available coins drops to 0. { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = m_chain->lock(); + LOCK(wallet->cs_wallet); std::vector available; - wallet->AvailableCoins(*m_locked_chain, available); + wallet->AvailableCoins(*locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto &group : list) { for (const auto &coin : group.second) { LOCK(wallet->cs_wallet); wallet->LockCoin(COutPoint(coin.tx->GetId(), coin.i)); } } { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = m_chain->lock(); + LOCK(wallet->cs_wallet); std::vector available; - wallet->AvailableCoins(*m_locked_chain, available); + wallet->AvailableCoins(*locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { - LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(*m_locked_chain); + auto locked_chain = m_chain->lock(); + LOCK(wallet->cs_wallet); + list = wallet->ListCoins(*locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); } BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { auto chain = interfaces::MakeChain(); std::shared_ptr wallet = std::make_shared( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d4fce2526..fbcbab739 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,5023 +1,5022 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include