Skip to content

Commit c888891

Browse files
authored
Merge pull request #189 from frstrtr/nmc/p1d-share-accept-gate
nmc(P1d): wire AuxPow::check_proof gate into add_auxpow_header
2 parents 6bb8dd4 + be5b1ef commit c888891

2 files changed

Lines changed: 122 additions & 8 deletions

File tree

src/impl/nmc/coin/header_chain.hpp

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -633,16 +633,36 @@ class HeaderChain {
633633
return false;
634634
}
635635

636+
/// Verify the AuxPow consensus GATE for an incoming merge-mined header.
637+
/// P1d: factored out so the gate is unit-testable independently of the
638+
/// (still-deferred) header-storage path. Runs the full four-leg
639+
/// AuxPow::check_proof against the AUX block hash (block_hash(header)), this
640+
/// chain's claimed aux_chain_id, and the header's own nBits as the aux PoW
641+
/// target (Namecoin checks the parent PoW against the AUX bits, not the
642+
/// parent's own). A header is admissible ONLY when this returns VALID.
643+
AuxPow::CheckResult verify_auxpow_header(const BlockHeaderType& header,
644+
const AuxPow& auxpow) const {
645+
return auxpow.check_proof(block_hash(header), m_params.aux_chain_id,
646+
header.m_bits);
647+
}
648+
636649
/// Add a header that carries a merge-mining AuxPow proof.
637-
/// P0-DEFER: stores nothing and verifies nothing. The real path must call
638-
/// AuxPow::check_proof() (itself a P0 stub) before accepting.
650+
/// P1d: the AuxPow VERIFICATION GATE is now wired - check_proof() must
651+
/// return VALID or the header is rejected, so the (future) accept path can
652+
/// never admit an unproven merge-mined header. The header-storage /
653+
/// chain-connection path, and the height-derived is_auxpow_active()
654+
/// activation gate (which needs a connected parent), remain P0-DEFER - see
655+
/// add_header(). A header that passes the gate is therefore NOT persisted
656+
/// yet; P1d's contract is the rejection half: nothing unproven gets in.
639657
bool add_auxpow_header(const BlockHeaderType& header, const AuxPow& auxpow) {
640-
(void)header;
641-
(void)auxpow;
642-
// P0-DEFER: auxpow header add/validate path not implemented.
643-
// NOTE: even when built, must reject unless params.is_auxpow_active(h)
644-
// and auxpow.check_proof(block_hash(header), params.aux_chain_id)
645-
// == AuxPow::CheckResult::VALID.
658+
if (verify_auxpow_header(header, auxpow) != AuxPow::CheckResult::VALID) {
659+
LOG_WARNING << "[EMB-NMC] reject auxpow header "
660+
<< block_hash(header).ToString()
661+
<< ": AuxPow check_proof != VALID";
662+
return false;
663+
}
664+
// P0-DEFER: proof verified, but header storage/connection + the
665+
// is_auxpow_active(height) activation gate are not built yet.
646666
return false;
647667
}
648668

src/impl/nmc/test/auxpow_merkle_test.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,4 +541,98 @@ TEST(NmcAuxStep4, ParentOwnBitsAreNotTheAuxGate)
541541
EXPECT_EQ(ap.check_proof(aux, 1, aux_bits), AuxPow::CheckResult::VALID);
542542
}
543543

544+
// ---------------------------------------------------------------------------
545+
// P1d - HeaderChain::add_auxpow_header / verify_auxpow_header consensus GATE.
546+
//
547+
// The gate wires AuxPow::check_proof into the header-accept path: an incoming
548+
// merge-mined header is admissible ONLY when its proof fully verifies against
549+
// THIS chain's params (aux_chain_id) and the header's own nBits as the aux PoW
550+
// target. These KATs pin the rejection contract - nothing unproven gets in -
551+
// reusing the step-4 complete_proof/mine_parent fixtures but binding the aux
552+
// block hash to a REAL header via block_hash(header), the production seam.
553+
// Storage stays P0-DEFER, so a verified header is not persisted yet (add
554+
// returns false on both arms); the assertion is on the verify verdict.
555+
// ---------------------------------------------------------------------------
556+
557+
using nmc::coin::HeaderChain;
558+
using nmc::coin::NMCChainParams;
559+
using nmc::coin::BlockHeaderType;
560+
using nmc::coin::block_hash;
561+
562+
// Params with a pinned aux_chain_id == 1 so the fixture's MM-marker slot
563+
// (chain_id=1) binds. Activation height stays unpinned: the P1d gate under test
564+
// is the cryptographic check_proof, not the (deferred) height-activation path.
565+
static NMCChainParams params_chain_id_1()
566+
{
567+
NMCChainParams p = NMCChainParams::mainnet();
568+
p.aux_chain_id = 1;
569+
return p;
570+
}
571+
572+
// A header whose block_hash() becomes the AUX hash the proof commits to; m_bits
573+
// carries the aux PoW target the parent hash must clear (Namecoin aux-bits rule).
574+
static BlockHeaderType aux_header_for(uint32_t aux_bits)
575+
{
576+
BlockHeaderType h{};
577+
h.m_version = 1;
578+
h.m_bits = aux_bits;
579+
return h;
580+
}
581+
582+
TEST(NmcP1dGate, FullyVerifiedAuxPowPassesTheGate)
583+
{
584+
uint32_t aux_bits = 0x207fffffu; // regtest-style easy target
585+
BlockHeaderType h = aux_header_for(aux_bits);
586+
uint256 aux = block_hash(h); // production seam
587+
AuxPow ap = complete_proof(aux, /*parent_own_bits=*/0x1d00ffffu);
588+
ASSERT_TRUE(mine_parent(ap, chain::bits_to_target(aux_bits)));
589+
590+
HeaderChain chain_(params_chain_id_1());
591+
EXPECT_EQ(chain_.verify_auxpow_header(h, ap), AuxPow::CheckResult::VALID);
592+
// Storage is still P0-DEFER, so even a verified header is not persisted yet.
593+
EXPECT_FALSE(chain_.add_auxpow_header(h, ap));
594+
EXPECT_FALSE(chain_.has_header(aux));
595+
}
596+
597+
TEST(NmcP1dGate, WrongChainIdIsNotValidAndIsRejected)
598+
{
599+
uint32_t aux_bits = 0x207fffffu;
600+
BlockHeaderType h = aux_header_for(aux_bits);
601+
uint256 aux = block_hash(h);
602+
AuxPow ap = complete_proof(aux, 0x1d00ffffu);
603+
ASSERT_TRUE(mine_parent(ap, chain::bits_to_target(aux_bits)));
604+
605+
NMCChainParams p = NMCChainParams::mainnet();
606+
p.aux_chain_id = 2; // != fixture's chain_id 1
607+
HeaderChain chain_(p);
608+
EXPECT_NE(chain_.verify_auxpow_header(h, ap), AuxPow::CheckResult::VALID);
609+
EXPECT_FALSE(chain_.add_auxpow_header(h, ap));
610+
}
611+
612+
TEST(NmcP1dGate, InsufficientParentWorkIsRejected)
613+
{
614+
BlockHeaderType h = aux_header_for(/*aux_bits=*/0x03000001u); // target == 1
615+
uint256 aux = block_hash(h);
616+
AuxPow ap = complete_proof(aux, 0x1d00ffffu);
617+
// Unmined parent: a real double-SHA256 hash exceeds target 1 => INVALID.
618+
ASSERT_TRUE(pow_hash(ap.parent_header) > chain::bits_to_target(h.m_bits));
619+
620+
HeaderChain chain_(params_chain_id_1());
621+
EXPECT_EQ(chain_.verify_auxpow_header(h, ap), AuxPow::CheckResult::INVALID);
622+
EXPECT_FALSE(chain_.add_auxpow_header(h, ap));
623+
}
624+
625+
TEST(NmcP1dGate, MissingAuxBitsKeepsProofIncompleteAndRejected)
626+
{
627+
BlockHeaderType h = aux_header_for(/*aux_bits=*/0u); // 0 == leg-only sentinel
628+
uint256 aux = block_hash(h);
629+
AuxPow ap = complete_proof(aux, 0x1d00ffffu);
630+
631+
HeaderChain chain_(params_chain_id_1());
632+
// header.m_bits == 0 feeds check_proof's leg-only sentinel: step 4 skipped,
633+
// the proof stays INCOMPLETE, and the gate rejects.
634+
EXPECT_EQ(chain_.verify_auxpow_header(h, ap), AuxPow::CheckResult::INCOMPLETE);
635+
EXPECT_FALSE(chain_.add_auxpow_header(h, ap));
636+
}
637+
544638
} // namespace

0 commit comments

Comments
 (0)