{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}

module Test.Consensus.Protocol.Praos2 (tests) where

import Cardano.Ledger.BaseTypes (Milliseconds32 (..), ProtVer (..))
import Cardano.Ledger.Coin (knownNonZeroCoin)
import Cardano.Ledger.State (PoolDistr (..), emptyLeiosCommittee)
import Cardano.Protocol.Crypto (StandardCrypto)
import Cardano.Slotting.EpochInfo (fixedEpochInfo)
import Cardano.Slotting.Slot (EpochSize (..), SlotNo (..), WithOrigin (..))
import Cardano.Slotting.Time (slotLengthFromSec)
import qualified Data.Map.Strict as Map
import Data.Proxy (Proxy (..))
import qualified Data.Strict.Either as Strict
import Data.Word (Word64)
import Ouroboros.Consensus.Leios.Protocol (LeiosAnnouncement, LeiosLedgerView (..))
import Ouroboros.Consensus.Protocol.Abstract (TranslateProto (..))
import Ouroboros.Consensus.Protocol.PolyPraos
  ( PolyPraosState (..)
  , Ticked (..)
  , WhyNotCertifiable (..)
  , tickChainDepStatePolyPraos
  )
import qualified Ouroboros.Consensus.Protocol.Praos.Views as Views
import Ouroboros.Consensus.Protocol.Praos2 (LeiosOnly (Praos2HasLeios), Praos2)
import Test.Consensus.Protocol.Serialisation.Generators ()
import Test.Tasty
import Test.Tasty.QuickCheck

tests :: TestTree
tests :: TestTree
tests =
  TestName -> [TestTree] -> TestTree
testGroup
    TestName
"Praos2"
    [ TestName
-> (PolyPraosState (Praos2 StandardCrypto) -> Property) -> TestTree
forall a. Testable a => TestName -> a -> TestTree
testProperty
        TestName
"crossing into another era with Leios makes an announcement uncertifiable"
        PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEraMakesAnnouncementUncertifiable
    , TestName
-> (PolyPraosState (Praos2 StandardCrypto) -> Property) -> TestTree
forall a. Testable a => TestName -> a -> TestTree
testProperty
        TestName
"crossing into another epoch makes an announcement uncertifiable"
        PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEpochMakesAnnouncementUncertifiable
    , TestName
-> (PolyPraosState (Praos2 StandardCrypto) -> Property) -> TestTree
forall a. Testable a => TestName -> a -> TestTree
testProperty
        TestName
"crossing an era and an epoch in either order leaves the era's reason"
        PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEraAndEpochInEitherOrder
    , TestName
-> (WhyNotCertifiable
    -> WhyNotCertifiable -> WhyNotCertifiable -> Property)
-> TestTree
forall a. Testable a => TestName -> a -> TestTree
testProperty TestName
"WhyNotCertifiable's <> is associative" WhyNotCertifiable
-> WhyNotCertifiable -> WhyNotCertifiable -> Property
prop_whyNotCertifiableAssociative
    , TestName
-> (WhyNotCertifiable -> WhyNotCertifiable -> Property) -> TestTree
forall a. Testable a => TestName -> a -> TestTree
testProperty TestName
"WhyNotCertifiable's <> is commutative" WhyNotCertifiable -> WhyNotCertifiable -> Property
prop_whyNotCertifiableCommutative
    ]

-- | An endorser block may not be certified across an era boundary.
prop_crossingEraMakesAnnouncementUncertifiable ::
  PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEraMakesAnnouncementUncertifiable :: PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEraMakesAnnouncementUncertifiable PolyPraosState (Praos2 StandardCrypto)
st =
  PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
translateEra PolyPraosState (Praos2 StandardCrypto)
st PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto) -> Property
forall a. (Eq a, Show a) => a -> a -> Property
=== PolyPraosState (Praos2 StandardCrypto)
st{praosStateLeiosAnnouncement = Praos2HasLeios expected}
 where
  Praos2HasLeios Either WhyNotCertifiable LeiosAnnouncement
before = PolyPraosState (Praos2 StandardCrypto)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall proto.
PolyPraosState proto
-> WhenLeios proto (Either WhyNotCertifiable LeiosAnnouncement)
praosStateLeiosAnnouncement PolyPraosState (Praos2 StandardCrypto)
st
  expected :: Either WhyNotCertifiable LeiosAnnouncement
