{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}

-- |
-- Module: Lightning.Protocol.BOLT5.Types
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Types for BOLT #5 on-chain transaction handling.

module Lightning.Protocol.BOLT5.Types (
    -- * Close identification
    CloseType(..)

    -- * Output classification
  , UnresolvedOutput(..)
  , OutputResolution(..)

    -- * Spending transactions
  , SpendingTx(..)

    -- * Penalty batching
  , PenaltyContext(..)

    -- * Weight constants (Appendix A)
  , to_local_penalty_witness_weight
  , offered_htlc_penalty_witness_weight
  , accepted_htlc_penalty_witness_weight
  , to_local_penalty_input_weight
  , offered_htlc_penalty_input_weight
  , accepted_htlc_penalty_input_weight
  , to_remote_input_weight
  , penalty_tx_base_weight
  , max_standard_weight

    -- * Fee calculation
  , spending_fee
  ) where

import Bitcoin.Prim.Tx (Tx(..))
import Bitcoin.Prim.Tx.Sighash (SighashType(..))
import Data.List.NonEmpty (NonEmpty)
import Data.Word (Word64)
import GHC.Generics (Generic)
import Lightning.Protocol.BOLT3.Types
import Lightning.Protocol.BOLT3.Tx (
    CommitmentKeys(..)
  , OutputType(..)
  )

-- close identification -----------------------------------------------

-- | What kind of close was detected on chain.
data CloseType
  = MutualClose
    -- ^ Cooperative closure agreed by both parties.
  | LocalCommitClose
    -- ^ Our commitment transaction was broadcast.
  | RemoteCommitClose
    -- ^ The remote party's commitment transaction was broadcast.
  | RevokedCommitClose
    -- ^ A revoked (outdated) commitment transaction was broadcast.
  deriving (CloseType -> CloseType -> Bool
(CloseType -> CloseType -> Bool)
-> (CloseType -> CloseType -> Bool) -> Eq CloseType
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CloseType -> CloseType -> Bool
== :: CloseType -> CloseType -> Bool
$c/= :: CloseType -> CloseType -> Bool
/= :: CloseType -> CloseType -> Bool
Eq, Int -> CloseType -> ShowS
[CloseType] -> ShowS
CloseType -> String
(Int -> CloseType -> ShowS)
-> (CloseType -> String)
-> ([CloseType] -> ShowS)
-> Show CloseType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CloseType -> ShowS
showsPrec :: Int -> CloseType -> ShowS
$cshow :: CloseType -> String
show :: CloseType -> String
$cshowList :: [CloseType] -> ShowS
showList :: [CloseType] -> ShowS
Show, (forall x. CloseType -> Rep CloseType x)
-> (forall x. Rep CloseType x -> CloseType) -> Generic CloseType
forall x. Rep CloseType x -> CloseType
forall x. CloseType -> Rep CloseType x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. CloseType -> Rep CloseType x
from :: forall x. CloseType -> Rep CloseType x
$cto :: forall x. Rep CloseType x -> CloseType
to :: forall x. Rep CloseType x -> CloseType
Generic)

-- output classification ----------------------------------------------

