ppad-bolt3-0.0.1: bench/Main.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.DeepSeq (NFData(..))
import Criterion.Main
import Data.Word (Word64)
import qualified Data.ByteString as BS
import Lightning.Protocol.BOLT3
-- NFData instances for benchmarking
-- Existing instances
instance NFData Satoshi where
rnf (Satoshi x) = rnf x
instance NFData MilliSatoshi where
rnf (MilliSatoshi x) = rnf x
instance NFData Pubkey where
rnf (Pubkey x) = rnf x
instance NFData Point where
rnf (Point x) = rnf x
instance NFData PerCommitmentPoint where
rnf (PerCommitmentPoint x) = rnf x
instance NFData RevocationPubkey where
rnf (RevocationPubkey x) = rnf x
instance NFData RevocationBasepoint where
rnf (RevocationBasepoint x) = rnf x
instance NFData ChannelFeatures where
rnf (ChannelFeatures x) = rnf x
instance NFData FeeratePerKw where
rnf (FeeratePerKw x) = rnf x
instance NFData DustLimit where
rnf (DustLimit x) = rnf x
instance NFData PaymentHash where
rnf (PaymentHash x) = rnf x
instance NFData CltvExpiry where
rnf (CltvExpiry x) = rnf x
instance NFData HTLCDirection where
rnf HTLCOffered = ()
rnf HTLCReceived = ()
instance NFData HTLC where
rnf (HTLC d a h c) = rnf d `seq` rnf a `seq` rnf h `seq` rnf c
-- Transaction types
instance NFData CommitmentTx where
rnf (CommitmentTx v l i s o f) =
rnf v `seq` rnf l `seq` rnf i `seq` rnf s `seq` rnf o `seq` rnf f
instance NFData HTLCTx where
rnf (HTLCTx v l i s ov os) =
rnf v `seq` rnf l `seq` rnf i `seq` rnf s `seq` rnf ov `seq` rnf os
instance NFData ClosingTx where
rnf (ClosingTx v l i s o f) =
rnf v `seq` rnf l `seq` rnf i `seq` rnf s `seq` rnf o `seq` rnf f
-- Output types
instance NFData TxOutput where
rnf (TxOutput v s t) = rnf v `seq` rnf s `seq` rnf t
instance NFData OutputType where
rnf OutputToLocal = ()
rnf OutputToRemote = ()
rnf OutputLocalAnchor = ()
rnf OutputRemoteAnchor = ()
rnf (OutputOfferedHTLC e) = rnf e
rnf (OutputReceivedHTLC e) = rnf e
-- Primitives
instance NFData Script where
rnf (Script bs) = rnf bs
instance NFData Witness where
rnf (Witness items) = rnf items
instance NFData Outpoint where
rnf (Outpoint t i) = rnf t `seq` rnf i
instance NFData Sequence where
rnf (Sequence x) = rnf x
instance NFData Locktime where
rnf (Locktime x) = rnf x
instance NFData TxId where
rnf (TxId bs) = rnf bs
instance NFData ToSelfDelay where
rnf (ToSelfDelay x) = rnf x
instance NFData CommitmentNumber where
rnf (CommitmentNumber x) = rnf x
-- Parsing types
instance NFData RawTx where
rnf (RawTx v i o w l) =
rnf v `seq` rnf i `seq` rnf o `seq` rnf w `seq` rnf l
instance NFData RawInput where
rnf (RawInput o scr sq) = rnf o `seq` rnf scr `seq` rnf sq
instance NFData RawOutput where
rnf (RawOutput v s) = rnf v `seq` rnf s
-- Context types
instance NFData CommitmentContext where
rnf ctx = rnf (cc_funding_outpoint ctx) `seq`
rnf (cc_commitment_number ctx) `seq`
rnf (cc_htlcs ctx) `seq`
rnf (cc_keys ctx)
instance NFData CommitmentKeys where
rnf keys = rnf (ck_revocation_pubkey keys) `seq`
rnf (ck_local_delayed keys) `seq`
rnf (ck_local_htlc keys) `seq`
rnf (ck_remote_htlc keys)
instance NFData HTLCContext where
rnf ctx = rnf (hc_commitment_txid ctx) `seq`
rnf (hc_htlc ctx)
instance NFData ClosingContext where
rnf ctx = rnf (clc_funding_outpoint ctx) `seq`
rnf (clc_local_amount ctx) `seq`
rnf (clc_remote_amount ctx)
-- Key types
instance NFData LocalDelayedPubkey where
rnf (LocalDelayedPubkey p) = rnf p
instance NFData RemoteDelayedPubkey where
rnf (RemoteDelayedPubkey p) = rnf p
instance NFData LocalHtlcPubkey where
rnf (LocalHtlcPubkey p) = rnf p
instance NFData RemoteHtlcPubkey where
rnf (RemoteHtlcPubkey p) = rnf p
instance NFData LocalPubkey where
rnf (LocalPubkey p) = rnf p
instance NFData RemotePubkey where
rnf (RemotePubkey p) = rnf p
instance NFData PaymentBasepoint where
rnf (PaymentBasepoint p) = rnf p
instance NFData DelayedPaymentBasepoint where
rnf (DelayedPaymentBasepoint p) = rnf p
instance NFData HtlcBasepoint where
rnf (HtlcBasepoint p) = rnf p
instance NFData FundingPubkey where
rnf (FundingPubkey p) = rnf p
instance NFData PerCommitmentSecret where
rnf (PerCommitmentSecret bs) = rnf bs
-- Secret storage (SecretStore is a newtype over list)
instance NFData SecretStore where
rnf store = rnf (derive_old_secret 0 store)
-- Validation errors
instance NFData ValidationError where
rnf (InvalidVersion a b) = rnf a `seq` rnf b
rnf (InvalidLocktime a) = rnf a
rnf (InvalidSequence a) = rnf a
rnf InvalidOutputOrdering = ()
rnf (DustLimitViolation a b c) = rnf a `seq` rnf b `seq` rnf c
rnf MissingAnchorOutput = ()
rnf (InvalidAnchorValue a) = rnf a
rnf (InvalidFee a b) = rnf a `seq` rnf b
rnf (InvalidHTLCLocktime a b) = rnf a `seq` rnf b
rnf (InvalidHTLCSequence a b) = rnf a `seq` rnf b
rnf NoOutputs = ()
rnf (TooManyOutputs a) = rnf a
-- Decode errors
instance NFData DecodeError where
rnf (InsufficientBytes a b) = rnf a `seq` rnf b
rnf (InvalidMarker a) = rnf a
rnf (InvalidFlag a) = rnf a
rnf InvalidVarint = ()
rnf EmptyInput = ()
main :: IO ()
main = defaultMain [
bgroup "key derivation" [
bench "derive_pubkey" $
whnf (derive_pubkey basepoint) perCommitmentPoint
, bench "derive_revocationpubkey" $
whnf (derive_revocationpubkey revocationBasepoint) perCommitmentPoint
]
, bgroup "secret generation" [
bench "generate_from_seed (final node)" $
whnf (generate_from_seed seed) 281474976710655
, bench "generate_from_seed (first node)" $
whnf (generate_from_seed seed) 0
]
, bgroup "fee calculation" [
bench "commitment_fee (no anchors, 0 htlcs)" $
whnf (commitment_fee feerate noAnchors) 0
, bench "commitment_fee (no anchors, 10 htlcs)" $
whnf (commitment_fee feerate noAnchors) 10
, bench "commitment_fee (anchors, 10 htlcs)" $
whnf (commitment_fee feerate withAnchors) 10
, bench "htlc_timeout_fee" $
whnf (htlc_timeout_fee feerate) noAnchors
, bench "htlc_success_fee" $
whnf (htlc_success_fee feerate) noAnchors
]
, bgroup "trimming" [
bench "is_trimmed (offered, not trimmed)" $
whnf (is_trimmed dust feerate noAnchors) htlcNotTrimmed
, bench "is_trimmed (offered, trimmed)" $
whnf (is_trimmed dust feerate noAnchors) htlcTrimmed
, bench "htlc_trim_threshold (offered)" $
whnf (htlc_trim_threshold dust feerate noAnchors) HTLCOffered
]
, bgroup "tx building" [
bench "build_commitment_tx (0 htlcs, no anchors)" $
whnf build_commitment_tx (mkCommitmentContext htlcs0 noAnchors)
, bench "build_commitment_tx (10 htlcs, no anchors)" $
whnf build_commitment_tx (mkCommitmentContext htlcs10 noAnchors)
, bench "build_commitment_tx (100 htlcs, no anchors)" $
whnf build_commitment_tx (mkCommitmentContext htlcs100 noAnchors)
, bench "build_commitment_tx (10 htlcs, anchors)" $
whnf build_commitment_tx (mkCommitmentContext htlcs10 withAnchors)
, bench "build_htlc_timeout_tx" $
whnf build_htlc_timeout_tx sampleHtlcContext
, bench "build_htlc_success_tx" $
whnf build_htlc_success_tx sampleHtlcContext
, bench "build_closing_tx" $
whnf build_closing_tx sampleClosingContext
]
, bgroup "script generation" [
bench "funding_script" $
whnf (funding_script (FundingPubkey samplePubkey1))
(FundingPubkey samplePubkey2)
, bench "to_local_script" $
whnf (to_local_script (RevocationPubkey samplePubkey1)
(ToSelfDelay 144))
(LocalDelayedPubkey samplePubkey2)
, bench "to_remote_script (no anchors)" $
whnf (to_remote_script (RemotePubkey samplePubkey1)) noAnchors
, bench "to_remote_script (anchors)" $
whnf (to_remote_script (RemotePubkey samplePubkey1)) withAnchors
, bench "anchor_script" $
whnf anchor_script (FundingPubkey samplePubkey1)
, bench "offered_htlc_script" $
whnf (offered_htlc_script (RevocationPubkey samplePubkey1)
(RemoteHtlcPubkey samplePubkey2)
(LocalHtlcPubkey samplePubkey3)
(PaymentHash $ BS.replicate 32 0))
noAnchors
, bench "received_htlc_script" $
whnf (received_htlc_script (RevocationPubkey samplePubkey1)
(RemoteHtlcPubkey samplePubkey2)
(LocalHtlcPubkey samplePubkey3)
(PaymentHash $ BS.replicate 32 0)
(CltvExpiry 500000))
noAnchors
]
, bgroup "serialization" [
env (pure $ build_commitment_tx $ mkCommitmentContext htlcs0 noAnchors)
$ \tx -> bench "encode_tx (0 htlcs)" $ whnf encode_tx tx
, env (pure $ build_commitment_tx $ mkCommitmentContext htlcs10 noAnchors)
$ \tx -> bench "encode_tx (10 htlcs)" $ whnf encode_tx tx
, env (pure $ build_commitment_tx $ mkCommitmentContext htlcs100 noAnchors)
$ \tx -> bench "encode_tx (100 htlcs)" $ whnf encode_tx tx
, bench "encode_htlc_tx" $
whnf encode_htlc_tx (build_htlc_timeout_tx sampleHtlcContext)
, bench "encode_closing_tx" $
whnf encode_closing_tx (build_closing_tx sampleClosingContext)
]
, bgroup "parsing" [
env (pure $ encode_tx $ build_commitment_tx $
mkCommitmentContext htlcs0 noAnchors)
$ \bs -> bench "decode_tx (0 htlcs)" $ whnf decode_tx bs
, env (pure $ encode_tx $ build_commitment_tx $
mkCommitmentContext htlcs10 noAnchors)
$ \bs -> bench "decode_tx (10 htlcs)" $ whnf decode_tx bs
, env (pure $ encode_tx $ build_commitment_tx $
mkCommitmentContext htlcs100 noAnchors)
$ \bs -> bench "decode_tx (100 htlcs)" $ whnf decode_tx bs
]
, bgroup "validation" [
env (pure $ build_commitment_tx $ mkCommitmentContext htlcs10 noAnchors)
$ \tx -> bench "validate_commitment_tx (valid)" $
whnf (validate_commitment_tx dust noAnchors) tx
, env (pure $ build_htlc_timeout_tx sampleHtlcContext)
$ \tx -> bench "validate_htlc_tx" $
whnf validate_htlc_tx tx
, env (pure $ build_closing_tx sampleClosingContext)
$ \tx -> bench "validate_closing_tx" $
whnf validate_closing_tx tx
, env (pure $ ctx_outputs $ build_commitment_tx $
mkCommitmentContext htlcs10 noAnchors)
$ \outs -> bench "validate_output_ordering" $
whnf validate_output_ordering outs
]
, bgroup "secret storage" [
bench "insert_secret (first)" $
whnf (insert_secret (BS.replicate 32 0xFF) 281474976710655)
empty_store
, env setupFilledStore $ \store ->
bench "derive_old_secret (recent)" $
whnf (derive_old_secret 281474976710654) store
, env setupFilledStore $ \store ->
bench "derive_old_secret (old)" $
whnf (derive_old_secret 281474976710600) store
]
, bgroup "output sorting" [
env (pure $ ctx_outputs $ build_commitment_tx $
mkCommitmentContext htlcs10 noAnchors)
$ \outs -> bench "sort_outputs (10)" $ nf sort_outputs outs
, env (pure $ ctx_outputs $ build_commitment_tx $
mkCommitmentContext htlcs100 noAnchors)
$ \outs -> bench "sort_outputs (100)" $ nf sort_outputs outs
]
]
where
-- Key derivation test data
basepoint = Point $ BS.pack
[0x03, 0x6d, 0x6c, 0xaa, 0xc2, 0x48, 0xaf, 0x96, 0xf6, 0xaf, 0xa7,
0xf9, 0x04, 0xf5, 0x50, 0x25, 0x3a, 0x0f, 0x3e, 0xf3, 0xf5, 0xaa,
0x2f, 0xe6, 0x83, 0x8a, 0x95, 0xb2, 0x16, 0x69, 0x14, 0x68, 0xe2]
perCommitmentPoint = PerCommitmentPoint $ Point $ BS.pack
[0x02, 0x5f, 0x71, 0x17, 0xa7, 0x81, 0x50, 0xfe, 0x2e, 0xf9, 0x7d,
0xb7, 0xcf, 0xc8, 0x3b, 0xd5, 0x7b, 0x2e, 0x2c, 0x0d, 0x0d, 0xd2,
0x5e, 0xaf, 0x46, 0x7a, 0x4a, 0x1c, 0x2a, 0x45, 0xce, 0x14, 0x86]
revocationBasepoint = RevocationBasepoint $ Point $ BS.pack
[0x03, 0x6d, 0x6c, 0xaa, 0xc2, 0x48, 0xaf, 0x96, 0xf6, 0xaf, 0xa7,
0xf9, 0x04, 0xf5, 0x50, 0x25, 0x3a, 0x0f, 0x3e, 0xf3, 0xf5, 0xaa,
0x2f, 0xe6, 0x83, 0x8a, 0x95, 0xb2, 0x16, 0x69, 0x14, 0x68, 0xe2]
-- Secret generation test data
seed = BS.replicate 32 0xFF
-- Fee calculation test data
feerate = FeeratePerKw 5000
noAnchors = ChannelFeatures { cf_option_anchors = False }
withAnchors = ChannelFeatures { cf_option_anchors = True }
-- Trimming test data
dust = DustLimit (Satoshi 546)
htlcNotTrimmed = HTLC
{ htlc_direction = HTLCOffered
, htlc_amount_msat = MilliSatoshi 5000000
, htlc_payment_hash = PaymentHash (BS.replicate 32 0)
, htlc_cltv_expiry = CltvExpiry 500000
}
htlcTrimmed = HTLC
{ htlc_direction = HTLCOffered
, htlc_amount_msat = MilliSatoshi 1000000
, htlc_payment_hash = PaymentHash (BS.replicate 32 0)
, htlc_cltv_expiry = CltvExpiry 500000
}
-- Sample pubkeys
samplePubkey1, samplePubkey2, samplePubkey3 :: Pubkey
samplePubkey1 = Pubkey $ BS.pack
[0x03, 0x6d, 0x6c, 0xaa, 0xc2, 0x48, 0xaf, 0x96, 0xf6, 0xaf, 0xa7,
0xf9, 0x04, 0xf5, 0x50, 0x25, 0x3a, 0x0f, 0x3e, 0xf3, 0xf5, 0xaa,
0x2f, 0xe6, 0x83, 0x8a, 0x95, 0xb2, 0x16, 0x69, 0x14, 0x68, 0xe2]
samplePubkey2 = Pubkey $ BS.pack
[0x02, 0x5f, 0x71, 0x17, 0xa7, 0x81, 0x50, 0xfe, 0x2e, 0xf9, 0x7d,
0xb7, 0xcf, 0xc8, 0x3b, 0xd5, 0x7b, 0x2e, 0x2c, 0x0d, 0x0d, 0xd2,
0x5e, 0xaf, 0x46, 0x7a, 0x4a, 0x1c, 0x2a, 0x45, 0xce, 0x14, 0x86]
samplePubkey3 = samplePubkey1
-- Funding outpoint
sampleFundingOutpoint :: Outpoint
sampleFundingOutpoint = Outpoint (TxId $ BS.replicate 32 0x01) 0
-- HTLC lists
mkHtlc :: HTLCDirection -> Word64 -> Word64 -> HTLC
mkHtlc dir amtMsat expiry = HTLC
{ htlc_direction = dir
, htlc_amount_msat = MilliSatoshi amtMsat
, htlc_payment_hash = PaymentHash (BS.replicate 32 0x00)
, htlc_cltv_expiry = CltvExpiry (fromIntegral expiry)
}
htlcs0, htlcs10, htlcs100 :: [HTLC]
htlcs0 = []
htlcs10 = [mkHtlc (if even i then HTLCOffered else HTLCReceived)
(5000000 + i * 100000) (500000 + i)
| i <- [0..9]]
htlcs100 = [mkHtlc (if even i then HTLCOffered else HTLCReceived)
(5000000 + i * 10000) (500000 + i)
| i <- [0..99]]
-- CommitmentKeys fixture
sampleCommitmentKeys :: CommitmentKeys
sampleCommitmentKeys = CommitmentKeys
{ ck_revocation_pubkey = RevocationPubkey samplePubkey1
, ck_local_delayed = LocalDelayedPubkey samplePubkey1
, ck_local_htlc = LocalHtlcPubkey samplePubkey1
, ck_remote_htlc = RemoteHtlcPubkey samplePubkey2
, ck_local_payment = LocalPubkey samplePubkey1
, ck_remote_payment = RemotePubkey samplePubkey2
, ck_local_funding = FundingPubkey samplePubkey1
, ck_remote_funding = FundingPubkey samplePubkey2
}
-- CommitmentContext builder
mkCommitmentContext :: [HTLC] -> ChannelFeatures -> CommitmentContext
mkCommitmentContext htlcs features = CommitmentContext
{ cc_funding_outpoint = sampleFundingOutpoint
, cc_commitment_number = CommitmentNumber 42
, cc_local_payment_bp =
PaymentBasepoint $ Point $ unPubkey samplePubkey1
, cc_remote_payment_bp =
PaymentBasepoint $ Point $ unPubkey samplePubkey2
, cc_to_self_delay = ToSelfDelay 144
, cc_dust_limit = DustLimit (Satoshi 546)
, cc_feerate = FeeratePerKw 5000
, cc_features = features
, cc_is_funder = True
, cc_to_local_msat = MilliSatoshi 500000000
, cc_to_remote_msat = MilliSatoshi 500000000
, cc_htlcs = htlcs
, cc_keys = sampleCommitmentKeys
}
-- HTLC context
sampleHtlcContext :: HTLCContext
sampleHtlcContext = HTLCContext
{ hc_commitment_txid = TxId $ BS.replicate 32 0x01
, hc_output_index = 0
, hc_htlc = mkHtlc HTLCOffered 5000000 500000
, hc_to_self_delay = ToSelfDelay 144
, hc_feerate = FeeratePerKw 5000
, hc_features = noAnchors
, hc_revocation_pubkey = RevocationPubkey samplePubkey1
, hc_local_delayed = LocalDelayedPubkey samplePubkey1
}
-- Closing context
sampleClosingContext :: ClosingContext
sampleClosingContext = ClosingContext
{ clc_funding_outpoint = sampleFundingOutpoint
, clc_local_amount = Satoshi 500000
, clc_remote_amount = Satoshi 500000
, clc_local_script =
Script $ BS.pack [0x00, 0x14] <> BS.replicate 20 0x01
, clc_remote_script =
Script $ BS.pack [0x00, 0x14] <> BS.replicate 20 0x02
, clc_local_dust_limit = DustLimit (Satoshi 546)
, clc_remote_dust_limit = DustLimit (Satoshi 546)
, clc_fee = Satoshi 1000
, clc_is_funder = True
, clc_locktime = Locktime 0
, clc_funding_script = funding_script (FundingPubkey samplePubkey1)
(FundingPubkey samplePubkey2)
}
-- Setup for secret storage benchmarks
setupFilledStore :: IO SecretStore
setupFilledStore = do
let secrets = [(generate_from_seed seed i, i)
| i <- [281474976710655, 281474976710654 .. 281474976710600]]
pure $! foldl insertOrFail empty_store secrets
where
insertOrFail store (sec, idx) =
case insert_secret sec idx store of
Just s -> s
Nothing -> store