@@ -5803,3 +5803,148 @@ 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
+ // Check that a node's scphistory table for a given ledger has the correct
5852
+ // number of entries of each type in `expectedTypes`
5853
+ auto checkSCPHistoryEntries =
5854
+ [&](Application::pointer node, uint32_t ledgerNum,
5855
+ UnorderedMap<SCPStatementType, size_t > const & expectedTypes) {
5856
+ // Prepare query
5857
+ auto & db = node->getDatabase ();
5858
+ auto prep = db.getPreparedStatement (
5859
+ " SELECT envelope FROM scphistory WHERE ledgerseq = :l" );
5860
+ auto & st = prep.statement ();
5861
+ st.exchange (soci::use (ledgerNum));
5862
+ std::string envStr;
5863
+ st.exchange (soci::into (envStr));
5864
+ st.define_and_bind ();
5865
+ st.execute (false );
5866
+
5867
+ // Count the number of entries of each type
5868
+ UnorderedMap<SCPStatementType, size_t > actualTypes;
5869
+ while (st.fetch ())
5870
+ {
5871
+ Value v;
5872
+ decoder::decode_b64 (envStr, v);
5873
+ SCPEnvelope env;
5874
+ xdr::xdr_from_opaque (v, env);
5875
+ ++actualTypes[env.statement .pledges .type ()];
5876
+ }
5877
+
5878
+ REQUIRE (actualTypes == expectedTypes);
5879
+ };
5880
+
5881
+ // A has 1 CONFIRM and 1 EXTERNALIZE in its scphistory table for ledger 2.
5882
+ checkSCPHistoryEntries (A, 2 ,
5883
+ {{SCPStatementType::SCP_ST_CONFIRM, 1 },
5884
+ {SCPStatementType::SCP_ST_EXTERNALIZE, 1 }});
5885
+
5886
+ // B has 2 EXTERNALIZEs in its scphistory table for ledger 2.
5887
+ checkSCPHistoryEntries (B, 2 , {{SCPStatementType::SCP_ST_EXTERNALIZE, 2 }});
5888
+
5889
+ // C has no entries in its scphistory table for ledger 2.
5890
+ checkSCPHistoryEntries (C, 2 , {});
5891
+
5892
+ // Get messages from A and B
5893
+ HerderImpl& herderA = dynamic_cast <HerderImpl&>(A->getHerder ());
5894
+ HerderImpl& herderB = dynamic_cast <HerderImpl&>(B->getHerder ());
5895
+ std::vector<SCPEnvelope> AEnvs = herderA.getSCP ().getLatestMessagesSend (2 );
5896
+ std::vector<SCPEnvelope> BEnvs = herderB.getSCP ().getLatestMessagesSend (2 );
5897
+
5898
+ // Pass A and B's messages to C
5899
+ for (auto const & env : AEnvs)
5900
+ {
5901
+ C->getHerder ().recvSCPEnvelope (env);
5902
+ }
5903
+ for (auto const & env : BEnvs)
5904
+ {
5905
+ C->getHerder ().recvSCPEnvelope (env);
5906
+ }
5907
+
5908
+ // Crank C until it is on ledger 2
5909
+ simulation->crankUntil (
5910
+ [&]() { return C->getLedgerManager ().getLastClosedLedgerNum () == 2 ; },
5911
+ 2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false );
5912
+
5913
+ // Get messages from C
5914
+ HerderImpl& herderC = dynamic_cast <HerderImpl&>(C->getHerder ());
5915
+ std::vector<SCPEnvelope> CEnvs = herderC.getSCP ().getLatestMessagesSend (2 );
5916
+
5917
+ // Pass C's messages to A and B
5918
+ for (auto const & env : CEnvs)
5919
+ {
5920
+ A->getHerder ().recvSCPEnvelope (env);
5921
+ B->getHerder ().recvSCPEnvelope (env);
5922
+ }
5923
+
5924
+ // Crank A and B until they're on ledger 3
5925
+ simulation->crankUntil (
5926
+ [&]() {
5927
+ return A->getLedgerManager ().getLastClosedLedgerNum () == 3 &&
5928
+ B->getLedgerManager ().getLastClosedLedgerNum () == 3 ;
5929
+ },
5930
+ 2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false );
5931
+
5932
+ // A and B should now each have 3 EXTERNALIZEs in their scphistory table for
5933
+ // ledger 2. A's CONFIRM entry has been replaced with an EXTERNALIZE.
5934
+ UnorderedMap<SCPStatementType, size_t > const expectedTypes = {
5935
+ {SCPStatementType::SCP_ST_EXTERNALIZE, 3 }};
5936
+ checkSCPHistoryEntries (A, 2 , expectedTypes);
5937
+ checkSCPHistoryEntries (B, 2 , expectedTypes);
5938
+
5939
+ // Connect C to B and crank C to catch up with A and B
5940
+ simulation->addConnection (validatorCKey.getPublicKey (),
5941
+ validatorBKey.getPublicKey ());
5942
+ simulation->crankUntil (
5943
+ [&]() { return C->getLedgerManager ().getLastClosedLedgerNum () == 3 ; },
5944
+ 2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false );
5945
+
5946
+ // C should have 3 EXTERNALIZEs in its scphistory table for ledger 2. This
5947
+ // check ensures that C does not double count messages from ledger 2 when
5948
+ // closing ledger 3.
5949
+ checkSCPHistoryEntries (C, 2 , expectedTypes);
5950
+ }
0 commit comments