Work toward ripple_path_find.

This commit is contained in:
Arthur Britto
2012-11-28 19:19:39 -08:00
parent 11d0e93cd8
commit 2268989609
5 changed files with 381 additions and 228 deletions

View File

@@ -115,6 +115,7 @@ TER PaymentTransactor::doApply()
// Ripple payment
STPathSet spsPaths = mTxn.getFieldPathSet(sfPaths);
std::vector<PathState::pointer> vpsExpanded;
STAmount saMaxAmountAct;
STAmount saDstAmountAct;
@@ -124,6 +125,7 @@ TER PaymentTransactor::doApply()
mEngine->getNodes(),
saMaxAmountAct,
saDstAmountAct,
vpsExpanded,
saMaxAmount,
saDstAmount,
uDstAccountID,

View File

@@ -779,13 +779,14 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
return rpcError(rpcINVALID_PARAMS);
}
STPathSet spsPaths;
STPathSet spsComputed;
std::vector<PathState::pointer> vpsExpanded;
Pathfinder pf(raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount);
pf.findPaths(5, 1, spsPaths, true);
pf.findPaths(5, 1, spsComputed, true);
if (spsPaths.isEmpty())
if (spsComputed.isEmpty())
{
cLog(lsDEBUG) << "ripple_path_find: No paths found.";
}
@@ -804,9 +805,9 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
saMaxAmount.negate();
// Strip empty/default path.
if (1 == spsPaths.size() && !spsPaths.begin()->size())
if (1 == spsComputed.size() && !spsComputed.begin()->size())
{
spsPaths.clear();
spsComputed.clear();
}
TER terResult =
@@ -814,11 +815,12 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
lesSnapshot,
saMaxAmountAct,
saDstAmountAct,
vpsExpanded,
saMaxAmount, // --> Amount to send is unlimited to get an estimate.
saDstAmount, // --> Amount to deliver.
raDst.getAccountID(), // --> Account to deliver to.
raSrc.getAccountID(), // --> Account sending from.
spsPaths, // --> Path set.
spsComputed, // --> Path set.
false, // --> Don't allow partial payment. This is for normal fill or kill payments.
// Must achive delivery goal.
false, // --> Don't limit quality. Average quality is wanted for normal payments.
@@ -836,8 +838,13 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
{
Json::Value jvEntry(Json::objectValue);
STPathSet spsCanonical;
RippleCalc::setCanonical(spsCanonical, vpsExpanded);
jvEntry["source_amount"] = saMaxAmountAct.getJson(0);
jvEntry["paths_expanded"] = spsPaths.getJson(0);
// jvEntry["paths_expanded"] = spsExpanded.getJson(0);
jvEntry["paths_canonical"] = spsCanonical.getJson(0);
jvArray.append(jvEntry);
}
@@ -852,7 +859,7 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
<< boost::str(boost::format("ripple_path_find: %s %s %s")
% strToken
% strHuman
% spsPaths.getJson(0));
% spsComputed.getJson(0));
}
}
}
@@ -860,6 +867,10 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest)
jvResult["alternatives"] = jvArray;
}
cLog(lsDEBUG)
<< boost::str(boost::format("ripple_path_find< %s")
% jvResult);
return jvResult;
}

View File

