Skip to content

Commit

Permalink
Add more tests on event ids being strictly monotonic
Browse files Browse the repository at this point in the history
  • Loading branch information
ch1bo committed Mar 12, 2024
1 parent a71cb88 commit fa7d970
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 13 deletions.
11 changes: 3 additions & 8 deletions hydra-node/test/Hydra/API/ServerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import Test.Hydra.Fixture (alice, testHeadId)
import Test.Network.Ports (withFreePort)
import Test.QuickCheck (checkCoverage, cover, generate)
import Test.QuickCheck.Monadic (monadicIO, monitor, pick, run)
import Test.Util (isContinuous)

spec :: Spec
spec =
Expand Down Expand Up @@ -212,7 +213,7 @@ spec =
waitMatch 5 conn $ \v ->
guard $ isNothing $ v ^? key "utxo"

it "sequence numbers are continuous and strictly monotonically increasing" $
it "sequence numbers are continuous" $
monadicIO $ do
outputs :: [ServerOutput SimpleTx] <- pick arbitrary
run $
Expand All @@ -226,7 +227,7 @@ spec =
case traverse Aeson.eitherDecode received of
Left{} -> failure $ "Failed to decode messages:\n" <> show received
Right (timedOutputs :: [TimedServerOutput SimpleTx]) ->
seq <$> timedOutputs `shouldSatisfy` strictlyMonotonic
seq <$> timedOutputs `shouldSatisfy` isContinuous

it "displays correctly headStatus and snapshotUtxo in a Greeting message" $
showLogsOnFailure "ServerSpec" $ \tracer ->
Expand Down Expand Up @@ -305,12 +306,6 @@ spec =
withFreePort $
\port -> sendsAnErrorWhenInputCannotBeDecoded port

strictlyMonotonic :: (Eq a, Enum a) => [a] -> Bool
strictlyMonotonic = \case
[] -> True
[_] -> True
(a : b : as) -> succ a == b && strictlyMonotonic (b : as)

sendsAnErrorWhenInputCannotBeDecoded :: PortNumber -> Expectation
sendsAnErrorWhenInputCannotBeDecoded port = do
showLogsOnFailure "ServerSpec" $ \tracer ->
Expand Down
4 changes: 2 additions & 2 deletions hydra-node/test/Hydra/Node/InputQueueSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module Hydra.Node.InputQueueSpec where
import Hydra.Prelude

import Control.Monad.IOSim (IOSim, runSimOrThrow)
import Hydra.API.ServerSpec (strictlyMonotonic)
import Hydra.Node.InputQueue (Queued (queuedId), createInputQueue, dequeue, enqueue)
import Test.Hspec (Spec)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (NonEmptyList (NonEmpty), Property, counterexample)
import Test.Util (isContinuous)

spec :: Spec
spec =
Expand All @@ -25,5 +25,5 @@ prop_identify_enqueued_items (NonEmpty inputs) =
enqueue q i
queuedId <$> dequeue q
ids = runSimOrThrow test
in strictlyMonotonic ids
in isContinuous ids
& counterexample ("queued ids: " <> show ids)
46 changes: 43 additions & 3 deletions hydra-node/test/Hydra/NodeSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Hydra.ContestationPeriod (ContestationPeriod (..))
import Hydra.Crypto (HydraKey, sign)
import Hydra.Environment (Environment (..))
import Hydra.Environment qualified as Environment
import Hydra.Events (EventSink (..), EventSource (..))
import Hydra.Events (EventSink (..), EventSource (..), getEventId)
import Hydra.HeadLogic (Input (..), defaultTTL)
import Hydra.HeadLogic.Outcome (StateChanged (HeadInitialized), genStateChanged)
import Hydra.HeadLogicSpec (inInitialState, testSnapshot)
Expand All @@ -42,7 +42,8 @@ import Hydra.Options (defaultContestationPeriod)
import Hydra.Party (Party, deriveParty)
import Hydra.Persistence (PersistenceIncremental (..), eventPairFromPersistenceIncremental)
import Test.Hydra.Fixture (alice, aliceSk, bob, bobSk, carol, carolSk, cperiod, deriveOnChainId, testEnvironment, testHeadId, testHeadSeed)
import Test.QuickCheck (elements, forAllBlind, forAllShrink, listOf, listOf1, (==>))
import Test.QuickCheck (classify, counterexample, elements, forAllBlind, forAllShrink, forAllShrinkBlind, idempotentIOProperty, listOf, listOf1, (==>))
import Test.Util (isStrictlyMonotonic)

spec :: Spec
spec = parallel $ do
Expand All @@ -63,6 +64,16 @@ spec = parallel $ do
getMockSinkEvents1 `shouldReturn` someEvents
getMockSinkEvents2 `shouldReturn` someEvents

it "event ids are consistent" $ \node ->
forAllShrink (listOf $ genStateChanged testEnvironment) shrink $
\someEvents -> do
(sink, getSinkEvents) <- createRecordingSink

void $ hydrate (mockSource someEvents) [sink] node

seenEvents <- getSinkEvents
getEventId <$> seenEvents `shouldBe` getEventId <$> someEvents

it "fails if one sink fails" $ \node ->
forAllShrink (listOf1 $ genStateChanged testEnvironment) shrink $
\someEvents -> do
Expand Down Expand Up @@ -97,6 +108,29 @@ spec = parallel $ do
events `shouldNotBe` []
getMockSinkEvents2 `shouldReturn` events

it "event ids are strictly monotonic" $ \dryNode -> do
forAllShrinkBlind arbitrary shrink $ \someInputs ->
idempotentIOProperty $ do
(sink, getSinkEvents) <- createRecordingSink

hydrate (mockSource []) [sink] dryNode
>>= notConnect
-- NOTE: Arbitrary inputs in open head state results more likely
-- in multiple state change events per input (during tx
-- processing).
>>= primeWith (inputsToOpenHead <> someInputs)
>>= runToCompletion

events <- getSinkEvents
let eventIds = getEventId <$> events
pure $
isStrictlyMonotonic eventIds
& counterexample "Not strictly monotonic"
& counterexample ("Event ids: " <> show eventIds)
& counterexample ("Events: " <> show events)
& counterexample ("Additional inputs: " <> show someInputs)
& classify (null eventIds) "empty list of events"

it "can continue after re-hydration" $ \dryNode ->
failAfter 1 $ do
persistence <- createPersistenceInMemory
Expand All @@ -110,15 +144,21 @@ spec = parallel $ do
let reqTx = NetworkInput{ttl = defaultTTL, party = alice, message = ReqTx{transaction = tx1}}
tx1 = SimpleTx{txSimpleId = 1, txInputs = utxoRefs [2], txOutputs = utxoRefs [4]}

(recordingSink, getRecordedEvents) <- createRecordingSink

(node, getServerOutputs) <-
hydrate eventSource [eventSink] dryNode
hydrate eventSource [eventSink, recordingSink] dryNode
>>= notConnect
>>= primeWith [reqTx]
>>= recordServerOutputs
runToCompletion node

getServerOutputs >>= (`shouldContain` [TxValid{headId = testHeadId, transaction = tx1}])

-- Ensures that event ids are corecctly hydrate
events <- getRecordedEvents
getEventId <$> events `shouldSatisfy` isStrictlyMonotonic

it "emits a single ReqSn and AckSn as leader, even after multiple ReqTxs" $
showLogsOnFailure "NodeSpec" $ \tracer -> do
-- NOTE(SN): Sequence of parties in OnInitTx of
Expand Down
21 changes: 21 additions & 0 deletions hydra-node/test/Test/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,24 @@ propCollisionResistant name gen =
forAll gen $ \a ->
forAll gen $ \b ->
a /= b

-- | Predicate which decides whether given list is continuous.
isContinuous :: (Eq a, Enum a) => [a] -> Bool
isContinuous = \case
[] -> True
[_] -> True
(a : b : as) -> succ a == b && isContinuous (b : as)

-- | Predicate which decides whether given list is monotonic.
isMonotonic :: Ord a => [a] -> Bool
isMonotonic = \case
[] -> True
[_] -> True
(a : b : as) -> a <= b && isMonotonic (b : as)

-- | Predicate which decides whether given list is strictly monotonic.
isStrictlyMonotonic :: Ord a => [a] -> Bool
isStrictlyMonotonic = \case
[] -> True
[_] -> True
(a : b : as) -> a < b && isStrictlyMonotonic (b : as)

0 comments on commit fa7d970

Please sign in to comment.