Merge #15931: Remove GetDepthInMainChain dependency on locked chain interface

36b68de5b2 Remove getBlockDepth method from Chain::interface (Antoine Riard)
b66c429c56 Remove locked_chain from GetDepthInMainChain and its callers (Antoine Riard)
0ff03871ad Use CWallet::m_last_block_processed_height in GetDepthInMainChain (Antoine Riard)
f77b1de16f Only return early from BlockUntilSyncedToCurrentChain if current tip is exact match (Antoine Riard)
769ff05e48 Refactor some importprunedfunds checks with guard clause (Antoine Riard)
5971d3848e Add block_height field in struct Confirmation (Antoine Riard)
9700fcb47f Replace CWalletTx::SetConf by Confirmation initialization list (Antoine Riard)
5aacc3eff1 Add m_last_block_processed_height field in CWallet (Antoine Riard)
10b4729e33 Pass block height in Chain::BlockConnected/Chain::BlockDisconnected (Antoine Riard)

Pull request description:

  Work starter to remove Chain::Lock interface by adding m_last_block_processed_height in CWallet and m_block_height in CMerkleTx to avoid GetDepthInMainChain having to keep a lock . Once this one done, it should ease work to wipe out more cs_main locks from wallet code.

  I think it's ready for a first round of review before to get further.

  - `BlockUntilSyncedToCurrent` : restrain isPotentialTip to isTip because we want to be sure that wallet see BlockDisconnected callbacks if its height differs from the Chain one. It means during a reorg, an RPC could return before the BlockDisconnected callback had been triggered. This could cause a tx that had been included in the disconnected block to be displayed as confirmed, for example.

  ~~- `AbandonTransaction` : in case of conflicted tx (nIndex = -1), we set its m_block_height to the one of conflicting blocks, but if this height is superior to CWallet::m_last_block_processed_height, that means tx isn't conflicted anymore so we return 0 as tx is again unconfirmed~~ After #16624, we instead rely on Confirmation.

  ~~- `AddToWalletIfInvolvingMe`: in case of block disconnected, transactions are added to mempool again, so we need to replace old txn in `mapWallet` with a height set to zero so we remove check on block_hash.IsNull~~ Already done in #16624

ACKs for top commit:
  jnewbery:
    @jkczyz you've ACKed an intermediate commit (github annoyingly orders commits in date order, not commit order). Did you mean to ACK the final commit in this branch (36b68de5b2).
  jkczyz:
    > @jkczyz you've ACKed an intermediate commit (github annoyingly orders commits in date order, not commit order). Did you mean to ACK the final commit in this branch ([36b68de](36b68de5b2)).
  meshcollider:
    utACK 36b68de5b2
  ryanofsky:
    Code review ACK 36b68de5b2. Changes since last review: new jkczyz refactor importprunedfunds commit, changed BlockUntilSyncedToCurrentChainChanges commit title and description, changed Confirmation struct field order and line-wrapped comment
  jnewbery:
    utACK 36b68de5b2
  promag:
    Code review ACK 36b68de5b2.

Tree-SHA512: 08b89a0bcc39f67c82a6cb6aee195e6a11697770c788ba737b90986b4893f44e90d1ab9ef87239ea3766508b7e24ea882b7199df41173ab27a3d000328c14644
This commit is contained in:
Samuel Dobson 2019-11-08 23:21:48 +13:00
commit 99ab3a72c5
No known key found for this signature in database
GPG Key ID: D300116E1C875A3D
16 changed files with 255 additions and 202 deletions

View File

@ -58,12 +58,6 @@ class LockImpl : public Chain::Lock, public UniqueLock<CCriticalSection>
} }
return nullopt; return nullopt;
} }
int getBlockDepth(const uint256& hash) override
{
const Optional<int> tip_height = getHeight();
const Optional<int> height = getBlockHeight(hash);
return tip_height && height ? *tip_height - *height + 1 : 0;
}
uint256 getBlockHash(int height) override uint256 getBlockHash(int height) override
{ {
LockAssertion lock(::cs_main); LockAssertion lock(::cs_main);
@ -182,11 +176,11 @@ public:
const CBlockIndex* index, const CBlockIndex* index,
const std::vector<CTransactionRef>& tx_conflicted) override const std::vector<CTransactionRef>& tx_conflicted) override
{ {
m_notifications->BlockConnected(*block, tx_conflicted); m_notifications->BlockConnected(*block, tx_conflicted, index->nHeight);
} }
void BlockDisconnected(const std::shared_ptr<const CBlock>& block) override void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override
{ {
m_notifications->BlockDisconnected(*block); m_notifications->BlockDisconnected(*block, index->nHeight);
} }
void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override
{ {
@ -353,13 +347,11 @@ public:
{ {
return MakeUnique<NotificationsHandlerImpl>(*this, notifications); return MakeUnique<NotificationsHandlerImpl>(*this, notifications);
} }
void waitForNotificationsIfNewBlocksConnected(const uint256& old_tip) override void waitForNotificationsIfTipChanged(const uint256& old_tip) override
{ {
if (!old_tip.IsNull()) { if (!old_tip.IsNull()) {
LOCK(::cs_main); LOCK(::cs_main);
if (old_tip == ::ChainActive().Tip()->GetBlockHash()) return; if (old_tip == ::ChainActive().Tip()->GetBlockHash()) return;
CBlockIndex* block = LookupBlockIndex(old_tip);
if (block && block->GetAncestor(::ChainActive().Height()) == ::ChainActive().Tip()) return;
} }
SyncWithValidationInterfaceQueue(); SyncWithValidationInterfaceQueue();
} }

View File

@ -76,10 +76,6 @@ public:
//! included in the current chain. //! included in the current chain.
virtual Optional<int> getBlockHeight(const uint256& hash) = 0; virtual Optional<int> getBlockHeight(const uint256& hash) = 0;
//! Get block depth. Returns 1 for chain tip, 2 for preceding block, and
//! so on. Returns 0 for a block not included in the current chain.
virtual int getBlockDepth(const uint256& hash) = 0;
//! Get block hash. Height must be valid or this function will abort. //! Get block hash. Height must be valid or this function will abort.
virtual uint256 getBlockHash(int height) = 0; virtual uint256 getBlockHash(int height) = 0;
@ -226,8 +222,8 @@ public:
virtual ~Notifications() {} virtual ~Notifications() {}
virtual void TransactionAddedToMempool(const CTransactionRef& tx) {} virtual void TransactionAddedToMempool(const CTransactionRef& tx) {}
virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx) {} virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx) {}
virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {} virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted, int height) {}
virtual void BlockDisconnected(const CBlock& block) {} virtual void BlockDisconnected(const CBlock& block, int height) {}
virtual void UpdatedBlockTip() {} virtual void UpdatedBlockTip() {}
virtual void ChainStateFlushed(const CBlockLocator& locator) {} virtual void ChainStateFlushed(const CBlockLocator& locator) {}
}; };
@ -236,9 +232,8 @@ public:
virtual std::unique_ptr<Handler> handleNotifications(Notifications& notifications) = 0; virtual std::unique_ptr<Handler> handleNotifications(Notifications& notifications) = 0;
//! Wait for pending notifications to be processed unless block hash points to the current //! Wait for pending notifications to be processed unless block hash points to the current
//! chain tip, or to a possible descendant of the current chain tip that isn't currently //! chain tip.
//! connected. virtual void waitForNotificationsIfTipChanged(const uint256& old_tip) = 0;
virtual void waitForNotificationsIfNewBlocksConnected(const uint256& old_tip) = 0;
//! Register handler for RPC. Command is not copied, so reference //! Register handler for RPC. Command is not copied, so reference
//! needs to remain valid until Handler is disconnected. //! needs to remain valid until Handler is disconnected.

