diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index 1aeffb14b0..5b6e6aa5fb 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -275,7 +275,10 @@ SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger, if (it != mEntries.end()) { if (it->second.mAction == taaDELETE) + { + cLog(lsFATAL) << "Trying to thread to deleted node"; return SLE::pointer(); + } if (it->second.mAction == taaCACHED) it->second.mAction = taaMODIFY; if (it->second.mSeq != mSeq) @@ -288,7 +291,10 @@ SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger, boost::unordered_map::iterator me = newMods.find(node); if (me != newMods.end()) + { + assert(me->second); return me->second; + } SLE::pointer ret = ledger->getSLE(node); if (ret) @@ -306,6 +312,7 @@ bool LedgerEntrySet::threadTx(const RippleAddress& threadTo, Ledger::ref ledger, SLE::pointer sle = getForMod(Ledger::getAccountRootIndex(threadTo.getAccountID()), ledger, newMods); if (!sle) { + cLog(lsFATAL) << "Threading to non-existent account: " << threadTo.humanAccountID(); assert(false); return false; } diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index 568449eb0e..c4fcbcd617 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -471,15 +471,13 @@ TER RippleCalc::calcNodeDeliverRev( return terResult; } -// Deliver maximum amount of funds from previous node. -// Goal: Make progress consuming the offer. +// For current offer, get input from deliver/limbo and output to next account or deliver for next offers. TER RippleCalc::calcNodeDeliverFwd( const unsigned int uNode, // 0 < uNode < uLast PathState::ref pspCur, const bool bMultiQuality, const uint160& uInAccountID, // --> Input owner's account. - const STAmount& saInFunds, // --> Funds available for delivery and fees. - const STAmount& saInReq, // --> Limit to deliver. + const STAmount& saInReq, // --> Amount to deliver. STAmount& saInAct, // <-- Amount delivered. STAmount& saInFees) // <-- Fees charged. { @@ -490,7 +488,9 @@ TER RippleCalc::calcNodeDeliverFwd( PaymentNode& pnNxt = pspCur->vpnNodes[uNode+1]; const uint160& uNxtAccountID = pnNxt.uAccountID; + const uint160& uCurCurrencyID = pnCur.uCurrencyID; const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvCurrencyID = pnPrv.uCurrencyID; const uint160& uPrvIssuerID = pnPrv.uIssuerID; const STAmount& saTransferRate = pnPrv.saTransferRate; @@ -498,19 +498,20 @@ TER RippleCalc::calcNodeDeliverFwd( uint256& uDirectTip = pnCur.uDirectTip; - uDirectTip = 0; // Restart book searching. + uDirectTip = 0; // Restart book searching. - saInAct.zero(saInFunds); - saInFees.zero(saInFunds); + saInAct.zero(saInReq); + saInFees.zero(saInReq); + saCurDeliverAct.zero(uCurCurrencyID, uCurIssuerID); while (tesSUCCESS == terResult - && saInAct != saInReq // Did not deliver limit. - && saInAct + saInFees != saInFunds) // Did not deliver all funds. + && saInAct + saInFees != saInReq) // Did not deliver all funds. { terResult = calcNodeAdvance(uNode, pspCur, bMultiQuality, false); // If needed, advance to next funded offer. if (tesSUCCESS == terResult) { + // Doesn't charge input. Input funds are in limbo. bool& bEntryAdvance = pnCur.bEntryAdvance; STAmount& saOfrRate = pnCur.saOfrRate; uint256& uOfferIndex = pnCur.uOfferIndex; @@ -521,9 +522,11 @@ TER RippleCalc::calcNodeDeliverFwd( STAmount& saTakerPays = pnCur.saTakerPays; STAmount& saTakerGets = pnCur.saTakerGets; - const STAmount saInFeeRate = uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. - ? saOne // No fee. - : saTransferRate; // Transfer rate of issuer. + const STAmount saInFeeRate = !!uPrvCurrencyID + ? uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. + ? saOne // No fee. + : saTransferRate // Transfer rate of issuer. + : saOne; // // First calculate assuming no output fees. @@ -532,11 +535,11 @@ TER RippleCalc::calcNodeDeliverFwd( STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees. STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout. STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees. - STAmount saInSum = std::min(saInTotal, saInFunds-saInAct-saInFees); // In limited by saInFunds. + STAmount saInSum = std::min(saInTotal, saInReq-saInAct-saInFees); // In limited by saInReq. STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees. STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out. - STAmount saInPassFees(saInFunds.getCurrency(), saInFunds.getIssuer()); + STAmount saInPassFees(saInReq.getCurrency(), saInReq.getIssuer()); STAmount saOutPassAct(saOfferFunds.getCurrency(), saOfferFunds.getIssuer()); cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s") @@ -551,22 +554,21 @@ TER RippleCalc::calcNodeDeliverFwd( { // ? --> OFFER --> account // Input fees: vary based upon the consumed offer's owner. - // Output fees: none as the destination account is the issuer. - - // XXX This doesn't claim input. - // XXX Assumes input is in limbo. XXX Check. - - // Debit offer owner. - lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPassMax); + // Output fees: none as XRP or the destination account is the issuer. saOutPassAct = saOutPassMax; + // Debit offer owner, send XRP or non-XPR to next account. + lesActive.accountSend(uOfrOwnerID, uNxtAccountID, saOutPassAct); + cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s") % saOutPassAct); } else { // ? --> OFFER --> offer + // Offer to offer means current order book's output currency and issuer match next order book's input current and + // issuer. STAmount saOutPassFees; terResult = RippleCalc::calcNodeDeliverFwd( @@ -575,16 +577,22 @@ TER RippleCalc::calcNodeDeliverFwd( bMultiQuality, uOfrOwnerID, saOutPassMax, - saOutPassMax, saOutPassAct, // <-- Amount delivered. saOutPassFees); // <-- Fees charged. if (tesSUCCESS != terResult) break; - // Offer maximum in limited by next payout. + // Offer maximum in split into fees by next payout. saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate); saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct; + + // Do outbound debiting. + // Send to issuer/limbo total amount (no fees to issuer). + lesActive.accountSend(uOfrOwnerID, !!uCurCurrencyID ? uCurIssuerID : ACCOUNT_XRP, saOutPassAct); + + cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> offer: saOutPassAct=%s") + % saOutPassAct); } cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s") @@ -596,13 +604,12 @@ TER RippleCalc::calcNodeDeliverFwd( // Funds were spent. bFundsDirty = true; - // Credit issuer transfer fees. - lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassFees); - - // Credit offer owner from offer. - lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassAct); + // Do inbound crediting. + // Credit offer owner from in issuer/limbo (don't take transfer fees). + lesActive.accountSend(!!uPrvCurrencyID ? uInAccountID : ACCOUNT_XRP, uOfrOwnerID, saInPassAct); // Adjust offer + // Fees are considered paid from a seperate budget and are not named in the offer. sleOffer->setFieldAmount(sfTakerGets, saTakerGets - saOutPassAct); sleOffer->setFieldAmount(sfTakerPays, saTakerPays - saInPassAct); @@ -661,7 +668,7 @@ TER RippleCalc::calcNodeOfferRev( } // Called to drive the from the first offer node in a chain. -// - Offer input is limbo. +// - Offer input is in issuer/limbo. // - Current offers consumed. // - Current offer owners debited. // - Transfer fees credited to issuer. @@ -688,7 +695,6 @@ TER RippleCalc::calcNodeOfferFwd( bMultiQuality, pnPrv.uAccountID, pnPrv.saFwdDeliver, - pnPrv.saFwdDeliver, saInAct, saInFees); @@ -843,6 +849,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC const uint160& uCurrencyID = pnCur.uCurrencyID; + // XXX Don't look up quality for XRP const uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; const uint32 uQualityOut = uNode != uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; @@ -908,15 +915,16 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC || !saNxtOwed.isNegative() // 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 (bPrvAccount && bNxtAccount) + if (!uNode) { - if (!uNode) - { - // ^ --> ACCOUNT --> account|offer - // Nothing to do, there is no previous to adjust. - nothing(); - } - else if (uNode == uLast) + // ^ --> ACCOUNT --> account|offer + // Nothing to do, there is no previous to adjust. + + nothing(); + } + else if (bPrvAccount && bNxtAccount) + { + if (uNode == uLast) { // account --> ACCOUNT --> $ // Overall deliverable. @@ -1157,7 +1165,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC } // The reverse pass has been narrowing by credit available and inflating by fees as it worked backwards. -// Now, push through the actual amount to each node and adjust balances. +// Now, for the current account node, take the actual amount from previous and adjust forward balances. // // Perform balance adjustments between previous and current node. // - The previous node: specifies what to push through to current. @@ -1165,6 +1173,8 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC // Then, compute current node's output for next node. // - Current node: specify what to push through to next. // - Output to next node is computed as input minus quality or transfer fee. +// - If next node is an offer and output is non-XRP then we are the issuer and do not need to push funds. +// - 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, @@ -1186,6 +1196,8 @@ TER RippleCalc::calcNodeAccountFwd( const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. + const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uCurrencyID = pnCur.uCurrencyID; uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; @@ -1216,6 +1228,9 @@ TER RippleCalc::calcNodeAccountFwd( const STAmount& saCurDeliverReq = pnCur.saRevDeliver; STAmount& saCurDeliverAct = pnCur.saFwdDeliver; + // For !uNode + STAmount& saCurSendMaxPass = pspCur->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 % uLast @@ -1242,7 +1257,6 @@ TER RippleCalc::calcNodeAccountFwd( const STAmount saCurSendMaxReq = pspCur->saInReq.isNegative() ? pspCur->saInReq // Negative for no limit, doing a calculation. : pspCur->saInReq-pspCur->saInAct; // request - done. - STAmount& saCurSendMaxPass = pspCur->saInPass; // Report how much pass sends. saCurRedeemAct = saCurRedeemReq // Redeem requested. @@ -1343,31 +1357,71 @@ TER RippleCalc::calcNodeAccountFwd( } else if (bPrvAccount && !bNxtAccount) { - // account --> ACCOUNT --> offer - cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); - - saCurDeliverAct.zero(saCurDeliverReq); - - // redeem -> issue. - // wants to redeem and current would and can issue. - // If redeeming cur to next is done, this implies can issue. - if (saPrvRedeemReq) // Previous wants to redeem. + if (uNode) { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); - } + // Non-XRP, current node is the issuer. + cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); - // issue -> issue - if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. - && saPrvIssueReq) // Previous wants to issue. To next must be ok. + saCurDeliverAct.zero(saCurDeliverReq); + + // redeem -> issue/deliver. + // Previous wants to redeem. + // Current is issuing to an offer so leave funds in account as "limbo". + if (saPrvRedeemReq) // Previous wants to redeem. + { + // Rate : 1.0 : transfer_rate + // XXX Is having the transfer rate here correct? + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); + } + + // issue -> issue/deliver + if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. + && saPrvIssueReq) // Previous wants to issue. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); + } + + // Adjust prv --> cur balance : take all inbound + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + else { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); - } + // Delivering amount requested from downstream. + saCurDeliverAct = saCurDeliverReq; - // Adjust prv --> cur balance : take all inbound - // XXX Currency must be in amount. - lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + // If limited, then limit by send max and available. + if (!pspCur->saInReq.isNegative()) + { + saCurDeliverAct = pspCur->saInReq-pspCur->saInAct; + + // Limit XRP by available. No limit for non-XRP as issuer. + if (!uCurAccountID) + saCurDeliverAct = std::min(saCurDeliverAct, lesActive.accountHolds(uCurAccountID, CURRENCY_XRP, ACCOUNT_XRP)); + + } + saCurSendMaxPass = saCurDeliverAct; // Record amount sent for pass. + + if (!!uCurrencyID) + { + // Non-XRP, current node is the issuer. + // We could be delivering to multiple accounts, so we don't know which ripple balance will be adjusted. Assume + // just issuing. + + cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT -- !XRP --> offer")); + + // As the issuer, would only issue. + // Don't need to actually deliver. As from delivering leave in the issuer as limbo. + nothing(); + } + else + { + cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT -- XRP --> offer")); + + // Deliver XRP to limbo. + lesActive.accountSend(uCurAccountID, ACCOUNT_XRP, saCurDeliverAct); + } + } } else if (!bPrvAccount && bNxtAccount) { diff --git a/src/cpp/ripple/RippleCalc.h b/src/cpp/ripple/RippleCalc.h index 6dffe4723d..7618ab8ef4 100644 --- a/src/cpp/ripple/RippleCalc.h +++ b/src/cpp/ripple/RippleCalc.h @@ -163,7 +163,6 @@ public: PathState::ref pspCur, const bool bMultiQuality, const uint160& uInAccountID, - const STAmount& saInFunds, const STAmount& saInReq, STAmount& saInAct, STAmount& saInFees); diff --git a/src/cpp/ripple/ValidationCollection.cpp b/src/cpp/ripple/ValidationCollection.cpp index eded830c66..acb6c2c4b3 100644 --- a/src/cpp/ripple/ValidationCollection.cpp +++ b/src/cpp/ripple/ValidationCollection.cpp @@ -189,6 +189,36 @@ int ValidationCollection::getLoadRatio(bool overLoaded) return (goodNodes * 100) / (goodNodes + badNodes); } +std::list ValidationCollection::getCurrentTrustedValidations() +{ + uint32 cutoff = theApp->getOPs().getNetworkTimeNC() - LEDGER_VAL_INTERVAL; + + std::list ret; + + boost::mutex::scoped_lock sl(mValidationLock); + boost::unordered_map::iterator it = mCurrentValidations.begin(); + while (it != mCurrentValidations.end()) + { + if (!it->second) // contains no record + it = mCurrentValidations.erase(it); + else if (it->second->getSignTime() < cutoff) + { // contains a stale record + mStaleValidations.push_back(it->second); + it->second.reset(); + condWrite(); + it = mCurrentValidations.erase(it); + } + else + { // contains a live record + if (it->second->isTrusted()) + ret.push_back(it->second); + ++it; + } + } + + return ret; +} + boost::unordered_map ValidationCollection::getCurrentValidations(uint256 currentLedger) { diff --git a/src/cpp/ripple/ValidationCollection.h b/src/cpp/ripple/ValidationCollection.h index 51f60a5a94..d8ecd791e9 100644 --- a/src/cpp/ripple/ValidationCollection.h +++ b/src/cpp/ripple/ValidationCollection.h @@ -46,6 +46,7 @@ public: int getLoadRatio(bool overLoaded); boost::unordered_map getCurrentValidations(uint256 currentLedger); + std::list getCurrentTrustedValidations(); void flush(); void sweep() { mValidations.sweep(); } diff --git a/src/js/remote.js b/src/js/remote.js index 347bc9d7fc..0f990fe950 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -176,12 +176,12 @@ Request.prototype.accounts = function (accounts) { // // --> trusted: truthy, if remote is trusted -var Remote = function (trusted, websocket_ip, websocket_port, trace) { - this.trusted = trusted; - this.websocket_ip = websocket_ip; - this.websocket_port = websocket_port; +var Remote = function (opts, trace) { + this.trusted = opts.trusted; + this.websocket_ip = opts.websocket_ip; + this.websocket_port = opts.websocket_port; this.id = 0; - this.trace = trace; + this.trace = opts.trace || trace; this._ledger_current_index = undefined; this._ledger_hash = undefined; this._ledger_time = undefined; @@ -219,10 +219,10 @@ var Remote = function (trusted, websocket_ip, websocket_port, trace) { Remote.prototype = new EventEmitter; -Remote.from_config = function (name, trace) { - var serverConfig = exports.config.servers[name]; +Remote.from_config = function (obj, trace) { + var serverConfig = 'string' === typeof obj ? exports.config.servers[obj] : obj; - var remote = new Remote(serverConfig.trusted, serverConfig.websocket_ip, serverConfig.websocket_port, trace); + var remote = new Remote(serverConfig, trace); for (var account in exports.config.accounts) { var accountInfo = exports.config.accounts[account]; @@ -450,12 +450,12 @@ Remote.prototype._connect_message = function (ws, json) { unexpected = true; } else if ('success' === message.status) { - if (this.trace) console.log("remote: response: %s", json); + if (this.trace) console.log("remote: response: %s", JSON.stringify(message, undefined, 2)); request.emit('success', message.result); } else if (message.error) { - if (this.trace) console.log("remote: error: %s", json); + if (this.trace) console.log("remote: error: %s", JSON.stringify(message, undefined, 2)); request.emit('error', { 'error' : 'remoteError', @@ -700,7 +700,7 @@ Remote.prototype.submit = function (transaction) { if (transaction.secret && !this.trusted) { transaction.emit('error', { - 'result' : 'serverUntrusted', + 'result' : 'tejServerUntrusted', 'result_message' : "Attempt to give a secret to an untrusted server." }); } @@ -1026,6 +1026,7 @@ var SUBMIT_LOST = 8; // Give up tracking. var Transaction = function (remote) { var self = this; + this.callback = undefined; this.remote = remote; this.secret = undefined; this.build_path = true; @@ -1108,14 +1109,25 @@ Transaction.prototype.set_state = function (state) { // XXX Don't allow a submit without knowing ledger_index. // XXX Have a network canSubmit(), post events for following. // XXX Also give broader status for tracking through network disconnects. -Transaction.prototype.submit = function () { +// callback = function (status, info) { +// // status is final status. Only works under a ledger_accepting conditions. +// switch status: +// case 'tesSUCCESS': all is well. +// case 'tejServerUntrusted': sending secret to untrusted server. +// case 'tejInvalidAccount': locally detected error. +// case 'tejLost': locally gave up looking +// default: some other TER +// } +Transaction.prototype.submit = function (callback) { var self = this; var tx_json = this.tx_json; + this.callback = callback; + if ('string' !== typeof tx_json.Account) { - this.emit('error', { - 'error' : 'invalidAccount', + (this.callback || this.emit)('error', { + 'error' : 'tejInvalidAccount', 'error_message' : 'Bad account.' }); return; @@ -1134,11 +1146,12 @@ Transaction.prototype.submit = function () { } } - if (this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { - // There are listeners for 'final', 'lost', or 'pending' arrange to emit them. + if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { + // There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them. this.submit_index = this.remote._ledger_current_index; + // When a ledger closes, look for the result. var on_ledger_closed = function (ledger_hash, ledger_index) { var stop = false; @@ -1148,6 +1161,11 @@ Transaction.prototype.submit = function () { .on('success', function (message) { self.set_state(message.metadata.TransactionResult); self.emit('final', message); + + if (self.callback) + self.callback(message.metadata.TransactionResult, message); + + stop = true; }) .on('error', function (message) { if ('remoteError' === message.error @@ -1155,6 +1173,10 @@ Transaction.prototype.submit = function () { if (self.submit_index + SUBMIT_LOST < ledger_index) { self.set_state('client_lost'); // Gave up. self.emit('lost'); + + if (self.callback) + self.callback('tejLost', message); + stop = true; } else if (self.submit_index + SUBMIT_MISSING < ledger_index) { @@ -1170,12 +1192,18 @@ Transaction.prototype.submit = function () { .request(); if (stop) { - self.removeListener('ledger_closed', on_ledger_closed); + self.remote.removeListener('ledger_closed', on_ledger_closed); self.emit('final', message); } }; this.remote.on('ledger_closed', on_ledger_closed); + + if (this.callback) { + this.on('error', function (message) { + self.callback(message.error, message); + }); + } } this.set_state('client_submitted'); diff --git a/test/config.js b/test/config-example.js similarity index 88% rename from test/config.js rename to test/config-example.js index 4038f93da8..b0e9c44481 100644 --- a/test/config.js +++ b/test/config-example.js @@ -5,8 +5,7 @@ var path = require("path"); // Where to find the binary. -//exports.rippled = path.join(process.cwd(), "newcoin_master"); -exports.rippled = path.join(process.cwd(), "Debug/newcoin"); +exports.rippled = path.resolve("build/rippled"); exports.server_default = "alpha"; @@ -15,13 +14,12 @@ exports.servers = { // A local test server. "alpha" : { 'trusted' : true, - 'no_server' : true, // "peer_ip" : "0.0.0.0", // "peer_port" : 51235, 'rpc_ip' : "0.0.0.0", 'rpc_port' : 5005, 'websocket_ip' : "127.0.0.1", - 'websocket_port' : 5006, + 'websocket_port' : 6005, // 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h", // 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta" } diff --git a/test/offer-test.js b/test/offer-test.js index 988136364d..6c9202443e 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -534,5 +534,86 @@ buster.testCase("Offer tests", { done(); }); }, + + "//ripple cross currency payment" : + // alice --> [XRP --> carol --> USD/mtgox] --> bob + + function (done) { + var self = this; + var seq; + + self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "carol", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "carol" : "1000/USD/mtgox", + "bob" : "2000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/carol" + }, + callback); + }, + function (callback) { + self.what = "Create offer."; + + self.remote.transaction() + .offer_create("carol", "500", "50/USD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice send USD/mtgox converting from XRP."; + + self.remote.transaction() + .payment("alice", "bob", "25/USD/mtgox") + .send_max("333") + .on('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", "500" ], + "bob" : "100/USD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Verify offer consumed."; + + testutils.verify_offer_not_found(self.remote, "bob", seq, callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, }); // vim:sw=2:sts=2:ts=8 diff --git a/test/server.js b/test/server.js index fde2dfae03..2608c1d73e 100644 --- a/test/server.js +++ b/test/server.js @@ -52,7 +52,7 @@ Server.prototype.once = function (e, c) { }; Server.prototype.serverPath = function() { - return "tmp/server/" + this.name; + return path.resolve("tmp/server", this.name); }; Server.prototype.configPath = function() {