From fd67c5b049aa43ccf2227651af966fe0e6e9b6ef Mon Sep 17 00:00:00 2001 From: Matt Hawkins Date: Sun, 30 Aug 2026 15:21:14 -0500 Subject: [PATCH] fullblocktests: Add ErrInvalidRevokeInput test. This adds a test which ensures the chain rejects a block when a revocation references a ticket output other than the submission output. The test mutates the revocation to reference the ticket change output. The change output still exists in the utxo set, so the input existence checks pass and the index check itself rejects the block. This also renames brt7 to brt8 so the accepted block that ends the revocation series keeps the highest number. --- blockchain/fullblocktests/error.go | 4 +++ blockchain/fullblocktests/error_test.go | 3 +- blockchain/fullblocktests/generate.go | 45 ++++++++++++++++++------- internal/blockchain/fullblocks_test.go | 4 ++- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/blockchain/fullblocktests/error.go b/blockchain/fullblocktests/error.go index 50afb0f417..5ee98a7bd0 100644 --- a/blockchain/fullblocktests/error.go +++ b/blockchain/fullblocktests/error.go @@ -228,6 +228,10 @@ const ( // amount required by the commitment. ErrBadPayeeValue = ErrorKind("ErrBadPayeeValue") + // ErrInvalidRevokeInput indicates that an input to a revocation transaction + // is either not a stake ticket submission or is not a supported version. + ErrInvalidRevokeInput = ErrorKind("ErrInvalidRevokeInput") + // ErrTxSStxOutSpend indicates that a non SSGen or SSRtx tx attempted to // spend an OP_SSTX tagged output from an SStx. ErrTxSStxOutSpend = ErrorKind("ErrTxSStxOutSpend") diff --git a/blockchain/fullblocktests/error_test.go b/blockchain/fullblocktests/error_test.go index b93dce5531..b7bd09b9eb 100644 --- a/blockchain/fullblocktests/error_test.go +++ b/blockchain/fullblocktests/error_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 The Decred developers +// Copyright (c) 2022-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -67,6 +67,7 @@ func TestErrorKindStringer(t *testing.T) { {ErrBadNumPayees, "ErrBadNumPayees"}, {ErrMismatchedPayeeHash, "ErrMismatchedPayeeHash"}, {ErrBadPayeeValue, "ErrBadPayeeValue"}, + {ErrInvalidRevokeInput, "ErrInvalidRevokeInput"}, {ErrTxSStxOutSpend, "ErrTxSStxOutSpend"}, {ErrRegTxCreateStakeOut, "ErrRegTxCreateStakeOut"}, {ErrInvalidFinalState, "ErrInvalidFinalState"}, diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index 0decb9b708..dff8977129 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -2857,12 +2857,33 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { g.AssertTipNumRevocations(2) rejected(ErrInvalidSSRtx) + // Create block that has a revocation that references the wrong output + // index of the associated ticket. + // + // ... -> brt1(24) + // \-> brt7(25) + g.SetTip("brt1") + g.NextBlock("brt7", outs[25], ticketOuts[25], func(b *wire.MsgBlock) { + g.AssertBlockRevocationTx(b, 10) + + // Modify the revocation to reference the ticket change output + // instead of the required ticket submission output. The change + // output still exists in the utxo set, so the input existence + // checks pass and the wrong index condition itself causes the + // rejection. + const ticketChangeOutputIdx = 2 + prevOut := &b.STransactions[10].TxIn[0].PreviousOutPoint + prevOut.Index = ticketChangeOutputIdx + }) + g.AssertTipNumRevocations(1) + rejected(ErrInvalidRevokeInput) + // Create block that contains a revocation due to previous missed vote. // - // ... -> brt1(24) -> brt7(25) + // ... -> brt1(24) -> brt8(25) g.SetTip("brt1") - g.NextBlock("brt7", outs[25], ticketOuts[25]) - brt7Tx1Out := chaingen.MakeSpendableOut(g.Tip(), 1, 0) + g.NextBlock("brt8", outs[25], ticketOuts[25]) + brt8Tx1Out := chaingen.MakeSpendableOut(g.Tip(), 1, 0) g.AssertTipNumRevocations(1) accepted() @@ -2873,9 +2894,9 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // Create block that disapproves the regular transaction tree of the prev // block and tries to spend a transaction from it. // - // ... -> brt7(25) + // ... -> brt8(25) // \-> bdt1(26) - g.NextBlock("bdt1", &brt7Tx1Out, ticketOuts[26], func(b *wire.MsgBlock) { + g.NextBlock("bdt1", &brt8Tx1Out, ticketOuts[26], func(b *wire.MsgBlock) { b.Header.VoteBits &^= voteBitYes for i := 0; i < 5; i++ { g.ReplaceVoteBitsN(i, voteBitNo)(b) @@ -2889,22 +2910,22 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // regular transaction tree of a block that will be disapproved via a side // chain. // - // ... -> brt7(25) -> bdt2(26) -> bdt3(27) - g.SetTip("brt7") - g.NextBlock("bdt2", &brt7Tx1Out, ticketOuts[26]) + // ... -> brt8(25) -> bdt2(26) -> bdt3(27) + g.SetTip("brt8") + g.NextBlock("bdt2", &brt8Tx1Out, ticketOuts[26]) accepted() g.NextBlock("bdt3", outs[27], ticketOuts[27]) accepted() - // Create a fork from brt7 that contains a couple of subsequent valid blocks + // Create a fork from brt8 that contains a couple of subsequent valid blocks // that disapprove the regular transaction tree of the previous blocks and // extend it to force a reorg to the chain that contains the disapproving // blocks. // - // ... -> brt7(25) -> bdt2(26) -> bdt3(27) + // ... -> brt8(25) -> bdt2(26) -> bdt3(27) // \-> bdt4(26) -> bdt5(27) -> bdt6(28) - g.SetTip("brt7") + g.SetTip("brt8") g.NextBlock("bdt4", outs[26], ticketOuts[26], func(b *wire.MsgBlock) { b.Header.VoteBits &^= voteBitYes for i := 0; i < 5; i++ { @@ -2929,7 +2950,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // Extend the original bdt3 fork in order to make the first chain longer and // force a reorg that removes the disapproving blocks. // - // ... -> brt7(25) -> bdt2(26) -> bdt3(27) -> bdt7(28) -> bdt8(29) + // ... -> brt8(25) -> bdt2(26) -> bdt3(27) -> bdt7(28) -> bdt8(29) // \-> bdt4(26) -> bdt5(27) -> bdt6(28) g.SetTip("bdt3") g.NextBlock("bdt7", outs[28], ticketOuts[28]) diff --git a/internal/blockchain/fullblocks_test.go b/internal/blockchain/fullblocks_test.go index b4610bef5e..685f8de0f9 100644 --- a/internal/blockchain/fullblocks_test.go +++ b/internal/blockchain/fullblocks_test.go @@ -1,5 +1,5 @@ // Copyright (c) 2016 The btcsuite developers -// Copyright (c) 2016-2022 The Decred developers +// Copyright (c) 2016-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -125,6 +125,8 @@ func fullBlockTestErrToLocalErr(t *testing.T, kind fullblocktests.ErrorKind) Err return ErrMismatchedPayeeHash case fullblocktests.ErrBadPayeeValue: return ErrBadPayeeValue + case fullblocktests.ErrInvalidRevokeInput: + return ErrInvalidRevokeInput case fullblocktests.ErrTxSStxOutSpend: return ErrTxSStxOutSpend case fullblocktests.ErrRegTxCreateStakeOut: