Skip to content

Commit 88cd5b9

Browse files
authored
getledgerentry endpoint for RPC (#4721)
# Description This PR adds an HTTP endpoint that can query ledger state and provide an entry's archival status. This can be used by RPC for preflighting TXs without needing to maintain a redundant ledger state database. The endpoint API can be found in `docs/software/commands.md`. # Checklist - [x] Reviewed the [contributing](https://github.com/stellar/stellar-core/blob/master/CONTRIBUTING.md#submitting-changes) document - [x] Rebased on top of master (no merge commits) - [x] Ran `clang-format` v8.0.0 (via `make format` or the Visual Studio extension) - [x] Compiles - [x] Ran all tests - [ ] If change impacts performance, include supporting evidence per the [performance document](https://github.com/stellar/stellar-core/blob/master/performance-eval/performance-eval.md)
2 parents a81672a + 94ff674 commit 88cd5b9

13 files changed

+958
-43
lines changed

docs/software/commands.md

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -619,11 +619,11 @@ enabled by specifying a port via the HTTP_QUERY_PORT config setting.
619619

620620
A JSON payload is returned as follows:
621621

622-
```
622+
```js
623623
{
624624
"entries": [
625-
{"le": "Base64-LedgerEntry"},
626-
{"le": "Base64-LedgerEntry"},
625+
{"entry": "Base64-LedgerEntry"},
626+
{"entry": "Base64-LedgerEntry"},
627627
...
628628
],
629629
"ledgerSeq": ledgerSeq
@@ -638,3 +638,57 @@ enabled by specifying a port via the HTTP_QUERY_PORT config setting.
638638
value of the `TTL` entry to the current ledger sequence number.
639639

640640
`ledgerSeq` gives the ledger number on which the query was performed.
641+
642+
* **`getledgerentry`**<br>
643+
A POST request with the following body:<br>
644+
645+
```
646+
ledgerSeq=NUM&k=Base64&k=Base64...
647+
```
648+
- `ledgerSeq`: An optional parameter, specifying the ledger snapshot to base the query on.
649+
If the specified ledger is not available, a 404 error will be returned with "Ledger not found\n" message.
650+
If this parameter is not set, the current ledger is used.
651+
- `key`: A series of Base64 encoded XDR strings specifying the `LedgerKey` to query. Keys must be
652+
unique and must not be TTL entries, as TTL data is automatically returned when querying a Soroban key.
653+
654+
A JSON payload is returned as follows:
655+
656+
```js
657+
{
658+
"entries": [
659+
{"entry": "Base64-LedgerEntry", "state": "live", /*optional*/ "liveUntilLedgerSeq": uint32},
660+
{"state": "not-found"}, // Given key is not found
661+
{"entry": "Base64-LedgerEntry", "state": "archived"}
662+
],
663+
"ledgerSeq": uint32
664+
}
665+
```
666+
667+
- `entries`: A list of entries for each queried LedgerKey, ordered by the order of the keys in the request.
668+
Every key queried is guaranteed to have a corresponding `state` field while the `entry` and `ledgerSeq` fields are optional.
669+
- `entry`: Present only for `live` and `archived` states. Contains the `LedgerEntry` encoded as a Base64 string.
670+
This field is omitted for entries with state `not-found`.
671+
- `state`: One of the following values:
672+
- `live`: Entry is live.
673+
- `not-found`: Entry does not exist. Either the entry has never existed or is an expired temp entry.
674+
- `archived`: Entry is archived, counts towards disk resources.
675+
- `liveUntilLedgerSeq`: An optional value, only returned for live Soroban entries. Contains
676+
a uint32 value for the entry's `liveUntilLedgerSeq`.
677+
- `ledgerSeq`: The ledger number on which the query was performed.
678+
679+
Classic entries will always return a state of `live` or `not-found`.
680+
If a classic entry does not exist, it will have a state of `not-found`.
681+
682+
Similarly, temporary Soroban entries will always return a state of `live` or
683+
`not-found`. If a temporary entry does not exist or has expired, it
684+
will have a state of `not-found`.
685+
686+
This endpoint will always give correct information for archived entries. Even
687+
if an entry has been archived and evicted to the Hot Archive, this endpoint will
688+
still return the archived entry's full `LedgerEntry` as well as the proper state.
689+
690+
The endpoint returns a 404 status code with the following error messages in these cases:
691+
- If no keys are provided: "Must specify key in POST body: key=<LedgerKey in base64 XDR format>\n"
692+
- If TTL keys are queried: "TTL keys are not allowed\n"
693+
- If duplicate keys are submitted: "Duplicate keys\n"
694+
- If the specified ledger is not found: "Ledger not found\n"

docs/stellar-core_example.cfg

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ PREFETCH_BATCH_SIZE=1000
5757

5858
# HTTP_PORT (integer) default 11626
5959
# What port stellar-core listens for commands on.
60-
# If set to 0, disable HTTP interface entirely
60+
# If set to 0, disable HTTP commands interface entirely
61+
# (note that this does not disable HTTP query interface).
6162
# Must not be the same as HTTP_QUERY_PORT if not 0.
6263
HTTP_PORT=11626
6364

@@ -80,7 +81,8 @@ COMMANDS=[
8081
# HTTP_QUERY_PORT (integer) default 0
8182
# What port stellar-core listens for query commands on,
8283
# such as getledgerentryraw.
83-
# If set to 0, disable HTTP query interface entirely.
84+
# If set to 0, disable HTTP query interface entirely
85+
# (note that this does not disable HTTP commands interface).
8486
# Must not be the same as HTTP_PORT if not 0.
8587
HTTP_QUERY_PORT=0
8688

src/bucket/BucketSnapshotManager.cpp

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ BucketSnapshotManager::copySearchableHotArchiveBucketListSnapshot() const
7474
copyHistoricalSnapshots(mHotArchiveHistoricalSnapshots)));
7575
}
7676

