From cda2beb551509a02c2968460a43fac5a4a0f4ae8 Mon Sep 17 00:00:00 2001 From: Matt Hawkins Date: Mon, 31 Aug 2026 00:41:04 -0500 Subject: [PATCH] blockchain: Test auto revocation input index. This adds a test case to TestAutoRevocations for the consensus rule that requires the first input of a revocation to reference the ticket submission output. The rule applies when automatic ticket revocations are active. The new case modifies the version 2 revocation for a missed ticket so that its ticket input references the ticket change output at index 2. It then ensures the block is rejected due to the invalid input. The case uses index 2 rather than index 1 since the commitment output at index 1 is an OP_RETURN output that is never part of the utxo set. A revocation that references it fails with a missing utxo error before the input index check. --- internal/blockchain/validate_test.go | 43 ++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/internal/blockchain/validate_test.go b/internal/blockchain/validate_test.go index 040d070207..3330a05cc5 100644 --- a/internal/blockchain/validate_test.go +++ b/internal/blockchain/validate_test.go @@ -1867,12 +1867,49 @@ func TestAutoRevocations(t *testing.T) { // ticket revocations agenda is active. g.RejectTipBlock(ErrRegTxCreateStakeOut) + // Create a block that misses a vote and contains a revocation that + // references an output other than the ticket submission output. + // + // ... + // \-> b4(0) + g.SetTip(startTip) + g.NextBlock("b4", outs[0], ticketOuts[0], g.ReplaceWithNVotes(4), + g.CreateRevocationsForMissedTickets(), replaceAutoRevocationsVersions, + func(b *wire.MsgBlock) { + for _, stx := range b.STransactions { + if !stake.IsSSRtx(stx) { + continue + } + + // Ensure the revocation references the ticket submission + // output before this test modifies it. + prevOut := &stx.TxIn[0].PreviousOutPoint + if prevOut.Index != 0 { + t.Fatalf("expected revocation to reference the ticket "+ + "submission output, got output %d", prevOut.Index) + } + + // Modify the ticket input to reference the ticket change + // output and return so that only a single revocation + // transaction is modified. + prevOut.Index = 2 + return + } + }) + g.AssertTipNumRevocations(1) + // Note that the revocation references the change output of the ticket + // at output index 2 rather than the commitment output at index 1 since + // the commitment output is an OP_RETURN output that is never part of + // the utxo set, which would result in a missing utxo error before the + // input index check. + g.RejectTipBlock(ErrInvalidRevokeInput) + // Create a valid block that misses multiple votes and contains revocation // transactions for those votes. // - // ... -> b4(0) + // ... -> b5(0) g.SetTip(startTip) - g.NextBlock("b4", outs[0], ticketOuts[0], g.ReplaceWithNVotes(3), + g.NextBlock("b5", outs[0], ticketOuts[0], g.ReplaceWithNVotes(3), g.CreateRevocationsForMissedTickets(), replaceAutoRevocationsVersions) g.AssertTipNumRevocations(2) g.AcceptTipBlock() @@ -1906,7 +1943,7 @@ func TestAutoRevocations(t *testing.T) { } // Invalidate the previously connected block so that it is disconnected. - g.InvalidateBlockAndExpectTip("b4", nil, startTip) + g.InvalidateBlockAndExpectTip("b5", nil, startTip) // Validate that the revocations from the disconnected block are now back in // the live ticket treap in the ticket database.