expected = Either WhyNotCertifiable LeiosAnnouncement
-> Either WhyNotCertifiable LeiosAnnouncement
afterEraBoundary Either WhyNotCertifiable LeiosAnnouncement
before

-- | An endorser block may not be certified across an epoch boundary.
prop_crossingEpochMakesAnnouncementUncertifiable ::
  PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEpochMakesAnnouncementUncertifiable :: PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEpochMakesAnnouncementUncertifiable PolyPraosState (Praos2 StandardCrypto)
st =
  Gen Word64 -> (Word64 -> Property) -> Property
forall a prop.
(Show a, Testable prop) =>
Gen a -> (a -> prop) -> Property
forAll ((Word64, Word64) -> Gen Word64
forall a. Random a => (a, a) -> Gen a
choose (Word64
1, Word64
2 Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
* Word64
epochSize)) ((Word64 -> Property) -> Property)
-> (Word64 -> Property) -> Property
forall a b. (a -> b) -> a -> b
$ \Word64
delta ->
    let slot :: SlotNo
slot = Word64 -> SlotNo
SlotNo (Word64 -> SlotNo) -> Word64 -> SlotNo
forall a b. (a -> b) -> a -> b
$ Word64
lastSlot Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
delta
        crosses :: Bool
crosses = Word64 -> Word64
epochOf (SlotNo -> Word64
unSlotNo SlotNo
slot) Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
> Word64 -> Word64
epochOf Word64
lastSlot
        expected :: Either WhyNotCertifiable LeiosAnnouncement
expected
          | Bool
crosses = Either WhyNotCertifiable LeiosAnnouncement
-> Either WhyNotCertifiable LeiosAnnouncement
afterEpochBoundary Either WhyNotCertifiable LeiosAnnouncement
before
          | Bool
otherwise = Either WhyNotCertifiable LeiosAnnouncement
before
     in Bool -> TestName -> Property -> Property
forall prop. Testable prop => Bool -> TestName -> prop -> Property
classify Bool
crosses TestName
"crosses an epoch boundary" (Property -> Property) -> Property -> Property
forall a b. (a -> b) -> a -> b
$
          PolyPraosState (Praos2 StandardCrypto)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall proto.
PolyPraosState proto
-> WhenLeios proto (Either WhyNotCertifiable LeiosAnnouncement)
praosStateLeiosAnnouncement (SlotNo
-> PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
tickTo SlotNo
slot PolyPraosState (Praos2 StandardCrypto)
st) LeiosOnly
  (Praos2 StandardCrypto)
  ()
  (Either WhyNotCertifiable LeiosAnnouncement)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
-> Property
forall a. (Eq a, Show a) => a -> a -> Property
=== Either WhyNotCertifiable LeiosAnnouncement
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall c a b. b -> LeiosOnly (Praos2 c) a b
Praos2HasLeios Either WhyNotCertifiable LeiosAnnouncement
expected
 where
  Praos2HasLeios Either WhyNotCertifiable LeiosAnnouncement
before = PolyPraosState (Praos2 StandardCrypto)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall proto.
PolyPraosState proto
-> WhenLeios proto (Either WhyNotCertifiable LeiosAnnouncement)
praosStateLeiosAnnouncement PolyPraosState (Praos2 StandardCrypto)
st
  lastSlot :: Word64
lastSlot = case PolyPraosState (Praos2 StandardCrypto) -> WithOrigin SlotNo
forall proto. PolyPraosState proto -> WithOrigin SlotNo
praosStateLastSlot PolyPraosState (Praos2 StandardCrypto)
st of
    WithOrigin SlotNo
Origin -> Word64
0
    At (SlotNo Word64
s) -> Word64
s

-- | The more specific reason survives, whichever boundary is crossed first.
prop_crossingEraAndEpochInEitherOrder ::
  PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEraAndEpochInEitherOrder :: PolyPraosState (Praos2 StandardCrypto) -> Property
prop_crossingEraAndEpochInEitherOrder PolyPraosState (Praos2 StandardCrypto)
st =
  PolyPraosState (Praos2 StandardCrypto)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall proto.
