Encapsulate tx status in a Confirmation struct

Instead of relying on combination of hashBlock and nIndex
values to manage tx in its lifecycle, we introduce 4
status : CONFIRMED, UNCONFIRMED, CONFLICTED, ABANDONED.

hashBlock and nIndex magic values should only be used at
serialization/deserialization for backward-compatibility.

At block disconnection, we know flag txn as UNCONFIRMED where
previously they kept their states until being override by a
block connection or abandontransaction call. This is a change
in behavior for which user may have to call abandon twice
if transaction is disconnected and not accepted back in the mempool.

We assert status transitioning right in AddToWallet. Doing so
flagged a misbehavior in ComputeTimeSmart unit test where same
tx is confirmed twice in different block. To avoid inconsistencies
we unconfirmed tx before new connection in different block. We
also remove a cs_main lock in test, as AddToWallet and its
callees don't rely on locked chain.
This commit is contained in:
Antoine Riard 2019-08-12 18:12:12 -04:00
parent 442a9c6477
commit a31be09bfd
6 changed files with 127 additions and 75 deletions

View File

@ -65,7 +65,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co
WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx) WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx)
{ {
WalletTxStatus result; WalletTxStatus result;
result.block_height = locked_chain.getBlockHeight(wtx.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(locked_chain);
result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain);
result.time_received = wtx.nTimeReceived; result.time_received = wtx.nTimeReceived;

View File

@ -384,8 +384,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
} }
wtx.nIndex = txnIndex; wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex);
wtx.hashBlock = merkleBlock.header.GetHash();
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);

View File

@ -134,10 +134,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
entry.pushKV("generated", true); entry.pushKV("generated", true);
if (confirms > 0) if (confirms > 0)
{ {
entry.pushKV("blockhash", wtx.hashBlock.GetHex()); entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
entry.pushKV("blockindex", wtx.nIndex); entry.pushKV("blockindex", wtx.m_confirm.nIndex);
int64_t block_time; int64_t block_time;
bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time); bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time);
assert(found_block); assert(found_block);
entry.pushKV("blocktime", block_time); entry.pushKV("blocktime", block_time);
} else { } else {

View File

@ -249,8 +249,7 @@ 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);
wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash(); wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0);
wtx.nIndex = 0;
// 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.
@ -281,14 +280,19 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
} }
CWalletTx wtx(&wallet, MakeTransactionRef(tx)); CWalletTx wtx(&wallet, MakeTransactionRef(tx));
if (block) { LOCK(cs_main);
wtx.SetMerkleBranch(block->GetBlockHash(), 0); LOCK(wallet.cs_wallet);
} // If transaction is already in map, to avoid inconsistencies, unconfirmation
{ // is needed before confirm again with different block.
LOCK(cs_main); std::map<uint256, CWalletTx>::iterator it = wallet.mapWallet.find(wtx.GetHash());
if (it != wallet.mapWallet.end()) {
wtx.setUnconfirmed();
wallet.AddToWallet(wtx); wallet.AddToWallet(wtx);
} }
LOCK(wallet.cs_wallet); if (block) {
wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0);
}
wallet.AddToWallet(wtx);
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart; return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
} }
@ -382,7 +386,7 @@ public:
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
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.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1);
return it->second; return it->second;
} }

View File

