From 0993b081c7f7fa0bddf0467973400cfb82252449 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 21:30:53 +0000 Subject: [PATCH] test(chain): pin HTLC expiration boundary Co-Authored-By: Charon --- chain/ring.go | 3 ++- chain/ring_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/chain/ring.go b/chain/ring.go index 1f4746c..174cfa9 100644 --- a/chain/ring.go +++ b/chain/ring.go @@ -53,7 +53,8 @@ func (c *Chain) GetRingOutputs(height, amount uint64, offsets []uint64) ([]types // TxOutMultisig does not carry enough context here to select the exact spend // path, so we return the first listed key as a deterministic fallback. // TxOutHTLC selects redeem vs refund based on whether the spending height is -// before or after the contract expiration. +// before or after the contract expiration. The refund path only opens after +// the expiration height has passed. func ringOutputSpendKey(height uint64, target types.TxOutTarget) (types.PublicKey, error) { if t, ok := types.AsTxOutToKey(target); ok { return t.Key, nil diff --git a/chain/ring_test.go b/chain/ring_test.go index 351633d..109db91 100644 --- a/chain/ring_test.go +++ b/chain/ring_test.go @@ -142,6 +142,47 @@ func TestGetRingOutputs_Good_HTLC(t *testing.T) { } } +func TestGetRingOutputs_Good_HTLCExpirationBoundary(t *testing.T) { + c := newTestChain(t) + + redeem := types.PublicKey{0xAA, 0xBB, 0xCC} + refund := types.PublicKey{0xDD, 0xEE, 0xFF} + tx := types.Transaction{ + Version: types.VersionPreHF4, + Vin: []types.TxInput{types.TxInputGenesis{Height: 0}}, + Vout: []types.TxOutput{ + types.TxOutputBare{ + Amount: 1000, + Target: types.TxOutHTLC{ + HTLCHash: types.Hash{0x01}, + Flags: 0, + Expiration: 200, + PKRedeem: redeem, + PKRefund: refund, + }, + }, + }, + Extra: wire.EncodeVarint(0), + Attachment: wire.EncodeVarint(0), + } + txHash := wire.TransactionHash(&tx) + + if err := c.PutTransaction(txHash, &tx, &TxMeta{KeeperBlock: 0, GlobalOutputIndexes: []uint64{0}}); err != nil { + t.Fatalf("PutTransaction: %v", err) + } + if _, err := c.PutOutput(1000, txHash, 0); err != nil { + t.Fatalf("PutOutput: %v", err) + } + + pubs, err := c.GetRingOutputs(200, 1000, []uint64{0}) + if err != nil { + t.Fatalf("GetRingOutputs boundary path: %v", err) + } + if pubs[0] != redeem { + t.Errorf("pubs[0] boundary path: got %x, want %x", pubs[0], redeem) + } +} + func TestGetRingOutputs_Good_MultipleOutputs(t *testing.T) { c := newTestChain(t)