diff --git a/main_test.go b/main_test.go index b1898cb9d..b096252cc 100644 --- a/main_test.go +++ b/main_test.go @@ -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) } } } diff --git a/services/validator/TxValidator.go b/services/validator/TxValidator.go index 6294532d5..8820dd6c8 100644 --- a/services/validator/TxValidator.go +++ b/services/validator/TxValidator.go @@ -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") diff --git a/services/validator/TxValidator_test.go b/services/validator/TxValidator_test.go index f3bd926bb..f0e17e01e 100644 --- a/services/validator/TxValidator_test.go +++ b/services/validator/TxValidator_test.go @@ -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)