77+
namespace
78+
{
79+
template <typename T, typename U>
80+
bool
81+
needsUpdate(std::shared_ptr<T const> const& snapshot,
82+
SnapshotPtrT<U> const& curr)
83+
{
84+
return !snapshot || snapshot->getLedgerSeq() < curr->getLedgerSeq();
85+
}
86+
}
87+
7788
void
7889
BucketSnapshotManager::maybeCopySearchableBucketListSnapshot(
7990
SearchableSnapshotConstPtr& snapshot)
@@ -82,9 +93,7 @@ BucketSnapshotManager::maybeCopySearchableBucketListSnapshot(
8293
// modified. Rather, a thread is checking it's copy against the canonical
8394
// snapshot, so use a shared lock.
8495
std::shared_lock<std::shared_mutex> lock(mSnapshotMutex);
85-
86-
if (!snapshot ||
87-
snapshot->getLedgerSeq() < mCurrLiveSnapshot->getLedgerSeq())
96+
if (needsUpdate(snapshot, mCurrLiveSnapshot))
8897
{
8998
snapshot = copySearchableLiveBucketListSnapshot();
9099
}
@@ -98,14 +107,33 @@ BucketSnapshotManager::maybeCopySearchableHotArchiveBucketListSnapshot(
98107
// modified. Rather, a thread is checking it's copy against the canonical
99108
// snapshot, so use a shared lock.
100109
std::shared_lock<std::shared_mutex> lock(mSnapshotMutex);
101-
102-
if (!snapshot ||
103-
snapshot->getLedgerSeq() < mCurrHotArchiveSnapshot->getLedgerSeq())
110+
if (needsUpdate(snapshot, mCurrHotArchiveSnapshot))
104111
{
105112
snapshot = copySearchableHotArchiveBucketListSnapshot();
106113
}
107114
}
108115

116+
void
117+
BucketSnapshotManager::maybeCopyLiveAndHotArchiveSnapshots(
118+
SearchableSnapshotConstPtr& liveSnapshot,
119+
SearchableHotArchiveSnapshotConstPtr& hotArchiveSnapshot)
120+
{
121+
// The canonical snapshot held by the BucketSnapshotManager is not being
122+
// modified. Rather, a thread is checking it's copy against the canonical
123+
// snapshot, so use a shared lock. For consistency we hold the lock while
124+
// updating both snapshots.
125+
std::shared_lock<std::shared_mutex> lock(mSnapshotMutex);
126+
if (needsUpdate(liveSnapshot, mCurrLiveSnapshot))
127+
{
128+
liveSnapshot = copySearchableLiveBucketListSnapshot();
129+
}
130+
131+
if (needsUpdate(hotArchiveSnapshot, mCurrHotArchiveSnapshot))
132+
{
133+
hotArchiveSnapshot = copySearchableHotArchiveBucketListSnapshot();
134+
}
135+
}
136+
109137
void
110138
BucketSnapshotManager::updateCurrentSnapshot(
111139
SnapshotPtrT<LiveBucket>&& liveSnapshot,

src/bucket/BucketSnapshotManager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,12 @@ class BucketSnapshotManager : NonMovableOrCopyable
8989
maybeCopySearchableBucketListSnapshot(SearchableSnapshotConstPtr& snapshot);
9090
void maybeCopySearchableHotArchiveBucketListSnapshot(
9191
SearchableHotArchiveSnapshotConstPtr& snapshot);
92+
93+
// This function is the same as snapshot refreshers above, but guarantees
94+
// that both snapshots are consistent with the same lcl. This is required
95+
// when querying both snapshot types as part of the same query.
96+
void maybeCopyLiveAndHotArchiveSnapshots(
97+
SearchableSnapshotConstPtr& liveSnapshot,
98+
SearchableHotArchiveSnapshotConstPtr& hotArchiveSnapshot);
9299
};
93100
}

