Skip to content

Commit a299bf4

Browse files
Unified iterating through txset history entries (#4655)
# Description Resolves #4567. # Checklist - [ ] Reviewed the [contributing](https://github.com/stellar/stellar-core/blob/master/CONTRIBUTING.md#submitting-changes) document - [ ] Rebased on top of master (no merge commits) - [ ] Ran `clang-format` v8.0.0 (via `make format` or the Visual Studio extension) - [ ] Compiles - [ ] 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 2b2aef9 + 5b34c42 commit a299bf4

File tree

4 files changed

+126
-80
lines changed

4 files changed

+126
-80
lines changed

src/catchup/ApplyCheckpointWork.cpp

+21-45
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "catchup/ApplyLedgerWork.h"
99
#include "history/FileTransferInfo.h"
1010
#include "history/HistoryManager.h"
11+
#include "history/HistoryUtils.h"
1112
#include "historywork/Progress.h"
1213
#include "ledger/CheckpointRange.h"
1314
#include "ledger/LedgerManager.h"
@@ -139,36 +140,22 @@ ApplyCheckpointWork::getCurrentTxSet()
139140
auto& lm = mApp.getLedgerManager();
140141
auto seq = lm.getLastClosedLedgerNum() + 1;
141142

142-
// Check mTxHistoryEntry prior to loading next history entry.
143-
// This order is important because it accounts for ledger "gaps"
144-
// in the history archives (which are caused by ledgers with empty tx
145-
// sets, as those are not uploaded).
146-
do
143+
auto foundEntry = getHistoryEntryForLedger<TransactionHistoryEntry>(
144+
mTxIn, mTxHistoryEntry, seq);
145+
146+
if (foundEntry)
147147
{
148-
if (mTxHistoryEntry.ledgerSeq < seq)
149-
{
150-
CLOG_DEBUG(History, "Skipping txset for ledger {}",
151-
mTxHistoryEntry.ledgerSeq);
152-
}
153-
else if (mTxHistoryEntry.ledgerSeq > seq)
148+
CLOG_DEBUG(History, "Loaded txset for ledger {}", seq);
149+
if (mTxHistoryEntry.ext.v() == 0)
154150
{
155-
break;
151+
return TxSetXDRFrame::makeFromWire(mTxHistoryEntry.txSet);
156152
}
157153
else
158154
{
159-
releaseAssert(mTxHistoryEntry.ledgerSeq == seq);
160-
CLOG_DEBUG(History, "Loaded txset for ledger {}", seq);
161-
if (mTxHistoryEntry.ext.v() == 0)
162-
{
163-
return TxSetXDRFrame::makeFromWire(mTxHistoryEntry.txSet);
164-
}
165-
else
166-
{
167-
return TxSetXDRFrame::makeFromWire(
168-
mTxHistoryEntry.ext.generalizedTxSet());
169-
}
155+
return TxSetXDRFrame::makeFromWire(
156+
mTxHistoryEntry.ext.generalizedTxSet());
170157
}
171-
} while (mTxIn && mTxIn.readOne(mTxHistoryEntry));
158+
}
172159

173160
CLOG_DEBUG(History, "Using empty txset for ledger {}", seq);
174161
return TxSetXDRFrame::makeEmpty(lm.getLastClosedLedgerHeader());
@@ -181,29 +168,18 @@ ApplyCheckpointWork::getCurrentTxResultSet()
181168
ZoneScoped;
182169
auto& lm = mApp.getLedgerManager();
183170
auto seq = lm.getLastClosedLedgerNum() + 1;
184-
// Check mTxResultSet prior to loading next result set.
185-
// This order is important because it accounts for ledger "gaps"
186-
// in the history archives (which are caused by ledgers with empty tx
187-
// sets, as those are not uploaded).
188-
while (mTxResultIn && mTxResultIn->readOne(*mTxHistoryResultEntry))
171+
releaseAssertOrThrow(mTxHistoryResultEntry);
172+
173+
if (mTxResultIn)
189174
{
190-
if (mTxHistoryResultEntry)
175+
auto foundEntry =
176+
getHistoryEntryForLedger<TransactionHistoryResultEntry>(
177+
*mTxResultIn, *mTxHistoryResultEntry, seq);
178+
179+
if (foundEntry)
191180
{
192-
if (mTxHistoryResultEntry->ledgerSeq < seq)
193-
{
194-
CLOG_DEBUG(History, "Advancing past txresultset for ledger {}",
195-
mTxHistoryResultEntry->ledgerSeq);
196-
}
197-
else if (mTxHistoryResultEntry->ledgerSeq > seq)
198-
{
199-
break;
200-
}
201-
else
202-
{
203-
releaseAssert(mTxHistoryResultEntry->ledgerSeq == seq);
204-
CLOG_DEBUG(History, "Loaded txresultset for ledger {}", seq);
205-
return std::make_optional(mTxHistoryResultEntry->txResultSet);
206-
}
181+
CLOG_DEBUG(History, "Loaded txresultset for ledger {}", seq);
182+
return std::make_optional(mTxHistoryResultEntry->txResultSet);
207183
}
208184
}
209185
CLOG_DEBUG(History, "No txresultset for ledger {}", seq);

src/history/HistoryUtils.cpp

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2025 Stellar Development Foundation and contributors. Licensed
2+
// under the Apache License, Version 2.0. See the COPYING file at the root
3+
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
4+
5+
#include "history/HistoryUtils.h"
6+
#include "util/Logging.h"
7+
#include "util/XDRStream.h"
8+
#include "xdr/Stellar-ledger.h"
9+
#include <Tracy.hpp>
10+
11+
namespace stellar
12+
{
13+
14+
template <typename T>
15+
bool
16+
getHistoryEntryForLedger(XDRInputFileStream& stream, T& currentEntry,
17+
uint32_t targetLedger,
18+
std::function<void(uint32_t ledgerSeq)> validateFn)
19+
{
20+
ZoneScoped;
21+
22+
auto readNextWithValidation = [&]() {
23+
auto res = stream.readOne(currentEntry);
24+
if (res && validateFn)
25+
{
26+
validateFn(currentEntry.ledgerSeq);
27+
}
28+
return res;
29+
};
30+
31+
do
32+
{
33+
if (currentEntry.ledgerSeq < targetLedger)
34+
{
35+
CLOG_DEBUG(History, "Advancing past txhistory entry for ledger {}",
36+
currentEntry.ledgerSeq);
37+
}
38+
else if (currentEntry.ledgerSeq > targetLedger)
39+
{
40+
// No entry for this ledger
41+
break;
42+
}
43+
else
44+
{
45+
// Found the entry for our target ledger
46+
return true;
47+
}
48+
} while (stream && readNextWithValidation());
49+
50+
return false;
51+
}
52+
53+
template bool getHistoryEntryForLedger<TransactionHistoryEntry>(
54+
XDRInputFileStream& stream, TransactionHistoryEntry& currentEntry,
55+
uint32_t targetLedger, std::function<void(uint32_t ledgerSeq)> validateFn);
56+
57+
template bool getHistoryEntryForLedger<TransactionHistoryResultEntry>(
58+
XDRInputFileStream& stream, TransactionHistoryResultEntry& currentEntry,
59+
uint32_t targetLedger, std::function<void(uint32_t ledgerSeq)> validateFn);
60+
}

src/history/HistoryUtils.h

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2025 Stellar Development Foundation and contributors. Licensed
2+
// under the Apache License, Version 2.0. See the COPYING file at the root
3+
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
4+
5+
#pragma once
6+
7+
#include <cstdint>
8+
#include <functional>
9+
10+
namespace stellar
11+
{
12+
13+
class XDRInputFileStream;
14+
15+
// This function centralizes the logic for iterating through
16+
// history archive tx set entries (TransactionHistoryEntry,
17+
// TransactionHistoryResultEntry) that may have gaps. Reads an entry from the
18+
// stream into currentEntry until eof or an entry is found with ledgerSeq >=
19+
// targetLedger. Returns true if targetLedger found (currentEntry will be set to
20+
// the target ledger). Otherwise returns false, where currentEntry is the last
21+
// entry read from the stream.
22+
template <typename T>
23+
bool getHistoryEntryForLedger(
24+
XDRInputFileStream& stream, T& currentEntry, uint32_t targetLedger,
25+
std::function<void(uint32_t ledgerSeq)> validateFn = nullptr);
26+
}

src/historywork/VerifyTxResultsWork.cpp

+19-35
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "historywork/VerifyTxResultsWork.h"
66
#include "history/FileTransferInfo.h"
7+
#include "history/HistoryUtils.h"
78
#include "ledger/LedgerManager.h"
89
#include "main/ErrorMessages.h"
910
#include "util/FileSystemException.h"
@@ -144,47 +145,30 @@ VerifyTxResultsWork::getCurrentTxResultSet(uint32_t ledger)
144145
TransactionHistoryResultEntry trs;
145146
trs.ledgerSeq = ledger;
146147

147-
auto readNextWithValidation = [&]() {
148-
auto res = mResIn.readOne(mTxResultEntry);
149-
if (res)
148+
auto validateFn = [this](uint32_t readLedger) {
149+
auto low = HistoryManager::firstLedgerInCheckpointContaining(
150+
mCheckpoint, mApp.getConfig());
151+
if (readLedger > mCheckpoint || readLedger < low)
150152
{
151-
auto readLedger = mTxResultEntry.ledgerSeq;
152-
auto low = HistoryManager::firstLedgerInCheckpointContaining(
153-
mCheckpoint, mApp.getConfig());
154-
if (readLedger > mCheckpoint || readLedger < low)
155-
{
156-
throw std::runtime_error("Results outside of checkpoint range");
157-
}
153+
throw std::runtime_error("Results outside of checkpoint range");
154+
}
158155

159-
if (readLedger <= mLastSeenLedger)
160-
{
161-
throw std::runtime_error("Malformed or duplicate results: "
162-
"ledgers must be strictly increasing");
163-
}
164-
mLastSeenLedger = readLedger;
156+
if (readLedger <= mLastSeenLedger)
157+
{
158+
throw std::runtime_error("Malformed or duplicate results: "
159+
"ledgers must be strictly increasing");
165160
}
166-
return res;
161+
mLastSeenLedger = readLedger;
167162
};
168163

169-
do
164+
auto foundEntry = getHistoryEntryForLedger<TransactionHistoryResultEntry>(
165+
mResIn, mTxResultEntry, ledger, validateFn);
166+
167+
if (foundEntry)
170168
{
171-
if (mTxResultEntry.ledgerSeq < ledger)
172-
{
173-
CLOG_DEBUG(History, "Processed tx results for ledger {}",
174-
mTxResultEntry.ledgerSeq);
175-
}
176-
else if (mTxResultEntry.ledgerSeq > ledger)
177-
{
178-
// No tx results in this ledger
179-
break;
180-
}
181-
else
182-
{
183-
CLOG_DEBUG(History, "Loaded tx result set for ledger {}", ledger);
184-
trs.txResultSet = mTxResultEntry.txResultSet;
185-
return trs;
186-
}
187-
} while (mResIn && readNextWithValidation());
169+
CLOG_DEBUG(History, "Loaded tx result set for ledger {}", ledger);
170+
trs.txResultSet = mTxResultEntry.txResultSet;
171+
}
188172

189173
return trs;
190174
}

0 commit comments

Comments
 (0)