diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index d4eeb154a0..a0a99df846 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -500,8 +500,9 @@ Json::Value RPCHandler::authorize(Ledger::ref lrLedger, } // --> strIdent: public key, account ID, or regular seed. +// --> bStrict: Only allow account id or public key. // <-- bIndex: true if iIndex > 0 and used the index. -Json::Value RPCHandler::accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex) +Json::Value RPCHandler::accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex, const bool bStrict) { RippleAddress naSeed; @@ -510,6 +511,10 @@ Json::Value RPCHandler::accountFromString(Ledger::ref lrLedger, RippleAddress& n // Got the account. bIndex = false; } + else if (bStrict) + { + return rpcError(rpcACT_MALFORMED); + } // Must be a seed. else if (!naSeed.setSeedGeneric(strIdent)) { @@ -570,6 +575,7 @@ Json::Value RPCHandler::doAcceptLedger(Json::Value jvRequest) // { // ident : , // account_index : // optional +// strict: // true, only allow public keys and addresses. false, default. // ledger_hash : // ledger_index : // } @@ -587,11 +593,12 @@ Json::Value RPCHandler::doAccountInfo(Json::Value jvRequest) std::string strIdent = jvRequest["ident"].asString(); bool bIndex; int iIndex = jvRequest.isMember("account_index") ? jvRequest["account_index"].asUInt() : 0; + bool bStrict = jvRequest.isMember("strict") && jvRequest["strict"].asBool(); RippleAddress naAccount; // Get info on account. - Json::Value jvAccepted = accountFromString(lpLedger, naAccount, bIndex, strIdent, iIndex); + Json::Value jvAccepted = accountFromString(lpLedger, naAccount, bIndex, strIdent, iIndex, bStrict); if (!jvAccepted.empty()) return jvAccepted; @@ -746,7 +753,7 @@ Json::Value RPCHandler::doNicknameInfo(Json::Value params) // 'ident' : , // 'account_index' : // optional // } -// XXX This would be better if it too the ledger. +// XXX This would be better if it took the ledger. Json::Value RPCHandler::doOwnerInfo(Json::Value jvRequest) { if (!jvRequest.isMember("ident")) @@ -761,11 +768,11 @@ Json::Value RPCHandler::doOwnerInfo(Json::Value jvRequest) // Get info on account. - Json::Value jAccepted = accountFromString(mNetOps->getClosedLedger(), raAccount, bIndex, strIdent, iIndex); + Json::Value jAccepted = accountFromString(mNetOps->getClosedLedger(), raAccount, bIndex, strIdent, iIndex, false); ret["accepted"] = jAccepted.empty() ? mNetOps->getOwnerInfo(mNetOps->getClosedLedger(), raAccount) : jAccepted; - Json::Value jCurrent = accountFromString(mNetOps->getCurrentLedger(), raAccount, bIndex, strIdent, iIndex); + Json::Value jCurrent = accountFromString(mNetOps->getCurrentLedger(), raAccount, bIndex, strIdent, iIndex, false); ret["current"] = jCurrent.empty() ? mNetOps->getOwnerInfo(mNetOps->getCurrentLedger(), raAccount) : jCurrent; @@ -901,7 +908,7 @@ Json::Value RPCHandler::doAccountLines(Json::Value jvRequest) RippleAddress raAccount; - jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex); + jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex, false); if (!jvResult.empty()) return jvResult; @@ -979,7 +986,7 @@ Json::Value RPCHandler::doAccountOffers(Json::Value jvRequest) RippleAddress raAccount; - jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex); + jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex, false); if (!jvResult.empty()) return jvResult; @@ -2090,7 +2097,7 @@ Json::Value RPCHandler::lookupLedger(Json::Value jvRequest, Ledger::pointer& lpL Json::Value jvResult; uint256 uLedger = jvRequest.isMember("ledger_hash") ? uint256(jvRequest["ledger_hash"].asString()) : 0; - uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0; + int32 iLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asInt() : -2; if (!!uLedger) { @@ -2100,38 +2107,51 @@ Json::Value RPCHandler::lookupLedger(Json::Value jvRequest, Ledger::pointer& lpL if (!lpLedger) { jvResult["error"] = "ledgerNotFound"; + return jvResult; } - uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed. + iLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed. } - else if (!!uLedgerIndex) + if (-1 == iLedgerIndex) { - lpLedger = mNetOps->getLedgerBySeq(uLedgerIndex); + lpLedger = theApp->getLedgerMaster().getClosedLedger(); + iLedgerIndex = lpLedger->getLedgerSeq(); + } + if (-2 == iLedgerIndex) + { + // Default to current ledger. + lpLedger = mNetOps->getCurrentLedger(); + iLedgerIndex = lpLedger->getLedgerSeq(); + } + else if (iLedgerIndex <= 0) + { + jvResult["error"] = "ledgerNotFound"; + + return jvResult; + } + else if (iLedgerIndex) + { + lpLedger = mNetOps->getLedgerBySeq(iLedgerIndex); if (!lpLedger) { jvResult["error"] = "ledgerNotFound"; // ledger_index from future? + return jvResult; } } - else - { - // Default to current ledger. - lpLedger = mNetOps->getCurrentLedger(); - uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index. - } if (lpLedger->isClosed()) { if (!!uLedger) jvResult["ledger_hash"] = uLedger.ToString(); - jvResult["ledger_index"] = uLedgerIndex; + jvResult["ledger_index"] = iLedgerIndex; } else { - jvResult["ledger_current_index"] = uLedgerIndex; + jvResult["ledger_current_index"] = iLedgerIndex; } return jvResult; diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index 91a0c11f2e..07aec9944e 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -41,7 +41,7 @@ class RPCHandler const RippleAddress& naVerifyGenerator); Json::Value accounts(Ledger::ref lrLedger, const RippleAddress& naMasterGenerator); - Json::Value accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex); + Json::Value accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex, const bool bStrict); Json::Value doAcceptLedger(Json::Value jvRequest); diff --git a/src/cpp/ripple/TransactionMeta.cpp b/src/cpp/ripple/TransactionMeta.cpp index bd87829a72..99ea1e4c38 100644 --- a/src/cpp/ripple/TransactionMeta.cpp +++ b/src/cpp/ripple/TransactionMeta.cpp @@ -68,6 +68,8 @@ std::vector TransactionMetaSet::getAffectedAccounts() std::vector accounts; accounts.reserve(10); + // This code should match the behavior of the JS method: + // Meta#getAffectedAccounts BOOST_FOREACH(const STObject& it, mNodes) { int index = it.getFieldIndex((it.getFName() == sfCreatedNode) ? sfNewFields : sfFinalFields); diff --git a/src/js/meta.js b/src/js/meta.js index ac0e108c90..5d93446da6 100644 --- a/src/js/meta.js +++ b/src/js/meta.js @@ -1,4 +1,6 @@ var extend = require('extend'); +var UInt160 = require('./uint160').UInt160; +var Amount = require('./amount').Amount; /** * Meta data processing facility. @@ -71,6 +73,38 @@ Meta.prototype.each = function (fn) for (var i = 0, l = this.nodes.length; i < l; i++) { fn(this.nodes[i], i); } -} +}; + +var amountFieldsAffectingIssuer = [ + "LowLimit", "HighLimit", "TakerPays", "TakerGets" +]; +Meta.prototype.getAffectedAccounts = function () +{ + var accounts = []; + + // This code should match the behavior of the C++ method: + // TransactionMetaSet::getAffectedAccounts + this.each(function (an) { + var fields = (an.diffType === "CreatedNode") ? an.fieldsNew : an.fieldsFinal; + + for (var i in fields) { + var field = fields[i]; + + if ("string" === typeof field && UInt160.is_valid(field)) { + accounts.push(field); + } else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) { + var amount = Amount.from_json(field); + var issuer = amount.issuer(); + if (issuer.is_valid() && !issuer.is_zero()) { + accounts.push(issuer.to_json()); + } + } + } + }); + + console.log("AFFECTS", accounts); + + return accounts; +}; exports.Meta = Meta; diff --git a/src/js/remote.js b/src/js/remote.js index 0bb467bc43..9fdd70f667 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -573,23 +573,23 @@ Remote.prototype._connect_message = function (ws, json) { case 'account': // XXX If not trusted, need proof. + if (this.trace) utils.logObject("remote: account: %s", message); // Process metadata message.mmeta = new Meta(message.meta); // Pass the event on to any related Account objects - message.mmeta.each(function (an) { - if (an.entryType === 'AccountRoot') { - var account = self._accounts[an.fields.Account]; + var affected = message.mmeta.getAffectedAccounts(); + for (var i = 0, l = affected.length; i < l; i++) { + var account = self._accounts[affected[i]]; - // Only trigger the event if the account object is actually - // subscribed - this prevents some weird phantom events from - // occurring. - if (account && account._subs) { - account.emit('transaction', message); - } + // Only trigger the event if the account object is actually + // subscribed - this prevents some weird phantom events from + // occurring. + if (account && account._subs) { + account.emit('transaction', message); } - }); + } this.emit('account', message); break; diff --git a/src/js/uint.js b/src/js/uint.js index 100dfc9c80..c02d8b27ca 100644 --- a/src/js/uint.js +++ b/src/js/uint.js @@ -92,6 +92,10 @@ UInt.prototype.is_valid = function () { return this._value instanceof BigInteger; }; +UInt.prototype.is_zero = function () { + return this._value.equals(BigInteger.ZERO); +}; + // value = NaN on error. UInt.prototype.parse_generic = function (j) { // Canonicalize and validate diff --git a/test/path-test.js b/test/path-test.js index 4c021ce568..00fbaef411 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -906,7 +906,6 @@ buster.testCase("Via offers", { // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), 'tearDown' : testutils.build_teardown(), - // XXX Triggers bad path expansion. "Via gateway" : // carol holds mtgoxAUD, sells mtgoxAUD for XRP // bob will hold mtgoxAUD @@ -1138,4 +1137,53 @@ buster.testCase("Via offers", { }, }); +buster.testCase("Indirect paths", { + // 'setUp' : testutils.build_setup(), + 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), + 'tearDown' : testutils.build_teardown(), + + "//path find" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "bob" : "1000/USD/alice", + "carol" : "2000/USD/bob", + }, + callback); + }, + function (callback) { + self.what = "Find path from alice to carol"; + + self.remote.request_ripple_path_find("alice", "carol", "5/USD/carol", + [ { 'currency' : "USD" } ]) + .on('success', function (m) { + console.log("proposed: %s", JSON.stringify(m)); + + // 1 alternative. + buster.assert.equals(1, m.alternatives.length) + // Path is empty. + buster.assert.equals(0, m.alternatives[0].paths_canonical.length) + + callback(); + }) + .request(); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); // vim:sw=2:sts=2:ts=8:et