src/bucket/HotArchiveBucket.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "bucket/BucketInputIterator.h"
77
#include "bucket/BucketOutputIterator.h"
88
#include "bucket/BucketUtils.h"
9+
#include "ledger/LedgerTypeUtils.h"
10+
#include "util/GlobalChecks.h"
911

1012
namespace stellar
1113
{
@@ -51,13 +53,15 @@ HotArchiveBucket::convertToBucketEntry(
5153
HotArchiveBucketEntry be;
5254
be.type(HOT_ARCHIVE_ARCHIVED);
5355
be.archivedEntry() = e;
56+
releaseAssertOrThrow(isPersistentEntry(e.data));
5457
bucket.push_back(be);
5558
}
5659
for (auto const& k : restoredEntries)
5760
{
5861
HotArchiveBucketEntry be;
5962
be.type(HOT_ARCHIVE_LIVE);
6063
be.key() = k;
64+
releaseAssertOrThrow(isPersistentEntry(k));
6165
bucket.push_back(be);
6266
}
6367

src/bucket/test/BucketIndexTests.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,8 @@ TEST_CASE("hot archive bucket lookups", "[bucket][bucketindex][archive]")
10601060
};
10611061

10621062
auto archivedEntries =
1063-
LedgerTestUtils::generateValidUniqueLedgerEntriesWithTypes(
1064-
{CONTRACT_DATA, CONTRACT_CODE}, 10);
1063+
LedgerTestUtils::generateUniquePersistentLedgerEntries(
1064+
10, keysToSearch);
10651065
for (auto const& e : archivedEntries)
10661066
{
10671067
auto k = LedgerEntryKey(e);
@@ -1071,8 +1071,8 @@ TEST_CASE("hot archive bucket lookups", "[bucket][bucketindex][archive]")
10711071

10721072
// Note: keys to search automatically populated by these functions
10731073
auto restoredEntries =
1074-
LedgerTestUtils::generateValidUniqueLedgerKeysWithTypes(
1075-
{CONTRACT_DATA, CONTRACT_CODE}, 10, keysToSearch);
1074+
LedgerTestUtils::generateUniquePersistentLedgerKeys(10,
1075+
keysToSearch);
10761076
for (auto const& k : restoredEntries)
10771077
{
10781078
expectedRestoredEntries.emplace(k);

src/bucket/test/BucketListTests.cpp

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,6 @@ basicBucketListTest()
166166
{
167167
bl.addBatch(
168168
*app, i, getAppLedgerVersion(app), {},
169-
LedgerTestUtils::generateValidUniqueLedgerEntries(
170-
8),
171169
LedgerTestUtils::
172170
generateValidLedgerEntryKeysWithExclusions(
173171
{CONFIG_SETTING}, 5));
@@ -176,14 +174,11 @@ basicBucketListTest()
176174
{
177175
bl.addBatch(
178176
*app, i, getAppLedgerVersion(app),
179-
stellar::LedgerTestUtils::
180-
generateValidUniqueLedgerEntriesWithTypes(
181-
{CONTRACT_CODE, CONTRACT_DATA}, 8,
182-
seenKeys),
183-
stellar::LedgerTestUtils::
184-
generateValidUniqueLedgerKeysWithTypes(
185-
{CONTRACT_CODE, CONTRACT_DATA}, 5,
186-
seenKeys));
177+
LedgerTestUtils::
178+
generateUniquePersistentLedgerEntries(8,
179+
seenKeys),
180+
LedgerTestUtils::generateUniquePersistentLedgerKeys(
181+
5, seenKeys));
187182
}
188183
}
189184

@@ -439,10 +434,8 @@ TEST_CASE_VERSIONS("hot archive bucket tombstones expire at bottom level",
439434
{
440435
bl.addBatch(
441436
*app, ledger, getAppLedgerVersion(app),
442-
LedgerTestUtils::generateValidUniqueLedgerEntriesWithTypes(
443-
{CONTRACT_DATA, CONTRACT_CODE}, 5, keys),
444-
LedgerTestUtils::generateValidUniqueLedgerKeysWithTypes(
445-
{CONTRACT_CODE, CONTRACT_DATA}, 5, keys));
437+
LedgerTestUtils::generateUniquePersistentLedgerEntries(5, keys),
438+
LedgerTestUtils::generateUniquePersistentLedgerKeys(5, keys));
446439

447440
// Once all entries merge to the bottom level, only deleted entries
448441
// should remain

src/ledger/test/LedgerTestUtils.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,38 @@ generateUniqueValidSorobanLedgerEntryKeys(size_t n)
688688
n);
689689
}
690690

