@@ -1013,4 +1013,107 @@ TEST(NmcP1eForkChoice, FirstHeaderBecomesTipRegardlessOfPriorState)
10131013 EXPECT_EQ (chain_.cumulative_work (), nmc::coin::get_block_proof (kEasyBits ));
10141014}
10151015
1016+ // ── P1f: HeaderChain::get_locator — BIP31 block locator over tip ancestry ──
1017+
1018+ TEST (NmcP1fLocator, EmptyChainProducesEmptyLocator)
1019+ {
1020+ HeaderChain chain_ (params_activation (19200 ));
1021+ EXPECT_TRUE (chain_.get_locator ().empty ());
1022+ }
1023+
1024+ TEST (NmcP1fLocator, SingleHeaderLocatorIsJustTheTip)
1025+ {
1026+ HeaderChain chain_ (params_activation (19200 ));
1027+ uint256 z; z.SetNull ();
1028+ BlockHeaderType g = plain_header (z, kEasyBits , 1 );
1029+ ASSERT_TRUE (chain_.add_header (g));
1030+ auto loc = chain_.get_locator ();
1031+ ASSERT_EQ (loc.size (), 1u );
1032+ EXPECT_EQ (loc.front (), block_hash (g));
1033+ }
1034+
1035+ TEST (NmcP1fLocator, ShortChainLocatorIsDenseTipToRoot)
1036+ {
1037+ HeaderChain chain_ (params_activation (19200 ));
1038+ uint256 z; z.SetNull ();
1039+ BlockHeaderType g = plain_header (z, kEasyBits , 1 );
1040+ ASSERT_TRUE (chain_.add_header (g));
1041+ BlockHeaderType a1 = plain_header (block_hash (g), kEasyBits , 2 );
1042+ ASSERT_TRUE (chain_.add_header (a1));
1043+ BlockHeaderType a2 = plain_header (block_hash (a1), kEasyBits , 3 );
1044+ ASSERT_TRUE (chain_.add_header (a2));
1045+ auto loc = chain_.get_locator ();
1046+ ASSERT_EQ (loc.size (), 3u ); // dense: every block while <= 10 deep
1047+ EXPECT_EQ (loc[0 ], block_hash (a2)); // tip first
1048+ EXPECT_EQ (loc[1 ], block_hash (a1));
1049+ EXPECT_EQ (loc[2 ], block_hash (g)); // root last
1050+ }
1051+
1052+ TEST (NmcP1fLocator, LongChainFrontIsContiguousAndAnchoredAtRoot)
1053+ {
1054+ HeaderChain chain_ (params_activation (19200 ));
1055+ uint256 z; z.SetNull ();
1056+ BlockHeaderType g = plain_header (z, kEasyBits , 1 );
1057+ ASSERT_TRUE (chain_.add_header (g));
1058+ std::vector<uint256> hashes{block_hash (g)};
1059+ uint256 prev = block_hash (g);
1060+ for (uint32_t i = 1 ; i <= 30 ; ++i) { // height 0..30 (31 headers)
1061+ BlockHeaderType h = plain_header (prev, kEasyBits , i + 1 );
1062+ ASSERT_TRUE (chain_.add_header (h));
1063+ prev = block_hash (h);
1064+ hashes.push_back (prev);
1065+ }
1066+ auto loc = chain_.get_locator ();
1067+ EXPECT_EQ (chain_.height (), 30u );
1068+ // First 11 entries are contiguous from the tip (step stays 1 until >10).
1069+ for (size_t i = 0 ; i < 11u ; ++i)
1070+ EXPECT_EQ (loc[i], hashes[hashes.size () - 1 - i]);
1071+ // Backoff makes the locator far shorter than the chain, root-anchored.
1072+ EXPECT_LT (loc.size (), hashes.size ());
1073+ EXPECT_EQ (loc.back (), block_hash (g));
1074+ }
1075+
1076+ TEST (NmcP1fLocator, BackoffStrideDoublesPastTheDenseHead)
1077+ {
1078+ HeaderChain chain_ (params_activation (19200 ));
1079+ uint256 z; z.SetNull ();
1080+ BlockHeaderType g = plain_header (z, kEasyBits , 1 );
1081+ ASSERT_TRUE (chain_.add_header (g));
1082+ uint256 prev = block_hash (g);
1083+ for (uint32_t i = 1 ; i <= 30 ; ++i) {
1084+ BlockHeaderType h = plain_header (prev, kEasyBits , i + 1 );
1085+ ASSERT_TRUE (chain_.add_header (h));
1086+ prev = block_hash (h);
1087+ }
1088+ auto loc = chain_.get_locator ();
1089+ // No duplicates, strictly descending toward the root, all known to the chain.
1090+ for (size_t i = 0 ; i < loc.size (); ++i)
1091+ EXPECT_TRUE (chain_.has_header (loc[i]));
1092+ for (size_t i = 1 ; i < loc.size (); ++i)
1093+ EXPECT_NE (loc[i], loc[i - 1 ]);
1094+ }
1095+
1096+ TEST (NmcP1fLocator, LocatorFollowsTheTipAfterAWorkReorg)
1097+ {
1098+ HeaderChain chain_ (params_activation (19200 ));
1099+ uint256 z; z.SetNull ();
1100+ BlockHeaderType g = plain_header (z, kEasyBits , 1 );
1101+ ASSERT_TRUE (chain_.add_header (g));
1102+ // Light branch a1<-a2 builds the initial tip.
1103+ BlockHeaderType a1 = plain_header (block_hash (g), kEasyBits , 2 );
1104+ ASSERT_TRUE (chain_.add_header (a1));
1105+ BlockHeaderType a2 = plain_header (block_hash (a1), kEasyBits , 3 );
1106+ ASSERT_TRUE (chain_.add_header (a2));
1107+ // One heavy sibling at height 1 reorgs the tip onto branch b.
1108+ BlockHeaderType b1 = plain_header (block_hash (g), kHardBits , 4 );
1109+ ASSERT_TRUE (chain_.add_header (b1));
1110+ ASSERT_EQ (chain_.tip ()->block_hash , block_hash (b1));
1111+ auto loc = chain_.get_locator ();
1112+ // Locator tracks the NEW tip's ancestry (b1 <- g), not the abandoned a-branch.
1113+ ASSERT_EQ (loc.size (), 2u );
1114+ EXPECT_EQ (loc[0 ], block_hash (b1));
1115+ EXPECT_EQ (loc[1 ], block_hash (g));
1116+ EXPECT_EQ (std::count (loc.begin (), loc.end (), block_hash (a2)), 0 );
1117+ }
1118+
10161119} // namespace
0 commit comments