View File

@ -31,7 +31,7 @@ namespace interfaces {
namespace { namespace {
//! Construct wallet tx struct. //! Construct wallet tx struct.
WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
{ {
WalletTx result; WalletTx result;
result.tx = wtx.tx; result.tx = wtx.tx;
@ -49,7 +49,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co
wallet.IsMine(result.txout_address.back()) : wallet.IsMine(result.txout_address.back()) :
ISMINE_NO); ISMINE_NO);
} }
result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); result.credit = wtx.GetCredit(ISMINE_ALL);
result.debit = wtx.GetDebit(ISMINE_ALL); result.debit = wtx.GetDebit(ISMINE_ALL);
result.change = wtx.GetChange(); result.change = wtx.GetChange();
result.time = wtx.GetTxTime(); result.time = wtx.GetTxTime();
@ -63,21 +63,20 @@ WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const C
{ {
WalletTxStatus result; WalletTxStatus result;
result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max()); result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max());
result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); result.blocks_to_maturity = wtx.GetBlocksToMaturity();
result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.depth_in_main_chain = wtx.GetDepthInMainChain();
result.time_received = wtx.nTimeReceived; result.time_received = wtx.nTimeReceived;
result.lock_time = wtx.tx->nLockTime; result.lock_time = wtx.tx->nLockTime;
result.is_final = locked_chain.checkFinalTx(*wtx.tx); result.is_final = locked_chain.checkFinalTx(*wtx.tx);
result.is_trusted = wtx.IsTrusted(locked_chain); result.is_trusted = wtx.IsTrusted(locked_chain);
result.is_abandoned = wtx.isAbandoned(); result.is_abandoned = wtx.isAbandoned();
result.is_coinbase = wtx.IsCoinBase(); result.is_coinbase = wtx.IsCoinBase();
result.is_in_main_chain = wtx.IsInMainChain(locked_chain); result.is_in_main_chain = wtx.IsInMainChain();
return result; return result;
} }
//! Construct wallet TxOut struct. //! Construct wallet TxOut struct.
WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock& locked_chain, WalletTxOut MakeWalletTxOut(CWallet& wallet,
CWallet& wallet,
const CWalletTx& wtx, const CWalletTx& wtx,
int n, int n,
int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
@ -86,7 +85,7 @@ WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock& locked_chain,
result.txout = wtx.tx->vout[n]; result.txout = wtx.tx->vout[n];
result.time = wtx.GetTxTime(); result.time = wtx.GetTxTime();
result.depth_in_main_chain = depth; result.depth_in_main_chain = depth;
result.is_spent = wallet.IsSpent(locked_chain, wtx.GetHash(), n); result.is_spent = wallet.IsSpent(wtx.GetHash(), n);
return result; return result;
} }
@ -235,7 +234,7 @@ public:
{ {
auto locked_chain = m_wallet->chain().lock(); auto locked_chain = m_wallet->chain().lock();
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
return m_wallet->AbandonTransaction(*locked_chain, txid); return m_wallet->AbandonTransaction(txid);
} }
bool transactionCanBeBumped(const uint256& txid) override bool transactionCanBeBumped(const uint256& txid) override
{ {
@ -282,7 +281,7 @@ public:
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
auto mi = m_wallet->mapWallet.find(txid); auto mi = m_wallet->mapWallet.find(txid);
if (mi != m_wallet->mapWallet.end()) { if (mi != m_wallet->mapWallet.end()) {
return MakeWalletTx(*locked_chain, *m_wallet, mi->second); return MakeWalletTx(*m_wallet, mi->second);
} }
return {}; return {};
} }
@ -293,7 +292,7 @@ public:
std::vector<WalletTx> result; std::vector<WalletTx> result;
result.reserve(m_wallet->mapWallet.size()); result.reserve(m_wallet->mapWallet.size());
for (const auto& entry : m_wallet->mapWallet) { for (const auto& entry : m_wallet->mapWallet) {
result.emplace_back(MakeWalletTx(*locked_chain, *m_wallet, entry.second)); result.emplace_back(MakeWalletTx(*m_wallet, entry.second));
} }
return result; return result;
} }
@ -338,7 +337,7 @@ public:
in_mempool = mi->second.InMempool(); in_mempool = mi->second.InMempool();
order_form = mi->second.vOrderForm; order_form = mi->second.vOrderForm;
tx_status = MakeWalletTxStatus(*locked_chain, mi->second); tx_status = MakeWalletTxStatus(*locked_chain, mi->second);
return MakeWalletTx(*locked_chain, *m_wallet, mi->second); return MakeWalletTx(*m_wallet, mi->second);
} }
return {}; return {};
} }
@ -407,7 +406,7 @@ public:
auto& group = result[entry.first]; auto& group = result[entry.first];
for (const auto& coin : entry.second) { for (const auto& coin : entry.second) {
group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
MakeWalletTxOut(*locked_chain, *m_wallet, *coin.tx, coin.i, coin.nDepth)); MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth));
} }
} }
return result; return result;
@ -422,9 +421,9 @@ public:
result.emplace_back(); result.emplace_back();
auto it = m_wallet->mapWallet.find(output.hash); auto it = m_wallet->mapWallet.find(output.hash);
if (it != m_wallet->mapWallet.end()) { if (it != m_wallet->mapWallet.end()) {
int depth = it->second.GetDepthInMainChain(*locked_chain); int depth = it->second.GetDepthInMainChain();
if (depth >= 0) { if (depth >= 0) {
result.back() = MakeWalletTxOut(*locked_chain, *m_wallet, it->second, output.n, depth); result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth);
} }
} }
} }

View File

