@@ -5803,3 +5803,126 @@ TEST_CASE("exclude transactions by operation type", "[herder]")
5803
5803
TransactionQueue::AddResult::ADD_STATUS_PENDING);
5804
5804
}
5805
5805
}
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