feat(consensus): miner transaction validation
ValidateMinerTx checks genesis input height, input count (1 for PoW, 2 for PoS), and stake input type per hardfork version. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
b3f33f5265
commit
3ee066e233
2 changed files with 94 additions and 0 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"forge.lthn.ai/core/go-blockchain/config"
|
||||
"forge.lthn.ai/core/go-blockchain/types"
|
||||
)
|
||||
|
||||
// IsPoS returns true if the block flags indicate a Proof-of-Stake block.
|
||||
|
|
@ -57,3 +58,42 @@ func medianTimestamp(timestamps []uint64) uint64 {
|
|||
}
|
||||
return sorted[n/2]
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if len(tx.Vin) == 0 {
|
||||
return fmt.Errorf("%w: no inputs", ErrMinerTxInputs)
|
||||
}
|
||||
|
||||
// First input must be TxInputGenesis.
|
||||
gen, ok := tx.Vin[0].(types.TxInputGenesis)
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: first input is not txin_gen", ErrMinerTxInputs)
|
||||
}
|
||||
if gen.Height != height {
|
||||
return fmt.Errorf("%w: got %d, expected %d", ErrMinerTxHeight, gen.Height, height)
|
||||
}
|
||||
|
||||
// PoW blocks: exactly 1 input. PoS: exactly 2.
|
||||
if len(tx.Vin) == 1 {
|
||||
// PoW — valid.
|
||||
} else if len(tx.Vin) == 2 {
|
||||
// PoS — second input must be a spend input.
|
||||
switch tx.Vin[1].(type) {
|
||||
case types.TxInputToKey:
|
||||
// Pre-HF4 PoS.
|
||||
default:
|
||||
hf4Active := config.IsHardForkActive(forks, config.HF4Zarcanum, height)
|
||||
if !hf4Active {
|
||||
return fmt.Errorf("%w: invalid PoS stake input type", ErrMinerTxInputs)
|
||||
}
|
||||
// Post-HF4: accept ZC inputs.
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("%w: %d inputs (expected 1 or 2)", ErrMinerTxInputs, len(tx.Vin))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"forge.lthn.ai/core/go-blockchain/config"
|
||||
"forge.lthn.ai/core/go-blockchain/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -62,3 +63,56 @@ func TestCheckTimestamp_Ugly(t *testing.T) {
|
|||
err := CheckTimestamp(now-200, 0, now, timestamps) // old but under 60 entries
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func validMinerTx(height uint64) *types.Transaction {
|
||||
return &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: height}},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Good(t *testing.T) {
|
||||
tx := validMinerTx(100)
|
||||
err := ValidateMinerTx(tx, 100, config.MainnetForks)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Bad_WrongHeight(t *testing.T) {
|
||||
tx := validMinerTx(100)
|
||||
err := ValidateMinerTx(tx, 200, config.MainnetForks) // height mismatch
|
||||
assert.ErrorIs(t, err, ErrMinerTxHeight)
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Bad_NoInputs(t *testing.T) {
|
||||
tx := &types.Transaction{Version: types.VersionInitial}
|
||||
err := ValidateMinerTx(tx, 100, config.MainnetForks)
|
||||
assert.ErrorIs(t, err, ErrMinerTxInputs)
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Bad_WrongFirstInput(t *testing.T) {
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Vin: []types.TxInput{types.TxInputToKey{Amount: 1}},
|
||||
}
|
||||
err := ValidateMinerTx(tx, 100, config.MainnetForks)
|
||||
assert.ErrorIs(t, err, ErrMinerTxInputs)
|
||||
}
|
||||
|
||||
func TestValidateMinerTx_Good_PoS(t *testing.T) {
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionInitial,
|
||||
Vin: []types.TxInput{
|
||||
types.TxInputGenesis{Height: 100},
|
||||
types.TxInputToKey{Amount: 1}, // PoS stake input
|
||||
},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: config.BlockReward, Target: types.TxOutToKey{Key: types.PublicKey{1}}},
|
||||
},
|
||||
}
|
||||
err := ValidateMinerTx(tx, 100, config.MainnetForks)
|
||||
// 2 inputs with genesis + TxInputToKey is valid PoS structure.
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue