6
6
#include " bucket/BucketSnapshotManager.h"
7
7
#include " bucket/SearchableBucketList.h"
8
8
#include " ledger/LedgerTxnImpl.h"
9
+ #include " ledger/LedgerTypeUtils.h"
9
10
#include " util/Logging.h"
10
11
#include " util/XDRStream.h" // IWYU pragma: keep
12
+ #include " util/types.h"
11
13
#include < exception>
12
14
#include < json/json.h>
13
15
@@ -54,7 +56,12 @@ namespace stellar
54
56
{
55
57
QueryServer::QueryServer (const std::string& address, unsigned short port,
56
58
int maxClient, size_t threadPoolSize,
57
- BucketSnapshotManager& bucketSnapshotManager)
59
+ BucketSnapshotManager& bucketSnapshotManager
60
+ #ifdef BUILD_TESTS
61
+ ,
62
+ bool useMainThreadForTesting
63
+ #endif
64
+ )
58
65
: mServer (address, port, maxClient, threadPoolSize)
59
66
, mBucketSnapshotManager (bucketSnapshotManager)
60
67
{
@@ -63,12 +70,28 @@ QueryServer::QueryServer(const std::string& address, unsigned short port,
63
70
64
71
mServer .add404 (std::bind (&QueryServer::notFound, this , _1, _2, _3));
65
72
addRoute (" getledgerentryraw" , &QueryServer::getLedgerEntryRaw);
73
+ addRoute (" getledgerentry" , &QueryServer::getLedgerEntry);
66
74
67
- auto workerPids = mServer . start ();
68
- for ( auto pid : workerPids )
75
+ # ifdef BUILD_TESTS
76
+ if (useMainThreadForTesting )
69
77
{
70
- mBucketListSnapshots [pid] = std::move (
71
- bucketSnapshotManager.copySearchableLiveBucketListSnapshot ());
78
+ mBucketListSnapshots [std::this_thread::get_id ()] =
79
+ bucketSnapshotManager.copySearchableLiveBucketListSnapshot ();
80
+ mHotArchiveBucketListSnapshots [std::this_thread::get_id ()] =
81
+ bucketSnapshotManager.copySearchableHotArchiveBucketListSnapshot ();
82
+ }
83
+ else
84
+ #endif
85
+ {
86
+ auto workerPids = mServer .start ();
87
+ for (auto pid : workerPids)
88
+ {
89
+ mBucketListSnapshots [pid] =
90
+ bucketSnapshotManager.copySearchableLiveBucketListSnapshot ();
91
+ mHotArchiveBucketListSnapshots [pid] =
92
+ bucketSnapshotManager
93
+ .copySearchableHotArchiveBucketListSnapshot ();
94
+ }
72
95
}
73
96
}
74
97
@@ -190,4 +213,196 @@ QueryServer::getLedgerEntryRaw(std::string const& params,
190
213
retStr = Json::FastWriter ().write (root);
191
214
return true ;
192
215
}
216
+
217
+ // This query needs to load all the given ledger entries and their "state"
218
+ // (live, archived, evicted, new). This requires a loading entry and TTL from
219
+ // the live BucketList and then checking the Hot Archive for any keys we didn't
220
+ // find. We do three passes:
221
+ // 1. Load all keys from the live BucketList
222
+ // 2. For any Soroban keys not in the live BucketList, load them from the Hot
223
+ // Archive
224
+ // 3. Load TTL keys for any live Soroban entries found in 1.
225
+ bool
226
+ QueryServer::getLedgerEntry (std::string const & params, std::string const & body,
227
+ std::string& retStr)
228
+ {
229
+ ZoneScoped;
230
+ Json::Value root;
231
+
232
+ std::map<std::string, std::vector<std::string>> paramMap;
233
+ httpThreaded::server::server::parsePostParams (body, paramMap);
234
+
235
+ auto keys = paramMap[" key" ];
236
+ auto snapshotLedger = parseOptionalParam<uint32_t >(paramMap, " ledgerSeq" );
237
+
238
+ if (keys.empty ())
239
+ {
240
+ throw std::invalid_argument (
241
+ " Must specify ledger key in POST body: key=<LedgerKey in base64 "
242
+ " XDR format>" );
243
+ }
244
+
245
+ // Get snapshots for both live and hot archive bucket lists
246
+ auto & liveBl = mBucketListSnapshots .at (std::this_thread::get_id ());
247
+ auto & hotArchiveBl =
248
+ mHotArchiveBucketListSnapshots .at (std::this_thread::get_id ());
249
+
250
+ // orderedNotFoundKeys is a set of keys we have not yet found (not in live
251
+ // BucketList or in an archived state in the Hot Archive)
252
+ LedgerKeySet orderedNotFoundKeys;
253
+ for (auto const & key : keys)
254
+ {
255
+ LedgerKey k;
256
+ fromOpaqueBase64 (k, key);
257
+
258
+ // Check for TTL keys which are not allowed
259
+ if (k.type () == TTL)
260
+ {
261
+ retStr = " TTL keys are not allowed" ;
262
+ return false ;
263
+ }
264
+
265
+ orderedNotFoundKeys.emplace (k);
266
+ }
267
+
268
+ mBucketSnapshotManager .maybeCopyLiveAndHotArchiveSnapshots (liveBl,
269
+ hotArchiveBl);
270
+
271
+ std::vector<LedgerEntry> liveEntries;
272
+ std::vector<HotArchiveBucketEntry> archivedEntries;
273
+ uint32_t ledgerSeq =
274
+ snapshotLedger ? *snapshotLedger : liveBl->getLedgerSeq ();
275
+ root[" ledgerSeq" ] = ledgerSeq;
276
+
277
+ auto liveEntriesOp =
278
+ liveBl->loadKeysFromLedger (orderedNotFoundKeys, ledgerSeq);
279
+
280
+ // Return 404 if ledgerSeq not found
281
+ if (!liveEntriesOp)
282
+ {
283
+ retStr = " LedgerSeq not found" ;
284
+ return false ;
285
+ }
286
+
287
+ liveEntries = std::move (*liveEntriesOp);
288
+
289
+ // Remove keys found in live bucketList
290
+ for (auto const & le : liveEntries)
291
+ {
292
+ orderedNotFoundKeys.erase (LedgerEntryKey (le));
293
+ }
294
+
295
+ LedgerKeySet hotArchiveKeysToSearch;
296
+ for (auto const & lk : orderedNotFoundKeys)
297
+ {
298
+ if (isSorobanEntry (lk))
299
+ {
300
+ hotArchiveKeysToSearch.emplace (lk);
301
+ }
302
+ }
303
+
304
+ // Only query archive for remaining keys
305
+ if (!hotArchiveKeysToSearch.empty ())
306
+ {
307
+ auto archivedEntriesOp =
308
+ hotArchiveBl->loadKeysFromLedger (hotArchiveKeysToSearch, ledgerSeq);
309
+ if (!archivedEntriesOp)
310
+ {
311
+ retStr = " LedgerSeq not found" ;
312
+ return false ;
313
+ }
314
+ archivedEntries = std::move (*archivedEntriesOp);
315
+ }
316
+
317
+ // Collect TTL keys for Soroban entries in the live BucketList
318
+ LedgerKeySet ttlKeys;
319
+ for (auto const & le : liveEntries)
320
+ {
321
+ if (isSorobanEntry (le.data ))
322
+ {
323
+ ttlKeys.emplace (getTTLKey (le));
324
+ }
325
+ }
326
+
327
+ std::vector<LedgerEntry> ttlEntries;
328
+ if (!ttlKeys.empty ())
329
+ {
330
+ // We haven't updated the live snapshot so we will never not have the
331
+ // requested ledgerSeq and return nullopt.
332
+ ttlEntries =
333
+ std::move (liveBl->loadKeysFromLedger (ttlKeys, ledgerSeq).value ());
334
+ }
335
+
336
+ std::unordered_map<LedgerKey, LedgerEntry> ttlMap;
337
+ for (auto const & ttlEntry : ttlEntries)
338
+ {
339
+ ttlMap.emplace (LedgerEntryKey (ttlEntry), ttlEntry);
340
+ }
341
+
342
+ // Process live entries
343
+ for (auto const & le : liveEntries)
344
+ {
345
+ Json::Value entry;
346
+ entry[" e" ] = toOpaqueBase64 (le);
347
+
348
+ // Check TTL for Soroban entries
349
+ if (isSorobanEntry (le.data ))
350
+ {
351
+ auto ttlIter = ttlMap.find (getTTLKey (le));
352
+ releaseAssertOrThrow (ttlIter != ttlMap.end ());
353
+ if (isLive (ttlIter->second , ledgerSeq))
354
+ {
355
+ entry[" state" ] = " live" ;
356
+ entry[" ttl" ] = ttlIter->second .data .ttl ().liveUntilLedgerSeq ;
357
+ }
358
+ else
359
+ {
360
+ entry[" state" ] = " archived" ;
361
+ }
362
+ }
363
+ else
364
+ {
365
+ entry[" state" ] = " live" ;
366
+ }
367
+
368
+ root[" entries" ].append (entry);
369
+ }
370
+
371
+ // Process archived entries - all are evicted since they come from hot
372
+ // archive
373
+ for (auto const & be : archivedEntries)
374
+ {
375
+ // If we get to this point, we know the key is not in the live
376
+ // BucketList, so if we get a DELETED or RESTORED entry, the entry is
377
+ // new wrt ledger state.
378
+ if (be.type () != HOT_ARCHIVE_ARCHIVED)
379
+ {
380
+ continue ;
381
+ }
382
+
383
+ auto const & le = be.archivedEntry ();
384
+
385
+ // At this point we've "found" the key and know it's archived, so remove
386
+ // it from our search set
387
+ orderedNotFoundKeys.erase (LedgerEntryKey (le));
388
+
389
+ Json::Value entry;
390
+ entry[" e" ] = toOpaqueBase64 (le);
391
+ entry[" state" ] = " evicted" ;
392
+ root[" entries" ].append (entry);
393
+ }
394
+
395
+ // Since we removed entries found in the live BucketList and archived
396
+ // entries found in the Hot Archive, any remaining keys must be new.
397
+ for (auto const & key : orderedNotFoundKeys)
398
+ {
399
+ Json::Value entry;
400
+ entry[" e" ] = toOpaqueBase64 (key);
401
+ entry[" state" ] = " new" ;
402
+ root[" entries" ].append (entry);
403
+ }
404
+
405
+ retStr = Json::FastWriter ().write (root);
406
+ return true ;
407
+ }
193
408
}
0 commit comments