Skip to content

Commit 8a0fd2b

Browse files
ctnguyenoskarszoon
andauthored
Add check for P2SH output after genesis activation (#134)
Co-authored-by: oskarszoon <[email protected]>
1 parent 513a70e commit 8a0fd2b

File tree

3 files changed

+52
-10
lines changed

3 files changed

+52
-10
lines changed

main_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -323,14 +323,16 @@ func TestPopulateVersionInfoComponents(t *testing.T) {
323323
parsedTime, err := time.Parse("20060102150405", timestampStr)
324324
if err == nil {
325325
// If we can parse it as a timestamp, verify it's reasonable
326-
// (Could be git timestamp or current time)
327-
// Allow for timezone differences and some timing flexibility
326+
// Git timestamps can be arbitrarily old, so we just check:
327+
// 1. Not before project start (2020)
328+
// 2. Not more than 1 hour in the future (allowing for clock skew)
328329
now := time.Now().UTC()
329-
timeDiff := parsedTime.Sub(now)
330-
assert.True(t, timeDiff < 25*time.Hour && timeDiff > -25*time.Hour,
331-
"Timestamp should be within 25 hours of current time (git timestamp: %v, current: %v, diff: %v)",
332-
parsedTime, now, timeDiff)
333-
t.Logf("Parsed timestamp: %v (current: %v, diff: %v)", parsedTime, now, timeDiff)
330+
projectStart := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
331+
assert.True(t, parsedTime.After(projectStart),
332+
"Timestamp should be after project start date (got: %v)", parsedTime)
333+
assert.True(t, parsedTime.Before(now.Add(1*time.Hour)),
334+
"Timestamp should not be more than 1 hour in the future (got: %v, now: %v)", parsedTime, now)
335+
t.Logf("Parsed timestamp: %v", parsedTime)
334336
}
335337
}
336338
}

services/validator/TxValidator.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,16 +319,24 @@ func isStandardInputScript(script *bscript.Script, blockHeight uint32, uahfHeigh
319319
func (tv *TxValidator) checkOutputs(tx *bt.Tx, blockHeight uint32, validationOptions *Options) error {
320320
total := uint64(0)
321321

322+
// Note: We use > instead of >= to exclude the Genesis activation block itself
323+
// because transactions in block 620538 were created before Genesis rules existed
324+
isGenesisActivated := blockHeight > tv.settings.ChainCfgParams.GenesisActivationHeight
325+
322326
for index, output := range tx.Outputs {
327+
// Check P2SH output after genesis activation
328+
if !validationOptions.SkipPolicyChecks && isGenesisActivated && output.LockingScript.IsP2SH() {
329+
// See https://github.com/bitcoin-sv/teranode/issues/4333
330+
return errors.NewTxInvalidError("transaction output %d is p2sh after genesis activation", index)
331+
}
332+
323333
if output.Satoshis > MaxSatoshis {
324334
return errors.NewTxInvalidError("transaction output %d satoshis is invalid", index)
325335
}
326336

327337
// Check dust limit after genesis activation
328-
// Note: We use > instead of >= to exclude the Genesis activation block itself
329-
// because transactions in block 620538 were created before Genesis rules existed
330338
// Dust checks are policy rules, not consensus rules - they only apply to mempool/relay
331-
if !validationOptions.SkipPolicyChecks && blockHeight > tv.settings.ChainCfgParams.GenesisActivationHeight {
339+
if !validationOptions.SkipPolicyChecks && isGenesisActivated {
332340
// Only enforce dust limit for spendable outputs when RequireStandard is true
333341
if tv.settings.ChainCfgParams.RequireStandard && output.Satoshis < DustLimit && !isUnspendableOutput(output.LockingScript) {
334342
return errors.NewTxInvalidError("zero-satoshi outputs require 'OP_FALSE OP_RETURN' prefix")

services/validator/TxValidator_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,38 @@ func Test_MinFeePolicy(t *testing.T) {
614614
}
615615
}
616616

617+
func TestCheckP2SHOutput(t *testing.T) {
618+
tSettings := test.CreateBaseTestSettings(t)
619+
tSettings.ChainCfgParams.RequireStandard = true
620+
621+
txValidator := NewTxValidator(ulogger.TestLogger{}, tSettings)
622+
623+
// See https://github.com/bitcoin-sv/teranode/issues/4333
624+
txP2SH, err := bt.NewTxFromString("020000000000000000ef01e0d8bc7aae870d67eaf3021492735637ddae403feb7914fb739a53872a82d301000000006a473044022041215b9ac965ce93684340d86d74df5ccf2d0910f36173a9d691e8405b37fd400220300ab0376d9d75542eaaffb4fe1eead267f0ac537ae13a4349506274978066f7412103afe4a8eb7f3f69757235bb8db804a01156af9d1cace07af534ca9be7f4928a5effffffffacc88203000000001976a9140533653ad7e12be8ee8151bc586f04bf859ae4d788ac0267307e03000000001976a9140533653ad7e12be8ee8151bc586f04bf859ae4d788ace09304000000000017a914496164f9f2e373628c5cc0a5895d995aaf3bec658700000000")
625+
require.NoError(t, err)
626+
627+
// At Genesis activation height, p2sh should not be rejected
628+
err = txValidator.ValidateTransaction(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight, nil, &Options{})
629+
require.NoError(t, err)
630+
631+
err = txValidator.checkOutputs(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight, &Options{})
632+
require.NoError(t, err)
633+
634+
// After Genesis activation height, p2sh should be rejected
635+
err = txValidator.ValidateTransaction(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, nil, &Options{})
636+
require.Error(t, err)
637+
638+
err = txValidator.checkOutputs(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, &Options{})
639+
require.Error(t, err)
640+
641+
// After Genesis activation height, with skip policy check, p2sh should be accepted
642+
err = txValidator.ValidateTransaction(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, nil, &Options{SkipPolicyChecks: true})
643+
require.NoError(t, err)
644+
645+
err = txValidator.checkOutputs(txP2SH, tSettings.ChainCfgParams.GenesisActivationHeight+1, &Options{SkipPolicyChecks: true})
646+
require.NoError(t, err)
647+
}
648+
617649
func TestCheckFees(t *testing.T) {
618650
tSettings := test.CreateBaseTestSettings(t)
619651

0 commit comments

Comments
 (0)