diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index 705d6fe6ba..6224869405 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -134,49 +134,70 @@ Json::Value RPCParser::parseInternal(const Json::Value& jvParams) return v; } -// account_tx -// account_tx -// account_tx binary -// account_tx binary +// account_tx accountID [ledger_min [ledger_max [limit [offset]]]] [binary] [count] [descending] Json::Value RPCParser::parseAccountTransactions(const Json::Value& jvParams) { Json::Value jvRequest(Json::objectValue); RippleAddress raAccount; - - unsigned size = jvParams.size(); - - if ((size > 1) && (jvParams[size - 1].asString() == "binary")) - { - jvRequest["binary"] = true; - --size; - } - - if (size < 2 || size > 3) - return rpcError(rpcINVALID_PARAMS); + unsigned int iParams = jvParams.size(); if (!raAccount.setAccountID(jvParams[0u].asString())) return rpcError(rpcACT_MALFORMED); - // YYY This could be more strict and report casting errors. - if (size == 2) + jvRequest["account"] = raAccount.humanAccountID(); + + bool bDone = false; + + while (!bDone && iParams >= 2) { + if (jvParams[iParams-1].asString() == "binary") + { + jvRequest["binary"] = true; + --iParams; + } + else if (jvParams[iParams-1].asString() == "count") + { + jvRequest["count"] = true; + --iParams; + } + else if (jvParams[iParams-1].asString() == "descending") + { + jvRequest["descending"] = true; + --iParams; + } + else + { + bDone = true; + } + } + + if (1 == iParams) { - jvRequest["ledger"] = jvParams[1u].asUInt(); + nothing(); + } + else if (2 == iParams) + { + if (!jvParseLedger(jvRequest, jvParams[1u].asString())) + return jvRequest; } else { - uint32 uLedgerMin = jvParams[1u].asUInt(); - uint32 uLedgerMax = jvParams[2u].asUInt(); + int64 uLedgerMin = jvParams[1u].asInt(); + int64 uLedgerMax = jvParams[2u].asInt(); - if ((uLedgerMax < uLedgerMin) || (uLedgerMax == 0)) + if (uLedgerMax != -1 && uLedgerMax < uLedgerMin) { return rpcError(rpcLGR_IDXS_INVALID); } - jvRequest["ledger_min"] = uLedgerMin; - jvRequest["ledger_max"] = uLedgerMax; - } + jvRequest["ledger_index_min"] = jvParams[1u].asInt(); + jvRequest["ledger_index_max"] = jvParams[2u].asInt(); - jvRequest["account"] = raAccount.humanAccountID(); + if (iParams >= 4) + jvRequest["limit"] = jvParams[3u].asInt(); + + if (iParams >= 5) + jvRequest["offset"] = jvParams[4u].asInt(); + } return jvRequest; } @@ -626,7 +647,7 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams) { "account_info", &RPCParser::parseAccountItems, 1, 2 }, { "account_lines", &RPCParser::parseAccountItems, 1, 2 }, { "account_offers", &RPCParser::parseAccountItems, 1, 2 }, - { "account_tx", &RPCParser::parseAccountTransactions, 2, 4 }, + { "account_tx", &RPCParser::parseAccountTransactions, 1, 8 }, { "book_offers", &RPCParser::parseBookOffers, 2, 7 }, { "connect", &RPCParser::parseConnect, 1, 2 }, { "consensus_info", &RPCParser::parseAsIs, 0, 0 }, diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index 3a9f5fd2b7..6aa584ca58 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -1065,12 +1065,12 @@ void NetworkOPs::setMode(OperatingMode om) std::string - NetworkOPs::transactionsSQL(std::string selection, const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, uint32 limit, bool binary, bool bAdmin) + NetworkOPs::transactionsSQL(std::string selection, const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool binary, bool bAdmin) { uint32 NONBINARY_PAGE_LENGTH = 200; uint32 BINARY_PAGE_LENGTH = 500; uint32 numberOfResults = limit; - if (limit == 0) {numberOfResults = std::numeric_limits::max();} + if (limit == -1) {numberOfResults = std::numeric_limits::max();} if (!bAdmin) { if (numberOfResults < (binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH)) { numberOfResults = (binary ? BINARY_PAGE_LENGTH : NONBINARY_PAGE_LENGTH); @@ -1106,7 +1106,7 @@ std::string } std::vector< std::pair > - NetworkOPs::getAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, uint32 limit, bool bAdmin) + NetworkOPs::getAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool bAdmin) { // can be called with no locks std::vector< std::pair > ret; @@ -1139,7 +1139,7 @@ std::vector< std::pair > } std::vector NetworkOPs::getAccountTxsB( - const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, uint32 limit, bool bAdmin) + const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool bAdmin) { // can be called with no locks std::vector< txnMetaLedgerType> ret; @@ -1184,10 +1184,10 @@ std::vector NetworkOPs::getAccountTxsB( uint32 - NetworkOPs::countAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger, uint32 offset) + NetworkOPs::countAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger) { // can be called with no locks uint32 ret = 0; - std::string sql = NetworkOPs::transactionsSQL("COUNT(1) AS 'TransactionCount'", account, minLedger, maxLedger, false, offset, 0, true, true); + std::string sql = NetworkOPs::transactionsSQL("COUNT(*) AS 'TransactionCount'", account, minLedger, maxLedger, false, 0, -1, true, true); Database* db = theApp->getTxnDB()->getDB(); ScopedLock sl(theApp->getTxnDB()->getDBLock()); diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 57b86246cb..f75757e5a2 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -294,20 +294,20 @@ public: bool isWantedHash(const uint256& h, bool remove); //Helper function to generate SQL query to get transactions - std::string transactionsSQL(std::string selection, const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, uint32 limit, bool binary, bool bAdmin); + std::string transactionsSQL(std::string selection, const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool binary, bool bAdmin); // client information retrieval functions std::vector< std::pair > - getAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, uint32 limit, bool bAdmin); + getAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool bAdmin); typedef boost::tuple txnMetaLedgerType; std::vector - getAccountTxsB(const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, uint32 limit, bool bAdmin); + getAccountTxsB(const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool bAdmin); std::vector getLedgerAffectedAccounts(uint32 ledgerSeq); std::vector getLedgerTransactions(uint32 ledgerSeq); - uint32 countAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger, uint32 offset); + uint32 countAccountTxs(const RippleAddress& account, int32 minLedger, int32 maxLedger); // // Monitoring: publisher side // diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index 8a505aa603..6efa15aed3 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -1656,23 +1656,29 @@ Json::Value RPCHandler::doLedger(Json::Value jvRequest, int& cost) return ret; } -// { account: , ledger: } -// { account: , ledger_min: , ledger_max: } -// THIS ROUTINE DOESN'T SCALE. -// FIXME: Require admin. -// FIXME: Doesn't report database holes. -// FIXME: For consistency change inputs to: ledger_index, ledger_index_min, ledger_index_max. +// { +// account: account, +// ledger_index_min: ledger_index, +// ledger_index_max: ledger_index, +// binary: boolean, // optional, defaults to false +// count: boolean, // optional, defaults to false +// descending: boolean, // optional, defaults to false +// offset: integer, // optional, defaults to 0 +// limit: integer // optional +// } Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest, int& cost) { + // XXX Perhaps don't allow a limit of 0. RippleAddress raAccount; - int32 minLedger; - int32 maxLedger; - bool descending = false; - uint32 offset = 0; - uint32 limit = 0; - bool count = false; - - bool allValidated = true; + uint32 offset = jvRequest.isMember("offset") ? jvRequest["offset"].asUInt() : 0; + int limit = jvRequest.isMember("limit") ? jvRequest["limit"].asUInt() : -1; + bool descending = jvRequest.isMember("descending") && jvRequest["descending"].asBool(); + bool count = jvRequest.isMember("count") && jvRequest["count"].asBool(); + uint32 uLedgerMin; + uint32 uLedgerMax; + uint32 uValidatedMin; + uint32 uValidatedMax; + bool bValidated = mNetOps->getValidatedRange(uValidatedMin, uValidatedMax); if (!jvRequest.isMember("account")) return rpcError(rpcINVALID_PARAMS); @@ -1680,10 +1686,23 @@ Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest, int& cost) if (!raAccount.setAccountID(jvRequest["account"].asString())) return rpcError(rpcACT_MALFORMED); - if (jvRequest.isMember("ledger_min") && jvRequest.isMember("ledger_max")) + if (jvRequest.isMember("ledger_index_min") || jvRequest.isMember("ledger_index_max")) { - minLedger = jvRequest["ledger_min"].asInt(); - maxLedger = jvRequest["ledger_max"].asInt(); + int64 iLedgerMin = jvRequest.isMember("ledger_index_min") ? jvRequest["ledger_index_min"].asInt() : -1; + int64 iLedgerMax = jvRequest.isMember("ledger_index_max") ? jvRequest["ledger_index_max"].asInt() : -1; + + if (!bValidated && (iLedgerMin == -1 || iLedgerMax == -1)) { + // Don't have a validated ledger range. + return rpcError(rpcLGR_IDXS_INVALID); + } + + uLedgerMin = iLedgerMin == -1 ? uValidatedMin : iLedgerMin; + uLedgerMax = iLedgerMax == -1 ? uValidatedMax : iLedgerMax; + + if (uLedgerMax < uLedgerMin) + { + return rpcError(rpcLGR_IDXS_INVALID); + } } else { @@ -1691,103 +1710,73 @@ Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest, int& cost) Json::Value ret = lookupLedger(jvRequest, l); if (!l) return ret; - minLedger = maxLedger = l->getLedgerSeq(); - } - - if ((maxLedger < minLedger) || (maxLedger == 0)) - { - return rpcError(rpcLGR_IDXS_INVALID); - } - - if (jvRequest.isMember("descending")) - { - descending = jvRequest["descending"].asBool(); - } - if (jvRequest.isMember("offset")) - { - offset = jvRequest["offset"].asUInt(); - } - if (jvRequest.isMember("limit")) - { - limit = jvRequest["limit"].asUInt(); - } - if (jvRequest.isMember("count")) - { - count = jvRequest["count"].asBool(); + uLedgerMin = uLedgerMax = l->getLedgerSeq(); } #ifndef DEBUG try { #endif - unsigned int vl = mNetOps->getValidatedSeq(); ScopedUnlock su(theApp->getMasterLock()); Json::Value ret(Json::objectValue); + ret["account"] = raAccount.humanAccountID(); if (jvRequest.isMember("binary") && jvRequest["binary"].asBool()) { std::vector txns = - mNetOps->getAccountTxsB(raAccount, minLedger, maxLedger, descending, offset, limit, mRole == ADMIN); + mNetOps->getAccountTxsB(raAccount, uLedgerMin, uLedgerMax, descending, offset, limit, mRole == ADMIN); for (std::vector::const_iterator it = txns.begin(), end = txns.end(); it != end; ++it) { - Json::Value obj(Json::objectValue); - obj["transaction"] = it->get<0>(); - obj["meta"] = it->get<1>(); - obj["inLedger"] = it->get<2>(); - if (it->get<2>() > vl) { - obj["validated"] = false; - allValidated = false; - } - else if (mNetOps->haveLedger(it->get<2>())) - obj["validated"] = true; - else { - //return rpcError(rpcLGR_NOT_FOUND); - } - ret["transactions"].append(obj); + Json::Value jvObj(Json::objectValue); + uint32 uLedgerIndex = it->get<2>(); + + jvObj["tx_blob"] = it->get<0>(); + jvObj["meta"] = it->get<1>(); + jvObj["ledger_index"] = uLedgerIndex; + jvObj["validated"] = bValidated && uValidatedMin <= uLedgerIndex && uValidatedMax >= uLedgerIndex; + + ret["transactions"].append(jvObj); } } else { - std::vector< std::pair > txns = mNetOps->getAccountTxs(raAccount, minLedger, maxLedger, descending, offset, limit, mRole == ADMIN); + std::vector< std::pair > txns = mNetOps->getAccountTxs(raAccount, uLedgerMin, uLedgerMax, descending, offset, limit, mRole == ADMIN); + for (std::vector< std::pair >::iterator it = txns.begin(), end = txns.end(); it != end; ++it) { - Json::Value obj(Json::objectValue); + Json::Value jvObj(Json::objectValue); if (it->first) - obj["tx"] = it->first->getJson(1); + jvObj["tx"] = it->first->getJson(1); + if (it->second) { - obj["meta"] = it->second->getJson(0); + uint32 uLedgerIndex = it->second->getLgrSeq(); - uint32 s = it->second->getLgrSeq(); - if (s > vl) { - obj["validated"] = false; - allValidated = false; - } - else if (mNetOps->haveLedger(s)) - obj["validated"] = true; - else { - //return rpcError(rpcLGR_NOT_FOUND); //Can this also happen when s > vl? - } + jvObj["meta"] = it->second->getJson(0); + jvObj["validated"] = bValidated && uValidatedMin <= uLedgerIndex && uValidatedMax >= uLedgerIndex; } - ret["transactions"].append(obj); + ret["transactions"].append(jvObj); } } //Add information about the original query - ret["validated"] = allValidated; - if(count) { - ret["count"] = mNetOps->countAccountTxs(raAccount, minLedger, maxLedger, offset); - } - ret["limit"] = limit; - ret["ledger_min"] = minLedger; - ret["ledger_max"] = maxLedger; - ret["offset"] = offset; + ret["ledger_index_min"] = uLedgerMin; + ret["ledger_index_max"] = uLedgerMax; + ret["validated"] = bValidated && uValidatedMin <= uLedgerMin && uValidatedMax >= uLedgerMax; + ret["offset"] = offset; + + if (count) + ret["count"] = mNetOps->countAccountTxs(raAccount, uLedgerMin, uLedgerMax); + + if (jvRequest.isMember("limit")) + ret["limit"] = limit; + return ret; #ifndef DEBUG diff --git a/src/cpp/ripple/main.cpp b/src/cpp/ripple/main.cpp index f3ebac2643..51852d96a3 100644 --- a/src/cpp/ripple/main.cpp +++ b/src/cpp/ripple/main.cpp @@ -73,7 +73,7 @@ void printHelp(const po::options_description& desc) cerr << " account_info |||| []" << endl; cerr << " account_lines || []" << endl; cerr << " account_offers || []" << endl; - cerr << " account_tx || |( )" << endl; + cerr << " account_tx accountID [ledger_min [ledger_max [limit [offset]]]] [binary] [count] [descending]" << endl; cerr << " book_offers [ [ [ []]]]]" << endl; cerr << " connect []" << endl; cerr << " consensus_info" << endl;