@ -139,10 +139,12 @@ void TestGUI(interfaces::Node& node)
wallet->LoadWallet(firstRun); wallet->LoadWallet(firstRun);
{ {
auto spk_man = wallet->GetLegacyScriptPubKeyMan(); auto spk_man = wallet->GetLegacyScriptPubKeyMan();
auto locked_chain = wallet->chain().lock();
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
AssertLockHeld(spk_man->cs_wallet); AssertLockHeld(spk_man->cs_wallet);
wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive");
spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash());
} }
{ {
auto locked_chain = wallet->chain().lock(); auto locked_chain = wallet->chain().lock();

View File

@ -40,9 +40,10 @@ struct TestSubscriber : public CValidationInterface {
m_expected_tip = block->GetHash(); m_expected_tip = block->GetHash();
} }
void BlockDisconnected(const std::shared_ptr<const CBlock>& block) override void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
{ {
BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash()); BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash());
BOOST_CHECK_EQUAL(m_expected_tip, pindex->GetBlockHash());
m_expected_tip = block->hashPrevBlock; m_expected_tip = block->hashPrevBlock;
} }

View File

@ -2458,7 +2458,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams&
UpdateTip(pindexDelete->pprev, chainparams); UpdateTip(pindexDelete->pprev, chainparams);
// Let wallets know transactions went from 1-confirmed to // Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted: // 0-confirmed or conflicted:
GetMainSignals().BlockDisconnected(pblock); GetMainSignals().BlockDisconnected(pblock, pindexDelete);
return true; return true;
} }

View File

@ -29,7 +29,7 @@ struct MainSignalsInstance {
boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip; boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip;
boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool; boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool;
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef>&)> BlockConnected; boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef>&)> BlockConnected;
boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected; boost::signals2::signal<void (const std::shared_ptr<const CBlock>&, const CBlockIndex* pindex)> BlockDisconnected;
boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool; boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool;
boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed; boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed;
boost::signals2::signal<void (const CBlock&, const BlockValidationState&)> BlockChecked; boost::signals2::signal<void (const CBlock&, const BlockValidationState&)> BlockChecked;
@ -92,7 +92,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect(std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect(std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect(std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1)); conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect(std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1));
conns.BlockConnected = g_signals.m_internals->BlockConnected.connect(std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); conns.BlockConnected = g_signals.m_internals->BlockConnected.connect(std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1)); conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1, std::placeholders::_2));
conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1)); conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1));
conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1)); conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1));
conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2)); conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2));
@ -156,9 +156,10 @@ void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, c
}); });
} }
void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock> &pblock) { void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
m_internals->m_schedulerClient.AddToProcessQueue([pblock, this] { {
m_internals->BlockDisconnected(pblock); m_internals->m_schedulerClient.AddToProcessQueue([pblock, pindex, this] {
m_internals->BlockDisconnected(pblock, pindex);
}); });
} }

View File

@ -114,7 +114,7 @@ protected:
* *
* Called on a background thread. * Called on a background thread.
*/ */
virtual void BlockDisconnected(const std::shared_ptr<const CBlock> &block) {} virtual void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) {}
/** /**
* Notifies listeners of the new active block chain on-disk. * Notifies listeners of the new active block chain on-disk.
* *
@ -178,7 +178,7 @@ public:
void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload);
void TransactionAddedToMempool(const CTransactionRef &); void TransactionAddedToMempool(const CTransactionRef &);
void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &);
void BlockDisconnected(const std::shared_ptr<const CBlock> &); void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex);
void ChainStateFlushed(const CBlockLocator &); void ChainStateFlushed(const CBlockLocator &);
void BlockChecked(const CBlock&, const BlockValidationState&); void BlockChecked(const CBlock&, const BlockValidationState&);
void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&);

View File

@ -16,7 +16,7 @@
//! Check whether transaction has descendant in wallet or mempool, or has been //! Check whether transaction has descendant in wallet or mempool, or has been
//! mined, or conflicts with a mined transaction. Return a feebumper::Result. //! mined, or conflicts with a mined transaction. Return a feebumper::Result.
static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chain, const CWallet& wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{ {
if (wallet.HasWalletSpend(wtx.GetHash())) { if (wallet.HasWalletSpend(wtx.GetHash())) {
errors.push_back("Transaction has descendants in the wallet"); errors.push_back("Transaction has descendants in the wallet");
@ -30,7 +30,7 @@ static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chai
} }
} }
if (wtx.GetDepthInMainChain(locked_chain) != 0) { if (wtx.GetDepthInMainChain() != 0) {
errors.push_back("Transaction has been mined, or is conflicted with a mined transaction"); errors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
return feebumper::Result::WALLET_ERROR; return feebumper::Result::WALLET_ERROR;
} }
@ -146,7 +146,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
if (wtx == nullptr) return false; if (wtx == nullptr) return false;
std::vector<std::string> errors_dummy; std::vector<std::string> errors_dummy;
feebumper::Result res = PreconditionChecks(*locked_chain, wallet, *wtx, errors_dummy); feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy);
return res == feebumper::Result::OK; return res == feebumper::Result::OK;
} }
@ -165,7 +165,7 @@ Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, co
} }
const CWalletTx& wtx = it->second; const CWalletTx& wtx = it->second;
Result result = PreconditionChecks(*locked_chain, *wallet, wtx, errors); Result result = PreconditionChecks(*wallet, wtx, errors);
if (result != Result::OK) { if (result != Result::OK) {
return result; return result;
} }
@ -291,7 +291,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
} }
const CWalletTx& wtx = it->second; const CWalletTx& wtx = it->second;
Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors); Result result = PreconditionChecks(wallet, wtx, errors);
if (result != Result::OK) { if (result != Result::OK) {
return result; return result;
} }
@ -382,7 +382,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti
CWalletTx& oldWtx = it->second; CWalletTx& oldWtx = it->second;
// make sure the transaction still has no descendants and hasn't been mined in the meantime // make sure the transaction still has no descendants and hasn't been mined in the meantime
Result result = PreconditionChecks(*locked_chain, wallet, oldWtx, errors); Result result = PreconditionChecks(wallet, oldWtx, errors);
if (result != Result::OK) { if (result != Result::OK) {
return result; return result;
} }

View File

@ -316,7 +316,7 @@ UniValue importaddress(const JSONRPCRequest& request)
{ {
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions(*locked_chain); pwallet->ReacceptWalletTransactions();
} }
} }
@ -354,28 +354,26 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
//Search partial merkle tree in proof for our transaction and index in valid block //Search partial merkle tree in proof for our transaction and index in valid block
std::vector<uint256> vMatch; std::vector<uint256> vMatch;
std::vector<unsigned int> vIndex; std::vector<unsigned int> vIndex;
unsigned int txnIndex = 0; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) {
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) {
auto locked_chain = pwallet->chain().lock();
if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) == nullopt) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
}
std::vector<uint256>::const_iterator it;
if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx))==vMatch.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
}
txnIndex = vIndex[it - vMatch.begin()];
}
else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
} }
wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex);
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
Optional<int> height = locked_chain->getBlockHeight(merkleBlock.header.GetHash());
if (height == nullopt) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
}
std::vector<uint256>::const_iterator it;
if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx)) == vMatch.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
}
unsigned int txnIndex = vIndex[it - vMatch.begin()];
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *height, merkleBlock.header.GetHash(), txnIndex);
wtx.m_confirm = confirm;
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
if (pwallet->IsMine(*wtx.tx)) { if (pwallet->IsMine(*wtx.tx)) {
@ -507,7 +505,7 @@ UniValue importpubkey(const JSONRPCRequest& request)
{ {
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions(*locked_chain); pwallet->ReacceptWalletTransactions();
} }
} }
@ -1406,7 +1404,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
{ {
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions(*locked_chain); pwallet->ReacceptWalletTransactions();
} }
if (pwallet->IsAbortingRescan()) { if (pwallet->IsAbortingRescan()) {

View File

@ -135,7 +135,7 @@ LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet)
static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry) static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry)
{ {
int confirms = wtx.GetDepthInMainChain(locked_chain); int confirms = wtx.GetDepthInMainChain();
entry.pushKV("confirmations", confirms); entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase()) if (wtx.IsCoinBase())
entry.pushKV("generated", true); entry.pushKV("generated", true);
@ -640,7 +640,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request)
for (const CTxOut& txout : wtx.tx->vout) for (const CTxOut& txout : wtx.tx->vout)
if (txout.scriptPubKey == scriptPubKey) if (txout.scriptPubKey == scriptPubKey)
if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue; nAmount += txout.nValue;
} }
@ -706,7 +706,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request)
{ {
CTxDestination address; CTxDestination address;
if (ExtractDestination(txout.scriptPubKey, address) && pwallet->IsMine(address) && setAddress.count(address)) { if (ExtractDestination(txout.scriptPubKey, address) && pwallet->IsMine(address) && setAddress.count(address)) {
if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue; nAmount += txout.nValue;
} }
} }
@ -1063,7 +1063,7 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co
continue; continue;
} }
int nDepth = wtx.GetDepthInMainChain(locked_chain); int nDepth = wtx.GetDepthInMainChain();
if (nDepth < nMinDepth) if (nDepth < nMinDepth)
continue; continue;
@ -1320,8 +1320,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con
} }
// Received // Received
if (listReceived.size() > 0 && wtx.GetDepthInMainChain(locked_chain) >= nMinDepth) if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) {
{
for (const COutputEntry& r : listReceived) for (const COutputEntry& r : listReceived)
{ {
std::string label; std::string label;
@ -1338,9 +1337,9 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con
MaybePushAddress(entry, r.destination); MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase()) if (wtx.IsCoinBase())
{ {
if (wtx.GetDepthInMainChain(locked_chain) < 1) if (wtx.GetDepthInMainChain() < 1)
entry.pushKV("category", "orphan"); entry.pushKV("category", "orphan");
else if (wtx.IsImmatureCoinBase(locked_chain)) else if (wtx.IsImmatureCoinBase())
entry.pushKV("category", "immature"); entry.pushKV("category", "immature");
else else
entry.pushKV("category", "generate"); entry.pushKV("category", "generate");
@ -1604,7 +1603,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
CWalletTx tx = pairWtx.second; CWalletTx tx = pairWtx.second;
if (depth == -1 || abs(tx.GetDepthInMainChain(*locked_chain)) < depth) { if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) {
ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */);
} }
} }
@ -1721,7 +1720,7 @@ static UniValue gettransaction(const JSONRPCRequest& request)
} }
const CWalletTx& wtx = it->second; const CWalletTx& wtx = it->second;
CAmount nCredit = wtx.GetCredit(*locked_chain, filter); CAmount nCredit = wtx.GetCredit(filter);
CAmount nDebit = wtx.GetDebit(filter); CAmount nDebit = wtx.GetDebit(filter);
CAmount nNet = nCredit - nDebit; CAmount nNet = nCredit - nDebit;
CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0);
@ -1785,7 +1784,7 @@ static UniValue abandontransaction(const JSONRPCRequest& request)
if (!pwallet->mapWallet.count(hash)) { if (!pwallet->mapWallet.count(hash)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
} }
if (!pwallet->AbandonTransaction(*locked_chain, hash)) { if (!pwallet->AbandonTransaction(hash)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
} }
@ -2216,7 +2215,7 @@ static UniValue lockunspent(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
} }
if (pwallet->IsSpent(*locked_chain, outpt.hash, outpt.n)) { if (pwallet->IsSpent(outpt.hash, outpt.n)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output");
} }

