Skip to content

Commit 93f8183

Browse files
test: add comprehensive unit tests for SML caching mechanism
Add extensive unit tests to verify the SML caching functionality: - Test basic cache functionality and cache hits - Test cache invalidation on AddMN operations - Test cache invalidation on RemoveMN operations - Test conditional cache invalidation on UpdateMN operations - Test thread safety of cache access - Test cache behavior with copy constructor and assignment operator These tests ensure the cache works correctly and efficiently, validating: - Cache is properly shared when no changes occur - Cache is invalidated when MN list changes - Cache preserves optimization in UpdateMN when SML entry doesn't change - Cache is thread-safe for concurrent access - Cache is properly copied/shared during object copying This provides comprehensive coverage of the caching mechanism's correctness and thread safety properties.
1 parent 5ec1901 commit 93f8183

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed

src/test/evo_deterministicmns_tests.cpp

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
#include <boost/test/unit_test.hpp>
3030

31+
#include <thread>
32+
#include <vector>
33+
3134
using node::GetTransaction;
3235

3336
using SimpleUTXOMap = std::map<COutPoint, std::pair<int, CAmount>>;
@@ -933,3 +936,220 @@ BOOST_AUTO_TEST_CASE(verify_db_legacy)
933936
}
934937

935938
BOOST_AUTO_TEST_SUITE_END()
939+
940+
BOOST_AUTO_TEST_SUITE(evo_sml_cache_tests)
941+
942+
BOOST_FIXTURE_TEST_CASE(sml_cache_basic_functionality, TestChainSetup)
943+
{
944+
auto& dmnman = *Assert(m_node.dmnman);
945+
946+
// Create empty list and verify SML cache
947+
CDeterministicMNList emptyList(uint256(), 0, 0);
948+
auto sml1 = emptyList.to_sml();
949+
auto sml2 = emptyList.to_sml();
950+
951+
// Should return the same cached object
952+
BOOST_CHECK(sml1 == sml2);
953+
BOOST_CHECK(sml1.get() == sml2.get()); // Same pointer
954+
955+
// Should contain empty list
956+
BOOST_CHECK_EQUAL(sml1->mnList.size(), 0);
957+
}
958+
959+
BOOST_FIXTURE_TEST_CASE(sml_cache_invalidation_on_addmn, TestChainSetup)
960+
{
961+
auto& chainman = *Assert(m_node.chainman.get());
962+
auto& dmnman = *Assert(m_node.dmnman);
963+
964+
// Start with empty list
965+
CDeterministicMNList mnList(uint256(), 0, 0);
966+
auto sml1 = mnList.to_sml();
967+
968+
// Create a mock MN
969+
CKey ownerKey;
970+
ownerKey.MakeNewKey(true);
971+
CBLSSecretKey operatorKey;
972+
operatorKey.MakeNewKey();
973+
974+
auto dmn = std::make_shared<CDeterministicMN>(dmn_types::Regular);
975+
dmn->proTxHash = GetRandHash();
976+
dmn->collateralOutpoint = COutPoint(GetRandHash(), 0);
977+
dmn->nOperatorReward = 0;
978+
dmn->internalId = 1;
979+
980+
auto dmnState = std::make_shared<CDeterministicMNState>();
981+
dmnState->confirmedHash = GetRandHash();
982+
dmnState->keyIDOwner = ownerKey.GetPubKey().GetID();
983+
dmnState->pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
984+
dmnState->keyIDVoting = ownerKey.GetPubKey().GetID();
985+
dmnState->scriptPayout = GetScriptForDestination(PKHash(ownerKey.GetPubKey()));
986+
dmnState->scriptOperatorPayout = GetScriptForDestination(PKHash(ownerKey.GetPubKey()));
987+
BOOST_CHECK_EQUAL(dmnState->netInfo.AddEntry("1.1.1.1:1"), NetInfoStatus::Success);
988+
dmn->pdmnState = dmnState;
989+
990+
// Add MN - should invalidate cache
991+
mnList.AddMN(dmn, true);
992+
auto sml2 = mnList.to_sml();
993+
994+
// Cache should be invalidated, so different pointer but equal content after regeneration
995+
BOOST_CHECK(sml1.get() != sml2.get()); // Different pointer (cache invalidated)
996+
BOOST_CHECK_EQUAL(sml2->mnList.size(), 1); // Should contain the added MN
997+
}
998+
999+
BOOST_FIXTURE_TEST_CASE(sml_cache_invalidation_on_removemn, TestChainSetup)
1000+
{
1001+
auto& chainman = *Assert(m_node.chainman.get());
1002+
auto& dmnman = *Assert(m_node.dmnman);
1003+
1004+
// Start with a list containing one MN
1005+
CDeterministicMNList mnList(uint256(), 0, 0);
1006+
1007+
// Create and add a mock MN
1008+
CKey ownerKey;
1009+
ownerKey.MakeNewKey(true);
1010+
CBLSSecretKey operatorKey;
1011+
operatorKey.MakeNewKey();
1012+
1013+
auto dmn = std::make_shared<CDeterministicMN>(dmn_types::Regular);
1014+
dmn->proTxHash = GetRandHash();
1015+
dmn->collateralOutpoint = COutPoint(GetRandHash(), 0);
1016+
dmn->nOperatorReward = 0;
1017+
dmn->internalId = 1;
1018+
1019+
auto dmnState = std::make_shared<CDeterministicMNState>();
1020+
dmnState->confirmedHash = GetRandHash();
1021+
dmnState->keyIDOwner = ownerKey.GetPubKey().GetID();
1022+
dmnState->pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
1023+
dmnState->keyIDVoting = ownerKey.GetPubKey().GetID();
1024+
dmnState->scriptPayout = GetScriptForDestination(PKHash(ownerKey.GetPubKey()));
1025+
dmnState->scriptOperatorPayout = GetScriptForDestination(PKHash(ownerKey.GetPubKey()));
1026+
BOOST_CHECK_EQUAL(dmnState->netInfo.AddEntry("1.1.1.1:1"), NetInfoStatus::Success);
1027+
dmn->pdmnState = dmnState;
1028+
1029+
mnList.AddMN(dmn, true);
1030+
auto sml1 = mnList.to_sml();
1031+
BOOST_CHECK_EQUAL(sml1->mnList.size(), 1);
1032+
1033+
// Remove MN - should invalidate cache
1034+
mnList.RemoveMN(dmn->proTxHash);
1035+
auto sml2 = mnList.to_sml();
1036+
1037+
// Cache should be invalidated
1038+
BOOST_CHECK(sml1.get() != sml2.get()); // Different pointer (cache invalidated)
1039+
BOOST_CHECK_EQUAL(sml2->mnList.size(), 0); // Should be empty after removal
1040+
}
1041+
1042+
BOOST_FIXTURE_TEST_CASE(sml_cache_conditional_invalidation_on_updatemn, TestChainSetup)
1043+
{
1044+
auto& chainman = *Assert(m_node.chainman.get());
1045+
auto& dmnman = *Assert(m_node.dmnman);
1046+
1047+
// Start with a list containing one MN
1048+
CDeterministicMNList mnList(uint256(), 0, 0);
1049+
1050+
// Create and add a mock MN
1051+
CKey ownerKey;
1052+
ownerKey.MakeNewKey(true);
1053+
CBLSSecretKey operatorKey;
1054+
operatorKey.MakeNewKey();
1055+
1056+
auto dmn = std::make_shared<CDeterministicMN>(dmn_types::Regular);
1057+
dmn->proTxHash = GetRandHash();
1058+
dmn->collateralOutpoint = COutPoint(GetRandHash(), 0);
1059+
dmn->nOperatorReward = 0;
1060+
dmn->internalId = 1;
1061+
1062+
auto dmnState = std::make_shared<CDeterministicMNState>();
1063+
dmnState->confirmedHash = GetRandHash();
1064+
dmnState->keyIDOwner = ownerKey.GetPubKey().GetID();
1065+
dmnState->pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load());
1066+
dmnState->keyIDVoting = ownerKey.GetPubKey().GetID();
1067+
dmnState->scriptPayout = GetScriptForDestination(PKHash(ownerKey.GetPubKey()));
1068+
dmnState->scriptOperatorPayout = GetScriptForDestination(PKHash(ownerKey.GetPubKey()));
1069+
BOOST_CHECK_EQUAL(dmnState->netInfo.AddEntry("1.1.1.1:1"), NetInfoStatus::Success);
1070+
dmn->pdmnState = dmnState;
1071+
1072+
mnList.AddMN(dmn, true);
1073+
auto sml1 = mnList.to_sml();
1074+
BOOST_CHECK_EQUAL(sml1->mnList.size(), 1);
1075+
1076+
// Test 1: Update with same SML entry data - cache should NOT be invalidated
1077+
auto unchangedState = std::make_shared<CDeterministicMNState>(*dmnState);
1078+
mnList.UpdateMN(*dmn, unchangedState);
1079+
auto sml2 = mnList.to_sml();
1080+
1081+
// Cache should NOT be invalidated since SML entry didn't change
1082+
BOOST_CHECK(sml1.get() == sml2.get()); // Same pointer (cache preserved)
1083+
1084+
// Test 2: Update with different SML entry data - cache SHOULD be invalidated
1085+
auto changedState = std::make_shared<CDeterministicMNState>(*dmnState);
1086+
BOOST_CHECK_EQUAL(changedState->netInfo.AddEntry("2.2.2.2:2"), NetInfoStatus::Success); // Change IP
1087+
mnList.UpdateMN(*dmn, changedState);
1088+
auto sml3 = mnList.to_sml();
1089+
1090+
// Cache should be invalidated since SML entry changed
1091+
BOOST_CHECK(sml2.get() != sml3.get()); // Different pointer (cache invalidated)
1092+
BOOST_CHECK_EQUAL(sml3->mnList.size(), 1); // Still one MN but with updated data
1093+
}
1094+
1095+
BOOST_FIXTURE_TEST_CASE(sml_cache_thread_safety, TestChainSetup)
1096+
{
1097+
auto& chainman = *Assert(m_node.chainman.get());
1098+
auto& dmnman = *Assert(m_node.dmnman);
1099+
1100+
CDeterministicMNList mnList(uint256(), 0, 0);
1101+
1102+
// Create multiple threads accessing the cache simultaneously
1103+
std::vector<std::thread> threads;
1104+
std::vector<std::shared_ptr<const CSimplifiedMNList>> results(10);
1105+
1106+
for (int i = 0; i < 10; ++i) {
1107+
threads.emplace_back([&mnList, &results, i]() {
1108+
results[i] = mnList.to_sml();
1109+
});
1110+
}
1111+
1112+
// Wait for all threads to complete
1113+
for (auto& thread : threads) {
1114+
thread.join();
1115+
}
1116+
1117+
// All results should be the same cached object
1118+
for (int i = 1; i < 10; ++i) {
1119+
BOOST_CHECK(results[0].get() == results[i].get());
1120+
}
1121+
}
1122+
1123+
BOOST_FIXTURE_TEST_CASE(sml_cache_copy_constructor, TestChainSetup)
1124+
{
1125+
auto& chainman = *Assert(m_node.chainman.get());
1126+
auto& dmnman = *Assert(m_node.dmnman);
1127+
1128+
CDeterministicMNList mnList1(uint256(), 0, 0);
1129+
auto sml1 = mnList1.to_sml(); // Populate cache
1130+
1131+
// Copy constructor should copy the cached SML
1132+
CDeterministicMNList mnList2(mnList1);
1133+
auto sml2 = mnList2.to_sml();
1134+
1135+
// Should return the same cached object (shared)
1136+
BOOST_CHECK(sml1.get() == sml2.get());
1137+
}
1138+
1139+
BOOST_FIXTURE_TEST_CASE(sml_cache_assignment_operator, TestChainSetup)
1140+
{
1141+
auto& chainman = *Assert(m_node.chainman.get());
1142+
auto& dmnman = *Assert(m_node.dmnman);
1143+
1144+
CDeterministicMNList mnList1(uint256(), 0, 0);
1145+
auto sml1 = mnList1.to_sml(); // Populate cache
1146+
1147+
CDeterministicMNList mnList2(uint256(), 1, 1);
1148+
mnList2 = mnList1; // Assignment should copy the cached SML
1149+
auto sml2 = mnList2.to_sml();
1150+
1151+
// Should return the same cached object (shared)
1152+
BOOST_CHECK(sml1.get() == sml2.get());
1153+
}
1154+
1155+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)