Skip to content

Commit d457d0e

Browse files
committed
Continue to capture SCP messages for previous ledger in database
Closes #3769 Upon closing a ledger, nodes record SCP messages received during the ledger period in the `scphistory` database table. However, any messages about that ledger heard *after* close were dropped. This change captures more of those messages by modifying the `processExternalized` function (which is responsible for recording SCP messages on ledger close) to also record any new or more recent messages heard about the previous ledger.
1 parent aaed4eb commit d457d0e

File tree

3 files changed

+158
-5
lines changed

3 files changed

+158
-5
lines changed

src/herder/HerderImpl.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,28 @@ HerderImpl::processExternalized(uint64 slotIndex, StellarValue const& value,
307307
// save the SCP messages in the database
308308
if (mApp.getConfig().MODE_STORES_HISTORY_MISC)
309309
{
310+
ZoneNamedN(updateSCPHistoryZone, "update SCP history", true);
311+
312+
// NOTE: Consolidating the two saveSCPHistory calls into one transaction
313+
// provides modest performance increases for sqlite and insignificant
314+
// performance increases for postgres.
315+
soci::transaction txScope(mApp.getDatabase().getSession());
316+
if (slotIndex != 0)
317+
{
318+
// Save any new SCP messages received about the previous ledger.
319+
// NOTE: This call uses an empty `QuorumTracker::QuorumMap` because
320+
// there is no new quorum map for the previous ledger.
321+
mApp.getHerderPersistence().saveSCPHistory(
322+
static_cast<uint32>(slotIndex - 1),
323+
getSCP().getExternalizingState(slotIndex - 1),
324+
QuorumTracker::QuorumMap());
325+
}
326+
// Store SCP messages received about the current ledger being closed.
310327
mApp.getHerderPersistence().saveSCPHistory(
311328
static_cast<uint32>(slotIndex),
312329
getSCP().getExternalizingState(slotIndex),
313330
mPendingEnvelopes.getCurrentlyTrackedQuorum());
331+
txScope.commit();
314332
}
315333

316334
// reflect upgrades with the ones included in this SCP round

src/herder/HerderPersistenceImpl.cpp

+17-5
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ HerderPersistenceImpl::saveSCPHistory(uint32_t seq,
6262
st.execute(true);
6363
}
6464
}
65+
66+
// Prepare multi-row insert into scphistory
67+
std::vector<std::string> nodeIDs;
68+
std::vector<uint32_t> seqs;
69+
std::vector<std::string> envelopes;
6570
for (auto const& e : envs)
6671
{
6772
auto const& qHash =
@@ -76,21 +81,28 @@ HerderPersistenceImpl::saveSCPHistory(uint32_t seq,
7681
std::string envelopeEncoded;
7782
envelopeEncoded = decoder::encode_b64(envelopeBytes);
7883

84+
nodeIDs.push_back(nodeIDStrKey);
85+
seqs.push_back(seq);
86+
envelopes.push_back(envelopeEncoded);
87+
}
88+
89+
if (!envs.empty())
90+
{
91+
// Perform multi-row insert into scphistory
7992
auto prepEnv =
8093
db.getPreparedStatement("INSERT INTO scphistory "
8194
"(nodeid, ledgerseq, envelope) VALUES "
8295
"(:n, :l, :e)");
83-
8496
auto& st = prepEnv.statement();
85-
st.exchange(soci::use(nodeIDStrKey));
86-
st.exchange(soci::use(seq));
87-
st.exchange(soci::use(envelopeEncoded));
97+
st.exchange(soci::use(nodeIDs, "n"));
98+
st.exchange(soci::use(seqs, "l"));
99+
st.exchange(soci::use(envelopes, "e"));
88100
st.define_and_bind();
89101
{
90102
ZoneNamedN(insertSCPHistoryZone, "insert scphistory", true);
91103
st.execute(true);
92104
}
93-
if (st.get_affected_rows() != 1)
105+
if (st.get_affected_rows() != envs.size())
94106
{
95107
throw std::runtime_error("Could not update data in SQL");
96108
}

src/herder/test/HerderTests.cpp

+123
Original file line numberDiff line numberDiff line change
@@ -5803,3 +5803,126 @@ TEST_CASE("exclude transactions by operation type", "[herder]")
58035803
TransactionQueue::AddResult::ADD_STATUS_PENDING);
58045804
}
58055805
}
5806+
5807+
// Test that Herder updates the scphistory table with additional messages from
5808+
// ledger `n-1` when closing ledger `n`
5809+
TEST_CASE("SCP message capture from previous ledger", "[herder]")
5810+
{
5811+
constexpr uint32_t version =
5812+
static_cast<uint32_t>(SOROBAN_PROTOCOL_VERSION);
5813+
5814+
// Initialize simulation
5815+
auto networkID = sha256(getTestConfig().NETWORK_PASSPHRASE);
5816+
auto simulation = std::make_shared<Simulation>(
5817+
Simulation::OVER_LOOPBACK, networkID, [](int i) {
5818+
auto cfg = getTestConfig(i, Config::TESTDB_ON_DISK_SQLITE);
5819+
cfg.TESTING_UPGRADE_LEDGER_PROTOCOL_VERSION = version;
5820+
return cfg;
5821+
});
5822+
5823+
// Create three validators: A, B, and C
5824+
auto validatorAKey = SecretKey::fromSeed(sha256("validator-A"));
5825+
auto validatorBKey = SecretKey::fromSeed(sha256("validator-B"));
5826+
auto validatorCKey = SecretKey::fromSeed(sha256("validator-C"));
5827+
5828+
// Put all validators in a quorum set of threshold 2
5829+
SCPQuorumSet qset;
5830+
qset.threshold = 2;
5831+
qset.validators.push_back(validatorAKey.getPublicKey());
5832+
qset.validators.push_back(validatorBKey.getPublicKey());
5833+
qset.validators.push_back(validatorCKey.getPublicKey());
5834+
5835+
// Connect validators A and B, but leave C disconnected
5836+
auto A = simulation->addNode(validatorAKey, qset);
5837+
auto B = simulation->addNode(validatorBKey, qset);
5838+
auto C = simulation->addNode(validatorCKey, qset);
5839+
simulation->addPendingConnection(validatorAKey.getPublicKey(),
5840+
validatorBKey.getPublicKey());
5841+
simulation->startAllNodes();
5842+
5843+
// Crank A and B until they're on ledger 2
5844+
simulation->crankUntil(
5845+
[&]() {
5846+
return A->getLedgerManager().getLastClosedLedgerNum() == 2 &&
5847+
B->getLedgerManager().getLastClosedLedgerNum() == 2;
5848+
},
5849+
2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false);
5850+
5851+
// Return the number of entries in a node's scphistory table for the given
5852+
// ledger number
5853+
auto getNumSCPHistoryEntries = [&](Application::pointer node,
5854+
uint32_t ledgerNum) {
5855+
auto& db = node->getDatabase();
5856+
auto prep = db.getPreparedStatement(
5857+
"SELECT COUNT(*) FROM scphistory WHERE ledgerseq = :l");
5858+
auto& st = prep.statement();
5859+
st.exchange(soci::use(ledgerNum));
5860+
uint32_t count;
5861+
st.exchange(soci::into(count));
5862+
st.define_and_bind();
5863+
st.execute(true);
5864+
return count;
5865+
};
5866+
5867+
// A and B should have 2 entries in their scphistory table for ledger 2. C
5868+
// should have 0.
5869+
REQUIRE(getNumSCPHistoryEntries(A, 2) == 2);
5870+
REQUIRE(getNumSCPHistoryEntries(B, 2) == 2);
5871+
REQUIRE(getNumSCPHistoryEntries(C, 2) == 0);
5872+
5873+
// Get messages from A and B
5874+
HerderImpl& herderA = dynamic_cast<HerderImpl&>(A->getHerder());
5875+
HerderImpl& herderB = dynamic_cast<HerderImpl&>(B->getHerder());
5876+
std::vector<SCPEnvelope> AEnvs = herderA.getSCP().getLatestMessagesSend(2);
5877+
std::vector<SCPEnvelope> BEnvs = herderB.getSCP().getLatestMessagesSend(2);
5878+
5879+
// Pass A and B's messages to C
5880+
for (auto const& env : AEnvs)
5881+
{
5882+
C->getHerder().recvSCPEnvelope(env);
5883+
}
5884+
for (auto const& env : BEnvs)
5885+
{
5886+
C->getHerder().recvSCPEnvelope(env);
5887+
}
5888+
5889+
// Crank C until it is on ledger 2
5890+
simulation->crankUntil(
5891+
[&]() { return C->getLedgerManager().getLastClosedLedgerNum() == 2; },
5892+
2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false);
5893+
5894+
// Get messages from C
5895+
HerderImpl& herderC = dynamic_cast<HerderImpl&>(C->getHerder());
5896+
std::vector<SCPEnvelope> CEnvs = herderC.getSCP().getLatestMessagesSend(2);
5897+
5898+
// Pass C's messages to A and B
5899+
for (auto const& env : CEnvs)
5900+
{
5901+
A->getHerder().recvSCPEnvelope(env);
5902+
B->getHerder().recvSCPEnvelope(env);
5903+
}
5904+
5905+
// Crank A and B until they're on ledger 3
5906+
simulation->crankUntil(
5907+
[&]() {
5908+
return A->getLedgerManager().getLastClosedLedgerNum() == 3 &&
5909+
B->getLedgerManager().getLastClosedLedgerNum() == 3;
5910+
},
5911+
2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false);
5912+
5913+
// A and B should now have 3 entries in their scphistory table for ledger 2.
5914+
REQUIRE(getNumSCPHistoryEntries(A, 2) == 3);
5915+
REQUIRE(getNumSCPHistoryEntries(B, 2) == 3);
5916+
5917+
// Connect C to B and crank C to catch up with A and B
5918+
simulation->addConnection(validatorCKey.getPublicKey(),
5919+
validatorBKey.getPublicKey());
5920+
simulation->crankUntil(
5921+
[&]() { return C->getLedgerManager().getLastClosedLedgerNum() == 3; },
5922+
2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false);
5923+
5924+
// C should have 3 entries in its scphistory table for ledger 2. This check
5925+
// ensures that C does not double count messages from ledger 2 when closing
5926+
// ledger 3.
5927+
REQUIRE(getNumSCPHistoryEntries(C, 2) == 3);
5928+
}

0 commit comments

Comments
 (0)