diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 19d17e4113..7cf1460c99 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -59,7 +59,11 @@ bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std { temINVALID, "temINVALID", "The transaction is ill-formed" }, { temREDUNDANT, "temREDUNDANT", "Sends same currency to self." }, { temRIPPLE_EMPTY, "temRIPPLE_EMPTY", "PathSet with no paths." }, - { temUNKNOWN, "temUNKNOWN", "The transactions requires logic not implemented yet" }, + { temUNCERTAIN, "temUNCERTAIN", "In process of determining result. Never returned." }, + { temUNKNOWN, "temUNKNOWN", "The transactions requires logic not implemented yet." }, + + { tepPATH_DRY, "tepPATH_DRY", "Path could not send partial amount." }, + { tepPATH_PARTIAL, "tepPATH_PARTIAL", "Path could not send full amount." }, { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, @@ -68,8 +72,6 @@ bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std { terNO_DST, "terNO_DST", "The destination does not exist" }, { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, - { terPATH_EMPTY, "terPATH_EMPTY", "Path could not send partial amount." }, - { terPATH_PARTIAL, "terPATH_PARTIAL", "Path could not send full amount." }, { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction" }, { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, @@ -420,14 +422,14 @@ STAmount TransactionEngine::accountSend(const uint160& uSenderID, const uint160& TransactionEngineResult TransactionEngine::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) { uint64 uOwnerNode = sleOffer->getIFieldU64(sfOwnerNode); - TransactionEngineResult terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex); + TransactionEngineResult terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false); if (tesSUCCESS == terResult) { uint256 uDirectory = sleOffer->getIFieldH256(sfBookDirectory); uint64 uBookNode = sleOffer->getIFieldU64(sfBookNode); - terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex); + terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex, true); } entryDelete(sleOffer); @@ -435,6 +437,14 @@ TransactionEngineResult TransactionEngine::offerDelete(const SLE::pointer& sleOf return terResult; } +TransactionEngineResult TransactionEngine::offerDelete(const uint256& uOfferIndex) +{ + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + const uint160 uOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + + return offerDelete(sleOffer, uOfferIndex, uOwnerID); +} + // <-- uNodeDir: For deletion, present to make dirDelete efficient. // --> uRootIndex: The index of the base of the directory. Nodes are based off of this. // --> uLedgerIndex: Value to add to directory. @@ -528,16 +538,13 @@ TransactionEngineResult TransactionEngine::dirAdd( return tesSUCCESS; } -// --> bKeepRoot: True, if we never completely clean up, after we overflow the root node. -// --> uNodeDir: Node containing entry. -// --> uRootIndex: The index of the base of the directory. Nodes are based off of this. -// --> uLedgerIndex: Value to add to directory. // Ledger must be in a state for this to work. TransactionEngineResult TransactionEngine::dirDelete( - bool bKeepRoot, - const uint64& uNodeDir, - const uint256& uRootIndex, - const uint256& uLedgerIndex) + const bool bKeepRoot, // --> True, if we never completely clean up, after we overflow the root node. + const uint64& uNodeDir, // --> Node containing entry. + const uint256& uRootIndex, // --> The index of the base of the directory. Nodes are based off of this. + const uint256& uLedgerIndex, // --> Value to add to directory. + const bool bStable) // --> True, not to change relative order of entries. { uint64 uNodeCur = uNodeDir; SLE::pointer sleNode = entryCache(ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex(uRootIndex, uNodeCur) : uRootIndex); @@ -569,9 +576,21 @@ TransactionEngineResult TransactionEngine::dirDelete( // Remove the element. if (vuiIndexes.size() > 1) - *it = vuiIndexes[vuiIndexes.size()-1]; - - vuiIndexes.resize(vuiIndexes.size()-1); + { + if (bStable) + { + vuiIndexes.erase(it); + } + else + { + *it = vuiIndexes[vuiIndexes.size()-1]; + vuiIndexes.resize(vuiIndexes.size()-1); + } + } + else + { + vuiIndexes.clear(); + } sleNode->setIFieldV256(sfIndexes, svIndexes); entryModify(sleNode); @@ -1294,7 +1313,7 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran mTxnAccount = SLE::pointer(); mNodes.clear(); - mUnfunded.clear(); + musUnfundedFound.clear(); return terResult; } @@ -1518,7 +1537,7 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti // Zero balance and eliminating last limit. bDelIndex = true; - terResult = dirDelete(false, uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex()); + terResult = dirDelete(false, uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex(), false); } } #endif @@ -1860,7 +1879,7 @@ void TransactionEngine::calcOfferBridgeNext( if (!bDone) { - // mUnfunded.insert(uOfferIndex); + // musUnfundedFound.insert(uOfferIndex); } } while (bNext); @@ -3611,6 +3630,9 @@ void TransactionEngine::pathNext(PathState::pointer pspCur, int iPaths) assert(pspCur->vpnNodes.size() >= 2); + pspCur->vUnfundedBecame.clear(); + pspCur->umSource.clear(); + pspCur->bValid = calcNode(uLast, pspCur, iPaths == 1); Log(lsINFO) << "pathNext: bValid=" @@ -3796,8 +3818,8 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction STAmount saWanted; LedgerEntrySet lesBase = mNodes; // Checkpoint with just fees paid. - terResult = temUNKNOWN; - while (temUNKNOWN == terResult) + terResult = temUNCERTAIN; + while (temUNCERTAIN == terResult) { PathState::pointer pspBest; LedgerEntrySet lesCheckpoint = mNodes; @@ -3820,28 +3842,37 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction { // Apply best path. - // Install ledger for best past. + // Record best pass' offers that became unfunded for deletion on success. + mvUnfundedBecame.insert(mvUnfundedBecame.end(), pspBest->vUnfundedBecame.begin(), pspBest->vUnfundedBecame.end()); + + // Record best pass' LedgerEntrySet to build off of and potentially return. mNodes.swapWith(pspBest->lesEntries); // Figure out if done. - if (temUNKNOWN == terResult && saPaid == saWanted) + if (temUNCERTAIN == terResult && saPaid == saWanted) { terResult = tesSUCCESS; } + else + { + // Prepare for next pass. + + // Merge best pass' umSource. + mumSource.insert(pspBest->umSource.begin(), pspBest->umSource.end()); + } } // Not done and ran out of paths. else if (!bPartialPayment) { // Partial payment not allowed. - terResult = terPATH_PARTIAL; + terResult = tepPATH_PARTIAL; mNodes = lesBase; // Revert to just fees charged. } // Partial payment ok. else if (!saPaid) { // No payment at all. - // XXX Mark for retry? - terResult = terPATH_EMPTY; + terResult = tepPATH_DRY; mNodes = lesBase; // Revert to just fees charged. } else @@ -3850,6 +3881,23 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction } } + if (tesSUCCESS == terResult) + { + // Delete became unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, mvUnfundedBecame) + { + if (tesSUCCESS == terResult) + terResult = offerDelete(uOfferIndex); + } + } + + // Delete found unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, musUnfundedFound) + { + if (tesSUCCESS == terResult) + terResult = offerDelete(uOfferIndex); + } + std::string strToken; std::string strHuman; @@ -3956,12 +4004,16 @@ TransactionEngineResult TransactionEngine::takeOffers( const uint160 uTakerPaysCurrency = saTakerPays.getCurrency(); const uint160 uTakerGetsAccountID = saTakerGets.getIssuer(); const uint160 uTakerGetsCurrency = saTakerGets.getCurrency(); - TransactionEngineResult terResult = temUNKNOWN; + TransactionEngineResult terResult = temUNCERTAIN; + + boost::unordered_set usOfferUnfundedFound; // Offers found unfunded. + boost::unordered_set usOfferUnfundedBecame; // Offers that became unfunded. + boost::unordered_set usAccountTouched; // Accounts touched. saTakerPaid = 0; saTakerGot = 0; - while (temUNKNOWN == terResult) + while (temUNCERTAIN == terResult) { SLE::pointer sleOfferDir; uint64 uTipQuality; @@ -3988,7 +4040,9 @@ TransactionEngineResult TransactionEngine::takeOffers( } } - if (!sleOfferDir || uTakeQuality < uTipQuality || (bPassive && uTakeQuality == uTipQuality)) + if (!sleOfferDir // No offer directory to take. + || uTakeQuality < uTipQuality // No offer's of sufficient quality available. + || (bPassive && uTakeQuality == uTipQuality)) { // Done. Log(lsINFO) << "takeOffers: done"; @@ -3997,7 +4051,7 @@ TransactionEngineResult TransactionEngine::takeOffers( } else { - // Have an offer to consider. + // Have an offer directory to consider. Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0); SLE::pointer sleBookNode; @@ -4016,19 +4070,17 @@ TransactionEngineResult TransactionEngine::takeOffers( if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) { - // Offer is expired. Delete it. + // Offer is expired. Expired offers are considered unfunded. Delete it. Log(lsINFO) << "takeOffers: encountered expired offer"; - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); - - mUnfunded.insert(uOfferIndex); + usOfferUnfundedFound.insert(uOfferIndex); } else if (uOfferOwnerID == uTakerAccountID) { - // Would take own offer. Consider old offer unfunded. + // Would take own offer. Consider old offer expired. Delete it. Log(lsINFO) << "takeOffers: encountered taker's own old offer"; - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); + usOfferUnfundedFound.insert(uOfferIndex); } else { @@ -4045,9 +4097,17 @@ TransactionEngineResult TransactionEngine::takeOffers( // Offer is unfunded, possibly due to previous balance action. Log(lsINFO) << "takeOffers: offer unfunded: delete"; - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); - - mUnfunded.insert(uOfferIndex); + boost::unordered_set::iterator account = usAccountTouched.find(uOfferOwnerID); + if (account != usAccountTouched.end()) + { + // Previously touched account. + usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. + } + else + { + // Never touched source account. + usOfferUnfundedFound.insert(uOfferIndex); // Delete found unfunded offer when possible. + } } else { @@ -4081,24 +4141,28 @@ TransactionEngineResult TransactionEngine::takeOffers( Log(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText(); // Adjust offer + + // Offer owner will pay less. Subtract what taker just got. + sleOffer->setIFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); + + // Offer owner will get less. Subtract what owner just paid. + sleOffer->setIFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); + + entryModify(sleOffer); + if (bOfferDelete) { // Offer now fully claimed or now unfunded. Log(lsINFO) << "takeOffers: offer claimed: delete"; - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); + usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. + + // Offer owner's account is no longer pristine. + usAccountTouched.insert(uOfferOwnerID); } else { - Log(lsINFO) << "takeOffers: offer partial claim: modify"; - - // Offer owner will pay less. Subtract what taker just got. - sleOffer->setIFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); - - // Offer owner will get less. Subtract what owner just paid. - sleOffer->setIFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); - - entryModify(sleOffer); + Log(lsINFO) << "takeOffers: offer partial claim."; } // Offer owner pays taker. @@ -4119,6 +4183,40 @@ TransactionEngineResult TransactionEngine::takeOffers( } } + // On storing meta data, delete offers that were found unfunded to prevent encountering them in future. + switch (terResult) + { + case tesSUCCESS: + case tepPATH_DRY: + case tepPATH_PARTIAL: + BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedFound) + { + TransactionEngineResult terDelete = offerDelete(uOfferIndex); + + if (tesSUCCESS != terDelete) + terResult = terDelete; + break; + } + break; + + default: + nothing(); + break; + } + + if (tesSUCCESS == terResult) + { + // On success, delete offers that became unfunded. + BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) + { + TransactionEngineResult terDelete = offerDelete(uOfferIndex); + + if (tesSUCCESS != terDelete) + terResult = terDelete; + break; + } + } + return terResult; } diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index 990bc8061d..f8b7f34ed1 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -40,6 +40,7 @@ enum TransactionEngineResult temINVALID, temREDUNDANT, temRIPPLE_EMPTY, + temUNCERTAIN, temUNKNOWN, // -199 .. -100: F Failure (sequence number previously used) @@ -77,12 +78,9 @@ enum TransactionEngineResult // 100 .. P Partial success (SR) (ripple transaction with no good paths, pay to non-existent account) // Transaction can be applied, can charge fee, forwarded, but does not achieve optimal result. - tesPARITAL = 100, - - // Might succeed in different order. - // XXX claim fee and try to delete unfunded. - terPATH_EMPTY, - terPATH_PARTIAL, + tepPARITAL = 100, + tepPATH_DRY, + tepPATH_PARTIAL, }; bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std::string& strHuman); @@ -132,9 +130,6 @@ public: bool bValid; std::vector vpnNodes; - // If the transaction fails to meet some constraint, still need to delete unfunded offers. - boost::unordered_set usUnfundedFound; // Offers that were found unfunded. - // When processing, don't want to complicate directory walking with deletion. std::vector vUnfundedBecame; // Offers that became unfunded. @@ -197,10 +192,11 @@ private: const uint256& uLedgerIndex); TransactionEngineResult dirDelete( - bool bKeepRoot, + const bool bKeepRoot, const uint64& uNodeDir, // Node item is mentioned in. const uint256& uRootIndex, - const uint256& uLedgerIndex); // Item being deleted + const uint256& uLedgerIndex, // Item being deleted + const bool bStable); bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); @@ -224,13 +220,23 @@ protected: uint160 mTxnAccountID; SLE::pointer mTxnAccount; - boost::unordered_set mUnfunded; // Indexes that were found unfunded. + // First time working in reverse a funding source was mentioned. Source may only be used there. + boost::unordered_map, int> mumSource; // Map of currency, issuer to node index. + + // When processing, don't want to complicate directory walking with deletion. + std::vector mvUnfundedBecame; // Offers that became unfunded. + + // If the transaction fails to meet some constraint, still need to delete unfunded offers. + boost::unordered_set musUnfundedFound; // Offers that were found unfunded. SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex); SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex); void entryDelete(SLE::pointer sleEntry, bool bUnfunded = false); void entryModify(SLE::pointer sleEntry); + TransactionEngineResult offerDelete(const uint256& uOfferIndex); + TransactionEngineResult offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); + uint32 rippleTransferRate(const uint160& uIssuerID); STAmount rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); STAmount rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); @@ -260,8 +266,6 @@ protected: void txnWrite(); - TransactionEngineResult offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); - TransactionEngineResult doAccountSet(const SerializedTransaction& txn); TransactionEngineResult doClaim(const SerializedTransaction& txn); TransactionEngineResult doCreditSet(const SerializedTransaction& txn);