PolyPraosState proto
-> WhenLeios proto (Either WhyNotCertifiable LeiosAnnouncement)
praosStateLeiosAnnouncement (SlotNo
-> PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
tickTo SlotNo
nextEpoch (PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
translateEra PolyPraosState (Praos2 StandardCrypto)
st))
    LeiosOnly
  (Praos2 StandardCrypto)
  ()
  (Either WhyNotCertifiable LeiosAnnouncement)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
-> Property
forall a. (Eq a, Show a) => a -> a -> Property
=== Either WhyNotCertifiable LeiosAnnouncement
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall c a b. b -> LeiosOnly (Praos2 c) a b
Praos2HasLeios Either WhyNotCertifiable LeiosAnnouncement
expected
    Property -> Property -> Property
forall prop1 prop2.
(Testable prop1, Testable prop2) =>
prop1 -> prop2 -> Property
.&&. PolyPraosState (Praos2 StandardCrypto)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall proto.
PolyPraosState proto
-> WhenLeios proto (Either WhyNotCertifiable LeiosAnnouncement)
praosStateLeiosAnnouncement (PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
translateEra (SlotNo
-> PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
tickTo SlotNo
nextEpoch PolyPraosState (Praos2 StandardCrypto)
st))
      LeiosOnly
  (Praos2 StandardCrypto)
  ()
  (Either WhyNotCertifiable LeiosAnnouncement)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
-> Property
forall a. (Eq a, Show a) => a -> a -> Property
=== Either WhyNotCertifiable LeiosAnnouncement
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall c a b. b -> LeiosOnly (Praos2 c) a b
Praos2HasLeios Either WhyNotCertifiable LeiosAnnouncement
expected
 where
  Praos2HasLeios Either WhyNotCertifiable LeiosAnnouncement
before = PolyPraosState (Praos2 StandardCrypto)
-> LeiosOnly
     (Praos2 StandardCrypto)
     ()
     (Either WhyNotCertifiable LeiosAnnouncement)
forall proto.
PolyPraosState proto
-> WhenLeios proto (Either WhyNotCertifiable LeiosAnnouncement)
praosStateLeiosAnnouncement PolyPraosState (Praos2 StandardCrypto)
st
  expected :: Either WhyNotCertifiable LeiosAnnouncement
expected = Either WhyNotCertifiable LeiosAnnouncement
-> Either WhyNotCertifiable LeiosAnnouncement
afterEraBoundary Either WhyNotCertifiable LeiosAnnouncement
before
  nextEpoch :: SlotNo
nextEpoch = Word64 -> SlotNo
SlotNo (Word64 -> SlotNo) -> Word64 -> SlotNo
forall a b. (a -> b) -> a -> b
$ (Word64 -> Word64
epochOf Word64
lastSlot Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
1) Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
* Word64
epochSize
  lastSlot :: Word64
lastSlot = case PolyPraosState (Praos2 StandardCrypto) -> WithOrigin SlotNo
forall proto. PolyPraosState proto -> WithOrigin SlotNo
praosStateLastSlot PolyPraosState (Praos2 StandardCrypto)
st of
    WithOrigin SlotNo
Origin -> Word64
0
    At (SlotNo Word64
s) -> Word64
s

prop_whyNotCertifiableAssociative ::
  WhyNotCertifiable -> WhyNotCertifiable -> WhyNotCertifiable -> Property
prop_whyNotCertifiableAssociative :: WhyNotCertifiable
-> WhyNotCertifiable -> WhyNotCertifiable -> Property
prop_whyNotCertifiableAssociative WhyNotCertifiable
a WhyNotCertifiable
b WhyNotCertifiable
c = (WhyNotCertifiable
a WhyNotCertifiable -> WhyNotCertifiable -> WhyNotCertifiable
forall a. Semigroup a => a -> a -> a
<> WhyNotCertifiable
b) WhyNotCertifiable -> WhyNotCertifiable -> WhyNotCertifiable
forall a. Semigroup a => a -> a -> a
<> WhyNotCertifiable
c WhyNotCertifiable -> WhyNotCertifiable -> Property
forall a. (Eq a, Show a) => a -> a -> Property
=== WhyNotCertifiable
a WhyNotCertifiable -> WhyNotCertifiable -> WhyNotCertifiable
forall a. Semigroup a => a -> a -> a
<> (WhyNotCertifiable
b WhyNotCertifiable -> WhyNotCertifiable -> WhyNotCertifiable
forall a. Semigroup a => a -> a -> a
<> WhyNotCertifiable
c)

prop_whyNotCertifiableCommutative :: WhyNotCertifiable -> WhyNotCertifiable -> Property
prop_whyNotCertifiableCommutative :: WhyNotCertifiable -> WhyNotCertifiable -> Property
prop_whyNotCertifiableCommutative WhyNotCertifiable
a WhyNotCertifiable
b = WhyNotCertifiable
a WhyNotCertifiable -> WhyNotCertifiable -> WhyNotCertifiable
forall a. Semigroup a => a -> a -> a
<> WhyNotCertifiable
b WhyNotCertifiable -> WhyNotCertifiable -> Property
forall a. (Eq a, Show a) => a -> a -> Property
=== WhyNotCertifiable
b WhyNotCertifiable -> WhyNotCertifiable -> WhyNotCertifiable
forall a. Semigroup a => a -> a -> a
<> WhyNotCertifiable
a

afterEraBoundary ::
  Strict.Either WhyNotCertifiable LeiosAnnouncement ->
  Strict.Either WhyNotCertifiable LeiosAnnouncement
afterEraBoundary :: Either WhyNotCertifiable LeiosAnnouncement
-> Either WhyNotCertifiable LeiosAnnouncement
afterEraBoundary = \case
  Strict.Left WhyNotCertifiable
NothingAnnounced -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
NothingAnnounced
  Strict.Left WhyNotCertifiable
AnnouncedInPreviousEra -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
AnnouncedInPreviousEra
  Strict.Left WhyNotCertifiable
AnnouncedInPreviousEpoch -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
AnnouncedInPreviousEra
  Strict.Right LeiosAnnouncement
_ann -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
AnnouncedInPreviousEra

afterEpochBoundary ::
  Strict.Either WhyNotCertifiable LeiosAnnouncement ->
  Strict.Either WhyNotCertifiable LeiosAnnouncement
afterEpochBoundary :: Either WhyNotCertifiable LeiosAnnouncement
-> Either WhyNotCertifiable LeiosAnnouncement
afterEpochBoundary = \case
  Strict.Left WhyNotCertifiable
NothingAnnounced -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
NothingAnnounced
  Strict.Left WhyNotCertifiable
AnnouncedInPreviousEra -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
AnnouncedInPreviousEra
  Strict.Left WhyNotCertifiable
AnnouncedInPreviousEpoch -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
AnnouncedInPreviousEpoch
  Strict.Right LeiosAnnouncement
_ann -> WhyNotCertifiable -> Either WhyNotCertifiable LeiosAnnouncement
forall a b. a -> Either a b
Strict.Left WhyNotCertifiable
AnnouncedInPreviousEpoch

translateEra :: PolyPraosState (Praos2 StandardCrypto) -> PolyPraosState (Praos2 StandardCrypto)
translateEra :: PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
translateEra =
  Proxy (Praos2 StandardCrypto, Praos2 StandardCrypto, ())
-> ChainDepState (Praos2 StandardCrypto)
-> ChainDepState (Praos2 StandardCrypto)
forall protoFrom protoTo eraTo.
TranslateProto protoFrom protoTo eraTo =>
Proxy (protoFrom, protoTo, eraTo)
-> ChainDepState protoFrom -> ChainDepState protoTo
translateChainDepState (forall t. Proxy t
forall {k} (t :: k). Proxy t
Proxy @(Praos2 StandardCrypto, Praos2 StandardCrypto, ()))

tickTo :: SlotNo -> PolyPraosState (Praos2 StandardCrypto) -> PolyPraosState (Praos2 StandardCrypto)
tickTo :: SlotNo
-> PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
tickTo SlotNo
slot =
  Ticked (PolyPraosState (Praos2 StandardCrypto))
