aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMarcoFalke <falke.marco@gmail.com>2020-04-14 07:18:02 -0400
committerMarcoFalke <falke.marco@gmail.com>2020-04-14 07:18:12 -0400
commit4702cadca94520a36bfe979c85750563c30f7c85 (patch)
tree5e83ec9d43b7b5f4459feb8c421b3282fad74604 /src
parent6110ae8326c74704c9e105deca725f2411395969 (diff)
parent48973402d8bccb673eaeb68b7aa86faa39d3cb8a (diff)
Merge #17954: wallet: Remove calls to Chain::Lock methods
48973402d8bccb673eaeb68b7aa86faa39d3cb8a wallet: Avoid use of Chain::Lock in CWallet::GetKeyBirthTimes (Russell Yanofsky) e958ff9ab5607da2cd321f29fc785a6d359e44f4 wallet: Avoid use of Chain::Lock in CWallet::CreateTransaction (Russell Yanofsky) c0d07dc4cba7634cde4e8bf586557772f3248a42 wallet: Avoid use of Chain::Lock in CWallet::ScanForWalletTransactions (Russell Yanofsky) 1be8ff280c78c30baabae9429c53c0bebb89c44d wallet: Avoid use of Chain::Lock in rescanblockchain (Russell Yanofsky) 3cb85ac594f115db99f96b0a0f4bfdcd69ef0590 wallet refactor: Avoid use of Chain::Lock in CWallet::RescanFromTime (Russell Yanofsky) f7ba881bc669451a60fedac58a449794702a3e23 wallet: Avoid use of Chain::Lock in listsinceblock (Russell Yanofsky) bc96a9bfc61afdb696fb92cb644ed5fc3d1793f1 wallet: Avoid use of Chain::Lock in importmulti (Russell Yanofsky) 25a9fcf9e53bfa94e8f8b19a4abfda0f444f6b2a wallet: Avoid use of Chain::Lock in importwallet and dumpwallet (Russell Yanofsky) c1694ce6bb7e19a8722d5583cd85ad17da40bb67 wallet: Avoid use of Chain::Lock in importprunedfunds (Russell Yanofsky) ade5f87971211bc67753f14a0d49e020142efc7c wallet refactor: Avoid use of Chain::Lock in qt wallettests (Russell Yanofsky) f6da44ccce4cfff53433e665305a6fe0a01364e4 wallet: Avoid use of Chain::Lock in tryGetTxStatus and tryGetBalances (Russell Yanofsky) bf30cd4922ea62577d7bf63f5029e8be62665d45 refactor: Add interfaces::FoundBlock class to selectively return block data (Russell Yanofsky) Pull request description: This is a set of changes updating wallet code to make fewer calls to `Chain::Lock` methods, so the `Chain::Lock` class will be easier to remove in #16426 with fewer code changes and small changes to behavior. ACKs for top commit: MarcoFalke: re-ACK 48973402d8, only change is fixing bug 📀 fjahr: re-ACK 48973402d8bccb673eaeb68b7aa86faa39d3cb8a, reviewed rebase and changes since last review, built and ran tests locally ariard: Coce Review ACK 4897340, only changes are one suggested by last review on more accurate variable naming, human-readable output, args comments in `findCommonAncestor` Tree-SHA512: cfd2f559f976b6faaa032794c40c9659191d5597b013abcb6c7968d36b2abb2b14d4e596f8ed8b9a077e96522365261299a241a939b3111eaf729ba0c3ef519b
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/interfaces/chain.cpp118
-rw-r--r--src/interfaces/chain.h75
-rw-r--r--src/interfaces/wallet.cpp17
-rw-r--r--src/qt/test/wallettests.cpp5
-rw-r--r--src/sync.h2
-rw-r--r--src/test/interfaces_tests.cpp157
-rw-r--r--src/wallet/rpcdump.cpp30
-rw-r--r--src/wallet/rpcwallet.cpp46
-rw-r--r--src/wallet/test/wallet_tests.cpp19
-rw-r--r--src/wallet/wallet.cpp130
-rw-r--r--src/wallet/wallet.h8
12 files changed, 409 insertions, 199 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 4def9b4f49..5bea1efa0d 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -201,6 +201,7 @@ BITCOIN_TESTS =\
test/fs_tests.cpp \
test/getarg_tests.cpp \
test/hash_tests.cpp \
+ test/interfaces_tests.cpp \
test/key_io_tests.cpp \
test/key_tests.cpp \
test/limitedmap_tests.cpp \
diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp
index 0b3cd08e22..c8311b2986 100644
--- a/src/interfaces/chain.cpp
+++ b/src/interfaces/chain.cpp
@@ -38,6 +38,21 @@
namespace interfaces {
namespace {
+bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock)
+{
+ if (!index) return false;
+ if (block.m_hash) *block.m_hash = index->GetBlockHash();
+ if (block.m_height) *block.m_height = index->nHeight;
+ if (block.m_time) *block.m_time = index->GetBlockTime();
+ if (block.m_max_time) *block.m_max_time = index->GetBlockTimeMax();
+ if (block.m_mtp_time) *block.m_mtp_time = index->GetMedianTimePast();
+ if (block.m_data) {
+ REVERSE_LOCK(lock);
+ if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) block.m_data->SetNull();
+ }
+ return true;
+}
+
class LockImpl : public Chain::Lock, public UniqueLock<RecursiveMutex>
{
Optional<int> getHeight() override
@@ -65,20 +80,6 @@ class LockImpl : public Chain::Lock, public UniqueLock<RecursiveMutex>
assert(block != nullptr);
return block->GetBlockHash();
}
- int64_t getBlockTime(int height) override
- {
- LockAssertion lock(::cs_main);
- CBlockIndex* block = ::ChainActive()[height];
- assert(block != nullptr);
- return block->GetBlockTime();
- }
- int64_t getBlockMedianTimePast(int height) override
- {
- LockAssertion lock(::cs_main);
- CBlockIndex* block = ::ChainActive()[height];
- assert(block != nullptr);
- return block->GetMedianTimePast();
- }
bool haveBlockOnDisk(int height) override
{
LockAssertion lock(::cs_main);
@@ -95,20 +96,6 @@ class LockImpl : public Chain::Lock, public UniqueLock<RecursiveMutex>
}
return nullopt;
}
- Optional<int> findPruned(int start_height, Optional<int> stop_height) override
- {
- LockAssertion lock(::cs_main);
- if (::fPruneMode) {
- CBlockIndex* block = stop_height ? ::ChainActive()[*stop_height] : ::ChainActive().Tip();
- while (block && block->nHeight >= start_height) {
- if ((block->nStatus & BLOCK_HAVE_DATA) == 0) {
- return block->nHeight;
- }
- block = block->pprev;
- }
- }
- return nullopt;
- }
Optional<int> findFork(const uint256& hash, Optional<int>* height) override
{
LockAssertion lock(::cs_main);
@@ -247,26 +234,48 @@ public:
std::unique_ptr<Chain::Lock> result = std::move(lock); // Temporary to avoid CWG 1579
return result;
}
- bool findBlock(const uint256& hash, CBlock* block, int64_t* time, int64_t* time_max) override
+ bool findBlock(const uint256& hash, const FoundBlock& block) 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();
+ WAIT_LOCK(cs_main, lock);
+ return FillBlock(LookupBlockIndex(hash), block, lock);
+ }
+ bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override
+ {
+ WAIT_LOCK(cs_main, lock);
+ return FillBlock(ChainActive().FindEarliestAtLeast(min_time, min_height), block, lock);
+ }
+ bool findNextBlock(const uint256& block_hash, int block_height, const FoundBlock& next, bool* reorg) override {
+ WAIT_LOCK(cs_main, lock);
+ CBlockIndex* block = ChainActive()[block_height];
+ if (block && block->GetBlockHash() != block_hash) block = nullptr;
+ if (reorg) *reorg = !block;
+ return FillBlock(block ? ChainActive()[block_height + 1] : nullptr, next, lock);
+ }
+ bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out) override
+ {
+ WAIT_LOCK(cs_main, lock);
+ if (const CBlockIndex* block = LookupBlockIndex(block_hash)) {
+ if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) {
+ return FillBlock(ancestor, ancestor_out, lock);
}
}
- if (block && !ReadBlockFromDisk(*block, index, Params().GetConsensus())) {
- block->SetNull();
- }
- return true;
+ return FillBlock(nullptr, ancestor_out, lock);
+ }
+ bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, const FoundBlock& ancestor_out) override
+ {
+ WAIT_LOCK(cs_main, lock);
+ const CBlockIndex* block = LookupBlockIndex(block_hash);
+ const CBlockIndex* ancestor = LookupBlockIndex(ancestor_hash);
+ if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr;
+ return FillBlock(ancestor, ancestor_out, lock);
+ }
+ bool findCommonAncestor(const uint256& block_hash1, const uint256& block_hash2, const FoundBlock& ancestor_out, const FoundBlock& block1_out, const FoundBlock& block2_out) override
+ {
+ WAIT_LOCK(cs_main, lock);
+ const CBlockIndex* block1 = LookupBlockIndex(block_hash1);
+ const CBlockIndex* block2 = LookupBlockIndex(block_hash2);
+ const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr;
+ return FillBlock(ancestor, ancestor_out, lock) & FillBlock(block1, block1_out, lock) & FillBlock(block2, block2_out, lock);
}
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
double guessVerificationProgress(const uint256& block_hash) override
@@ -274,6 +283,25 @@ public:
LOCK(cs_main);
return GuessVerificationProgress(Params().TxData(), LookupBlockIndex(block_hash));
}
+ bool hasBlocks(const uint256& block_hash, int min_height, Optional<int> max_height) override
+ {
+ // hasBlocks returns true if all ancestors of block_hash in specified
+ // range have block data (are not pruned), false if any ancestors in
+ // specified range are missing data.
+ //
+ // For simplicity and robustness, min_height and max_height are only
+ // used to limit the range, and passing min_height that's too low or
+ // max_height that's too high will not crash or change the result.
+ LOCK(::cs_main);
+ if (CBlockIndex* block = LookupBlockIndex(block_hash)) {
+ if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height);
+ for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) {
+ // Check pprev to not segfault if min_height is too low
+ if (block->nHeight <= min_height || !block->pprev) return true;
+ }
+ }
+ return false;
+ }
RBFTransactionState isRBFOptIn(const CTransaction& tx) override
{
LOCK(::mempool.cs);
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index ffa9e90c79..fb77c81cdc 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -30,6 +30,27 @@ namespace interfaces {
class Handler;
class Wallet;
+//! Helper for findBlock to selectively return pieces of block data.
+class FoundBlock
+{
+public:
+ FoundBlock& hash(uint256& hash) { m_hash = &hash; return *this; }
+ FoundBlock& height(int& height) { m_height = &height; return *this; }
+ FoundBlock& time(int64_t& time) { m_time = &time; return *this; }
+ FoundBlock& maxTime(int64_t& max_time) { m_max_time = &max_time; return *this; }
+ FoundBlock& mtpTime(int64_t& mtp_time) { m_mtp_time = &mtp_time; return *this; }
+ //! Read block data from disk. If the block exists but doesn't have data
+ //! (for example due to pruning), the CBlock variable will be set to null.
+ FoundBlock& data(CBlock& data) { m_data = &data; return *this; }
+
+ uint256* m_hash = nullptr;
+ int* m_height = nullptr;
+ int64_t* m_time = nullptr;
+ int64_t* m_max_time = nullptr;
+ int64_t* m_mtp_time = nullptr;
+ CBlock* m_data = nullptr;
+};
+
//! 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.
@@ -79,13 +100,6 @@ public:
//! Get block hash. Height must be valid or this function will abort.
virtual uint256 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;
@@ -97,10 +111,6 @@ public:
//! (to avoid the cost of a second lookup in case this information is needed.)
virtual Optional<int> findFirstBlockWithTimeAndHeight(int64_t time, int height, uint256* hash) = 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<int> findPruned(int start_height = 0, Optional<int> stop_height = nullopt) = 0;
-
//! Return height of the specified block if it is on the chain, otherwise
//! return the height of the highest block on chain that's an ancestor
//! of the specified block, or nullopt if there is no common ancestor.
@@ -127,14 +137,36 @@ public:
//! 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 uint256& hash,
- CBlock* block = nullptr,
- int64_t* time = nullptr,
- int64_t* max_time = nullptr) = 0;
+ virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0;
+
+ //! Find first block in the chain with timestamp >= the given time
+ //! and height >= than the given height, return false if there is no block
+ //! with a high enough timestamp and height. Optionally return block
+ //! information.
+ virtual bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block={}) = 0;
+
+ //! Find next block if block is part of current chain. Also flag if
+ //! there was a reorg and the specified block hash is no longer in the
+ //! current chain, and optionally return block information.
+ virtual bool findNextBlock(const uint256& block_hash, int block_height, const FoundBlock& next={}, bool* reorg=nullptr) = 0;
+
+ //! Find ancestor of block at specified height and optionally return
+ //! ancestor information.
+ virtual bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out={}) = 0;
+
+ //! Return whether block descends from a specified ancestor, and
+ //! optionally return ancestor information.
+ virtual bool findAncestorByHash(const uint256& block_hash,
+ const uint256& ancestor_hash,
+ const FoundBlock& ancestor_out={}) = 0;
+
+ //! Find most recent common ancestor between two blocks and optionally
+ //! return block information.
+ virtual bool findCommonAncestor(const uint256& block_hash1,
+ const uint256& block_hash2,
+ const FoundBlock& ancestor_out={},
+ const FoundBlock& block1_out={},
+ const FoundBlock& block2_out={}) = 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
@@ -145,6 +177,11 @@ public:
//! the specified block hash are verified.
virtual double guessVerificationProgress(const uint256& block_hash) = 0;
+ //! Return true if data is available for all blocks in the specified range
+ //! of blocks. This checks all blocks that are ancestors of block_hash in
+ //! the height range from min_height to max_height, inclusive.
+ virtual bool hasBlocks(const uint256& block_hash, int min_height = 0, Optional<int> max_height = {}) = 0;
+
//! Check if transaction is RBF opt in.
virtual RBFTransactionState isRBFOptIn(const CTransaction& tx) = 0;
diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp
index c6e0db34f7..73fe11f2af 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -14,6 +14,7 @@
#include <sync.h>
#include <ui_interface.h>
#include <uint256.h>
+#include <util/check.h>
#include <util/system.h>
#include <wallet/feebumper.h>
#include <wallet/fees.h>
@@ -62,7 +63,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx)
{
WalletTxStatus result;
- result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max());
+ result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max();
result.blocks_to_maturity = wtx.GetBlocksToMaturity();
result.depth_in_main_chain = wtx.GetDepthInMainChain();
result.time_received = wtx.nTimeReceived;
@@ -318,13 +319,9 @@ public:
if (mi == m_wallet->mapWallet.end()) {
return false;
}
- if (Optional<int> height = locked_chain->getHeight()) {
- num_blocks = *height;
- block_time = locked_chain->getBlockTime(*height);
- } else {
- num_blocks = -1;
- block_time = -1;
- }
+ num_blocks = m_wallet->GetLastBlockHeight();
+ block_time = -1;
+ CHECK_NONFATAL(m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), FoundBlock().time(block_time)));
tx_status = MakeWalletTxStatus(*locked_chain, mi->second);
return true;
}
@@ -373,12 +370,12 @@ public:
{
auto locked_chain = m_wallet->chain().lock(true /* try_lock */);
if (!locked_chain) return false;
- num_blocks = locked_chain->getHeight().get_value_or(-1);
- if (!force && num_blocks == cached_num_blocks) return false;
TRY_LOCK(m_wallet->cs_wallet, locked_wallet);
if (!locked_wallet) {
return false;
}
+ num_blocks = m_wallet->GetLastBlockHeight();
+ if (!force && num_blocks == cached_num_blocks) return false;
balances = getBalances();
return true;
}
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index 41f377d6d2..c08086457e 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -152,12 +152,9 @@ void TestGUI(interfaces::Node& node)
wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash());
}
{
- auto locked_chain = wallet->chain().lock();
- LockAssertion lock(::cs_main);
-
WalletRescanReserver reserver(wallet.get());
reserver.reserve();
- CWallet::ScanResult result = wallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {} /* stop_block */, reserver, true /* fUpdate */);
+ CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */);
QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash());
QVERIFY(result.last_failed_block.IsNull());
diff --git a/src/sync.h b/src/sync.h
index ead2cdc67b..0c6f0ef0a7 100644
--- a/src/sync.h
+++ b/src/sync.h
@@ -210,7 +210,7 @@ public:
friend class reverse_lock;
};
-#define REVERSE_LOCK(g) decltype(g)::reverse_lock PASTE2(revlock, __COUNTER__)(g, #g, __FILE__, __LINE__)
+#define REVERSE_LOCK(g) typename std::decay<decltype(g)>::type::reverse_lock PASTE2(revlock, __COUNTER__)(g, #g, __FILE__, __LINE__)
template<typename MutexArg>
using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>;
diff --git a/src/test/interfaces_tests.cpp b/src/test/interfaces_tests.cpp
new file mode 100644
index 0000000000..fab3571756
--- /dev/null
+++ b/src/test/interfaces_tests.cpp
@@ -0,0 +1,157 @@
+// Copyright (c) 2020 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 <chainparams.h>
+#include <consensus/validation.h>
+#include <interfaces/chain.h>
+#include <script/standard.h>
+#include <test/util/setup_common.h>
+#include <validation.h>
+
+#include <boost/test/unit_test.hpp>
+
+using interfaces::FoundBlock;
+
+BOOST_FIXTURE_TEST_SUITE(interfaces_tests, TestChain100Setup)
+
+BOOST_AUTO_TEST_CASE(findBlock)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto& active = ChainActive();
+
+ uint256 hash;
+ BOOST_CHECK(chain->findBlock(active[10]->GetBlockHash(), FoundBlock().hash(hash)));
+ BOOST_CHECK_EQUAL(hash, active[10]->GetBlockHash());
+
+ int height = -1;
+ BOOST_CHECK(chain->findBlock(active[20]->GetBlockHash(), FoundBlock().height(height)));
+ BOOST_CHECK_EQUAL(height, active[20]->nHeight);
+
+ CBlock data;
+ BOOST_CHECK(chain->findBlock(active[30]->GetBlockHash(), FoundBlock().data(data)));
+ BOOST_CHECK_EQUAL(data.GetHash(), active[30]->GetBlockHash());
+
+ int64_t time = -1;
+ BOOST_CHECK(chain->findBlock(active[40]->GetBlockHash(), FoundBlock().time(time)));
+ BOOST_CHECK_EQUAL(time, active[40]->GetBlockTime());
+
+ int64_t max_time = -1;
+ BOOST_CHECK(chain->findBlock(active[50]->GetBlockHash(), FoundBlock().maxTime(max_time)));
+ BOOST_CHECK_EQUAL(max_time, active[50]->GetBlockTimeMax());
+
+ int64_t mtp_time = -1;
+ BOOST_CHECK(chain->findBlock(active[60]->GetBlockHash(), FoundBlock().mtpTime(mtp_time)));
+ BOOST_CHECK_EQUAL(mtp_time, active[60]->GetMedianTimePast());
+
+ BOOST_CHECK(!chain->findBlock({}, FoundBlock()));
+}
+
+BOOST_AUTO_TEST_CASE(findFirstBlockWithTimeAndHeight)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto& active = ChainActive();
+ uint256 hash;
+ int height;
+ BOOST_CHECK(chain->findFirstBlockWithTimeAndHeight(/* min_time= */ 0, /* min_height= */ 5, FoundBlock().hash(hash).height(height)));
+ BOOST_CHECK_EQUAL(hash, active[5]->GetBlockHash());
+ BOOST_CHECK_EQUAL(height, 5);
+ BOOST_CHECK(!chain->findFirstBlockWithTimeAndHeight(/* min_time= */ active.Tip()->GetBlockTimeMax() + 1, /* min_height= */ 0));
+}
+
+BOOST_AUTO_TEST_CASE(findNextBlock)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto& active = ChainActive();
+ bool reorg;
+ uint256 hash;
+ BOOST_CHECK(chain->findNextBlock(active[20]->GetBlockHash(), 20, FoundBlock().hash(hash), &reorg));
+ BOOST_CHECK_EQUAL(hash, active[21]->GetBlockHash());
+ BOOST_CHECK_EQUAL(reorg, false);
+ BOOST_CHECK(!chain->findNextBlock(uint256(), 20, {}, &reorg));
+ BOOST_CHECK_EQUAL(reorg, true);
+ BOOST_CHECK(!chain->findNextBlock(active.Tip()->GetBlockHash(), active.Height(), {}, &reorg));
+ BOOST_CHECK_EQUAL(reorg, false);
+}
+
+BOOST_AUTO_TEST_CASE(findAncestorByHeight)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto& active = ChainActive();
+ uint256 hash;
+ BOOST_CHECK(chain->findAncestorByHeight(active[20]->GetBlockHash(), 10, FoundBlock().hash(hash)));
+ BOOST_CHECK_EQUAL(hash, active[10]->GetBlockHash());
+ BOOST_CHECK(!chain->findAncestorByHeight(active[10]->GetBlockHash(), 20));
+}
+
+BOOST_AUTO_TEST_CASE(findAncestorByHash)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto& active = ChainActive();
+ int height = -1;
+ BOOST_CHECK(chain->findAncestorByHash(active[20]->GetBlockHash(), active[10]->GetBlockHash(), FoundBlock().height(height)));
+ BOOST_CHECK_EQUAL(height, 10);
+ BOOST_CHECK(!chain->findAncestorByHash(active[10]->GetBlockHash(), active[20]->GetBlockHash()));
+}
+
+BOOST_AUTO_TEST_CASE(findCommonAncestor)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto& active = ChainActive();
+ auto* orig_tip = active.Tip();
+ for (int i = 0; i < 10; ++i) {
+ BlockValidationState state;
+ ChainstateActive().InvalidateBlock(state, Params(), active.Tip());
+ }
+ BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight - 10);
+ coinbaseKey.MakeNewKey(true);
+ for (int i = 0; i < 20; ++i) {
+ CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ }
+ BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight + 10);
+ uint256 fork_hash;
+ int fork_height;
+ int orig_height;
+ BOOST_CHECK(chain->findCommonAncestor(orig_tip->GetBlockHash(), active.Tip()->GetBlockHash(), FoundBlock().height(fork_height).hash(fork_hash), FoundBlock().height(orig_height)));
+ BOOST_CHECK_EQUAL(orig_height, orig_tip->nHeight);
+ BOOST_CHECK_EQUAL(fork_height, orig_tip->nHeight - 10);
+ BOOST_CHECK_EQUAL(fork_hash, active[fork_height]->GetBlockHash());
+}
+
+BOOST_AUTO_TEST_CASE(hasBlocks)
+{
+ auto chain = interfaces::MakeChain(m_node);
+ auto& active = ChainActive();
+
+ // Test ranges
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 10, 90));
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 10, {}));
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 0, 90));
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 0, {}));
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), -1000, 1000));
+ active[5]->nStatus &= ~BLOCK_HAVE_DATA;
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 10, 90));
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 10, {}));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 0, 90));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 0, {}));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), -1000, 1000));
+ active[95]->nStatus &= ~BLOCK_HAVE_DATA;
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 10, 90));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 10, {}));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 0, 90));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 0, {}));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), -1000, 1000));
+ active[50]->nStatus &= ~BLOCK_HAVE_DATA;
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 10, 90));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 10, {}));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 0, 90));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 0, {}));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), -1000, 1000));
+
+ // Test edge cases
+ BOOST_CHECK(chain->hasBlocks(active.Tip()->GetBlockHash(), 6, 49));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 5, 49));
+ BOOST_CHECK(!chain->hasBlocks(active.Tip()->GetBlockHash(), 6, 50));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index ea54027c48..6baada3f46 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -28,6 +28,8 @@
+using interfaces::FoundBlock;
+
std::string static EncodeDumpString(const std::string &str) {
std::stringstream ret;
for (const unsigned char c : str) {
@@ -360,8 +362,9 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
}
auto locked_chain = pwallet->chain().lock();
- Optional<int> height = locked_chain->getBlockHeight(merkleBlock.header.GetHash());
- if (height == nullopt) {
+ LOCK(pwallet->cs_wallet);
+ int height;
+ if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
}
@@ -372,11 +375,9 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
unsigned int txnIndex = vIndex[it - vMatch.begin()];
- CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *height, merkleBlock.header.GetHash(), txnIndex);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, merkleBlock.header.GetHash(), txnIndex);
wtx.m_confirm = confirm;
- LOCK(pwallet->cs_wallet);
-
if (pwallet->IsMine(*wtx.tx)) {
pwallet->AddToWallet(wtx, false);
return NullUniValue;
@@ -566,8 +567,7 @@ UniValue importwallet(const JSONRPCRequest& request)
if (!file.is_open()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
}
- Optional<int> tip_height = locked_chain->getHeight();
- nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0;
+ CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin)));
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
file.seekg(0, file.beg);
@@ -791,9 +791,10 @@ UniValue dumpwallet(const JSONRPCRequest& request)
// produce output
file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD);
file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
- const Optional<int> tip_height = locked_chain->getHeight();
- file << strprintf("# * Best block at time of backup was %i (%s),\n", tip_height.get_value_or(-1), tip_height ? locked_chain->getBlockHash(*tip_height).ToString() : "(missing block hash)");
- file << strprintf("# mined on %s\n", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : "(missing block time)");
+ file << strprintf("# * Best block at time of backup was %i (%s),\n", pwallet->GetLastBlockHeight(), pwallet->GetLastBlockHash().ToString());
+ int64_t block_time = 0;
+ CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(block_time)));
+ file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time));
file << "\n";
// add the base58check encoded extended master if the wallet uses HD
@@ -1379,20 +1380,13 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
EnsureWalletIsUnlocked(pwallet);
// Verify all timestamps are present before importing any keys.
- const Optional<int> tip_height = locked_chain->getHeight();
- now = tip_height ? locked_chain->getBlockMedianTimePast(*tip_height) : 0;
+ CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now)));
for (const UniValue& data : requests.getValues()) {
GetImportTimestamp(data, now);
}
const int64_t minimumTimestamp = 1;
- if (fRescan && tip_height) {
- nLowestTimestamp = locked_chain->getBlockTime(*tip_height);
- } else {
- fRescan = false;
- }
-
for (const UniValue& data : requests.getValues()) {
const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
const UniValue result = ProcessImport(pwallet, data, timestamp);
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index e98e928a1d..d24d92a178 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -37,6 +37,8 @@
#include <univalue.h>
+using interfaces::FoundBlock;
+
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
@@ -143,8 +145,7 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
entry.pushKV("blockheight", wtx.m_confirm.block_height);
entry.pushKV("blockindex", wtx.m_confirm.nIndex);
int64_t block_time;
- bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time);
- CHECK_NONFATAL(found_block);
+ CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time)));
entry.pushKV("blocktime", block_time);
} else {
entry.pushKV("trusted", wtx.IsTrusted(locked_chain));
@@ -1578,8 +1579,9 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
uint256 blockId;
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
blockId = ParseHashV(request.params[0], "blockhash");
- height = locked_chain->findFork(blockId, &altheight);
- if (!height) {
+ height.emplace();
+ altheight.emplace();
+ if (!pwallet->chain().findCommonAncestor(blockId, pwallet->GetLastBlockHash(), /* ancestor out */ FoundBlock().height(*height), /* blockId out */ FoundBlock().height(*altheight))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
@@ -1598,8 +1600,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
- const Optional<int> tip_height = locked_chain->getHeight();
- int depth = tip_height && height ? (1 + *tip_height - *height) : -1;
+ int depth = height ? pwallet->GetLastBlockHeight() + 1 - *height : -1;
UniValue transactions(UniValue::VARR);
@@ -1616,7 +1617,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
UniValue removed(UniValue::VARR);
while (include_removed && altheight && *altheight > *height) {
CBlock block;
- if (!pwallet->chain().findBlock(blockId, &block) || block.IsNull()) {
+ if (!pwallet->chain().findBlock(blockId, FoundBlock().data(block)) || block.IsNull()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
}
for (const CTransactionRef& tx : block.vtx) {
@@ -1631,8 +1632,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
--*altheight;
}
- int last_height = tip_height ? *tip_height + 1 - target_confirms : -1;
- uint256 lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : uint256();
+ uint256 lastblock;
+ CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), pwallet->GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
UniValue ret(UniValue::VOBJ);
ret.pushKV("transactions", transactions);
@@ -3541,22 +3542,23 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
}
int start_height = 0;
- uint256 start_block, stop_block;
+ Optional<int> stop_height;
+ uint256 start_block;
{
auto locked_chain = pwallet->chain().lock();
- Optional<int> tip_height = locked_chain->getHeight();
+ LOCK(pwallet->cs_wallet);
+ int tip_height = pwallet->GetLastBlockHeight();
if (!request.params[0].isNull()) {
start_height = request.params[0].get_int();
- if (start_height < 0 || !tip_height || start_height > *tip_height) {
+ if (start_height < 0 || start_height > tip_height) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
}
}
- Optional<int> stop_height;
if (!request.params[1].isNull()) {
stop_height = request.params[1].get_int();
- if (*stop_height < 0 || !tip_height || *stop_height > *tip_height) {
+ if (*stop_height < 0 || *stop_height > tip_height) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
}
else if (*stop_height < start_height) {
@@ -3565,25 +3567,15 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
}
// We can't rescan beyond non-pruned blocks, stop and throw an error
- if (locked_chain->findPruned(start_height, stop_height)) {
+ if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) {
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
}
- if (tip_height) {
- start_block = locked_chain->getBlockHash(start_height);
- // If called with a stop_height, set the stop_height here to
- // trigger a rescan to that height.
- // If called without a stop height, leave stop_height as null here
- // so rescan continues to the tip (even if the tip advances during
- // rescan).
- if (stop_height) {
- stop_block = locked_chain->getBlockHash(*stop_height);
- }
- }
+ CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block)));
}
CWallet::ScanResult result =
- pwallet->ScanForWalletTransactions(start_block, stop_block, reserver, true /* fUpdate */);
+ pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, true /* fUpdate */);
switch (result.status) {
case CWallet::ScanResult::SUCCESS:
break;
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 13e10c1eab..32665f41c8 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -46,7 +46,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
auto locked_chain = chain->lock();
LockAssertion lock(::cs_main);
- // Verify ScanForWalletTransactions accommodates a null start block.
+ // Verify ScanForWalletTransactions fails to read an unknown start block.
{
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
{
@@ -56,8 +56,8 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, {} /* stop_block */, reserver, false /* update */);
- BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
+ BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK(result.last_scanned_block.IsNull());
BOOST_CHECK(!result.last_scanned_height);
@@ -75,7 +75,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, 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());
@@ -98,7 +98,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, 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());
@@ -120,7 +120,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, 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());
@@ -152,6 +152,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
wallet->SetupLegacyScriptPubKeyMan();
+ WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
AddWallet(wallet);
UniValue keys;
keys.setArray();
@@ -225,6 +226,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
request.params.setArray();
request.params.push_back(backup_file);
AddWallet(wallet);
+ wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
::dumpwallet(request);
RemoveWallet(wallet);
}
@@ -233,16 +235,17 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// were scanned, and no prior blocks were scanned.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ LOCK(wallet->cs_wallet);
wallet->SetupLegacyScriptPubKeyMan();
JSONRPCRequest request;
request.params.setArray();
request.params.push_back(backup_file);
AddWallet(wallet);
+ wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
::importwallet(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) {
@@ -462,7 +465,7 @@ public:
AddKey(*wallet, coinbaseKey);
WalletRescanReserver reserver(wallet.get());
reserver.reserve();
- CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, 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());
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 45f5542cad..519f022cc8 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -22,6 +22,7 @@
#include <script/script.h>
#include <script/signingprovider.h>
#include <util/bip32.h>
+#include <util/check.h>
#include <util/error.h>
#include <util/fees.h>
#include <util/moneystr.h>
@@ -35,6 +36,8 @@
#include <boost/algorithm/string/replace.hpp>
+using interfaces::FoundBlock;
+
const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
{WALLET_FLAG_AVOID_REUSE,
"You need to rescan the blockchain in order to correctly mark used "
@@ -1594,22 +1597,17 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
// Find starting block. May be null if nCreateTime is greater than the
// highest blockchain timestamp, in which case there is nothing that needs
// to be scanned.
+ int start_height = 0;
uint256 start_block;
- {
- auto locked_chain = chain().lock();
- const Optional<int> start_height = locked_chain->findFirstBlockWithTimeAndHeight(startTime - TIMESTAMP_WINDOW, 0, &start_block);
- const Optional<int> tip_height = locked_chain->getHeight();
- WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, tip_height && start_height ? *tip_height - *start_height + 1 : 0);
- }
+ bool start = chain().findFirstBlockWithTimeAndHeight(startTime - TIMESTAMP_WINDOW, 0, FoundBlock().hash(start_block).height(start_height));
+ WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, start ? WITH_LOCK(cs_wallet, return GetLastBlockHeight()) - start_height + 1 : 0);
- if (!start_block.IsNull()) {
+ if (start) {
// TODO: this should take into account failure by ScanResult::USER_ABORT
- ScanResult result = ScanForWalletTransactions(start_block, {} /* stop_block */, reserver, update);
+ ScanResult result = ScanForWalletTransactions(start_block, start_height, {} /* max_height */, reserver, update);
if (result.status == ScanResult::FAILURE) {
int64_t time_max;
- if (!chain().findBlock(result.last_failed_block, nullptr /* block */, nullptr /* time */, &time_max)) {
- throw std::logic_error("ScanForWalletTransactions returned invalid block hash");
- }
+ CHECK_NONFATAL(chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max)));
return time_max + TIMESTAMP_WINDOW + 1;
}
}
@@ -1623,9 +1621,9 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
*
* @param[in] start_block Scan starting block. If block is not on the active
* chain, the scan will return SUCCESS immediately.
- * @param[in] stop_block Scan ending block. If block is not on the active
- * chain, the scan will continue until it reaches the
- * chain tip.
+ * @param[in] start_height Height of start_block
+ * @param[in] max_height Optional max scanning height. If unset there is
+ * no maximum and scanning can continue to the tip
*
* @return ScanResult returning scan information and indicating success or
* failure. Return status will be set to SUCCESS if scan was
@@ -1637,7 +1635,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
* the main chain after to the addition of any new keys you want to detect
* transactions for.
*/
-CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, const uint256& stop_block, const WalletRescanReserver& reserver, bool fUpdate)
+CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate)
{
int64_t nNow = GetTime();
int64_t start_time = GetTimeMillis();
@@ -1651,36 +1649,32 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
fAbortRescan = false;
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
- uint256 tip_hash;
- // The way the 'block_height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0.
- Optional<int> block_height = MakeOptional(false, int());
- double progress_begin;
- double progress_end;
- {
- auto locked_chain = chain().lock();
- if (Optional<int> tip_height = locked_chain->getHeight()) {
- tip_hash = locked_chain->getBlockHash(*tip_height);
- }
- block_height = locked_chain->getBlockHeight(block_hash);
- progress_begin = chain().guessVerificationProgress(block_hash);
- progress_end = chain().guessVerificationProgress(stop_block.IsNull() ? tip_hash : stop_block);
- }
+ uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
+ uint256 end_hash = tip_hash;
+ if (max_height) chain().findAncestorByHeight(tip_hash, *max_height, FoundBlock().hash(end_hash));
+ double progress_begin = chain().guessVerificationProgress(block_hash);
+ double progress_end = chain().guessVerificationProgress(end_hash);
double progress_current = progress_begin;
- while (block_height && !fAbortRescan && !chain().shutdownRequested()) {
+ int block_height = start_height;
+ while (!fAbortRescan && !chain().shutdownRequested()) {
m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin);
- if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
+ if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
}
if (GetTime() >= nNow + 60) {
nNow = GetTime();
- WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current);
+ WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
CBlock block;
- if (chain().findBlock(block_hash, &block) && !block.IsNull()) {
+ bool next_block;
+ uint256 next_block_hash;
+ bool reorg = false;
+ if (chain().findBlock(block_hash, FoundBlock().data(block)) && !block.IsNull()) {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
- if (!locked_chain->getBlockHeight(block_hash)) {
+ next_block = chain().findNextBlock(block_hash, block_height, FoundBlock().hash(next_block_hash), &reorg);
+ if (reorg) {
// Abort scan if current block is no longer active, to prevent
// marking transactions as coming from the wrong block.
// TODO: This should return success instead of failure, see
@@ -1690,37 +1684,38 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
break;
}
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
- CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *block_height, block_hash, posInBlock);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, block_height, block_hash, posInBlock);
SyncTransaction(block.vtx[posInBlock], confirm, fUpdate);
}
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
- result.last_scanned_height = *block_height;
+ result.last_scanned_height = block_height;
} else {
// could not scan block, keep scanning but record this block as the most recent failure
result.last_failed_block = block_hash;
result.status = ScanResult::FAILURE;
+ next_block = chain().findNextBlock(block_hash, block_height, FoundBlock().hash(next_block_hash), &reorg);
}
- if (block_hash == stop_block) {
+ if (max_height && block_height >= *max_height) {
break;
}
{
auto locked_chain = chain().lock();
- Optional<int> tip_height = locked_chain->getHeight();
- if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) {
+ if (!next_block || reorg) {
// break successfully when rescan has reached the tip, or
// previous block is no longer on the chain due to a reorg
break;
}
// increment block and verification progress
- block_hash = locked_chain->getBlockHash(++*block_height);
+ block_hash = next_block_hash;
+ ++block_height;
progress_current = chain().guessVerificationProgress(block_hash);
// handle updated tip hash
const uint256 prev_tip_hash = tip_hash;
- tip_hash = locked_chain->getBlockHash(*tip_height);
- if (stop_block.IsNull() && prev_tip_hash != tip_hash) {
+ tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
+ if (!max_height && prev_tip_hash != tip_hash) {
// in case the tip has changed, update progress max
progress_end = chain().guessVerificationProgress(tip_hash);
}
@@ -1728,10 +1723,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
}
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 100); // hide progress dialog in GUI
if (block_height && fAbortRescan) {
- WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", *block_height, progress_current);
+ WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current);
result.status = ScanResult::USER_ABORT;
} else if (block_height && chain().shutdownRequested()) {
- WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", *block_height, progress_current);
+ WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current);
result.status = ScanResult::USER_ABORT;
} else {
WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time);
@@ -2631,13 +2626,15 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
return true;
}
-static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain)
+static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash)
{
if (chain.isInitialBlockDownload()) {
return false;
}
constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds
- if (locked_chain.getBlockTime(*locked_chain.getHeight()) < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) {
+ int64_t block_time;
+ CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time)));
+ if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) {
return false;
}
return true;
@@ -2647,9 +2644,8 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, interfaces::Cha
* Return a height-based locktime for new transactions (uses the height of the
* current chain tip unless we are not synced with the current chain
*/
-static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain)
+static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height)
{
- uint32_t const height = locked_chain.getHeight().get_value_or(-1);
uint32_t locktime;
// Discourage fee sniping.
//
@@ -2671,8 +2667,8 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, interface
// enough, that fee sniping isn't a problem yet, but by implementing a fix
// now we ensure code won't be written that makes assumptions about
// nLockTime that preclude a fix later.
- if (IsCurrentForAntiFeeSniping(chain, locked_chain)) {
- locktime = height;
+ if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
+ locktime = block_height;
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
@@ -2686,7 +2682,6 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, interface
// unique "nLockTime fingerprint", set nLockTime to a constant.
locktime = 0;
}
- assert(locktime <= height);
assert(locktime < LOCKTIME_THRESHOLD);
return locktime;
}
@@ -2746,9 +2741,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
}
CMutableTransaction txNew;
-
- txNew.nLockTime = GetLocktimeForNewTransaction(chain(), locked_chain);
-
FeeCalculation feeCalc;
CAmount nFeeNeeded;
int nBytes;
@@ -2756,6 +2748,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
std::set<CInputCoin> setCoins;
auto locked_chain = chain().lock();
LOCK(cs_wallet);
+ txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
{
std::vector<COutput> vAvailableCoins;
AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
@@ -3576,12 +3569,13 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
}
// map in which we'll infer heights of other keys
- const Optional<int> tip_height = locked_chain.getHeight();
- const int max_height = tip_height && *tip_height > 144 ? *tip_height - 144 : 0; // the tip can be reorganized; use a 144-block safety margin
- std::map<CKeyID, int> mapKeyFirstBlock;
+ std::map<CKeyID, const CWalletTx::Confirmation*> mapKeyFirstBlock;
+ CWalletTx::Confirmation max_confirm;
+ max_confirm.block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin
+ CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.block_height, FoundBlock().hash(max_confirm.hashBlock)));
for (const CKeyID &keyid : spk_man->GetKeys()) {
if (mapKeyBirth.count(keyid) == 0)
- mapKeyFirstBlock[keyid] = max_height;
+ mapKeyFirstBlock[keyid] = &max_confirm;
}
// if there are no such keys, we're done
@@ -3592,23 +3586,27 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
for (const auto& entry : mapWallet) {
// iterate over all wallet transactions...
const CWalletTx &wtx = entry.second;
- if (Optional<int> height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) {
+ if (wtx.m_confirm.status == CWalletTx::CONFIRMED) {
// ... which are already in a block
for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs
for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
// ... and all their affected keys
- std::map<CKeyID, int>::iterator rit = mapKeyFirstBlock.find(keyid);
- if (rit != mapKeyFirstBlock.end() && *height < rit->second)
- rit->second = *height;
+ auto rit = mapKeyFirstBlock.find(keyid);
+ if (rit != mapKeyFirstBlock.end() && wtx.m_confirm.block_height < rit->second->block_height) {
+ rit->second = &wtx.m_confirm;
+ }
}
}
}
}
// Extract block timestamps for those keys
- for (const auto& entry : mapKeyFirstBlock)
- mapKeyBirth[entry.first] = locked_chain.getBlockTime(entry.second) - TIMESTAMP_WINDOW; // block times can be 2h off
+ for (const auto& entry : mapKeyFirstBlock) {
+ int64_t block_time;
+ CHECK_NONFATAL(chain().findBlock(entry.second->hashBlock, FoundBlock().time(block_time)));
+ mapKeyBirth[entry.first] = block_time - TIMESTAMP_WINDOW; // block times can be 2h off
+ }
}
/**
@@ -3637,7 +3635,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
int64_t blocktime;
- if (chain().findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &blocktime)) {
+ if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(blocktime))) {
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
@@ -4065,7 +4063,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
{
WalletRescanReserver reserver(walletInstance.get());
- if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), {} /* stop block */, reserver, true /* update */).status)) {
+ if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
error = _("Failed to rescan the wallet during initialization").translated;
return nullptr;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 6c54c72e76..577a739d89 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -907,7 +907,7 @@ public:
//! USER_ABORT.
uint256 last_failed_block;
};
- ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
+ ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate);
void transactionRemovedFromMempool(const CTransactionRef &ptx) override;
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions();
@@ -1213,6 +1213,12 @@ public:
assert(m_last_block_processed_height >= 0);
return m_last_block_processed_height;
};
+ uint256 GetLastBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
+ {
+ AssertLockHeld(cs_wallet);
+ assert(m_last_block_processed_height >= 0);
+ return m_last_block_processed;
+ }
/** Set last block processed height, currently only use in unit test */
void SetLastBlockProcessed(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
{