diff --git a/src/ripple_app/ledger/LedgerEntrySet.cpp b/src/ripple_app/ledger/LedgerEntrySet.cpp index 39ce900599..dc91b71f5e 100644 --- a/src/ripple_app/ledger/LedgerEntrySet.cpp +++ b/src/ripple_app/ledger/LedgerEntrySet.cpp @@ -1518,6 +1518,7 @@ TER LedgerEntrySet::rippleCredit (const uint160& uSenderID, const uint160& uRece if (saBefore.isPositive () // Sender balance was positive. && !saBalance.isPositive () // Sender is zero or negative. && isSetBit ((uFlags = sleRippleState->getFieldU32 (sfFlags)), !bSenderHigh ? lsfLowReserve : lsfHighReserve) // Sender reserve is set. + && !isSetBit (uFlags, !bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple) && !sleRippleState->getFieldAmount (!bSenderHigh ? sfLowLimit : sfHighLimit) // Sender trust limit is 0. && !sleRippleState->getFieldU32 (!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) // Sender quality in is 0. && !sleRippleState->getFieldU32 (!bSenderHigh ? sfLowQualityOut : sfHighQualityOut)) // Sender quality out is 0. diff --git a/src/ripple_app/paths/PathState.cpp b/src/ripple_app/paths/PathState.cpp index 49a38e3a34..192d42d15d 100644 --- a/src/ripple_app/paths/PathState.cpp +++ b/src/ripple_app/paths/PathState.cpp @@ -316,13 +316,6 @@ TER PathState::pushNode ( terResult = terNO_AUTH; } - else if (isSetBit (sleRippleState->getFieldU32 (sfFlags), bHigh ? lsfHighNoRipple : lsfLowNoRipple) && - (vpnNodes.size() > 1)) - { // If the link leaves the side that set no ripple, it must be the first link - WriteLog (lsWARNING, RippleCalc) << "pushNode: illegal use of noRipple link"; - - terResult = terNO_AUTH; - } if (tesSUCCESS == terResult) { @@ -431,7 +424,7 @@ void PathState::setExpanded ( ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uSenderID, - uMaxCurrencyID, // Max specifes the currency. + uMaxCurrencyID, // Max specifies the currency. uSenderIssuerID); WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: pushed: account=%s currency=%s issuer=%s") @@ -773,6 +766,133 @@ void PathState::setCanonical ( % getJson ()); } +/** Check if a sequence of three accounts violates the no ripple constrains + [first] -> [second] -> [third] + Disallowed if 'second' set no ripple on [first]->[second] and [second]->[third] +*/ +void PathState::checkNoRipple ( + uint160 const& firstAccount, + uint160 const& secondAccount, // This is the account whose constraints we are checking + uint160 const& thirdAccount, + uint160 const& currency) +{ + // fetch the ripple lines into and out of this node + SLE::pointer sleIn = lesEntries.entryCache (ltRIPPLE_STATE, + Ledger::getRippleStateIndex (firstAccount, secondAccount, currency)); + SLE::pointer sleOut = lesEntries.entryCache (ltRIPPLE_STATE, + Ledger::getRippleStateIndex (secondAccount, thirdAccount, currency)); + + if (!sleIn || !sleOut) + { + terStatus = terNO_LINE; + } + else if ( + isSetBit (sleIn->getFieldU32 (sfFlags), + (secondAccount > firstAccount) ? lsfHighNoRipple : lsfLowNoRipple) && + isSetBit (sleOut->getFieldU32 (sfFlags), + (secondAccount > thirdAccount) ? lsfHighNoRipple : lsfLowNoRipple)) + { + WriteLog (lsINFO, RippleCalc) << "Path violates noRipple constraint between " << + RippleAddress::createHumanAccountID (firstAccount) << ", " << + RippleAddress::createHumanAccountID (secondAccount) << " and " << + RippleAddress::createHumanAccountID (thirdAccount); + + terStatus = terNO_RIPPLE; + } +} + +// Check a fully-expanded path to make sure it doesn't violate no-Ripple settings +void PathState::checkNoRipple (uint160 const& uDstAccountID, uint160 const& uSrcAccountID) +{ + + // There must be at least one node for there to be two consecutive ripple lines + if (vpnNodes.size() == 0) + return; + + if (vpnNodes.size() == 1) + { + // There's just one link in the path + // We only need to check source-node-dest + if (isSetBit (vpnNodes[0].uFlags, STPathElement::typeAccount) && + (vpnNodes[0].uAccountID != uSrcAccountID) && + (vpnNodes[0].uAccountID != uDstAccountID)) + { + if (saInReq.getCurrency() != saOutReq.getCurrency()) + terStatus = terNO_LINE; + else + checkNoRipple (uSrcAccountID, vpnNodes[0].uAccountID, uDstAccountID, + vpnNodes[0].uCurrencyID); + } + return; + } + + // Check source <-> first <-> second + if (isSetBit (vpnNodes[0].uFlags, STPathElement::typeAccount) && + isSetBit (vpnNodes[1].uFlags, STPathElement::typeAccount) && + (vpnNodes[0].uAccountID != uSrcAccountID)) + { + if ((vpnNodes[0].uCurrencyID != vpnNodes[1].uCurrencyID)) + { + terStatus = terNO_LINE; + return; + } + else + { + checkNoRipple (uSrcAccountID, vpnNodes[0].uAccountID, vpnNodes[1].uAccountID, + vpnNodes[0].uCurrencyID); + if (tesSUCCESS != terStatus) + return; + } + } + + // Check second_from_last <-> last <-> destination + size_t s = vpnNodes.size() - 2; + if (isSetBit (vpnNodes[s].uFlags, STPathElement::typeAccount) && + isSetBit (vpnNodes[s+1].uFlags, STPathElement::typeAccount) && + (uDstAccountID != vpnNodes[s+1].uAccountID)) + { + if ((vpnNodes[s].uCurrencyID != vpnNodes[s+1].uCurrencyID)) + { + terStatus = terNO_LINE; + return; + } + else + { + checkNoRipple (vpnNodes[s].uAccountID, vpnNodes[s+1].uAccountID, uDstAccountID, + vpnNodes[s].uCurrencyID); + if (tesSUCCESS != terStatus) + return; + } + } + + + // Loop through all nodes that have a prior node and successor nodes + // These are the nodes whose no ripple constratints could be violated + for (int i = 1; i < (vpnNodes.size() - 1); ++i) + { + + if (isSetBit (vpnNodes[i-1].uFlags, STPathElement::typeAccount) && + isSetBit (vpnNodes[i].uFlags, STPathElement::typeAccount) && + isSetBit (vpnNodes[i+1].uFlags, STPathElement::typeAccount)) + { // two consecutive account-to-account links + + uint160 const& currencyID = vpnNodes[i].uCurrencyID; + if ((vpnNodes[i-1].uCurrencyID != currencyID) || + (vpnNodes[i+1].uCurrencyID != currencyID)) + { + terStatus = temBAD_PATH; + return; + } + checkNoRipple ( + vpnNodes[i-1].uAccountID, vpnNodes[i].uAccountID, vpnNodes[i+1].uAccountID, + currencyID); + if (terStatus != tesSUCCESS) + return; + } + + } +} + // This is for debugging not end users. Output names can be changed without warning. Json::Value PathState::getJson () const { diff --git a/src/ripple_app/paths/PathState.h b/src/ripple_app/paths/PathState.h index 8c8661f54b..1eae7afff3 100644 --- a/src/ripple_app/paths/PathState.h +++ b/src/ripple_app/paths/PathState.h @@ -126,6 +126,9 @@ public: const uint160& uSenderID ); + void checkNoRipple (uint160 const& destinationAccountID, uint160 const& sourceAccountID); + void checkNoRipple (uint160 const&, uint160 const&, uint160 const&, uint160 const&); + void setCanonical ( const PathState& psExpanded ); @@ -147,7 +150,7 @@ public: static bool lessPriority (PathState& lhs, PathState& rhs); public: - TER terStatus; + TER terStatus; std::vector vpnNodes; // When processing, don't want to complicate directory walking with deletion. diff --git a/src/ripple_app/paths/Pathfinder.cpp b/src/ripple_app/paths/Pathfinder.cpp index 85efcd5f46..b322e0dc34 100644 --- a/src/ripple_app/paths/Pathfinder.cpp +++ b/src/ripple_app/paths/Pathfinder.cpp @@ -487,8 +487,8 @@ int Pathfinder::getPathsOut (RippleCurrency const& currencyID, const uint160& ac nothing (); else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ())) count += 10000; // count a path to the destination extra - else if (rspEntry->getNoRipple()) - nothing (); // This isn't a useful path out + else if (rspEntry->getNoRipplePeer ()) + nothing (); // This probably isn't a useful path out else ++count; } @@ -546,10 +546,7 @@ STPathSet& Pathfinder::getPaths(PathType_t const& type, bool addComplete) break; case nt_ACCOUNTS: - if (type.size() == 2) // "sa", so can use noRipple paths - addLink(pathsIn, pathsOut, afADD_ACCOUNTS | afALL_ACCOUNTS); - else - addLink(pathsIn, pathsOut, afADD_ACCOUNTS); + addLink(pathsIn, pathsOut, afADD_ACCOUNTS); break; case nt_BOOKS: @@ -578,6 +575,33 @@ STPathSet& Pathfinder::getPaths(PathType_t const& type, bool addComplete) return pathsOut; } +bool Pathfinder::isNoRipple (const uint160& setByID, const uint160& setOnID, const uint160& currencyID) +{ + SLE::pointer sleRipple = mLedger->getSLEi (Ledger::getRippleStateIndex (setByID, setOnID, currencyID)); + return sleRipple && + isSetBit (sleRipple->getFieldU32 (sfFlags), (setByID > setOnID) ? lsfHighNoRipple : lsfLowNoRipple); +} + +// Does this path end on an account-to-account link whose last account +// has set no ripple on the link? +bool Pathfinder::isNoRippleOut (const STPath& currentPath) +{ + // Must have at least one link + if (currentPath.size() == 0) + return false; + + // Last link must be an account + STPathElement const& endElement = *(currentPath.end() - 1); + if (!isSetBit(endElement.getNodeType(), STPathElement::typeAccount)) + return false; + + // What account are we leaving? + uint160 const& fromAccount = + (currentPath.size() == 1) ? mSrcAccountID : (currentPath.end() - 2)->mAccountID; + + return isNoRipple (endElement.mAccountID, fromAccount, endElement.mCurrencyID); +} + void Pathfinder::addLink( const STPath& currentPath, // The path to build from STPathSet& incompletePaths, // The set of partial paths we add to @@ -604,12 +628,12 @@ void Pathfinder::addLink( } else { // search for accounts to add - bool bAllAccounts = (addFlags & afALL_ACCOUNTS) != 0; SLE::pointer sleEnd = mLedger->getSLEi(Ledger::getAccountRootIndex(uEndAccount)); if (sleEnd) { bool const bRequireAuth = isSetBit(sleEnd->getFieldU32(sfFlags), lsfRequireAuth); bool const bIsEndCurrency = (uEndCurrency == mDstAmount.getCurrency()); + bool const bIsNoRippleOut = isNoRippleOut (currentPath); AccountItems& rippleLines(mRLCache->getRippleLines(uEndAccount)); @@ -631,7 +655,7 @@ void Pathfinder::addLink( { // path has no credit } - else if (!bAllAccounts && rspEntry.getNoRipple()) + else if (bIsNoRippleOut && rspEntry.getNoRipple()) { // Can't leave on this path } diff --git a/src/ripple_app/paths/Pathfinder.h b/src/ripple_app/paths/Pathfinder.h index 2bdec594d1..99030c5c93 100644 --- a/src/ripple_app/paths/Pathfinder.h +++ b/src/ripple_app/paths/Pathfinder.h @@ -103,6 +103,9 @@ private: STPathSet& getPaths(const PathType_t& type, bool addComplete = true); STPathSet filterPaths(int iMaxPaths, STPath& extraPath); + bool isNoRippleOut (const STPath& currentPath); + bool isNoRipple (uint160 const& setByID, uint160 const& setOnID, uint160 const& currencyID); + // Our main table of paths static std::map mPathTable; @@ -131,7 +134,6 @@ private: static const uint32 afOB_XRP = 0x010; // Add order book to XRP only static const uint32 afOB_LAST = 0x040; // Must link to destination currency static const uint32 afAC_LAST = 0x080; // Destination account only - static const uint32 afALL_ACCOUNTS = 0x100; // Include no ripple paths }; boost::unordered_set usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger, diff --git a/src/ripple_app/paths/RippleCalc.cpp b/src/ripple_app/paths/RippleCalc.cpp index 859ad7651c..d60943df1a 100644 --- a/src/ripple_app/paths/RippleCalc.cpp +++ b/src/ripple_app/paths/RippleCalc.cpp @@ -1976,7 +1976,7 @@ void RippleCalc::pathNext (PathState::ref psrCur, const bool bMultiQuality, cons // <-- TER: Only returns tepPATH_PARTIAL if !bPartialPayment. TER RippleCalc::rippleCalc ( // Compute paths vs this ledger entry set. Up to caller to actually apply to ledger. - LedgerEntrySet& lesActive, // <-> --> = Fee already applied to src balance. + LedgerEntrySet& lesActive, // <-> --> = Fee already applied to src balance. STAmount& saMaxAmountAct, // <-- The computed input amount. STAmount& saDstAmountAct, // <-- The computed output amount. std::vector& vpsExpanded, @@ -2033,6 +2033,9 @@ TER RippleCalc::rippleCalc ( pspDirect->setExpanded (lesActive, STPath (), uDstAccountID, uSrcAccountID); + if (tesSUCCESS == pspDirect->terStatus) + pspDirect->checkNoRipple (uDstAccountID, uSrcAccountID); + pspDirect->setIndex (vpsExpanded.size ()); WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: Build direct: status: %s") @@ -2073,6 +2076,9 @@ TER RippleCalc::rippleCalc ( pspExpanded->setExpanded (lesActive, spPath, uDstAccountID, uSrcAccountID); + if (tesSUCCESS == pspExpanded->terStatus) + pspExpanded->checkNoRipple (uDstAccountID, uSrcAccountID); + WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: Build path: %d: status: %s") % ++iIndex % transToken (pspExpanded->terStatus)); diff --git a/src/ripple_app/tx/TrustSetTransactor.cpp b/src/ripple_app/tx/TrustSetTransactor.cpp index 8b7fa4fdae..38f718a165 100644 --- a/src/ripple_app/tx/TrustSetTransactor.cpp +++ b/src/ripple_app/tx/TrustSetTransactor.cpp @@ -217,21 +217,35 @@ TER TrustSetTransactor::doApply () uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut); } + const uint32 uFlagsIn = sleRippleState->getFieldU32 (sfFlags); + uint32 uFlagsOut = uFlagsIn; + + if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance).isGEZero()) + { + uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); + } + else if (bClearNoRipple && !bSetNoRipple) + { + uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); + } + if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; - const bool bLowReserveSet = uLowQualityIn || uLowQualityOut || !!saLowLimit || saLowBalance.isPositive (); + + const bool bLowReserveSet = uLowQualityIn || uLowQualityOut || + isSetBit (uFlagsOut, lsfLowNoRipple) || + !!saLowLimit || saLowBalance.isPositive (); const bool bLowReserveClear = !bLowReserveSet; - const bool bHighReserveSet = uHighQualityIn || uHighQualityOut || !!saHighLimit || saHighBalance.isPositive (); + const bool bHighReserveSet = uHighQualityIn || uHighQualityOut || + isSetBit (uFlagsOut, lsfHighNoRipple) || + !!saHighLimit || saHighBalance.isPositive (); const bool bHighReserveClear = !bHighReserveSet; const bool bDefault = bLowReserveClear && bHighReserveClear; - const uint32 uFlagsIn = sleRippleState->getFieldU32 (sfFlags); - uint32 uFlagsOut = uFlagsIn; - const bool bLowReserved = isSetBit (uFlagsIn, lsfLowReserve); const bool bHighReserved = isSetBit (uFlagsIn, lsfHighReserve); @@ -242,15 +256,6 @@ TER TrustSetTransactor::doApply () uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } - if (bSetNoRipple && !bClearNoRipple) - { - uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); - } - else if (bClearNoRipple && !bSetNoRipple) - { - uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); - } - if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. diff --git a/src/ripple_data/protocol/SerializedTypes.h b/src/ripple_data/protocol/SerializedTypes.h index 41a8f16b99..f31b884776 100644 --- a/src/ripple_data/protocol/SerializedTypes.h +++ b/src/ripple_data/protocol/SerializedTypes.h @@ -592,6 +592,10 @@ public: { return !mIsNegative && !isZero (); } + bool isLEZero () const + { + return mIsNegative || isZero (); + } bool isGEZero () const { return !mIsNegative; diff --git a/src/ripple_data/protocol/TER.cpp b/src/ripple_data/protocol/TER.cpp index e39e1979f5..232260afa8 100644 --- a/src/ripple_data/protocol/TER.cpp +++ b/src/ripple_data/protocol/TER.cpp @@ -108,6 +108,7 @@ bool transResultInfo (TER terCode, std::string& strToken, std::string& strHuman) { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee." }, { terLAST, "terLAST", "Process last." }, + { terNO_RIPPLE, "terNO_RIPPLE", "Path does not permit rippling." }, { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." }, { terNO_AUTH, "terNO_AUTH", "Not authorized to hold IOUs." }, { terNO_LINE, "terNO_LINE", "No such line." }, diff --git a/src/ripple_data/protocol/TER.h b/src/ripple_data/protocol/TER.h index c91be322d8..912a549826 100644 --- a/src/ripple_data/protocol/TER.h +++ b/src/ripple_data/protocol/TER.h @@ -128,6 +128,7 @@ enum TER // aka TransactionEngineResult terOWNERS, // Can't succeed with non-zero owner count. terPRE_SEQ, // Can't pay fee, no point in forwarding, therefore don't burden network. terLAST, // Process after all other transactions + terNO_RIPPLE, // Rippling not allowed // 0: S Success (success) // Causes: