TER computeForwardLiqudity ( RippleCalc& rippleCalc, const unsigned int nodeIndex, PathState& pathState, const bool bMultiQuality) { auto const& node = pathState.nodes()[nodeIndex]; WriteLog (lsTRACE, RippleCalc) << "computeForwardLiqudity> nodeIndex=" << nodeIndex; TER resultCode = node.isAccount() ? computeForwardLiquidityForAccount ( rippleCalc, nodeIndex, pathState, bMultiQuality) : computeForwardLiquidityForOffer ( rippleCalc, nodeIndex, pathState, bMultiQuality); if (resultCode == tesSUCCESS && nodeIndex + 1 != pathState.nodes().size ()) resultCode = computeForwardLiqudity (rippleCalc, nodeIndex + 1, pathState, bMultiQuality); if (resultCode == tesSUCCESS && !(pathState.inPass() && pathState.outPass())) resultCode = tecPATH_DRY; WriteLog (lsTRACE, RippleCalc) << "computeForwardLiqudity<" << " nodeIndex:" << nodeIndex << " resultCode:" << resultCode; return resultCode; }
/*! \details No detailed. */ SampledWavelengths WavelengthSampler::sampleRandomly( Sampler& sampler, PathState& path_state) noexcept { using zisc::cast; constexpr uint sample_size = SampledWavelengths::size(); constexpr Float inverse_probability = cast<Float>(CoreConfig::spectraSize()) / cast<Float>(sample_size); std::array<uint16, sample_size> wavelengths; for (uint i = 0; i < sample_size; ++i) { const Float offset = sampler.draw1D(path_state); path_state.setDimension(path_state.dimension() + 1); const Float position = cast<Float>(CoreConfig::spectraSize()) * offset; const uint index = cast<uint>(position); const uint16 wavelength = getWavelength(index); wavelengths[i] = wavelength; } std::sort(wavelengths.begin(), wavelengths.end()); SampledWavelengths sampled_wavelengths; for (uint i = 0; i < sample_size; ++i) sampled_wavelengths.set(i, wavelengths[i], inverse_probability); sampled_wavelengths.selectPrimaryWavelength(sampler, path_state); return sampled_wavelengths; }
/*! */ SampledWavelengths WavelengthSampler::sampleRgb( Sampler& sampler, PathState& path_state) noexcept { constexpr uint sample_size = SampledWavelengths::size(); path_state.setDimension(path_state.dimension() + sample_size); SampledWavelengths sampled_wavelengths; sampled_wavelengths.set(0, CoreConfig::blueWavelength(), 1.0); sampled_wavelengths.set(1, CoreConfig::greenWavelength(), 1.0); sampled_wavelengths.set(2, CoreConfig::redWavelength(), 1.0); sampled_wavelengths.selectPrimaryWavelength(sampler, path_state); return sampled_wavelengths; }
/*! \details No detailed. */ SampledWavelengths WavelengthSampler::sampleRegularly( Sampler& sampler, PathState& path_state) noexcept { using zisc::cast; constexpr uint sample_size = SampledWavelengths::size(); constexpr Float interval = cast<Float>(CoreConfig::spectraSize()) / cast<Float>(sample_size); constexpr Float inverse_probability = interval; SampledWavelengths sampled_wavelengths; const Float offset = sampler.draw1D(path_state); for (uint i = 0; i < sample_size; ++i) { path_state.setDimension(path_state.dimension() + 1); const uint index = cast<uint>(interval * (cast<Float>(i) + offset)); const uint16 wavelength = getWavelength(index); sampled_wavelengths.set(i, wavelength, inverse_probability); } sampled_wavelengths.selectPrimaryWavelength(sampler, path_state); return sampled_wavelengths; }
TER computeReverseLiqudity ( RippleCalc& rippleCalc, const unsigned int nodeIndex, PathState& pathState, const bool bMultiQuality) { auto& node = pathState.nodes()[nodeIndex]; // Every account has a transfer rate for its issuances. // TOMOVE: The account charges // a fee when third parties transfer that account's own issuances. // node.transferRate_ caches the output transfer rate for this node. node.transferRate_ = STAmount::saFromRate ( rippleCalc.mActiveLedger.rippleTransferRate (node.issuer_)); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiqudity>" << " nodeIndex=" << nodeIndex << " issuer_=" << RippleAddress::createHumanAccountID (node.issuer_) << " transferRate_=" << node.transferRate_; auto resultCode = node.isAccount() ? computeReverseLiquidityForAccount ( rippleCalc, nodeIndex, pathState, bMultiQuality) : computeReverseLiquidityForOffer ( rippleCalc, nodeIndex, pathState, bMultiQuality); // Do previous. if (resultCode == tesSUCCESS && nodeIndex) { // Continue in reverse. TODO(tom): remove unnecessary recursion. resultCode = computeReverseLiqudity ( rippleCalc, nodeIndex - 1, pathState, bMultiQuality); } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiqudity< " << "nodeIndex=" << nodeIndex << " resultCode=%s" << transToken (resultCode) << "/" << resultCode; return resultCode; }
TER computeReverseLiquidityForAccount ( RippleCalc& rippleCalc, const unsigned int nodeIndex, PathState& pathState, const bool bMultiQuality) { TER terResult = tesSUCCESS; auto const lastNodeIndex = pathState.nodes().size () - 1; auto const isFinalNode = (nodeIndex == lastNodeIndex); // 0 quality means none has yet been determined. std::uint64_t uRateMax = 0; auto& previousNode = pathState.nodes()[nodeIndex ? nodeIndex - 1 : 0]; auto& node = pathState.nodes()[nodeIndex]; auto& nextNode = pathState.nodes()[isFinalNode ? lastNodeIndex : nodeIndex + 1]; // Current is allowed to redeem to next. const bool previousNodeIsAccount = !nodeIndex || previousNode.isAccount(); const bool nextNodeIsAccount = isFinalNode || nextNode.isAccount(); Account const& previousAccountID = previousNodeIsAccount ? previousNode.account_ : node.account_; Account const& nextAccountID = nextNodeIsAccount ? nextNode.account_ : node.account_; // Offers are always issue. // This is the quality from from the previous node to this one. const std::uint32_t uQualityIn = (nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleQualityIn ( node.account_, previousAccountID, node.currency_) : QUALITY_ONE; // And this is the quality from the next one to this one. const std::uint32_t uQualityOut = (nodeIndex != lastNodeIndex) ? rippleCalc.mActiveLedger.rippleQualityOut ( node.account_, nextAccountID, node.currency_) : QUALITY_ONE; // For previousNodeIsAccount: // Previous account is already owed. const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleOwed ( node.account_, previousAccountID, node.currency_) : STAmount ({node.currency_, node.account_}); // The limit amount that the previous account may owe. const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleLimit ( node.account_, previousAccountID, node.currency_) : STAmount ({node.currency_, node.account_}); // Next account is owed. const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex != lastNodeIndex) ? rippleCalc.mActiveLedger.rippleOwed ( node.account_, nextAccountID, node.currency_) : STAmount ({node.currency_, node.account_}); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount>" << " nodeIndex=%d/%d" << nodeIndex << "/" << lastNodeIndex << " previousAccountID=" << previousAccountID << " node.account_=" << node.account_ << " nextAccountID=" << nextAccountID << " currency_=" << node.currency_ << " uQualityIn=" << uQualityIn << " uQualityOut=" << uQualityOut << " saPrvOwed=" << saPrvOwed << " saPrvLimit=" << saPrvLimit; // Requests are computed to be the maximum flow possible. // Previous can redeem the owed IOUs it holds. const STAmount saPrvRedeemReq = (saPrvOwed > zero) ? saPrvOwed : STAmount (saPrvOwed.issue ()); // This is the amount we're actually going to be setting for the previous // node. STAmount& saPrvRedeemAct = previousNode.saRevRedeem; // Previous can issue up to limit minus whatever portion of limit already // used (not including redeemable amount) - another "maximum flow". const STAmount saPrvIssueReq = (saPrvOwed < zero) ? saPrvLimit + saPrvOwed : saPrvLimit; STAmount& saPrvIssueAct = previousNode.saRevIssue; // Precompute these values in case we have an order book. auto deliverCurrency = previousNode.saRevDeliver.getCurrency (); const STAmount saPrvDeliverReq ( {deliverCurrency, previousNode.saRevDeliver.getIssuer ()}, -1); // Unlimited delivery. STAmount& saPrvDeliverAct = previousNode.saRevDeliver; // For nextNodeIsAccount const STAmount& saCurRedeemReq = node.saRevRedeem; // Set to zero, because we're trying to hit the previous node. auto saCurRedeemAct = saCurRedeemReq.zeroed(); const STAmount& saCurIssueReq = node.saRevIssue; // Track the amount we actually redeem. auto saCurIssueAct = saCurIssueReq.zeroed(); // For !nextNodeIsAccount const STAmount& saCurDeliverReq = node.saRevDeliver; auto saCurDeliverAct = saCurDeliverReq.zeroed(); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saPrvRedeemReq:" << saPrvRedeemReq << " saPrvIssueReq:" << saPrvIssueReq << " saPrvDeliverAct:" << saPrvDeliverAct << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq << " saNxtOwed:" << saNxtOwed; WriteLog (lsTRACE, RippleCalc) << pathState.getJson (); // Current redeem req can't be more than IOUs on hand. assert (!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); assert (!saCurIssueReq // If not issuing, fine. || saNxtOwed >= zero // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: // Sender holding next IOUs. || -saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. if (nodeIndex == 0) { // ^ --> ACCOUNT --> account|offer // Nothing to do, there is no previous to adjust. // // TODO(tom): we could have skipped all that setup and just left // or even just never call this whole routine on nodeIndex = 0! } // The next four cases correspond to the table at the bottom of this Wiki // page section: https://ripple.com/wiki/Transit_Fees#Implementation else if (previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // account --> ACCOUNT --> $ // Overall deliverable. const STAmount saCurWantedReq = std::min ( pathState.outReq() - pathState.outAct(), saPrvLimit + saPrvOwed); auto saCurWantedAct = saCurWantedReq.zeroed (); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: account --> ACCOUNT --> $ :" << " saCurWantedReq=" << saCurWantedReq; // Calculate redeem if (saPrvRedeemReq) // Previous has IOUs to redeem. { // Redeem your own IOUs at 1:1 saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq); saPrvRedeemAct = saCurWantedAct; uRateMax = STAmount::uRateOne; WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: Redeem at 1:1" << " saPrvRedeemReq=" << saPrvRedeemReq << " (available) saPrvRedeemAct=" << saPrvRedeemAct << " uRateMax=" << STAmount::saFromRate (uRateMax).getText (); } else { saPrvRedeemAct.clear (saPrvRedeemReq); } // Calculate issuing. saPrvIssueAct.clear (saPrvIssueReq); if (saCurWantedReq != saCurWantedAct // Need more. && saPrvIssueReq) // Will accept IOUs from previous. { // Rate: quality in : 1.0 // If we previously redeemed and this has a poorer rate, this // won't be included the current increment. computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: Issuing: Rate: quality in : 1.0" << " saPrvIssueAct:" << saPrvIssueAct << " saCurWantedAct:" << saCurWantedAct; } if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } } else { // Not final node. // account --> ACCOUNT --> account saPrvRedeemAct.clear (saPrvRedeemReq); saPrvIssueAct.clear (saPrvIssueReq); // redeem (part 1) -> redeem if (saCurRedeemReq // Next wants IOUs redeemed from current account. && saPrvRedeemReq) // Previous has IOUs to redeem to the current account. { // TODO(tom): add English. // Rate : 1.0 : quality out - we must accept our own IOUs as 1:1. computeRippleLiquidity ( rippleCalc, QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate : 1.0 : quality out" << " saPrvRedeemAct:" << saPrvRedeemAct << " saCurRedeemAct:" << saCurRedeemAct; } // issue (part 1) -> redeem if (saCurRedeemReq != saCurRedeemAct // The current node has more IOUs to redeem. && saPrvRedeemAct == saPrvRedeemReq) // The previous node has no IOUs to redeem remaining, so issues. { // Rate: quality in : quality out computeRippleLiquidity ( rippleCalc, uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate: quality in : quality out:" << " saPrvIssueAct:" << saPrvIssueAct << " saCurRedeemAct:" << saCurRedeemAct; } // redeem (part 2) -> issue. if (saCurIssueReq // Next wants IOUs issued. // TODO(tom): this condition seems redundant. && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. && saPrvRedeemAct != saPrvRedeemReq) // Did not complete redeeming previous IOUs. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); WriteLog (lsDEBUG, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate : 1.0 : transfer_rate:" << " saPrvRedeemAct:" << saPrvRedeemAct << " saCurIssueAct:" << saCurIssueAct; } // issue (part 2) -> issue if (saCurIssueReq != saCurIssueAct // Need wants more IOUs issued. && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. && saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed IOUs. && saPrvIssueReq) // Previous can issue. { // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate: quality in : 1.0:" << " saPrvIssueAct:" << saPrvIssueAct << " saCurIssueAct:" << saCurIssueAct; } if (!saCurRedeemAct && !saCurIssueAct) { // Did not make progress. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "^|account --> ACCOUNT --> account :" << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq << " saPrvOwed:" << saPrvOwed << " saCurRedeemAct:" << saCurRedeemAct << " saCurIssueAct:" << saCurIssueAct; } } else if (previousNodeIsAccount && !nextNodeIsAccount) { // account --> ACCOUNT --> offer // Note: deliver is always issue as ACCOUNT is the issuer for the offer // input. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "account --> ACCOUNT --> offer"; saPrvRedeemAct.clear (saPrvRedeemReq); saPrvIssueAct.clear (saPrvIssueReq); // We have three cases: the nxt offer can be owned by current account, // previous account or some third party account. // // Also, the current account may or may not have a redeemable balance // with the account for the next offer, so we don't yet know if we're // redeeming or issuing. // // TODO(tom): Make sure deliver was cleared, or check actual is zero. // redeem -> deliver/issue. if (saPrvOwed > zero // Previous has IOUs to redeem. && saCurDeliverReq) // Need some issued. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); } // issue -> deliver/issue if (saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed. && saCurDeliverReq != saCurDeliverAct) // Still need some issued. { // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); } if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << " saCurDeliverReq:" << saCurDeliverReq << " saCurDeliverAct:" << saCurDeliverAct << " saPrvOwed:" << saPrvOwed; } else if (!previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // offer --> ACCOUNT --> $ // Previous is an offer, no limit: redeem own IOUs. // // This is the final node; we can't look to the right to get values; // we have to go up to get the out value for the entire path state. const STAmount& saCurWantedReq = pathState.outReq() - pathState.outAct(); STAmount saCurWantedAct = saCurWantedReq.zeroed(); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "offer --> ACCOUNT --> $ :" << " saCurWantedReq:" << saCurWantedReq << " saOutAct:" << pathState.outAct() << " saOutReq:" << pathState.outReq(); if (saCurWantedReq <= zero) { // TEMPORARY emergency fix // // TODO(tom): why can't saCurWantedReq be -1 if you want to // compute maximum liquidity? This might be unimplemented // functionality. TODO(tom): should the same check appear in // other paths or even be pulled up? WriteLog (lsFATAL, RippleCalc) << "CurWantReq was not positive"; return tefEXCEPTION; } assert (saCurWantedReq > zero); // FIXME: We got one of these // The previous node is an offer; we are receiving our own currency; // The previous order book's entries might hold our issuances; might // not hold our issuances; might be our own offer. // // Assume the worst case, the case which costs the most to go // through, which is that it is not our own offer or our own // issuances. Later on the forward pass we may be able to do // better. // // TODO: this comment applies generally to this section - move it up // to a document. // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saPrvDeliverAct:" << saPrvDeliverAct << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurWantedAct:" << saCurWantedAct << " saCurWantedReq:" << saCurWantedReq; } else { // offer --> ACCOUNT --> account // Note: offer is always delivering(redeeming) as account is issuer. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "offer --> ACCOUNT --> account :" << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq; // deliver -> redeem // TODO(tom): now we have more checking in nodeRipple, these checks // might be redundant. if (saCurRedeemReq) // Next wants us to redeem. { // cur holds IOUs from the account to the right, the nxt // account. If someone is making the current account get rid of // the nxt account's IOUs, then charge the input for quality out. // // Rate : 1.0 : quality out computeRippleLiquidity ( rippleCalc, QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); } // deliver -> issue. if (saCurRedeemReq == saCurRedeemAct // Can only issue if previously redeemed all. && saCurIssueReq) // Need some issued. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saCurRedeemAct:" << saCurRedeemAct << " saCurRedeemReq:" << saCurRedeemReq << " saPrvDeliverAct:" << saPrvDeliverAct << " saCurIssueReq:" << saCurIssueReq; if (!saPrvDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: offer --> ACCOUNT --> offer"; // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } return terResult; }
void pathNext ( RippleCalc& rippleCalc, PathState& pathState, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) { // The next state is what is available in preference order. // This is calculated when referenced accounts changed. pathState.clear(); WriteLog (lsTRACE, RippleCalc) << "pathNext: Path In: " << pathState.getJson (); assert (pathState.nodes().size () >= 2); lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint. for (unsigned int uIndex = pathState.nodes().size (); uIndex--;) { auto& node = pathState.nodes()[uIndex]; node.saRevRedeem.clear (); node.saRevIssue.clear (); node.saRevDeliver.clear (); node.saFwdDeliver.clear (); } pathState.setStatus(computeReverseLiquidity ( rippleCalc, pathState, bMultiQuality)); WriteLog (lsTRACE, RippleCalc) << "pathNext: Path after reverse: " << pathState.getJson (); if (tesSUCCESS == pathState.status()) { // Do forward. lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint. pathState.setStatus(computeForwardLiquidity ( rippleCalc, pathState, bMultiQuality)); } if (tesSUCCESS == pathState.status()) { CondLog (!pathState.inPass() || !pathState.outPass(), lsDEBUG, RippleCalc) << "pathNext: Error computeForwardLiquidity reported success for nothing:" << " saOutPass="******" inPass()=" << pathState.inPass(); if (!pathState.outPass() || !pathState.inPass()) throw std::runtime_error ("Made no progress."); // Calculate relative quality. pathState.setQuality(STAmount::getRate ( pathState.outPass(), pathState.inPass())); WriteLog (lsTRACE, RippleCalc) << "pathNext: Path after forward: " << pathState.getJson (); } else { pathState.setQuality(0); } }
// OPTIMIZE: When calculating path increment, note if increment consumes all // liquidity. No need to revisit path in the future if all liquidity is used. // // nodeAdvance advances through offers in an order book. // If needed, advance to next funded offer. // - Automatically advances to first offer. // --> bEntryAdvance: true, to advance to next entry. false, recalculate. // <-- uOfferIndex : 0=end of list. TER nodeAdvance ( RippleCalc& rippleCalc, const unsigned int nodeIndex, PathState& pathState, const bool bMultiQuality, const bool bReverse) { auto& previousNode = pathState.nodes()[nodeIndex - 1]; auto& node = pathState.nodes()[nodeIndex]; TER resultCode = tesSUCCESS; // Taker is the active party against an offer in the ledger - the entity // that is taking advantage of an offer in the order book. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: TakerPays:" << node.saTakerPays << " TakerGets:" << node.saTakerGets; int loopCount = 0; do { // VFALCO NOTE Why not use a for() loop? // VFALCO TODO The limit on loop iterations puts an // upper limit on the number of different quality // levels (ratio of pay:get) that will be considered for one path. // Changing this value has repercusssions on validation and consensus. // if (++loopCount > NODE_ADVANCE_MAX_LOOPS) { WriteLog (lsWARNING, RippleCalc) << "Loop count exceeded"; return tefEXCEPTION; } bool bDirectDirDirty = false; if (!node.currentDirectory_) { // Need to initialize current node. node.currentDirectory_.copyFrom(Ledger::getBookBase ({ {previousNode.currency_, previousNode.issuer_}, {node.currency_, node.issuer_} })); node.nextDirectory_.copyFrom( Ledger::getQualityNext (node.currentDirectory_)); // TODO(tom): it seems impossible that any actual offers with // quality == 0 could occur - we should disallow them, and clear // sleDirectDir without the database call in the next line. node.sleDirectDir = rippleCalc.mActiveLedger.entryCache ( ltDIR_NODE, node.currentDirectory_); // Associated vars are dirty, if found it. bDirectDirDirty = !!node.sleDirectDir; // Advance, if didn't find it. Normal not to be unable to lookup // firstdirectory. Maybe even skip this lookup. node.bDirectAdvance = !node.sleDirectDir; node.bDirectRestart = false; WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: Initialize node:" << " node.currentDirectory_=" << node.currentDirectory_ <<" node.nextDirectory_=" << node.nextDirectory_ << " node.bDirectAdvance=" << node.bDirectAdvance; } if (node.bDirectAdvance || node.bDirectRestart) { // Get next quality. if (node.bDirectAdvance) { // This works because the Merkel radix tree is ordered by key so // we can go to the next one in O(1). node.currentDirectory_ = rippleCalc.mActiveLedger.getNextLedgerIndex ( node.currentDirectory_, node.nextDirectory_); } bDirectDirDirty = true; node.bDirectAdvance = false; node.bDirectRestart = false; if (node.currentDirectory_ != zero) { // We didn't run off the end of this order book and found // another quality directory. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: Quality advance: node.currentDirectory_=" << node.currentDirectory_; node.sleDirectDir = rippleCalc.mActiveLedger.entryCache (ltDIR_NODE, node.currentDirectory_); } else if (bReverse) { WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: No more offers."; node.offerIndex_ = 0; break; } else { // No more offers. Should be done rather than fall off end of // book. WriteLog (lsWARNING, RippleCalc) << "nodeAdvance: Unreachable: " << "Fell off end of order book."; // FIXME: why? return rippleCalc.mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; } } if (bDirectDirDirty) { // Our quality changed since last iteration. // Use the rate from the directory. node.saOfrRate = STAmount::setRate (Ledger::getQuality (node.currentDirectory_)); // For correct ratio node.uEntry = 0; node.bEntryAdvance = true; WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: directory dirty: node.saOfrRate=" << node.saOfrRate; } if (!node.bEntryAdvance) { if (node.bFundsDirty) { // We were called again probably merely to update structure // variables. node.saTakerPays = node.sleOffer->getFieldAmount (sfTakerPays); node.saTakerGets = node.sleOffer->getFieldAmount (sfTakerGets); // Funds left. node.saOfferFunds = rippleCalc.mActiveLedger.accountFunds ( node.offerOwnerAccount_, node.saTakerGets); node.bFundsDirty = false; WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: funds dirty: node.saOfrRate=" << node.saOfrRate; } else { WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: as is"; } } else if (!rippleCalc.mActiveLedger.dirNext ( node.currentDirectory_, node.sleDirectDir, node.uEntry, node.offerIndex_)) // This is the only place that offerIndex_ changes. { // Failed to find an entry in directory. // Do another cur directory iff bMultiQuality if (bMultiQuality) { // We are allowed to process multiple qualities if this is the // only path. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: next quality"; node.bDirectAdvance = true; // Process next quality. } else if (!bReverse) { // We didn't run dry going backwards - why are we running dry // going forwards - this should be impossible! // TODO(tom): these warnings occur in production! They // shouldn't. WriteLog (lsWARNING, RippleCalc) << "nodeAdvance: unreachable: ran out of offers"; return rippleCalc.mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; } else { // Ran off end of offers. node.bEntryAdvance = false; // Done. node.offerIndex_ = 0; // Report no more entries. } } else { // Got a new offer. node.sleOffer = rippleCalc.mActiveLedger.entryCache ( ltOFFER, node.offerIndex_); if (!node.sleOffer) { // Corrupt directory that points to an entry that doesn't exist. // This has happened in production. WriteLog (lsWARNING, RippleCalc) << "Missing offer in directory"; node.bEntryAdvance = true; } else { node.offerOwnerAccount_ = node.sleOffer->getFieldAccount160 (sfAccount); node.saTakerPays = node.sleOffer->getFieldAmount (sfTakerPays); node.saTakerGets = node.sleOffer->getFieldAmount (sfTakerGets); const AccountCurrencyIssuer asLine ( node.offerOwnerAccount_, node.currency_, node.issuer_); WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: offerOwnerAccount_=" << to_string (node.offerOwnerAccount_) << " node.saTakerPays=" << node.saTakerPays << " node.saTakerGets=" << node.saTakerGets << " node.offerIndex_=" << node.offerIndex_; if (node.sleOffer->isFieldPresent (sfExpiration) && (node.sleOffer->getFieldU32 (sfExpiration) <= rippleCalc.mActiveLedger.getLedger ()->getParentCloseTimeNC ())) { // Offer is expired. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: expired offer"; rippleCalc.mUnfundedOffers.insert(node.offerIndex_); continue; } if (node.saTakerPays <= zero || node.saTakerGets <= zero) { // Offer has bad amounts. Offers should never have a bad // amounts. if (bReverse) { // Past internal error, offer had bad amounts. // This has occurred in production. WriteLog (lsWARNING, RippleCalc) << "nodeAdvance: PAST INTERNAL ERROR" << " REVERSE: OFFER NON-POSITIVE:" << " node.saTakerPays=" << node.saTakerPays << " node.saTakerGets=%s" << node.saTakerGets; // Mark offer for always deletion. rippleCalc.mUnfundedOffers.insert (node.offerIndex_); } else if (rippleCalc.mUnfundedOffers.find (node.offerIndex_) != rippleCalc.mUnfundedOffers.end ()) { // Past internal error, offer was found failed to place // this in mUnfundedOffers. // Just skip it. It will be deleted. WriteLog (lsDEBUG, RippleCalc) << "nodeAdvance: PAST INTERNAL ERROR " << " FORWARD CONFIRM: OFFER NON-POSITIVE:" << " node.saTakerPays=" << node.saTakerPays << " node.saTakerGets=" << node.saTakerGets; } else { // Reverse should have previously put bad offer in list. // An internal error previously left a bad offer. WriteLog (lsWARNING, RippleCalc) << "nodeAdvance: INTERNAL ERROR" <<" FORWARD NEWLY FOUND: OFFER NON-POSITIVE:" << " node.saTakerPays=" << node.saTakerPays << " node.saTakerGets=" << node.saTakerGets; // Don't process at all, things are in an unexpected // state for this transactions. resultCode = tefEXCEPTION; } continue; } // Allowed to access source from this node? // // XXX This can get called multiple times for same source in a // row, caching result would be nice. // // XXX Going forward could we fund something with a worse // quality which was previously skipped? Might need to check // quality. auto itForward = pathState.forward().find (asLine); const bool bFoundForward = itForward != pathState.forward().end (); // Only allow a source to be used once, in the first node // encountered from initial path scan. This prevents // conflicting uses of the same balance when going reverse vs // forward. if (bFoundForward && itForward->second != nodeIndex && node.offerOwnerAccount_ != node.issuer_) { // Temporarily unfunded. Another node uses this source, // ignore in this offer. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: temporarily unfunded offer" << " (forward)"; continue; } // This is overly strict. For contributions to past. We should // only count source if actually used. auto itReverse = pathState.reverse().find (asLine); bool bFoundReverse = itReverse != pathState.reverse().end (); // For this quality increment, only allow a source to be used // from a single node, in the first node encountered from // applying offers in reverse. if (bFoundReverse && itReverse->second != nodeIndex && node.offerOwnerAccount_ != node.issuer_) { // Temporarily unfunded. Another node uses this source, // ignore in this offer. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: temporarily unfunded offer" <<" (reverse)"; continue; } // Determine if used in past. // We only need to know if it might need to be marked unfunded. auto itPast = rippleCalc.mumSource.find (asLine); bool bFoundPast = (itPast != rippleCalc.mumSource.end ()); // Only the current node is allowed to use the source. node.saOfferFunds = rippleCalc.mActiveLedger.accountFunds (node.offerOwnerAccount_, node.saTakerGets); // Funds held. if (node.saOfferFunds <= zero) { // Offer is unfunded. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: unfunded offer"; if (bReverse && !bFoundReverse && !bFoundPast) { // Never mentioned before, clearly just: found unfunded. // That is, even if this offer fails due to fill or kill // still do deletions. // Mark offer for always deletion. rippleCalc.mUnfundedOffers.insert (node.offerIndex_); } else { // Moving forward, don't need to insert again // Or, already found it. } // YYY Could verify offer is correct place for unfundeds. continue; } if (bReverse // Need to remember reverse mention. && !bFoundPast // Not mentioned in previous passes. && !bFoundReverse) // New to pass. { // Consider source mentioned by current path state. WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: remember=" << to_string (node.offerOwnerAccount_) << "/" << to_string (node.currency_) << "/" << to_string (node.issuer_); pathState.reverse().insert (std::make_pair (asLine, nodeIndex)); } node.bFundsDirty = false; node.bEntryAdvance = false; } } } while (resultCode == tesSUCCESS && (node.bEntryAdvance || node.bDirectAdvance)); if (resultCode == tesSUCCESS) { WriteLog (lsTRACE, RippleCalc) << "nodeAdvance: node.offerIndex_=" << node.offerIndex_; } else { WriteLog (lsDEBUG, RippleCalc) << "nodeAdvance: resultCode=" << transToken (resultCode); } return resultCode; }