-- | An unresolved commitment transaction output.
data UnresolvedOutput = UnresolvedOutput
  { UnresolvedOutput -> OutPoint
uo_outpoint :: !OutPoint
  , UnresolvedOutput -> Satoshi
uo_value    :: {-# UNPACK #-} !Satoshi
  , UnresolvedOutput -> OutputResolution
uo_type     :: !OutputResolution
  } deriving (UnresolvedOutput -> UnresolvedOutput -> Bool
(UnresolvedOutput -> UnresolvedOutput -> Bool)
-> (UnresolvedOutput -> UnresolvedOutput -> Bool)
-> Eq UnresolvedOutput
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: UnresolvedOutput -> UnresolvedOutput -> Bool
== :: UnresolvedOutput -> UnresolvedOutput -> Bool
$c/= :: UnresolvedOutput -> UnresolvedOutput -> Bool
/= :: UnresolvedOutput -> UnresolvedOutput -> Bool
Eq, Int -> UnresolvedOutput -> ShowS
[UnresolvedOutput] -> ShowS
UnresolvedOutput -> String
(Int -> UnresolvedOutput -> ShowS)
-> (UnresolvedOutput -> String)
-> ([UnresolvedOutput] -> ShowS)
-> Show UnresolvedOutput
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> UnresolvedOutput -> ShowS
showsPrec :: Int -> UnresolvedOutput -> ShowS
$cshow :: UnresolvedOutput -> String
show :: UnresolvedOutput -> String
$cshowList :: [UnresolvedOutput] -> ShowS
showList :: [UnresolvedOutput] -> ShowS
Show, (forall x. UnresolvedOutput -> Rep UnresolvedOutput x)
-> (forall x. Rep UnresolvedOutput x -> UnresolvedOutput)
-> Generic UnresolvedOutput
forall x. Rep UnresolvedOutput x -> UnresolvedOutput
forall x. UnresolvedOutput -> Rep UnresolvedOutput x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. UnresolvedOutput -> Rep UnresolvedOutput x
from :: forall x. UnresolvedOutput -> Rep UnresolvedOutput x
$cto :: forall x. Rep UnresolvedOutput x -> UnresolvedOutput
to :: forall x. Rep UnresolvedOutput x -> UnresolvedOutput
Generic)

-- | How to resolve an output, per BOLT #5 rules.
data OutputResolution
  = Resolved
    -- ^ Already resolved (e.g. to_remote on local commit).
  | SpendToLocal
      !ToSelfDelay !RevocationPubkey !LocalDelayedPubkey
    -- ^ Spend to_local after CSV delay.
  | SpendHTLCTimeout
      !HTLC !CommitmentKeys !ChannelFeatures
    -- ^ Spend via HTLC-timeout second-stage tx (local commit,
    --   local offer).
  | SpendHTLCSuccess
      !HTLC !CommitmentKeys !ChannelFeatures
    -- ^ Spend via HTLC-success second-stage tx (local commit,
    --   remote offer).
  | SpendHTLCTimeoutDirect !HTLC
    -- ^ Spend HTLC directly after timeout (remote commit,
    --   local offer).
  | SpendHTLCPreimageDirect !HTLC
    -- ^ Spend HTLC directly with preimage (remote commit,
    --   remote offer).
  | Revoke !RevocationPubkey
    -- ^ Spend revoked to_local with revocation key.
  | RevokeHTLC !RevocationPubkey !OutputType
    -- ^ Spend revoked HTLC output with revocation key.
  | AnchorSpend !FundingPubkey
    -- ^ Spend anchor output.
  deriving (OutputResolution -> OutputResolution -> Bool
(OutputResolution -> OutputResolution -> Bool)
-> (OutputResolution -> OutputResolution -> Bool)
-> Eq OutputResolution
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: OutputResolution -> OutputResolution -> Bool
== :: OutputResolution -> OutputResolution -> Bool
$c/= :: OutputResolution -> OutputResolution -> Bool
/= :: OutputResolution -> OutputResolution -> Bool
Eq, Int -> OutputResolution -> ShowS
[OutputResolution] -> ShowS
OutputResolution -> String
(Int -> OutputResolution -> ShowS)
-> (OutputResolution -> String)
-> ([OutputResolution] -> ShowS)
-> Show OutputResolution
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> OutputResolution -> ShowS
showsPrec :: Int -> OutputResolution -> ShowS
$cshow :: OutputResolution -> String
show :: OutputResolution -> String
$cshowList :: [OutputResolution] -> ShowS
showList :: [OutputResolution] -> ShowS
Show, (forall x. OutputResolution -> Rep OutputResolution x)
-> (forall x. Rep OutputResolution x -> OutputResolution)
-> Generic OutputResolution
forall x. Rep OutputResolution x -> OutputResolution
forall x. OutputResolution -> Rep OutputResolution x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. OutputResolution -> Rep OutputResolution x
from :: forall x. OutputResolution -> Rep OutputResolution x
$cto :: forall x. Rep OutputResolution x -> OutputResolution
to :: forall x. Rep OutputResolution x -> OutputResolution
Generic)

-- spending transactions ----------------------------------------------

-- | Unsigned spending transaction, ready for caller to sign.
--
-- The caller uses bolt3 witness constructors to assemble the
-- final witness after signing.
data SpendingTx = SpendingTx
  { SpendingTx -> Tx
stx_tx           :: !Tx
    -- ^ The unsigned transaction.
  , SpendingTx -> Script
stx_input_script :: !Script
    -- ^ Witness script for the input being spent.
  , SpendingTx -> Satoshi
stx_input_value  :: {-# UNPACK #-} !Satoshi
    -- ^ Value of the input being spent (for sighash).
  , SpendingTx -> SighashType
stx_sighash_type :: !SighashType
    -- ^ Sighash type to use when signing.
  } deriving (SpendingTx -> SpendingTx -> Bool
(SpendingTx -> SpendingTx -> Bool)
-> (SpendingTx -> SpendingTx -> Bool) -> Eq SpendingTx
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SpendingTx -> SpendingTx -> Bool
== :: SpendingTx -> SpendingTx -> Bool
$c/= :: SpendingTx -> SpendingTx -> Bool
/= :: SpendingTx -> SpendingTx -> Bool
Eq, Int -> SpendingTx -> ShowS
[SpendingTx] -> ShowS
SpendingTx -> String
(Int -> SpendingTx -> ShowS)
-> (SpendingTx -> String)
-> ([SpendingTx] -> ShowS)
-> Show SpendingTx
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SpendingTx -> ShowS
showsPrec :: Int -> SpendingTx -> ShowS
$cshow :: SpendingTx -> String
show :: SpendingTx -> String
$cshowList :: [SpendingTx] -> ShowS
showList :: [SpendingTx] -> ShowS
Show, (forall x. SpendingTx -> Rep SpendingTx x)
-> (forall x. Rep SpendingTx x -> SpendingTx) -> Generic SpendingTx
forall x. Rep SpendingTx x -> SpendingTx
forall x. SpendingTx -> Rep SpendingTx x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. SpendingTx -> Rep SpendingTx x
from :: forall x. SpendingTx -> Rep SpendingTx x
$cto :: forall x. Rep SpendingTx x -> SpendingTx
to :: forall x. Rep SpendingTx x -> SpendingTx
Generic)

-- penalty batching ---------------------------------------------------

-- | Context for constructing batched penalty transactions.
data PenaltyContext = PenaltyContext
  { PenaltyContext -> NonEmpty UnresolvedOutput
pc_outputs        :: !(NonEmpty UnresolvedOutput)
    -- ^ Revoked outputs to sweep (must be non-empty).
  , PenaltyContext -> RevocationPubkey
pc_revocation_key :: !RevocationPubkey
    -- ^ Revocation pubkey for all outputs.
  , PenaltyContext -> Script
pc_destination    :: !Script
    -- ^ Destination scriptPubKey.
  , PenaltyContext -> FeeratePerKw
pc_feerate        :: !FeeratePerKw
    -- ^ Fee rate for the penalty transaction.
  } deriving (PenaltyContext -> PenaltyContext -> Bool
(PenaltyContext -> PenaltyContext -> Bool)
-> (PenaltyContext -> PenaltyContext -> Bool) -> Eq PenaltyContext
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PenaltyContext -> PenaltyContext -> Bool
== :: PenaltyContext -> PenaltyContext -> Bool
$c/= :: PenaltyContext -> PenaltyContext -> Bool
/= :: PenaltyContext -> PenaltyContext -> Bool
Eq, Int -> PenaltyContext -> ShowS
[PenaltyContext] -> ShowS
PenaltyContext -> String
(Int -> PenaltyContext -> ShowS)
-> (PenaltyContext -> String)
-> ([PenaltyContext] -> ShowS)
-> Show PenaltyContext
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PenaltyContext -> ShowS
showsPrec :: Int -> PenaltyContext -> ShowS
$cshow :: PenaltyContext -> String
show :: PenaltyContext -> String
$cshowList :: [PenaltyContext] -> ShowS
showList :: [PenaltyContext] -> ShowS
Show, (forall x. PenaltyContext -> Rep PenaltyContext x)
-> (forall x. Rep PenaltyContext x -> PenaltyContext)
-> Generic PenaltyContext
forall x. Rep PenaltyContext x -> PenaltyContext
forall x. PenaltyContext -> Rep PenaltyContext x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. PenaltyContext -> Rep PenaltyContext x
from :: forall x. PenaltyContext -> Rep PenaltyContext x
$cto :: forall x. Rep PenaltyContext x -> PenaltyContext
to :: forall x. Rep PenaltyContext x -> PenaltyContext
Generic)

-- weight constants (BOLT #5 Appendix A) ------------------------------

-- | Expected weight of the to_local penalty transaction witness
--   (160 bytes).
to_local_penalty_witness_weight :: Word64
to_local_penalty_witness_weight :: Word64
to_local_penalty_witness_weight = Word64
160

-- | Expected weight of the offered_htlc penalty transaction
--   witness (243 bytes).
offered_htlc_penalty_witness_weight :: Word64
offered_htlc_penalty_witness_weight :: Word64
offered_htlc_penalty_witness_weight = Word64
243

-- | Expected weight of the accepted_htlc penalty transaction
--   witness (249 bytes).
accepted_htlc_penalty_witness_weight :: Word64
accepted_htlc_penalty_witness_weight :: Word64
accepted_htlc_penalty_witness_weight = Word64
249

-- | Weight of a to_local penalty input (164 + 160 = 324 bytes).
to_local_penalty_input_weight :: Word64
to_local_penalty_input_weight :: Word64
to_local_penalty_input_weight = Word64
324

-- | Weight of an offered_htlc penalty input
--   (164 + 243 = 407 bytes).
offered_htlc_penalty_input_weight :: Word64
offered_htlc_penalty_input_weight :: Word64
offered_htlc_penalty_input_weight = Word64
407

-- | Weight of an accepted_htlc penalty input
--   (164 + 249 = 413 bytes).
accepted_htlc_penalty_input_weight :: Word64
accepted_htlc_penalty_input_weight :: Word64
accepted_htlc_penalty_input_weight = Word64
413

-- | Weight of a to_remote P2WPKH input
--   (108 + 164 = 272 bytes).
to_remote_input_weight :: Word64
to_remote_input_weight :: Word64
to_remote_input_weight = Word64
272

-- | Base weight of a penalty transaction (4*53 + 2 = 214 bytes).
--
-- Non-witness: version(4) + input_count(1) + output_count(1) +
-- value(8) + script_len(1) + p2wsh_script(34) + locktime(4) = 53
-- Witness header: 2 bytes.
penalty_tx_base_weight :: Word64
penalty_tx_base_weight :: Word64
penalty_tx_base_weight = Word64
214

-- | Maximum standard transaction weight (400,000 bytes).
max_standard_weight :: Word64
max_standard_weight :: Word64
max_standard_weight = Word64
400000

-- fee calculation ----------------------------------------------------

-- | Calculate the fee for a spending transaction given its weight.
--
-- @fee = feerate_per_kw * weight / 1000@
spending_fee :: FeeratePerKw -> Word64 -> Satoshi
spending_fee :: FeeratePerKw -> Word64 -> Satoshi
spending_fee (FeeratePerKw !Word32
rate) !Word64
weight =
  Word64 -> Satoshi
Satoshi ((Word32 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
rate Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
* Word64
weight) Word64 -> Word64 -> Word64
forall a. Integral a => a -> a -> a
`div` Word64
1000)
{-# INLINE spending_fee #-}