|
28 | 28 |
|
29 | 29 | #include <boost/test/unit_test.hpp> |
30 | 30 |
|
| 31 | +#include <thread> |
| 32 | +#include <vector> |
| 33 | + |
31 | 34 | using node::GetTransaction; |
32 | 35 |
|
33 | 36 | using SimpleUTXOMap = std::map<COutPoint, std::pair<int, CAmount>>; |
@@ -933,3 +936,220 @@ BOOST_AUTO_TEST_CASE(verify_db_legacy) |
933 | 936 | } |
934 | 937 |
|
935 | 938 | 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