-> PolyPraosState (Praos2 StandardCrypto)
forall proto. Ticked (PolyPraosState proto) -> PolyPraosState proto
tickedPraosStateChainDepState
    (Ticked (PolyPraosState (Praos2 StandardCrypto))
 -> PolyPraosState (Praos2 StandardCrypto))
-> (PolyPraosState (Praos2 StandardCrypto)
    -> Ticked (PolyPraosState (Praos2 StandardCrypto)))
-> PolyPraosState (Praos2 StandardCrypto)
-> PolyPraosState (Praos2 StandardCrypto)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. EpochInfo (Except PastHorizonException)
-> PolyPraosLedgerView (Praos2 StandardCrypto)
-> SlotNo
-> PolyPraosState (Praos2 StandardCrypto)
-> Ticked (PolyPraosState (Praos2 StandardCrypto))
forall proto.
Functor (WhenLeios proto) =>
EpochInfo (Except PastHorizonException)
-> PolyPraosLedgerView proto
-> SlotNo
-> PolyPraosState proto
-> Ticked (PolyPraosState proto)
tickChainDepStatePolyPraos
      (EpochSize -> SlotLength -> EpochInfo (Except PastHorizonException)
forall (m :: * -> *).
Monad m =>
EpochSize -> SlotLength -> EpochInfo m
fixedEpochInfo (Word64 -> EpochSize
EpochSize Word64
epochSize) (Integer -> SlotLength
slotLengthFromSec Integer
1))
      PolyPraosLedgerView (Praos2 StandardCrypto)
ledgerView
      SlotNo
slot

epochSize :: Word64
epochSize :: Word64
epochSize = Word64
5

epochOf :: Word64 -> Word64
epochOf :: Word64 -> Word64
epochOf Word64
s = Word64
s Word64 -> Word64 -> Word64
forall a. Integral a => a -> a -> a
`div` Word64
epochSize

-- | The tick only carries it along.
ledgerView :: Views.PolyPraosLedgerView (Praos2 StandardCrypto)
ledgerView :: PolyPraosLedgerView (Praos2 StandardCrypto)
ledgerView =
  Views.PraosLedgerView
    { plvPoolDistr :: PoolDistr
Views.plvPoolDistr = Map (KeyHash StakePool) IndividualPoolStake
-> NonZero Coin -> PoolDistr
PoolDistr Map (KeyHash StakePool) IndividualPoolStake
forall k a. Map k a
Map.empty (forall (n :: Natural). (KnownNat n, 1 <= n) => NonZero Coin
knownNonZeroCoin @1)
    , plvMaxHeaderSize :: Word16
Views.plvMaxHeaderSize = Word16
0
    , plvMaxBodySize :: Word32
Views.plvMaxBodySize = Word32
0
    , plvProtocolVersion :: ProtVer
Views.plvProtocolVersion = Version -> Word32 -> ProtVer
ProtVer Version
forall a. Bounded a => a
minBound Word32
0
    , plvLeios :: WhenLeios (Praos2 StandardCrypto) LeiosLedgerView
Views.plvLeios =
        LeiosLedgerView
-> WhenLeios (Praos2 StandardCrypto) LeiosLedgerView
forall c a b. b -> LeiosOnly (Praos2 c) a b
Praos2HasLeios
          MkLeiosLedgerView
            { llvCommittee :: LeiosCommittee
llvCommittee = LeiosCommittee
emptyLeiosCommittee
            , llvQuorumStakeThreshold :: UnitInterval
llvQuorumStakeThreshold = UnitInterval
forall a. Bounded a => a
maxBound
            , llvAnnouncementPeriod :: Milliseconds32
llvAnnouncementPeriod = Word32 -> Milliseconds32
Milliseconds32 Word32
1000
            , llvVotePeriod :: Milliseconds32
llvVotePeriod = Word32 -> Milliseconds32
Milliseconds32 Word32
4000
            , llvDiffusionPeriod :: Milliseconds32
llvDiffusionPeriod = Word32 -> Milliseconds32
Milliseconds32 Word32
1000
            , llvMaxEbBodySize :: Word32
llvMaxEbBodySize = Word32
0
            , llvMaxEbTxsSize :: Word32
llvMaxEbTxsSize = Word32
0
            }
    }