commit 698b5a1cb601f52c8668d2e4342459682c5b2db2
parent 56ce6c5246ce245dae96cd3a1d2af8fb538af01d
Author: Jared Tobin <jared@jtobin.io>
Date: Mon, 20 Apr 2026 15:35:45 +0800
merge: type safety improvements - HTLCOutputType, RevokedOutput, Internal module
Diffstat:
10 files changed, 221 insertions(+), 80 deletions(-)
diff --git a/bench/Main.hs b/bench/Main.hs
@@ -66,10 +66,22 @@ instance NFData B5.OutputResolution where
rnf (B5.Revoke rk) = rnf rk
rnf _ = ()
+instance NFData B5.HTLCOutputType where
+ rnf (B5.HTLCOfferedOutput e) = rnf e
+ rnf (B5.HTLCReceivedOutput e) = rnf e
+
instance NFData B5.UnresolvedOutput where
rnf (B5.UnresolvedOutput op v t) =
rnf op `seq` rnf v `seq` rnf t
+instance NFData B5.RevokedOutputType where
+ rnf B5.RevokedToLocal = ()
+ rnf (B5.RevokedHTLC h) = rnf h
+
+instance NFData B5.RevokedOutput where
+ rnf (B5.RevokedOutput op v t) =
+ rnf op `seq` rnf v `seq` rnf t
+
instance NFData B5.SpendingTx where
rnf (B5.SpendingTx tx scr val sh) =
rnf tx `seq` rnf scr `seq` rnf val `seq` rnf sh
@@ -118,13 +130,13 @@ dummyFeerate = FeeratePerKw 253
dummyDelay :: ToSelfDelay
dummyDelay = ToSelfDelay 144
-mkRevokedOutputs :: Int -> NE.NonEmpty B5.UnresolvedOutput
+mkRevokedOutputs :: Int -> NE.NonEmpty B5.RevokedOutput
mkRevokedOutputs n =
- let uo i = B5.UnresolvedOutput
+ let ro i = B5.RevokedOutput
(OutPoint dummyTxId (fromIntegral i))
(Satoshi 10000)
- (B5.Revoke dummyRevPk)
- in uo 0 NE.:| [ uo i | i <- [1..n-1] ]
+ B5.RevokedToLocal
+ in ro 0 NE.:| [ ro i | i <- [1..n-1] ]
-- benchmarks ---------------------------------------------------------
diff --git a/bench/Weight.hs b/bench/Weight.hs
@@ -43,6 +43,9 @@ instance NFData FeeratePerKw where
instance NFData ToSelfDelay where
rnf (ToSelfDelay x) = rnf x
+instance NFData CltvExpiry where
+ rnf (CltvExpiry x) = rnf x
+
instance NFData SighashType
instance NFData B5.OutputResolution where
@@ -50,10 +53,22 @@ instance NFData B5.OutputResolution where
rnf (B5.Revoke rk) = rnf rk
rnf _ = ()
+instance NFData B5.HTLCOutputType where
+ rnf (B5.HTLCOfferedOutput e) = rnf e
+ rnf (B5.HTLCReceivedOutput e) = rnf e
+
instance NFData B5.UnresolvedOutput where
rnf (B5.UnresolvedOutput op v t) =
rnf op `seq` rnf v `seq` rnf t
+instance NFData B5.RevokedOutputType where
+ rnf B5.RevokedToLocal = ()
+ rnf (B5.RevokedHTLC h) = rnf h
+
+instance NFData B5.RevokedOutput where
+ rnf (B5.RevokedOutput op v t) =
+ rnf op `seq` rnf v `seq` rnf t
+
instance NFData B5.SpendingTx where
rnf (B5.SpendingTx tx scr val sh) =
rnf tx `seq` rnf scr `seq` rnf val `seq` rnf sh
@@ -102,13 +117,13 @@ dummyFeerate = FeeratePerKw 253
dummyDelay :: ToSelfDelay
dummyDelay = ToSelfDelay 144
-mkRevokedOutputs :: Int -> NE.NonEmpty B5.UnresolvedOutput
+mkRevokedOutputs :: Int -> NE.NonEmpty B5.RevokedOutput
mkRevokedOutputs n =
- let uo i = B5.UnresolvedOutput
+ let ro i = B5.RevokedOutput
(OutPoint dummyTxId (fromIntegral i))
(Satoshi 10000)
- (B5.Revoke dummyRevPk)
- in uo 0 NE.:| [ uo i | i <- [1..n-1] ]
+ B5.RevokedToLocal
+ in ro 0 NE.:| [ ro i | i <- [1..n-1] ]
-- weights ------------------------------------------------------------
diff --git a/flake.lock b/flake.lock
@@ -228,11 +228,11 @@
]
},
"locked": {
- "lastModified": 1776570879,
- "narHash": "sha256-XsgGBvYWL+sD7pDZoPPi4l39DE7GH7maNnhm8iUeB/E=",
+ "lastModified": 1776668614,
+ "narHash": "sha256-ZckuUOZHrSya8kn7aRizWIQtqTZMrhSoi2NX7BE2s90=",
"ref": "master",
- "rev": "20ea43188d781368e5e64c7c646285a6b0aaeb94",
- "revCount": 27,
+ "rev": "580036e8f5cb22d423a205abeb15fca33307267c",
+ "revCount": 29,
"type": "git",
"url": "git://git.ppad.tech/bolt1.git"
},
@@ -272,11 +272,11 @@
]
},
"locked": {
- "lastModified": 1776572200,
- "narHash": "sha256-d3+A3XTTvKl4McyXkeQT840kpRI+9mtcXWwJQLoEbT0=",
+ "lastModified": 1776669534,
+ "narHash": "sha256-clRaTQr9j/fWdK5mAZ0SO1x/8QEto/0ZxRcNcbJNyZ4=",
"ref": "master",
- "rev": "2113539a278a3b1ab8484a5b205d9388b758ddb6",
- "revCount": 39,
+ "rev": "b930e58309f1afb27a72cb026c40991cfd9687c3",
+ "revCount": 44,
"type": "git",
"url": "git://git.ppad.tech/bolt3.git"
},
diff --git a/lib/Lightning/Protocol/BOLT5.hs b/lib/Lightning/Protocol/BOLT5.hs
@@ -52,11 +52,19 @@ module Lightning.Protocol.BOLT5 (
, UnresolvedOutput(..)
, OutputResolution(..)
+ -- ** HTLC output type
+ , HTLCOutputType(..)
+ , htlcOutputType
+ , htlcOutputTypeWeight
+
-- ** Spending transactions
, SpendingTx(..)
-- ** Penalty batching
+ , RevokedOutput(..)
+ , RevokedOutputType(..)
, PenaltyContext(..)
+ , revoked_output_weight
-- * Weight constants (Appendix A)
, to_local_penalty_witness_weight
diff --git a/lib/Lightning/Protocol/BOLT5/Detect.hs b/lib/Lightning/Protocol/BOLT5/Detect.hs
@@ -251,10 +251,10 @@ classifyRevokedOutput !txid !revpk !idx !out =
Resolved -- Can be swept by anyone after 16 blocks
OutputRemoteAnchor ->
Resolved -- Our anchor
- otype@(OutputOfferedHTLC _) ->
- RevokeHTLC revpk otype
- otype@(OutputReceivedHTLC _) ->
- RevokeHTLC revpk otype
+ OutputOfferedHTLC expiry ->
+ RevokeHTLC revpk (HTLCOfferedOutput expiry)
+ OutputReceivedHTLC expiry ->
+ RevokeHTLC revpk (HTLCReceivedOutput expiry)
in UnresolvedOutput op val resolution
-- preimage extraction ------------------------------------------------
diff --git a/lib/Lightning/Protocol/BOLT5/Internal.hs b/lib/Lightning/Protocol/BOLT5/Internal.hs
@@ -0,0 +1,27 @@
+{-# OPTIONS_HADDOCK hide #-}
+
+-- |
+-- Module: Lightning.Protocol.BOLT5.Internal
+-- Copyright: (c) 2025 Jared Tobin
+-- License: MIT
+-- Maintainer: Jared Tobin <jared@ppad.tech>
+--
+-- Internal definitions for BOLT #5.
+--
+-- This module re-exports all constructors from the Types
+-- module, including those not exported by the public API.
+-- Use only in tests or trusted internal code.
+
+module Lightning.Protocol.BOLT5.Internal (
+ -- * All type constructors (for test use)
+ CloseType(..)
+ , UnresolvedOutput(..)
+ , OutputResolution(..)
+ , HTLCOutputType(..)
+ , SpendingTx(..)
+ , RevokedOutput(..)
+ , RevokedOutputType(..)
+ , PenaltyContext(..)
+ ) where
+
+import Lightning.Protocol.BOLT5.Types
diff --git a/lib/Lightning/Protocol/BOLT5/Spend.hs b/lib/Lightning/Protocol/BOLT5/Spend.hs
@@ -306,14 +306,14 @@ spend_revoked_to_local !op !value !revpk !delay
-- 'received_htlc_witness_revoke' from bolt3, depending on
-- the output type.
--
--- Returns 'Nothing' if the output type is not an HTLC, or
--- if the fee would exceed the output value.
+-- Returns 'Nothing' if the fee would exceed the output
+-- value.
spend_revoked_htlc
:: OutPoint
-- ^ Outpoint of the HTLC output.
-> Satoshi
-- ^ Value of the HTLC output.
- -> OutputType
+ -> HTLCOutputType
-- ^ Whether offered or received HTLC.
-> RevocationPubkey
-> CommitmentKeys
@@ -323,10 +323,10 @@ spend_revoked_htlc
-- ^ Destination scriptPubKey.
-> FeeratePerKw
-> Maybe SpendingTx
-spend_revoked_htlc !op !value !otype !revpk !keys
+spend_revoked_htlc !op !value !htype !revpk !keys
!features !ph !destScript !feerate =
- case otype of
- OutputOfferedHTLC _ ->
+ case htype of
+ HTLCOfferedOutput _ ->
let !witnessScript = offered_htlc_script
revpk
(ck_remote_htlc keys)
@@ -346,7 +346,7 @@ spend_revoked_htlc !op !value !otype !revpk !keys
destScript outputValue 0
in Just (SpendingTx tx witnessScript value
SIGHASH_ALL)
- OutputReceivedHTLC expiry ->
+ HTLCReceivedOutput expiry ->
let !witnessScript = received_htlc_script
revpk
(ck_remote_htlc keys)
@@ -367,7 +367,6 @@ spend_revoked_htlc !op !value !otype !revpk !keys
destScript outputValue 0
in Just (SpendingTx tx witnessScript value
SIGHASH_ALL)
- _ -> Nothing
-- | Spend a revoked second-stage HTLC output (HTLC-timeout or
-- HTLC-success output) using the revocation key.
@@ -413,8 +412,9 @@ spend_revoked_htlc_output !op !value !revpk !delay
-- be resolved in a single penalty transaction (within the
-- 400,000 weight limit). The caller signs each input with the
-- revocation privkey.
--- | Returns 'Nothing' if the total fee would exceed the
--- total input value.
+--
+-- Returns 'Nothing' if the total fee would exceed the total
+-- input value.
spend_revoked_batch :: PenaltyContext -> Maybe SpendingTx
spend_revoked_batch !ctx =
let !outs = pc_outputs ctx
@@ -455,22 +455,16 @@ spend_revoked_batch !ctx =
SIGHASH_ALL)
where
go !totalVal !totalWt [] = (totalVal, totalWt)
- go !totalVal !totalWt (uo:rest) =
- let !w = case uo_type uo of
- Revoke _ ->
- to_local_penalty_input_weight
- RevokeHTLC _ (OutputOfferedHTLC _) ->
- offered_htlc_penalty_input_weight
- RevokeHTLC _ (OutputReceivedHTLC _) ->
- accepted_htlc_penalty_input_weight
- _ -> 0
+ go !totalVal !totalWt (ro:rest) =
+ let !w = revoked_output_weight ro
!v = Satoshi
- (unSatoshi totalVal + unSatoshi (uo_value uo))
+ (unSatoshi totalVal
+ + unSatoshi (ro_value ro))
in go v (totalWt + w) rest
- mkPenaltyInput !uo =
+ mkPenaltyInput !ro =
TxIn
- { txin_prevout = uo_outpoint uo
+ { txin_prevout = ro_outpoint ro
, txin_script_sig = BS.empty
, txin_sequence = 0xFFFFFFFF
}
diff --git a/lib/Lightning/Protocol/BOLT5/Types.hs b/lib/Lightning/Protocol/BOLT5/Types.hs
@@ -18,11 +18,19 @@ module Lightning.Protocol.BOLT5.Types (
, UnresolvedOutput(..)
, OutputResolution(..)
+ -- * HTLC output type
+ , HTLCOutputType(..)
+ , htlcOutputType
+ , htlcOutputTypeWeight
+
-- * Spending transactions
, SpendingTx(..)
-- * Penalty batching
+ , RevokedOutput(..)
+ , RevokedOutputType(..)
, PenaltyContext(..)
+ , revoked_output_weight
-- * Weight constants (Appendix A)
, to_local_penalty_witness_weight
@@ -96,12 +104,50 @@ data OutputResolution
-- remote offer).
| Revoke !RevocationPubkey
-- ^ Spend revoked to_local with revocation key.
- | RevokeHTLC !RevocationPubkey !OutputType
+ | RevokeHTLC !RevocationPubkey !HTLCOutputType
-- ^ Spend revoked HTLC output with revocation key.
| AnchorSpend !FundingPubkey
-- ^ Spend anchor output.
deriving (Eq, Show, Generic)
+-- HTLC output type -------------------------------------------------
+
+-- | Type of HTLC output, restricted to only HTLC variants.
+--
+-- Unlike bolt3's 'OutputType' which includes all six output
+-- types, this ADT makes it impossible to confuse HTLC outputs
+-- with to_local, to_remote, or anchor outputs.
+data HTLCOutputType
+ = HTLCOfferedOutput {-# UNPACK #-} !CltvExpiry
+ -- ^ Offered HTLC output with CLTV expiry.
+ | HTLCReceivedOutput {-# UNPACK #-} !CltvExpiry
+ -- ^ Received HTLC output with CLTV expiry.
+ deriving (Eq, Show, Generic)
+
+-- | Extract an 'HTLCOutputType' from a bolt3 'OutputType'.
+--
+-- Returns 'Nothing' for non-HTLC output types.
+--
+-- >>> htlcOutputType (OutputOfferedHTLC (CltvExpiry 500))
+-- Just (HTLCOfferedOutput (CltvExpiry ...))
+-- >>> htlcOutputType OutputToLocal
+-- Nothing
+htlcOutputType :: OutputType -> Maybe HTLCOutputType
+htlcOutputType (OutputOfferedHTLC e) =
+ Just (HTLCOfferedOutput e)
+htlcOutputType (OutputReceivedHTLC e) =
+ Just (HTLCReceivedOutput e)
+htlcOutputType _ = Nothing
+{-# INLINE htlcOutputType #-}
+
+-- | Penalty input weight for an HTLC output type.
+htlcOutputTypeWeight :: HTLCOutputType -> Word64
+htlcOutputTypeWeight (HTLCOfferedOutput _) =
+ offered_htlc_penalty_input_weight
+htlcOutputTypeWeight (HTLCReceivedOutput _) =
+ accepted_htlc_penalty_input_weight
+{-# INLINE htlcOutputTypeWeight #-}
+
-- spending transactions ----------------------------------------------
-- | Unsigned spending transaction, ready for caller to sign.
@@ -121,9 +167,38 @@ data SpendingTx = SpendingTx
-- penalty batching ---------------------------------------------------
+-- | A revoked output that can be swept with a revocation key.
+--
+-- This type restricts penalty transaction inputs to only
+-- valid revocation targets: to_local outputs and HTLC outputs.
+-- Other output types (to_remote, anchors) cannot appear in
+-- penalty transactions.
+data RevokedOutput = RevokedOutput
+ { ro_outpoint :: !OutPoint
+ , ro_value :: {-# UNPACK #-} !Satoshi
+ , ro_type :: !RevokedOutputType
+ } deriving (Eq, Show, Generic)
+
+-- | What kind of revoked output is being swept.
+data RevokedOutputType
+ = RevokedToLocal
+ -- ^ Revoked to_local output.
+ | RevokedHTLC !HTLCOutputType
+ -- ^ Revoked HTLC output.
+ deriving (Eq, Show, Generic)
+
+-- | Penalty input weight for a revoked output.
+revoked_output_weight :: RevokedOutput -> Word64
+revoked_output_weight !ro = case ro_type ro of
+ RevokedToLocal ->
+ to_local_penalty_input_weight
+ RevokedHTLC !htype ->
+ htlcOutputTypeWeight htype
+{-# INLINE revoked_output_weight #-}
+
-- | Context for constructing batched penalty transactions.
data PenaltyContext = PenaltyContext
- { pc_outputs :: !(NonEmpty UnresolvedOutput)
+ { pc_outputs :: !(NonEmpty RevokedOutput)
-- ^ Revoked outputs to sweep (must be non-empty).
, pc_revocation_key :: !RevocationPubkey
-- ^ Revocation pubkey for all outputs.
diff --git a/ppad-bolt5.cabal b/ppad-bolt5.cabal
@@ -26,6 +26,7 @@ library
exposed-modules:
Lightning.Protocol.BOLT5
Lightning.Protocol.BOLT5.Detect
+ Lightning.Protocol.BOLT5.Internal
Lightning.Protocol.BOLT5.Spend
Lightning.Protocol.BOLT5.Types
build-depends:
diff --git a/test/Main.hs b/test/Main.hs
@@ -240,8 +240,8 @@ detect_tests = testGroup "Detect" [
preimage = dummyPreimage
wit = Witness [sig, preimage]
case B5.extract_preimage_offered wit of
- Just (PaymentPreimage bs) ->
- bs @?= preimage
+ Just pp ->
+ unPaymentPreimage pp @?= preimage
Nothing ->
assertFailure "expected preimage"
@@ -263,8 +263,8 @@ detect_tests = testGroup "Detect" [
preimage = dummyPreimage
wit = Witness [zero, remoteSig, localSig, preimage]
case B5.extract_preimage_htlc_success wit of
- Just (PaymentPreimage bs) ->
- bs @?= preimage
+ Just pp ->
+ unPaymentPreimage pp @?= preimage
Nothing ->
assertFailure "expected preimage"
@@ -415,13 +415,13 @@ classify_tests = testGroup "Classify" [
@?= B5.Revoke dummyRevocationPubkey
B5.uo_type o2 @?= B5.Resolved
case B5.uo_type o3 of
- B5.RevokeHTLC _ (OutputOfferedHTLC _) ->
+ B5.RevokeHTLC _ (B5.HTLCOfferedOutput _) ->
pure ()
other -> assertFailure $
"expected RevokeHTLC offered, got "
<> show other
case B5.uo_type o4 of
- B5.RevokeHTLC _ (OutputReceivedHTLC _) ->
+ B5.RevokeHTLC _ (B5.HTLCReceivedOutput _) ->
pure ()
other -> assertFailure $
"expected RevokeHTLC received, got "
@@ -600,12 +600,12 @@ spend_tests = testGroup "Spend" [
, testCase "spend_revoked_batch total value" $ do
let op1 = OutPoint dummyTxId 0
op2 = OutPoint dummyTxId 1
- uo1 = B5.UnresolvedOutput op1 (Satoshi 50000)
- (B5.Revoke dummyRevocationPubkey)
- uo2 = B5.UnresolvedOutput op2 (Satoshi 30000)
- (B5.Revoke dummyRevocationPubkey)
+ ro1 = B5.RevokedOutput op1 (Satoshi 50000)
+ B5.RevokedToLocal
+ ro2 = B5.RevokedOutput op2 (Satoshi 30000)
+ B5.RevokedToLocal
pctx = B5.PenaltyContext
- { B5.pc_outputs = uo1 :| [uo2]
+ { B5.pc_outputs = ro1 :| [ro2]
, B5.pc_revocation_key =
dummyRevocationPubkey
, B5.pc_destination = dummyDestScript
@@ -623,11 +623,11 @@ spend_tests = testGroup "Spend" [
length (tx_inputs tx) @?= 2
, testCase "spend_revoked_batch single element" $ do
- let uo = B5.UnresolvedOutput
+ let ro = B5.RevokedOutput
dummyOutPoint (Satoshi 50000)
- (B5.Revoke dummyRevocationPubkey)
+ B5.RevokedToLocal
pctx = B5.PenaltyContext
- { B5.pc_outputs = uo :| []
+ { B5.pc_outputs = ro :| []
, B5.pc_revocation_key =
dummyRevocationPubkey
, B5.pc_destination = dummyDestScript
@@ -643,19 +643,21 @@ spend_tests = testGroup "Spend" [
let op1 = OutPoint dummyTxId 0
op2 = OutPoint dummyTxId 1
op3 = OutPoint dummyTxId 2
- uo1 = B5.UnresolvedOutput op1
+ ro1 = B5.RevokedOutput op1
(Satoshi 50000)
- (B5.Revoke dummyRevocationPubkey)
- uo2 = B5.UnresolvedOutput op2
+ B5.RevokedToLocal
+ ro2 = B5.RevokedOutput op2
(Satoshi 10000)
- (B5.RevokeHTLC dummyRevocationPubkey
- (OutputOfferedHTLC (CltvExpiry 500000)))
- uo3 = B5.UnresolvedOutput op3
+ (B5.RevokedHTLC
+ (B5.HTLCOfferedOutput
+ (CltvExpiry 500000)))
+ ro3 = B5.RevokedOutput op3
(Satoshi 10000)
- (B5.RevokeHTLC dummyRevocationPubkey
- (OutputReceivedHTLC (CltvExpiry 500000)))
+ (B5.RevokedHTLC
+ (B5.HTLCReceivedOutput
+ (CltvExpiry 500000)))
pctx = B5.PenaltyContext
- { B5.pc_outputs = uo1 :| [uo2, uo3]
+ { B5.pc_outputs = ro1 :| [ro2, ro3]
, B5.pc_revocation_key =
dummyRevocationPubkey
, B5.pc_destination = dummyDestScript
@@ -675,11 +677,11 @@ spend_tests = testGroup "Spend" [
@?= Nothing
, testCase "spend_revoked_batch fee underflow" $ do
- let uo = B5.UnresolvedOutput
+ let ro = B5.RevokedOutput
dummyOutPoint (Satoshi 1)
- (B5.Revoke dummyRevocationPubkey)
+ B5.RevokedToLocal
pctx = B5.PenaltyContext
- { B5.pc_outputs = uo :| []
+ { B5.pc_outputs = ro :| []
, B5.pc_revocation_key =
dummyRevocationPubkey
, B5.pc_destination = dummyDestScript
@@ -810,7 +812,7 @@ revoked_htlc_spend_tests = testGroup "Revoked HTLC Spend" [
testCase "spend_revoked_htlc - offered" $ do
case B5.spend_revoked_htlc
dummyOutPoint (Satoshi 50000)
- (OutputOfferedHTLC (CltvExpiry 500000))
+ (B5.HTLCOfferedOutput (CltvExpiry 500000))
dummyRevocationPubkey dummyKeys
dummyFeatures dummyPaymentHash
dummyDestScript dummyFeerate of
@@ -825,7 +827,8 @@ revoked_htlc_spend_tests = testGroup "Revoked HTLC Spend" [
, testCase "spend_revoked_htlc - received" $ do
case B5.spend_revoked_htlc
dummyOutPoint (Satoshi 50000)
- (OutputReceivedHTLC (CltvExpiry 500000))
+ (B5.HTLCReceivedOutput
+ (CltvExpiry 500000))
dummyRevocationPubkey dummyKeys
dummyFeatures dummyPaymentHash
dummyDestScript dummyFeerate of
@@ -838,14 +841,20 @@ revoked_htlc_spend_tests = testGroup "Revoked HTLC Spend" [
assertBool "output < input"
(outVal < 50000)
- , testCase "spend_revoked_htlc - invalid type" $ do
- B5.spend_revoked_htlc
- dummyOutPoint (Satoshi 50000)
- OutputToLocal
- dummyRevocationPubkey dummyKeys
- dummyFeatures dummyPaymentHash
- dummyDestScript dummyFeerate
- @?= Nothing
+ , testCase "htlcOutputType - valid HTLC types" $ do
+ B5.htlcOutputType
+ (OutputOfferedHTLC (CltvExpiry 500))
+ @?= Just (B5.HTLCOfferedOutput (CltvExpiry 500))
+ B5.htlcOutputType
+ (OutputReceivedHTLC (CltvExpiry 600))
+ @?= Just (B5.HTLCReceivedOutput
+ (CltvExpiry 600))
+
+ , testCase "htlcOutputType - non-HTLC types" $ do
+ B5.htlcOutputType OutputToLocal @?= Nothing
+ B5.htlcOutputType OutputToRemote @?= Nothing
+ B5.htlcOutputType OutputLocalAnchor @?= Nothing
+ B5.htlcOutputType OutputRemoteAnchor @?= Nothing
, testCase "spend_revoked_htlc_output structure" $ do
stx <- assertJust "spend_revoked_htlc_output" $