{-# LANGUAGE FlexibleContexts #-}

module Test.Util.Serialisation.TxWireSize
  ( prop_txWireSize
  , prop_txWireSize_txSubmission
  ) where

import Codec.CBOR.Write (toLazyByteString)
import qualified Data.ByteString.Lazy as BSL
import Ouroboros.Consensus.Block.Abstract (CodecConfig)
import Ouroboros.Consensus.Ledger.SupportsMempool
  ( GenTx
  , TxLimits (..)
  )
import Ouroboros.Consensus.Node.NetworkProtocolVersion
  ( BlockNodeToNodeVersion
  )
import Ouroboros.Consensus.Node.Serialisation
import Ouroboros.Network.SizeInBytes
import Ouroboros.Network.TxSubmission.Inbound.V2.State
  ( const_MAX_TX_SIZE_DISCREPANCY
  )
import Test.Tasty.QuickCheck
import Test.Util.Serialisation.Roundtrip (WithVersion (..))

-- | Verify that `txWriteSize` agrees with the encoded `GenTx` size up to
-- `config_MX_TX_SIZE_DISCREPANCY` allowed by `tx-submission` mini-protocol.
--
-- NOTE: `tx`s which do not satisfy this property will terminate connections.
prop_txWireSize_txSubmission ::
  ( SerialiseNodeToNode blk (GenTx blk)
  , TxLimits blk
  ) =>
  CodecConfig blk ->
  WithVersion (BlockNodeToNodeVersion blk) (GenTx blk) ->
  Property
prop_txWireSize_txSubmission :: forall blk.
(SerialiseNodeToNode blk (GenTx blk), TxLimits blk) =>
CodecConfig blk
-> WithVersion (BlockNodeToNodeVersion blk) (GenTx blk) -> Property
prop_txWireSize_txSubmission CodecConfig blk
ccfg (WithVersion BlockNodeToNodeVersion blk
version GenTx blk
tx) =
  String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
counterexample (String
"encoded size " String -> String -> String
forall a. [a] -> [a] -> [a]
++ SizeInBytes -> String
forall a. Show a => a -> String
show SizeInBytes
encSize String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", computed size " String -> String -> String
forall a. [a] -> [a] -> [a]
++ SizeInBytes -> String
forall a. Show a => a -> String
show SizeInBytes
cmpSize) (Property -> Property) -> Property -> Property
forall a b. (a -> b) -> a -> b
$
    String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
counterexample (String
"diff size " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
diffSize) (Property -> Property) -> Property -> Property
forall a b. (a -> b) -> a -> b
$
      String -> Bool -> Property
forall prop. Testable prop => String -> prop -> Property
label (Int -> String
forall a. Show a => a -> String
show Int
diffSize) (Bool -> Property) -> Bool -> Property
forall a b. (a -> b) -> a -> b
$
        Int -> SizeInBytes
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Int
forall a. Num a => a -> a
abs Int
diffSize) SizeInBytes -> SizeInBytes -> Bool
forall a. Ord a => a -> a -> Bool
<= SizeInBytes
const_MAX_TX_SIZE_DISCREPANCY
 where
  encSize, cmpSize :: SizeInBytes

  encSize :: SizeInBytes
encSize = Int64 -> SizeInBytes
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int64
BSL.length (ByteString -> Int64) -> ByteString -> Int64
forall a b. (a -> b) -> a -> b
$ Encoding -> ByteString
toLazyByteString (CodecConfig blk
-> BlockNodeToNodeVersion blk -> GenTx blk -> Encoding
forall blk a.
SerialiseNodeToNode blk a =>
CodecConfig blk -> BlockNodeToNodeVersion blk -> a -> Encoding
encodeNodeToNode CodecConfig blk
ccfg BlockNodeToNodeVersion blk
version GenTx blk
tx))
  cmpSize :: SizeInBytes
cmpSize = GenTx blk -> SizeInBytes
forall blk. TxLimits blk => GenTx blk -> SizeInBytes
txWireSize GenTx blk
tx

  diffSize :: Int
  diffSize :: Int
diffSize = SizeInBytes -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral SizeInBytes
encSize Int -> Int -> Int
forall a. Num a => a -> a -> a
- SizeInBytes -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral SizeInBytes
cmpSize