View File

@ -50,6 +50,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions accommodates a null start block. // Verify ScanForWalletTransactions accommodates a null start block.
{ {
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
@ -65,6 +69,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// and new block files. // and new block files.
{ {
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
@ -84,6 +92,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// file. // file.
{ {
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
@ -102,6 +114,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions scans no blocks. // Verify ScanForWalletTransactions scans no blocks.
{ {
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
@ -258,18 +274,20 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
LockAssertion lock(::cs_main); LockAssertion lock(::cs_main);
LOCK(wallet.cs_wallet); LOCK(wallet.cs_wallet);
AssertLockHeld(spk_man->cs_wallet); AssertLockHeld(spk_man->cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0); CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 0);
wtx.m_confirm = confirm;
// Call GetImmatureCredit() once before adding the key to the wallet to // Call GetImmatureCredit() once before adding the key to the wallet to
// cache the current immature credit amount, which is 0. // cache the current immature credit amount, which is 0.
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0);
// Invalidate the cached vanue, add the key, and make sure a new immature // Invalidate the cached vanue, add the key, and make sure a new immature
// credit amount is calculated. // credit amount is calculated.
wtx.MarkDirty(); wtx.MarkDirty();
BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()));
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN);
} }
static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime)
@ -300,7 +318,8 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
wallet.AddToWallet(wtx); wallet.AddToWallet(wtx);
} }
if (block) { if (block) {
wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0); CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, block->nHeight, block->GetBlockHash(), 0);
wtx.m_confirm = confirm;
} }
wallet.AddToWallet(wtx); wallet.AddToWallet(wtx);
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart; return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
@ -435,6 +454,10 @@ public:
{ {
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock());
{
LOCK(wallet->cs_wallet);
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
bool firstRun; bool firstRun;
wallet->LoadWallet(firstRun); wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey); AddKey(*wallet, coinbaseKey);
@ -473,9 +496,11 @@ public:
LOCK(cs_main); LOCK(cs_main);
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, ::ChainActive().Tip()->GetBlockHash());
auto it = wallet->mapWallet.find(tx->GetHash()); auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end()); BOOST_CHECK(it != wallet->mapWallet.end());
it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1); CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 1);
it->second.m_confirm = confirm;
return it->second; return it->second;
} }

View File

@ -452,7 +452,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
* Outpoint is spent if any non-conflicted transaction * Outpoint is spent if any non-conflicted transaction
* spends it: * spends it:
*/ */
bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
{ {
const COutPoint outpoint(hash, n); const COutPoint outpoint(hash, n);
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
@ -463,7 +463,7 @@ bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash
const uint256& wtxid = it->second; const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) { if (mit != mapWallet.end()) {
int depth = mit->second.GetDepthInMainChain(locked_chain); int depth = mit->second.GetDepthInMainChain();
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
return true; // Spent return true; // Spent
} }
@ -768,10 +768,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
wtx.m_confirm.status = wtxIn.m_confirm.status; wtx.m_confirm.status = wtxIn.m_confirm.status;
wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex; wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex;
wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock; wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock;
wtx.m_confirm.block_height = wtxIn.m_confirm.block_height;
fUpdated = true; fUpdated = true;
} else { } else {
assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex); assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex);
assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock); assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock);
assert(wtx.m_confirm.block_height == wtxIn.m_confirm.block_height);
} }
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{ {
@ -822,12 +824,22 @@ void CWallet::LoadToWallet(CWalletTx& wtxIn)
{ {
// If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken. // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken.
auto locked_chain = LockChain(); auto locked_chain = LockChain();
// If tx hasn't been reorged out of chain while wallet being shutdown if (locked_chain) {
// change tx status to UNCONFIRMED and reset hashBlock/nIndex. Optional<int> block_height = locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock);
if (!wtxIn.m_confirm.hashBlock.IsNull()) { if (block_height) {
if (locked_chain && !locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) { // Update cached block height variable since it not stored in the
// serialized transaction.
wtxIn.m_confirm.block_height = *block_height;
} else if (wtxIn.isConflicted() || wtxIn.isConfirmed()) {
// If tx block (or conflicting block) was reorged out of chain
// while the wallet was shutdown, change tx status to UNCONFIRMED
// and reset block height, hash, and index. ABANDONED tx don't have
// associated blocks and don't need to be updated. The case where a
// transaction was reorged out while online and then reconfirmed
// while offline is covered by the rescan logic.
wtxIn.setUnconfirmed(); wtxIn.setUnconfirmed();
wtxIn.m_confirm.hashBlock = uint256(); wtxIn.m_confirm.hashBlock = uint256();
wtxIn.m_confirm.block_height = 0;
wtxIn.m_confirm.nIndex = 0; wtxIn.m_confirm.nIndex = 0;
} }
} }
@ -844,25 +856,25 @@ void CWallet::LoadToWallet(CWalletTx& wtxIn)
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second; CWalletTx& prevtx = it->second;
if (prevtx.isConflicted()) { if (prevtx.isConflicted()) {
MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash()); MarkConflicted(prevtx.m_confirm.hashBlock, prevtx.m_confirm.block_height, wtx.GetHash());
} }
} }
} }
} }
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool fUpdate)
{ {
const CTransaction& tx = *ptx; const CTransaction& tx = *ptx;
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(cs_wallet);
if (!block_hash.IsNull()) { if (!confirm.hashBlock.IsNull()) {
for (const CTxIn& txin : tx.vin) { for (const CTxIn& txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) { while (range.first != range.second) {
if (range.first->second != tx.GetHash()) { if (range.first->second != tx.GetHash()) {
WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), block_hash.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
MarkConflicted(block_hash, range.first->second); MarkConflicted(confirm.hashBlock, confirm.block_height, range.first->second);
} }
range.first++; range.first++;
} }
@ -890,7 +902,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::St
// Block disconnection override an abandoned tx as unconfirmed // Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again // which means user may have to call abandontransaction again
wtx.SetConf(status, block_hash, posInBlock); wtx.m_confirm = confirm;
return AddToWallet(wtx, false); return AddToWallet(wtx, false);
} }
@ -903,7 +915,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx); const CWalletTx* wtx = GetWalletTx(hashTx);
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain(*locked_chain) == 0 && !wtx->InMempool(); return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
} }
void CWallet::MarkInputsDirty(const CTransactionRef& tx) void CWallet::MarkInputsDirty(const CTransactionRef& tx)
@ -916,9 +928,9 @@ void CWallet::MarkInputsDirty(const CTransactionRef& tx)
} }
} }
bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx) bool CWallet::AbandonTransaction(const uint256& hashTx)
{ {
auto locked_chain_recursive = chain().lock(); // Temporary. Removed in upcoming lock cleanup auto locked_chain = chain().lock(); // Temporary. Removed in upcoming lock cleanup
LOCK(cs_wallet); LOCK(cs_wallet);
WalletBatch batch(*database, "r+"); WalletBatch batch(*database, "r+");
@ -930,7 +942,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
auto it = mapWallet.find(hashTx); auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end()); assert(it != mapWallet.end());
CWalletTx& origtx = it->second; CWalletTx& origtx = it->second;
if (origtx.GetDepthInMainChain(locked_chain) != 0 || origtx.InMempool()) { if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false; return false;
} }
@ -943,14 +955,13 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
auto it = mapWallet.find(now); auto it = mapWallet.find(now);
assert(it != mapWallet.end()); assert(it != mapWallet.end());
CWalletTx& wtx = it->second; CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain(locked_chain); int currentconfirm = wtx.GetDepthInMainChain();
// If the orig tx was not in block, none of its spends can be // If the orig tx was not in block, none of its spends can be
assert(currentconfirm <= 0); assert(currentconfirm <= 0);
// if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon}
if (currentconfirm == 0 && !wtx.isAbandoned()) { if (currentconfirm == 0 && !wtx.isAbandoned()) {
// If the orig tx was not in block/mempool, none of its spends can be in mempool // If the orig tx was not in block/mempool, none of its spends can be in mempool
assert(!wtx.InMempool()); assert(!wtx.InMempool());
wtx.m_confirm.nIndex = 0;
wtx.setAbandoned(); wtx.setAbandoned();
wtx.MarkDirty(); wtx.MarkDirty();
batch.WriteTx(wtx); batch.WriteTx(wtx);
@ -972,12 +983,12 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
return true; return true;
} }
void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx)
{ {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
int conflictconfirms = -locked_chain->getBlockDepth(hashBlock); int conflictconfirms = (m_last_block_processed_height - conflicting_height + 1) * -1;
// If number of conflict confirms cannot be determined, this means // If number of conflict confirms cannot be determined, this means
// that the block is still unknown or not yet part of the main chain, // that the block is still unknown or not yet part of the main chain,
// for example when loading the wallet during a reindex. Do nothing in that // for example when loading the wallet during a reindex. Do nothing in that
@ -1000,12 +1011,13 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
auto it = mapWallet.find(now); auto it = mapWallet.find(now);
assert(it != mapWallet.end()); assert(it != mapWallet.end());
CWalletTx& wtx = it->second; CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain(*locked_chain); int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) { if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update. // Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block. // Mark transaction as conflicted with this block.
wtx.m_confirm.nIndex = 0; wtx.m_confirm.nIndex = 0;
wtx.m_confirm.hashBlock = hashBlock; wtx.m_confirm.hashBlock = hashBlock;
wtx.m_confirm.block_height = conflicting_height;
wtx.setConflicted(); wtx.setConflicted();
wtx.MarkDirty(); wtx.MarkDirty();
batch.WriteTx(wtx); batch.WriteTx(wtx);
@ -1024,9 +1036,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
} }
} }
void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx) void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool update_tx)
{ {
if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx)) if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx))
return; // Not one of ours return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance // If a transaction changes 'conflicted' state, that changes the balance
@ -1038,7 +1050,8 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status stat
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
SyncTransaction(ptx, confirm);
auto it = mapWallet.find(ptx->GetHash()); auto it = mapWallet.find(ptx->GetHash());
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
@ -1054,23 +1067,26 @@ void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
} }
} }
void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) { void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted, int height)
{
const uint256& block_hash = block.GetHash(); const uint256& block_hash = block.GetHash();
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
for (size_t i = 0; i < block.vtx.size(); i++) { m_last_block_processed_height = height;
SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i); m_last_block_processed = block_hash;
TransactionRemovedFromMempool(block.vtx[i]); for (size_t index = 0; index < block.vtx.size(); index++) {
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, block_hash, index);
SyncTransaction(block.vtx[index], confirm);
TransactionRemovedFromMempool(block.vtx[index]);
} }
for (const CTransactionRef& ptx : vtxConflicted) { for (const CTransactionRef& ptx : vtxConflicted) {
TransactionRemovedFromMempool(ptx); TransactionRemovedFromMempool(ptx);
} }
m_last_block_processed = block_hash;
} }
void CWallet::BlockDisconnected(const CBlock& block) { void CWallet::BlockDisconnected(const CBlock& block, int height)
{
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
@ -1078,8 +1094,11 @@ void CWallet::BlockDisconnected(const CBlock& block) {
// be unconfirmed, whether or not the transaction is added back to the mempool. // be unconfirmed, whether or not the transaction is added back to the mempool.
// User may have to call abandontransaction again. It may be addressed in the // User may have to call abandontransaction again. It may be addressed in the
// future with a stickier abandoned state or even removing abandontransaction call. // future with a stickier abandoned state or even removing abandontransaction call.
m_last_block_processed_height = height - 1;
m_last_block_processed = block.hashPrevBlock;
for (const CTransactionRef& ptx : block.vtx) { for (const CTransactionRef& ptx : block.vtx) {
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
SyncTransaction(ptx, confirm);
} }
} }
@ -1096,7 +1115,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
// for the queue to drain enough to execute it (indicating we are caught up // for the queue to drain enough to execute it (indicating we are caught up
// at least with the time we entered this function). // at least with the time we entered this function).
uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed); uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed);
chain().waitForNotificationsIfNewBlocksConnected(last_block_hash); chain().waitForNotificationsIfTipChanged(last_block_hash);
} }
@ -1625,7 +1644,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
break; break;
} }
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate); 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 // scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash; result.last_scanned_block = block_hash;
@ -1673,7 +1693,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
return result; return result;
} }
void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) void CWallet::ReacceptWalletTransactions()
{ {
// If transactions aren't being broadcasted, don't let them into local mempool either // If transactions aren't being broadcasted, don't let them into local mempool either
if (!fBroadcastTransactions) if (!fBroadcastTransactions)
@ -1686,7 +1706,7 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain)
CWalletTx& wtx = item.second; CWalletTx& wtx = item.second;
assert(wtx.GetHash() == wtxid); assert(wtx.GetHash() == wtxid);
int nDepth = wtx.GetDepthInMainChain(locked_chain); int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
@ -1697,11 +1717,11 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain)
for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
CWalletTx& wtx = *(item.second); CWalletTx& wtx = *(item.second);
std::string unused_err_string; std::string unused_err_string;
wtx.SubmitMemoryPoolAndRelay(unused_err_string, false, locked_chain); wtx.SubmitMemoryPoolAndRelay(unused_err_string, false);
} }
} }
bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain) bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay)
{ {
// Can't relay if wallet is not broadcasting // Can't relay if wallet is not broadcasting
if (!pwallet->GetBroadcastTransactions()) return false; if (!pwallet->GetBroadcastTransactions()) return false;
@ -1711,7 +1731,7 @@ bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, in
// cause log spam. // cause log spam.
if (IsCoinBase()) return false; if (IsCoinBase()) return false;
// Don't try to submit conflicted or confirmed transactions. // Don't try to submit conflicted or confirmed transactions.
if (GetDepthInMainChain(locked_chain) != 0) return false; if (GetDepthInMainChain() != 0) return false;
// Submit transaction to mempool for relay // Submit transaction to mempool for relay
pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString());
@ -1765,10 +1785,10 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
return debit; return debit;
} }
CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const CAmount CWalletTx::GetCredit(const isminefilter& filter) const
{ {
// Must wait until coinbase is safely deep enough in the chain before valuing it // Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsImmatureCoinBase(locked_chain)) if (IsImmatureCoinBase())
return 0; return 0;
CAmount credit = 0; CAmount credit = 0;
@ -1782,16 +1802,16 @@ CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const ismine
return credit; return credit;
} }
CAmount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache) const CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
{ {
if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
} }
return 0; return 0;
} }
CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache, const isminefilter& filter) const CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const
{ {
if (pwallet == nullptr) if (pwallet == nullptr)
return 0; return 0;
@ -1800,7 +1820,7 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
// Must wait until coinbase is safely deep enough in the chain before valuing it // Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsImmatureCoinBase(locked_chain)) if (IsImmatureCoinBase())
return 0; return 0;
if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) {
@ -1812,7 +1832,7 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
uint256 hashTx = GetHash(); uint256 hashTx = GetHash();
for (unsigned int i = 0; i < tx->vout.size(); i++) for (unsigned int i = 0; i < tx->vout.size(); i++)
{ {
if (!pwallet->IsSpent(locked_chain, hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) { if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) {
const CTxOut &txout = tx->vout[i]; const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, filter); nCredit += pwallet->GetCredit(txout, filter);
if (!MoneyRange(nCredit)) if (!MoneyRange(nCredit))
@ -1827,9 +1847,9 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
return nCredit; return nCredit;
} }
CAmount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache) const CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
{ {
if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
} }
@ -1860,7 +1880,7 @@ bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint25
{ {
// Quick answer in most cases // Quick answer in most cases
if (!locked_chain.checkFinalTx(*tx)) return false; if (!locked_chain.checkFinalTx(*tx)) return false;
int nDepth = GetDepthInMainChain(locked_chain); int nDepth = GetDepthInMainChain();
if (nDepth >= 1) return true; if (nDepth >= 1) return true;
if (nDepth < 0) return false; if (nDepth < 0) return false;
// using wtx's cached debit // using wtx's cached debit
@ -1936,7 +1956,7 @@ void CWallet::ResendWalletTransactions()
// any confirmed or conflicting txs. // any confirmed or conflicting txs.
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
std::string unused_err_string; std::string unused_err_string;
if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true, *locked_chain)) ++submitted_tx_count; if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count;
} }
} // locked_chain and cs_wallet } // locked_chain and cs_wallet
@ -1973,9 +1993,9 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons
{ {
const CWalletTx& wtx = entry.second; const CWalletTx& wtx = entry.second;
const bool is_trusted{wtx.IsTrusted(*locked_chain, trusted_parents)}; const bool is_trusted{wtx.IsTrusted(*locked_chain, trusted_parents)};
const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)}; const int tx_depth{wtx.GetDepthInMainChain()};
const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
if (is_trusted && tx_depth >= min_depth) { if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += tx_credit_mine; ret.m_mine_trusted += tx_credit_mine;
ret.m_watchonly_trusted += tx_credit_watchonly; ret.m_watchonly_trusted += tx_credit_watchonly;
@ -1984,8 +2004,8 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons
ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_mine_untrusted_pending += tx_credit_mine;
ret.m_watchonly_untrusted_pending += tx_credit_watchonly; ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
} }
ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain); ret.m_mine_immature += wtx.GetImmatureCredit();
ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain); ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit();
} }
} }
return ret; return ret;
@ -2029,10 +2049,10 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
continue; continue;
} }
if (wtx.IsImmatureCoinBase(locked_chain)) if (wtx.IsImmatureCoinBase())
continue; continue;
int nDepth = wtx.GetDepthInMainChain(locked_chain); int nDepth = wtx.GetDepthInMainChain();
if (nDepth < 0) if (nDepth < 0)
continue; continue;
@ -2092,7 +2112,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
if (IsLockedCoin(entry.first, i)) if (IsLockedCoin(entry.first, i))
continue; continue;
if (IsSpent(locked_chain, wtxid, i)) if (IsSpent(wtxid, i))
continue; continue;
isminetype mine = IsMine(wtx.tx->vout[i]); isminetype mine = IsMine(wtx.tx->vout[i]);
@ -2151,7 +2171,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch
for (const COutPoint& output : lockedCoins) { for (const COutPoint& output : lockedCoins) {
auto it = mapWallet.find(output.hash); auto it = mapWallet.find(output.hash);
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
int depth = it->second.GetDepthInMainChain(locked_chain); int depth = it->second.GetDepthInMainChain();
if (depth >= 0 && output.n < it->second.tx->vout.size() && if (depth >= 0 && output.n < it->second.tx->vout.size() &&
IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) {
CTxDestination address; CTxDestination address;
@ -2891,7 +2911,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
} }
std::string err_string; std::string err_string;
if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) { if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) {
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string);
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
} }
@ -3111,10 +3131,10 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain:
if (!wtx.IsTrusted(locked_chain, trusted_parents)) if (!wtx.IsTrusted(locked_chain, trusted_parents))
continue; continue;
if (wtx.IsImmatureCoinBase(locked_chain)) if (wtx.IsImmatureCoinBase())
continue; continue;
int nDepth = wtx.GetDepthInMainChain(locked_chain); int nDepth = wtx.GetDepthInMainChain();
if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1))
continue; continue;
@ -3126,7 +3146,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain:
if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
continue; continue;
CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
if (!balances.count(addr)) if (!balances.count(addr))
balances[addr] = 0; balances[addr] = 0;
@ -3785,8 +3805,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
const Optional<int> tip_height = locked_chain->getHeight(); const Optional<int> tip_height = locked_chain->getHeight();
if (tip_height) { if (tip_height) {
walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height); walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height);
walletInstance->m_last_block_processed_height = *tip_height;
} else { } else {
walletInstance->m_last_block_processed.SetNull(); walletInstance->m_last_block_processed.SetNull();
walletInstance->m_last_block_processed_height = -1;
} }
if (tip_height && *tip_height != rescan_height) if (tip_height && *tip_height != rescan_height)
@ -3888,7 +3910,7 @@ void CWallet::postInitProcess()
// Add wallet transactions that aren't already in a block to mempool // Add wallet transactions that aren't already in a block to mempool
// Do this here as mempool requires genesis block to be loaded // Do this here as mempool requires genesis block to be loaded
ReacceptWalletTransactions(*locked_chain); ReacceptWalletTransactions();
// Update wallet transactions with current mempool transactions. // Update wallet transactions with current mempool transactions.
chain().requestMempoolTransactions(*this); chain().requestMempoolTransactions(*this);
@ -3914,38 +3936,28 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
m_pre_split = false; m_pre_split = false;
} }
void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock) int CWalletTx::GetDepthInMainChain() const
{
// Update tx status
m_confirm.status = status;
// Update the tx's hashBlock
m_confirm.hashBlock = block_hash;
// set the position of the transaction in the block
m_confirm.nIndex = posInBlock;
}
int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
{ {
assert(pwallet != nullptr);
AssertLockHeld(pwallet->cs_wallet);
if (isUnconfirmed() || isAbandoned()) return 0; if (isUnconfirmed() || isAbandoned()) return 0;
return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1); return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1);
} }
int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const int CWalletTx::GetBlocksToMaturity() const
{ {
if (!IsCoinBase()) if (!IsCoinBase())
return 0; return 0;
int chain_depth = GetDepthInMainChain(locked_chain); int chain_depth = GetDepthInMainChain();
assert(chain_depth >= 0); // coinbase tx should not be conflicted assert(chain_depth >= 0); // coinbase tx should not be conflicted
return std::max(0, (COINBASE_MATURITY+1) - chain_depth); return std::max(0, (COINBASE_MATURITY+1) - chain_depth);
} }
bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const bool CWalletTx::IsImmatureCoinBase() const
{ {
// note GetBlocksToMaturity is 0 for non-coinbase tx // note GetBlocksToMaturity is 0 for non-coinbase tx
return GetBlocksToMaturity(locked_chain) > 0; return GetBlocksToMaturity() > 0;
} }
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const { std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const {

View File

@ -356,14 +356,17 @@ public:
ABANDONED ABANDONED
}; };
/* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed. /* Confirmation includes tx status and a triplet of {block height/block hash/tx index in block}
* This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state * at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned.
* where they instead point to block hash and index of the deepest conflicting tx. * Meaning of these fields changes with CONFLICTED state where they instead point to block hash
* and block height of the deepest conflicting tx.
*/ */
struct Confirmation { struct Confirmation {
Status status = UNCONFIRMED; Status status;
uint256 hashBlock = uint256(); int block_height;
int nIndex = 0; uint256 hashBlock;
int nIndex;
Confirmation(Status s = UNCONFIRMED, int b = 0, uint256 h = uint256(), int i = 0) : status(s), block_height(b), hashBlock(h), nIndex(i) {}
}; };
Confirmation m_confirm; Confirmation m_confirm;
@ -406,7 +409,6 @@ public:
* compatibility (pre-commit 9ac63d6). * compatibility (pre-commit 9ac63d6).
*/ */
if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) {
m_confirm.hashBlock = uint256();
setAbandoned(); setAbandoned();
} else if (serializedIndex == -1) { } else if (serializedIndex == -1) {
setConflicted(); setConflicted();
@ -447,14 +449,14 @@ public:
//! filter decides which addresses will count towards the debit //! filter decides which addresses will count towards the debit
CAmount GetDebit(const isminefilter& filter) const; CAmount GetDebit(const isminefilter& filter) const;
CAmount GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const; CAmount GetCredit(const isminefilter& filter) const;
CAmount GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true) const; CAmount GetImmatureCredit(bool fUseCache = true) const;
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The
// annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid
// having to resolve the issue of member access into incomplete type CWallet. // having to resolve the issue of member access into incomplete type CWallet.
CAmount GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS;
CAmount GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache=true) const; CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const;
CAmount GetChange() const; CAmount GetChange() const;
// Get the marginal bytes if spending the specified output from this transaction // Get the marginal bytes if spending the specified output from this transaction
@ -481,7 +483,7 @@ public:
int64_t GetTxTime() const; int64_t GetTxTime() const;
// Pass this transaction to node for mempool insertion and relay to peers if flag set to true // Pass this transaction to node for mempool insertion and relay to peers if flag set to true
bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain); bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay);
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
@ -491,38 +493,44 @@ public:
// in place. // in place.
std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
void SetConf(Status status, const uint256& block_hash, int posInBlock);
/** /**
* Return depth of transaction in blockchain: * Return depth of transaction in blockchain:
* <0 : conflicts with a transaction this deep in the blockchain * <0 : conflicts with a transaction this deep in the blockchain
* 0 : in memory pool, waiting to be included in a block * 0 : in memory pool, waiting to be included in a block
* >=1 : this many blocks deep in the main chain * >=1 : this many blocks deep in the main chain
*/ */
int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
// "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to
// resolve the issue of member access into incomplete type CWallet. Note
// that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)"
// in place.
int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS;
bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
/** /**
* @return number of blocks to maturity for this transaction: * @return number of blocks to maturity for this transaction:
* 0 : is not a coinbase transaction, or is a mature coinbase transaction * 0 : is not a coinbase transaction, or is a mature coinbase transaction
* >0 : is a coinbase transaction which matures in this many blocks * >0 : is a coinbase transaction which matures in this many blocks
*/ */
int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; int GetBlocksToMaturity() const;
bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
void setAbandoned() void setAbandoned()
{ {
m_confirm.status = CWalletTx::ABANDONED; m_confirm.status = CWalletTx::ABANDONED;
m_confirm.hashBlock = uint256(); m_confirm.hashBlock = uint256();
m_confirm.block_height = 0;
m_confirm.nIndex = 0; m_confirm.nIndex = 0;
} }
bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; }
void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; }
bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; }
void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; }
bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; }
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
const uint256& GetHash() const { return tx->GetHash(); } const uint256& GetHash() const { return tx->GetHash(); }
bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsCoinBase() const { return tx->IsCoinBase(); }
bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; bool IsImmatureCoinBase() const;
}; };
class COutput class COutput
@ -642,10 +650,10 @@ private:
* Abandoned state should probably be more carefully tracked via different * Abandoned state should probably be more carefully tracked via different
* posInBlock signals or by checking mempool presence when necessary. * posInBlock signals or by checking mempool presence when necessary.
*/ */
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx);
/* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */ /* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */
void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -654,7 +662,7 @@ private:
/* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
* Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */ * Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */
void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::atomic<uint64_t> m_wallet_flags{0}; std::atomic<uint64_t> m_wallet_flags{0};
@ -679,12 +687,18 @@ private:
* The following is used to keep track of how far behind the wallet is * The following is used to keep track of how far behind the wallet is
* from the chain sync, and to allow clients to block on us being caught up. * from the chain sync, and to allow clients to block on us being caught up.
* *
* Note that this is *not* how far we've processed, we may need some rescan * Processed hash is a pointer on node's tip and doesn't imply that the wallet
* to have seen all transactions in the chain, but is only used to track * has scanned sequentially all blocks up to this one.
* live BlockConnected callbacks.
*/ */
uint256 m_last_block_processed GUARDED_BY(cs_wallet); uint256 m_last_block_processed GUARDED_BY(cs_wallet);
/* Height of last block processed is used by wallet to know depth of transactions
* without relying on Chain interface beyond asynchronous updates. For safety, we
* initialize it to -1. Height is a pointer on node's tip and doesn't imply
* that the wallet has scanned sequentially all blocks up to this one.
*/
int m_last_block_processed_height GUARDED_BY(cs_wallet) = -1;
public: public:
/* /*
* Main wallet lock. * Main wallet lock.
@ -794,7 +808,7 @@ public:
bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const; std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const;
bool IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// Whether this or any UTXO with the same CTxDestination has been spent. // Whether this or any UTXO with the same CTxDestination has been spent.
bool IsUsedDestination(const CTxDestination& dst) const; bool IsUsedDestination(const CTxDestination& dst) const;
@ -855,8 +869,8 @@ public:
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void TransactionAddedToMempool(const CTransactionRef& tx) override; void TransactionAddedToMempool(const CTransactionRef& tx) override;
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted, int height) override;
void BlockDisconnected(const CBlock& block) override; void BlockDisconnected(const CBlock& block, int height) override;
void UpdatedBlockTip() override; void UpdatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
@ -877,7 +891,7 @@ public:
}; };
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate); ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions(); void ResendWalletTransactions();
struct Balance { struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
@ -1056,7 +1070,7 @@ public:
bool TransactionCanBeAbandoned(const uint256& hashTx) const; bool TransactionCanBeAbandoned(const uint256& hashTx) const;
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
bool AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx); bool AbandonTransaction(const uint256& hashTx);
/** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
bool MarkReplaced(const uint256& originalHash, const uint256& newHash); bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
@ -1129,6 +1143,21 @@ public:
LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys; LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys;
WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch; WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch;
using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap; using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap;
/** Get last block processed height */
int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
{
AssertLockHeld(cs_wallet);
assert(m_last_block_processed_height >= 0);
return m_last_block_processed_height;
};
/** Set last block processed height, currently only use in unit test */
void SetLastBlockProcessed(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
{
AssertLockHeld(cs_wallet);
m_last_block_processed_height = block_height;
m_last_block_processed = block_hash;
};
}; };
/** /**

View File

@ -185,7 +185,7 @@ void CZMQNotificationInterface::BlockConnected(const std::shared_ptr<const CBloc
} }
} }
void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected)
{ {
for (const CTransactionRef& ptx : pblock->vtx) { for (const CTransactionRef& ptx : pblock->vtx) {
// Do a normal notify for each transaction removed in block disconnection // Do a normal notify for each transaction removed in block disconnection

View File

@ -27,7 +27,7 @@ protected:
// CValidationInterface // CValidationInterface
void TransactionAddedToMempool(const CTransactionRef& tx) override; void TransactionAddedToMempool(const CTransactionRef& tx) override;
void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override; void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) override;
void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override;
private: private: