fix(consensus): validate miner tx versions by fork era
Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
b34afa827f
commit
0ab8bfbd01
3 changed files with 143 additions and 17 deletions
|
|
@ -61,10 +61,34 @@ func medianTimestamp(timestamps []uint64) uint64 {
|
|||
return sorted[n/2]
|
||||
}
|
||||
|
||||
func expectedMinerTxVersion(forks []config.HardFork, height uint64) uint64 {
|
||||
switch {
|
||||
case config.IsHardForkActive(forks, config.HF5, height):
|
||||
return types.VersionPostHF5
|
||||
case config.IsHardForkActive(forks, config.HF4Zarcanum, height):
|
||||
return types.VersionPostHF4
|
||||
default:
|
||||
return types.VersionPreHF4
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateMinerTx checks the structure of a coinbase (miner) transaction.
|
||||
// For PoW blocks: exactly 1 input (TxInputGenesis). For PoS blocks: exactly
|
||||
// 2 inputs (TxInputGenesis + stake input).
|
||||
func ValidateMinerTx(tx *types.Transaction, height uint64, forks []config.HardFork) error {
|
||||
expectedVersion := expectedMinerTxVersion(forks, height)
|
||||
if tx.Version != expectedVersion {
|
||||
return coreerr.E("ValidateMinerTx", fmt.Sprintf("version %d invalid at height %d (expected %d)",
|
||||
tx.Version, height, expectedVersion), ErrMinerTxVersion)
|
||||
}
|
||||
if tx.Version >= types.VersionPostHF5 {
|
||||
currentFork := config.VersionAtHeight(forks, height)
|
||||
if tx.HardforkID != currentFork {
|
||||
return coreerr.E("ValidateMinerTx", fmt.Sprintf("hardfork id %d does not match active fork %d at height %d",
|
||||
tx.HardforkID, currentFork, height), ErrMinerTxVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if len(tx.Vin) == 0 {
|
||||
return coreerr.E("ValidateMinerTx", "no inputs", ErrMinerTxInputs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ func TestCheckTimestamp_Ugly(t *testing.T) {
|
|||
|
||||
func validMinerTx(height uint64) *types.Transaction {
|
||||
return &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: height}},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}},
|
||||
|
|
@ -74,6 +74,15 @@ func validMinerTx(height uint64) *types.Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
func validMinerTxForForks(height uint64, forks []config.HardFork) *types.Transaction {
|
||||
tx := validMinerTx(height)
|
||||
tx.Version = expectedMinerTxVersion(forks, height)
|
||||
if tx.Version >= types.VersionPostHF5 {
|
||||
tx.HardforkID = config.VersionAtHeight(forks, height)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Good(t *testing.T) {
|
||||
tx := validMinerTx(100)
|
||||
err := ValidateMinerTx(tx, 100, config.MainnetForks)
|
||||
|
|
@ -87,14 +96,14 @@ func TestValidateMinerTx_Bad_WrongHeight(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValidateMinerTx_Bad_NoInputs(t *testing.T) {
|
||||
tx := &types.Transaction{Version: types.VersionInitial}
|
||||
tx := &types.Transaction{Version: types.VersionPreHF4}
|
||||
err := ValidateMinerTx(tx, 100, config.MainnetForks)
|
||||
assert.ErrorIs(t, err, ErrMinerTxInputs)
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Bad_WrongFirstInput(t *testing.T) {
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{types.TxInputToKey{Amount: 1}},
|
||||
}
|
||||
err := ValidateMinerTx(tx, 100, config.MainnetForks)
|
||||
|
|
@ -103,7 +112,7 @@ func TestValidateMinerTx_Bad_WrongFirstInput(t *testing.T) {
|
|||
|
||||
func TestValidateMinerTx_Good_PoS(t *testing.T) {
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{
|
||||
types.TxInputGenesis{Height: 100},
|
||||
types.TxInputToKey{Amount: 1}, // PoS stake input
|
||||
|
|
@ -117,6 +126,98 @@ func TestValidateMinerTx_Good_PoS(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Version_Good(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forks []config.HardFork
|
||||
tx *types.Transaction
|
||||
height uint64
|
||||
}{
|
||||
{
|
||||
name: "mainnet_pre_hf4_v1",
|
||||
forks: config.MainnetForks,
|
||||
height: 100,
|
||||
tx: validMinerTx(100),
|
||||
},
|
||||
{
|
||||
name: "testnet_post_hf4_v2",
|
||||
forks: config.TestnetForks,
|
||||
height: 101,
|
||||
tx: &types.Transaction{
|
||||
Version: types.VersionPostHF4,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: 101}},
|
||||
Vout: []types.TxOutput{types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testnet_post_hf5_v3",
|
||||
forks: config.TestnetForks,
|
||||
height: 201,
|
||||
tx: &types.Transaction{
|
||||
Version: types.VersionPostHF5,
|
||||
HardforkID: config.HF5,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: 201}},
|
||||
Vout: []types.TxOutput{types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateMinerTx(tt.tx, tt.height, tt.forks)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Version_Bad(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
forks []config.HardFork
|
||||
height uint64
|
||||
tx *types.Transaction
|
||||
}{
|
||||
{
|
||||
name: "mainnet_pre_hf4_v0",
|
||||
forks: config.MainnetForks,
|
||||
height: 100,
|
||||
tx: &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: 100}},
|
||||
Vout: []types.TxOutput{types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testnet_post_hf4_v1",
|
||||
forks: config.TestnetForks,
|
||||
height: 101,
|
||||
tx: &types.Transaction{
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: 101}},
|
||||
Vout: []types.TxOutput{types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "testnet_post_hf5_wrong_hardfork_id",
|
||||
forks: config.TestnetForks,
|
||||
height: 201,
|
||||
tx: &types.Transaction{
|
||||
Version: types.VersionPostHF5,
|
||||
HardforkID: config.HF4Zarcanum,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: 201}},
|
||||
Vout: []types.TxOutput{types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateMinerTx(tt.tx, tt.height, tt.forks)
|
||||
assert.ErrorIs(t, err, ErrMinerTxVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBlockReward_Good(t *testing.T) {
|
||||
height := uint64(100)
|
||||
tx := validMinerTx(height)
|
||||
|
|
@ -127,7 +228,7 @@ func TestValidateBlockReward_Good(t *testing.T) {
|
|||
func TestValidateBlockReward_Bad_TooMuch(t *testing.T) {
|
||||
height := uint64(100)
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: height}},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: config.BlockReward + 1, Target: types.TxOutToKey{Key: types.PublicKey{1}}},
|
||||
|
|
@ -141,7 +242,7 @@ func TestValidateBlockReward_Good_WithFees(t *testing.T) {
|
|||
height := uint64(100)
|
||||
fees := uint64(50_000_000_000)
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: height}},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: config.BlockReward + fees, Target: types.TxOutToKey{Key: types.PublicKey{1}}},
|
||||
|
|
@ -423,7 +524,7 @@ func TestValidateBlock_MajorVersion_Good(t *testing.T) {
|
|||
Timestamp: now,
|
||||
Flags: 0,
|
||||
},
|
||||
MinerTx: *validMinerTx(tt.height),
|
||||
MinerTx: *validMinerTxForForks(tt.height, tt.forks),
|
||||
}
|
||||
err := ValidateBlock(blk, tt.height, 1000, config.BlockGrantedFullRewardZone, 0, now, nil, tt.forks)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -457,7 +558,7 @@ func TestValidateBlock_MajorVersion_Bad(t *testing.T) {
|
|||
Timestamp: now,
|
||||
Flags: 0,
|
||||
},
|
||||
MinerTx: *validMinerTx(tt.height),
|
||||
MinerTx: *validMinerTxForForks(tt.height, tt.forks),
|
||||
}
|
||||
err := ValidateBlock(blk, tt.height, 1000, config.BlockGrantedFullRewardZone, 0, now, nil, tt.forks)
|
||||
assert.ErrorIs(t, err, ErrBlockMajorVersion)
|
||||
|
|
|
|||
|
|
@ -29,15 +29,16 @@ var (
|
|||
ErrNegativeFee = errors.New("consensus: outputs exceed inputs")
|
||||
|
||||
// Block errors.
|
||||
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")
|
||||
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")
|
||||
ErrMinerTxVersion = errors.New("consensus: invalid miner transaction version for current hardfork")
|
||||
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")
|
||||
|
||||
// ErrBlockVersion is an alias for ErrBlockMajorVersion, used by
|
||||
// checkBlockVersion when the block major version does not match
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue