@@ -635,4 +635,171 @@ TEST(NmcP1dGate, MissingAuxBitsKeepsProofIncompleteAndRejected)
635635 EXPECT_FALSE (chain_.add_auxpow_header (h, ap));
636636}
637637
638+ // ---------------------------------------------------------------------------
639+ // P1e: activation-height admission gate KATs (NmcP1eActivationGate).
640+ // Exercise HeaderChain::check_activation_gate() - the height-derived MM
641+ // activation gate, factored like the P1d proof gate so it is testable without
642+ // the deferred storage path. The production activation height stays the -1
643+ // sentinel; these fixtures pin a TEST-only height (19200, the historically
644+ // cited NMC mainnet value) purely to drive the gate - no consensus promotion.
645+ // ---------------------------------------------------------------------------
646+ static NMCChainParams params_activation (int32_t act_height)
647+ {
648+ NMCChainParams p = NMCChainParams::mainnet ();
649+ p.aux_chain_id = 1 ;
650+ p.auxpow_activation_height = act_height; // TEST-only pin; production stays -1
651+ return p;
652+ }
653+
654+ TEST (NmcP1eActivationGate, UnpinnedActivationRefusesToJudge)
655+ {
656+ // mainnet() leaves auxpow_activation_height at -1: the gate must refuse
657+ // rather than guess, regardless of whether the header carries an AuxPow.
658+ HeaderChain chain_ (params_chain_id_1 ());
659+ EXPECT_EQ (chain_.check_activation_gate (50000 , /* has_auxpow=*/ true ),
660+ HeaderChain::AdmitResult::REJECT_UNPINNED );
661+ EXPECT_EQ (chain_.check_activation_gate (50000 , /* has_auxpow=*/ false ),
662+ HeaderChain::AdmitResult::REJECT_UNPINNED );
663+ }
664+
665+ TEST (NmcP1eActivationGate, AuxPowBelowActivationIsPremature)
666+ {
667+ HeaderChain chain_ (params_activation (19200 ));
668+ EXPECT_EQ (chain_.check_activation_gate (19199 , /* has_auxpow=*/ true ),
669+ HeaderChain::AdmitResult::REJECT_PREMATURE_AUXPOW );
670+ // a plain (non-MM) header below activation is admissible.
671+ EXPECT_EQ (chain_.check_activation_gate (19199 , /* has_auxpow=*/ false ),
672+ HeaderChain::AdmitResult::ADMIT );
673+ }
674+
675+ TEST (NmcP1eActivationGate, AuxPowRequiredAtAndAfterActivation)
676+ {
677+ HeaderChain chain_ (params_activation (19200 ));
678+ // exactly at the activation height the AuxPow becomes mandatory.
679+ EXPECT_EQ (chain_.check_activation_gate (19200 , /* has_auxpow=*/ false ),
680+ HeaderChain::AdmitResult::REJECT_MISSING_AUXPOW );
681+ EXPECT_EQ (chain_.check_activation_gate (19200 , /* has_auxpow=*/ true ),
682+ HeaderChain::AdmitResult::ADMIT );
683+ // and well past it.
684+ EXPECT_EQ (chain_.check_activation_gate (250000 , /* has_auxpow=*/ true ),
685+ HeaderChain::AdmitResult::ADMIT );
686+ }
687+
688+ TEST (NmcP1eActivationGate, ActivationBoundaryIsInclusive)
689+ {
690+ HeaderChain chain_ (params_activation (19200 ));
691+ // height == activation-1 is still pre-activation (MM not yet required);
692+ // height == activation flips MM to mandatory.
693+ EXPECT_EQ (chain_.check_activation_gate (19199 , /* has_auxpow=*/ false ),
694+ HeaderChain::AdmitResult::ADMIT );
695+ EXPECT_EQ (chain_.check_activation_gate (19200 , /* has_auxpow=*/ false ),
696+ HeaderChain::AdmitResult::REJECT_MISSING_AUXPOW );
697+ }
698+
699+
700+ // ---------------------------------------------------------------------------
701+ // P1e storage leg: in-memory connect path KATs (NmcP1eStore). Exercise
702+ // HeaderChain::add_header / add_auxpow_header now that a passing header is
703+ // CONNECTED: genesis-seed, prev-hash linkage, height derivation, the activation
704+ // gate enforced from the connected height, and tip advance. Cumulative-work
705+ // summation + work-based reorg stay a later sub-leg.
706+ // ---------------------------------------------------------------------------
707+ static BlockHeaderType plain_header (const uint256& prev, uint32_t bits, uint32_t nonce)
708+ {
709+ BlockHeaderType h{};
710+ h.m_version = 1 ;
711+ h.m_previous_block = prev;
712+ h.m_bits = bits;
713+ h.m_nonce = nonce;
714+ return h;
715+ }
716+
717+ TEST (NmcP1eStore, EmptyChainSeedsRootAtHeightZero)
718+ {
719+ HeaderChain chain_ (params_activation (19200 )); // pinned so plain<activation ADMITs
720+ uint256 z; z.SetNull ();
721+ BlockHeaderType g = plain_header (z, 0x1d00ffffu , 1 );
722+ EXPECT_TRUE (chain_.add_header (g));
723+ EXPECT_TRUE (chain_.has_header (block_hash (g)));
724+ EXPECT_EQ (chain_.size (), 1u );
725+ EXPECT_EQ (chain_.height (), 0u );
726+ }
727+
728+ TEST (NmcP1eStore, ConnectsChildByPrevHashAndAdvancesTip)
729+ {
730+ HeaderChain chain_ (params_activation (19200 ));
731+ uint256 z; z.SetNull ();
732+ BlockHeaderType g = plain_header (z, 0x1d00ffffu , 1 );
733+ ASSERT_TRUE (chain_.add_header (g));
734+ BlockHeaderType c = plain_header (block_hash (g), 0x1d00ffffu , 2 );
735+ EXPECT_TRUE (chain_.add_header (c));
736+ EXPECT_EQ (chain_.size (), 2u );
737+ EXPECT_EQ (chain_.height (), 1u );
738+ ASSERT_TRUE (chain_.tip ().has_value ());
739+ EXPECT_EQ (chain_.tip ()->block_hash , block_hash (c));
740+ }
741+
742+ TEST (NmcP1eStore, OrphanWithUnknownParentIsRejected)
743+ {
744+ HeaderChain chain_ (params_activation (19200 ));
745+ uint256 z; z.SetNull ();
746+ BlockHeaderType g = plain_header (z, 0x1d00ffffu , 1 );
747+ ASSERT_TRUE (chain_.add_header (g));
748+ uint256 bogus = leaf_of (0xAB ); // not g's hash
749+ BlockHeaderType o = plain_header (bogus, 0x1d00ffffu , 3 );
750+ EXPECT_FALSE (chain_.add_header (o));
751+ EXPECT_FALSE (chain_.has_header (block_hash (o)));
752+ EXPECT_EQ (chain_.size (), 1u );
753+ }
754+
755+ TEST (NmcP1eStore, UnpinnedActivationRefusesToConnect)
756+ {
757+ HeaderChain chain_ (params_chain_id_1 ()); // activation height == -1
758+ uint256 z; z.SetNull ();
759+ BlockHeaderType g = plain_header (z, 0x1d00ffffu , 1 );
760+ EXPECT_FALSE (chain_.add_header (g)); // REJECT_UNPINNED: build nothing
761+ EXPECT_EQ (chain_.size (), 0u );
762+ }
763+
764+ TEST (NmcP1eStore, VerifiedAuxPowConnectsAtActivationHeight)
765+ {
766+ HeaderChain chain_ (params_activation (1 )); // activation at height 1
767+ uint256 z; z.SetNull ();
768+ BlockHeaderType g = plain_header (z, 0x1d00ffffu , 1 );
769+ ASSERT_TRUE (chain_.add_header (g)); // genesis (height 0, plain)
770+
771+ uint32_t aux_bits = 0x207fffffu ; // easy aux target
772+ BlockHeaderType h1 = plain_header (block_hash (g), aux_bits, 7 );
773+ uint256 aux = block_hash (h1); // production seam: aux == header hash
774+ AuxPow ap = complete_proof (aux, /* parent_own_bits=*/ 0x1d00ffffu );
775+ ASSERT_TRUE (mine_parent (ap, chain::bits_to_target (aux_bits)));
776+ ASSERT_EQ (chain_.verify_auxpow_header (h1, ap), AuxPow::CheckResult::VALID );
777+
778+ EXPECT_TRUE (chain_.add_auxpow_header (h1, ap)); // height 1 >= activation, proof VALID
779+ EXPECT_TRUE (chain_.has_header (aux));
780+ EXPECT_EQ (chain_.height (), 1u );
781+ ASSERT_TRUE (chain_.get_header (aux).has_value ());
782+ EXPECT_TRUE (chain_.get_header (aux)->auxpow .has_value ());
783+ }
784+
785+ TEST (NmcP1eStore, PrematureAuxPowIsRejectedByStoreEvenWhenProofValid)
786+ {
787+ HeaderChain chain_ (params_activation (19200 )); // activation far above height 1
788+ uint256 z; z.SetNull ();
789+ BlockHeaderType g = plain_header (z, 0x1d00ffffu , 1 );
790+ ASSERT_TRUE (chain_.add_header (g));
791+
792+ uint32_t aux_bits = 0x207fffffu ;
793+ BlockHeaderType h1 = plain_header (block_hash (g), aux_bits, 9 );
794+ uint256 aux = block_hash (h1);
795+ AuxPow ap = complete_proof (aux, 0x1d00ffffu );
796+ ASSERT_TRUE (mine_parent (ap, chain::bits_to_target (aux_bits)));
797+ ASSERT_EQ (chain_.verify_auxpow_header (h1, ap), AuxPow::CheckResult::VALID );
798+
799+ // proof is VALID, but height 1 is below activation -> premature, NOT stored.
800+ EXPECT_FALSE (chain_.add_auxpow_header (h1, ap));
801+ EXPECT_FALSE (chain_.has_header (aux));
802+ EXPECT_EQ (chain_.height (), 0u );
803+ }
804+
638805} // namespace
0 commit comments