-- | Verify that `txWriteSize` is very close to the real tx size.
--
-- The `txWireSize` doesn't take into account if HFC is enabled or not.  If it
-- is enabled, the `wireTxSize` for `GenTx (HardForkBlock xs)` will agree with
-- the encoded size, but if it is disabled it will overestimate the value by HFC
-- envelope (at most 3 bytes, 2 for forcible future)
prop_txWireSize ::
  ( SerialiseNodeToNode blk (GenTx blk)
  , TxLimits blk
  ) =>
  -- | show tx bytes
  (GenTx blk -> Maybe String) ->
  CodecConfig blk ->
  WithVersion (BlockNodeToNodeVersion blk) (GenTx blk) ->
  Property
prop_txWireSize :: forall blk.
(SerialiseNodeToNode blk (GenTx blk), TxLimits blk) =>
(GenTx blk -> Maybe String)
-> CodecConfig blk
-> WithVersion (BlockNodeToNodeVersion blk) (GenTx blk)
-> Property
prop_txWireSize GenTx blk -> Maybe String
getTxBytes CodecConfig blk
ccfg (WithVersion BlockNodeToNodeVersion blk
version GenTx blk
tx) =
  String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
counterexample (String
"encoded size " String -> String -> String
forall a. [a] -> [a] -> [a]
++ SizeInBytes -> String
forall a. Show a => a -> String
show SizeInBytes
encSize String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", computed size " String -> String -> String
forall a. [a] -> [a] -> [a]
++ SizeInBytes -> String
forall a. Show a => a -> String
show SizeInBytes
cmpSize)
    (Property -> Property) -> Property -> Property
forall a b. (a -> b) -> a -> b
$ String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
counterexample (String
"encoded tx:\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ ByteString -> String
forall a. Show a => a -> String
show ByteString
encoded)
    (Property -> Property) -> Property -> Property
forall a b. (a -> b) -> a -> b
$ String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
label (Int -> String
forall a. Show a => a -> String
show Int
diffSize)
    (Property -> Property) -> Property -> Property
forall a b. (a -> b) -> a -> b
$ case GenTx blk -> Maybe String
getTxBytes GenTx blk
tx of
      Just String
txBytes -> String -> Property -> Property
forall prop. Testable prop => String -> prop -> Property
counterexample (String
"tx bytes:\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
txBytes)
      Maybe String
Nothing -> Property -> Property
forall prop. Testable prop => prop -> Property
property
    (Property -> Property) -> Property -> Property
forall a b. (a -> b) -> a -> b
$ SizeInBytes
encSize SizeInBytes -> SizeInBytes -> Bool
forall a. Ord a => a -> a -> Bool
<= SizeInBytes
cmpSize
      Bool -> Bool -> Property
forall prop1 prop2.
(Testable prop1, Testable prop2) =>
prop1 -> prop2 -> Property
.&&. SizeInBytes
encSize SizeInBytes -> SizeInBytes -> SizeInBytes
forall a. Num a => a -> a -> a
+ SizeInBytes
3 SizeInBytes -> SizeInBytes -> Bool
forall a. Ord a => a -> a -> Bool
>= SizeInBytes
cmpSize
 where
  encoded :: BSL.ByteString
  encoded :: ByteString
encoded = Encoding -> ByteString
toLazyByteString (CodecConfig blk
-> BlockNodeToNodeVersion blk -> GenTx blk -> Encoding
forall blk a.
SerialiseNodeToNode blk a =>
CodecConfig blk -> BlockNodeToNodeVersion blk -> a -> Encoding
encodeNodeToNode CodecConfig blk
ccfg BlockNodeToNodeVersion blk
version GenTx blk
tx)

  encSize, cmpSize :: SizeInBytes
  encSize :: SizeInBytes
encSize = Int64 -> SizeInBytes
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int64
BSL.length ByteString
encoded)
  cmpSize :: SizeInBytes
cmpSize = GenTx blk -> SizeInBytes
forall blk. TxLimits blk => GenTx blk -> SizeInBytes
txWireSize GenTx blk
tx

  diffSize :: Int
  diffSize :: Int
diffSize = SizeInBytes -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral SizeInBytes
encSize Int -> Int -> Int
forall a. Num a => a -> a -> a
- SizeInBytes -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral SizeInBytes
cmpSize