diff --git a/chain/sync.go b/chain/sync.go index e81d2e1..0ba28ed 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -173,7 +173,7 @@ func (c *Chain) processBlockBlobs(blockBlob []byte, txBlobs [][]byte, } // Validate header. - if err := c.ValidateHeader(&blk, height); err != nil { + if err := c.ValidateHeader(&blk, height, opts.Forks); err != nil { return err } diff --git a/chain/validate.go b/chain/validate.go index 50c5322..c106348 100644 --- a/chain/validate.go +++ b/chain/validate.go @@ -12,13 +12,14 @@ import ( coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/config" + "dappco.re/go/core/blockchain/consensus" "dappco.re/go/core/blockchain/types" "dappco.re/go/core/blockchain/wire" ) // ValidateHeader checks a block header before storage. // expectedHeight is the height at which this block would be stored. -func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { +func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64, forks []config.HardFork) error { currentHeight, err := c.Height() if err != nil { return coreerr.E("Chain.ValidateHeader", "validate: get height", err) @@ -34,6 +35,9 @@ func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { if !b.PrevID.IsZero() { return coreerr.E("Chain.ValidateHeader", "validate: genesis block has non-zero prev_id", nil) } + if err := consensus.CheckBlockVersion(b.MajorVersion, forks, expectedHeight); err != nil { + return coreerr.E("Chain.ValidateHeader", "validate: block version", err) + } return nil } @@ -46,6 +50,11 @@ func (c *Chain) ValidateHeader(b *types.Block, expectedHeight uint64) error { return coreerr.E("Chain.ValidateHeader", fmt.Sprintf("validate: prev_id %s does not match top block %s", b.PrevID, topMeta.Hash), nil) } + // Block major version check. + if err := consensus.CheckBlockVersion(b.MajorVersion, forks, expectedHeight); err != nil { + return coreerr.E("Chain.ValidateHeader", "validate: block version", err) + } + // Block size check. var buf bytes.Buffer enc := wire.NewEncoder(&buf) diff --git a/chain/validate_test.go b/chain/validate_test.go index d7230b8..7c7d0a8 100644 --- a/chain/validate_test.go +++ b/chain/validate_test.go @@ -8,8 +8,9 @@ package chain import ( "testing" - store "dappco.re/go/core/store" + "dappco.re/go/core/blockchain/config" "dappco.re/go/core/blockchain/types" + store "dappco.re/go/core/store" ) func TestValidateHeader_Good_Genesis(t *testing.T) { @@ -19,13 +20,13 @@ func TestValidateHeader_Good_Genesis(t *testing.T) { blk := &types.Block{ BlockHeader: types.BlockHeader{ - MajorVersion: 1, + MajorVersion: 0, Timestamp: 1770897600, }, MinerTx: testCoinbaseTx(0), } - err := c.ValidateHeader(blk, 0) + err := c.ValidateHeader(blk, 0, config.MainnetForks) if err != nil { t.Fatalf("ValidateHeader genesis: %v", err) } @@ -38,7 +39,7 @@ func TestValidateHeader_Good_Sequential(t *testing.T) { // Store block 0. blk0 := &types.Block{ - BlockHeader: types.BlockHeader{MajorVersion: 1, Timestamp: 1770897600}, + BlockHeader: types.BlockHeader{MajorVersion: 0, Timestamp: 1770897600}, MinerTx: testCoinbaseTx(0), } hash0 := types.Hash{0x01} @@ -47,14 +48,14 @@ func TestValidateHeader_Good_Sequential(t *testing.T) { // Validate block 1. blk1 := &types.Block{ BlockHeader: types.BlockHeader{ - MajorVersion: 1, + MajorVersion: 0, Timestamp: 1770897720, PrevID: hash0, }, MinerTx: testCoinbaseTx(1), } - err := c.ValidateHeader(blk1, 1) + err := c.ValidateHeader(blk1, 1, config.MainnetForks) if err != nil { t.Fatalf("ValidateHeader block 1: %v", err) } @@ -66,21 +67,21 @@ func TestValidateHeader_Bad_WrongPrevID(t *testing.T) { c := New(s) blk0 := &types.Block{ - BlockHeader: types.BlockHeader{MajorVersion: 1, Timestamp: 1770897600}, + BlockHeader: types.BlockHeader{MajorVersion: 0, Timestamp: 1770897600}, MinerTx: testCoinbaseTx(0), } c.PutBlock(blk0, &BlockMeta{Hash: types.Hash{0x01}, Height: 0}) blk1 := &types.Block{ BlockHeader: types.BlockHeader{ - MajorVersion: 1, + MajorVersion: 0, Timestamp: 1770897720, PrevID: types.Hash{0xFF}, // wrong }, MinerTx: testCoinbaseTx(1), } - err := c.ValidateHeader(blk1, 1) + err := c.ValidateHeader(blk1, 1, config.MainnetForks) if err == nil { t.Fatal("expected error for wrong prev_id") } @@ -92,12 +93,12 @@ func TestValidateHeader_Bad_WrongHeight(t *testing.T) { c := New(s) blk := &types.Block{ - BlockHeader: types.BlockHeader{MajorVersion: 1, Timestamp: 1770897600}, + BlockHeader: types.BlockHeader{MajorVersion: 0, Timestamp: 1770897600}, MinerTx: testCoinbaseTx(0), } // Chain is empty (height 0), but we pass expectedHeight=5. - err := c.ValidateHeader(blk, 5) + err := c.ValidateHeader(blk, 5, config.MainnetForks) if err == nil { t.Fatal("expected error for wrong height") } @@ -110,14 +111,33 @@ func TestValidateHeader_Bad_GenesisNonZeroPrev(t *testing.T) { blk := &types.Block{ BlockHeader: types.BlockHeader{ - MajorVersion: 1, + MajorVersion: 0, PrevID: types.Hash{0xFF}, // genesis must have zero prev_id }, MinerTx: testCoinbaseTx(0), } - err := c.ValidateHeader(blk, 0) + err := c.ValidateHeader(blk, 0, config.MainnetForks) if err == nil { t.Fatal("expected error for genesis with non-zero prev_id") } } + +func TestValidateHeader_Bad_WrongVersion(t *testing.T) { + s, _ := store.New(":memory:") + defer s.Close() + c := New(s) + + blk := &types.Block{ + BlockHeader: types.BlockHeader{ + MajorVersion: 1, + Timestamp: 1770897600, + }, + MinerTx: testCoinbaseTx(0), + } + + err := c.ValidateHeader(blk, 0, config.MainnetForks) + if err == nil { + t.Fatal("expected error for wrong block version") + } +} diff --git a/consensus/block.go b/consensus/block.go index f34b4f8..fa1bc77 100644 --- a/consensus/block.go +++ b/consensus/block.go @@ -127,14 +127,14 @@ func ValidateBlockReward(minerTx *types.Transaction, height, blockSize, medianSi return nil } -// expectedBlockMajorVersion returns the expected block major version for a +// ExpectedBlockMajorVersion returns the expected block major version for a // given height and fork schedule. This maps hardfork eras to block versions: // // HF0 (genesis) -> 0 // HF1 -> 1 // HF3 -> 2 // HF4+ -> 3 -func expectedBlockMajorVersion(forks []config.HardFork, height uint64) uint8 { +func ExpectedBlockMajorVersion(forks []config.HardFork, height uint64) uint8 { if config.IsHardForkActive(forks, config.HF4Zarcanum, height) { return config.CurrentBlockMajorVersion // 3 } @@ -147,13 +147,13 @@ func expectedBlockMajorVersion(forks []config.HardFork, height uint64) uint8 { return config.BlockMajorVersionInitial // 0 } -// checkBlockVersion validates that the block's major version matches +// CheckBlockVersion validates that the block's major version matches // what is expected at the given height in the fork schedule. -func checkBlockVersion(blk *types.Block, forks []config.HardFork, height uint64) error { - expected := expectedBlockMajorVersion(forks, height) - if blk.MajorVersion != expected { - return coreerr.E("checkBlockVersion", fmt.Sprintf("got %d, want %d at height %d", - blk.MajorVersion, expected, height), ErrBlockMajorVersion) +func CheckBlockVersion(majorVersion uint8, forks []config.HardFork, height uint64) error { + expected := ExpectedBlockMajorVersion(forks, height) + if majorVersion != expected { + return coreerr.E("CheckBlockVersion", fmt.Sprintf("got %d, want %d at height %d", + majorVersion, expected, height), ErrBlockMajorVersion) } return nil } @@ -166,7 +166,7 @@ func ValidateBlock(blk *types.Block, height, blockSize, medianSize, totalFees, a recentTimestamps []uint64, forks []config.HardFork) error { // Block major version check. - if err := checkBlockVersion(blk, forks, height); err != nil { + if err := CheckBlockVersion(blk.MajorVersion, forks, height); err != nil { return err } diff --git a/consensus/block_test.go b/consensus/block_test.go index 0c6f781..9492941 100644 --- a/consensus/block_test.go +++ b/consensus/block_test.go @@ -263,9 +263,9 @@ func TestExpectedBlockMajorVersion_Good(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := expectedBlockMajorVersion(tt.forks, tt.height) + got := ExpectedBlockMajorVersion(tt.forks, tt.height) if got != tt.want { - t.Errorf("expectedBlockMajorVersion(%d) = %d, want %d", tt.height, got, tt.want) + t.Errorf("ExpectedBlockMajorVersion(%d) = %d, want %d", tt.height, got, tt.want) } }) } @@ -294,7 +294,7 @@ func TestCheckBlockVersion_Good(t *testing.T) { Flags: 0, }, } - err := checkBlockVersion(blk, tt.forks, tt.height) + err := CheckBlockVersion(blk.MajorVersion, tt.forks, tt.height) require.NoError(t, err) }) } @@ -323,7 +323,7 @@ func TestCheckBlockVersion_Bad(t *testing.T) { Flags: 0, }, } - err := checkBlockVersion(blk, tt.forks, tt.height) + err := CheckBlockVersion(blk.MajorVersion, tt.forks, tt.height) assert.ErrorIs(t, err, ErrBlockVersion) }) } @@ -336,17 +336,17 @@ func TestCheckBlockVersion_Ugly(t *testing.T) { blk := &types.Block{ BlockHeader: types.BlockHeader{MajorVersion: 255, Timestamp: now}, } - err := checkBlockVersion(blk, config.MainnetForks, 0) + err := CheckBlockVersion(blk.MajorVersion, config.MainnetForks, 0) assert.ErrorIs(t, err, ErrBlockVersion) - err = checkBlockVersion(blk, config.MainnetForks, 10081) + err = CheckBlockVersion(blk.MajorVersion, config.MainnetForks, 10081) assert.ErrorIs(t, err, ErrBlockVersion) // Version 0 at the exact HF1 boundary (height 10080 -- fork not yet active). blk0 := &types.Block{ BlockHeader: types.BlockHeader{MajorVersion: config.BlockMajorVersionInitial, Timestamp: now}, } - err = checkBlockVersion(blk0, config.MainnetForks, 10080) + err = CheckBlockVersion(blk0.MajorVersion, config.MainnetForks, 10080) require.NoError(t, err) }