Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/wallet.cpp
Show First 20 Lines • Show All 1,183 Lines • ▼ Show 20 Lines | if (fInsertedNew) { | ||||
wtx.m_it_wtxOrdered = | wtx.m_it_wtxOrdered = | ||||
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); | wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); | ||||
wtx.nTimeSmart = ComputeTimeSmart(wtx); | wtx.nTimeSmart = ComputeTimeSmart(wtx); | ||||
AddToSpends(txid); | AddToSpends(txid); | ||||
} | } | ||||
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.hashBlock = wtxIn.hashBlock; | wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex; | ||||
fUpdated = true; | wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock; | ||||
} | |||||
// 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) { | ||||
wtx.fFromMe = wtxIn.fFromMe; | wtx.fFromMe = wtxIn.fFromMe; | ||||
fUpdated = true; | fUpdated = true; | ||||
} | } | ||||
} | } | ||||
Show All 35 Lines | if (/* insertion took place */ ins.second) { | ||||
wtx.m_it_wtxOrdered = | wtx.m_it_wtxOrdered = | ||||
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); | wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); | ||||
} | } | ||||
AddToSpends(txid); | AddToSpends(txid); | ||||
for (const CTxIn &txin : wtx.tx->vin) { | for (const CTxIn &txin : wtx.tx->vin) { | ||||
auto it = mapWallet.find(txin.prevout.GetTxId()); | auto it = mapWallet.find(txin.prevout.GetTxId()); | ||||
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.GetId()); | MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetId()); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx, | bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx, | ||||
CWalletTx::Status status, | |||||
const BlockHash &block_hash, | const BlockHash &block_hash, | ||||
int posInBlock, bool fUpdate) { | int posInBlock, bool fUpdate) { | ||||
const CTransaction &tx = *ptx; | const CTransaction &tx = *ptx; | ||||
AssertLockHeld(cs_wallet); | AssertLockHeld(cs_wallet); | ||||
if (!block_hash.IsNull()) { | if (!block_hash.IsNull()) { | ||||
for (const CTxIn &txin : tx.vin) { | for (const CTxIn &txin : tx.vin) { | ||||
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> | std::pair<TxSpends::const_iterator, TxSpends::const_iterator> | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | if (fExisted || IsMine(tx) || IsFromMe(tx)) { | ||||
__func__); | __func__); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
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); | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const { | bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const { | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | while (!todo.empty()) { | ||||
// 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 | // If (currentconfirm < 0) {Tx and spends are already conflicted, no | ||||
// need to abandon} | // 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 | // If the orig tx was not in block/mempool, none of its spends can | ||||
// be in mempool. | // 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); | ||||
NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED); | NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED); | ||||
// Iterate over all its outputs, and mark transactions in the wallet | // Iterate over all its outputs, and mark transactions in the wallet | ||||
// that spend them abandoned too. | // that spend them abandoned too. | ||||
TxSpends::const_iterator iter = | TxSpends::const_iterator iter = | ||||
mapTxSpends.lower_bound(COutPoint(now, 0)); | mapTxSpends.lower_bound(COutPoint(now, 0)); | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | while (!todo.empty()) { | ||||
done.insert(now); | done.insert(now); | ||||
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(*locked_chain); | ||||
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 | // Iterate over all its outputs, and mark transactions in the wallet | ||||
// that spend them conflicted too. | // that spend them conflicted too. | ||||
TxSpends::const_iterator iter = | TxSpends::const_iterator iter = | ||||
mapTxSpends.lower_bound(COutPoint(now, 0)); | mapTxSpends.lower_bound(COutPoint(now, 0)); | ||||
while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { | while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { | ||||
if (!done.count(iter->second)) { | if (!done.count(iter->second)) { | ||||
todo.insert(iter->second); | todo.insert(iter->second); | ||||
} | } | ||||
iter++; | iter++; | ||||
} | } | ||||
// If a transaction changes 'conflicted' state, that changes the | // If a transaction changes 'conflicted' state, that changes the | ||||
// balance available of the outputs it spends. So force those to be | // balance available of the outputs it spends. So force those to be | ||||
// recomputed. | // recomputed. | ||||
MarkInputsDirty(wtx.tx); | MarkInputsDirty(wtx.tx); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void CWallet::SyncTransaction(const CTransactionRef &ptx, | void CWallet::SyncTransaction(const CTransactionRef &ptx, | ||||
CWalletTx::Status status, | |||||
const BlockHash &block_hash, int posInBlock, | const BlockHash &block_hash, int posInBlock, | ||||
bool update_tx) { | bool update_tx) { | ||||
if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) { | if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, | ||||
update_tx)) { | |||||
// Not one of ours | // Not one of ours | ||||
return; | return; | ||||
} | } | ||||
// If a transaction changes 'conflicted' state, that changes the balance | // If a transaction changes 'conflicted' state, that changes the balance | ||||
// available of the outputs it spends. So force those to be | // available of the outputs it spends. So force those to be | ||||
// recomputed, also: | // recomputed, also: | ||||
MarkInputsDirty(ptx); | MarkInputsDirty(ptx); | ||||
} | } | ||||
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, BlockHash(), 0 /* position in block */); | SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, | ||||
BlockHash() /* block hash */, 0 /* position in block */); | |||||
auto it = mapWallet.find(ptx->GetId()); | auto it = mapWallet.find(ptx->GetId()); | ||||
if (it != mapWallet.end()) { | if (it != mapWallet.end()) { | ||||
it->second.fInMempool = true; | it->second.fInMempool = true; | ||||
} | } | ||||
} | } | ||||
void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { | void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { | ||||
Show All 13 Lines | void CWallet::BlockConnected( | ||||
// TODO: Temporarily ensure that mempool removals are notified before | // TODO: Temporarily ensure that mempool removals are notified before | ||||
// connected transactions. This shouldn't matter, but the abandoned state of | // connected transactions. This shouldn't matter, but the abandoned state of | ||||
// transactions in our wallet is currently cleared when we receive another | // transactions in our wallet is currently cleared when we receive another | ||||
// notification and there is a race condition where notification of a | // notification and there is a race condition where notification of a | ||||
// connected conflict might cause an outside process to abandon a | // connected conflict might cause an outside process to abandon a | ||||
// transaction and then have it inadvertently cleared by the notification | // transaction and then have it inadvertently cleared by the notification | ||||
// that the conflicted transaction was evicted. | // that the conflicted transaction was evicted. | ||||
for (const CTransactionRef &ptx : vtxConflicted) { | for (const CTransactionRef &ptx : vtxConflicted) { | ||||
SyncTransaction(ptx, BlockHash(), 0 /* position in block */); | SyncTransaction(ptx, CWalletTx::Status::CONFLICTED, | ||||
BlockHash() /* 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]); | ||||
} | } | ||||
m_last_block_processed = block_hash; | m_last_block_processed = block_hash; | ||||
} | } | ||||
void CWallet::BlockDisconnected(const CBlock &block) { | 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, BlockHash(), 0 /* position in block */); | SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, | ||||
BlockHash() /* block hash */, | |||||
0 /* position in block */); | |||||
} | } | ||||
} | } | ||||
void CWallet::UpdatedBlockTip() { | void CWallet::UpdatedBlockTip() { | ||||
m_best_block_time = GetTime(); | m_best_block_time = GetTime(); | ||||
} | } | ||||
void CWallet::BlockUntilSyncedToCurrentChain() { | void CWallet::BlockUntilSyncedToCurrentChain() { | ||||
▲ Show 20 Lines • Show All 677 Lines • ▼ Show 20 Lines | while (block_height && !fAbortRescan && !chain().shutdownRequested()) { | ||||
// TODO: This should return success instead of failure, see | // TODO: This should return success instead of failure, see | ||||
// https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 | // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 | ||||
result.last_failed_block = block_hash; | result.last_failed_block = block_hash; | ||||
result.status = ScanResult::FAILURE; | result.status = ScanResult::FAILURE; | ||||
break; | break; | ||||
} | } | ||||
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); | for (size_t posInBlock = 0; posInBlock < block.vtx.size(); | ||||
++posInBlock) { | ++posInBlock) { | ||||
SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, | SyncTransaction(block.vtx[posInBlock], | ||||
fUpdate); | CWalletTx::Status::CONFIRMED, block_hash, | ||||
posInBlock, fUpdate); | |||||
} | } | ||||
// scan succeeded, record block as most recent successfully | // scan succeeded, record block as most recent successfully | ||||
// scanned | // scanned | ||||
result.last_scanned_block = block_hash; | result.last_scanned_block = block_hash; | ||||
result.last_scanned_height = *block_height; | result.last_scanned_height = *block_height; | ||||
} else { | } else { | ||||
// could not scan block, keep scanning but record this block as | // could not scan block, keep scanning but record this block as | ||||
// the most recent failure | // the most recent failure | ||||
▲ Show 20 Lines • Show All 2,205 Lines • ▼ Show 20 Lines | void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock &locked_chain, | ||||
if (mapKeyFirstBlock.empty()) { | if (mapKeyFirstBlock.empty()) { | ||||
return; | return; | ||||
} | } | ||||
// Find first block that affects those keys, if there are any left. | // Find first block that affects those keys, if there are any left. | ||||
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... | ||||
for (const auto &keyid : | for (const auto &keyid : | ||||
GetAffectedKeys(txout.scriptPubKey, *this)) { | GetAffectedKeys(txout.scriptPubKey, *this)) { | ||||
// ... and all their affected keys. | // ... and all their affected keys. | ||||
std::map<CKeyID, int>::iterator rit = | std::map<CKeyID, int>::iterator rit = | ||||
mapKeyFirstBlock.find(keyid); | mapKeyFirstBlock.find(keyid); | ||||
Show All 32 Lines | |||||
* the block time. | * the block time. | ||||
* | * | ||||
* For more information see CWalletTx::nTimeSmart, | * For more information see CWalletTx::nTimeSmart, | ||||
* https://bitcointalk.org/?topic=54527, or | * https://bitcointalk.org/?topic=54527, or | ||||
* https://github.com/bitcoin/bitcoin/pull/1393. | * https://github.com/bitcoin/bitcoin/pull/1393. | ||||
*/ | */ | ||||
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; | ||||
// Tolerate times up to the last timestamp in the wallet not more | // Tolerate times up to the last timestamp in the wallet not more | ||||
// than 5 minutes into the future | // than 5 minutes into the future | ||||
int64_t latestTolerated = latestNow + 300; | int64_t latestTolerated = latestNow + 300; | ||||
const TxItems &txOrdered = wtxOrdered; | const TxItems &txOrdered = wtxOrdered; | ||||
for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { | for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { | ||||
Show All 13 Lines | if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) { | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
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__, | WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, | ||||
wtx.GetId().ToString(), wtx.hashBlock.ToString()); | wtx.GetId().ToString(), | ||||
wtx.m_confirm.hashBlock.ToString()); | |||||
} | } | ||||
} | } | ||||
return nTimeSmart; | return nTimeSmart; | ||||
} | } | ||||
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, | bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, | ||||
const std::string &value) { | const std::string &value) { | ||||
if (boost::get<CNoDestination>(&dest)) { | if (boost::get<CNoDestination>(&dest)) { | ||||
▲ Show 20 Lines • Show All 577 Lines • ▼ Show 20 Lines | |||||
CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) { | CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) { | ||||
nTime = GetTime(); | nTime = GetTime(); | ||||
vchPubKey = vchPubKeyIn; | vchPubKey = vchPubKeyIn; | ||||
fInternal = internalIn; | fInternal = internalIn; | ||||
m_pre_split = false; | m_pre_split = false; | ||||
} | } | ||||
void CWalletTx::SetMerkleBranch(const BlockHash &block_hash, int posInBlock) { | void CWalletTx::SetConf(Status status, const BlockHash &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( | int CWalletTx::GetDepthInMainChain( | ||||
interfaces::Chain::Lock &locked_chain) const { | 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( | int CWalletTx::GetBlocksToMaturity( | ||||
interfaces::Chain::Lock &locked_chain) const { | interfaces::Chain::Lock &locked_chain) const { | ||||
if (!IsCoinBase()) { | if (!IsCoinBase()) { | ||||
return 0; | return 0; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 286 Lines • Show Last 20 Lines |