diff --git a/src/ripple/app/ledger/Ledger.cpp b/src/ripple/app/ledger/Ledger.cpp index 88af6bb8afa..173a1100b89 100644 --- a/src/ripple/app/ledger/Ledger.cpp +++ b/src/ripple/app/ledger/Ledger.cpp @@ -1381,6 +1381,67 @@ void Ledger::visitAccountItems ( } +bool Ledger::visitAccountItems ( + Account const& accountID, + uint256 const& startAfter, + std::uint64_t const hint, + unsigned int limit, + std::function func) const +{ + // Visit each item in this account's owner directory + uint256 const rootIndex (Ledger::getOwnerDirIndex (accountID)); + uint256 currentIndex (rootIndex); + + // If startAfter is not zero try jumping to that page using the hint + if (startAfter.isNonZero ()) + { + uint256 const hintIndex (getDirNodeIndex (rootIndex, hint)); + SLE::pointer hintDir (getSLEi (hintIndex)); + if (hintDir != nullptr) + { + for (auto const& node : hintDir->getFieldV256 (sfIndexes)) + { + if (node == startAfter) + { + // We found the hint, we can start here + currentIndex = hintIndex; + break; + } + } + } + } + + bool found (false); + + while (1) + { + SLE::pointer ownerDir (getSLEi (currentIndex)); + + if (!ownerDir || ownerDir->getType () != ltDIR_NODE) + return found; + + for (auto const& node : ownerDir->getFieldV256 (sfIndexes)) + { + if (!found) + { + if (startAfter.isZero () || node == startAfter) + found = true; + } + else if (func (getSLEi (node)) && limit-- <= 1) + { + return found; + } + } + + std::uint64_t const uNodeNext (ownerDir->getFieldU64 (sfIndexNext)); + + if (uNodeNext == 0) + return found; + + currentIndex = Ledger::getDirNodeIndex (rootIndex, uNodeNext); + } +} + static void visitHelper ( std::function& function, SHAMapItem::ref item) { @@ -1784,12 +1845,20 @@ uint256 Ledger::getRippleStateIndex ( { Serializer s (62); - bool const bAltB = a < b; + s.add16 (spaceRipple); // 2 + + if (a < b) + { + s.add160 (a); // 20 + s.add160 (b); // 20 + } + else + { + s.add160 (b); // 20 + s.add160 (a); // 20 + } - s.add16 (spaceRipple); // 2 - s.add160 (bAltB ? a : b); // 20 - s.add160 (bAltB ? b : a); // 20 - s.add160 (currency); // 20 + s.add160 (currency); // 20 return s.getSHA512Half (); } diff --git a/src/ripple/app/ledger/Ledger.h b/src/ripple/app/ledger/Ledger.h index a56764e1aab..6814983dfa1 100644 --- a/src/ripple/app/ledger/Ledger.h +++ b/src/ripple/app/ledger/Ledger.h @@ -286,8 +286,15 @@ class Ledger SLE::pointer getAccountRoot (Account const& accountID) const; SLE::pointer getAccountRoot (const RippleAddress & naAccountID) const; void updateSkipList (); + void visitAccountItems ( - Account const& acctID, std::function) const; + Account const& accountID, std::function) const; + bool visitAccountItems ( + Account const& accountID, + uint256 const& startAfter, // Entry to start after + std::uint64_t const hint, // Hint which page to start at + unsigned int limit, + std::function ) const; void visitStateItems (std::function) const; // database functions (low-level) diff --git a/src/ripple/rpc/handlers/AccountLines.cpp b/src/ripple/rpc/handlers/AccountLines.cpp index 8c3b39824c9..3ab7511f4b3 100644 --- a/src/ripple/rpc/handlers/AccountLines.cpp +++ b/src/ripple/rpc/handlers/AccountLines.cpp @@ -21,6 +21,49 @@ namespace ripple { +struct VisitData +{ + std::vector items; + Account const& accountID; + RippleAddress const& rippleAddressPeer; + Account const& raPeerAccount; +}; + +void addLine (Json::Value& jsonLines, RippleState const& line) +{ + STAmount const& saBalance (line.getBalance ()); + STAmount const& saLimit (line.getLimit ()); + STAmount const& saLimitPeer (line.getLimitPeer ()); + Json::Value& jPeer (jsonLines.append (Json::objectValue)); + + jPeer[jss::account] = to_string (line.getAccountIDPeer ()); + // Amount reported is positive if current account holds other + // account's IOUs. + // + // Amount reported is negative if other account holds current + // account's IOUs. + jPeer[jss::balance] = saBalance.getText (); + jPeer[jss::currency] = saBalance.getHumanCurrency (); + jPeer[jss::limit] = saLimit.getText (); + jPeer[jss::limit_peer] = saLimitPeer.getText (); + jPeer[jss::quality_in] + = static_cast (line.getQualityIn ()); + jPeer[jss::quality_out] + = static_cast (line.getQualityOut ()); + if (line.getAuth ()) + jPeer[jss::authorized] = true; + if (line.getAuthPeer ()) + jPeer[jss::peer_authorized] = true; + if (line.getNoRipple ()) + jPeer[jss::no_ripple] = true; + if (line.getNoRipplePeer ()) + jPeer[jss::no_ripple_peer] = true; + if (line.getFreeze ()) + jPeer[jss::freeze] = true; + if (line.getFreezePeer ()) + jPeer[jss::freeze_peer] = true; +} + // { // account: | // account_index: // optional, defaults to 0. @@ -34,48 +77,53 @@ Json::Value doAccountLines (RPC::Context& context) auto& params = context.params_; Ledger::pointer ledger; - Json::Value result = RPC::lookupLedger (params, ledger, context.netOps_); + Json::Value result (RPC::lookupLedger (params, ledger, context.netOps_)); - if (!ledger) + if (! ledger) return result; - if (!params.isMember (jss::account)) + if (! params.isMember (jss::account)) return RPC::missing_field_error ("account"); - std::string strIdent = params[jss::account].asString (); - bool bIndex = params.isMember (jss::account_index); - int iIndex = bIndex ? params[jss::account_index].asUInt () : 0; - - RippleAddress raAccount; + std::string strIdent (params[jss::account].asString ()); + bool bIndex (params.isMember (jss::account_index)); + int iIndex (bIndex ? params[jss::account_index].asUInt () : 0); + RippleAddress rippleAddress; result = RPC::accountFromString ( - ledger, raAccount, bIndex, strIdent, iIndex, false, context.netOps_); + ledger, rippleAddress, bIndex, strIdent, iIndex, false, context.netOps_); - if (!result.empty ()) + if (! result.empty ()) return result; - std::string strPeer = params.isMember (jss::peer) - ? params[jss::peer].asString () : ""; - bool bPeerIndex = params.isMember (jss::peer_index); - int iPeerIndex = bIndex ? params[jss::peer_index].asUInt () : 0; + if (! ledger->hasAccount (rippleAddress)) + return rpcError (rpcACT_NOT_FOUND); - RippleAddress raPeer; + std::string strPeer (params.isMember (jss::peer) + ? params[jss::peer].asString () : ""); + bool bPeerIndex (params.isMember (jss::peer_index)); + int iPeerIndex (bIndex ? params[jss::peer_index].asUInt () : 0); - if (!strPeer.empty ()) + RippleAddress rippleAddressPeer; + + if (! strPeer.empty ()) { - result[jss::peer] = raAccount.humanAccountID (); + result[jss::peer] = rippleAddress.humanAccountID (); if (bPeerIndex) - result[jss::peer_index] = iPeerIndex; + result[jss::peer_index] = iPeerIndex; - result = RPC::accountFromString ( - ledger, raPeer, bPeerIndex, strPeer, iPeerIndex, false, - context.netOps_); + result = RPC::accountFromString (ledger, rippleAddressPeer, bPeerIndex, strPeer, + iPeerIndex, false, context.netOps_); - if (!result.empty ()) + if (! result.empty ()) return result; } + Account raPeerAccount; + if (rippleAddressPeer.isValid ()) + raPeerAccount = rippleAddressPeer.getAccountID (); + unsigned int limit; if (params.isMember (jss::limit)) { @@ -88,88 +136,83 @@ Json::Value doAccountLines (RPC::Context& context) limit = RPC::Tuning::defaultLinesPerRequest; } - RippleAddress resumeAddress; + Json::Value& jsonLines (result[jss::lines] = Json::arrayValue); + Account const& raAccount(rippleAddress.getAccountID ()); + VisitData visitData = { {}, raAccount, rippleAddressPeer, raPeerAccount }; + unsigned int reserve (limit); + uint256 startAfter; + std::uint64_t startHint; + if (params.isMember (jss::marker)) { - if (!resumeAddress.setAccountID (params[jss::marker].asString ())) + // We have a start point. Use limit - 1 from the result and use the + // very last one for the resume. + Json::Value const& marker (params[jss::marker]); + + if (! marker.isString ()) return rpcError (rpcACT_MALFORMED); - } - if (ledger->hasAccount (raAccount)) + startAfter.SetHex (marker.asString ()); + SLE::pointer sleLine (ledger->getSLEi (startAfter)); + + if (sleLine == nullptr || sleLine->getType () != ltRIPPLE_STATE) + return rpcError (rpcINVALID_PARAMS); + + if (sleLine->getFieldAmount (sfLowLimit).getIssuer () == raAccount) + startHint = sleLine->getFieldU64 (sfLowNode); + else if (sleLine->getFieldAmount (sfHighLimit).getIssuer () == raAccount) + startHint = sleLine->getFieldU64 (sfHighNode); + else + return rpcError (rpcINVALID_PARAMS); + + // Caller provided the first line (startAfter), add it as first result + auto const line (RippleState::makeItem (raAccount, sleLine)); + if (line == nullptr) + return rpcError (rpcINVALID_PARAMS); + + addLine (jsonLines, *line); + visitData.items.reserve (reserve); + } + else { - result[jss::account] = raAccount.humanAccountID (); - Json::Value& jsonLines = (result[jss::lines] = Json::arrayValue); - - bool resume (! resumeAddress.isValid ()); - unsigned int i (0); + startHint = 0; + // We have no start point, limit should be one higher than requested. + visitData.items.reserve (++reserve); + } - for (auto const& item : getRippleStateItems (raAccount.getAccountID (), ledger)) + if (! ledger->visitAccountItems (raAccount, startAfter, startHint, reserve, + [&visitData](SLE::ref sleCur) { - RippleState const& line (*item.get ()); - Account const& lineAccount (line.getAccountIDPeer ()); - - if (! resume && resumeAddress.getAccountID () == lineAccount) - resume = true; - - if (resume && - (!raPeer.isValid () || raPeer.getAccountID () == lineAccount)) + auto const line (RippleState::makeItem (visitData.accountID, sleCur)); + if (line != nullptr && + (! visitData.rippleAddressPeer.isValid () || + visitData.raPeerAccount == line->getAccountIDPeer ())) { - if (i < limit) - { - STAmount const& saBalance = line.getBalance (); - STAmount const& saLimit = line.getLimit (); - STAmount const& saLimitPeer = line.getLimitPeer (); - - Json::Value& jPeer = jsonLines.append (Json::objectValue); - - jPeer[jss::account] = to_string (lineAccount); - // Amount reported is positive if current account holds other - // account's IOUs. - // - // Amount reported is negative if other account holds current - // account's IOUs. - jPeer[jss::balance] = saBalance.getText (); - jPeer[jss::currency] = saBalance.getHumanCurrency (); - jPeer[jss::limit] = saLimit.getText (); - jPeer[jss::limit_peer] = saLimitPeer.getText (); - jPeer[jss::quality_in] - = static_cast (line.getQualityIn ()); - jPeer[jss::quality_out] - = static_cast (line.getQualityOut ()); - if (line.getAuth ()) - jPeer[jss::authorized] = true; - if (line.getAuthPeer ()) - jPeer[jss::peer_authorized] = true; - if (line.getNoRipple ()) - jPeer[jss::no_ripple] = true; - if (line.getNoRipplePeer ()) - jPeer[jss::no_ripple_peer] = true; - if (line.getFreeze ()) - jPeer[jss::freeze] = true; - if (line.getFreezePeer ()) - jPeer[jss::freeze_peer] = true; - - ++i; - } - else - { - result[jss::limit] = limit; - result[jss::marker] = to_string (lineAccount); - break; - } + visitData.items.emplace_back (line); + return true; } - } - - if (! resume) - return rpcError (rpcACT_MALFORMED); - context.loadType_ = Resource::feeMediumBurdenRPC; + return false; + })) + { + return rpcError (rpcINVALID_PARAMS); } - else + + if (visitData.items.size () == reserve) { - result = rpcError (rpcACT_NOT_FOUND); + result[jss::limit] = limit; + + RippleState::pointer line (visitData.items.back ()); + result[jss::marker] = to_string (line->peekSLE ().getIndex ()); + visitData.items.pop_back (); } + result[jss::account] = rippleAddress.humanAccountID (); + + for (auto const& item : visitData.items) + addLine (jsonLines, *item.get ()); + + context.loadType_ = Resource::feeMediumBurdenRPC; return result; } diff --git a/src/ripple/rpc/handlers/AccountOffers.cpp b/src/ripple/rpc/handlers/AccountOffers.cpp index 561cfe143b4..1caabc02052 100644 --- a/src/ripple/rpc/handlers/AccountOffers.cpp +++ b/src/ripple/rpc/handlers/AccountOffers.cpp @@ -44,24 +44,23 @@ Json::Value doAccountOffers (RPC::Context& context) std::string strIdent (params[jss::account].asString ()); bool bIndex (params.isMember (jss::account_index)); - int iIndex (bIndex ? params[jss::account_index].asUInt () : 0); + int const iIndex (bIndex ? params[jss::account_index].asUInt () : 0); - RippleAddress raAccount; + RippleAddress rippleAddress; - result = RPC::accountFromString ( - ledger, raAccount, bIndex, strIdent, iIndex, false, context.netOps_); + result = RPC::accountFromString (ledger, rippleAddress, bIndex, strIdent, + iIndex, false, context.netOps_); if (! result.empty ()) return result; // Get info on account. - - result[jss::account] = raAccount.humanAccountID (); + result[jss::account] = rippleAddress.humanAccountID (); if (bIndex) - result[jss::account_index] = iIndex; + result[jss::account_index] = iIndex; - if (! ledger->hasAccount (raAccount)) + if (! ledger->hasAccount (rippleAddress)) return rpcError (rpcACT_NOT_FOUND); unsigned int limit; @@ -76,99 +75,81 @@ Json::Value doAccountOffers (RPC::Context& context) limit = RPC::Tuning::defaultOffersPerRequest; } - uint256 const rootIndex (Ledger::getOwnerDirIndex (raAccount.getAccountID ())); - std::uint32_t resumeSeq; - uint256 currentIndex; - bool resume (true); - - if (params.isMember (jss::marker)) - { + Account const& raAccount (rippleAddress.getAccountID ()); + Json::Value& jsonOffers (result[jss::offers] = Json::arrayValue); + std::vector offers; + unsigned int reserve (limit); + uint256 startAfter; + std::uint64_t startHint; + + if (params.isMember(jss::marker)) + { + // We have a start point. Use limit - 1 from the result and use the + // very last one for the resume. Json::Value const& marker (params[jss::marker]); - if (! marker.isObject () || marker.size () != 2 || - ! marker.isMember (jss::seq) || ! marker[jss::seq].isIntegral () || - ! marker.isMember (jss::account_index) || - ! marker[jss::account_index].isString ()) - { + if (! marker.isString ()) return rpcError (rpcACT_MALFORMED); + + startAfter.SetHex (marker.asString ()); + SLE::pointer sleOffer (ledger->getSLEi (startAfter)); + + if (sleOffer == nullptr || + sleOffer->getType () != ltOFFER || + raAccount != sleOffer->getFieldAccount160 (sfAccount)) + { + return rpcError (rpcINVALID_PARAMS); } - resumeSeq = marker[jss::seq].asUInt (); - currentIndex = Ledger::getDirNodeIndex (rootIndex, - uintFromHex (marker[jss::account_index].asString ())); + startHint = sleOffer->getFieldU64(sfOwnerNode); + + // Caller provided the first offer (startAfter), add it as first result + Json::Value& obj (jsonOffers.append (Json::objectValue)); + sleOffer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]); + sleOffer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]); + obj[jss::seq] = sleOffer->getFieldU32 (sfSequence); + obj[jss::flags] = sleOffer->getFieldU32 (sfFlags); - resume = false; + offers.reserve (reserve); } else { - currentIndex = rootIndex; + startHint = 0; + // We have no start point, limit should be one higher than requested. + offers.reserve (++reserve); } - Json::Value& jvsOffers(result[jss::offers] = Json::arrayValue); - unsigned int i (0); - bool process (true); - - while (process) - { - SLE::pointer ownerDir (ledger->getSLEi (currentIndex)); - - if (!ownerDir || ownerDir->getType () != ltDIR_NODE) - break; - - for (auto const& node : ownerDir->getFieldV256 (sfIndexes).peekValue ()) + if (! ledger->visitAccountItems (raAccount, startAfter, startHint, reserve, + [&offers](SLE::ref offer) { - SLE::ref offer (ledger->getSLEi (node)); - if (offer->getType () == ltOFFER) { - std::uint32_t const seq (offer->getFieldU32 (sfSequence)); - - if (!resume && resumeSeq == seq) - resume = true; - - if (resume) - { - if (i < limit) - { - Json::Value& obj (jvsOffers.append (Json::objectValue)); - offer->getFieldAmount (sfTakerPays).setJson ( - obj[jss::taker_pays]); - offer->getFieldAmount (sfTakerGets).setJson ( - obj[jss::taker_gets]); - obj[jss::seq] = seq; - obj[jss::flags] = offer->getFieldU32 (sfFlags); - - ++i; - } - else - { - result[jss::limit] = limit; - - Json::Value& marker (result[jss::marker] = Json::objectValue); - marker[jss::seq] = seq; - marker[jss::account_index] = strHex( - ownerDir->getFieldU64 (sfIndexPrevious)); - - process = false; - break; - } - } + offers.emplace_back (offer); + return true; } - } - if (process) - { - std::uint64_t const uNodeNext(ownerDir->getFieldU64(sfIndexNext)); + return false; + })) + { + return rpcError (rpcINVALID_PARAMS); + } - if (!uNodeNext) - break; + if (offers.size () == reserve) + { + result[jss::limit] = limit; - currentIndex = Ledger::getDirNodeIndex(rootIndex, uNodeNext); - } + result[jss::marker] = to_string (offers.back ()->getIndex ()); + offers.pop_back (); } - if (!resume) - return rpcError (rpcACT_MALFORMED); + for (auto const& offer : offers) + { + Json::Value& obj (jsonOffers.append (Json::objectValue)); + offer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]); + offer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]); + obj[jss::seq] = offer->getFieldU32 (sfSequence); + obj[jss::flags] = offer->getFieldU32 (sfFlags); + } context.loadType_ = Resource::feeMediumBurdenRPC; return result;