@@ -32,17 +32,17 @@ std::size_t hash_value(const aciSource& asValue)
//
// Return true, iff lhs has less priority than rhs.
bool PathState::lessPriority(PathState::ref lhs, PathState::ref rhs)
bool PathState::lessPriority(PathState& lhs, PathState& rhs)
{
if (lhs->uQuality != rhs->uQuality)
return lhs->uQuality > rhs->uQuality; // Bigger is worse.
if (lhs.uQuality != rhs.uQuality)
return lhs.uQuality > rhs.uQuality; // Bigger is worse.
// Best quanity is second rank.
if (lhs->saOutPass != rhs->saOutPass)
return lhs->saOutPass < rhs->saOutPass; // Smaller is worse.
if (lhs.saOutPass != rhs.saOutPass)
return lhs.saOutPass < rhs.saOutPass; // Smaller is worse.
// Path index is third rank.
return lhs->mIndex > rhs->mIndex; // Bigger is worse.
return lhs.mIndex > rhs.mIndex; // Bigger is worse.
}
// Make sure last path node delivers to uAccountID: uCurrencyID from uIssuerID.
@@ -420,7 +420,7 @@ cLog(lsDEBUG) << boost::str(boost::format("PathState: receiver implied: account=
}
}
cLog(lsINFO) << boost::str(boost::format("PathState: in=%s/%s out=%s/%s %s")
cLog(lsDEBUG) << boost::str(boost::format("PathState: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency(uMaxCurrencyID)
% RippleAddress::createHumanAccountID(uMaxIssuerID)
% STAmount::createHumanCurrency(uOutCurrencyID)
@@ -429,10 +429,159 @@ cLog(lsDEBUG) << boost::str(boost::format("PathState: receiver implied: account=
}
void PathState::setCanonical(
PathState::ref pspExpanded
const PathState& psExpanded
)
{
;
saInAct = psExpanded.saInAct;
saOutAct = psExpanded.saOutAct;
const uint160 uMaxCurrencyID = saInAct.getCurrency();
const uint160 uMaxIssuerID = saInAct.getIssuer();
const uint160 uOutCurrencyID = saOutAct.getCurrency();
const uint160 uOutIssuerID = saOutAct.getIssuer();
unsigned int uNode = 0;
unsigned int uEnd = psExpanded.vpnNodes.size(); // The node, indexed by 0, not to include.
uint160 uDstAccountID = psExpanded.vpnNodes[uEnd].uAccountID;
uint160 uAccountID = psExpanded.vpnNodes[0].uAccountID;
uint160 uCurrencyID = uMaxCurrencyID;
uint160 uIssuerID = uMaxIssuerID;
// Node 0 is a composit of the sending account and saInAct.
++uNode; // skip node 0
// Last node is implied: Always skip last node
--uEnd; // skip last node
// saInAct
// - currency is always the same as vpnNodes[0].
if (uMaxIssuerID != uAccountID);
{
// saInAct issuer is not the sender. This forces an implied node.
// skip node 1
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode;
}
if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID)
{
// The next to last node is saOutAct if an issuer different from receiver is supplied.
// The next to last node can be implied.
--uEnd;
}
const PaymentNode& pnEnd = psExpanded.vpnNodes[uEnd];
if (uNode != uEnd
&& !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID)
{
// The current end node is an offer converting to saOutAct's currency and issuer and can be implied.
--uEnd;
}
// Do not include uEnd.
for (; uNode != uEnd; ++uNode)
{
const PaymentNode& pnPrv = psExpanded.vpnNodes[uNode-1];
const PaymentNode& pnCur = psExpanded.vpnNodes[uNode];
const PaymentNode& pnNxt = psExpanded.vpnNodes[uNode+1];
const bool bPrvAccount = isSetBit(pnPrv.uFlags, STPathElement::typeAccount);
const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = isSetBit(pnNxt.uFlags, STPathElement::typeAccount);
bool bSkip = false;
if (bCurAccount)
{
// Currently at an account.
if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID)
{
// Account issues itself.
bSkip = true;
}
}
else
{
// Currently at an offer.
if (bPrvAccount && bNxtAccount // Offer surrounded by accounts.
&& pnPrv.uCurrencyID != pnNxt.uCurrencyID)
{
// Offer can be implied by currenct change.
bSkip = true;
}
}
if (!bSkip)
{
// Copy node
PaymentNode pnNew;
bool bSetAccount = bCurAccount;
bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID;
bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID;
pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0)
| (bSetCurrency ? STPathElement::typeCurrency : 0)
| (bSetIssuer ? STPathElement::typeIssuer : 0);
if (bSetAccount)
pnNew.uAccountID = pnCur.uAccountID;
if (bSetCurrency)
{
pnNew.uCurrencyID = pnCur.uCurrencyID;
uCurrencyID = pnNew.uCurrencyID;
}
if (bSetIssuer)
pnNew.uIssuerID = pnCur.uIssuerID;
if (bSetCurrency && !uCurrencyID)
uIssuerID.zero();
vpnNodes.push_back(pnNew);
}
}
cLog(lsDEBUG) << boost::str(boost::format("setCanonical: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency(uMaxCurrencyID)
% RippleAddress::createHumanAccountID(uMaxIssuerID)
% STAmount::createHumanCurrency(uOutCurrencyID)
% RippleAddress::createHumanAccountID(uOutIssuerID)
% getJson());
}
void RippleCalc::setCanonical(STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded)
{
BOOST_FOREACH(PathState::ref pspExpanded, vpsExpanded)
{
PathState psCanonical(*pspExpanded, false);
psCanonical.setCanonical(*pspExpanded);
STPath spCanonical;
BOOST_FOREACH(const PaymentNode& pnElem, psCanonical.vpnNodes)
{
STPathElement speElem(pnElem.uFlags, pnElem.uAccountID, pnElem.uCurrencyID, pnElem.uIssuerID);
spCanonical.addElement(speElem);
}
spsDst.addPath(spCanonical);
}
}
Json::Value PathState::getJson() const
@@ -460,22 +609,22 @@ Json::Value PathState::getJson() const
if (!!pnNode.uIssuerID)
jvNode["issuer"] = RippleAddress::createHumanAccountID(pnNode.uIssuerID);
// if (pnNode.saRevRedeem)
if (pnNode.saRevRedeem)
jvNode["rev_redeem"] = pnNode.saRevRedeem.getFullText();
// if (pnNode.saRevIssue)
if (pnNode.saRevIssue)
jvNode["rev_issue"] = pnNode.saRevIssue.getFullText();
// if (pnNode.saRevDeliver)
if (pnNode.saRevDeliver)
jvNode["rev_deliver"] = pnNode.saRevDeliver.getFullText();
// if (pnNode.saFwdRedeem)
if (pnNode.saFwdRedeem)
jvNode["fwd_redeem"] = pnNode.saFwdRedeem.getFullText();
// if (pnNode.saFwdIssue)
if (pnNode.saFwdIssue)
jvNode["fwd_issue"] = pnNode.saFwdIssue.getFullText();
// if (pnNode.saFwdDeliver)
if (pnNode.saFwdDeliver)
jvNode["fwd_deliver"] = pnNode.saFwdDeliver.getFullText();
jvNodes.append(jvNode);
@@ -519,12 +668,12 @@ Json::Value PathState::getJson() const
// <-- uOfferIndex : 0=end of list.
TER RippleCalc::calcNodeAdvance(
const unsigned int uNode, // 0 < uNode < uLast
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality,
const bool bReverse)
{
PaymentNode& pnPrv = pspCur->vpnNodes[uNode-1];
PaymentNode& pnCur = pspCur->vpnNodes[uNode];
PaymentNode& pnPrv = psCur.vpnNodes[uNode-1];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
const uint160& uPrvCurrencyID = pnPrv.uCurrencyID;
const uint160& uPrvIssuerID = pnPrv.uIssuerID;
@@ -668,8 +817,8 @@ TER RippleCalc::calcNodeAdvance(
// 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.
curIssuerNodeConstIterator itForward = pspCur->umForward.find(asLine);
const bool bFoundForward = itForward != pspCur->umForward.end();
curIssuerNodeConstIterator itForward = psCur.umForward.find(asLine);
const bool bFoundForward = itForward != psCur.umForward.end();
if (bFoundForward && itForward->second != uNode)
{
@@ -692,8 +841,8 @@ TER RippleCalc::calcNodeAdvance(
continue;
}
curIssuerNodeConstIterator itReverse = pspCur->umReverse.find(asLine);
bool bFoundReverse = itReverse != pspCur->umReverse.end();
curIssuerNodeConstIterator itReverse = psCur.umReverse.find(asLine);
bool bFoundReverse = itReverse != psCur.umReverse.end();
if (bFoundReverse && itReverse->second != uNode)
{
@@ -735,7 +884,7 @@ TER RippleCalc::calcNodeAdvance(
% STAmount::createHumanCurrency(uCurCurrencyID)
% RippleAddress::createHumanAccountID(uCurIssuerID));
pspCur->umReverse.insert(std::make_pair(asLine, uNode));
psCur.umReverse.insert(std::make_pair(asLine, uNode));
}
bFundsDirty = false;
@@ -761,7 +910,7 @@ TER RippleCalc::calcNodeAdvance(
// till request is satisified while we the rate does not increase past the initial rate.
TER RippleCalc::calcNodeDeliverRev(
const unsigned int uNode, // 0 < uNode < uLast
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality,
const uint160& uOutAccountID, // --> Output owner's account.
const STAmount& saOutReq, // --> Funds wanted.
@@ -769,8 +918,8 @@ TER RippleCalc::calcNodeDeliverRev(
{
TER terResult = tesSUCCESS;
PaymentNode& pnPrv = pspCur->vpnNodes[uNode-1];
PaymentNode& pnCur = pspCur->vpnNodes[uNode];
PaymentNode& pnPrv = psCur.vpnNodes[uNode-1];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
const uint160& uCurIssuerID = pnCur.uIssuerID;
const uint160& uPrvAccountID = pnPrv.uAccountID;
@@ -802,7 +951,7 @@ TER RippleCalc::calcNodeDeliverRev(
STAmount& saTakerGets = pnCur.saTakerGets;
STAmount& saRateMax = pnCur.saRateMax;
terResult = calcNodeAdvance(uNode, pspCur, bMultiQuality, true); // If needed, advance to next funded offer.
terResult = calcNodeAdvance(uNode, psCur, bMultiQuality, true); // If needed, advance to next funded offer.
if (tesSUCCESS != terResult || !uOfferIndex)
{
@@ -908,7 +1057,7 @@ TER RippleCalc::calcNodeDeliverRev(
terResult = calcNodeDeliverRev(
uNode-1,
pspCur,
psCur,
bMultiQuality,
uOfrOwnerID,
saInPassReq,
@@ -969,7 +1118,7 @@ TER RippleCalc::calcNodeDeliverRev(
// <-- pnCur.saFwdDeliver: For calcNodeAccountFwd to know how much went through
TER RippleCalc::calcNodeDeliverFwd(
const unsigned int uNode, // 0 < uNode < uLast
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality,
const uint160& uInAccountID, // --> Input owner's account.
const STAmount& saInReq, // --> Amount to deliver.
@@ -978,9 +1127,9 @@ TER RippleCalc::calcNodeDeliverFwd(
{
TER terResult = tesSUCCESS;
PaymentNode& pnPrv = pspCur->vpnNodes[uNode-1];
PaymentNode& pnCur = pspCur->vpnNodes[uNode];
PaymentNode& pnNxt = pspCur->vpnNodes[uNode+1];
PaymentNode& pnPrv = psCur.vpnNodes[uNode-1];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode+1];
const uint160& uNxtAccountID = pnNxt.uAccountID;
const uint160& uCurCurrencyID = pnCur.uCurrencyID;
@@ -1002,7 +1151,7 @@ TER RippleCalc::calcNodeDeliverFwd(
&& saInAct + saInFees != saInReq) // Did not deliver all funds.
{
// Determine values for pass to adjust saInAct, saInFees, and saCurDeliverAct
terResult = calcNodeAdvance(uNode, pspCur, bMultiQuality, false); // If needed, advance to next funded offer.
terResult = calcNodeAdvance(uNode, psCur, bMultiQuality, false); // If needed, advance to next funded offer.
if (tesSUCCESS == terResult)
{
@@ -1079,7 +1228,7 @@ TER RippleCalc::calcNodeDeliverFwd(
// Therefore, immediately push through output for current offer.
terResult = RippleCalc::calcNodeDeliverFwd(
uNode+1,
pspCur,
psCur,
bMultiQuality,
uOfrOwnerID, // --> Current holder.
saOutPassMax, // --> Amount available.
@@ -1138,7 +1287,7 @@ TER RippleCalc::calcNodeDeliverFwd(
if (saOutPassAct == saOutFunded)
{
// Offer became unfunded.
pspCur->vUnfundedBecame.push_back(uOfferIndex);
psCur.vUnfundedBecame.push_back(uOfferIndex);
bEntryAdvance = true;
}
@@ -1161,13 +1310,13 @@ TER RippleCalc::calcNodeDeliverFwd(
// Called to drive from the last offer node in a chain.
TER RippleCalc::calcNodeOfferRev(
const unsigned int uNode, // 0 < uNode < uLast
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality)
{
TER terResult;
PaymentNode& pnCur = pspCur->vpnNodes[uNode];
PaymentNode& pnNxt = pspCur->vpnNodes[uNode+1];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode+1];
if (!!pnNxt.uAccountID)
{
@@ -1176,7 +1325,7 @@ TER RippleCalc::calcNodeOfferRev(
terResult = calcNodeDeliverRev(
uNode,
pspCur,
psCur,
bMultiQuality,
pnNxt.uAccountID,
@@ -1201,12 +1350,12 @@ TER RippleCalc::calcNodeOfferRev(
// - Deliver is set without transfer fees.
TER RippleCalc::calcNodeOfferFwd(
const unsigned int uNode, // 0 < uNode < uLast
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality
)
{
TER terResult;
PaymentNode& pnPrv = pspCur->vpnNodes[uNode-1];
PaymentNode& pnPrv = psCur.vpnNodes[uNode-1];
if (!!pnPrv.uAccountID)
{
@@ -1216,7 +1365,7 @@ TER RippleCalc::calcNodeOfferFwd(
terResult = calcNodeDeliverFwd(
uNode,
pspCur,
psCur,
bMultiQuality,
pnPrv.uAccountID,
pnPrv.saFwdDeliver, // Previous is sending this much.
@@ -1353,16 +1502,16 @@ void RippleCalc::calcNodeRipple(
// Issues are limited based on credit limits and amount owed.
// No account balance adjustments as we don't know how much is going to actually be pushed through yet.
// <-- tesSUCCESS or tepPATH_DRY
TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality)
TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
TER terResult = tesSUCCESS;
const unsigned int uLast = pspCur->vpnNodes.size() - 1;
const unsigned int uLast = psCur.vpnNodes.size() - 1;
uint64 uRateMax = 0;
PaymentNode& pnPrv = pspCur->vpnNodes[uNode ? uNode-1 : 0];
PaymentNode& pnCur = pspCur->vpnNodes[uNode];
PaymentNode& pnNxt = pspCur->vpnNodes[uNode == uLast ? uLast : uNode+1];
PaymentNode& pnPrv = psCur.vpnNodes[uNode ? uNode-1 : 0];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode+1];
// Current is allowed to redeem to next.
const bool bPrvAccount = !uNode || isSetBit(pnPrv.uFlags, STPathElement::typeAccount);
@@ -1433,7 +1582,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
% saCurIssueReq.getFullText()
% saNxtOwed.getFullText());
cLog(lsINFO) << pspCur->getJson();
cLog(lsINFO) << psCur.getJson();
assert(!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand.
assert(!saCurIssueReq // If not issuing, fine.
@@ -1454,8 +1603,8 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
// account --> ACCOUNT --> $
// Overall deliverable.
const STAmount& saCurWantedReq = bPrvAccount
? std::min(pspCur->saOutReq-pspCur->saOutAct, saPrvLimit+saPrvOwed) // If previous is an account, limit.
: pspCur->saOutReq-pspCur->saOutAct; // Previous is an offer, no limit: redeem own IOUs.
? std::min(psCur.saOutReq-psCur.saOutAct, saPrvLimit+saPrvOwed) // If previous is an account, limit.
: psCur.saOutReq-psCur.saOutAct; // Previous is an offer, no limit: redeem own IOUs.
STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer());
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s")
@@ -1618,8 +1767,8 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
{
// offer --> ACCOUNT --> $
const STAmount& saCurWantedReq = bPrvAccount
? std::min(pspCur->saOutReq-pspCur->saOutAct, saPrvLimit+saPrvOwed) // If previous is an account, limit.
: pspCur->saOutReq-pspCur->saOutAct; // Previous is an offer, no limit: redeem own IOUs.
? std::min(psCur.saOutReq-psCur.saOutAct, saPrvLimit+saPrvOwed) // If previous is an account, limit.
: psCur.saOutReq-psCur.saOutAct; // Previous is an offer, no limit: redeem own IOUs.
STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer());
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s")
@@ -1702,17 +1851,17 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
// - If next node is an offer and output is XRP then we need to deliver funds to limbo.
TER RippleCalc::calcNodeAccountFwd(
const unsigned int uNode, // 0 <= uNode <= uLast
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality)
{
TER terResult = tesSUCCESS;
const unsigned int uLast = pspCur->vpnNodes.size() - 1;
const unsigned int uLast = psCur.vpnNodes.size() - 1;
uint64 uRateMax = 0;
PaymentNode& pnPrv = pspCur->vpnNodes[uNode ? uNode-1 : 0];
PaymentNode& pnCur = pspCur->vpnNodes[uNode];
PaymentNode& pnNxt = pspCur->vpnNodes[uNode == uLast ? uLast : uNode+1];
PaymentNode& pnPrv = psCur.vpnNodes[uNode ? uNode-1 : 0];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode+1];
const bool bPrvAccount = isSetBit(pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = isSetBit(pnNxt.uFlags, STPathElement::typeAccount);
@@ -1754,7 +1903,7 @@ TER RippleCalc::calcNodeAccountFwd(
STAmount& saCurDeliverAct = pnCur.saFwdDeliver;
// For !uNode
STAmount& saCurSendMaxPass = pspCur->saInPass; // Report how much pass sends.
STAmount& saCurSendMaxPass = psCur.saInPass; // Report how much pass sends.
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uNode=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s")
% uNode
@@ -1780,10 +1929,10 @@ TER RippleCalc::calcNodeAccountFwd(
saCurRedeemAct = saCurRedeemReq;
if (!pspCur->saInReq.isNegative())
if (!psCur.saInReq.isNegative())
{
// Limit by send max.
saCurRedeemAct = std::min(saCurRedeemAct, pspCur->saInReq-pspCur->saInAct);
saCurRedeemAct = std::min(saCurRedeemAct, psCur.saInReq-psCur.saInAct);
}
saCurSendMaxPass = saCurRedeemAct;
@@ -1792,17 +1941,17 @@ TER RippleCalc::calcNodeAccountFwd(
? saCurIssueReq
: STAmount(saCurIssueReq);
if (!!saCurIssueAct && !pspCur->saInReq.isNegative())
if (!!saCurIssueAct && !psCur.saInReq.isNegative())
{
// Limit by send max.
saCurIssueAct = std::min(saCurIssueAct, pspCur->saInReq-pspCur->saInAct-saCurRedeemAct);
saCurIssueAct = std::min(saCurIssueAct, psCur.saInReq-psCur.saInAct-saCurRedeemAct);
}
saCurSendMaxPass += saCurIssueAct;
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saInReq=%s saInAct=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s saCurSendMaxPass=%s")
% pspCur->saInReq.getFullText()
% pspCur->saInAct.getFullText()
% psCur.saInReq.getFullText()
% psCur.saInAct.getFullText()
% saCurRedeemAct.getFullText()
% saCurIssueReq.getFullText()
% saCurIssueAct.getFullText()
@@ -1819,7 +1968,7 @@ TER RippleCalc::calcNodeAccountFwd(
// Last node. Accept all funds. Calculate amount actually to credit.
STAmount& saCurReceive = pspCur->saOutPass;
STAmount& saCurReceive = psCur.saOutPass;
STAmount saIssueCrd = uQualityIn >= QUALITY_ONE
? saPrvIssueReq // No fee.
@@ -1916,10 +2065,10 @@ TER RippleCalc::calcNodeAccountFwd(
saCurDeliverAct = saCurDeliverReq;
// If limited, then limit by send max and available.
if (!pspCur->saInReq.isNegative())
if (!psCur.saInReq.isNegative())
{
// Limit by send max.
saCurDeliverAct = std::min(saCurDeliverAct, pspCur->saInReq-pspCur->saInAct);
saCurDeliverAct = std::min(saCurDeliverAct, psCur.saInReq-psCur.saInAct);
// Limit XRP by available. No limit for non-XRP as issuer.
if (!uCurAccountID)
@@ -1956,7 +2105,7 @@ TER RippleCalc::calcNodeAccountFwd(
// offer --> ACCOUNT --> $
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $ : %s") % saPrvDeliverReq.getFullText());
STAmount& saCurReceive = pspCur->saOutPass;
STAmount& saCurReceive = psCur.saOutPass;
// Amount to credit.
saCurReceive = saPrvDeliverReq;
@@ -2012,20 +2161,20 @@ TER RippleCalc::calcNodeAccountFwd(
return terResult;
}
TER RippleCalc::calcNodeFwd(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality)
TER RippleCalc::calcNodeFwd(const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
const PaymentNode& pnCur = pspCur->vpnNodes[uNode];
const PaymentNode& pnCur = psCur.vpnNodes[uNode];
const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount);
cLog(lsINFO) << boost::str(boost::format("calcNodeFwd> uNode=%d") % uNode);
TER terResult = bCurAccount
? calcNodeAccountFwd(uNode, pspCur, bMultiQuality)
: calcNodeOfferFwd(uNode, pspCur, bMultiQuality);
? calcNodeAccountFwd(uNode, psCur, bMultiQuality)
: calcNodeOfferFwd(uNode, psCur, bMultiQuality);
if (tesSUCCESS == terResult && uNode + 1 != pspCur->vpnNodes.size())
if (tesSUCCESS == terResult && uNode + 1 != psCur.vpnNodes.size())
{
terResult = calcNodeFwd(uNode+1, pspCur, bMultiQuality);
terResult = calcNodeFwd(uNode+1, psCur, bMultiQuality);
}
cLog(lsINFO) << boost::str(boost::format("calcNodeFwd< uNode=%d terResult=%d") % uNode % terResult);
@@ -2042,9 +2191,9 @@ TER RippleCalc::calcNodeFwd(const unsigned int uNode, PathState::ref pspCur, con
// --> [all]saWanted.mCurrency
// --> [all]saAccount
// <-> [0]saWanted.mAmount : --> limit, <-- actual
TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality)
TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
PaymentNode& pnCur = pspCur->vpnNodes[uNode];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount);
TER terResult;
@@ -2060,8 +2209,8 @@ TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState::ref pspCur, con
% saTransferRate.getFullText());
terResult = bCurAccount
? calcNodeAccountRev(uNode, pspCur, bMultiQuality)
: calcNodeOfferRev(uNode, pspCur, bMultiQuality);
? calcNodeAccountRev(uNode, psCur, bMultiQuality)
: calcNodeOfferRev(uNode, psCur, bMultiQuality);
// Do previous.
if (tesSUCCESS != terResult)
@@ -2073,7 +2222,7 @@ TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState::ref pspCur, con
{
// Continue in reverse.
terResult = calcNodeRev(uNode-1, pspCur, bMultiQuality);
terResult = calcNodeRev(uNode-1, psCur, bMultiQuality);
}
cLog(lsINFO) << boost::str(boost::format("calcNodeRev< uNode=%d terResult=%s/%d") % uNode % transToken(terResult) % terResult);
@@ -2083,59 +2232,59 @@ TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState::ref pspCur, con
// Calculate the next increment of a path.
// The increment is what can satisfy a portion or all of the requested output at the best quality.
// <-- pspCur->uQuality
void RippleCalc::pathNext(PathState::ref pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent)
// <-- psCur.uQuality
void RippleCalc::pathNext(PathState& psCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent)
{
// The next state is what is available in preference order.
// This is calculated when referenced accounts changed.
const bool bMultiQuality = iPaths == 1;
const unsigned int uLast = pspCur->vpnNodes.size() - 1;
const unsigned int uLast = psCur.vpnNodes.size() - 1;
pspCur->bConsumed = false;
psCur.bConsumed = false;
// YYY This clearing should only be needed for nice logging.
pspCur->saInPass = STAmount(pspCur->saInReq.getCurrency(), pspCur->saInReq.getIssuer());
pspCur->saOutPass = STAmount(pspCur->saOutReq.getCurrency(), pspCur->saOutReq.getIssuer());
psCur.saInPass = STAmount(psCur.saInReq.getCurrency(), psCur.saInReq.getIssuer());
psCur.saOutPass = STAmount(psCur.saOutReq.getCurrency(), psCur.saOutReq.getIssuer());
pspCur->vUnfundedBecame.clear();
pspCur->umReverse.clear();
psCur.vUnfundedBecame.clear();
psCur.umReverse.clear();
cLog(lsINFO) << "Path In: " << pspCur->getJson();
cLog(lsINFO) << "Path In: " << psCur.getJson();
assert(pspCur->vpnNodes.size() >= 2);
assert(psCur.vpnNodes.size() >= 2);
lesCurrent = lesCheckpoint; // Restore from checkpoint.
lesCurrent.bumpSeq(); // Begin ledger varance.
pspCur->terStatus = calcNodeRev(uLast, pspCur, bMultiQuality);
psCur.terStatus = calcNodeRev(uLast, psCur, bMultiQuality);
cLog(lsINFO) << "Path after reverse: " << pspCur->getJson();
cLog(lsINFO) << "Path after reverse: " << psCur.getJson();
if (tesSUCCESS == pspCur->terStatus)
if (tesSUCCESS == psCur.terStatus)
{
// Do forward.
lesCurrent = lesCheckpoint; // Restore from checkpoint.
lesCurrent.bumpSeq(); // Begin ledger varance.
pspCur->terStatus = calcNodeFwd(0, pspCur, bMultiQuality);
psCur.terStatus = calcNodeFwd(0, psCur, bMultiQuality);
}
if (tesSUCCESS == pspCur->terStatus)
if (tesSUCCESS == psCur.terStatus)
{
tLog(!pspCur->saInPass || !pspCur->saOutPass, lsDEBUG)
tLog(!psCur.saInPass || !psCur.saOutPass, lsDEBUG)
<< boost::str(boost::format("saOutPass=%s saInPass=%s")
% pspCur->saOutPass.getFullText()
% pspCur->saInPass.getFullText());
% psCur.saOutPass.getFullText()
% psCur.saInPass.getFullText());
assert(!!pspCur->saOutPass && !!pspCur->saInPass);
assert(!!psCur.saOutPass && !!psCur.saInPass);
pspCur->uQuality = STAmount::getRate(pspCur->saOutPass, pspCur->saInPass); // Calculate relative quality.
psCur.uQuality = STAmount::getRate(psCur.saOutPass, psCur.saInPass); // Calculate relative quality.
cLog(lsINFO) << "Path after forward: " << pspCur->getJson();
cLog(lsINFO) << "Path after forward: " << psCur.getJson();
}
else
{
pspCur->uQuality = 0;
psCur.uQuality = 0;
}
}
@@ -2144,7 +2293,7 @@ TER RippleCalc::rippleCalc(
LedgerEntrySet& lesActive, // <-> --> = Fee already applied to src balance.
STAmount& saMaxAmountAct, // <-- The computed input amount.
STAmount& saDstAmountAct, // <-- The computed output amount.
std::vector<PathState::pointer>& vpsExpanded,
// Issuer:
// XRP: ACCOUNT_XRP
// non-XRP: uSrcAccountID (for any issuer) or another account with trust node.
@@ -2178,28 +2327,23 @@ TER RippleCalc::rippleCalc(
}
// Incrementally search paths.
std::vector<PathState::pointer> vpsPaths;
if (!bNoRippleDirect)
{
// Direct path.
// XXX Might also make a XRP bridge by default.
PathState::pointer pspDirect = PathState::createExpanded(
lesActive,
STPath(),
uDstAccountID,
uSrcAccountID,
saDstAmountReq,
saMaxAmountReq);
PathState::pointer pspDirect = boost::make_shared<PathState>(saDstAmountReq, saMaxAmountReq, lesActive.getLedgerRef());
pspDirect->setIndex(vpsPaths.size());
if (!pspDirect)
return temUNKNOWN;
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build direct: add: %d status: %s")
% !!pspDirect
% transToken(pspDirect ? pspDirect->terStatus : temUNKNOWN));
if (pspDirect)
{
pspDirect->setExpanded(lesActive, STPath(), uDstAccountID, uSrcAccountID);
pspDirect->setIndex(vpsExpanded.size());
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build direct: status: %s")
% transToken(pspDirect->terStatus));
// Return if malformed.
if (isTemMalformed(pspDirect->terStatus))
return pspDirect->terStatus;
@@ -2209,35 +2353,30 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build direct: add: %d sta
// Had a success.
terResult = tesSUCCESS;
vpsPaths.push_back(pspDirect);
vpsExpanded.push_back(pspDirect);
}
else if (terNO_LINE != pspDirect->terStatus)
{
terResult = pspDirect->terStatus;
}
}
}
cLog(lsINFO) << "rippleCalc: Paths in set: " << spsPaths.size();
int iIndex = 0;
BOOST_FOREACH(const STPath& spPath, spsPaths)
{
PathState::pointer pspExpanded = PathState::createExpanded(
lesActive,
spPath,
uDstAccountID,
uSrcAccountID,
saDstAmountReq,
saMaxAmountReq);
PathState::pointer pspExpanded = boost::make_shared<PathState>(saDstAmountReq, saMaxAmountReq, lesActive.getLedgerRef());
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build path: %d: add: %d status: %s")
if (!pspExpanded)
return temUNKNOWN;
pspExpanded->setExpanded(lesActive, spPath, uDstAccountID, uSrcAccountID);
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build path: %d: status: %s")
% ++iIndex
% !!pspExpanded
% transToken(pspExpanded ? pspExpanded->terStatus : temUNKNOWN));
% transToken(pspExpanded->terStatus));
if (pspExpanded)
{
// Return, if the path specification was malformed.
if (isTemMalformed(pspExpanded->terStatus))
return pspExpanded->terStatus;
@@ -2245,16 +2384,15 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build path: %d: add: %d s
if (tesSUCCESS == pspExpanded->terStatus) {
terResult = tesSUCCESS; // Had a success.
pspExpanded->setIndex(vpsPaths.size());
pspExpanded->setIndex(vpsExpanded.size());
vpsPaths.push_back(pspExpanded);
vpsExpanded.push_back(pspExpanded);
}
else if (terNO_LINE != pspExpanded->terStatus)
{
terResult = pspExpanded->terStatus;
}
}
}
if (tesSUCCESS != terResult)
{
@@ -2276,19 +2414,19 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build path: %d: add: %d s
int iPass = 0;
while (temUNCERTAIN == terResult)
{
PathState::pointer pspBest;
int iBest = -1;
const LedgerEntrySet lesCheckpoint = lesActive;
int iDry = 0;
// Find the best path.
BOOST_FOREACH(PathState::pointer& pspCur, vpsPaths)
BOOST_FOREACH(PathState::pointer pspCur, vpsExpanded)
{
if (pspCur->uQuality)
{
pspCur->saInAct = saMaxAmountAct; // Update to current amount processed.
pspCur->saOutAct = saDstAmountAct;
rc.pathNext(pspCur, vpsPaths.size(), lesCheckpoint, lesActive); // Compute increment.
rc.pathNext(*pspCur, vpsExpanded.size(), lesCheckpoint, lesActive); // Compute increment.
if (!pspCur->uQuality) {
// Path was dry.
@@ -2305,8 +2443,8 @@ int iPass = 0;
assert(!!pspCur->saInPass && !!pspCur->saOutPass);
if ((!bLimitQuality || pspCur->uQuality <= uQualityLimit) // Quality is not limted or increment has allowed quality.
&& (!pspBest // Best is not yet set.
|| PathState::lessPriority(pspBest, pspCur))) // Current is better than set.
&& (iBest < 0 // Best is not yet set.
|| PathState::lessPriority(*vpsExpanded[iBest], *pspCur))) // Current is better than set.
{
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: better: uQuality=%s saInPass=%s saOutPass=%s")
% STAmount::saFromRate(pspCur->uQuality)
@@ -2314,20 +2452,21 @@ int iPass = 0;
% pspCur->saOutPass.getFullText());
lesActive.swapWith(pspCur->lesEntries); // For the path, save ledger state.
pspBest = pspCur;
iBest = pspCur->getIndex();
}
}
}
}
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: Pass: %d Dry: %d Paths: %d") % ++iPass % iDry % vpsPaths.size());
BOOST_FOREACH(PathState::pointer& pspCur, vpsPaths)
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: Pass: %d Dry: %d Paths: %d") % ++iPass % iDry % vpsExpanded.size());
BOOST_FOREACH(PathState::ref pspCur, vpsExpanded)
{
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d best: %d consumed: %d") % pspCur->mIndex % pspCur->uQuality % (pspBest == pspCur) % pspCur->bConsumed);
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d best: %d consumed: %d") % pspCur->mIndex % pspCur->uQuality % (iBest == pspCur->getIndex()) % pspCur->bConsumed);
}
if (pspBest)
if (iBest >= 0)
{
// Apply best path.
PathState::pointer pspBest = vpsExpanded[iBest];
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: best: uQuality=%s saInPass=%s saOutPass=%s")
% STAmount::saFromRate(pspBest->uQuality)
@@ -2355,7 +2494,7 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be
terResult = tesSUCCESS;
}
else if (saMaxAmountAct != saMaxAmountReq && iDry != vpsPaths.size())
else if (saMaxAmountAct != saMaxAmountReq && iDry != vpsExpanded.size())
{
// Have not met requested amount or max send, try to do more. Prepare for next pass.

View File

@@ -107,12 +107,17 @@ public:
return this;
}
int getIndex() { return mIndex; };
PathState(
const STAmount& saSend,
const STAmount& saSendMax,
const Ledger::ref lrLedger = Ledger::pointer()
) : mLedger(lrLedger), saInReq(saSendMax), saOutReq(saSend) { ; }
PathState(const PathState& psSrc, bool bUnsed)
: mLedger(psSrc.mLedger), saInReq(psSrc.saInReq), saOutReq(psSrc.saOutReq) { ; }
void setExpanded(
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
@@ -121,29 +126,14 @@ public:
);
void setCanonical(
PathState::ref pspExpanded
const PathState& psExpanded
);
Json::Value getJson() const;
static PathState::pointer createExpanded(
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
const uint160& uReceiverID,
const uint160& uSenderID,
const STAmount& saSend,
const STAmount& saSendMax
)
{
PathState::pointer pspNew = boost::make_shared<PathState>(saSend, saSendMax, lesSource.getLedgerRef());
pspNew->setExpanded(lesSource, spSourcePath, uReceiverID, uSenderID);
return pspNew;
}
#if 0
static PathState::pointer createCanonical(
PathState::ref pspExpanded
PathState&ref pspExpanded
)
{
PathState::pointer pspNew = boost::make_shared<PathState>(pspExpanded->saOutAct, pspExpanded->saInAct);
@@ -152,8 +142,8 @@ public:
return pspNew;
}
static bool lessPriority(PathState::ref lhs, PathState::ref rhs);
#endif
static bool lessPriority(PathState& lhs, PathState& rhs);
};
class RippleCalc
@@ -168,18 +158,18 @@ public:
// If the transaction fails to meet some constraint, still need to delete unfunded offers.
boost::unordered_set<uint256> musUnfundedFound; // Offers that were found unfunded.
void pathNext(PathState::ref pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
TER calcNode(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality);
TER calcNodeRev(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality);
TER calcNodeFwd(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality);
TER calcNodeOfferRev(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality);
TER calcNodeOfferFwd(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality);
TER calcNodeAccountRev(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality);
TER calcNodeAccountFwd(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality);
TER calcNodeAdvance(const unsigned int uNode, PathState::ref pspCur, const bool bMultiQuality, const bool bReverse);
void pathNext(PathState& psCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
TER calcNode(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeFwd(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferFwd(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountFwd(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAdvance(const unsigned int uNode, PathState& psCur, const bool bMultiQuality, const bool bReverse);
TER calcNodeDeliverRev(
const unsigned int uNode,
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality,
const uint160& uOutAccountID,
const STAmount& saOutReq,
@@ -187,7 +177,7 @@ public:
TER calcNodeDeliverFwd(
const unsigned int uNode,
PathState::ref pspCur,
PathState& psCur,
const bool bMultiQuality,
const uint160& uInAccountID,
const STAmount& saInReq,
@@ -205,6 +195,7 @@ public:
LedgerEntrySet& lesActive,
STAmount& saMaxAmountAct,
STAmount& saDstAmountAct,
std::vector<PathState::pointer>& vpsExpanded,
const STAmount& saDstAmountReq,
const STAmount& saMaxAmountReq,
const uint160& uDstAccountID,
@@ -215,6 +206,8 @@ public:
const bool bNoRippleDirect,
const bool bStandAlone
);
static void setCanonical(STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded);
};
#endif

View File

@@ -570,7 +570,7 @@ public:
};
protected:
int mType;
unsigned int mType;
uint160 mAccountID;
uint160 mCurrencyID;
uint160 mIssuerID;
@@ -586,6 +586,12 @@ public:
| (uIssuerID.isZero() ? 0 : STPathElement::typeIssuer);
}
STPathElement(unsigned int uType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID)
: mType(uType), mAccountID(uAccountID), mCurrencyID(uCurrencyID), mIssuerID(uIssuerID)
{
;
}
int getNodeType() const { return mType; }
bool isOffer() const { return mAccountID.isZero(); }
bool isAccount() const { return !isOffer(); }
@@ -634,6 +640,8 @@ public:
std::vector<STPathElement>::const_iterator end() const { return mPath.end(); }
bool operator==(const STPath& t) const { return mPath == t.mPath; }
void setCanonical(const STPath& spExpanded);
};
inline std::vector<STPathElement>::iterator range_begin(STPath & x)