diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index 0817ee51a..1cda367e2 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -30,6 +30,7 @@ zcash_gtest_SOURCES += \ gtest/test_deprecation.cpp \ gtest/test_dynamicusage.cpp \ gtest/test_equihash.cpp \ + gtest/test_feature_flagging.cpp \ gtest/test_history.cpp \ gtest/test_httprpc.cpp \ gtest/test_joinsplit.cpp \ diff --git a/src/gtest/test_feature_flagging.cpp b/src/gtest/test_feature_flagging.cpp new file mode 100644 index 000000000..b23c38b7b --- /dev/null +++ b/src/gtest/test_feature_flagging.cpp @@ -0,0 +1,97 @@ +#include + +#include "chainparams.h" +#include "consensus/params.h" +#include "utiltest.h" + +#include + +enum TestFeature : uint32_t { + TF_1, + TF_2, + TF_3, + TF_4, + TF_MAX +}; + +struct TestParams { + std::set vRequiredFeatures; + + bool NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const { + switch (idx) { + case Consensus::BASE_SPROUT: + return nHeight >= 0; + case Consensus::UPGRADE_OVERWINTER: + return nHeight >= 5; + case Consensus::UPGRADE_SAPLING: + return nHeight >= 10; + case Consensus::UPGRADE_BLOSSOM: + return nHeight >= 15; + case Consensus::UPGRADE_HEARTWOOD: + return nHeight >= 20; + case Consensus::UPGRADE_CANOPY: + return nHeight >= 25; + case Consensus::UPGRADE_ZFUTURE: + return nHeight >= 30; + default: + return false; + } + } + + bool FeatureRequired(const TestFeature feature) const { + return vRequiredFeatures.count(feature) > 0; + } +}; + +TEST(FeatureFlagging, FeatureDependencies) { + TestParams params; + + Consensus::FeatureSet features({ + { + .dependsOn = {}, + .activation = Consensus::BASE_SPROUT + }, + { + .dependsOn = {TF_1}, + .activation = Consensus::UPGRADE_OVERWINTER + }, + { + .dependsOn = {}, + .activation = Consensus::UPGRADE_BLOSSOM + }, + { + .dependsOn = {TF_2, TF_3}, + .activation = Consensus::UPGRADE_HEARTWOOD + } + }); + + EXPECT_TRUE(features.FeatureActive(params, 1, TF_1)); + EXPECT_FALSE(features.FeatureActive(params, 1, TF_2)); + EXPECT_FALSE(features.FeatureActive(params, 1, TF_3)); + EXPECT_FALSE(features.FeatureActive(params, 1, TF_4)); + + EXPECT_TRUE(features.FeatureActive(params, 5, TF_1)); + EXPECT_TRUE(features.FeatureActive(params, 5, TF_2)); + EXPECT_FALSE(features.FeatureActive(params, 5, TF_3)); + EXPECT_FALSE(features.FeatureActive(params, 5, TF_4)); + + EXPECT_TRUE(features.FeatureActive(params, 15, TF_1)); + EXPECT_TRUE(features.FeatureActive(params, 15, TF_2)); + EXPECT_TRUE(features.FeatureActive(params, 15, TF_3)); + EXPECT_FALSE(features.FeatureActive(params, 15, TF_4)); + + EXPECT_TRUE(features.FeatureActive(params, 20, TF_1)); + EXPECT_TRUE(features.FeatureActive(params, 20, TF_2)); + EXPECT_TRUE(features.FeatureActive(params, 20, TF_3)); + EXPECT_TRUE(features.FeatureActive(params, 20, TF_4)); + + // Force TF_4 to be active + params.vRequiredFeatures.insert(TF_4); + + // Ensure that a feature cannot be forced to be active if + // all of its dependencies are not active + EXPECT_DEATH(features.FeatureActive(params, 5, TF_4), ""); + // A feature can be activated before its activation height + // if all of its dependencies are active + EXPECT_TRUE(features.FeatureActive(params, 15, TF_4)); +}