From fcf9da67a374f6a4269f605972f4430973eb47fc Mon Sep 17 00:00:00 2001 From: woodser Date: Sat, 20 Jul 2019 18:22:53 -0400 Subject: [PATCH] identify and handle bug in monero core: failed tx in pool (after testUpdateLockedDifferentAccounts()) associated with non-originating saved wallets returning duplicate incoming transfers but one has amount/numSuggestedConfirmations of 0. Resolved by rescan_bc; MoneroUtils::reconcile() supports optional custom error message for debugging --- src/daemon/MoneroDaemonModel.cpp | 50 ++++++++++++++++---------------- src/utils/MoneroUtils.h | 33 +++++++++------------ src/wallet/MoneroWalletModel.cpp | 16 +++++++--- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/daemon/MoneroDaemonModel.cpp b/src/daemon/MoneroDaemonModel.cpp index 7a029e3..8a3b6e4 100644 --- a/src/daemon/MoneroDaemonModel.cpp +++ b/src/daemon/MoneroDaemonModel.cpp @@ -113,22 +113,22 @@ namespace monero { if (this != self.get()) throw runtime_error("this != self"); if (self == other) return; id = MoneroUtils::reconcile(id, other->id); - height = MoneroUtils::reconcile(height, other->height, boost::none, boost::none, true); // height can increase - timestamp = MoneroUtils::reconcile(timestamp, other->timestamp, boost::none, boost::none, true); // timestamp can increase - size = MoneroUtils::reconcile(size, other->size); - weight = MoneroUtils::reconcile(weight, other->weight); - longTermWeight = MoneroUtils::reconcile(longTermWeight, other->longTermWeight); - depth = MoneroUtils::reconcile(depth, other->depth); - difficulty = MoneroUtils::reconcile(difficulty, other->difficulty); - cumulativeDifficulty = MoneroUtils::reconcile(cumulativeDifficulty, other->cumulativeDifficulty); - majorVersion = MoneroUtils::reconcile(majorVersion, other->majorVersion); - minorVersion = MoneroUtils::reconcile(minorVersion, other->minorVersion); - nonce = MoneroUtils::reconcile(nonce, other->nonce); + height = MoneroUtils::reconcile(height, other->height, boost::none, boost::none, true, "block height"); // height can increase + timestamp = MoneroUtils::reconcile(timestamp, other->timestamp, boost::none, boost::none, true, "block header timestamp"); // timestamp can increase + size = MoneroUtils::reconcile(size, other->size, "block header size"); + weight = MoneroUtils::reconcile(weight, other->weight, "block header weight"); + longTermWeight = MoneroUtils::reconcile(longTermWeight, other->longTermWeight, "block header long term weight"); + depth = MoneroUtils::reconcile(depth, other->depth, "block header depth"); + difficulty = MoneroUtils::reconcile(difficulty, other->difficulty, "difficulty"); + cumulativeDifficulty = MoneroUtils::reconcile(cumulativeDifficulty, other->cumulativeDifficulty, "cumulativeDifficulty"); + majorVersion = MoneroUtils::reconcile(majorVersion, other->majorVersion, "majorVersion"); + minorVersion = MoneroUtils::reconcile(minorVersion, other->minorVersion, "minorVersion"); + nonce = MoneroUtils::reconcile(nonce, other->nonce, "nonce"); coinbaseTxId = MoneroUtils::reconcile(coinbaseTxId, other->coinbaseTxId); - numTxs = MoneroUtils::reconcile(numTxs, other->numTxs); + numTxs = MoneroUtils::reconcile(numTxs, other->numTxs, "block header numTxs"); orphanStatus = MoneroUtils::reconcile(orphanStatus, other->orphanStatus); prevId = MoneroUtils::reconcile(prevId, other->prevId); - reward = MoneroUtils::reconcile(reward, other->reward); + reward = MoneroUtils::reconcile(reward, other->reward, "block header reward"); powHash = MoneroUtils::reconcile(powHash, other->powHash); } @@ -249,8 +249,8 @@ namespace monero { id = MoneroUtils::reconcile(id, other->id); version = MoneroUtils::reconcile(version, other->version); paymentId = MoneroUtils::reconcile(paymentId, other->paymentId); - fee = MoneroUtils::reconcile(fee, other->fee); - mixin = MoneroUtils::reconcile(mixin, other->mixin); + fee = MoneroUtils::reconcile(fee, other->fee, "tx fee"); + mixin = MoneroUtils::reconcile(mixin, other->mixin, "tx mixin"); isConfirmed = MoneroUtils::reconcile(isConfirmed, other->isConfirmed); doNotRelay = MoneroUtils::reconcile(doNotRelay, other->doNotRelay); isRelayed = MoneroUtils::reconcile(isRelayed, other->isRelayed); @@ -260,8 +260,8 @@ namespace monero { prunedHex = MoneroUtils::reconcile(prunedHex, other->prunedHex); prunableHex = MoneroUtils::reconcile(prunableHex, other->prunableHex); prunableHash = MoneroUtils::reconcile(prunableHash, other->prunableHash); - size = MoneroUtils::reconcile(size, other->size); - weight = MoneroUtils::reconcile(weight, other->weight); + size = MoneroUtils::reconcile(size, other->size, "tx size"); + weight = MoneroUtils::reconcile(weight, other->weight, "tx weight"); //outputIndices = MoneroUtils::reconcile(outputIndices, other->outputIndices); // TODO metadata = MoneroUtils::reconcile(metadata, other->metadata); commonTxSets = MoneroUtils::reconcile(commonTxSets, other->commonTxSets); @@ -270,13 +270,13 @@ namespace monero { rctSigPrunable = MoneroUtils::reconcile(rctSigPrunable, other->rctSigPrunable); isKeptByBlock = MoneroUtils::reconcile(isKeptByBlock, other->isKeptByBlock); isFailed = MoneroUtils::reconcile(isFailed, other->isFailed); - lastFailedHeight = MoneroUtils::reconcile(lastFailedHeight, other->lastFailedHeight); + lastFailedHeight = MoneroUtils::reconcile(lastFailedHeight, other->lastFailedHeight, "lastFailedHeight"); lastFailedId = MoneroUtils::reconcile(lastFailedId, other->lastFailedId); - maxUsedBlockHeight = MoneroUtils::reconcile(maxUsedBlockHeight, other->maxUsedBlockHeight); + maxUsedBlockHeight = MoneroUtils::reconcile(maxUsedBlockHeight, other->maxUsedBlockHeight, "maxUsedBlockHeight"); maxUsedBlockId = MoneroUtils::reconcile(maxUsedBlockId, other->maxUsedBlockId); //signatures = MoneroUtils::reconcile(signatures, other->signatures); // TODO - unlockTime = MoneroUtils::reconcile(unlockTime, other->unlockTime); - numConfirmations = MoneroUtils::reconcile(numConfirmations, other->numConfirmations); + unlockTime = MoneroUtils::reconcile(unlockTime, other->unlockTime, "unlockTime"); + numConfirmations = MoneroUtils::reconcile(numConfirmations, other->numConfirmations, "numConfirmations"); // merge vins if (!other->vins.empty()) { @@ -349,8 +349,8 @@ namespace monero { lastRelayedTimestamp = boost::none; } else { inTxPool = MoneroUtils::reconcile(inTxPool, other->inTxPool, boost::none, true, boost::none); // unrelayed -> tx pool - receivedTimestamp = MoneroUtils::reconcile(receivedTimestamp, other->receivedTimestamp, boost::none, boost::none, false); // take earliest receive time - lastRelayedTimestamp = MoneroUtils::reconcile(lastRelayedTimestamp, other->lastRelayedTimestamp, boost::none, boost::none, true); // take latest relay time + receivedTimestamp = MoneroUtils::reconcile(receivedTimestamp, other->receivedTimestamp, boost::none, boost::none, false, "receivedTimestamp"); // take earliest receive time + lastRelayedTimestamp = MoneroUtils::reconcile(lastRelayedTimestamp, other->lastRelayedTimestamp, boost::none, boost::none, true, "lastRelayedTimestamp"); // take latest relay time } } @@ -395,7 +395,7 @@ namespace monero { // otherwise merge output fields if (keyImage == boost::none) keyImage = other->keyImage; else if (other->keyImage != boost::none) keyImage.get()->merge(keyImage.get(), other->keyImage.get()); - amount = MoneroUtils::reconcile(amount, other->amount); - index = MoneroUtils::reconcile(index, other->index); + amount = MoneroUtils::reconcile(amount, other->amount, "output amount"); + index = MoneroUtils::reconcile(index, other->index, "output index"); } } diff --git a/src/utils/MoneroUtils.h b/src/utils/MoneroUtils.h index 8d003e4..5328854 100644 --- a/src/utils/MoneroUtils.h +++ b/src/utils/MoneroUtils.h @@ -108,7 +108,7 @@ namespace MoneroUtils // TODO: refactor common template code template ::value, T>::type* = nullptr> - boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolveDefined, boost::optional resolveTrue, boost::optional resolveMax) { + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolveDefined, boost::optional resolveTrue, boost::optional resolveMax, const string& errMsg = "") { // check for equality if (val1 == val2) return val1; @@ -119,16 +119,15 @@ namespace MoneroUtils else return val1 == boost::none ? val2 : val1; } - throw runtime_error("Cannot reconcile strings"); + throw runtime_error(string("Cannot reconcile strings: ") + to_string(val1) + string(" vs ") + to_string(val2) + (!errMsg.empty() ? string(". ") + errMsg : string(""))); } - template ::value, T>::type* = nullptr> - boost::optional reconcile(const boost::optional& val1, const boost::optional& val2) { - return reconcile(val1, val2, boost::none, boost::none, boost::none); + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, const string& errMsg = "") { + return reconcile(val1, val2, boost::none, boost::none, boost::none, errMsg); } template ::value, T>::type* = nullptr> - boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolveDefined, boost::optional resolveTrue, boost::optional resolveMax) { + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolveDefined, boost::optional resolveTrue, boost::optional resolveMax, const string& errMsg = "") { // check for equality if (val1 == val2) return val1; @@ -145,16 +144,15 @@ namespace MoneroUtils } // throw runtime_error("Cannot reconcile values " + val1 + " and " + val2 + " with config: [" + resolveDefined + ", " + resolveTrue + ", " + resolveMax + "]", val1, val2); - throw runtime_error("Cannot reconcile integrals"); + throw runtime_error(string("Cannot reconcile integrals: ") + to_string(val1) + string(" vs ") + to_string(val2) + (!errMsg.empty() ? string(". ") + errMsg : string(""))); } - template ::value, T>::type* = nullptr> - boost::optional reconcile(const boost::optional& val1, const boost::optional& val2) { - return reconcile(val1, val2, boost::none, boost::none, boost::none); + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, const string& errMsg = "") { + return reconcile(val1, val2, boost::none, boost::none, boost::none, errMsg); } template ::value, T>::type> - boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolveDefined, boost::optional resolveTrue, boost::optional resolveMax) { + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, boost::optional resolveDefined, boost::optional resolveTrue, boost::optional resolveMax, const string& errMsg = "") { // check for equality if (val1 == val2) return val1; @@ -169,19 +167,16 @@ namespace MoneroUtils if (resolveTrue != boost::none) { return val1 == resolveTrue ? val1 : val2; // if resolve true, return true, else return false } else { - throw runtime_error("Cannot reconcile booleans"); + throw runtime_error(string("Cannot reconcile booleans: ") + to_string(val1) + string(" vs ") + to_string(val2) + (!errMsg.empty() ? string(". ") + errMsg : string(""))); } - - throw runtime_error("Cannot reconcile booleans"); } - template ::value, T>::type> - boost::optional reconcile(const boost::optional& val1, const boost::optional& val2) { - return reconcile(val1, val2, boost::none, boost::none, boost::none); + boost::optional reconcile(const boost::optional& val1, const boost::optional& val2, const string& errMsg = "") { + return reconcile(val1, val2, boost::none, boost::none, boost::none, errMsg); } template - vector reconcile(const vector& v1, const vector& v2) { + vector reconcile(const vector& v1, const vector& v2, const string& errMsg = "") { // check for equality if (v1 == v2) return v1; @@ -191,7 +186,7 @@ namespace MoneroUtils if (v2.empty()) return v1; // otherwise cannot reconcile - throw runtime_error("Cannot reconcile vectors"); + throw runtime_error("Cannot reconcile vectors" + (!errMsg.empty() ? string(". ") + errMsg : string(""))); } // template diff --git a/src/wallet/MoneroWalletModel.cpp b/src/wallet/MoneroWalletModel.cpp index bee8ace..a94346f 100644 --- a/src/wallet/MoneroWalletModel.cpp +++ b/src/wallet/MoneroWalletModel.cpp @@ -289,9 +289,17 @@ namespace monero { } // otherwise merge transfer fields - amount = MoneroUtils::reconcile(amount, other->amount); - accountIndex = MoneroUtils::reconcile(accountIndex, other->accountIndex); - numSuggestedConfirmations = MoneroUtils::reconcile(numSuggestedConfirmations, other->numSuggestedConfirmations, boost::none, boost::none, false); + accountIndex = MoneroUtils::reconcile(accountIndex, other->accountIndex, "acountIndex"); + + // TODO monero core: failed tx in pool (after testUpdateLockedDifferentAccounts()) causes non-originating saved wallets to return duplicate incoming transfers but one has amount/numSuggestedConfirmations of 0 + if (amount != boost::none && other->amount != boost::none && *amount != *other->amount && (*amount == 0 || *other->amount == 0)) { + accountIndex = MoneroUtils::reconcile(accountIndex, other->accountIndex, boost::none, boost::none, true, "acountIndex"); + numSuggestedConfirmations = MoneroUtils::reconcile(numSuggestedConfirmations, other->numSuggestedConfirmations, boost::none, boost::none, true, "numSuggestedConfirmations"); + cout << "WARNING: failed tx in pool causes non-originating wallets to return duplicate incoming transfers but with one amount/numSuggestedConfirmations of 0" << endl; + } else { + amount = MoneroUtils::reconcile(amount, other->amount, "transfer amount"); + numSuggestedConfirmations = MoneroUtils::reconcile(numSuggestedConfirmations, other->numSuggestedConfirmations, boost::none, boost::none, false, "numSuggestedConfirmations"); + } } // ----------------------- MONERO INCOMING TRANSFER ------------------------- @@ -314,7 +322,7 @@ namespace monero { //cout << "MoneroIncomingTransfer::merge" << endl; if (self == other) return; MoneroTransfer::merge(self, other); - subaddressIndex = MoneroUtils::reconcile(subaddressIndex, other->subaddressIndex); + subaddressIndex = MoneroUtils::reconcile(subaddressIndex, other->subaddressIndex, "incoming transfer subaddressIndex"); address = MoneroUtils::reconcile(address, other->address); }