diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index 5440eb34e5..6ff83e670c 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -42,14 +42,15 @@ bool PaymentNode::operator==(const PaymentNode& pnOther) const { // Return true, iff lhs has less priority than rhs. bool PathState::lessPriority(PathState& lhs, PathState& rhs) { + // First rank is quality. if (lhs.uQuality != rhs.uQuality) return lhs.uQuality > rhs.uQuality; // Bigger is worse. - // Best quanity is second rank. + // Second rank is best quantity. if (lhs.saOutPass != rhs.saOutPass) return lhs.saOutPass < rhs.saOutPass; // Smaller is worse. - // Path index is third rank. + // Third rank is path index. return lhs.mIndex > rhs.mIndex; // Bigger is worse. } @@ -2271,58 +2272,58 @@ TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState& psCur, const bo // 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. // <-- psCur.uQuality -void RippleCalc::pathNext(PathState& psCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) +void RippleCalc::pathNext(PathState::ref psrCur, 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 = psCur.vpnNodes.size() - 1; + const unsigned int uLast = psrCur->vpnNodes.size() - 1; - psCur.bConsumed = false; + psrCur->bConsumed = false; // YYY This clearing should only be needed for nice logging. - psCur.saInPass = STAmount(psCur.saInReq.getCurrency(), psCur.saInReq.getIssuer()); - psCur.saOutPass = STAmount(psCur.saOutReq.getCurrency(), psCur.saOutReq.getIssuer()); + psrCur->saInPass = STAmount(psrCur->saInReq.getCurrency(), psrCur->saInReq.getIssuer()); + psrCur->saOutPass = STAmount(psrCur->saOutReq.getCurrency(), psrCur->saOutReq.getIssuer()); - psCur.vUnfundedBecame.clear(); - psCur.umReverse.clear(); + psrCur->vUnfundedBecame.clear(); + psrCur->umReverse.clear(); - cLog(lsINFO) << "Path In: " << psCur.getJson(); + cLog(lsINFO) << "pathNext: Path In: " << psrCur->getJson(); - assert(psCur.vpnNodes.size() >= 2); + assert(psrCur->vpnNodes.size() >= 2); lesCurrent = lesCheckpoint; // Restore from checkpoint. lesCurrent.bumpSeq(); // Begin ledger varance. - psCur.terStatus = calcNodeRev(uLast, psCur, bMultiQuality); + psrCur->terStatus = calcNodeRev(uLast, *psrCur, bMultiQuality); - cLog(lsINFO) << "Path after reverse: " << psCur.getJson(); + cLog(lsINFO) << "pathNext: Path after reverse: " << psrCur->getJson(); - if (tesSUCCESS == psCur.terStatus) + if (tesSUCCESS == psrCur->terStatus) { // Do forward. lesCurrent = lesCheckpoint; // Restore from checkpoint. lesCurrent.bumpSeq(); // Begin ledger varance. - psCur.terStatus = calcNodeFwd(0, psCur, bMultiQuality); + psrCur->terStatus = calcNodeFwd(0, *psrCur, bMultiQuality); } - if (tesSUCCESS == psCur.terStatus) + if (tesSUCCESS == psrCur->terStatus) { - tLog(!psCur.saInPass || !psCur.saOutPass, lsDEBUG) - << boost::str(boost::format("saOutPass=%s saInPass=%s") - % psCur.saOutPass.getFullText() - % psCur.saInPass.getFullText()); + tLog(!psrCur->saInPass || !psrCur->saOutPass, lsDEBUG) + << boost::str(boost::format("pathNext: saOutPass=%s saInPass=%s") + % psrCur->saOutPass.getFullText() + % psrCur->saInPass.getFullText()); - assert(!!psCur.saOutPass && !!psCur.saInPass); + assert(!!psrCur->saOutPass && !!psrCur->saInPass); - psCur.uQuality = STAmount::getRate(psCur.saOutPass, psCur.saInPass); // Calculate relative quality. + psrCur->uQuality = STAmount::getRate(psrCur->saOutPass, psrCur->saInPass); // Calculate relative quality. - cLog(lsINFO) << "Path after forward: " << psCur.getJson(); + cLog(lsINFO) << "pathNext: Path after forward: " << psrCur->getJson(); } else { - psCur.uQuality = 0; + psrCur->uQuality = 0; } } @@ -2458,14 +2459,18 @@ int iPass = 0; int iDry = 0; // Find the best path. - BOOST_FOREACH(PathState::pointer pspCur, vpsExpanded) + BOOST_FOREACH(PathState::ref pspCur, vpsExpanded) { if (pspCur->uQuality) { - pspCur->saInAct = saMaxAmountAct; // Update to current amount processed. + pspCur->saInAct = saMaxAmountAct; // Update to current amount processed. pspCur->saOutAct = saDstAmountAct; - rc.pathNext(*pspCur, vpsExpanded.size(), lesCheckpoint, lesActive); // Compute increment. + rc.pathNext(pspCur, vpsExpanded.size(), lesCheckpoint, lesActive); // Compute increment. + cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: AFTER: mIndex=%d uQuality=%d rate=%s") + % pspCur->mIndex + % pspCur->uQuality + % STAmount::saFromRate(pspCur->uQuality)); if (!pspCur->uQuality) { // Path was dry. @@ -2485,7 +2490,9 @@ int iPass = 0; && (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") + cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: better: mIndex=%d uQuality=%s rate=%s saInPass=%s saOutPass=%s") + % pspCur->mIndex + % pspCur->uQuality % STAmount::saFromRate(pspCur->uQuality) % pspCur->saInPass.getFullText() % pspCur->saOutPass.getFullText()); @@ -2499,7 +2506,12 @@ int iPass = 0; 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 % (iBest == pspCur->getIndex()) % pspCur->bConsumed); +cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d rate: %s quality:%d best: %d consumed: %d") + % pspCur->mIndex + % STAmount::saFromRate(pspCur->uQuality) + % pspCur->uQuality + % (iBest == pspCur->getIndex()) + % pspCur->bConsumed); } if (iBest >= 0) diff --git a/src/cpp/ripple/RippleCalc.h b/src/cpp/ripple/RippleCalc.h index 906dd191fd..2935061be4 100644 --- a/src/cpp/ripple/RippleCalc.h +++ b/src/cpp/ripple/RippleCalc.h @@ -161,7 +161,7 @@ public: // If the transaction fails to meet some constraint, still need to delete unfunded offers. boost::unordered_set musUnfundedFound; // Offers that were found unfunded. - void pathNext(PathState& psCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent); + void pathNext(PathState::ref psrCur, 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); diff --git a/test/path-test.js b/test/path-test.js index 9886eedb68..581b0c89d0 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -12,7 +12,8 @@ require("../src/js/remote.js").config = require("./config.js"); buster.testRunner.timeout = 5000; -buster.testCase("Path finding", { +if (false) +buster.testCase("Basic Path finding", { // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), // 'setUp' : testutils.build_setup({ verbose: true }), 'setUp' : testutils.build_setup(), @@ -221,4 +222,215 @@ buster.testCase("Path finding", { }, }); +buster.testCase("Extended Path finding", { + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), + 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup(), + 'tearDown' : testutils.build_teardown(), + + "alternative paths - consume both" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "mtgox", "bitstamp"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : [ "600/USD/mtgox", "800/USD/bitstamp" ], + "bob" : [ "700/USD/mtgox", "900/USD/bitstamp" ] + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "70/USD/alice", + "mtgox" : "70/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Payment with auto path"; + + self.remote.transaction() + .payment('alice', 'bob', "140/USD/bob") + .build_path(true) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "0/USD/mtgox", "0/USD/bitstamp" ], + "bob" : [ "70/USD/mtgox", "70/USD/bitstamp" ], + "bitstamp" : [ "0/USD/alice", "-70/USD/bob" ], + "mtgox" : [ "0/USD/alice", "-70/USD/bob" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "alternative paths - consume best transfer" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "mtgox", "bitstamp"], callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("bitstamp") + .transfer_rate(1e9*1.1) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : [ "600/USD/mtgox", "800/USD/bitstamp" ], + "bob" : [ "700/USD/mtgox", "900/USD/bitstamp" ] + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "70/USD/alice", + "mtgox" : "70/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Payment with auto path"; + + self.remote.transaction() + .payment('alice', 'bob', "70/USD/bob") + .build_path(true) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "0/USD/mtgox", "70/USD/bitstamp" ], + "bob" : [ "70/USD/mtgox", "0/USD/bitstamp" ], + "bitstamp" : [ "-70/USD/alice", "0/USD/bob" ], + "mtgox" : [ "0/USD/alice", "-70/USD/bob" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "=>alternative paths - consume best transfer first" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "mtgox", "bitstamp"], callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("bitstamp") + .transfer_rate(1e9*1.1) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : [ "600/USD/mtgox", "800/USD/bitstamp" ], + "bob" : [ "700/USD/mtgox", "900/USD/bitstamp" ] + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "70/USD/alice", + "mtgox" : "70/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Payment with auto path"; + + self.remote.transaction() + .payment('alice', 'bob', "77/USD/bob") + .build_path(true) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "0/USD/mtgox", "63.63636363636363/USD/bitstamp" ], + "bob" : [ "70/USD/mtgox", "7/USD/bitstamp" ], + "bitstamp" : [ "-63.63636363636363/USD/alice", "-7/USD/bob" ], + "mtgox" : [ "0/USD/alice", "-70/USD/bob" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); + // vim:sw=2:sts=2:ts=8:et