@ -1118,22 +1118,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
bool fUpdated = false; bool fUpdated = false;
if (!fInsertedNew) if (!fInsertedNew)
{ {
// Merge if (wtxIn.m_confirm.status != wtx.m_confirm.status) {
if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) wtx.m_confirm.status = wtxIn.m_confirm.status;
{ wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex;
wtx.hashBlock = wtxIn.hashBlock; wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock;
fUpdated = true;
}
// If no longer abandoned, update
if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned())
{
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex))
{
wtx.nIndex = wtxIn.nIndex;
fUpdated = true; fUpdated = true;
} else {
assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex);
assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock);
} }
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{ {
@ -1194,14 +1186,14 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn)
auto it = mapWallet.find(txin.prevout.hash); auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second; CWalletTx& prevtx = it->second;
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { if (prevtx.isConflicted()) {
MarkConflicted(prevtx.hashBlock, wtx.GetHash()); MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash());
} }
} }
} }
} }
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate) bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate)
{ {
const CTransaction& tx = *ptx; const CTransaction& tx = *ptx;
{ {
@ -1248,9 +1240,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256
CWalletTx wtx(this, ptx); CWalletTx wtx(this, ptx);
// Get merkle branch if transaction was found in a block // Block disconnection override an abandoned tx as unconfirmed
if (!block_hash.IsNull()) // which means user may have to call abandontransaction again
wtx.SetMerkleBranch(block_hash, posInBlock); wtx.SetConf(status, block_hash, posInBlock);
return AddToWallet(wtx, false); return AddToWallet(wtx, false);
} }
@ -1310,7 +1302,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
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.nIndex = -1; wtx.m_confirm.nIndex = 0;
wtx.setAbandoned(); wtx.setAbandoned();
wtx.MarkDirty(); wtx.MarkDirty();
batch.WriteTx(wtx); batch.WriteTx(wtx);
@ -1364,8 +1356,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
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.nIndex = -1; wtx.m_confirm.nIndex = 0;
wtx.hashBlock = hashBlock; wtx.m_confirm.hashBlock = hashBlock;
wtx.setConflicted();
wtx.MarkDirty(); wtx.MarkDirty();
batch.WriteTx(wtx); batch.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too // Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
@ -1383,8 +1376,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
} }
} }
void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) { void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx)
if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) {
if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, 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
@ -1396,7 +1390,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_h
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, {} /* block hash */, 0 /* position in block */); SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
auto it = mapWallet.find(ptx->GetHash()); auto it = mapWallet.find(ptx->GetHash());
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
@ -1425,11 +1419,11 @@ void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransaction
// the notification that the conflicted transaction was evicted. // the notification that the conflicted transaction was evicted.
for (const CTransactionRef& ptx : vtxConflicted) { for (const CTransactionRef& ptx : vtxConflicted) {
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); SyncTransaction(ptx, CWalletTx::Status::CONFLICTED, {} /* block hash */, 0 /* position in block */);
TransactionRemovedFromMempool(ptx); TransactionRemovedFromMempool(ptx);
} }
for (size_t i = 0; i < block.vtx.size(); i++) { for (size_t i = 0; i < block.vtx.size(); i++) {
SyncTransaction(block.vtx[i], block_hash, i); SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i);
TransactionRemovedFromMempool(block.vtx[i]); TransactionRemovedFromMempool(block.vtx[i]);
} }
@ -1440,8 +1434,12 @@ void CWallet::BlockDisconnected(const CBlock& block) {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
// At block disconnection, this will change an abandoned transaction to
// 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
// future with a stickier abandoned state or even removing abandontransaction call.
for (const CTransactionRef& ptx : block.vtx) { for (const CTransactionRef& ptx : block.vtx) {
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
} }
} }
@ -2078,7 +2076,7 @@ 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], block_hash, posInBlock, fUpdate); SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, 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;
@ -4050,7 +4048,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
for (const auto& entry : mapWallet) { for (const auto& entry : mapWallet) {
// iterate over all wallet transactions... // iterate over all wallet transactions...
const CWalletTx &wtx = entry.second; const CWalletTx &wtx = entry.second;
if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) { if (Optional<int> height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) {
// ... which are already in a block // ... which are already in a block
for (const CTxOut &txout : wtx.tx->vout) { for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs // iterate over all their outputs
@ -4093,9 +4091,9 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
{ {
unsigned int nTimeSmart = wtx.nTimeReceived; unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.hashUnset()) { if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
int64_t blocktime; int64_t blocktime;
if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) { if (chain().findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &blocktime)) {
int64_t latestNow = wtx.nTimeReceived; int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0; int64_t latestEntry = 0;
@ -4123,7 +4121,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else { } else {
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString());
} }
} }
return nTimeSmart; return nTimeSmart;
@ -4634,21 +4632,23 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
m_pre_split = false; m_pre_split = false;
} }
void CWalletTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock)
{ {
// Update tx status
m_confirm.status = status;
// Update the tx's hashBlock // Update the tx's hashBlock
hashBlock = block_hash; m_confirm.hashBlock = block_hash;
// set the position of the transaction in the block // set the position of the transaction in the block
nIndex = posInBlock; m_confirm.nIndex = posInBlock;
} }
int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
{ {
if (hashUnset()) if (isUnconfirmed() || isAbandoned()) return 0;
return 0;
return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1); return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1);
} }
int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const

View File

