Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,16 @@ func TestPopulateVersionInfoComponents(t *testing.T) {
parsedTime, err := time.Parse("20060102150405", timestampStr)
if err == nil {
// If we can parse it as a timestamp, verify it's reasonable
// (Could be git timestamp or current time)
// Allow for timezone differences and some timing flexibility
// Git timestamps can be arbitrarily old, so we just check:
// 1. Not before project start (2020)
// 2. Not more than 1 hour in the future (allowing for clock skew)
now := time.Now().UTC()
timeDiff := parsedTime.Sub(now)
assert.True(t, timeDiff < 25*time.Hour && timeDiff > -25*time.Hour,
"Timestamp should be within 25 hours of current time (git timestamp: %v, current: %v, diff: %v)",
parsedTime, now, timeDiff)
t.Logf("Parsed timestamp: %v (current: %v, diff: %v)", parsedTime, now, timeDiff)
projectStart := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
assert.True(t, parsedTime.After(projectStart),
"Timestamp should be after project start date (got: %v)", parsedTime)
assert.True(t, parsedTime.Before(now.Add(1*time.Hour)),
"Timestamp should not be more than 1 hour in the future (got: %v, now: %v)", parsedTime, now)
t.Logf("Parsed timestamp: %v", parsedTime)
}
}
}
Expand Down
14 changes: 11 additions & 3 deletions services/validator/TxValidator.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,16 +319,24 @@ func isStandardInputScript(script *bscript.Script, blockHeight uint32, uahfHeigh
func (tv *TxValidator) checkOutputs(tx *bt.Tx, blockHeight uint32, validationOptions *Options) error {
total := uint64(0)

// Note: We use > instead of >= to exclude the Genesis activation block itself
// because transactions in block 620538 were created before Genesis rules existed
isGenesisActivated := blockHeight > tv.settings.ChainCfgParams.GenesisActivationHeight

for index, output := range tx.Outputs {
// Check P2SH output after genesis activation
if !validationOptions.SkipPolicyChecks && isGenesisActivated && output.LockingScript.IsP2SH() {
// See https://github.com/bitcoin-sv/teranode/issues/4333
return errors.NewTxInvalidError("transaction output %d is p2sh after genesis activation", index)
}

if output.Satoshis > MaxSatoshis {
return errors.NewTxInvalidError("transaction output %d satoshis is invalid", index)
}

// Check dust limit after genesis activation
// Note: We use > instead of >= to exclude the Genesis activation block itself
// because transactions in block 620538 were created before Genesis rules existed
// Dust checks are policy rules, not consensus rules - they only apply to mempool/relay
if !validationOptions.SkipPolicyChecks && blockHeight > tv.settings.ChainCfgParams.GenesisActivationHeight {
if !validationOptions.SkipPolicyChecks && isGenesisActivated {
// Only enforce dust limit for spendable outputs when RequireStandard is true
if tv.settings.ChainCfgParams.RequireStandard && output.Satoshis < DustLimit && !isUnspendableOutput(output.LockingScript) {
return errors.NewTxInvalidError("zero-satoshi outputs require 'OP_FALSE OP_RETURN' prefix")
Expand Down
32 changes: 32 additions & 0 deletions services/validator/TxValidator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,38 @@ func Test_MinFeePolicy(t *testing.T) {
}
}

func TestCheckP2SHOutput(t *testing.T) {
tSettings := test.CreateBaseTestSettings(t)
tSettings.ChainCfgParams.RequireStandard = true

txValidator := NewTxValidator(ulogger.TestLogger{}, tSettings)

// See https://github.com/bitcoin-sv/teranode/issues/4333
txP2SH, err := bt.NewTxFromString("020000000000000000ef01e0d8bc7aae870d67eaf3021492735637ddae403feb7914fb739a53872a82d301000000006a473044022041215b9ac965ce93684340d86d74df5ccf2d0910f36173a9d691e8405b37fd400220300ab0376d9d75542eaaffb4fe1eead267f0ac537ae13a4349506274978066f7412103afe4a8eb7f3f69757235bb8db804a01156af9d1cace07af534ca9be7f4928a5effffffffacc88203000000001976a9140533653ad7e12be8ee8151bc586f04bf859ae4d788ac0267307e03000000001976a9140533653ad7e12be8ee8151bc586f04bf859ae4d788ace09304000000000017a914496164f9f2e373628c5cc0a5895d995aaf3bec658700000000")
require.NoError(t, err)

// At Genesis activation height, p2sh should not be rejected
err = txValidator.ValidateTransaction(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight, nil, &Options{})
require.NoError(t, err)

err = txValidator.checkOutputs(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight, &Options{})
require.NoError(t, err)

// After Genesis activation height, p2sh should be rejected
err = txValidator.ValidateTransaction(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, nil, &Options{})
require.Error(t, err)

err = txValidator.checkOutputs(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, &Options{})
require.Error(t, err)

// After Genesis activation height, with skip policy check, p2sh should be accepted
err = txValidator.ValidateTransaction(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, nil, &Options{SkipPolicyChecks: true})
require.NoError(t, err)

err = txValidator.checkOutputs(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, &Options{SkipPolicyChecks: true})
require.NoError(t, err)
}

func TestCheckFees(t *testing.T) {
tSettings := test.CreateBaseTestSettings(t)

Expand Down