diff --git a/blockchain/blake2b_test.go b/blockchain/blake2b_test.go index 80535d0d80..b9c4f3d160 100644 --- a/blockchain/blake2b_test.go +++ b/blockchain/blake2b_test.go @@ -249,3 +249,35 @@ func TestBlake2bHeaderContext(t *testing.T) { t.Fatalf("classic header without BLAKE2b configured: %v", err) } } + +// TestCheckHeaderV2Sanity pins the context-free rule Knots applies in +// CheckBlockHeader: a v2 header committing to the activation height is +// accepted, one committing to the block below it is refused (bad-version- +// sha256d), and a network without BLAKE2b configured takes no v2 header. +func TestCheckHeaderV2Sanity(t *testing.T) { + params := &chaincfg.MainNetParams + v2 := mustHeader(t, activationHeaderRaw) + if v2.Height != 961640 { + t.Fatalf("the activation header commits to %d", v2.Height) + } + + if err := CheckHeaderV2Sanity(v2, params); err != nil { + t.Fatalf("v2 header committing to the activation height: %v", err) + } + below := *v2 + below.Height = 961639 + if code, _ := ruleCode(CheckHeaderV2Sanity(&below, params)); code != ErrBadHeaderV2 { + t.Fatalf("v2 header committing to the block below the activation: got %v, want ErrBadHeaderV2", code) + } + + classic := chaincfg.MainNetParams.GenesisBlock.Header + if err := CheckHeaderV2Sanity(&classic, params); err != nil { + t.Fatalf("classic header: %v", err) + } + + none := chaincfg.MainNetParams + none.Blake2bHeight = 0 + if code, _ := ruleCode(CheckHeaderV2Sanity(v2, &none)); code != ErrUnsupportedProofOfWork { + t.Fatalf("v2 header without BLAKE2b configured: got %v, want ErrUnsupportedProofOfWork", code) + } +} diff --git a/blockchain/blake2bpow.go b/blockchain/blake2bpow.go index 65f9518fd3..d7cff5c3c2 100644 --- a/blockchain/blake2bpow.go +++ b/blockchain/blake2bpow.go @@ -73,6 +73,30 @@ func checkHeaderV2Context(header *wire.BlockHeader, height int32, return nil } +// CheckHeaderV2Sanity is the context-free part of Knots' CheckBlockHeader for +// BLAKE2b headers: a v2 header must commit to a height at which BLAKE2b proof +// of work is required (Knots' bad-version-sha256d), and a network without +// BLAKE2b proof of work configured takes no v2 header. It needs only the +// header, so it runs where the block's real height is not known yet (an +// orphan, a header out of order); checkHeaderV2Context then checks the +// committed height against the real one. +func CheckHeaderV2Sanity(header *wire.BlockHeader, params *chaincfg.Params) error { + if !header.IsV2() { + return nil + } + if !params.Blake2bPowDeployed() { + return ruleError(ErrUnsupportedProofOfWork, "BLAKE2b (header v2) "+ + "proof of work is not configured for network "+params.Name) + } + if !params.IsBlake2bHeight(header.Height) { + str := fmt.Sprintf("header v2 commits to height %d: blocks require "+ + "SHA256d proof of work until height %d", header.Height, + params.Blake2bHeight) + return ruleError(ErrBadHeaderV2, str) + } + return nil +} + // applyBlake2bTargetShift is Knots' ApplyBlake2bTargetShift: the target of the // first block mined under BLAKE2b, from the one the usual rules give. func applyBlake2bTargetShift(bits uint32, params *chaincfg.Params) uint32 { diff --git a/blockchain/process.go b/blockchain/process.go index 64d5c1e14f..8e76e84c70 100644 --- a/blockchain/process.go +++ b/blockchain/process.go @@ -169,6 +169,10 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo if err != nil { return false, false, err } + err = CheckHeaderV2Sanity(&block.MsgBlock().Header, b.chainParams) + if err != nil { + return false, false, err + } // Find the previous checkpoint and perform some additional checks based // on the checkpoint. This provides a few nice properties such as diff --git a/blockchain/validate.go b/blockchain/validate.go index 2febf62f1c..85d3859902 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -1394,6 +1394,10 @@ func (b *BlockChain) CheckConnectBlockTemplate(block *btcutil.Block) error { if err != nil { return err } + err = CheckHeaderV2Sanity(&header, b.chainParams) + if err != nil { + return err + } err = b.checkBlockContext(block, tip, flags) if err != nil { diff --git a/rpcserver.go b/rpcserver.go index bec0fad82c..ec53b69eec 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3720,6 +3720,11 @@ func verifyChain(s *rpcServer, level, depth int32) error { if level > 0 { err := blockchain.CheckBlockSanity(block, s.cfg.ChainParams.PowLimit, s.cfg.TimeSource) + if err == nil { + err = blockchain.CheckHeaderV2Sanity( + &block.MsgBlock().Header, s.cfg.ChainParams, + ) + } if err != nil { rpcsLog.Errorf("Verify is unable to validate "+ "block at hash %v height %d: %v",