691+
std::vector<LedgerEntry>
692+
generateUniquePersistentLedgerEntries(size_t n,
693+
UnorderedSet<LedgerKey>& seenKeys)
694+
{
695+
auto res = LedgerTestUtils::generateValidUniqueLedgerEntriesWithTypes(
696+
{CONTRACT_DATA, CONTRACT_CODE}, n, seenKeys);
697+
for (auto& le : res)
698+
{
699+
if (le.data.type() == CONTRACT_DATA)
700+
{
701+
seenKeys.erase(LedgerEntryKey(le));
702+
le.data.contractData().durability = PERSISTENT;
703+
seenKeys.insert(LedgerEntryKey(le));
704+
}
705+
}
706+
707+
return res;
708+
}
709+
710+
std::vector<LedgerKey>
711+
generateUniquePersistentLedgerKeys(size_t n, UnorderedSet<LedgerKey>& seenKeys)
712+
{
713+
auto entries = generateUniquePersistentLedgerEntries(n, seenKeys);
714+
std::vector<LedgerKey> res;
715+
for (auto const& le : entries)
716+
{
717+
res.push_back(LedgerEntryKey(le));
718+
}
719+
720+
return res;
721+
}
722+
691723
std::vector<LedgerKey>
692724
generateValidUniqueLedgerEntryKeysWithExclusions(
693725
std::unordered_set<LedgerEntryType> const& excludedTypes, size_t n)

src/ledger/test/LedgerTestUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ std::vector<LedgerKey> generateValidUniqueLedgerKeysWithTypes(
5151
std::unordered_set<LedgerEntryType> const& types, size_t n,
5252
UnorderedSet<LedgerKey>& seenKeys);
5353

54+
std::vector<LedgerEntry>
55+
generateUniquePersistentLedgerEntries(size_t n,
56+
UnorderedSet<LedgerKey>& seenKeys);
57+
std::vector<LedgerKey>
58+
generateUniquePersistentLedgerKeys(size_t n, UnorderedSet<LedgerKey>& seenKeys);
59+
5460
std::vector<LedgerKey> generateUniqueValidSorobanLedgerEntryKeys(size_t n);
5561

5662
std::vector<LedgerKey> generateValidUniqueLedgerEntryKeysWithExclusions(

src/main/CommandHandler.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ namespace stellar
5050
{
5151
CommandHandler::CommandHandler(Application& app) : mApp(app)
5252
{
53-
if (mApp.getConfig().HTTP_PORT)
53+
if (mApp.getConfig().HTTP_PORT || mApp.getConfig().HTTP_QUERY_PORT)
5454
{
5555
std::string ipStr;
5656
if (mApp.getConfig().PUBLIC_HTTP_PORT)
@@ -66,9 +66,12 @@ CommandHandler::CommandHandler(Application& app) : mApp(app)
6666

6767
int httpMaxClient = mApp.getConfig().HTTP_MAX_CLIENT;
6868

69-
mServer = std::make_unique<http::server::server>(
70-
app.getClock().getIOContext(), ipStr, mApp.getConfig().HTTP_PORT,
71-
httpMaxClient);
69+
if (mApp.getConfig().HTTP_PORT)
70+
{
71+
mServer = std::make_unique<http::server::server>(
72+
app.getClock().getIOContext(), ipStr,
73+
mApp.getConfig().HTTP_PORT, httpMaxClient);
74+
}
7275

7376
if (mApp.getConfig().HTTP_QUERY_PORT)
7477
{
@@ -78,7 +81,8 @@ CommandHandler::CommandHandler(Application& app) : mApp(app)
7881
mApp.getBucketManager().getBucketSnapshotManager());
7982
}
8083
}
81-
else
84+
85+
if (!mApp.getConfig().HTTP_PORT)
8286
{
8387
mServer = std::make_unique<http::server::server>(
8488
app.getClock().getIOContext());

0 commit comments

Comments
 (0)