diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 361de64ad49..7283f16c97b 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -280,6 +280,7 @@ JSS(hotwallet); // in: GatewayBalances JSS(id); // websocket. JSS(ident); // in: AccountCurrencies, AccountInfo, // OwnerInfo +JSS(ignore_default); // in: AccountLines JSS(inLedger); // out: tx/Transaction JSS(inbound); // out: PeerImp JSS(index); // in: LedgerEntry, DownloadShard diff --git a/src/ripple/rpc/handlers/AccountLines.cpp b/src/ripple/rpc/handlers/AccountLines.cpp index 2e1dac1d71d..ad3cd9f84d2 100644 --- a/src/ripple/rpc/handlers/AccountLines.cpp +++ b/src/ripple/rpc/handlers/AccountLines.cpp @@ -36,6 +36,10 @@ struct VisitData AccountID const& accountID; bool hasPeer; AccountID const& raPeerAccount; + + bool ignoreDefault; + uint32_t foundCount; + RippleState::pointer lastFound; }; void @@ -78,6 +82,8 @@ addLine(Json::Value& jsonLines, RippleState const& line) // ledger_index : // limit: integer // optional // marker: opaque // optional, resume previous query +// ignore_default: bool // do not return lines in default state (on +// this account's side) // } Json::Value doAccountLines(RPC::JsonContext& context) @@ -124,8 +130,14 @@ doAccountLines(RPC::JsonContext& context) if (auto err = readLimitField(limit, RPC::Tuning::accountLines, context)) return *err; + // this flag allows the requester to ask incoming trustlines in default + // state be omitted + bool ignoreDefault = params.isMember(jss::ignore_default) && + params[jss::ignore_default].asBool(); + Json::Value& jsonLines(result[jss::lines] = Json::arrayValue); - VisitData visitData = {{}, accountID, hasPeer, raPeerAccount}; + VisitData visitData = { + {}, accountID, hasPeer, raPeerAccount, ignoreDefault, 0, nullptr}; unsigned int reserve(limit); uint256 startAfter; std::uint64_t startHint; @@ -147,20 +159,38 @@ doAccountLines(RPC::JsonContext& context) if (!sleLine) return rpcError(rpcINVALID_PARAMS); + bool isDefault = false; if (sleLine->getFieldAmount(sfLowLimit).getIssuer() == accountID) + { startHint = sleLine->getFieldU64(sfLowNode); + isDefault = !(sleLine->getFieldU32(sfFlags) & lsfLowReserve); + } else if (sleLine->getFieldAmount(sfHighLimit).getIssuer() == accountID) + { startHint = sleLine->getFieldU64(sfHighNode); + isDefault = !(sleLine->getFieldU32(sfFlags) & lsfHighReserve); + } else return rpcError(rpcINVALID_PARAMS); // Caller provided the first line (startAfter), add it as first result - auto const line = RippleState::makeItem(accountID, sleLine); - if (line == nullptr) - return rpcError(rpcINVALID_PARAMS); + // (but only if it meets inclusion criteria) + + if (isDefault && ignoreDefault) + { + // even though we're starting our search here we don't include the + // first entry in this edge case + visitData.items.reserve(++reserve); + } + else + { + auto const line = RippleState::makeItem(accountID, sleLine); + if (line == nullptr) + return rpcError(rpcINVALID_PARAMS); - addLine(jsonLines, *line); - visitData.items.reserve(reserve); + addLine(jsonLines, *line); + visitData.items.reserve(reserve); + } } else { @@ -177,13 +207,30 @@ doAccountLines(RPC::JsonContext& context) startHint, reserve, [&visitData](std::shared_ptr const& sleCur) { + bool ignore = false; + if (visitData.ignoreDefault) + { + if (sleCur->getFieldAmount(sfLowLimit).getIssuer() == + visitData.accountID) + ignore = + !(sleCur->getFieldU32(sfFlags) & lsfLowReserve); + else + ignore = !( + sleCur->getFieldU32(sfFlags) & lsfHighReserve); + } + auto const line = RippleState::makeItem(visitData.accountID, sleCur); if (line != nullptr && (!visitData.hasPeer || visitData.raPeerAccount == line->getAccountIDPeer())) { - visitData.items.emplace_back(line); + if (!ignore) + visitData.items.emplace_back(line); + + visitData.lastFound = line; + visitData.foundCount++; + return true; } @@ -194,13 +241,20 @@ doAccountLines(RPC::JsonContext& context) } } - if (visitData.items.size() == reserve) + // RH Note: + // If ignore_default flag is present all lines must still be iterated, the + // flag only suppresses output. It does not change how iteration works. This + // means the RPC call may return an empty set AND a marker. In this case + // another query must be made until iteration is complete if a complete set + // of non-default state lines are required. + if (visitData.items.size() == reserve || visitData.foundCount >= reserve) { result[jss::limit] = limit; - RippleState::pointer line(visitData.items.back()); + RippleState::pointer line(visitData.lastFound); result[jss::marker] = to_string(line->key()); - visitData.items.pop_back(); + if (visitData.items.back() == visitData.lastFound) + visitData.items.pop_back(); } result[jss::account] = context.app.accountIDCache().toBase58(accountID);