@ -396,7 +396,9 @@ class CWalletTx
private: private:
const CWallet* pwallet; const CWallet* pwallet;
/** Constant used in hashBlock to indicate tx has been abandoned */ /** Constant used in hashBlock to indicate tx has been abandoned, only used at
* serialization/deserialization to avoid ambiguity with conflicted.
*/
static const uint256 ABANDON_HASH; static const uint256 ABANDON_HASH;
public: public:
@ -457,9 +459,7 @@ public:
mutable CAmount nChangeCached; mutable CAmount nChangeCached;
CWalletTx(const CWallet* pwalletIn, CTransactionRef arg) CWalletTx(const CWallet* pwalletIn, CTransactionRef arg)
: tx(std::move(arg)), : tx(std::move(arg))
hashBlock(uint256()),
nIndex(-1)
{ {
Init(pwalletIn); Init(pwalletIn);
} }
@ -477,16 +477,37 @@ public:
fInMempool = false; fInMempool = false;
nChangeCached = 0; nChangeCached = 0;
nOrderPos = -1; nOrderPos = -1;
m_confirm = Confirmation{};
} }
CTransactionRef tx; CTransactionRef tx;
uint256 hashBlock;
/* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest /* New transactions start as UNCONFIRMED. At BlockConnected,
* block in the chain we know this or any in-wallet dependency conflicts * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected,
* with. Older clients interpret nIndex == -1 as unconfirmed for backward * they roll back to UNCONFIRMED. If we detect a conflicting transaction at
* compatibility. * block connection, we update conflicted tx and its dependencies as CONFLICTED.
* If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED
* by using the abandontransaction call. This last status may be override by a CONFLICTED
* or CONFIRMED transition.
*/ */
int nIndex; enum Status {
UNCONFIRMED,
CONFIRMED,
CONFLICTED,
ABANDONED
};
/* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed.
* This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state
* where they instead point to block hash and index of the deepest conflicting tx.
*/
struct Confirmation {
Status status = UNCONFIRMED;
uint256 hashBlock = uint256();
int nIndex = 0;
};
Confirmation m_confirm;
template<typename Stream> template<typename Stream>
void Serialize(Stream& s) const void Serialize(Stream& s) const
@ -502,7 +523,9 @@ public:
std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch
std::vector<char> dummy_vector2; //!< Used to be vtxPrev std::vector<char> dummy_vector2; //!< Used to be vtxPrev
bool dummy_bool = false; //!< Used to be fSpent bool dummy_bool = false; //!< Used to be fSpent
s << tx << hashBlock << dummy_vector1 << nIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock;
int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex;
s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool;
} }
template<typename Stream> template<typename Stream>
@ -513,7 +536,25 @@ public:
std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch
std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev
bool dummy_bool; //! Used to be fSpent bool dummy_bool; //! Used to be fSpent
s >> tx >> hashBlock >> dummy_vector1 >> nIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; int serializedIndex;
s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool;
/* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to
* the earliest block in the chain we know this or any in-wallet ancestor conflicts
* with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned.
* In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or
* unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward
* compatibility (pre-commit 9ac63d6).
*/
if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) {
m_confirm.hashBlock = uint256();
setAbandoned();
} else if (serializedIndex == -1) {
setConflicted();
} else if (!m_confirm.hashBlock.IsNull()) {
m_confirm.nIndex = serializedIndex;
setConfirmed();
}
ReadOrderPos(nOrderPos, mapValue); ReadOrderPos(nOrderPos, mapValue);
nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
@ -590,7 +631,7 @@ public:
// in place. // in place.
std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
void SetMerkleBranch(const uint256& block_hash, int posInBlock); void SetConf(Status status, const uint256& block_hash, int posInBlock);
/** /**
* Return depth of transaction in blockchain: * Return depth of transaction in blockchain:
@ -607,10 +648,18 @@ public:
* >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(interfaces::Chain::Lock& locked_chain) const;
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } void setAbandoned()
void setAbandoned() { hashBlock = ABANDON_HASH; } {
m_confirm.status = CWalletTx::ABANDONED;
m_confirm.hashBlock = uint256();
m_confirm.nIndex = 0;
}
bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; }
void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; }
bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; }
void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; }
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(interfaces::Chain::Lock& locked_chain) const;
@ -750,7 +799,7 @@ 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, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, 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, const uint256& hashTx);
@ -762,7 +811,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, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* the HD chain data model (external chain counters) */ /* the HD chain data model (external chain counters) */
CHDChain hdChain; CHDChain hdChain;