feat(consensus): validate block major version for HF1
Adds expectedBlockMajorVersion and checkBlockVersion, called from ValidateBlock before timestamp validation. Block version must match the fork era: HF0->0, HF1->1, HF3->2, HF4+->3. Tests cover both mainnet and testnet fork schedules including boundary heights. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
f88d582c64
commit
b1a0e9637b
3 changed files with 150 additions and 14 deletions
|
|
@ -125,13 +125,49 @@ func ValidateBlockReward(minerTx *types.Transaction, height, blockSize, medianSi
|
|||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if config.IsHardForkActive(forks, config.HF4Zarcanum, height) {
|
||||
return config.CurrentBlockMajorVersion // 3
|
||||
}
|
||||
if config.IsHardForkActive(forks, config.HF3, height) {
|
||||
return config.HF3BlockMajorVersion // 2
|
||||
}
|
||||
if config.IsHardForkActive(forks, config.HF1, height) {
|
||||
return config.HF1BlockMajorVersion // 1
|
||||
}
|
||||
return config.BlockMajorVersionInitial // 0
|
||||
}
|
||||
|
||||
// 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 fmt.Errorf("%w: got %d, want %d at height %d",
|
||||
ErrBlockMajorVersion, blk.MajorVersion, expected, height)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBlock performs full consensus validation on a block. It checks
|
||||
// the timestamp, miner transaction structure, and reward. Transaction
|
||||
// semantic validation for regular transactions should be done separately
|
||||
// via ValidateTransaction for each tx in the block.
|
||||
// the block version, timestamp, miner transaction structure, and reward.
|
||||
// Transaction semantic validation for regular transactions should be done
|
||||
// separately via ValidateTransaction for each tx in the block.
|
||||
func ValidateBlock(blk *types.Block, height, blockSize, medianSize, totalFees, adjustedTime uint64,
|
||||
recentTimestamps []uint64, forks []config.HardFork) error {
|
||||
|
||||
// Block major version check.
|
||||
if err := checkBlockVersion(blk, forks, height); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Timestamp validation.
|
||||
if err := CheckTimestamp(blk.Timestamp, blk.Flags, adjustedTime, recentTimestamps); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ func TestValidateBlock_Good(t *testing.T) {
|
|||
height := uint64(100)
|
||||
blk := &types.Block{
|
||||
BlockHeader: types.BlockHeader{
|
||||
MajorVersion: 1,
|
||||
MajorVersion: 0, // pre-HF1 on mainnet
|
||||
Timestamp: now,
|
||||
Flags: 0, // PoW
|
||||
},
|
||||
|
|
@ -172,7 +172,7 @@ func TestValidateBlock_Bad_Timestamp(t *testing.T) {
|
|||
height := uint64(100)
|
||||
blk := &types.Block{
|
||||
BlockHeader: types.BlockHeader{
|
||||
MajorVersion: 1,
|
||||
MajorVersion: 0, // pre-HF1 on mainnet
|
||||
Timestamp: now + config.BlockFutureTimeLimit + 100,
|
||||
Flags: 0,
|
||||
},
|
||||
|
|
@ -188,7 +188,7 @@ func TestValidateBlock_Bad_MinerTx(t *testing.T) {
|
|||
height := uint64(100)
|
||||
blk := &types.Block{
|
||||
BlockHeader: types.BlockHeader{
|
||||
MajorVersion: 1,
|
||||
MajorVersion: 0, // pre-HF1 on mainnet
|
||||
Timestamp: now,
|
||||
Flags: 0,
|
||||
},
|
||||
|
|
@ -198,3 +198,102 @@ func TestValidateBlock_Bad_MinerTx(t *testing.T) {
|
|||
err := ValidateBlock(blk, height, 1000, config.BlockGrantedFullRewardZone, 0, now, nil, config.MainnetForks)
|
||||
assert.ErrorIs(t, err, ErrMinerTxHeight)
|
||||
}
|
||||
|
||||
// --- Block major version tests (Task 10) ---
|
||||
|
||||
func TestValidateBlock_MajorVersion_Good(t *testing.T) {
|
||||
now := uint64(time.Now().Unix())
|
||||
tests := []struct {
|
||||
name string
|
||||
forks []config.HardFork
|
||||
height uint64
|
||||
version uint8
|
||||
}{
|
||||
// Mainnet: pre-HF1 expects version 0.
|
||||
{name: "mainnet_preHF1", forks: config.MainnetForks, height: 5000, version: 0},
|
||||
// Mainnet: post-HF1 expects version 1.
|
||||
{name: "mainnet_postHF1", forks: config.MainnetForks, height: 20000, version: 1},
|
||||
// Testnet: HF1 active from genesis, HF3 active from genesis, expects version 2.
|
||||
{name: "testnet_genesis", forks: config.TestnetForks, height: 5, version: 2},
|
||||
// Testnet: post-HF4 (height > 100) expects version 3.
|
||||
{name: "testnet_postHF4", forks: config.TestnetForks, height: 200, version: 3},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
blk := &types.Block{
|
||||
BlockHeader: types.BlockHeader{
|
||||
MajorVersion: tt.version,
|
||||
Timestamp: now,
|
||||
Flags: 0,
|
||||
},
|
||||
MinerTx: *validMinerTx(tt.height),
|
||||
}
|
||||
err := ValidateBlock(blk, tt.height, 1000, config.BlockGrantedFullRewardZone, 0, now, nil, tt.forks)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBlock_MajorVersion_Bad(t *testing.T) {
|
||||
now := uint64(time.Now().Unix())
|
||||
tests := []struct {
|
||||
name string
|
||||
forks []config.HardFork
|
||||
height uint64
|
||||
version uint8
|
||||
}{
|
||||
// Mainnet: pre-HF1 with wrong version 1.
|
||||
{name: "mainnet_preHF1_v1", forks: config.MainnetForks, height: 5000, version: 1},
|
||||
// Mainnet: post-HF1 with wrong version 0.
|
||||
{name: "mainnet_postHF1_v0", forks: config.MainnetForks, height: 20000, version: 0},
|
||||
// Mainnet: post-HF1 with wrong version 2.
|
||||
{name: "mainnet_postHF1_v2", forks: config.MainnetForks, height: 20000, version: 2},
|
||||
// Testnet: post-HF4 with wrong version 2.
|
||||
{name: "testnet_postHF4_v2", forks: config.TestnetForks, height: 200, version: 2},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
blk := &types.Block{
|
||||
BlockHeader: types.BlockHeader{
|
||||
MajorVersion: tt.version,
|
||||
Timestamp: now,
|
||||
Flags: 0,
|
||||
},
|
||||
MinerTx: *validMinerTx(tt.height),
|
||||
}
|
||||
err := ValidateBlock(blk, tt.height, 1000, config.BlockGrantedFullRewardZone, 0, now, nil, tt.forks)
|
||||
assert.ErrorIs(t, err, ErrBlockMajorVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBlock_MajorVersion_Ugly(t *testing.T) {
|
||||
now := uint64(time.Now().Unix())
|
||||
// Boundary test: exactly at HF1 activation height (10080) on mainnet.
|
||||
// HF1 activates at heights strictly greater than 10080, so at height
|
||||
// 10080 itself HF1 is NOT active; version must be 0.
|
||||
blk := &types.Block{
|
||||
BlockHeader: types.BlockHeader{
|
||||
MajorVersion: 0,
|
||||
Timestamp: now,
|
||||
Flags: 0,
|
||||
},
|
||||
MinerTx: *validMinerTx(10080),
|
||||
}
|
||||
err := ValidateBlock(blk, 10080, 1000, config.BlockGrantedFullRewardZone, 0, now, nil, config.MainnetForks)
|
||||
require.NoError(t, err)
|
||||
|
||||
// At height 10081, HF1 IS active; version must be 1.
|
||||
blk2 := &types.Block{
|
||||
BlockHeader: types.BlockHeader{
|
||||
MajorVersion: 1,
|
||||
Timestamp: now,
|
||||
Flags: 0,
|
||||
},
|
||||
MinerTx: *validMinerTx(10081),
|
||||
}
|
||||
err = ValidateBlock(blk2, 10081, 1000, config.BlockGrantedFullRewardZone, 0, now, nil, config.MainnetForks)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,12 +27,13 @@ var (
|
|||
ErrNegativeFee = errors.New("consensus: outputs exceed inputs")
|
||||
|
||||
// Block errors.
|
||||
ErrBlockTooLarge = errors.New("consensus: block exceeds max size")
|
||||
ErrTimestampFuture = errors.New("consensus: block timestamp too far in future")
|
||||
ErrTimestampOld = errors.New("consensus: block timestamp below median")
|
||||
ErrMinerTxInputs = errors.New("consensus: invalid miner transaction inputs")
|
||||
ErrMinerTxHeight = errors.New("consensus: miner transaction height mismatch")
|
||||
ErrMinerTxUnlock = errors.New("consensus: miner transaction unlock time invalid")
|
||||
ErrRewardMismatch = errors.New("consensus: block reward mismatch")
|
||||
ErrMinerTxProofs = errors.New("consensus: miner transaction proof count invalid")
|
||||
ErrBlockTooLarge = errors.New("consensus: block exceeds max size")
|
||||
ErrBlockMajorVersion = errors.New("consensus: invalid block major version for height")
|
||||
ErrTimestampFuture = errors.New("consensus: block timestamp too far in future")
|
||||
ErrTimestampOld = errors.New("consensus: block timestamp below median")
|
||||
ErrMinerTxInputs = errors.New("consensus: invalid miner transaction inputs")
|
||||
ErrMinerTxHeight = errors.New("consensus: miner transaction height mismatch")
|
||||
ErrMinerTxUnlock = errors.New("consensus: miner transaction unlock time invalid")
|
||||
ErrRewardMismatch = errors.New("consensus: block reward mismatch")
|
||||
ErrMinerTxProofs = errors.New("consensus: miner transaction proof count invalid")
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue