rippled
Loading...
Searching...
No Matches
AccountObjects.cpp
1#include <xrpld/rpc/Context.h>
2#include <xrpld/rpc/detail/RPCHelpers.h>
3#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
4#include <xrpld/rpc/detail/Tuning.h>
5
6#include <xrpl/ledger/ReadView.h>
7#include <xrpl/protocol/ErrorCodes.h>
8#include <xrpl/protocol/Indexes.h>
9#include <xrpl/protocol/LedgerFormats.h>
10#include <xrpl/protocol/RPCErr.h>
11#include <xrpl/protocol/jss.h>
12#include <xrpl/protocol/nftPageMask.h>
13#include <xrpl/resource/Fees.h>
14
15#include <string>
16
17namespace xrpl {
18
28bool
30 ReadView const& ledger,
31 AccountID const& account,
33 uint256 dirIndex,
34 uint256 entryIndex,
35 std::uint32_t const limit,
36 Json::Value& jvResult)
37{
38 // check if dirIndex is valid
39 if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex}))
40 return false;
41
42 auto typeMatchesFilter = [](std::vector<LedgerEntryType> const& typeFilter,
43 LedgerEntryType ledgerType) {
44 auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType);
45 return it != typeFilter.end();
46 };
47
48 // if dirIndex != 0, then all NFTs have already been returned. only
49 // iterate NFT pages if the filter says so AND dirIndex == 0
50 bool iterateNFTPages =
51 (!typeFilter.has_value() || typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) &&
52 dirIndex == beast::zero;
53
54 Keylet const firstNFTPage = keylet::nftpage_min(account);
55
56 // we need to check the marker to see if it is an NFTTokenPage index.
57 if (iterateNFTPages && entryIndex != beast::zero)
58 {
59 // if it is we will try to iterate the pages up to the limit
60 // and then change over to the owner directory
61
62 if (firstNFTPage.key != (entryIndex & ~nft::pageMask))
63 iterateNFTPages = false;
64 }
65
66 auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue);
67
68 // this is a mutable version of limit, used to seamlessly switch
69 // to iterating directory entries when nftokenpages are exhausted
70 uint32_t mlimit = limit;
71
72 // iterate NFTokenPages preferentially
73 if (iterateNFTPages)
74 {
75 Keylet const first =
76 entryIndex == beast::zero ? firstNFTPage : Keylet{ltNFTOKEN_PAGE, entryIndex};
77
78 Keylet const last = keylet::nftpage_max(account);
79
80 // current key
81 uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key);
82
83 // current page
84 auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck});
85
86 while (cp)
87 {
88 jvObjects.append(cp->getJson(JsonOptions::none));
89 auto const npm = (*cp)[~sfNextPageMin];
90 if (npm)
91 {
92 cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm));
93 }
94 else
95 {
96 cp = nullptr;
97 }
98
99 if (--mlimit == 0)
100 {
101 if (cp)
102 {
103 jvResult[jss::limit] = limit;
104 jvResult[jss::marker] = std::string("0,") + to_string(ck);
105 return true;
106 }
107 }
108
109 if (!npm)
110 break;
111
112 ck = *npm;
113 }
114
115 // if execution reaches here then we're about to transition
116 // to iterating the root directory (and the conventional
117 // behaviour of this RPC function.) Therefore we should
118 // zero entryIndex so as not to terribly confuse things.
119 entryIndex = beast::zero;
120 }
121
122 auto const root = keylet::ownerDir(account);
123 auto found = false;
124
125 if (dirIndex.isZero())
126 {
127 dirIndex = root.key;
128 found = true;
129 }
130
131 auto dir = ledger.read({ltDIR_NODE, dirIndex});
132 if (!dir)
133 {
134 // it's possible the user had nftoken pages but no
135 // directory entries. If there's no nftoken page, we will
136 // give empty array for account_objects.
137 if (mlimit >= limit)
138 jvResult[jss::account_objects] = Json::arrayValue;
139
140 // non-zero dirIndex validity was checked in the beginning of this
141 // function; by this point, it should be zero. This function returns
142 // true regardless of nftoken page presence; if absent, account_objects
143 // is already set as an empty array. Notice we will only return false in
144 // this function when entryIndex can not be found, indicating an invalid
145 // marker error.
146 return true;
147 }
148
149 std::uint32_t i = 0;
150 for (;;)
151 {
152 auto const& entries = dir->getFieldV256(sfIndexes);
153 auto iter = entries.begin();
154
155 if (!found)
156 {
157 iter = std::find(iter, entries.end(), entryIndex);
158 if (iter == entries.end())
159 return false;
160
161 found = true;
162 }
163
164 // it's possible that the returned NFTPages exactly filled the
165 // response. Check for that condition.
166 if (i == mlimit && mlimit < limit)
167 {
168 jvResult[jss::limit] = limit;
169 jvResult[jss::marker] = to_string(dirIndex) + ',' + to_string(*iter);
170 return true;
171 }
172
173 for (; iter != entries.end(); ++iter)
174 {
175 auto const sleNode = ledger.read(keylet::child(*iter));
176
177 if (!typeFilter.has_value() ||
178 typeMatchesFilter(typeFilter.value(), sleNode->getType()))
179 {
180 jvObjects.append(sleNode->getJson(JsonOptions::none));
181 }
182
183 if (++i == mlimit)
184 {
185 if (++iter != entries.end())
186 {
187 jvResult[jss::limit] = limit;
188 jvResult[jss::marker] = to_string(dirIndex) + ',' + to_string(*iter);
189 return true;
190 }
191
192 break;
193 }
194 }
195
196 auto const nodeIndex = dir->getFieldU64(sfIndexNext);
197 if (nodeIndex == 0)
198 return true;
199
200 dirIndex = keylet::page(root, nodeIndex).key;
201 dir = ledger.read({ltDIR_NODE, dirIndex});
202 if (!dir)
203 return true;
204
205 if (i == mlimit)
206 {
207 auto const& e = dir->getFieldV256(sfIndexes);
208 if (!e.empty())
209 {
210 jvResult[jss::limit] = limit;
211 jvResult[jss::marker] = to_string(dirIndex) + ',' + to_string(*e.begin());
212 }
213
214 return true;
215 }
216 }
217}
218
221{
222 auto const& params = context.params;
223 if (!params.isMember(jss::account))
224 return RPC::missing_field_error(jss::account);
225
226 if (!params[jss::account].isString())
227 return RPC::invalid_field_error(jss::account);
228
230 auto result = RPC::lookupLedger(ledger, context);
231 if (ledger == nullptr)
232 return result;
233
234 auto const id = parseBase58<AccountID>(params[jss::account].asString());
235 if (!id)
236 {
238 return result;
239 }
240 auto const accountID{id.value()};
241
242 if (!ledger->exists(keylet::account(accountID)))
244
246
247 if (params.isMember(jss::deletion_blockers_only) &&
248 params[jss::deletion_blockers_only].asBool())
249 {
250 struct
251 {
253 LedgerEntryType type;
254 } static constexpr deletionBlockers[] = {
255 {jss::check, ltCHECK},
256 {jss::escrow, ltESCROW},
257 {jss::nft_page, ltNFTOKEN_PAGE},
258 {jss::payment_channel, ltPAYCHAN},
259 {jss::state, ltRIPPLE_STATE},
260 {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID},
261 {jss::xchain_owned_create_account_claim_id, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID},
262 {jss::bridge, ltBRIDGE},
263 {jss::mpt_issuance, ltMPTOKEN_ISSUANCE},
264 {jss::mptoken, ltMPTOKEN},
265 {jss::permissioned_domain, ltPERMISSIONED_DOMAIN},
266 {jss::vault, ltVAULT},
267 };
268
269 typeFilter.emplace();
270 typeFilter->reserve(std::size(deletionBlockers));
271
272 for (auto [name, type] : deletionBlockers)
273 {
274 if (params.isMember(jss::type) && name != params[jss::type])
275 {
276 continue;
277 }
278
279 typeFilter->push_back(type);
280 }
281 }
282 else
283 {
284 auto [rpcStatus, type] = RPC::chooseLedgerEntryType(params);
285
287 return RPC::invalid_field_error(jss::type);
288
289 if (rpcStatus)
290 {
291 result.clear();
292 rpcStatus.inject(result);
293 return result;
294 }
295 if (type != ltANY)
296 {
297 typeFilter = std::vector<LedgerEntryType>({type});
298 }
299 }
300
301 unsigned int limit = 0;
302 if (auto err = readLimitField(limit, RPC::Tuning::accountObjects, context))
303 return *err;
304
305 uint256 dirIndex;
306 uint256 entryIndex;
307 if (params.isMember(jss::marker))
308 {
309 auto const& marker = params[jss::marker];
310 if (!marker.isString())
311 return RPC::expected_field_error(jss::marker, "string");
312
313 auto const& markerStr = marker.asString();
314 auto const& idx = markerStr.find(',');
315 if (idx == std::string::npos)
316 return RPC::invalid_field_error(jss::marker);
317
318 if (!dirIndex.parseHex(markerStr.substr(0, idx)))
319 return RPC::invalid_field_error(jss::marker);
320
321 if (!entryIndex.parseHex(markerStr.substr(idx + 1)))
322 return RPC::invalid_field_error(jss::marker);
323 }
324
325 if (!getAccountObjects(*ledger, accountID, typeFilter, dirIndex, entryIndex, limit, result))
326 return RPC::invalid_field_error(jss::marker);
327
328 result[jss::account] = toBase58(accountID);
330 return result;
331}
332
333} // namespace xrpl
T begin(T... args)
Lightweight wrapper to tag static string.
Definition json_value.h:44
Represents a JSON value.
Definition json_value.h:130
A view into a ledger.
Definition ReadView.h:31
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:476
base_uint next() const
Definition base_uint.h:429
bool isZero() const
Definition base_uint.h:513
T emplace(T... args)
T end(T... args)
T find(T... args)
T is_same_v
@ arrayValue
array value (ordered list)
Definition json_value.h:25
static LimitRange constexpr accountObjects
Limits for the account_objects command.
Json::Value invalid_field_error(std::string const &name)
Definition ErrorCodes.h:273
std::pair< RPC::Status, LedgerEntryType > chooseLedgerEntryType(Json::Value const &params)
Chooses the ledger entry type based on RPC parameters.
bool isAccountObjectsValidType(LedgerEntryType const &type)
Checks if the type is a valid filtering type for the account_objects method.
Json::Value expected_field_error(std::string const &name, std::string const &type)
Definition ErrorCodes.h:297
Json::Value missing_field_error(std::string const &name)
Definition ErrorCodes.h:231
void inject_error(error_code_i code, Json::Value &json)
Add or update the json update to reflect the error code.
Status lookupLedger(std::shared_ptr< ReadView const > &ledger, JsonContext const &context, Json::Value &result)
Looks up a ledger from a request and fills a Json::Value with ledger data.
Charge const feeMediumBurdenRPC
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:371
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:336
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:171
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:363
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:342
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
Number root(Number f, unsigned d)
Definition Number.cpp:958
bool getAccountObjects(ReadView const &ledger, AccountID const &account, std::optional< std::vector< LedgerEntryType > > const &typeFilter, uint256 dirIndex, uint256 entryIndex, std::uint32_t const limit, Json::Value &jvResult)
Gathers all objects for an account in a ledger.
Json::Value doAccountObjects(RPC::JsonContext &context)
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
LedgerEntryType
Identifiers for on-ledger objects.
@ ltANY
A special type, matching any ledger entry type.
@ rpcACT_NOT_FOUND
Definition ErrorCodes.h:50
@ rpcACT_MALFORMED
Definition ErrorCodes.h:70
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
uint256 key
Definition Keylet.h:20
Resource::Charge & loadType
Definition Context.h:22
Json::Value params
Definition Context.h:43