Merge #17954: wallet: Remove calls to Chain::Lock methods
48973402d8
wallet: Avoid use of Chain::Lock in CWallet::GetKeyBirthTimes (Russell Yanofsky)e958ff9ab5
wallet: Avoid use of Chain::Lock in CWallet::CreateTransaction (Russell Yanofsky)c0d07dc4cb
wallet: Avoid use of Chain::Lock in CWallet::ScanForWalletTransactions (Russell Yanofsky)1be8ff280c
wallet: Avoid use of Chain::Lock in rescanblockchain (Russell Yanofsky)3cb85ac594
wallet refactor: Avoid use of Chain::Lock in CWallet::RescanFromTime (Russell Yanofsky)f7ba881bc6
wallet: Avoid use of Chain::Lock in listsinceblock (Russell Yanofsky)bc96a9bfc6
wallet: Avoid use of Chain::Lock in importmulti (Russell Yanofsky)25a9fcf9e5
wallet: Avoid use of Chain::Lock in importwallet and dumpwallet (Russell Yanofsky)c1694ce6bb
wallet: Avoid use of Chain::Lock in importprunedfunds (Russell Yanofsky)ade5f87971
wallet refactor: Avoid use of Chain::Lock in qt wallettests (Russell Yanofsky)f6da44ccce
wallet: Avoid use of Chain::Lock in tryGetTxStatus and tryGetBalances (Russell Yanofsky)bf30cd4922
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-ACK48973402d8
, only change is fixing bug 📀 fjahr: re-ACK48973402d8
, reviewed rebase and changes since last review, built and ran tests locally ariard: Coce Review ACK4897340
, only changes are one suggested by last review on more accurate variable naming, human-readable output, args comments in `findCommonAncestor` Tree-SHA512: cfd2f559f976b6faaa032794c40c9659191d5597b013abcb6c7968d36b2abb2b14d4e596f8ed8b9a077e96522365261299a241a939b3111eaf729ba0c3ef519b
This commit is contained in:
commit
4702cadca9
|
@ -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 \
|
||||
|
|
|
@ -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;
|
||||
WAIT_LOCK(cs_main, lock);
|
||||
return FillBlock(LookupBlockIndex(hash), block, lock);
|
||||
}
|
||||
bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override
|
||||
{
|
||||
LOCK(cs_main);
|
||||
index = LookupBlockIndex(hash);
|
||||
if (!index) {
|
||||
return false;
|
||||
WAIT_LOCK(cs_main, lock);
|
||||
return FillBlock(ChainActive().FindEarliestAtLeast(min_time, min_height), block, lock);
|
||||
}
|
||||
if (time) {
|
||||
*time = index->GetBlockTime();
|
||||
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);
|
||||
}
|
||||
if (time_max) {
|
||||
*time_max = index->GetBlockTimeMax();
|
||||
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 FillBlock(nullptr, ancestor_out, lock);
|
||||
}
|
||||
return true;
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>;
|
||||
|
|
157
src/test/interfaces_tests.cpp
Normal file
157
src/test/interfaces_tests.cpp
Normal file
|
@ -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()
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue