Skip to content

Commit

Permalink
No orphan arbitrary in hydra-tx (#1596)
Browse files Browse the repository at this point in the history
Removes orphan `Arbitrary` instances of our own data types.

Different than functions, type class instances are not explicitly
imported and cannot be looked up easily through tooling. Hence, any
orphan instance is a pain to find if needed. We should try to not
introduce orphan instances we can easily avoid.
  • Loading branch information
ch1bo authored Sep 5, 2024
2 parents 0062556 + 3ab8034 commit 1a32aee
Show file tree
Hide file tree
Showing 19 changed files with 93 additions and 136 deletions.
2 changes: 1 addition & 1 deletion hydra-node/bench/tx-cost/TxCost.hs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ import Hydra.Ledger.Cardano.Evaluate (
)
import Hydra.Ledger.Cardano.Time (slotNoFromUTCTime)
import Hydra.Plutus.Orphans ()
import Hydra.Tx.Snapshot (genConfirmedSnapshot)
import PlutusLedgerApi.V2 (toBuiltinData)
import PlutusTx.Builtins (lengthOfByteString, serialiseData)
import Test.Hydra.Tx.Gen (genOutput, genUTxOAdaOnlyOfSize)
import Test.Hydra.Tx.Snapshot (genConfirmedSnapshot)
import Test.QuickCheck (generate)

computeInitCost :: IO [(NumParties, TxSize, MemUnit, CpuUnit, Coin)]
Expand Down
1 change: 0 additions & 1 deletion hydra-node/src/Hydra/Chain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import Hydra.Tx (
)
import Hydra.Tx.OnChainId (OnChainId)
import Test.Cardano.Ledger.Core.Arbitrary ()
import Test.Hydra.Tx ()
import Test.Hydra.Tx.Gen (ArbitraryIsTx)
import Test.QuickCheck.Instances.Semigroup ()
import Test.QuickCheck.Instances.Time ()
Expand Down
2 changes: 1 addition & 1 deletion hydra-node/src/Hydra/Chain/Direct/State.hs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import Hydra.Tx.Decrement (decrementTx)
import Hydra.Tx.Fanout (fanoutTx)
import Hydra.Tx.Init (initTx)
import Hydra.Tx.OnChainId (OnChainId)
import Hydra.Tx.Snapshot (genConfirmedSnapshot)
import Hydra.Tx.Utils (splitUTxO, verificationKeyToOnChainId)
import Test.Hydra.Tx.Gen (
genOneUTxOFor,
Expand All @@ -116,7 +117,6 @@ import Test.Hydra.Tx.Gen (
genUTxOAdaOnlyOfSize,
genVerificationKey,
)
import Test.Hydra.Tx.Snapshot (genConfirmedSnapshot)
import Test.QuickCheck (choose, frequency, oneof, suchThat, vector)
import Test.QuickCheck.Gen (elements)
import Test.QuickCheck.Modifiers (Positive (Positive))
Expand Down
1 change: 0 additions & 1 deletion hydra-node/src/Hydra/Chain/Direct/Tx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import Hydra.Tx.OnChainId (OnChainId (..))
import Hydra.Tx.Utils (assetNameToOnChainId, findFirst, hydraHeadV1AssetName, hydraMetadataLabel)
import PlutusLedgerApi.V2 (CurrencySymbol, fromBuiltin)
import PlutusLedgerApi.V2 qualified as Plutus
import Test.Hydra.Tx ()
import Test.Hydra.Tx.Gen ()
import Test.QuickCheck (vectorOf)

Expand Down
1 change: 0 additions & 1 deletion hydra-node/src/Hydra/HeadLogic/State.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import Hydra.Tx.Snapshot (
SnapshotNumber,
SnapshotVersion,
)
import Test.Hydra.Tx ()
import Test.Hydra.Tx.Gen (ArbitraryIsTx)

-- | The main state of the Hydra protocol state machine. It holds both, the
Expand Down
1 change: 0 additions & 1 deletion hydra-node/src/Hydra/Network/Message.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import Hydra.Tx (
UTxOType,
)
import Hydra.Tx.Crypto (Signature)
import Test.Hydra.Tx ()
import Test.Hydra.Tx.Gen (ArbitraryIsTx)

data NetworkEvent msg
Expand Down
3 changes: 0 additions & 3 deletions hydra-tx/hydra-tx.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,9 @@ library testlib
hs-source-dirs: testlib
visibility: public
exposed-modules:
Test.Hydra.Tx
Test.Hydra.Tx.Fixture
Test.Hydra.Tx.Gen
Test.Hydra.Tx.HeadId
Test.Hydra.Tx.Mutation
Test.Hydra.Tx.Snapshot

other-modules: Paths_hydra_tx
build-depends:
Expand Down
10 changes: 10 additions & 0 deletions hydra-tx/src/Hydra/Tx/HeadId.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ module Hydra.Tx.HeadId where

import Hydra.Prelude

import Data.ByteString qualified as BS
import Hydra.Cardano.Api (
HasTypeProxy (..),
PolicyId,
SerialiseAsRawBytes (..),
UsingRawBytesHex (..),
)
import PlutusLedgerApi.V2 (CurrencySymbol (..), toBuiltin)
import Test.QuickCheck (vectorOf)
import Test.QuickCheck.Instances.Semigroup ()
import Test.QuickCheck.Instances.Time ()

-- | Uniquely identifies a Hydra Head.
newtype HeadId = UnsafeHeadId ByteString
Expand All @@ -26,6 +30,9 @@ instance HasTypeProxy HeadId where
data AsType HeadId = AsHeadId
proxyToAsType _ = AsHeadId

instance Arbitrary HeadId where
arbitrary = UnsafeHeadId . BS.pack <$> vectorOf 16 arbitrary

-- | Unique seed to create a 'HeadId'
--
-- XXX: This might actually be the 'HeadId' to the protocol and users? Then the
Expand All @@ -44,6 +51,9 @@ instance HasTypeProxy HeadSeed where
data AsType HeadSeed = AsHeadSeed
proxyToAsType _ = AsHeadSeed

instance Arbitrary HeadSeed where
arbitrary = UnsafeHeadSeed . BS.pack <$> vectorOf 16 arbitrary

headIdToCurrencySymbol :: HeadId -> CurrencySymbol
headIdToCurrencySymbol (UnsafeHeadId headId) = CurrencySymbol (toBuiltin headId)

Expand Down
4 changes: 1 addition & 3 deletions hydra-tx/src/Hydra/Tx/Party.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Hydra.Tx.Crypto (AsType (AsHydraKey), HydraKey)
newtype Party = Party {vkey :: VerificationKey HydraKey}
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
deriving newtype (Arbitrary)

instance ToJSONKey Party where
toJSONKey = toJSONKeyText (serialiseToRawBytesHexText . vkey)
Expand All @@ -43,9 +44,6 @@ instance Ord Party where
Party{vkey = a} <= Party{vkey = b} =
verificationKeyHash a <= verificationKeyHash b

instance Arbitrary Party where
arbitrary = Party <$> arbitrary

instance FromCBOR Party where
fromCBOR = Party <$> fromCBOR

Expand Down
86 changes: 80 additions & 6 deletions hydra-tx/src/Hydra/Tx/Snapshot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ import Cardano.Crypto.Util (SignableRepresentation (..))
import Codec.Serialise (serialise)
import Data.Aeson (object, withObject, (.:), (.:?), (.=))
import Data.ByteString.Lazy qualified as LBS
import Hydra.Cardano.Api (SerialiseAsRawBytes (..))
import Hydra.Cardano.Api (SerialiseAsRawBytes (..), SigningKey)
import Hydra.Contract.HeadState qualified as Onchain
import Hydra.Tx.Crypto (MultiSignature)
import Hydra.Tx.Crypto (HydraKey, MultiSignature, aggregate, sign)
import Hydra.Tx.HeadId (HeadId)
import Hydra.Tx.IsTx (IsTx (..))
import PlutusLedgerApi.V2 (toBuiltin, toData)
import Test.QuickCheck (frequency, suchThat)
import Test.QuickCheck.Instances.Natural ()

-- * SnapshotNumber and SnapshotVersion

newtype SnapshotNumber
= UnsafeSnapshotNumber Natural
deriving stock (Eq, Ord, Generic)
deriving newtype (Show, ToJSON, FromJSON, ToCBOR, FromCBOR, Real, Num, Enum, Integral)
deriving newtype (Show, ToJSON, FromJSON, ToCBOR, FromCBOR, Real, Num, Enum, Integral, Arbitrary)

-- NOTE: On-chain scripts ensure snapshot number does not become negative.
fromChainSnapshotNumber :: Onchain.SnapshotNumber -> SnapshotNumber
Expand All @@ -30,13 +33,15 @@ fromChainSnapshotNumber =
newtype SnapshotVersion
= UnsafeSnapshotVersion Natural
deriving stock (Eq, Ord, Generic)
deriving newtype (Show, ToJSON, FromJSON, ToCBOR, FromCBOR, Real, Num, Enum, Integral)
deriving newtype (Show, ToJSON, FromJSON, ToCBOR, FromCBOR, Real, Num, Enum, Integral, Arbitrary)

-- NOTE: On-chain scripts ensure snapshot version does not become negative.
fromChainSnapshotVersion :: Onchain.SnapshotVersion -> SnapshotVersion
fromChainSnapshotVersion =
UnsafeSnapshotVersion . fromMaybe 0 . integerToNatural

-- * Snapshot

data Snapshot tx = Snapshot
{ headId :: HeadId
, version :: SnapshotVersion
Expand All @@ -54,6 +59,17 @@ data Snapshot tx = Snapshot
deriving stock instance IsTx tx => Eq (Snapshot tx)
deriving stock instance IsTx tx => Show (Snapshot tx)

-- | Binary representation of snapshot signatures. That is, concatenated CBOR for
-- 'headId', 'version', 'number', 'utxoHash' and 'utxoToDecommitHash' according
-- to CDDL schemata:
--
-- headId = bytes .size 16
-- version = uint
-- number = uint
-- utxoHash = bytes
-- utxoToDecommitHash = bytes
--
-- where hashes are the result of applying 'hashUTxO'.
instance forall tx. IsTx tx => SignableRepresentation (Snapshot tx) where
getSignableRepresentation Snapshot{headId, version, number, utxo, utxoToDecommit} =
LBS.toStrict $
Expand Down Expand Up @@ -106,6 +122,19 @@ instance (Typeable tx, FromCBOR (UTxOType tx), FromCBOR (TxIdType tx)) => FromCB
<*> fromCBOR
<*> fromCBOR

instance (Arbitrary (UTxOType tx), Arbitrary (TxIdType tx)) => Arbitrary (Snapshot tx) where
arbitrary = genericArbitrary

-- NOTE: See note on 'Arbitrary (ClientInput tx)'
shrink Snapshot{headId, version, number, utxo, confirmed, utxoToDecommit} =
[ Snapshot headId version number confirmed' utxo' utxoToDecommit'
| confirmed' <- shrink confirmed
, utxo' <- shrink utxo
, utxoToDecommit' <- shrink utxoToDecommit
]

-- * ConfirmedSnapshot

-- | A snapshot that can be used to close a head with. Either the initial one,
-- or when it was signed by all parties, i.e. it is confirmed.
data ConfirmedSnapshot tx
Expand All @@ -121,13 +150,13 @@ data ConfirmedSnapshot tx
deriving stock (Generic, Eq, Show)
deriving anyclass (ToJSON, FromJSON)

-- | Safely get a 'Snapshot' from a confirmed snapshot.
--
-- NOTE: While we could use 'snapshot' directly, this is a record-field accessor
-- which may become partial (and lead to unnoticed runtime errors) if we ever
-- add a new branch to the sumtype. So, we explicitely define a getter which
-- will force us into thinking about changing the signature properly if this
-- happens.

-- | Safely get a 'Snapshot' from a confirmed snapshot.
getSnapshot :: ConfirmedSnapshot tx -> Snapshot tx
getSnapshot = \case
InitialSnapshot{headId, initialUTxO} ->
Expand All @@ -147,3 +176,48 @@ isInitialSnapshot :: ConfirmedSnapshot tx -> Bool
isInitialSnapshot = \case
InitialSnapshot{} -> True
ConfirmedSnapshot{} -> False

instance (Arbitrary (UTxOType tx), Arbitrary (TxIdType tx), IsTx tx) => Arbitrary (ConfirmedSnapshot tx) where
arbitrary = do
ks <- arbitrary
utxo <- arbitrary
utxoToDecommit <- arbitrary
headId <- arbitrary
genConfirmedSnapshot headId 0 0 utxo utxoToDecommit ks

shrink = \case
InitialSnapshot hid sn -> [InitialSnapshot hid sn' | sn' <- shrink sn]
ConfirmedSnapshot sn sigs -> ConfirmedSnapshot <$> shrink sn <*> shrink sigs

genConfirmedSnapshot ::
IsTx tx =>
HeadId ->
-- | Exact snapshot version to generate.
SnapshotVersion ->
-- | The lower bound on snapshot number to generate.
-- If this is 0, then we can generate an `InitialSnapshot` or a `ConfirmedSnapshot`.
-- Otherwise we generate only `ConfirmedSnapshot` with a number strictly superior to
-- this lower bound.
SnapshotNumber ->
UTxOType tx ->
Maybe (UTxOType tx) ->
[SigningKey HydraKey] ->
Gen (ConfirmedSnapshot tx)
genConfirmedSnapshot headId version minSn utxo utxoToDecommit sks
| minSn > 0 = confirmedSnapshot
| otherwise =
frequency
[ (1, initialSnapshot)
, (9, confirmedSnapshot)
]
where
initialSnapshot =
InitialSnapshot <$> arbitrary <*> pure utxo

confirmedSnapshot = do
-- FIXME: This is another nail in the coffin to our current modeling of
-- snapshots
number <- arbitrary `suchThat` (> minSn)
let snapshot = Snapshot{headId, version, number, confirmed = [], utxo, utxoToDecommit}
let signatures = aggregate $ fmap (`sign` snapshot) sks
pure $ ConfirmedSnapshot{snapshot, signatures}
2 changes: 0 additions & 2 deletions hydra-tx/src/Hydra/Tx/Utils.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{-# OPTIONS_GHC -Wno-orphans #-}

module Hydra.Tx.Utils where

import Hydra.Cardano.Api
Expand Down
1 change: 0 additions & 1 deletion hydra-tx/test/Hydra/Tx/Contract/Close/CloseUnused.hs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import Test.Hydra.Tx.Mutation (
replaceSnapshotVersion,
replaceUTxOHash,
)
import Test.Hydra.Tx.Snapshot ()
import Test.QuickCheck (arbitrarySizedNatural, choose, elements, listOf1, oneof, resize, suchThat)
import Test.QuickCheck.Instances ()

Expand Down
2 changes: 0 additions & 2 deletions hydra-tx/test/Hydra/Tx/Contract/Close/CloseUsed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import Hydra.Tx.Contract.Close.Healthy (
somePartyCardanoVerificationKey,
)
import Hydra.Tx.Crypto (MultiSignature (..), toPlutusSignatures)
import Hydra.Tx.Snapshot ()
import PlutusLedgerApi.V1.Time (DiffMilliSeconds (..), fromMilliSeconds)
import PlutusLedgerApi.V2 (POSIXTime, PubKeyHash (PubKeyHash), toBuiltin)
import Test.Hydra.Tx.Fixture qualified as Fixture
Expand All @@ -71,7 +70,6 @@ import Test.Hydra.Tx.Mutation (
replaceSnapshotVersion,
replaceUTxOHash,
)
import Test.Hydra.Tx.Snapshot ()
import Test.QuickCheck (arbitrarySizedNatural, choose, elements, listOf1, oneof, suchThat)
import Test.QuickCheck.Instances ()

Expand Down
1 change: 0 additions & 1 deletion hydra-tx/test/Hydra/Tx/Contract/Contest/ContestCurrent.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import Hydra.Tx.Utils (
)
import PlutusLedgerApi.V2 (BuiltinByteString, toBuiltin)
import PlutusLedgerApi.V2 qualified as Plutus
import Test.Hydra.Tx ()
import Test.Hydra.Tx.Fixture (aliceSk, bobSk, carolSk, slotLength, systemStart, testNetworkId, testPolicyId)
import Test.Hydra.Tx.Fixture qualified as Fixture
import Test.Hydra.Tx.Gen (
Expand Down
1 change: 0 additions & 1 deletion hydra-tx/test/Hydra/Tx/Contract/Decrement.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import Hydra.Tx.Utils (adaOnly, splitUTxO)
import PlutusTx.Builtins (toBuiltin)
import Test.Hydra.Tx.Fixture (aliceSk, bobSk, carolSk, testNetworkId, testPolicyId)
import Test.Hydra.Tx.Gen (genForParty, genScriptRegistry, genUTxOSized, genValue, genVerificationKey)
import Test.Hydra.Tx.Snapshot ()
import Test.QuickCheck (arbitrarySizedNatural, choose, elements, oneof)
import Test.QuickCheck.Gen (suchThat)
import Test.QuickCheck.Instances ()
Expand Down
3 changes: 0 additions & 3 deletions hydra-tx/testlib/Test/Hydra/Tx.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
module Test.Hydra.Tx where

import Test.Hydra.Tx.HeadId ()
import Test.Hydra.Tx.Snapshot ()
1 change: 0 additions & 1 deletion hydra-tx/testlib/Test/Hydra/Tx/Fixture.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | Generic Cardano constants for use in testing.
module Test.Hydra.Tx.Fixture (
Expand Down
18 changes: 0 additions & 18 deletions hydra-tx/testlib/Test/Hydra/Tx/HeadId.hs

This file was deleted.

Loading

0 comments on commit 1a32aee

Please sign in to comment.