From 4e94ab381fca969287e1eb7ba126d81b10729448 Mon Sep 17 00:00:00 2001 From: Sasha Bogicevic Date: Thu, 16 Jul 2026 14:24:20 +0200 Subject: [PATCH 1/4] Add CBOR instances Signed-off-by: Sasha Bogicevic --- .../src/Hydra/Cardano/Api/AddressInEra.hs | 29 ++ .../src/Hydra/Cardano/Api/ChainPoint.hs | 23 ++ .../src/Hydra/Cardano/Api/NetworkId.hs | 14 + .../src/Hydra/Cardano/Api/PolicyAssets.hs | 24 ++ .../src/Hydra/Cardano/Api/PolicyId.hs | 14 + hydra-node/hydra-node.cabal | 5 + hydra-node/src/Hydra/API/ClientInput.hs | 28 ++ hydra-node/src/Hydra/API/ServerOutput.hs | 296 +++++++++++++++++- hydra-node/src/Hydra/CBOR/Orphans.hs | 49 +++ hydra-node/src/Hydra/Chain.hs | 232 ++++++++++++++ hydra-node/src/Hydra/Chain/Direct/State.hs | 7 + hydra-node/src/Hydra/HeadLogic/Error.hs | 97 ++++++ hydra-node/src/Hydra/HeadLogic/State.hs | 142 +++++++++ hydra-node/src/Hydra/Ledger.hs | 1 + hydra-node/src/Hydra/Ledger/Simple.hs | 2 +- hydra-node/src/Hydra/Network.hs | 36 ++- .../src/Hydra/Node/ApiTransactionTimeout.hs | 2 +- hydra-node/src/Hydra/Node/Environment.hs | 38 +++ hydra-node/src/Hydra/Node/State.hs | 64 ++++ hydra-node/src/Hydra/Node/UnsyncedPeriod.hs | 2 +- hydra-node/test/Hydra/CBORSpec.hs | 65 ++++ hydra-node/test/Main.hs | 2 + hydra-node/testlib/Test/Hydra/CBOR.hs | 29 ++ hydra-prelude/hydra-prelude.cabal | 1 + hydra-prelude/src/Hydra/Prelude.hs | 13 + hydra-tx/src/Hydra/Chain/ChainState.hs | 6 +- hydra-tx/src/Hydra/Tx/ContestationPeriod.hs | 2 +- hydra-tx/src/Hydra/Tx/Crypto.hs | 2 +- hydra-tx/src/Hydra/Tx/DepositPeriod.hs | 2 +- hydra-tx/src/Hydra/Tx/HeadId.hs | 2 + hydra-tx/src/Hydra/Tx/HeadParameters.hs | 7 + hydra-tx/src/Hydra/Tx/OnChainId.hs | 1 + hydra-tx/src/Hydra/Tx/Snapshot.hs | 38 +++ 33 files changed, 1266 insertions(+), 9 deletions(-) create mode 100644 hydra-node/src/Hydra/CBOR/Orphans.hs create mode 100644 hydra-node/test/Hydra/CBORSpec.hs create mode 100644 hydra-node/testlib/Test/Hydra/CBOR.hs diff --git a/hydra-cardano-api/src/Hydra/Cardano/Api/AddressInEra.hs b/hydra-cardano-api/src/Hydra/Cardano/Api/AddressInEra.hs index b73ce27756f..1b853e76783 100644 --- a/hydra-cardano-api/src/Hydra/Cardano/Api/AddressInEra.hs +++ b/hydra-cardano-api/src/Hydra/Cardano/Api/AddressInEra.hs @@ -1,7 +1,10 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + module Hydra.Cardano.Api.AddressInEra where import Hydra.Cardano.Api.Prelude +import Cardano.Api qualified as Api import Cardano.Ledger.Address qualified as Ledger import Cardano.Ledger.BaseTypes qualified as Ledger import Cardano.Ledger.Credential qualified as Ledger @@ -14,6 +17,32 @@ import PlutusLedgerApi.V3 ( ) import PlutusLedgerApi.V3 qualified as Plutus +-- * Orphans + +-- missing CBOR instances + +-- NOTE: Encoded as the bech32/base58 address text, consistent with the JSON +-- representation. +instance (IsShelleyBasedEra era, Typeable era) => ToCBOR (AddressInEra era) where + toCBOR = toCBOR . serialiseAddress + +instance (IsShelleyBasedEra era, Typeable era) => FromCBOR (AddressInEra era) where + fromCBOR = do + t <- fromCBOR + case deserialiseAddress (proxyToAsType $ Proxy @(AddressInEra era)) t of + Nothing -> fail $ "failed to deserialise AddressInEra from " <> show (t :: Text) + Just addr -> pure addr + +instance ToCBOR (Api.Address ByronAddr) where + toCBOR = toCBOR . serialiseToRawBytes + +instance FromCBOR (Api.Address ByronAddr) where + fromCBOR = do + bs <- fromCBOR + case deserialiseFromRawBytes (proxyToAsType $ Proxy @(Api.Address ByronAddr)) bs of + Left err -> fail (show err) + Right v -> pure v + -- * Extras -- | Construct a Shelley-style address from a verification key. This address has diff --git a/hydra-cardano-api/src/Hydra/Cardano/Api/ChainPoint.hs b/hydra-cardano-api/src/Hydra/Cardano/Api/ChainPoint.hs index b35cb736ad7..79bf74d6853 100644 --- a/hydra-cardano-api/src/Hydra/Cardano/Api/ChainPoint.hs +++ b/hydra-cardano-api/src/Hydra/Cardano/Api/ChainPoint.hs @@ -10,3 +10,26 @@ getChainPoint header = ChainPoint slotNo headerHash where (BlockHeader slotNo headerHash _) = header + +-- * Orphans + +-- missing CBOR instances + +instance ToCBOR ChainPoint where + toCBOR = \case + ChainPointAtGenesis -> + toCBOR ("ChainPointAtGenesis" :: Text) + ChainPoint slotNo headerHash -> + toCBOR ("ChainPoint" :: Text) <> toCBOR slotNo <> toCBOR (serialiseToRawBytes headerHash) + +instance FromCBOR ChainPoint where + fromCBOR = + fromCBOR >>= \case + ("ChainPointAtGenesis" :: Text) -> pure ChainPointAtGenesis + "ChainPoint" -> do + slotNo <- fromCBOR + bytes <- fromCBOR + case deserialiseFromRawBytes (proxyToAsType $ Proxy @(Hash BlockHeader)) bytes of + Left err -> fail (show err) + Right headerHash -> pure $ ChainPoint slotNo headerHash + tag -> fail $ show (tag :: Text) <> " is not a proper CBOR-encoded ChainPoint" diff --git a/hydra-cardano-api/src/Hydra/Cardano/Api/NetworkId.hs b/hydra-cardano-api/src/Hydra/Cardano/Api/NetworkId.hs index b9ebafd3d87..9596cf23a27 100644 --- a/hydra-cardano-api/src/Hydra/Cardano/Api/NetworkId.hs +++ b/hydra-cardano-api/src/Hydra/Cardano/Api/NetworkId.hs @@ -25,3 +25,17 @@ instance FromJSON NetworkId where "Mainnet" -> pure Mainnet "Testnet" -> Testnet <$> o .: "magic" _ -> fail "Expected tag to be Mainnet | Testnet" + +-- missing CBOR instances + +instance ToCBOR NetworkId where + toCBOR = \case + Mainnet -> toCBOR ("Mainnet" :: Text) + Testnet (NetworkMagic magic) -> toCBOR ("Testnet" :: Text) <> toCBOR magic + +instance FromCBOR NetworkId where + fromCBOR = + fromCBOR >>= \case + ("Mainnet" :: Text) -> pure Mainnet + "Testnet" -> Testnet . NetworkMagic <$> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded NetworkId" diff --git a/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyAssets.hs b/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyAssets.hs index 2fae022e449..69d406c21db 100644 --- a/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyAssets.hs +++ b/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyAssets.hs @@ -11,3 +11,27 @@ instance ToJSON PolicyAssets where instance FromJSON PolicyAssets where parseJSON v = PolicyAssets <$> parseJSON v + +-- missing CBOR instances + +instance ToCBOR AssetName where + toCBOR = toCBOR . serialiseToRawBytes + +instance FromCBOR AssetName where + fromCBOR = do + bs <- fromCBOR + case deserialiseFromRawBytes AsAssetName bs of + Left err -> fail (show err) + Right v -> pure v + +instance ToCBOR Quantity where + toCBOR (Quantity q) = toCBOR q + +instance FromCBOR Quantity where + fromCBOR = Quantity <$> fromCBOR + +instance ToCBOR PolicyAssets where + toCBOR (PolicyAssets assets) = toCBOR assets + +instance FromCBOR PolicyAssets where + fromCBOR = PolicyAssets <$> fromCBOR diff --git a/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyId.hs b/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyId.hs index 1a56d40df3d..6501cf33b49 100644 --- a/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyId.hs +++ b/hydra-cardano-api/src/Hydra/Cardano/Api/PolicyId.hs @@ -9,6 +9,20 @@ import Cardano.Ledger.Mary.Value qualified as Ledger import Hydra.Cardano.Api.ScriptHash () import PlutusLedgerApi.V3 (CurrencySymbol, fromBuiltin, unCurrencySymbol) +-- * Orphans + +-- missing CBOR instances + +instance ToCBOR PolicyId where + toCBOR = toCBOR . serialiseToRawBytes + +instance FromCBOR PolicyId where + fromCBOR = do + bs <- fromCBOR + case deserialiseFromRawBytes AsPolicyId bs of + Left err -> fail (show err) + Right v -> pure v + -- * Type conversions -- | Convert Cardano api 'PolicyId' to Cardano ledger `PolicyID`. diff --git a/hydra-node/hydra-node.cabal b/hydra-node/hydra-node.cabal index 7aed86aeecf..28d7d1d0343 100644 --- a/hydra-node/hydra-node.cabal +++ b/hydra-node/hydra-node.cabal @@ -56,6 +56,7 @@ library Hydra.API.ServerOutput Hydra.API.ServerOutputFilter Hydra.API.WSServer + Hydra.CBOR.Orphans Hydra.Chain Hydra.Chain.Backend Hydra.Chain.Blockfrost @@ -127,6 +128,7 @@ library , cardano-slotting , cardano-strict-containers , cborg + , cborg-json , conduit , containers , contra-tracer @@ -190,6 +192,7 @@ library testlib Test.Hydra.API.ClientInput Test.Hydra.API.HTTPServer Test.Hydra.API.ServerOutput + Test.Hydra.CBOR Test.Hydra.Chain Test.Hydra.Chain.Direct.State Test.Hydra.Chain.Direct.TimeHandle @@ -223,6 +226,7 @@ library testlib , cardano-ledger-core , cardano-ledger-core:testlib , cardano-slotting + , cborg , containers , directory , filepath @@ -380,6 +384,7 @@ test-suite tests Hydra.API.ServerOutputSpec Hydra.API.ServerSpec Hydra.BehaviorSpec + Hydra.CBORSpec Hydra.Chain.BlockfrostSpec Hydra.Chain.Direct.HandlersSpec Hydra.Chain.Direct.ScriptRegistrySpec diff --git a/hydra-node/src/Hydra/API/ClientInput.hs b/hydra-node/src/Hydra/API/ClientInput.hs index e8e8886895e..7f69e952580 100644 --- a/hydra-node/src/Hydra/API/ClientInput.hs +++ b/hydra-node/src/Hydra/API/ClientInput.hs @@ -27,3 +27,31 @@ deriving stock instance IsTx tx => Eq (ClientInput tx) deriving stock instance IsTx tx => Show (ClientInput tx) deriving anyclass instance IsTx tx => ToJSON (ClientInput tx) deriving anyclass instance IsTx tx => FromJSON (ClientInput tx) + +instance IsTx tx => ToCBOR (ClientInput tx) where + toCBOR = \case + Init -> toCBOR ("Init" :: Text) + NewTx{transaction} -> toCBOR ("NewTx" :: Text) <> toCBOR transaction + Recover{recoverTxId} -> toCBOR ("Recover" :: Text) <> toCBOR recoverTxId + Decommit{decommitTx} -> toCBOR ("Decommit" :: Text) <> toCBOR decommitTx + Close -> toCBOR ("Close" :: Text) + SafeClose -> toCBOR ("SafeClose" :: Text) + Contest -> toCBOR ("Contest" :: Text) + Fanout -> toCBOR ("Fanout" :: Text) + PartialFanout{utxoToFanout} -> toCBOR ("PartialFanout" :: Text) <> toCBOR utxoToFanout + SideLoadSnapshot{snapshot} -> toCBOR ("SideLoadSnapshot" :: Text) <> toCBOR snapshot + +instance IsTx tx => FromCBOR (ClientInput tx) where + fromCBOR = + fromCBOR >>= \case + ("Init" :: Text) -> pure Init + "NewTx" -> NewTx <$> fromCBOR + "Recover" -> Recover <$> fromCBOR + "Decommit" -> Decommit <$> fromCBOR + "Close" -> pure Close + "SafeClose" -> pure SafeClose + "Contest" -> pure Contest + "Fanout" -> pure Fanout + "PartialFanout" -> PartialFanout <$> fromCBOR + "SideLoadSnapshot" -> SideLoadSnapshot <$> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded ClientInput" diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index 1b3fcd16dc8..23551c63392 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -4,6 +4,7 @@ module Hydra.API.ServerOutput where +import Cardano.Binary (Decoder) import Control.Lens ((.~)) import Data.Aeson (Value (..), defaultOptions, encode, genericParseJSON, genericToJSON, omitNothingFields, tagSingleConstructors, withObject, (.:)) import Data.Aeson.KeyMap qualified as KeyMap @@ -13,7 +14,7 @@ import Hydra.API.ClientInput (ClientInput) import Hydra.Chain (PostChainTx, PostTxError) import Hydra.Chain.ChainState (ChainSlot, IsChainState) import Hydra.HeadLogic.Error (SideLoadRequirementFailure) -import Hydra.HeadLogic.State (ClosedState (..), FanoutMode (..), HeadState (..), OpenState (..), PartialFanoutState (..), SeenSnapshot (..)) +import Hydra.HeadLogic.State (ClosedState (..), FanoutMode (..), HeadState, OpenState (..), PartialFanoutState (..), SeenSnapshot (..)) import Hydra.HeadLogic.State qualified as HeadState import Hydra.Ledger (ValidationError) import Hydra.Network (Host, ProtocolVersion) @@ -48,6 +49,29 @@ instance IsChainState tx => FromJSON (TimedServerOutput tx) where parseJSON v = flip (withObject "TimedServerOutput") v $ \o -> TimedServerOutput <$> parseJSON v <*> o .: "seq" <*> o .: "timestamp" +-- NOTE: Unlike the JSON instance, which merges 'seq' and 'timestamp' into the +-- inner 'ServerOutput' object, the CBOR encoding is a plain tagged envelope. +-- The tag makes any server-sent message start with a unique text token, so +-- decoders can dispatch on it. +instance IsChainState tx => ToCBOR (TimedServerOutput tx) where + toCBOR TimedServerOutput{output, seq, time} = + toCBOR ("TimedServerOutput" :: Text) <> toCBOR seq <> toCBOR time <> toCBOR output + +instance IsChainState tx => FromCBOR (TimedServerOutput tx) where + fromCBOR = + fromCBOR >>= \case + ("TimedServerOutput" :: Text) -> decodeTimedServerOutputBody + tag -> fail $ show tag <> " is not a proper CBOR-encoded TimedServerOutput" + +-- | Decode a 'TimedServerOutput' after its @TimedServerOutput@ tag has already +-- been consumed (used for tag-based dispatch). +decodeTimedServerOutputBody :: IsChainState tx => Decoder s (TimedServerOutput tx) +decodeTimedServerOutputBody = do + seq <- fromCBOR + time <- fromCBOR + output <- fromCBOR + pure TimedServerOutput{output, seq, time} + data DecommitInvalidReason tx = DecommitTxInvalid {localUTxO :: UTxOType tx, validationError :: ValidationError} | DecommitAlreadyInFlight {otherDecommitTxId :: TxIdType tx} @@ -62,6 +86,20 @@ instance (ToJSON (TxIdType tx), ToJSON (UTxOType tx)) => ToJSON (DecommitInvalid instance (FromJSON (TxIdType tx), FromJSON (UTxOType tx)) => FromJSON (DecommitInvalidReason tx) where parseJSON = genericParseJSON defaultOptions +instance IsTx tx => ToCBOR (DecommitInvalidReason tx) where + toCBOR = \case + DecommitTxInvalid{localUTxO, validationError} -> + toCBOR ("DecommitTxInvalid" :: Text) <> toCBOR localUTxO <> toCBOR validationError + DecommitAlreadyInFlight{otherDecommitTxId} -> + toCBOR ("DecommitAlreadyInFlight" :: Text) <> toCBOR otherDecommitTxId + +instance IsTx tx => FromCBOR (DecommitInvalidReason tx) where + fromCBOR = + fromCBOR >>= \case + ("DecommitTxInvalid" :: Text) -> DecommitTxInvalid <$> fromCBOR <*> fromCBOR + "DecommitAlreadyInFlight" -> DecommitAlreadyInFlight <$> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded DecommitInvalidReason" + -- | Individual messages as produced by the 'Hydra.HeadLogic' in -- the 'ClientEffect'. data ClientMessage tx @@ -85,6 +123,30 @@ instance IsChainState tx => FromJSON (ClientMessage tx) where { omitNothingFields = True } +instance IsChainState tx => ToCBOR (ClientMessage tx) where + toCBOR = \case + CommandFailed{clientInput, state = headState} -> + toCBOR ("CommandFailed" :: Text) <> toCBOR clientInput <> toCBOR headState + PostTxOnChainFailed{postChainTx, postTxError} -> + toCBOR ("PostTxOnChainFailed" :: Text) <> toCBOR postChainTx <> toCBOR postTxError + RejectedInputBecauseUnsynced{clientInput, drift} -> + toCBOR ("RejectedInputBecauseUnsynced" :: Text) <> toCBOR clientInput <> toCBOR drift + SideLoadSnapshotRejected{clientInput, requirementFailure} -> + toCBOR ("SideLoadSnapshotRejected" :: Text) <> toCBOR clientInput <> toCBOR requirementFailure + +instance IsChainState tx => FromCBOR (ClientMessage tx) where + fromCBOR = fromCBOR >>= decodeClientMessageBody + +-- | Decode a 'ClientMessage' given its already-decoded constructor tag (used +-- for tag-based dispatch). +decodeClientMessageBody :: IsChainState tx => Text -> Decoder s (ClientMessage tx) +decodeClientMessageBody = \case + "CommandFailed" -> CommandFailed <$> fromCBOR <*> fromCBOR + "PostTxOnChainFailed" -> PostTxOnChainFailed <$> fromCBOR <*> fromCBOR + "RejectedInputBecauseUnsynced" -> RejectedInputBecauseUnsynced <$> fromCBOR <*> fromCBOR + "SideLoadSnapshotRejected" -> SideLoadSnapshotRejected <$> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded ClientMessage" + -- | A friendly welcome message which tells a client something about the -- node. Currently used for knowing what signing key the server uses (it -- only knows one), 'HeadStatus' and optionally (if 'HeadIsOpen' or @@ -121,6 +183,40 @@ instance IsChainState tx => FromJSON (Greetings tx) where , tagSingleConstructors = True } +instance IsChainState tx => ToCBOR (Greetings tx) where + toCBOR Greetings{me, headStatus, hydraHeadId, snapshotUtxo, hydraNodeVersion, env, networkInfo, chainSyncedStatus, currentSlot} = + toCBOR ("Greetings" :: Text) + <> toCBOR me + <> toCBOR headStatus + <> toCBOR hydraHeadId + <> toCBOR snapshotUtxo + <> toCBOR (toText hydraNodeVersion) + <> toCBOR env + <> toCBOR networkInfo + <> toCBOR chainSyncedStatus + <> toCBOR currentSlot + +instance IsChainState tx => FromCBOR (Greetings tx) where + fromCBOR = + fromCBOR >>= \case + ("Greetings" :: Text) -> decodeGreetingsBody + tag -> fail $ show tag <> " is not a proper CBOR-encoded Greetings" + +-- | Decode a 'Greetings' after its @Greetings@ tag has already been consumed +-- (used for tag-based dispatch). +decodeGreetingsBody :: IsChainState tx => Decoder s (Greetings tx) +decodeGreetingsBody = do + me <- fromCBOR + headStatus <- fromCBOR + hydraHeadId <- fromCBOR + snapshotUtxo <- fromCBOR + hydraNodeVersion <- toString <$> fromCBOR @Text + env <- fromCBOR + networkInfo <- fromCBOR + chainSyncedStatus <- fromCBOR + currentSlot <- fromCBOR + pure Greetings{me, headStatus, hydraHeadId, snapshotUtxo, hydraNodeVersion, env, networkInfo, chainSyncedStatus, currentSlot} + data InvalidInput = InvalidInput { reason :: String , input :: Text @@ -130,6 +226,24 @@ data InvalidInput = InvalidInput deriving anyclass instance ToJSON InvalidInput deriving anyclass instance FromJSON InvalidInput +instance ToCBOR InvalidInput where + toCBOR InvalidInput{reason, input} = + toCBOR ("InvalidInput" :: Text) <> toCBOR (toText reason) <> toCBOR input + +instance FromCBOR InvalidInput where + fromCBOR = + fromCBOR >>= \case + ("InvalidInput" :: Text) -> decodeInvalidInputBody + tag -> fail $ show tag <> " is not a proper CBOR-encoded InvalidInput" + +-- | Decode an 'InvalidInput' after its @InvalidInput@ tag has already been +-- consumed (used for tag-based dispatch). +decodeInvalidInputBody :: Decoder s InvalidInput +decodeInvalidInputBody = do + reason <- toString <$> fromCBOR @Text + input <- fromCBOR + pure InvalidInput{reason, input} + data ServerOutput tx = NetworkConnected | NetworkDisconnected @@ -219,6 +333,149 @@ deriving stock instance IsChainState tx => Show (ServerOutput tx) deriving anyclass instance IsChainState tx => FromJSON (ServerOutput tx) deriving anyclass instance IsChainState tx => ToJSON (ServerOutput tx) +instance IsChainState tx => ToCBOR (ServerOutput tx) where + toCBOR = \case + NetworkConnected -> + toCBOR ("NetworkConnected" :: Text) + NetworkDisconnected -> + toCBOR ("NetworkDisconnected" :: Text) + NetworkVersionMismatch{ourVersion, theirVersion} -> + toCBOR ("NetworkVersionMismatch" :: Text) <> toCBOR ourVersion <> toCBOR theirVersion + NetworkClusterIDMismatch{clusterPeers, misconfiguredPeers} -> + toCBOR ("NetworkClusterIDMismatch" :: Text) <> toCBOR clusterPeers <> toCBOR misconfiguredPeers + PeerConnected{peer} -> + toCBOR ("PeerConnected" :: Text) <> toCBOR peer + PeerDisconnected{peer} -> + toCBOR ("PeerDisconnected" :: Text) <> toCBOR peer + HeadIsOpen{headId, parties} -> + toCBOR ("HeadIsOpen" :: Text) <> toCBOR headId <> toCBOR parties + HeadIsClosed{headId, snapshotNumber, contestationDeadline} -> + toCBOR ("HeadIsClosed" :: Text) + <> toCBOR headId + <> toCBOR snapshotNumber + <> toCBOR contestationDeadline + HeadIsContested{headId, snapshotNumber, contestationDeadline} -> + toCBOR ("HeadIsContested" :: Text) + <> toCBOR headId + <> toCBOR snapshotNumber + <> toCBOR contestationDeadline + ReadyToFanout{headId} -> + toCBOR ("ReadyToFanout" :: Text) <> toCBOR headId + HeadPartiallyFannedOut{headId, distributedUTxO, remainingUTxO, fanoutMode} -> + toCBOR ("HeadPartiallyFannedOut" :: Text) + <> toCBOR headId + <> toCBOR distributedUTxO + <> toCBOR remainingUTxO + <> toCBOR fanoutMode + HeadIsFinalized{headId, finalizedUTxO} -> + toCBOR ("HeadIsFinalized" :: Text) <> toCBOR headId <> toCBOR finalizedUTxO + TxValid{headId, transactionId} -> + toCBOR ("TxValid" :: Text) <> toCBOR headId <> toCBOR transactionId + TxInvalid{headId, utxo, transaction, validationError} -> + toCBOR ("TxInvalid" :: Text) + <> toCBOR headId + <> toCBOR utxo + <> toCBOR transaction + <> toCBOR validationError + SnapshotConfirmed{headId, snapshot, signatures} -> + toCBOR ("SnapshotConfirmed" :: Text) + <> toCBOR headId + <> toCBOR snapshot + <> toCBOR signatures + IgnoredHeadInitializing{headId, contestationPeriod, parties, participants} -> + toCBOR ("IgnoredHeadInitializing" :: Text) + <> toCBOR headId + <> toCBOR contestationPeriod + <> toCBOR parties + <> toCBOR participants + DecommitRequested{headId, decommitTx, utxoToDecommit} -> + toCBOR ("DecommitRequested" :: Text) + <> toCBOR headId + <> toCBOR decommitTx + <> toCBOR utxoToDecommit + DecommitInvalid{headId, decommitTx, decommitInvalidReason} -> + toCBOR ("DecommitInvalid" :: Text) + <> toCBOR headId + <> toCBOR decommitTx + <> toCBOR decommitInvalidReason + DecommitApproved{headId, decommitTxId, utxoToDecommit} -> + toCBOR ("DecommitApproved" :: Text) + <> toCBOR headId + <> toCBOR decommitTxId + <> toCBOR utxoToDecommit + DecommitFinalized{headId, distributedUTxO} -> + toCBOR ("DecommitFinalized" :: Text) <> toCBOR headId <> toCBOR distributedUTxO + CommitRecorded{headId, utxoToCommit, pendingDeposit, deadline} -> + toCBOR ("CommitRecorded" :: Text) + <> toCBOR headId + <> toCBOR utxoToCommit + <> toCBOR pendingDeposit + <> toCBOR deadline + DepositActivated{headId, depositTxId, deadline, chainTime} -> + toCBOR ("DepositActivated" :: Text) + <> toCBOR headId + <> toCBOR depositTxId + <> toCBOR deadline + <> toCBOR chainTime + DepositExpired{headId, depositTxId, deadline, chainTime} -> + toCBOR ("DepositExpired" :: Text) + <> toCBOR headId + <> toCBOR depositTxId + <> toCBOR deadline + <> toCBOR chainTime + CommitApproved{headId, utxoToCommit} -> + toCBOR ("CommitApproved" :: Text) <> toCBOR headId <> toCBOR utxoToCommit + CommitFinalized{headId, depositTxId} -> + toCBOR ("CommitFinalized" :: Text) <> toCBOR headId <> toCBOR depositTxId + CommitRecovered{headId, recoveredUTxO, recoveredTxId} -> + toCBOR ("CommitRecovered" :: Text) + <> toCBOR headId + <> toCBOR recoveredUTxO + <> toCBOR recoveredTxId + SnapshotSideLoaded{headId, snapshotNumber} -> + toCBOR ("SnapshotSideLoaded" :: Text) <> toCBOR headId <> toCBOR snapshotNumber + EventLogRotated{checkpoint} -> + toCBOR ("EventLogRotated" :: Text) <> toCBOR checkpoint + NodeUnsynced{chainSlot, chainTime, drift} -> + toCBOR ("NodeUnsynced" :: Text) <> toCBOR chainSlot <> toCBOR chainTime <> toCBOR drift + NodeSynced{chainSlot, chainTime, drift} -> + toCBOR ("NodeSynced" :: Text) <> toCBOR chainSlot <> toCBOR chainTime <> toCBOR drift + +instance IsChainState tx => FromCBOR (ServerOutput tx) where + fromCBOR = + fromCBOR >>= \case + ("NetworkConnected" :: Text) -> pure NetworkConnected + "NetworkDisconnected" -> pure NetworkDisconnected + "NetworkVersionMismatch" -> NetworkVersionMismatch <$> fromCBOR <*> fromCBOR + "NetworkClusterIDMismatch" -> NetworkClusterIDMismatch <$> fromCBOR <*> fromCBOR + "PeerConnected" -> PeerConnected <$> fromCBOR + "PeerDisconnected" -> PeerDisconnected <$> fromCBOR + "HeadIsOpen" -> HeadIsOpen <$> fromCBOR <*> fromCBOR + "HeadIsClosed" -> HeadIsClosed <$> fromCBOR <*> fromCBOR <*> fromCBOR + "HeadIsContested" -> HeadIsContested <$> fromCBOR <*> fromCBOR <*> fromCBOR + "ReadyToFanout" -> ReadyToFanout <$> fromCBOR + "HeadPartiallyFannedOut" -> HeadPartiallyFannedOut <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "HeadIsFinalized" -> HeadIsFinalized <$> fromCBOR <*> fromCBOR + "TxValid" -> TxValid <$> fromCBOR <*> fromCBOR + "TxInvalid" -> TxInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "SnapshotConfirmed" -> SnapshotConfirmed <$> fromCBOR <*> fromCBOR <*> fromCBOR + "IgnoredHeadInitializing" -> IgnoredHeadInitializing <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "DecommitRequested" -> DecommitRequested <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DecommitInvalid" -> DecommitInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DecommitApproved" -> DecommitApproved <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DecommitFinalized" -> DecommitFinalized <$> fromCBOR <*> fromCBOR + "CommitRecorded" -> CommitRecorded <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "DepositActivated" -> DepositActivated <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "DepositExpired" -> DepositExpired <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "CommitApproved" -> CommitApproved <$> fromCBOR <*> fromCBOR + "CommitFinalized" -> CommitFinalized <$> fromCBOR <*> fromCBOR + "CommitRecovered" -> CommitRecovered <$> fromCBOR <*> fromCBOR <*> fromCBOR + "SnapshotSideLoaded" -> SnapshotSideLoaded <$> fromCBOR <*> fromCBOR + "EventLogRotated" -> EventLogRotated <$> fromCBOR + "NodeUnsynced" -> NodeUnsynced <$> fromCBOR <*> fromCBOR <*> fromCBOR + "NodeSynced" -> NodeSynced <$> fromCBOR <*> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded ServerOutput" + -- | Whether or not to include full UTxO in server outputs. data WithUTxO = WithUTxO | WithoutUTxO deriving stock (Eq, Show) @@ -303,6 +560,24 @@ data HeadStatus deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR HeadStatus where + toCBOR = \case + Idle -> toCBOR ("Idle" :: Text) + Open -> toCBOR ("Open" :: Text) + Closed -> toCBOR ("Closed" :: Text) + FanoutPossible -> toCBOR ("FanoutPossible" :: Text) + FanningOut -> toCBOR ("FanningOut" :: Text) + +instance FromCBOR HeadStatus where + fromCBOR = + fromCBOR >>= \case + ("Idle" :: Text) -> pure Idle + "Open" -> pure Open + "Closed" -> pure Closed + "FanoutPossible" -> pure FanoutPossible + "FanningOut" -> pure FanningOut + tag -> fail $ show tag <> " is not a proper CBOR-encoded HeadStatus" + -- | Client-facing projection of the node's fanout 'FanoutMode': whether a -- fanning-out head will continue draining on its own or is waiting for the -- client to choose the next 'PartialFanout'. Surfacing this lets clients render @@ -326,6 +601,18 @@ fanoutProgressMode = \case DistributingSelection{} -> AutoFanningOut AwaitingSelection -> AwaitingFanoutSelection +instance ToCBOR FanoutProgressMode where + toCBOR = \case + AutoFanningOut -> toCBOR ("AutoFanningOut" :: Text) + AwaitingFanoutSelection -> toCBOR ("AwaitingFanoutSelection" :: Text) + +instance FromCBOR FanoutProgressMode where + fromCBOR = + fromCBOR >>= \case + ("AutoFanningOut" :: Text) -> pure AutoFanningOut + "AwaitingFanoutSelection" -> pure AwaitingFanoutSelection + tag -> fail $ show tag <> " is not a proper CBOR-encoded FanoutProgressMode" + -- | All information needed to distinguish behavior of the commit endpoint. data CommitInfo = CannotCommit @@ -339,6 +626,13 @@ data NetworkInfo = NetworkInfo deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR NetworkInfo where + toCBOR NetworkInfo{networkConnected, peersInfo} = + toCBOR networkConnected <> toCBOR peersInfo + +instance FromCBOR NetworkInfo where + fromCBOR = NetworkInfo <$> fromCBOR <*> fromCBOR + -- | Get latest confirmed snapshot UTxO from 'HeadState'. getSnapshotUtxo :: IsTx tx => HeadState tx -> Maybe (UTxOType tx) getSnapshotUtxo = \case diff --git a/hydra-node/src/Hydra/CBOR/Orphans.hs b/hydra-node/src/Hydra/CBOR/Orphans.hs new file mode 100644 index 00000000000..c4ff496b504 --- /dev/null +++ b/hydra-node/src/Hydra/CBOR/Orphans.hs @@ -0,0 +1,49 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | Orphan 'ToCBOR' / 'FromCBOR' instances for third-party types that appear +-- in CBOR-encoded hydra-node data (logs and API messages). +module Hydra.CBOR.Orphans () where + +import Hydra.Prelude + +import Codec.CBOR.JSON (decodeValue, encodeValue) +import Data.Aeson qualified as Aeson +import Data.IP (IP) +import Hydra.Cardano.Api (File (..)) +import Network.Socket (PortNumber) + +-- | 'Aeson.Value' is CBOR-encoded using the standard JSON-in-CBOR mapping +-- from cborg-json. This is used for the few log types which carry raw JSON +-- payloads (e.g. 'Hydra.API.APIServerLog.APIServerLog'). +-- +-- NOTE: Non-integral JSON numbers round-trip through 'Double', which may lose +-- precision; integers are exact. +instance ToCBOR Aeson.Value where + toCBOR = encodeValue + +instance FromCBOR Aeson.Value where + fromCBOR = decodeValue False + +-- | Encoded as its textual representation (e.g. @127.0.0.1@). +instance ToCBOR IP where + toCBOR = toCBOR . (show :: IP -> Text) + +instance FromCBOR IP where + fromCBOR = do + t :: Text <- fromCBOR + case readMaybe (toString t) of + Nothing -> fail $ "failed to parse IP address from " <> show t + Just ip -> pure ip + +instance ToCBOR PortNumber where + toCBOR = toCBOR . (fromIntegral :: PortNumber -> Word16) + +instance FromCBOR PortNumber where + fromCBOR = (fromIntegral :: Word16 -> PortNumber) <$> fromCBOR + +-- | Encoded as the file path text (e.g. for 'Hydra.Cardano.Api.SocketPath'). +instance (Typeable content, Typeable direction) => ToCBOR (File content direction) where + toCBOR (File fp) = toCBOR (toText fp) + +instance (Typeable content, Typeable direction) => FromCBOR (File content direction) where + fromCBOR = File . toString <$> fromCBOR @Text diff --git a/hydra-node/src/Hydra/Chain.hs b/hydra-node/src/Hydra/Chain.hs index fe84cc4012d..532a724f7ab 100644 --- a/hydra-node/src/Hydra/Chain.hs +++ b/hydra-node/src/Hydra/Chain.hs @@ -113,6 +113,76 @@ deriving stock instance IsTx tx => Show (PostChainTx tx) deriving anyclass instance IsTx tx => ToJSON (PostChainTx tx) deriving anyclass instance IsTx tx => FromJSON (PostChainTx tx) +instance IsTx tx => ToCBOR (PostChainTx tx) where + toCBOR = \case + InitTx{participants, headParameters} -> + toCBOR ("InitTx" :: Text) <> toCBOR participants <> toCBOR headParameters + IncrementTx{headSeed, headId, headParameters, incrementingSnapshot, depositTxId} -> + toCBOR ("IncrementTx" :: Text) + <> toCBOR headSeed + <> toCBOR headId + <> toCBOR headParameters + <> toCBOR incrementingSnapshot + <> toCBOR depositTxId + RecoverTx{headId, recoverTxId, deadline, recoverUTxO} -> + toCBOR ("RecoverTx" :: Text) + <> toCBOR headId + <> toCBOR recoverTxId + <> toCBOR deadline + <> toCBOR recoverUTxO + DecrementTx{headSeed, headId, headParameters, decrementingSnapshot} -> + toCBOR ("DecrementTx" :: Text) + <> toCBOR headSeed + <> toCBOR headId + <> toCBOR headParameters + <> toCBOR decrementingSnapshot + CloseTx{headId, headParameters, openVersion, closingSnapshot} -> + toCBOR ("CloseTx" :: Text) + <> toCBOR headId + <> toCBOR headParameters + <> toCBOR openVersion + <> toCBOR closingSnapshot + ContestTx{headId, headParameters, openVersion, contestingSnapshot} -> + toCBOR ("ContestTx" :: Text) + <> toCBOR headId + <> toCBOR headParameters + <> toCBOR openVersion + <> toCBOR contestingSnapshot + FanoutTx{utxo, utxoToCommit, utxoToDecommit, utxoForProof, headSeed, contestationDeadline} -> + toCBOR ("FanoutTx" :: Text) + <> toCBOR utxo + <> toCBOR utxoToCommit + <> toCBOR utxoToDecommit + <> toCBOR utxoForProof + <> toCBOR headSeed + <> toCBOR contestationDeadline + PartialFanoutTx{utxoToDistribute, utxoForProof, headSeed, contestationDeadline} -> + toCBOR ("PartialFanoutTx" :: Text) + <> toCBOR utxoToDistribute + <> toCBOR utxoForProof + <> toCBOR headSeed + <> toCBOR contestationDeadline + FinalPartialFanoutTx{utxoToDistribute, presettledUTxO, headSeed, contestationDeadline} -> + toCBOR ("FinalPartialFanoutTx" :: Text) + <> toCBOR utxoToDistribute + <> toCBOR presettledUTxO + <> toCBOR headSeed + <> toCBOR contestationDeadline + +instance IsTx tx => FromCBOR (PostChainTx tx) where + fromCBOR = + fromCBOR >>= \case + ("InitTx" :: Text) -> InitTx <$> fromCBOR <*> fromCBOR + "IncrementTx" -> IncrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "RecoverTx" -> RecoverTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "DecrementTx" -> DecrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "CloseTx" -> CloseTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "ContestTx" -> ContestTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "FanoutTx" -> FanoutTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "PartialFanoutTx" -> PartialFanoutTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "FinalPartialFanoutTx" -> FinalPartialFanoutTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded PostChainTx" + -- | Describes transactions as seen on chain. Holds as minimal information as -- possible to simplify observing the chain. data OnChainTx tx @@ -163,6 +233,65 @@ deriving stock instance IsTx tx => Show (OnChainTx tx) deriving anyclass instance IsTx tx => ToJSON (OnChainTx tx) deriving anyclass instance IsTx tx => FromJSON (OnChainTx tx) +instance IsTx tx => ToCBOR (OnChainTx tx) where + toCBOR = \case + OnInitTx{headId, headSeed, headParameters, participants} -> + toCBOR ("OnInitTx" :: Text) + <> toCBOR headId + <> toCBOR headSeed + <> toCBOR headParameters + <> toCBOR participants + OnDepositTx{headId, depositTxId, deposited, created, deadline} -> + toCBOR ("OnDepositTx" :: Text) + <> toCBOR headId + <> toCBOR depositTxId + <> toCBOR deposited + <> toCBOR created + <> toCBOR deadline + OnRecoverTx{headId, recoveredTxId, recoveredUTxO} -> + toCBOR ("OnRecoverTx" :: Text) + <> toCBOR headId + <> toCBOR recoveredTxId + <> toCBOR recoveredUTxO + OnIncrementTx{headId, newVersion, depositTxId} -> + toCBOR ("OnIncrementTx" :: Text) + <> toCBOR headId + <> toCBOR newVersion + <> toCBOR depositTxId + OnDecrementTx{headId, newVersion, distributedUTxO} -> + toCBOR ("OnDecrementTx" :: Text) + <> toCBOR headId + <> toCBOR newVersion + <> toCBOR distributedUTxO + OnCloseTx{headId, snapshotNumber, contestationDeadline} -> + toCBOR ("OnCloseTx" :: Text) + <> toCBOR headId + <> toCBOR snapshotNumber + <> toCBOR contestationDeadline + OnContestTx{headId, snapshotNumber, contestationDeadline} -> + toCBOR ("OnContestTx" :: Text) + <> toCBOR headId + <> toCBOR snapshotNumber + <> toCBOR contestationDeadline + OnFanoutTx{headId, fanoutUTxO} -> + toCBOR ("OnFanoutTx" :: Text) <> toCBOR headId <> toCBOR fanoutUTxO + OnPartialFanoutTx{headId, distributedOutputs} -> + toCBOR ("OnPartialFanoutTx" :: Text) <> toCBOR headId <> toCBOR distributedOutputs + +instance IsTx tx => FromCBOR (OnChainTx tx) where + fromCBOR = + fromCBOR >>= \case + ("OnInitTx" :: Text) -> OnInitTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "OnDepositTx" -> OnDepositTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "OnRecoverTx" -> OnRecoverTx <$> fromCBOR <*> fromCBOR <*> fromCBOR + "OnIncrementTx" -> OnIncrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR + "OnDecrementTx" -> OnDecrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR + "OnCloseTx" -> OnCloseTx <$> fromCBOR <*> fromCBOR <*> fromCBOR + "OnContestTx" -> OnContestTx <$> fromCBOR <*> fromCBOR <*> fromCBOR + "OnFanoutTx" -> OnFanoutTx <$> fromCBOR <*> fromCBOR + "OnPartialFanoutTx" -> OnPartialFanoutTx <$> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded OnChainTx" + -- | Exceptions thrown by 'postTx'. data PostTxError tx = NoSeedInput @@ -214,6 +343,86 @@ deriving anyclass instance IsChainState tx => FromJSON (PostTxError tx) instance IsChainState tx => Exception (PostTxError tx) +instance IsChainState tx => ToCBOR (PostTxError tx) where + toCBOR = \case + NoSeedInput -> + toCBOR ("NoSeedInput" :: Text) + InvalidSeed{headSeed} -> + toCBOR ("InvalidSeed" :: Text) <> toCBOR headSeed + InvalidHeadId{headId} -> + toCBOR ("InvalidHeadId" :: Text) <> toCBOR headId + UnsupportedLegacyOutput{byronAddress} -> + toCBOR ("UnsupportedLegacyOutput" :: Text) <> toCBOR byronAddress + DepositTooLow{providedValue, minimumValue} -> + toCBOR ("DepositTooLow" :: Text) <> toCBOR providedValue <> toCBOR minimumValue + InvalidStateToPost{txTried, chainState} -> + toCBOR ("InvalidStateToPost" :: Text) <> toCBOR txTried <> toCBOR chainState + NotEnoughFuel{failingTx} -> + toCBOR ("NotEnoughFuel" :: Text) <> toCBOR failingTx + NoFuelUTXOFound{failingTx} -> + toCBOR ("NoFuelUTXOFound" :: Text) <> toCBOR failingTx + ScriptFailedInWallet{redeemerPtr, failureReason, failingTx} -> + toCBOR ("ScriptFailedInWallet" :: Text) + <> toCBOR redeemerPtr + <> toCBOR failureReason + <> toCBOR failingTx + InternalWalletError{headUTxO, reason, failingTx} -> + toCBOR ("InternalWalletError" :: Text) + <> toCBOR headUTxO + <> toCBOR reason + <> toCBOR failingTx + FailedToPostTx{failureReason, failingTx} -> + toCBOR ("FailedToPostTx" :: Text) <> toCBOR failureReason <> toCBOR failingTx + FailedToConstructCloseTx -> + toCBOR ("FailedToConstructCloseTx" :: Text) + FailedToConstructContestTx -> + toCBOR ("FailedToConstructContestTx" :: Text) + FailedToConstructDepositTx{failureReason} -> + toCBOR ("FailedToConstructDepositTx" :: Text) <> toCBOR failureReason + FailedToConstructRecoverTx{failureReason} -> + toCBOR ("FailedToConstructRecoverTx" :: Text) <> toCBOR failureReason + FailedToConstructIncrementTx{failureReason} -> + toCBOR ("FailedToConstructIncrementTx" :: Text) <> toCBOR failureReason + FailedToConstructDecrementTx{failureReason} -> + toCBOR ("FailedToConstructDecrementTx" :: Text) <> toCBOR failureReason + FailedToConstructFanoutTx -> + toCBOR ("FailedToConstructFanoutTx" :: Text) + FailedToConstructPartialFanoutTx -> + toCBOR ("FailedToConstructPartialFanoutTx" :: Text) + StalePartialFanoutTx -> + toCBOR ("StalePartialFanoutTx" :: Text) + ContestationDeadlineOutsideTimeHorizon{failureReason} -> + toCBOR ("ContestationDeadlineOutsideTimeHorizon" :: Text) <> toCBOR failureReason + InvalidTokenRequest tokens -> + toCBOR ("InvalidTokenRequest" :: Text) <> toCBOR tokens + +instance IsChainState tx => FromCBOR (PostTxError tx) where + fromCBOR = + fromCBOR >>= \case + ("NoSeedInput" :: Text) -> pure NoSeedInput + "InvalidSeed" -> InvalidSeed <$> fromCBOR + "InvalidHeadId" -> InvalidHeadId <$> fromCBOR + "UnsupportedLegacyOutput" -> UnsupportedLegacyOutput <$> fromCBOR + "DepositTooLow" -> DepositTooLow <$> fromCBOR <*> fromCBOR + "InvalidStateToPost" -> InvalidStateToPost <$> fromCBOR <*> fromCBOR + "NotEnoughFuel" -> NotEnoughFuel <$> fromCBOR + "NoFuelUTXOFound" -> NoFuelUTXOFound <$> fromCBOR + "ScriptFailedInWallet" -> ScriptFailedInWallet <$> fromCBOR <*> fromCBOR <*> fromCBOR + "InternalWalletError" -> InternalWalletError <$> fromCBOR <*> fromCBOR <*> fromCBOR + "FailedToPostTx" -> FailedToPostTx <$> fromCBOR <*> fromCBOR + "FailedToConstructCloseTx" -> pure FailedToConstructCloseTx + "FailedToConstructContestTx" -> pure FailedToConstructContestTx + "FailedToConstructDepositTx" -> FailedToConstructDepositTx <$> fromCBOR + "FailedToConstructRecoverTx" -> FailedToConstructRecoverTx <$> fromCBOR + "FailedToConstructIncrementTx" -> FailedToConstructIncrementTx <$> fromCBOR + "FailedToConstructDecrementTx" -> FailedToConstructDecrementTx <$> fromCBOR + "FailedToConstructFanoutTx" -> pure FailedToConstructFanoutTx + "FailedToConstructPartialFanoutTx" -> pure FailedToConstructPartialFanoutTx + "StalePartialFanoutTx" -> pure StalePartialFanoutTx + "ContestationDeadlineOutsideTimeHorizon" -> ContestationDeadlineOutsideTimeHorizon <$> fromCBOR + "InvalidTokenRequest" -> InvalidTokenRequest <$> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded PostTxError" + -- | A non empty sequence of chain states that can be rolled back. -- This is expected to be constructed by using the smart constructor -- 'initHistory'. @@ -355,6 +564,29 @@ deriving stock instance (IsTx tx, IsChainState tx) => Show (ChainEvent tx) deriving anyclass instance (IsTx tx, IsChainState tx) => ToJSON (ChainEvent tx) deriving anyclass instance (IsTx tx, IsChainState tx) => FromJSON (ChainEvent tx) +instance IsChainState tx => ToCBOR (ChainEvent tx) where + toCBOR = \case + Observation{observedTx, newChainState} -> + toCBOR ("Observation" :: Text) <> toCBOR observedTx <> toCBOR newChainState + Rollback{chainTime, rolledBackChainState} -> + toCBOR ("Rollback" :: Text) <> toCBOR chainTime <> toCBOR rolledBackChainState + Tick{chainTime, chainPoint} -> + toCBOR ("Tick" :: Text) <> toCBOR chainTime <> toCBOR chainPoint + PostTxError{postChainTx, postTxError, failingTx} -> + toCBOR ("PostTxError" :: Text) + <> toCBOR postChainTx + <> toCBOR postTxError + <> toCBOR failingTx + +instance IsChainState tx => FromCBOR (ChainEvent tx) where + fromCBOR = + fromCBOR >>= \case + ("Observation" :: Text) -> Observation <$> fromCBOR <*> fromCBOR + "Rollback" -> Rollback <$> fromCBOR <*> fromCBOR + "Tick" -> Tick <$> fromCBOR <*> fromCBOR + "PostTxError" -> PostTxError <$> fromCBOR <*> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded ChainEvent" + -- | A callback indicating a 'ChainEvent tx' happened. Most importantly the -- 'Observation' of a relevant Hydra transaction. type ChainCallback tx m = ChainEvent tx -> m () diff --git a/hydra-node/src/Hydra/Chain/Direct/State.hs b/hydra-node/src/Hydra/Chain/Direct/State.hs index f3da475e93c..f30494a35a0 100644 --- a/hydra-node/src/Hydra/Chain/Direct/State.hs +++ b/hydra-node/src/Hydra/Chain/Direct/State.hs @@ -114,6 +114,13 @@ data ChainStateAt = ChainStateAt deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR ChainStateAt where + toCBOR ChainStateAt{spendableUTxO, recordedAt} = + toCBOR spendableUTxO <> toCBOR recordedAt + +instance FromCBOR ChainStateAt where + fromCBOR = ChainStateAt <$> fromCBOR <*> fromCBOR + instance IsChainState Tx where type ChainPointType Tx = ChainPoint diff --git a/hydra-node/src/Hydra/HeadLogic/Error.hs b/hydra-node/src/Hydra/HeadLogic/Error.hs index cf13c7d06a9..a8a8c5cc18f 100644 --- a/hydra-node/src/Hydra/HeadLogic/Error.hs +++ b/hydra-node/src/Hydra/HeadLogic/Error.hs @@ -5,6 +5,7 @@ module Hydra.HeadLogic.Error where import Hydra.Prelude +import Hydra.Chain.ChainState (IsChainState) import Hydra.HeadLogic.Input (Input) import Hydra.HeadLogic.State (HeadState) import Hydra.Ledger (ValidationError) @@ -49,6 +50,29 @@ deriving anyclass instance ) => ToJSON (LogicError tx) +instance (IsChainState tx, ToCBOR (Input tx)) => ToCBOR (LogicError tx) where + toCBOR = \case + UnhandledInput{input, currentHeadState} -> + toCBOR ("UnhandledInput" :: Text) <> toCBOR input <> toCBOR currentHeadState + RequireFailed{requirementFailure} -> + toCBOR ("RequireFailed" :: Text) <> toCBOR requirementFailure + AssertionFailed{message} -> + toCBOR ("AssertionFailed" :: Text) <> toCBOR message + NotOurHead{ourHeadId, otherHeadId} -> + toCBOR ("NotOurHead" :: Text) <> toCBOR ourHeadId <> toCBOR otherHeadId + SideLoadSnapshotFailed{sideLoadRequirementFailure} -> + toCBOR ("SideLoadSnapshotFailed" :: Text) <> toCBOR sideLoadRequirementFailure + +instance (IsChainState tx, FromCBOR (Input tx)) => FromCBOR (LogicError tx) where + fromCBOR = + fromCBOR >>= \case + ("UnhandledInput" :: Text) -> UnhandledInput <$> fromCBOR <*> fromCBOR + "RequireFailed" -> RequireFailed <$> fromCBOR + "AssertionFailed" -> AssertionFailed <$> fromCBOR + "NotOurHead" -> NotOurHead <$> fromCBOR <*> fromCBOR + "SideLoadSnapshotFailed" -> SideLoadSnapshotFailed <$> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded LogicError" + data RequirementFailure tx = ReqSnNumberInvalid {requestedSn :: SnapshotNumber, lastSeenSn :: SnapshotNumber} | ReqSvNumberInvalid {requestedSv :: SnapshotVersion, lastSeenSv :: SnapshotVersion} @@ -69,6 +93,53 @@ deriving stock instance Eq (TxIdType tx) => Eq (RequirementFailure tx) deriving stock instance Show (TxIdType tx) => Show (RequirementFailure tx) deriving anyclass instance ToJSON (TxIdType tx) => ToJSON (RequirementFailure tx) +instance IsTx tx => ToCBOR (RequirementFailure tx) where + toCBOR = \case + ReqSnNumberInvalid{requestedSn, lastSeenSn} -> + toCBOR ("ReqSnNumberInvalid" :: Text) <> toCBOR requestedSn <> toCBOR lastSeenSn + ReqSvNumberInvalid{requestedSv, lastSeenSv} -> + toCBOR ("ReqSvNumberInvalid" :: Text) <> toCBOR requestedSv <> toCBOR lastSeenSv + ReqSnNotLeader{requestedSn, leader} -> + toCBOR ("ReqSnNotLeader" :: Text) <> toCBOR requestedSn <> toCBOR leader + ReqSnDecommitNotSettled -> + toCBOR ("ReqSnDecommitNotSettled" :: Text) + ReqSnCommitNotSettled -> + toCBOR ("ReqSnCommitNotSettled" :: Text) + InvalidMultisignature{multisig, vkeys} -> + toCBOR ("InvalidMultisignature" :: Text) <> toCBOR multisig <> toCBOR vkeys + SnapshotAlreadySigned{knownSignatures, receivedSignature} -> + toCBOR ("SnapshotAlreadySigned" :: Text) <> toCBOR knownSignatures <> toCBOR receivedSignature + AckSnNumberInvalid{requestedSn, lastSeenSn} -> + toCBOR ("AckSnNumberInvalid" :: Text) <> toCBOR requestedSn <> toCBOR lastSeenSn + SnapshotDoesNotApply{requestedSn, txid, error = validationError} -> + toCBOR ("SnapshotDoesNotApply" :: Text) <> toCBOR requestedSn <> toCBOR txid <> toCBOR validationError + NoMatchingDeposit -> + toCBOR ("NoMatchingDeposit" :: Text) + RequestedDepositExpired{depositTxId} -> + toCBOR ("RequestedDepositExpired" :: Text) <> toCBOR depositTxId + RequestedDepositNotFoundLocally{depositTxId} -> + toCBOR ("RequestedDepositNotFoundLocally" :: Text) <> toCBOR depositTxId + ReqSnUTxOSetTooLarge{utxoCount, maxAllowed} -> + toCBOR ("ReqSnUTxOSetTooLarge" :: Text) <> toCBOR utxoCount <> toCBOR maxAllowed + +instance IsTx tx => FromCBOR (RequirementFailure tx) where + fromCBOR = + fromCBOR >>= \case + ("ReqSnNumberInvalid" :: Text) -> ReqSnNumberInvalid <$> fromCBOR <*> fromCBOR + "ReqSvNumberInvalid" -> ReqSvNumberInvalid <$> fromCBOR <*> fromCBOR + "ReqSnNotLeader" -> ReqSnNotLeader <$> fromCBOR <*> fromCBOR + "ReqSnDecommitNotSettled" -> pure ReqSnDecommitNotSettled + "ReqSnCommitNotSettled" -> pure ReqSnCommitNotSettled + "InvalidMultisignature" -> InvalidMultisignature <$> fromCBOR <*> fromCBOR + "SnapshotAlreadySigned" -> SnapshotAlreadySigned <$> fromCBOR <*> fromCBOR + "AckSnNumberInvalid" -> AckSnNumberInvalid <$> fromCBOR <*> fromCBOR + "SnapshotDoesNotApply" -> SnapshotDoesNotApply <$> fromCBOR <*> fromCBOR <*> fromCBOR + "NoMatchingDeposit" -> pure NoMatchingDeposit + "RequestedDepositExpired" -> RequestedDepositExpired <$> fromCBOR + "RequestedDepositNotFoundLocally" -> RequestedDepositNotFoundLocally <$> fromCBOR + "ReqSnUTxOSetTooLarge" -> ReqSnUTxOSetTooLarge <$> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded RequirementFailure" + data SideLoadRequirementFailure tx = SideLoadInitialSnapshotMismatch | SideLoadSnNumberInvalid {requestedSn :: SnapshotNumber, lastSeenSn :: SnapshotNumber} @@ -82,3 +153,29 @@ deriving stock instance Eq (UTxOType tx) => Eq (SideLoadRequirementFailure tx) deriving stock instance Show (UTxOType tx) => Show (SideLoadRequirementFailure tx) deriving anyclass instance ToJSON (UTxOType tx) => ToJSON (SideLoadRequirementFailure tx) deriving anyclass instance FromJSON (UTxOType tx) => FromJSON (SideLoadRequirementFailure tx) + +instance IsTx tx => ToCBOR (SideLoadRequirementFailure tx) where + toCBOR = \case + SideLoadInitialSnapshotMismatch -> + toCBOR ("SideLoadInitialSnapshotMismatch" :: Text) + SideLoadSnNumberInvalid{requestedSn, lastSeenSn} -> + toCBOR ("SideLoadSnNumberInvalid" :: Text) <> toCBOR requestedSn <> toCBOR lastSeenSn + SideLoadSvNumberInvalid{requestedSv, lastSeenSv} -> + toCBOR ("SideLoadSvNumberInvalid" :: Text) <> toCBOR requestedSv <> toCBOR lastSeenSv + SideLoadUTxOToCommitInvalid{requestedSc, lastSeenSc} -> + toCBOR ("SideLoadUTxOToCommitInvalid" :: Text) <> toCBOR requestedSc <> toCBOR lastSeenSc + SideLoadUTxOToDecommitInvalid{requestedSd, lastSeenSd} -> + toCBOR ("SideLoadUTxOToDecommitInvalid" :: Text) <> toCBOR requestedSd <> toCBOR lastSeenSd + SideLoadInvalidMultisignature{multisig, vkeys} -> + toCBOR ("SideLoadInvalidMultisignature" :: Text) <> toCBOR multisig <> toCBOR vkeys + +instance IsTx tx => FromCBOR (SideLoadRequirementFailure tx) where + fromCBOR = + fromCBOR >>= \case + ("SideLoadInitialSnapshotMismatch" :: Text) -> pure SideLoadInitialSnapshotMismatch + "SideLoadSnNumberInvalid" -> SideLoadSnNumberInvalid <$> fromCBOR <*> fromCBOR + "SideLoadSvNumberInvalid" -> SideLoadSvNumberInvalid <$> fromCBOR <*> fromCBOR + "SideLoadUTxOToCommitInvalid" -> SideLoadUTxOToCommitInvalid <$> fromCBOR <*> fromCBOR + "SideLoadUTxOToDecommitInvalid" -> SideLoadUTxOToDecommitInvalid <$> fromCBOR <*> fromCBOR + "SideLoadInvalidMultisignature" -> SideLoadInvalidMultisignature <$> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded SideLoadRequirementFailure" diff --git a/hydra-node/src/Hydra/HeadLogic/State.hs b/hydra-node/src/Hydra/HeadLogic/State.hs index d7e2ddbee53..16f53d6e66b 100644 --- a/hydra-node/src/Hydra/HeadLogic/State.hs +++ b/hydra-node/src/Hydra/HeadLogic/State.hs @@ -52,6 +52,22 @@ deriving stock instance (IsTx tx, Show (ChainStateType tx)) => Show (HeadState t deriving anyclass instance (IsTx tx, ToJSON (ChainStateType tx)) => ToJSON (HeadState tx) deriving anyclass instance (IsTx tx, FromJSON (ChainStateType tx)) => FromJSON (HeadState tx) +instance IsChainState tx => ToCBOR (HeadState tx) where + toCBOR = \case + Idle st -> toCBOR ("Idle" :: Text) <> toCBOR st + Open st -> toCBOR ("Open" :: Text) <> toCBOR st + Closed st -> toCBOR ("Closed" :: Text) <> toCBOR st + FanoutProgress st -> toCBOR ("FanoutProgress" :: Text) <> toCBOR st + +instance IsChainState tx => FromCBOR (HeadState tx) where + fromCBOR = + fromCBOR >>= \case + ("Idle" :: Text) -> Idle <$> fromCBOR + "Open" -> Open <$> fromCBOR + "Closed" -> Closed <$> fromCBOR + "FanoutProgress" -> FanoutProgress <$> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded HeadState" + -- | Update the chain state in any 'HeadState'. setChainState :: ChainStateType tx -> HeadState tx -> HeadState tx setChainState chainState = \case @@ -95,6 +111,12 @@ deriving stock instance Show (ChainStateType tx) => Show (IdleState tx) deriving anyclass instance ToJSON (ChainStateType tx) => ToJSON (IdleState tx) deriving anyclass instance FromJSON (ChainStateType tx) => FromJSON (IdleState tx) +instance IsChainState tx => ToCBOR (IdleState tx) where + toCBOR IdleState{chainState} = toCBOR chainState + +instance IsChainState tx => FromCBOR (IdleState tx) where + fromCBOR = IdleState <$> fromCBOR + -- ** Open -- | An 'Open' head with a 'CoordinatedHeadState' tracking off-chain @@ -113,6 +135,17 @@ deriving stock instance (IsTx tx, Show (ChainStateType tx)) => Show (OpenState t deriving anyclass instance (IsTx tx, ToJSON (ChainStateType tx)) => ToJSON (OpenState tx) deriving anyclass instance (IsTx tx, FromJSON (ChainStateType tx)) => FromJSON (OpenState tx) +instance IsChainState tx => ToCBOR (OpenState tx) where + toCBOR OpenState{parameters, coordinatedHeadState, chainState, headId, headSeed} = + toCBOR parameters + <> toCBOR coordinatedHeadState + <> toCBOR chainState + <> toCBOR headId + <> toCBOR headSeed + +instance IsChainState tx => FromCBOR (OpenState tx) where + fromCBOR = OpenState <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + -- | Off-chain state of the Coordinated Head protocol. data CoordinatedHeadState tx = CoordinatedHeadState { localUTxO :: UTxOType tx @@ -144,6 +177,29 @@ deriving stock instance IsTx tx => Show (CoordinatedHeadState tx) deriving anyclass instance IsTx tx => ToJSON (CoordinatedHeadState tx) deriving anyclass instance IsTx tx => FromJSON (CoordinatedHeadState tx) +instance IsTx tx => ToCBOR (CoordinatedHeadState tx) where + toCBOR CoordinatedHeadState{localUTxO, localTxs, allTxs, confirmedSnapshot, seenSnapshot, currentDepositTxId, decommitTx, version} = + toCBOR localUTxO + <> toCBOR localTxs + <> toCBOR allTxs + <> toCBOR confirmedSnapshot + <> toCBOR seenSnapshot + <> toCBOR currentDepositTxId + <> toCBOR decommitTx + <> toCBOR version + +instance IsTx tx => FromCBOR (CoordinatedHeadState tx) where + fromCBOR = + CoordinatedHeadState + <$> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + -- | Data structure to help in tracking whether we have seen or requested a -- ReqSn already and if seen, the signatures we collected already. data SeenSnapshot tx @@ -211,6 +267,28 @@ instance IsTx tx => FromJSON (SeenSnapshot tx) where pure $ mkSeenSnapshot snapshot signatories other -> fail $ "unknown SeenSnapshot tag: " <> toString other +-- Manual instances that exclude 'signableBytes' from CBOR (it is derived from +-- 'snapshot' and recomputed on deserialisation), like the JSON instances above. +instance IsTx tx => ToCBOR (SeenSnapshot tx) where + toCBOR = \case + NoSeenSnapshot -> + toCBOR ("NoSeenSnapshot" :: Text) + LastSeenSnapshot{lastSeen} -> + toCBOR ("LastSeenSnapshot" :: Text) <> toCBOR lastSeen + RequestedSnapshot{lastSeen, requested} -> + toCBOR ("RequestedSnapshot" :: Text) <> toCBOR lastSeen <> toCBOR requested + SeenSnapshot{snapshot, signatories} -> + toCBOR ("SeenSnapshot" :: Text) <> toCBOR snapshot <> toCBOR signatories + +instance IsTx tx => FromCBOR (SeenSnapshot tx) where + fromCBOR = + fromCBOR >>= \case + ("NoSeenSnapshot" :: Text) -> pure NoSeenSnapshot + "LastSeenSnapshot" -> LastSeenSnapshot <$> fromCBOR + "RequestedSnapshot" -> RequestedSnapshot <$> fromCBOR <*> fromCBOR + "SeenSnapshot" -> mkSeenSnapshot <$> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded SeenSnapshot" + -- | Smart constructor for 'SeenSnapshot' that computes and caches -- 'signableBytes' from 'snapshot', enforcing the invariant that they stay in sync. mkSeenSnapshot :: @@ -269,6 +347,29 @@ deriving stock instance (IsTx tx, Show (ChainStateType tx)) => Show (ClosedState deriving anyclass instance (IsTx tx, ToJSON (ChainStateType tx)) => ToJSON (ClosedState tx) deriving anyclass instance (IsTx tx, FromJSON (ChainStateType tx)) => FromJSON (ClosedState tx) +instance IsChainState tx => ToCBOR (ClosedState tx) where + toCBOR ClosedState{parameters, confirmedSnapshot, contestationDeadline, readyToFanoutSent, chainState, headId, headSeed, version} = + toCBOR parameters + <> toCBOR confirmedSnapshot + <> toCBOR contestationDeadline + <> toCBOR readyToFanoutSent + <> toCBOR chainState + <> toCBOR headId + <> toCBOR headSeed + <> toCBOR version + +instance IsChainState tx => FromCBOR (ClosedState tx) where + fromCBOR = + ClosedState + <$> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + -- ** PartialFanout -- Terminology note: the selective-fanout feature spans several near-synonymous @@ -300,6 +401,20 @@ deriving stock instance IsTx tx => Show (FanoutMode tx) deriving anyclass instance IsTx tx => ToJSON (FanoutMode tx) deriving anyclass instance IsTx tx => FromJSON (FanoutMode tx) +instance IsTx tx => ToCBOR (FanoutMode tx) where + toCBOR = \case + AutoDrain -> toCBOR ("AutoDrain" :: Text) + DistributingSelection utxo -> toCBOR ("DistributingSelection" :: Text) <> toCBOR utxo + AwaitingSelection -> toCBOR ("AwaitingSelection" :: Text) + +instance IsTx tx => FromCBOR (FanoutMode tx) where + fromCBOR = + fromCBOR >>= \case + ("AutoDrain" :: Text) -> pure AutoDrain + "DistributingSelection" -> DistributingSelection <$> fromCBOR + "AwaitingSelection" -> pure AwaitingSelection + tag -> fail $ show tag <> " is not a proper CBOR-encoded FanoutMode" + -- | A closed head whose UTxO is being distributed across multiple fanout -- transactions (on-chain @FanoutProgress@). Holds the partial-fanout bookkeeping -- that used to live in 'ClosedState'. @@ -325,3 +440,30 @@ deriving stock instance (IsTx tx, Eq (ChainStateType tx)) => Eq (PartialFanoutSt deriving stock instance (IsTx tx, Show (ChainStateType tx)) => Show (PartialFanoutState tx) deriving anyclass instance (IsTx tx, ToJSON (ChainStateType tx)) => ToJSON (PartialFanoutState tx) deriving anyclass instance (IsTx tx, FromJSON (ChainStateType tx)) => FromJSON (PartialFanoutState tx) + +instance IsChainState tx => ToCBOR (PartialFanoutState tx) where + toCBOR PartialFanoutState{parameters, confirmedSnapshot, contestationDeadline, chainState, headId, headSeed, version, remainingOutputs, distributedOutputs, mode} = + toCBOR parameters + <> toCBOR confirmedSnapshot + <> toCBOR contestationDeadline + <> toCBOR chainState + <> toCBOR headId + <> toCBOR headSeed + <> toCBOR version + <> toCBOR remainingOutputs + <> toCBOR distributedOutputs + <> toCBOR mode + +instance IsChainState tx => FromCBOR (PartialFanoutState tx) where + fromCBOR = + PartialFanoutState + <$> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR + <*> fromCBOR diff --git a/hydra-node/src/Hydra/Ledger.hs b/hydra-node/src/Hydra/Ledger.hs index f58a41b9e8e..588e7e85607 100644 --- a/hydra-node/src/Hydra/Ledger.hs +++ b/hydra-node/src/Hydra/Ledger.hs @@ -62,3 +62,4 @@ data ValidationResult newtype ValidationError = ValidationError {reason :: Text} deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) + deriving newtype (ToCBOR, FromCBOR) diff --git a/hydra-node/src/Hydra/Ledger/Simple.hs b/hydra-node/src/Hydra/Ledger/Simple.hs index 996842c78fc..1a3c9b48d2e 100644 --- a/hydra-node/src/Hydra/Ledger/Simple.hs +++ b/hydra-node/src/Hydra/Ledger/Simple.hs @@ -108,7 +108,7 @@ instance IsTx SimpleTx where newtype SimpleChainState = SimpleChainState {slot :: ChainSlot} deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) - deriving newtype (Num) + deriving newtype (Num, ToCBOR, FromCBOR) instance IsChainState SimpleTx where type ChainPointType SimpleTx = ChainSlot diff --git a/hydra-node/src/Hydra/Network.hs b/hydra-node/src/Hydra/Network.hs index 1869c994d40..bed57a245dd 100644 --- a/hydra-node/src/Hydra/Network.hs +++ b/hydra-node/src/Hydra/Network.hs @@ -61,6 +61,18 @@ data WhichEtcd = EmbeddedEtcd | SystemEtcd deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR WhichEtcd where + toCBOR = \case + EmbeddedEtcd -> toCBOR ("EmbeddedEtcd" :: Text) + SystemEtcd -> toCBOR ("SystemEtcd" :: Text) + +instance FromCBOR WhichEtcd where + fromCBOR = + fromCBOR >>= \case + ("EmbeddedEtcd" :: Text) -> pure EmbeddedEtcd + "SystemEtcd" -> pure SystemEtcd + tag -> fail $ show tag <> " is not a proper CBOR-encoded WhichEtcd" + -- | Configuration for a `Node` network layer. data NetworkConfiguration = NetworkConfiguration { persistenceDir :: FilePath @@ -116,7 +128,7 @@ instance FromJSON PortNumber where -- ** NodeId newtype NodeId = NodeId {nodeId :: Text} - deriving newtype (Eq, Show, IsString, Read, Ord, ToJSON, FromJSON) + deriving newtype (Eq, Show, IsString, Read, Ord, ToJSON, FromJSON, ToCBOR, FromCBOR) -- ** Host @@ -183,6 +195,28 @@ data Connectivity deriving stock (Generic, Eq, Show) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR Connectivity where + toCBOR = \case + PeerConnected{peer} -> toCBOR ("PeerConnected" :: Text) <> toCBOR peer + PeerDisconnected{peer} -> toCBOR ("PeerDisconnected" :: Text) <> toCBOR peer + NetworkConnected -> toCBOR ("NetworkConnected" :: Text) + NetworkDisconnected -> toCBOR ("NetworkDisconnected" :: Text) + VersionMismatch{ourVersion, theirVersion} -> + toCBOR ("VersionMismatch" :: Text) <> toCBOR ourVersion <> toCBOR theirVersion + ClusterIDMismatch{clusterPeers} -> + toCBOR ("ClusterIDMismatch" :: Text) <> toCBOR clusterPeers + +instance FromCBOR Connectivity where + fromCBOR = + fromCBOR >>= \case + ("PeerConnected" :: Text) -> PeerConnected <$> fromCBOR + "PeerDisconnected" -> PeerDisconnected <$> fromCBOR + "NetworkConnected" -> pure NetworkConnected + "NetworkDisconnected" -> pure NetworkDisconnected + "VersionMismatch" -> VersionMismatch <$> fromCBOR <*> fromCBOR + "ClusterIDMismatch" -> ClusterIDMismatch <$> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded Connectivity" + newtype ProtocolVersion = ProtocolVersion Natural deriving stock (Eq, Show, Generic, Ord) deriving newtype (ToCBOR, FromCBOR) diff --git a/hydra-node/src/Hydra/Node/ApiTransactionTimeout.hs b/hydra-node/src/Hydra/Node/ApiTransactionTimeout.hs index 8cb39230b7d..6b70e82a842 100644 --- a/hydra-node/src/Hydra/Node/ApiTransactionTimeout.hs +++ b/hydra-node/src/Hydra/Node/ApiTransactionTimeout.hs @@ -7,7 +7,7 @@ newtype ApiTransactionTimeout = ApiTransactionTimeout { apiTransactionTimeoutNominalDiffTime :: NominalDiffTime } deriving stock (Eq, Ord) - deriving newtype (Show, Read, Num, Enum, Real, ToJSON, FromJSON) + deriving newtype (Show, Read, Num, Enum, Real, ToJSON, FromJSON, ToCBOR, FromCBOR) -- | Truncates to whole seconds. instance Integral ApiTransactionTimeout where diff --git a/hydra-node/src/Hydra/Node/Environment.hs b/hydra-node/src/Hydra/Node/Environment.hs index 948a1d35a16..cf0867c8c5a 100644 --- a/hydra-node/src/Hydra/Node/Environment.hs +++ b/hydra-node/src/Hydra/Node/Environment.hs @@ -93,6 +93,44 @@ instance FromJSON Environment where <*> o .: "unsyncedPeriod" <*> o .: "configuredPeers" +-- | Like the JSON instance above, 'ToCBOR' deliberately omits 'signingKey' +-- (CBOR-encoding a 'Secret' is a compile-time error by design). +instance ToCBOR Environment where + toCBOR Environment{party, otherParties, participants, contestationPeriod, depositPeriod, depositActivation, unsyncedPeriod, configuredPeers} = + toCBOR party + <> toCBOR otherParties + <> toCBOR participants + <> toCBOR contestationPeriod + <> toCBOR depositPeriod + <> toCBOR depositActivation + <> toCBOR unsyncedPeriod + <> toCBOR configuredPeers + +-- | Like the JSON instance above, the decoded signing key is +-- 'placeholderSigningKey', NOT the real key. +instance FromCBOR Environment where + fromCBOR = do + party <- fromCBOR + otherParties <- fromCBOR + participants <- fromCBOR + contestationPeriod <- fromCBOR + depositPeriod <- fromCBOR + depositActivation <- fromCBOR + unsyncedPeriod <- fromCBOR + configuredPeers <- fromCBOR + pure + Environment + { party + , signingKey = placeholderSigningKey + , otherParties + , participants + , contestationPeriod + , depositPeriod + , depositActivation + , unsyncedPeriod + , configuredPeers + } + -- | Sentinel signing key used when an 'Environment' has to be -- reconstructed without access to the real one (e.g. JSON roundtrip -- tests). Exported so 'Arbitrary' generators can use the same value, diff --git a/hydra-node/src/Hydra/Node/State.hs b/hydra-node/src/Hydra/Node/State.hs index 5355d47c563..4a75a62f990 100644 --- a/hydra-node/src/Hydra/Node/State.hs +++ b/hydra-node/src/Hydra/Node/State.hs @@ -26,6 +26,13 @@ data ChainPointTime = ChainPointTime deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR ChainPointTime where + toCBOR ChainPointTime{currentSlot, currentChainTime, drift} = + toCBOR currentSlot <> toCBOR currentChainTime <> toCBOR drift + +instance FromCBOR ChainPointTime where + fromCBOR = ChainPointTime <$> fromCBOR <*> fromCBOR <*> fromCBOR + data NodeState tx = -- | Normal operation of the node where it is connected and has a recent -- view of the chain. @@ -54,6 +61,26 @@ deriving stock instance (IsTx tx, Show (ChainStateType tx)) => Show (NodeState t deriving anyclass instance (IsTx tx, ToJSON (ChainStateType tx)) => ToJSON (NodeState tx) deriving anyclass instance (IsTx tx, FromJSON (ChainStateType tx)) => FromJSON (NodeState tx) +instance IsChainState tx => ToCBOR (NodeState tx) where + toCBOR = \case + NodeInSync{headState, pendingDeposits, chainPointTime} -> + toCBOR ("NodeInSync" :: Text) + <> toCBOR headState + <> toCBOR pendingDeposits + <> toCBOR chainPointTime + NodeCatchingUp{headState, pendingDeposits, chainPointTime} -> + toCBOR ("NodeCatchingUp" :: Text) + <> toCBOR headState + <> toCBOR pendingDeposits + <> toCBOR chainPointTime + +instance IsChainState tx => FromCBOR (NodeState tx) where + fromCBOR = + fromCBOR >>= \case + ("NodeInSync" :: Text) -> NodeInSync <$> fromCBOR <*> fromCBOR <*> fromCBOR + "NodeCatchingUp" -> NodeCatchingUp <$> fromCBOR <*> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded NodeState" + initNodeState :: IsChainState tx => ChainStateType tx -> NodeState tx initNodeState chainState = NodeCatchingUp @@ -77,6 +104,18 @@ data SyncedStatus = InSync | CatchingUp deriving stock (Generic, Eq, Show) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR SyncedStatus where + toCBOR = \case + InSync -> toCBOR ("InSync" :: Text) + CatchingUp -> toCBOR ("CatchingUp" :: Text) + +instance FromCBOR SyncedStatus where + fromCBOR = + fromCBOR >>= \case + ("InSync" :: Text) -> pure InSync + "CatchingUp" -> pure CatchingUp + tag -> fail $ show tag <> " is not a proper CBOR-encoded SyncedStatus" + syncedStatus :: NodeState tx -> SyncedStatus syncedStatus NodeInSync{} = InSync syncedStatus NodeCatchingUp{} = CatchingUp @@ -97,10 +136,35 @@ deriving stock instance IsTx tx => Show (Deposit tx) deriving anyclass instance IsTx tx => ToJSON (Deposit tx) deriving anyclass instance IsTx tx => FromJSON (Deposit tx) +instance IsTx tx => ToCBOR (Deposit tx) where + toCBOR Deposit{headId, deposited, created, deadline, status} = + toCBOR headId + <> toCBOR deposited + <> toCBOR created + <> toCBOR deadline + <> toCBOR status + +instance IsTx tx => FromCBOR (Deposit tx) where + fromCBOR = Deposit <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + data DepositStatus = Inactive | Active | Expired deriving stock (Generic, Eq, Show) deriving anyclass (ToJSON, FromJSON) +instance ToCBOR DepositStatus where + toCBOR = \case + Inactive -> toCBOR ("Inactive" :: Text) + Active -> toCBOR ("Active" :: Text) + Expired -> toCBOR ("Expired" :: Text) + +instance FromCBOR DepositStatus where + fromCBOR = + fromCBOR >>= \case + ("Inactive" :: Text) -> pure Inactive + "Active" -> pure Active + "Expired" -> pure Expired + tag -> fail $ show tag <> " is not a proper CBOR-encoded DepositStatus" + depositsForHead :: HeadId -> PendingDeposits tx -> PendingDeposits tx depositsForHead targetHeadId = Map.filter (\Deposit{headId} -> headId == targetHeadId) diff --git a/hydra-node/src/Hydra/Node/UnsyncedPeriod.hs b/hydra-node/src/Hydra/Node/UnsyncedPeriod.hs index 024cbd4ab3d..6a206f1dae5 100644 --- a/hydra-node/src/Hydra/Node/UnsyncedPeriod.hs +++ b/hydra-node/src/Hydra/Node/UnsyncedPeriod.hs @@ -8,7 +8,7 @@ import Hydra.Tx.ContestationPeriod (ContestationPeriod, toNominalDiffTime) -- Beyond this period the node will refuse to process new transactions and signing snapshots. newtype UnsyncedPeriod = UnsyncedPeriod {unsyncedPeriodToNominalDiffTime :: NominalDiffTime} deriving stock (Eq, Ord) - deriving newtype (Show, Read, Num, Real, ToJSON, FromJSON) + deriving newtype (Show, Read, Num, Real, ToJSON, FromJSON, ToCBOR, FromCBOR) -- | Compute a default 'UnsyncedPeriod' based on the 'ContestationPeriod'. -- This is the legacy behavior: half of the contestation period. diff --git a/hydra-node/test/Hydra/CBORSpec.hs b/hydra-node/test/Hydra/CBORSpec.hs new file mode 100644 index 00000000000..33bc6d029f6 --- /dev/null +++ b/hydra-node/test/Hydra/CBORSpec.hs @@ -0,0 +1,65 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | Roundtrip tests for all the hand-written 'ToCBOR' / 'FromCBOR' codecs. +-- +-- This is the CI guard that keeps encoder/decoder pairs in sync: adding a +-- constructor to any of these types without a matching codec (or with fields +-- decoded in the wrong order) fails here. +module Hydra.CBORSpec where + +import Hydra.Prelude +import Test.Hydra.Prelude + +import Hydra.API.ClientInput (ClientInput) +import Hydra.API.ServerOutput ( + ClientMessage, + Greetings, + HeadStatus, + InvalidInput (..), + NetworkInfo, + ServerOutput, + TimedServerOutput, + ) +import Hydra.Chain (ChainEvent, OnChainTx, PostChainTx, PostTxError) +import Hydra.Chain.Direct.State (ChainStateAt) +import Hydra.HeadLogic.State (HeadState, SeenSnapshot) +import Hydra.Ledger.Cardano (Tx) +import Hydra.Network (Connectivity) +import Hydra.Node.Environment (Environment) +import Hydra.Node.State (Deposit, NodeState) +import Hydra.Tx (ConfirmedSnapshot, Snapshot) +import Test.Hydra.API.ClientInput () +import Test.Hydra.API.ServerOutput () +import Test.Hydra.CBOR (roundtripCBOR) +import Test.Hydra.Chain.Direct.State () +import Test.Hydra.Node.Environment () + +instance Arbitrary InvalidInput where + arbitrary = InvalidInput <$> arbitrary <*> arbitrary + +spec :: Spec +spec = parallel $ do + describe "API types" $ do + roundtripCBOR $ Proxy @(ClientInput Tx) + roundtripCBOR $ Proxy @(ServerOutput Tx) + roundtripCBOR $ Proxy @(TimedServerOutput Tx) + roundtripCBOR $ Proxy @(ClientMessage Tx) + roundtripCBOR $ Proxy @(Greetings Tx) + roundtripCBOR $ Proxy @InvalidInput + roundtripCBOR $ Proxy @HeadStatus + roundtripCBOR $ Proxy @NetworkInfo + + describe "protocol types" $ do + roundtripCBOR $ Proxy @(Snapshot Tx) + roundtripCBOR $ Proxy @(ConfirmedSnapshot Tx) + roundtripCBOR $ Proxy @(PostChainTx Tx) + roundtripCBOR $ Proxy @(OnChainTx Tx) + roundtripCBOR $ Proxy @(PostTxError Tx) + roundtripCBOR $ Proxy @(ChainEvent Tx) + roundtripCBOR $ Proxy @ChainStateAt + roundtripCBOR $ Proxy @(HeadState Tx) + roundtripCBOR $ Proxy @(SeenSnapshot Tx) + roundtripCBOR $ Proxy @(NodeState Tx) + roundtripCBOR $ Proxy @(Deposit Tx) + roundtripCBOR $ Proxy @Environment + roundtripCBOR $ Proxy @Connectivity diff --git a/hydra-node/test/Main.hs b/hydra-node/test/Main.hs index 6940db3ef84..e81530f185f 100644 --- a/hydra-node/test/Main.hs +++ b/hydra-node/test/Main.hs @@ -7,6 +7,7 @@ import Hydra.API.HTTPServerSpec qualified import Hydra.API.ServerOutputSpec qualified import Hydra.API.ServerSpec qualified import Hydra.BehaviorSpec qualified +import Hydra.CBORSpec qualified import Hydra.Chain.BlockfrostSpec qualified import Hydra.Chain.Direct.HandlersSpec qualified import Hydra.Chain.Direct.ScriptRegistrySpec qualified @@ -56,6 +57,7 @@ main = , testSpec "API.ServerOutput" Hydra.API.ServerOutputSpec.spec , testSpec "API.Server" Hydra.API.ServerSpec.spec , testSpec "Behavior" Hydra.BehaviorSpec.spec + , testSpec "CBOR" Hydra.CBORSpec.spec , testSpec "Chain.Blockfrost" Hydra.Chain.BlockfrostSpec.spec , testSpec "Chain.Direct.Handlers" Hydra.Chain.Direct.HandlersSpec.spec , testSpec "Chain.Direct.ScriptRegistry" Hydra.Chain.Direct.ScriptRegistrySpec.spec diff --git a/hydra-node/testlib/Test/Hydra/CBOR.hs b/hydra-node/testlib/Test/Hydra/CBOR.hs new file mode 100644 index 00000000000..e545ddff3b1 --- /dev/null +++ b/hydra-node/testlib/Test/Hydra/CBOR.hs @@ -0,0 +1,29 @@ +-- | Helpers to test 'ToCBOR' / 'FromCBOR' instances. +module Test.Hydra.CBOR where + +import Hydra.Prelude +import Test.Hydra.Prelude + +import Codec.CBOR.Read (deserialiseFromBytes) +import Codec.CBOR.Write (toLazyByteString) +import Data.Typeable (typeRep) +import Test.QuickCheck (Property, (===)) + +-- | Test that a value can be roundtripped through its CBOR encoding. +prop_canRoundtripCBOREncoding :: + (ToCBOR a, FromCBOR a, Eq a, Show a) => a -> Property +prop_canRoundtripCBOREncoding a = + let encoded = toLazyByteString $ toCBOR a + in (snd <$> deserialiseFromBytes fromCBOR encoded) === Right a + +-- | A property spec asserting CBOR roundtrips for arbitrary values of @a@. +-- This is the CI guard that keeps the hand-written encoder/decoder pairs in +-- sync: a new constructor without a (correct) codec fails here. +roundtripCBOR :: + forall a. + (Arbitrary a, ToCBOR a, FromCBOR a, Eq a, Show a) => + Proxy a -> + Spec +roundtripCBOR p = + prop ("roundtrips CBOR encoding: " <> show (typeRep p)) $ + prop_canRoundtripCBOREncoding @a diff --git a/hydra-prelude/hydra-prelude.cabal b/hydra-prelude/hydra-prelude.cabal index 44e49e4a76f..4cd3cc62859 100644 --- a/hydra-prelude/hydra-prelude.cabal +++ b/hydra-prelude/hydra-prelude.cabal @@ -31,6 +31,7 @@ library , io-classes:si-timers , pretty-simple , relude + , time , transformers default-language: GHC2021 diff --git a/hydra-prelude/src/Hydra/Prelude.hs b/hydra-prelude/src/Hydra/Prelude.hs index bef4ee3abf0..5afeee997fa 100644 --- a/hydra-prelude/src/Hydra/Prelude.hs +++ b/hydra-prelude/src/Hydra/Prelude.hs @@ -1,5 +1,7 @@ -- NOTE: Usage of 'trace' in 'spy' is accepted here. {-# OPTIONS_GHC -Wno-deprecations #-} +-- NOTE: For the 'NominalDiffTime' CBOR instances below. +{-# OPTIONS_GHC -Wno-orphans #-} module Hydra.Prelude ( module Relude, @@ -108,7 +110,9 @@ import Data.Aeson.Encode.Pretty ( encodePretty, ) import Data.ByteString.Base16 qualified as Base16 +import Data.Fixed (Fixed (..)) import Data.Text qualified as T +import Data.Time.Clock (nominalDiffTimeToSeconds, secondsToNominalDiffTime) import Relude hiding ( MVar, Nat, @@ -158,6 +162,15 @@ import Relude.Extra.Map ( import System.IO qualified import Text.Pretty.Simple (pShow) +-- Orphan CBOR instances: cardano-binary provides 'UTCTime' instances, but +-- lacks 'NominalDiffTime'. Encoded as integer picoseconds, which is lossless +-- since 'NominalDiffTime' has fixed picosecond resolution. +instance ToCBOR NominalDiffTime where + toCBOR ndt = case nominalDiffTimeToSeconds ndt of MkFixed i -> toCBOR i + +instance FromCBOR NominalDiffTime where + fromCBOR = secondsToNominalDiffTime . MkFixed <$> fromCBOR + -- | Pad a text-string to right with the given character until it reaches the given -- length. -- diff --git a/hydra-tx/src/Hydra/Chain/ChainState.hs b/hydra-tx/src/Hydra/Chain/ChainState.hs index 83f06743026..8b39e1e6345 100644 --- a/hydra-tx/src/Hydra/Chain/ChainState.hs +++ b/hydra-tx/src/Hydra/Chain/ChainState.hs @@ -9,7 +9,7 @@ import Hydra.Tx (IsTx (..)) -- | A generic description for a chain slot all implementations need to use. newtype ChainSlot = ChainSlot Natural deriving stock (Ord, Eq, Show, Generic) - deriving newtype (Num, ToJSON, FromJSON) + deriving newtype (Num, ToJSON, FromJSON, ToCBOR, FromCBOR) -- | Types that can be used on-chain by the Hydra protocol. This includes the -- information that needs to be retained about the L1 state to interact with @@ -23,10 +23,14 @@ class , Show (ChainPointType tx) , FromJSON (ChainPointType tx) , ToJSON (ChainPointType tx) + , FromCBOR (ChainPointType tx) + , ToCBOR (ChainPointType tx) , Eq (ChainStateType tx) , Show (ChainStateType tx) , FromJSON (ChainStateType tx) , ToJSON (ChainStateType tx) + , FromCBOR (ChainStateType tx) + , ToCBOR (ChainStateType tx) ) => IsChainState tx where diff --git a/hydra-tx/src/Hydra/Tx/ContestationPeriod.hs b/hydra-tx/src/Hydra/Tx/ContestationPeriod.hs index f36e7458723..d69a98d2003 100644 --- a/hydra-tx/src/Hydra/Tx/ContestationPeriod.hs +++ b/hydra-tx/src/Hydra/Tx/ContestationPeriod.hs @@ -13,7 +13,7 @@ import Text.Show (Show (..)) -- values of unknown sign. newtype ContestationPeriod = UnsafeContestationPeriod Natural deriving stock (Eq, Ord) - deriving newtype (Real, Integral, ToJSON, FromJSON) + deriving newtype (Real, Integral, ToJSON, FromJSON, ToCBOR, FromCBOR) instance Show ContestationPeriod where show (UnsafeContestationPeriod s) = show s <> "s" diff --git a/hydra-tx/src/Hydra/Tx/Crypto.hs b/hydra-tx/src/Hydra/Tx/Crypto.hs index 0548956859c..827f9b99d04 100644 --- a/hydra-tx/src/Hydra/Tx/Crypto.hs +++ b/hydra-tx/src/Hydra/Tx/Crypto.hs @@ -363,7 +363,7 @@ verify (HydraVerificationKey vk) (HydraSignature sig) a = -- | Naiively aggregated multi-signatures. newtype MultiSignature a = HydraMultiSignature {multiSignature :: [Signature a]} deriving stock (Eq, Show, Generic) - deriving newtype (Semigroup, Monoid) + deriving newtype (Semigroup, Monoid, ToCBOR, FromCBOR) deriving anyclass instance ToJSON a => ToJSON (MultiSignature a) deriving anyclass instance FromJSON a => FromJSON (MultiSignature a) diff --git a/hydra-tx/src/Hydra/Tx/DepositPeriod.hs b/hydra-tx/src/Hydra/Tx/DepositPeriod.hs index 24820a10b2c..def9a75b6be 100644 --- a/hydra-tx/src/Hydra/Tx/DepositPeriod.hs +++ b/hydra-tx/src/Hydra/Tx/DepositPeriod.hs @@ -9,7 +9,7 @@ import Text.Show (Show (..)) -- Nodes within the same Head must configure identical values. newtype DepositPeriod = DepositPeriod {toNominalDiffTime :: NominalDiffTime} deriving stock (Eq, Ord) - deriving newtype (Read, Num, Real, ToJSON, FromJSON) + deriving newtype (Read, Num, Real, ToJSON, FromJSON, ToCBOR, FromCBOR) instance Show DepositPeriod where show (DepositPeriod dt) = show (round dt :: Integer) <> "s" diff --git a/hydra-tx/src/Hydra/Tx/HeadId.hs b/hydra-tx/src/Hydra/Tx/HeadId.hs index 6072738d5ed..942aea15033 100644 --- a/hydra-tx/src/Hydra/Tx/HeadId.hs +++ b/hydra-tx/src/Hydra/Tx/HeadId.hs @@ -21,6 +21,7 @@ import PlutusLedgerApi.V3 (CurrencySymbol (..), toBuiltin) newtype HeadId = UnsafeHeadId ByteString deriving stock (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via (UsingRawBytesHex HeadId) + deriving newtype (ToCBOR, FromCBOR) instance SerialiseAsRawBytes HeadId where serialiseToRawBytes (UnsafeHeadId bytes) = bytes @@ -53,6 +54,7 @@ mkHeadId = UnsafeHeadId . serialiseToRawBytes newtype HeadSeed = UnsafeHeadSeed ByteString deriving stock (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via (UsingRawBytesHex HeadSeed) + deriving newtype (ToCBOR, FromCBOR) instance IsString HeadSeed where fromString = UnsafeHeadSeed . fromString diff --git a/hydra-tx/src/Hydra/Tx/HeadParameters.hs b/hydra-tx/src/Hydra/Tx/HeadParameters.hs index 89cc56c1569..f9ee6cb6d74 100644 --- a/hydra-tx/src/Hydra/Tx/HeadParameters.hs +++ b/hydra-tx/src/Hydra/Tx/HeadParameters.hs @@ -14,3 +14,10 @@ data HeadParameters = HeadParameters } deriving stock (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) + +instance ToCBOR HeadParameters where + toCBOR HeadParameters{contestationPeriod, depositPeriod, parties} = + toCBOR contestationPeriod <> toCBOR depositPeriod <> toCBOR parties + +instance FromCBOR HeadParameters where + fromCBOR = HeadParameters <$> fromCBOR <*> fromCBOR <*> fromCBOR diff --git a/hydra-tx/src/Hydra/Tx/OnChainId.hs b/hydra-tx/src/Hydra/Tx/OnChainId.hs index a8e30527201..e0b9893fbaf 100644 --- a/hydra-tx/src/Hydra/Tx/OnChainId.hs +++ b/hydra-tx/src/Hydra/Tx/OnChainId.hs @@ -16,6 +16,7 @@ import Hydra.Cardano.Api ( newtype OnChainId = UnsafeOnChainId ByteString deriving stock (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via (UsingRawBytesHex OnChainId) + deriving newtype (ToCBOR, FromCBOR) instance SerialiseAsRawBytes OnChainId where serialiseToRawBytes (UnsafeOnChainId bytes) = bytes diff --git a/hydra-tx/src/Hydra/Tx/Snapshot.hs b/hydra-tx/src/Hydra/Tx/Snapshot.hs index 721c9fd61aa..d560400a832 100644 --- a/hydra-tx/src/Hydra/Tx/Snapshot.hs +++ b/hydra-tx/src/Hydra/Tx/Snapshot.hs @@ -134,6 +134,30 @@ instance IsTx tx => FromJSON (Snapshot tx) where let accumulator = Accumulator.buildFromSnapshotUTxOs utxo utxoToCommit utxoToDecommit pure $ Snapshot{headId, version, number, confirmed, utxo, utxoToCommit, utxoToDecommit, accumulator} +-- NOTE: Like the JSON encoding, the accumulator is not transmitted (only +-- derived data) and gets rebuilt from the UTxO sets on decode. +instance IsTx tx => ToCBOR (Snapshot tx) where + toCBOR Snapshot{headId, version, number, confirmed, utxo, utxoToCommit, utxoToDecommit} = + toCBOR headId + <> toCBOR version + <> toCBOR number + <> toCBOR confirmed + <> toCBOR utxo + <> toCBOR utxoToCommit + <> toCBOR utxoToDecommit + +instance IsTx tx => FromCBOR (Snapshot tx) where + fromCBOR = do + headId <- fromCBOR + version <- fromCBOR + number <- fromCBOR + confirmed <- fromCBOR + utxo <- fromCBOR + utxoToCommit <- fromCBOR + utxoToDecommit <- fromCBOR + let accumulator = Accumulator.buildFromSnapshotUTxOs @tx utxo utxoToCommit utxoToDecommit + pure Snapshot{headId, version, number, confirmed, utxo, utxoToCommit, utxoToDecommit, accumulator} + -- | All UTxOs represented by this snapshot: settled plus any pending commit/decommit. snapshotUTxO :: IsTx tx => Snapshot tx -> UTxOType tx snapshotUTxO Snapshot{utxo, utxoToCommit, utxoToDecommit} = @@ -155,6 +179,20 @@ data ConfirmedSnapshot tx deriving stock (Generic, Eq, Show) deriving anyclass (ToJSON, FromJSON) +instance IsTx tx => ToCBOR (ConfirmedSnapshot tx) where + toCBOR = \case + InitialSnapshot{headId} -> + toCBOR ("InitialSnapshot" :: Text) <> toCBOR headId + ConfirmedSnapshot{snapshot, signatures} -> + toCBOR ("ConfirmedSnapshot" :: Text) <> toCBOR snapshot <> toCBOR signatures + +instance IsTx tx => FromCBOR (ConfirmedSnapshot tx) where + fromCBOR = + fromCBOR >>= \case + ("InitialSnapshot" :: Text) -> InitialSnapshot <$> fromCBOR + "ConfirmedSnapshot" -> ConfirmedSnapshot <$> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded ConfirmedSnapshot" + -- | Safely get a 'Snapshot' from a confirmed snapshot. -- -- NOTE: While we could use 'snapshot' directly, this is a record-field accessor From 40be8ee09fc73745239f3ecb1daf04dc1baa60ff Mon Sep 17 00:00:00 2001 From: Sasha Bogicevic Date: Mon, 20 Jul 2026 10:43:37 +0200 Subject: [PATCH 2/4] Persist hydra.db events as CBOR instead of JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ToCBOR/FromCBOR for StateChanged and StateEvent and store event rows as CBOR: 2.6x smaller on a real database and faster to replay. Existing databases are migrated automatically (schema v2, one-shot re-encode in a single transaction + VACUUM); a corrupt row aborts startup and leaves the v1 database intact. No downgrade path. The client API is unaffected — JSON output stays byte-identical. The on-disk format is locked by roundtrip properties (genStateChanged now covers all constructors) and a golden CBOR file with one sample per constructor. Signed-off-by: Sasha Bogicevic --- CHANGELOG.md | 10 + docs/docs/dev/architecture/event-sourcing.md | 10 +- head-state-viewer/head-state-viewer.cabal | 1 + head-state-viewer/src/HydraVis/History.hs | 44 ++-- head-state-viewer/src/HydraVis/SampleDb.hs | 14 +- head-state-viewer/src/HydraVis/UI/Update.hs | 2 +- hydra-node/golden/StateEvent.cbor | Bin 0 -> 81917 bytes hydra-node/hydra-node.cabal | 2 + hydra-node/src/Hydra/Events/SQLiteBased.hs | 119 +++++++--- hydra-node/src/Hydra/HeadLogic/Outcome.hs | 207 ++++++++++++++++++ hydra-node/src/Hydra/HeadLogic/StateEvent.hs | 10 + hydra-node/test/Hydra/CBORSpec.hs | 26 ++- .../test/Hydra/Events/SQLiteBasedSpec.hs | 54 ++++- hydra-node/testlib/Test/Hydra/CBOR.hs | 34 +++ .../testlib/Test/Hydra/HeadLogic/Outcome.hs | 4 +- 15 files changed, 472 insertions(+), 65 deletions(-) create mode 100644 hydra-node/golden/StateEvent.cbor diff --git a/CHANGELOG.md b/CHANGELOG.md index 1006a93844c..a6b3c572e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,16 @@ changes. observed a block (so a stalled chain backend can be detected even while drift is frozen). [#2749](https://github.com/cardano-scaling/hydra/issues/2749) +- **BREAKING**: Event payloads in the `hydra.db` persistence database are now + CBOR-encoded instead of JSON, reducing disk usage and speeding up event + append and replay. Existing databases are migrated automatically on first + start (one-shot re-encode of all rows followed by `VACUUM`); a corrupt row + aborts startup and leaves the database untouched. After migration, older + hydra-node versions refuse to open the database — there is no downgrade + path. + +## [2.3.0] - 2026.07.15 + - Add **selective partial fanout**: distribute a chosen subset of a closed head's UTxO instead of draining it all at once. Introduces the `PartialFanout` client input, the `HeadPartiallyFannedOut` server output (with a `fanoutMode` diff --git a/docs/docs/dev/architecture/event-sourcing.md b/docs/docs/dev/architecture/event-sourcing.md index 1e9aab59d08..12560805bcf 100644 --- a/docs/docs/dev/architecture/event-sourcing.md +++ b/docs/docs/dev/architecture/event-sourcing.md @@ -21,7 +21,7 @@ The database contains a single `events` table: | Column | Type | Description | |-------------|---------|------------------------------------------| | `event_id` | INTEGER | Primary key, matches the in-memory event id | -| `event_data` | BLOB | JSON-encoded event payload | +| `event_data` | BLOB | CBOR-encoded event payload (`ToCBOR`/`FromCBOR`) | The following connection pragmas are set on every open: @@ -39,16 +39,18 @@ The last-seen event id is updated atomically at enqueue time (not write time), s #### Schema versioning -The database schema is versioned using SQLite's built-in `PRAGMA user_version`. On startup, `initSchema` reads the current version and applies any pending migration steps incrementally up to `nextVersion`. Each step is defined as a case in `migrateStep`: +The database schema is versioned using SQLite's built-in `PRAGMA user_version`. On startup, `initSchema` reads the current version and applies any pending migration steps incrementally up to `nextVersion`. Each step runs together with its version bump in a single transaction, so a crash or failure mid-migration rolls back to a well-defined version. The steps are defined as cases in `migrateStep`: ```haskell -migrateStep conn = \case +migrateStep conn reencodeRow = \case 0 -> -- create the events table - 1 -> -- (future) e.g. add an index or new column + 1 -> -- re-encode all JSON event payloads to CBOR ``` A fresh database starts at version 0 (SQLite default). After all migrations run, `user_version` is set to `nextVersion`. If the database has a version higher than `nextVersion` (e.g. from a newer release), the node refuses to start to prevent silent data corruption on downgrade. +Version 1 databases (written by earlier hydra-node releases) store event payloads as JSON. Opening one automatically re-encodes every row to CBOR and runs `VACUUM` afterwards to reclaim the freed space. A row that fails to decode aborts the migration — and thereby node startup — leaving the database untouched at version 1, still usable by the previous hydra-node version. Note that after a successful migration there is no downgrade path: older releases refuse to open a version 2 database. The legacy `state` file migration also inserts CBOR (the file itself remains JSON lines). + To add a new migration: 1. Bump `nextVersion` in `Hydra.Events.SQLiteBased` diff --git a/head-state-viewer/head-state-viewer.cabal b/head-state-viewer/head-state-viewer.cabal index 50932ce589d..4cfb62cec94 100644 --- a/head-state-viewer/head-state-viewer.cabal +++ b/head-state-viewer/head-state-viewer.cabal @@ -57,6 +57,7 @@ library , aeson , base , bytestring + , cardano-binary , containers , hydra-node , hydra-prelude diff --git a/head-state-viewer/src/HydraVis/History.hs b/head-state-viewer/src/HydraVis/History.hs index 07bebc4ae5e..51e0a1813a4 100644 --- a/head-state-viewer/src/HydraVis/History.hs +++ b/head-state-viewer/src/HydraVis/History.hs @@ -6,8 +6,10 @@ -- The Hydra node persists events using the schema defined by -- "Hydra.Events.SQLiteBased": a single @events@ table with -- @event_id INTEGER PRIMARY KEY@ and a @event_data BLOB@ column that holds a --- JSON-encoded @StateEvent tx@. We open the database read-only (so a running --- node can keep writing) and replay the events in event-id order. +-- CBOR-encoded @StateEvent tx@ (JSON in version 1 databases; we decode CBOR +-- first and fall back to JSON so pre-migration databases and archives still +-- open). We open the database read-only (so a running node can keep writing) +-- and replay the events in event-id order. module HydraVis.History ( HistoryStep (..), loadHistoryFor, @@ -18,6 +20,7 @@ module HydraVis.History ( import Hydra.Prelude +import Cardano.Binary (decodeFull') import Data.Aeson qualified as Aeson import Data.Text.Encoding qualified as TE import Database.SQLite.Simple (Only (..), SQLData (..), open, query, query_) @@ -50,7 +53,7 @@ deriving stock instance (IsChainState tx, Show (NodeState tx)) => Show (HistoryS -- recover. If the file does not exist the underlying 'open' call throws. loadHistoryFor :: forall tx. - (FromJSON (StateEvent tx), IsChainState tx) => + (FromCBOR (StateEvent tx), FromJSON (StateEvent tx), IsChainState tx) => NodeState tx -> FilePath -> IO [HistoryStep tx] @@ -62,13 +65,29 @@ loadHistoryFor initial path = do "SELECT event_data FROM events ORDER BY event_id ASC" :: IO [Only EventBlob] SQL.close conn - events <- forM rows $ \(Only (EventBlob blob)) -> - case Aeson.eitherDecodeStrict' blob of + events <- forM rows $ \(Only (EventBlob blob)) -> decodeEventBlob path blob + pure (buildHistory initial events) + +-- | Decode an event payload: CBOR (the current production format) first, +-- falling back to JSON for version 1 databases and pre-rotation archives. +decodeEventBlob :: + (FromCBOR (StateEvent tx), FromJSON (StateEvent tx)) => + FilePath -> + ByteString -> + IO (StateEvent tx) +decodeEventBlob path blob = + case decodeFull' blob of + Right e -> pure e + Left cborErr -> case Aeson.eitherDecodeStrict' blob of Right e -> pure e - Left err -> + Left jsonErr -> fail $ - "HydraVis.History: failed to decode event in " <> path <> ": " <> err - pure (buildHistory initial events) + "HydraVis.History: failed to decode event in " + <> path + <> ": CBOR: " + <> show cborErr + <> "; JSON: " + <> jsonErr -- | Wrapper that accepts both BLOB (the production format) and TEXT (what -- a quick @sqlite3 CLI@ insert produces) columns when reading event payloads. @@ -98,7 +117,7 @@ buildHistory initial events = -- Used by the follow loop to fetch only newly persisted rows on each poll. loadEventsAfter :: forall tx. - FromJSON (StateEvent tx) => + (FromCBOR (StateEvent tx), FromJSON (StateEvent tx)) => FilePath -> Maybe EventId -> IO [StateEvent tx] @@ -117,12 +136,7 @@ loadEventsAfter path lastSeen = do (Only eid) :: IO [Only EventBlob] SQL.close conn - forM rows $ \(Only (EventBlob blob)) -> - case Aeson.eitherDecodeStrict' blob of - Right e -> pure e - Left err -> - fail $ - "HydraVis.History: failed to decode event in " <> path <> ": " <> err + forM rows $ \(Only (EventBlob blob)) -> decodeEventBlob path blob -- | Extend an existing history with newly observed events, folding them -- through 'aggregateNodeState' starting from the final state of the existing diff --git a/head-state-viewer/src/HydraVis/SampleDb.hs b/head-state-viewer/src/HydraVis/SampleDb.hs index 77daed4cee3..fdbbe809486 100644 --- a/head-state-viewer/src/HydraVis/SampleDb.hs +++ b/head-state-viewer/src/HydraVis/SampleDb.hs @@ -3,14 +3,14 @@ -- -- Real Hydra nodes write to @/hydra.db@ via -- "Hydra.Events.SQLiteBased"; this module writes a file with the same shape --- (the @events (event_id, event_data BLOB)@ schema, JSON-encoded --- @StateEvent tx@ per row) using purely synthesised events so the --- visualizer can be exercised without a running node. +-- (the @events (event_id, event_data BLOB)@ schema, CBOR-encoded +-- @StateEvent tx@ per row, schema version 2) using purely synthesised events +-- so the visualizer can be exercised without a running node. module HydraVis.SampleDb (writeSampleDb, sampleStateEvents) where import Hydra.Prelude -import Data.Aeson qualified as Aeson +import Cardano.Binary (serialize') import Database.SQLite.Simple (close, execute, execute_, open) import Hydra.Events (EventId) import Hydra.HeadLogic (aggregateState, update) @@ -62,7 +62,8 @@ sampleStateEvents = in (n, scs) : collect (n + 1) s' rest -- | Create (or recreate) a SQLite database at @path@ holding --- 'sampleStateEvents'. Overwrites any existing rows. +-- 'sampleStateEvents'. Overwrites any existing rows. Rows are CBOR-encoded +-- and the schema version set to 2, matching what a current hydra-node writes. writeSampleDb :: FilePath -> IO () writeSampleDb path = do conn <- open path @@ -70,10 +71,11 @@ writeSampleDb path = do conn "CREATE TABLE IF NOT EXISTS events \ \(event_id INTEGER NOT NULL PRIMARY KEY, event_data BLOB NOT NULL)" + execute_ conn "PRAGMA user_version = 2" execute_ conn "DELETE FROM events" forM_ sampleStateEvents $ \e -> execute conn "INSERT INTO events (event_id, event_data) VALUES (?, ?)" - (eventId e, toStrict (Aeson.encode e)) + (eventId e, serialize' e) close conn diff --git a/head-state-viewer/src/HydraVis/UI/Update.hs b/head-state-viewer/src/HydraVis/UI/Update.hs index b2a4af6a09a..ad6433d415a 100644 --- a/head-state-viewer/src/HydraVis/UI/Update.hs +++ b/head-state-viewer/src/HydraVis/UI/Update.hs @@ -242,7 +242,7 @@ applyAction a m = case a of -- | Background poll loop. Reads any new rows from the SQLite file at a -- fixed cadence and dispatches them as 'AppendEvents'. followSub :: - FromJSON (StateEvent tx) => + (FromCBOR (StateEvent tx), FromJSON (StateEvent tx)) => FilePath -> Maybe EventId -> Sub (Action tx) diff --git a/hydra-node/golden/StateEvent.cbor b/hydra-node/golden/StateEvent.cbor new file mode 100644 index 0000000000000000000000000000000000000000..0f75f8d6a0a4a368f2e4470376e7e0df72c11a9a GIT binary patch literal 81917 zcmagGbzGED6E?g>cS^gIgmjmXA`n=Ek{qej%-PPSQ_nA58%sDf2&794Jct1$z3pqCGu4>}~z*?V{=N)aW7^ zA%-GqJG?nGP~X1BgD~Oc1$%{4Cbz9Z}0P;9vp=Pz&|3;i>~f5LS!Z4 z^~spW;9elvfA{m3QtS_96eHw;ZH3Wpnjj+JFH;kHA75ur4{c{(cWXaeCukJnqR-om zP$Kq5f)DyfI6-v(2H@uJ>u2wyuJn)C(f;<$-Rjg#CwUWN{N4K2hKolZFhU5+RVFCs z&v9MU?5*u|z3e^g?dW-<86g% zS^jd*xcth|^I|Lp34y>N2m};(gFzv1C=3pTLm@EWe-J3}83L!{!eXQ$N38n;iE4$J zggFNe>8Vq-y#*#Ni^@F%zt1AZ7}iDgFHI@AXEnarpN@AuuDXAayZH0ki>R(2fmj9z zMfF@hhG~a+MPLJKJ^1RQ&uQq*k6ZRI+kYF??TXsCP*1>`jK|xWV za0Cnj2#0{epa?h&_!okJUkFMk1xl2^GcxscJiO<$efpi{R}!XU2qf%PTSDMya_GcHzz3inR?~9JPLVG?zPHjs@Ln<$1z?d z<>>0^7$~r2lxlXsnvt8|W301N>KllYOnRiq@L@y5m8`)}UR}OXa4cW_^kj^dc%E%& z2?oPLC+HMdqkPd$*3KS!p3WYA3Vy19Li#%U>H!)Bg8>r@Y8f0@NIfVZJsc1p7(DPo zzyL+9ZGLv+$Qo!tq!H(S8$qhexdUTj1V)EbK|kEvv|Hn zKGq(-*0z4Yt}?K4<$h|_OO*vfM7`)UNebRX)i0@6VbALpo+hQg<=x3Yg#k2f z{WMcmbgJT0`>I^}%|@@{yX%=Io?F*iB)=F5GInYLX`<$b)lAk#LaQz3QQPe?YSGaj zs1#?yr%CU``>?VSv_mKXp`ro8E8`8bS%%yzd?LutsA9xsq#suwn{)sA7t)HVYOjZN zpk5jo(%)nXfv%UuxzttXv{d^=4OmcaxNh;vMD;)O_{k13+?m#r=ZtiXU|e2iQl-r> z+@}2KKwei-TYv4#K`(SkGS<|c^&x&VdZfwNo(JX_m^Q3W9BZjlx2su~01V6jSx_)JF2K3T;#E!U`($CZVl1?_%Q)t-`1zf zbLGJ46r8Lj8!DAV>Uo3hOEpv3@R)@rXUK0BA1Sj`r_FowCoqAki0`F`SYTxUlJIYN zFDh%2t>u-Rxb#y3A6Y3=7?y?&Ct+5)NWpdq4RWk+OW@woO=iMpEClyrLfFTOR1E7H z1Uve{_S%`d%+9|;3Md$|^qpJOmR^&Qb7_b7xYICrIRa}TE%e#5`shSZOlGxXeO*&*H#38Khj+VWeW- z0oG}Wphl{5SLP{OYq%*)BmB5rJJ<51gYfx44RKpOhGly0PO=vn`1*3ylQS=b!GJDd4{)mgE<_`(<4}e13z%@Kw7`TY!P2VIBEhu%}N(fjBx4 zm$0Db9}!qd2(@i!PF)#0qHh(ag!%7a9#Rm-oY~E=`y9Yr1c_A(BzLH_JBU6g-tLtg z2fHYFgetq1^CSVk+&IyVWyw>|T>ESIhQR62DdG#*F5l%C_*rCj7}hpzmii5Q(+h#j zd%87Vb|rI59c(Aa*UkC>g?C%pN5e(%#Fd3&4$4FZf8C*{%D5g4heZC>FGNnM7(=%% zcGLc@={4sF2P5c>bFKv22)6tQvA-m4Wwkv$S7fKT>QUm~e&Ip!b;ej?2IqEfkK34n zoTf=H%i4V-C(jo6Op)Uz^kVgWwI|-)nbnnO(><`=K|fcwk^)D)|Am;Yl*04gAKNxu zS1j=9eyKo}!Sw}5$c1=b3Z22Hn&O?%jC3}Pxpxj8jL`ODHoAf?218%Q+bmK*U!d3` z$3l8t_3)0EV6DiHv?#b}Km+)E%jFJ9m%4c0G;_7iSs1Zu*yc3hhmj|(OYO5a!1iM0 zbuzxjou!2FG<=JxxptjWjUc8BvB?BX%kKZtuj)s24g(amR&RL%`U0VRpXJCO=@xxY z&+Cdy6~jjtMc9PG#v#b*7SIkJ2k{$qs=!LSdxpG@O`8H zo3Wd<%P?e^K=)5JaqMb#6?v$Y0UHtZj$ zE|$29<-S2Kj9#1s80Oq!Bs&B+4-^lg zIL#wHil>(rSgnb5o`{med6 zk0_IkNVNyd3ZsSP46y-TK>=_KDFcTP0tWJgm`nxWDOEfUS}SX!aDUpIXCbZXSob|e zdDd3FzEm~f%e~%&(r%(xPd7iRi%|sYM6WYsL4FA`#sA@{5`lSe)hHDwChyxiX94HD z3$-DUK>C{0dztHO`Kq6W(ojd}ia$po#;_n4k(ZatjygXtnh42dMH^(U#I0q2}8uMmj$L`cVEHKKt68uPk47DOO+GVzT?@kyOwj!<~HN6B!cASyCl@ zLj-<2P7#8_!BcAP@t+Z)^V#$MP9`*xf&oH0Y65s*Pz2zBL`CC+in~Mtb^b6Jxf=XR z8t(T262HSQ)Fax-Z)v<-yMe%ml7f^NrgtybVM22TpI@mo@k4*zqU@9_?QRpJfCN%> zS`dJGSG6-)&zRKfjb6i;2q++rGvX0Upq${iDy%B+e;cObFIU%+LiW-}{~J+$>LG zSG6SNdEEOLfrT9aWLL>2AQBT30ttzu&eR>dJB?*i+Y`B_9mN#2VuLKYCBQbo23>LT z^YikR5fqdVQuY?*H`g&zbu$ZA*Aex%bKqCwxA8Po7qIno#~@>jl!v$cmU9r2FT&cT zR{A^Ha1_;tHM+gka*J9&+bRMK4YvZ7u~Hg$KGoEGDq8uJ$ypf}Qodl_QKj(^wL!xW z2nSBVfPehF9JPhTo&D@hl>YSJ@v%~O`>^{4|6&}TPG$=9(N|;JQpTWmvEBQFR-T>B0D)KxmT0=FZu$r|s=JyZ;ON_t<@%|Ge zNZQ9*-BUQoUPnsB*3830+FuvrAT2JfC!*=|p znr+LwAJ>y2I!)7b8p8mAeYy^;Q}fl7m$XTKAN|jp1W<2}Ft*qrDP3+M890k8_i%rlgM7#2Y8xFKbaRdJB-z2AXq{iY>?PLY3)7SjU6zO(!yHq z!kT7*8g2sa0xF_G!3L^o7*kDiw6~M0x~7W^3TnUreg5(Kx`2 zhHa1yZBB;mlsFt^`)kw}g?7T9&%AB-Bk;M+o7(c31n@8e|H&wNqYPB75}iwrTJ$+xsp@vmeDHN0?dVo`9tjv7xYA(CD?)aqocg<5019F9d{8Og|2%b15JEg8u-LuPG)8L@klP}slvG`_?TG}ykN$8 z>F%Y`lIGSFH?xHh3x&XG@t=5pdR^JnKxSLt- z#cmmH%&hejGHK#e6!GB-zzGYB0;!m)8RG1`bClQ5#1-?ndUTSDr6e0?tREKi}HSHS7*Gxl_L`M0QCK`( zmxRo*fTsilcsBNy1!q!!KRA(J6jvFFlNe${eY|BK@=$D#fphIFj+hlhA!gpi;eSTC zR*@`rMD9GP8F(R%jA6i?bD21T7-x*&;v)c>S)3O_3FKtcR0}amiDzVi_wQN-LB-_0 zw>o7dk+OIvUqBazoPBZJYcIv=aJrAaQEt& ze)~&FiSHXf!WIegK#TOUSFf<~v2g}p(CPK&4jvO$rw6~QlBH>zuTV)9@RL@7G#}e$ zR?x1lj=u^EZ>g`{_D-plLVXliiv02RE{dtCksASxjmBKXPS9akqcKcccy{zRQi4WL zZS-4SbQFr8*==+$NF}i;zGu0f2V*E4S*!ei`@`zi@rg}N_~Z53jOeT;oG!Cp5g24s zc3BN%rBv$h0cXKw)js}37uY%74eGIBo5JZrk;95;z+V%vZ}|_yB{HY|GE?Z(ikyiTQGc89PstYBPy> zaDKp>l%6=f>^%Ak^oSu$dB~@Z{7~Kk|X{Rrh&Ds7S7=q{q z>N|hEThzG3UXq=mC$e2wci@!*1UzQf>Qb7C%kcp*|2I%2%*dVe#wMk3PV!HHV*~i_ ztcb{B^ri`8F?wJMN6E2xosA8Nf-7$Dv$La5x?J+%>}W&V&|(<+@UYLnl=HJzhd;*$5I26k*bxrk^lnhK4OW zIS50I3^MUTv8=5}8@B1MGq8&0Y2FdzNo7TGG~(@AEqs?RE`6 zKMn6H(FS9u@m>LVI7&ji~HUK)(!v_v+!>+Amwk)*gBfh_wq)$ zib?DlGtcji&*`yOw%!-60p`~XY6*gh|2f`OsYXTU;J;a{ApY3 zZjGbe-?LdoUgvQ~M1wd7qb_fwz^?hgW3RKk#g*vX*lEVUMnYrk+HXlIcTKqjWHhHeyf!VFpOc|^9EpFdECL5}*QNP*+U?v7mDpaQSi=(lU=>lo>!Ke4b^dYM?8L`1 z)OPFvp#hs;$qcQUgpSenX;vnnw4_M_adwDYnx*EO@?DQA?<{%sLA0~3G-^o?WP$B! zw9!J^m5nA;BU*O^xceu+;LH&tCI@7m-%w8=zsMPwQrXRf1?NCthobq2Err=>8{|Gw zJz%7(z1*uN+heG?#&tzgJ2~P68qlnI!!eq=|AA8~zYW=IKndiP%-o)^1?B=uz~87q zlAh%Np8(6MiG``D0%;63Bowe_ zfL#L&Sxht;7K&j~S&7jZU#he|{cdF!8aTzB<2A*&JuaV`q0l^n_5A1QDFs;n@%66m z$QIwZsu-}6yyQ#c7r9t&%W%$`j8%S2on~?be^!|Y$1s&{gh&6}eH&9szjO1rjh~xq zpRQU*V+*4aQOJxz+Oncr@(849POjYR5>8KHQuCEqj(d_E8_vfqP>J_=l>vf7M9Kic zMSq`Q0hAunLiaT^}_sOP!`M#kemk|%*FRx6 zg+Mgu<&-PyBb3{BnKni+K9VzJf`N?#_xk20=5i!A4lqkUu$-B7PSbuX@DBZK0`*(^ zX!N)GACq zhCh)ShrMq({8SO<^y$-=?(6sNM{NJD5?gF-(*=&ZFlml9znzW@zdGuizO2^R3fU{P zv}*C6xX*|%NGg+e@4oS*1t1e+ZdI;+nbemon-0?1!MhQ^IDVF-+6;!z(J@d!*ueWs z(qkN5H-1LT>^CTtYkm{IZxH&%_3nAKL{AhhHi+fl+}k<}qji0BB}JWGO+>wQgwW=H z^m3Vh$Vs!z(g5~l^Mp9FdZ=XX<9)Xbk{r$JUYs}J5wt*LMdUaLaQezpV9=9kcD`>+ zK1UV5fA-dne4d{iA4D0Wh(z?SIZa7w8;dxBGZVKKEH{J zx4OZf;mOyvg=jwblG+e}^ZcRPcNm^FA{Uu|z&u;!eZaU4kWVUuvfPfd;jgvdUmUGNFO(OQ_n0 zh+h-+yWh-I+AC1K+3kf)usz^ui}m}bCMDBS-*C$+YF}k<5c2N9epIve5QgQ049HEx z1WJ#F4)B}(H+GWHzU8i0#@ch5mHCl6DCot$hqf5Rv)pCs8=$}u&RFjky{+m$3)8BzhTdqv45cq2L~ zTmcYeN!?AJh4?8p`m@zGwE@Qq8$1E>?r+xJ;oC; z$Qz19J{$U3?DhLbZxg>RYivo$@A1h{vwtKZ_(XCQbAo<9Ap?jKZm5^M+o+$Fm}`uN zc2pAg_>wdD*dlv7HOvW#vX=ZXD9ly0MkJ?!+%LDuh!~KVC*KX@Cp!wb*11;Ow>b|(gT%E#&xNnlQiynru5*+{>s}v8KJ2U z7}T&$LrLR3Z6mx9*8I-)!dW-92;)!|%%IUBZSIHOyrSC6LC4C^WisW9ugGxyrtxnx z3+hg<5GnIce$^+(YB+~Mb`WJ~?vn;UuMKXNYHg?9uJ+q+zao}q;7%sgcRSQ3O|iKV zkfiua**3D~3Tj#$^`5>^uln?W=Bmr@x4v2-^ZxhxqINK-A>q=S7QTHx@5f44K81ML zu!dW#L5oq*o+~4jr?_^^J){MfEFUIH?m}eSKm4rM&DG^K3o^Oh<8V~09!m8Pg+Yq9 zJ*mZwilfb9H-d#p8b428F~EI5lj}2tgP#;G(Jh36f)*8vLyA)Xcg>_53@ z`P+pV{zPsJz0Or#&xvaJDm}l3LPgzUxhf0;YYsqIz%Rbal%|m4#q)-;w^Ceg6>s#b zoWdd{54%6?u&x%C`T@ySAO_F_gA)}XI3d4T{NcCegjDWI%i^QrgX=yLY^gclIH08g zVr=;r!HMydTY^HWcxg&?eq^Z3$h0k8@Z*o?W622jkzL!<8AAvIuf8<|@Crq?Mrk?@ zZcqXC`hP}k82XrL2^%K)UpMAIa3=abEzsAm1y%lEZ$qh8U=D@haQn$-~8b*aCyy0+49G-gH&mTt6=gZDrH5xPTs8rV#Odt#XJj7ky(xNdtNj zv^=?}gt)-K{$yXfGRlHQk40jrD@A1Qe!msGGWtvS+79L0&|Ae9?pI&O@tn=8*k4Qs z+8(+t1|0a=%CxBqEbeDErn8^{3%(_yGL-c+-GVho`qs=r=Lx^USAwA7yeVkO6xiN- z9~bh*us+nyWSel|0liCE;qU{3zEGo|vjg-CIl+AUKzLM-{c+ahV;ZlyxMMgc2 zIDR?7)HCXN_79Hf8s)eoB^`-}R}Nhz_cXMCDDMKnLsSkNT1#ig%V0@N-^?24V?GDo zUEeI0G#d=QNZ)q{G1NzeES5qM#thNRte=&I&(o}3=;Q|PeObR)-f((b?jMIpBt-K(Uz3_(wSM6T!^2yMnV=%0Qyk|CTii@BF#lhDQiT_tz08E z&u>nC@f!CfMXH`g!N6c_tK>a@{PI~%J#JY2SMvTn;?gD~01Q@&_4x*v3AZ{UNH&q8`ACmx`AT3dw5R(v9J!yL*#Q=Ae z00~_klVEWVUl-6H{I38aP(8$4NWs)VSVe;0UqnSuSsElj#gc&2*=;CeFU!pDKymC` z#_R(L4Q=Nb@agPrI4Cm7!!Uht=-my@WV|LnR<2l4B&hzWnK-RGC#2)-i5)={90*zE zU^w|eROL9BzU1}dQD~M-j`bAG2oX+V##{9|r#$cFuqF9T++!7_AZG?AIviFleYmX(~{WA=c z?ZeaQ3Nz~rjl}9vB)P$lU`?*YZ!O5AHfugM4Abo0K&~|gC}FtlfX3zlyxO)js2eSM z#a8vGQ3DL>{%6S{I(YD&tOIw`oS}(z29QXu4OUrh+F%>CEN>Qv_)g@hETGSj@dZV%-uTPgEDPSvzZ+?~As4mN-2r!N=+>(=6$xCzj zPZxNEoDcVd#2!;>k7auyRtBDYT-%d|0w_+9^uNf+52Gn6;ikkdtmJGS=pW1<w-9VqRs<|X88_s8RJ@Im&nesL6c7x}F=k%9xu_x4gS4zXeV zLH=BmzQUgckfAOZA;va1YcKpX#QXFrQ`}Bn%wxWom}^0ke3J-x<5S?2zy@*tlR*Jv zC#5dn9irfgR&{eXb~m%uaS(MA(A4|GZ*p0$Cgu8jR=U30T!N;NEaE=@rIkRXu@wHSR_=N-;5`__l6?De54$fBb}P(f-2P zE`hE&n0u}jl8GrV^BE5C?1;C^FZ>K0hP3(L2ThN0N@SVx5pp@rFqk523yy z|3i7n8Ha3fSvm`}oLfF1#GT)ctW2k;fkudm&NRU)!D%Rs^k6K}e~S==%X zOP5P-e|||?vVmK|&mJv$6^E}7V9e065@xto+0sA09KxGs`yA0!O#M1YPO_}bE9s7} zq%DxG#|Fv$Dkf|$simuku}8aTC@F}z_$ivYX{t+!Ns8Nw z`0LmR=>A!8qOkxY+$&q5UeS*dLK-=&BE1r*`@)v^Km2r~DJ1r|0*Ik{+lIaV1nDFh zsNR}j%-ghTsB==jMR=BlGuxc-mOvE&jQ{_DYW@y?I`(^&(vy7Sc_!ZFN4#^dhnhA& z_bUDTU|?>nC-nTaO9BSyc$uW*xqoc=k->xLX(A6QM%izca~-*RP7Ri4tc2PH92cw0 z|8IQ39%fo<(wZ14H$MS&DIwbc4WSS}H3hW4yPcb^zOl2>AHJnSX-}G$ZmwhnynrEi zwkX8~@f7yCBK;ZX6-Hd1_Ne|TZveogdF6pP{~ixp0uS zhQH7sN$iZAuW}S_BkYZyi(1s|6@H+p9V=nPIZ9w`!_aKAK+Smm;K+; ziK{daZZqbGUtFCTx1JXdPwG|UewJyvag{B z*{+=OrEF2D^Ys2pS1wI%{z>7v7T0WO6y|%0Sg)(%* zPnr-UP&le?r}Gu5SeWWjt?F4ZdH13`dpaOvTP;D$fDo{fB-Gd-hJS+Uy9bLHdn>7F zNt)@4{2gZl{f__+%|RHnbl$7boK%P%VN4afY-CyCepif-RP{y-IN9rg;&(>BZgg{6 zU2pTt6tg+xZ=3zDw)`s`-k^U`qp*Oq5`f*-aaRz%q(`fdXTYWx`<~8id#ush3|E6y zs~3tT{_n#yP{mwAOw!riR!dJ9E#RZ>1*2u?*KTB@D(j!>k`Vi6kZjI ze{JGM1-y4SV$j8}PEK;ZPm@FPMj!?(vc4|cd7=|vBq&Zk`20Ji%~PR=DWP`m)z&rZ zh2r}%qFsmv2mzD;@Oof40P@>*h)aYA!H0vB$>3Z)P{$A`{t!YAv{Wks9umWZuT{Obw zgb4WOsTd*baXX&v5C|P3jE)IL#|))ofzn;Zr9%QWQU9k%#G9XARZU;V&e>NbNGHTy z+n3)(Nz%mB%S^{tK%C#yRzcLvSkzzoZ^_M!bOf8Gs*(Q&>qPv`;Xr#rk0Mn(*;$V2 z1$rGgiRf>z-e{X{CtvXWJ`3*+(oqoeEu)a1=blsb^IpN*I znJ{XLzXot7e&lMvI?>)nqNC;^^9n+D| zw*J};>im29l1Gmqu!IaQHR&|_nC_9?XQl~*YFpVP?Sgs#W@?VutqzT;d?AV{+susXSqK?z1=`l&l1UsQDM{9N@fjJ6MM&~D6y6_hPcN!VDk4~?vP zE@$IJ4b4klA(Tp*q>kmG;gx-LQ)`*5FLzW~2Y|3!*4am>cJgo+)q}m?QKbwHJuY^j zwFpa|$Qcn{h|Hry0dh>^+l%hKkpFeW#2|gW{OHiC{aJBX48P{Q$Yq)N^*VbDO8ULA zl0HMQ)+%Qw&8@KXyZVZrly)3X2N+DdHwHxNj;#sfN2Qio<)rgM5L=~m7$Y-*_oJr+ z(mB(6LB26e0T?8?(9x$$Y_B<56u65^^cv%glO8VcY!qK!lz*Br-(Yl2=~~encCe2}6#6w|hI&Rb@r%coR+p>hM1Mkb*)dIxU$}voIMz0m>ee?D0Kc4ZPFj8WJ0)!T9m>Z=0k0 z3z4#Q>uet`g@cPjeVbUj-F7uS>{&Y5DWaB$&a9I6Qn7PUf=XsgVlOdJZ{$`~F;2Io z+g!LTxp!O(RWHc>^n<|Xck{^uwnT1O%ga_<>kG4{8*+tK^| z-s__ji&_{YK@YLJ7jli{$;cqhWIlZ2>ty5_(+2I$IA){tJLlD{im)mWMvKMb;9?P= z(+BRW99(&;0}(sspQ)*6KQvC#^k!4iU=f`5<(QN&ZDtO{e!$ zgEAJpVif-JI7J#;DL`^j#RNg^Uz7_K;OFP%5k})E+|IWXagGRbb=rDdo=vwGm7Ed` zq(~76Gcux>S`h7y0ZwITw=fO&5D|(+IG^aTWSpn))b9<5kJY#(Pu}pSZ-u5{U4}pF zO61-Ve{ps(nt1T?$CNVLJ6wpq`r!4gK_XkPncrYxN+MC<+r1E?YwjZa4*cYq)|2aU z-B@>l>?$cHrZSGNPrzD|pG4Z}Tl$^NoTY?=BdX7f1@3M6?iioH)M)ueH2amUAO5de zCOphfIMa90J!(--{mtWi7*b|_{)N}N!(vZa)fclwX0WqXV_JTq$9A6IZI}pLQabOI z7bsb>edfeO_$?Zl%IuASfZcn96>d{SM&Lc4qMuJKUlOG6y=d*)=qjIXnj`lJzVQ$o z+)hdKyZ%DI6K+P*tlDb&h3h?`mg~mmKHt`U3{)@ONJIPs^Ku?0r5HHy@4s#z%V;Xp1xJ!vG3a#`7ZvBc+*-6zFu`QoCRA@gf#O`X~b&eA$; zeZ9T%x0)*N%INsdJQf!jE_Qq7-{yNae9*51fH+ic5~#d8pazS~|2Li>{wI%*cAU=h z-rM^1T!r|HWz2Q{M3)Wz)x#ueHZGmCvqec5fO3E(-Z(&V10xMRqvM!;5OIr>^Sv*X ze2Qj&F;{{cwkJ<%poPi`ag-leMU;KTzNJ>5a`N3(*PNHX@Ut(aY3&^11FkwCkO1ZT zrObH)MyhDv)Rym*Y6Xkdmn3m*e)y-}HOV*jEAkgk#X%)P-i3PhK1d=dJIHd!XVj7G zgbAB^ufhNwM!sBz7cZ(N)OmkKBFQ0B=64(v{FiLWl`I>^9PgC-P5%>?5JVJ(;-XGQ zHTx8`Y=;}2A9#t{qgosZk1LrUN4+e9coYS|V=;OmcEgVhgFfF5D3I&mb9;<#-Rje5 z`y=fC1c)?sj~;E2SVqd8wSKxl4PpHMN*!YULUtteZc(gr>-B<{4w^y=GO@pnYNku` zIP1S!tS^E4v7f6n^e|J-?KGXjni5vBP2`$JH?ephMagM-QShz(d~HhJ<#L!)%N0`T zZpWz{%G!iJ_6J^EYvt~LSv)|b_oX)uTr(Q4Zy_#kscqKXsP}pnH#a=T#|pA2{`E~u zJyr4P%Ff2;dt)D>%co_FNCBx|p(&*?e4rS+4DD5X^8e6*Hu zupR>n7$U(JNSBji#h~4_oFgbQI#K1wy!_?05i@L*S-?4re-xmJiH83HO6}@SuTuIA z6GC1|*aoe)^L@5u$Z3({ROe>Rxj-phJI(HX$qtWpRMSJ5J*ay>erthhZizwsJ)Yni z$mdnE)ORG*?IV-4c>QT`0B2muXe$HTBHTGzkN|Aou8R&Jlgy@ z&YaU>*#}iT&6?<2bs|h~=H&o}asguEd?%rvf9jSzxp8i>duB%Q!@F_2;v8S>=bIZB zH5pUAg+yit5hr1r_v;xX+me2q%ut3$U29{K5xlszc3(tajm(}RaUUl>t>l>h$94pz za%cL>QHJS_0iezWoYjlV2EvJ#3-a|9e$p)TNVOg>Nl3eGMy5|LMGzZkPYW&Ty}0Mm za@Fy`?x$&iC1WKenRgK<%jpl17o_RHHot!TYLZ*$hh|_`aq7-1-)(a4dH1#F{frxr zw(HZkcbzU#54Fw+q~xnvITh*U;4Kd`x}}^CFyfz=Uu-1SQD}#(`Nx9To}`@{GK_R4 zYiTZDDr@TBIYCt2I_Lqj^PlP{3kY4guC2Cw*VNwNyYL$27b|ov_7F5LzbNE8;6>wp zAp~$eoN|*tSYtGZH3q8k&?qV`vRg3{*6ROgausy+s3m4y5g9h>p~$DfREM8sGfucRzH9sl{_(T`XN4&v z>V6aq7DEn#L}f)oz0B>@ z`p8rO^Q1|Y*K}=fNBeXL89kN8l;F5LkI3~neI_}>2Et$eyK+W^QDr*E)cm6-4dDM} z6tfeGsYeW8@h@$g)1x|8qKIINXPH#(WoX^ZxL;iWK4xqmn=G%u(-=U~Hb{OVw>nzi z?`>r%uj`u)EseWN9!v)PgOCy4%8yg7i(cXW>TmenN+Uj~*A^#J>t&(|&G(H0z$6VA z3EvsZX+5^F+e1{%?%zn0gKx_2Zq6&mUq0gYNg#j&0M36(ueJX8&9~RM@p050hrilM zcQkM>>%17qE7uX+_L~rKr~G#1lV7YUFIQ1h|V5Vmtt`NLkt z$6>5ioNz7K)2&H%kIq)TjF_hSbC{d~$sLtzR9y>L0Dz{ea50~dVXm=16)H+#yjLfX zI2^@8q;mPz3!OpmW;{og`YoX`Hb4l@nj3US@}=%8FQ#^KhSb~z^%I32 z2|ar`aYt3~)=Y6LIQRdjZo@<&SXkTFG(Q1rn^Ze)tvb znx_x**gsA>lyPB!u0tpFtUlgWWhaYmeRG|BZJuw|2$#oE9<#M9`FzIwjTMMS1u_5Y zTT0Z`&(KrDOIuYy&E3}3=kM%|Oq_o#l!(!1#M>`alJ=IT#;q5@PKX!3w-8s7#b|=4 z*J8CE@-RRp<0OvgCk1ym1?v{kSH7n7ndG4pH;Q=ne84(mgRcJL6D;bb>1m?o7^vnd zVqm7~>7nYcCoQBTiIx)a*U&Kjvt}L+C$RXNZ~bwb{G^E)%??~`YB~i3yz}S@{Vj?; z-1b4u66r^k+ro7R>Y8f?#RQHa_|XJSPgW$OhfBrn!3`B6n>ns}tpqu+Sx)e!!B1>v zyF>e+J9R>N6tdzH@;(EoBn59SoPO`lFINiu1 zAZwP1qXn)`;FSC;QTZ(vD3qZg$79*v<#C&&%wtA->cK=teB_=1K}NLZ`le zS1~#5C7>zPyeI$yf+q5=A8)EP&n;iD_^d2#-kyDkixyG(NTeQpNq3A*@3{pARZZ4- zDgG^`ztT3!u2#D~S>!Q}ej&w_)(=EO6U?+yYf_nhG8ExTAc&xxLLN z=FZGn{y3gef*7P`qsSAXQ3m2q*Bx@gjyez<9I3NT`%mzDUWQ3{D6}b}`xG7lq6kAB zLaCOBUsS+8?v&Ui+iC(9tqQ=)PPY9aMDemnn zA-?R*+gfS!NKgdNRb&fJ(iq80wn5J-DmF7n72NFhH`Og(1jx4sIoyrLAaUrewahsV zUuELTYTX&$FTqB6s7>{kii-E*Ys)l`(Q1|+G5NSXaK$bMqTFMj%sc1BpqkoQ?& zfrqk%#qhhc6P{KKsz$YOSJ>Vs^n0jtpYn6NtB&iFkBODnu5X+Ousr5X`Z@XCI?L9l zr`tX3D%qaOQc#@ZEhQUr>dwnHdQ4BgXg$LqY1E8!d-wa0`_S(2Cxa>Fqzx48ye_V* zR~b*CeAed$7v&f+U>y0!cWVD;PPfI6;aPoKb?NH0#1Nj9o87g-w#4i4Xm{}917NmA z?kze~^Nh*x);2SzkE!$o`fEQrzLlIQGnzC52drt7iZWOkY7It{u?kthg%yr`L;(WE zh8|%+D>ZtasT38l!daC_2X2j{Z@-#TI!ryWP|QD5Ak2j!Pzdmb3rbPK?_41d1>Ea| zvqGRK@Gd6;ZL1OkJy*07InPB;Ut&F1Wcv>NQ$&&r-n&sMOB;H0UaW{<#$km)6n9F{ z<(b{A2(2djx12XDDFnV;paF|pmsz6{d(zf9G50z zP|cvAZovJ=U|xhcx{Py%Omr~(i)Ck_OKyM_8AO0dIOfOM={bPGbvTb_q zlZxw0oz}bKT_U&6jKEMfnI}PPekv;Id8#psykpKTd!x~c%#@3?df(J+5QZigWSC-{ zLN+%@=n}V3T{R|Mm+|CT`1#ES-EWEPz8Lq-qZY8Ait9vuv&tT6Bv(K2xZ$}LippK3 zkhj*jrQ0viJKK~;4Wh7LJ+zRbZ}&oTnNT^~!gL?ocAKT%$_C6$Opy)uiEJeSMI>Ob zC|v7pD-~+|ddm$4p_?z{zvcVP{NWE*O3SZ(>1kq=%jq&xxM%O5dEdB!3l~%^YPd!$u1!V?liF?v5{W#Xql2%$I@#o z92Iks6(G|~@EYf;%gq+dfhXh0)WJ_LD0SkK7up&!H46r%C?=xBdKxL2)*B<)?D z2F?9rRP>zTy#hxwaDQ^yigpafvFMkmE;8~l{4nAV=T>;fG;8%zT&NSL7_c}z2ghoWU|e$_ zvO1Y%>8KeX1S6aP&5mbmnHC=do0#R*JHpPG)e`hrk4W$r%M`qU&wyo{zl>|gC8v6Y z9+vr)94nkm0A+walU|Np6lrvM3BdjP-2mL5()a%{_SIog2Ho4s0@5AQ9nzhG?y^fa z2rMW_cXu}k2qK^eB9hV#1|=Y%2q;JkA|VYTA|3AcJd65!-+$igyDq(4cV=hK%yVX* zIp^Hx9*dJJQ>n!q!Oc`xkc$ItVLy#kG8YbPud>zrO=y;mhFIoQXRZCLm_3Un`nK-6 zI@X*JyD3#y@9P1czW=97!DOobM)%&*Qf9phf150s>Q|SOH}=2D@EO_Ng}C|QzP7-D zTb?`Q%lm`m5Gm0}wkBHJ1LM}F8awp23d@Hh(+XxU!pR_D;3^u3QQ!?Z0Rw;j-s9?z z{=P7C(evr6om#@hSKh}n%D~RL8?5Lrf>$Dc&n9-N!NGwZe$G1XZYnOek|;eDPZNJD zX;J+fvL;H_$|zAy#Kns{X6-Q_%kY#<^OQ>bX$g(J)7v_-=P@j+FRF2t_c?`10QdjD zXqiN9NKZyX*HF&|?kjxJB*T?Nn{pPKmU|7}8B!Tfjli~s2zkkFTxzY_%kWE+p+IXX6MP|H?dy#S(n` z;X0d;Z-2S#u2JHq6H25id2yc>rwBZ=fE(e?F9%VfuL4hLVsUcE&UfPob$YQS#)I%# zau`ECMHWn>!-Iu1^GV0@S z$hI)+lSv6mCeIuf2Md`Yjq}EI=PjUFmetQYGQ#92 znBeRtvlq%!f9IWM_IJwNVVEDHutrG3NWQtEge9D-BD6DJE9_?huRKg!}%( zEB6JE_TFUhvWxuM;K+R3f@t8?@(iZ{I1ym8p<%^E>`43Oy?5{|P8_8*F<2yGnynd| zGZIJ~%fnXUdV*}!di|XpilKl&Gz_P30C`{7c}V5?&?fXVTwJ&lE{u(x4T254>2cxg z$hzms_tAL(Wdw3`$#uRE2a%+*#sPz`feaIfR-*gpA}%GfUc^bnG7_fQl?CHsiv$Ew z8qeW^DQR<~r$QqO5HF_(*`ZvK`O%C5cb57{mq(F|Bp=^}fBH=b0X^$QkY`wyxX5u2 zk_z3ULAc9~@H9&kr#0K%--@aWG&zif@_~xnDlw zB$WF_T8>te*`!*LeS8|GP{!~ILp&q#P0uy=vA%Xom*hQT-t}z!fp@)^hqA}~IEpa1 zM1hs?-wPV@mqo}jisKDd_XoQJBYyX!$b9}mp9#KfTJ#wn>xDW@pYQ!#=U4oc&pq|N zij=N){fprC-9JZ4BW^*%kuAbA{%ry7vZ3bf)Md5O13i2*${!YU7`iE-89IGLBHaXq zYbmmDY?PM4)E~wm#-eLhY|I}p0(RqBm8dVq^wH09-cZE6EvmM7|OP{`dMZ_U|i*dkc1h{hv z?;LD4exR@kTR_155B#bWSyHvFX$ovyUmS6-Jh%{NQJ3Q@Y&Do_G_0oS9P`|!j#4LqKW zc+)ltXZd~XPvH3(o%+Sn`i9fKGMsf4%5v*vLdlzMaB$PH-E&>{Ue#UV;)I{ySZvU{ z)-~;B9ph1z5M`kvL|PA^ivbrpv*)v@OYYD841^w1`!(SUo{D%`hCfg1!Ks{~HhKK# zn3iwSHTZxj+&{Yvy1RYwIIOmIy3mMG&B1Z)Wq~%eNR|wn?Qg-hpFf$~z*qvR1~A`& znAJtXx7NZ*+S_zmm51QPTPM@jmF=TaPgSzURQ^!`mw>VZq~>t(L}&A!%*!#zO@B9FlbseK=Y`+aU7 zQaZm-%VD4um4cc5nW6th$*1k*@L84?$0lM&vrBh`k#qNu$+hQ<3Ye+?OtZsCvBjF{ zh<{URX5@oWC0XvkJubg7CGxn~7q4fSpCr*}*pFJ{=LQgc9qDX5 znYoZ+&?lV?eJ#7A-(ob}VYhaRRX^t!qh_e1wsJℌ7ooI?s5sKDyv?F_w^)!bN> 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.1.1.0 , plutus-tx , QuickCheck + , quickcheck-arbitrary-adt , quickcheck-dynamic >=3.4 && <3.5 , quickcheck-instances , regex-tdfa diff --git a/hydra-node/src/Hydra/Events/SQLiteBased.hs b/hydra-node/src/Hydra/Events/SQLiteBased.hs index b04d2a912b6..e7acec2d63b 100644 --- a/hydra-node/src/Hydra/Events/SQLiteBased.hs +++ b/hydra-node/src/Hydra/Events/SQLiteBased.hs @@ -7,9 +7,21 @@ -- == Architecture -- -- Events are stored in a single @events@ table with an integer primary key --- (@event_id@) and a BLOB column (@event_data@) containing JSON-encoded event --- data. The database uses WAL journal mode with @synchronous=NORMAL@ to avoid --- per-write fsyncs while still syncing at WAL checkpoints. +-- (@event_id@) and a BLOB column (@event_data@) containing CBOR-encoded event +-- data (via 'ToCBOR' / 'FromCBOR'). The database uses WAL journal mode with +-- @synchronous=NORMAL@ to avoid per-write fsyncs while still syncing at WAL +-- checkpoints. +-- +-- == Schema migrations +-- +-- The schema version is tracked in @PRAGMA user_version@ and migrated on open +-- (see 'applyMigrations'). Version 1 stored event data as JSON; opening a +-- version 1 database re-encodes every row to CBOR in one transaction and runs +-- @VACUUM@ afterwards to reclaim the freed space. A row that fails to decode +-- aborts the migration (and thereby node startup) with +-- 'EventDecodingException', rolling back to an intact version 1 database. +-- The legacy file-based store (JSON lines) is migrated by decoding each line +-- as JSON and inserting CBOR. -- -- == Async write-behind -- @@ -58,6 +70,7 @@ module Hydra.Events.SQLiteBased where import Hydra.Prelude +import Cardano.Binary (decodeFull', serialize') import Conduit (ConduitT, ResourceT, bracketP, runConduitRes, sourceFile, yield, (.|)) import Control.Concurrent.Class.MonadSTM (flushTBQueue, newEmptyTMVarIO, newTBQueueIO, putTMVar, readTBQueue, takeTMVar, writeTBQueue, writeTVar) import Control.Monad.Class.MonadAsync (async, cancel, link) @@ -65,7 +78,7 @@ import Data.Aeson qualified as Aeson import Data.ByteString qualified as BS import Data.Conduit.Combinators (linesUnboundedAscii) import Data.Conduit.Combinators qualified as C -import Database.SQLite.Simple (Connection, Only (..), Statement, close, closeStatement, execute, executeMany, execute_, nextRow, open, openStatement, query_, withTransaction) +import Database.SQLite.Simple (Connection, Only (..), Statement, close, closeStatement, execute, executeMany, execute_, nextRow, open, openStatement, query, query_, withTransaction) import Hydra.Events (EventSink (..), EventSource (..), HasEventId (..)) import Hydra.Events.Rotation (EventStore (..)) import Hydra.Logging (Tracer, traceWith) @@ -91,7 +104,7 @@ data SQLiteLog -- | Items in the write-behind queue: either an event to insert or a flush -- marker that the writer thread signals after processing all preceding items. -- --- Events are queued unencoded and JSON-encoded on the writer thread: the +-- Events are queued unencoded and CBOR-encoded on the writer thread: the -- encoding of e.g. a SnapshotRequested event carrying a large UTxO otherwise -- sits on the node loop between processing a ReqSn and broadcasting the -- AckSn. The bounded queue briefly pins event values instead of compact @@ -112,7 +125,7 @@ type WriteItem e = Either (TMVar IO ()) (Word64, e) -- event id TVar, and this bracket flushes on exit. withSQLiteEventStore :: forall e a. - (FromJSON e, ToJSON e, HasEventId e) => + (ToCBOR e, FromCBOR e, FromJSON e, HasEventId e) => Tracer IO SQLiteLog -> FilePath -> FilePath -> @@ -131,13 +144,20 @@ withSQLiteEventStore tracer dbFile legacyStateFile callback = do -- automatically. mkSQLiteEventStore :: forall e. - (ToJSON e, FromJSON e, HasEventId e) => + (ToCBOR e, FromCBOR e, FromJSON e, HasEventId e) => FilePath -> IO (Connection, EventStore e IO, IO (), IO (), IO ()) mkSQLiteEventStore dbFile = do createDirectoryIfMissing True (takeDirectory dbFile) conn <- open dbFile - initSchema conn + -- Rows of a version 1 database are JSON-encoded and get re-encoded to CBOR + -- by the schema migration; a row that fails to decode aborts startup. + let reencodeRow :: Word64 -> ByteString -> IO ByteString + reencodeRow eid bytes = + case Aeson.eitherDecodeStrict' @e bytes of + Right evt -> pure $ serialize' evt + Left err -> throwIO EventDecodingException{eventId = eid, decodeError = err} + initSchema conn reencodeRow -- Dedicated connection for 'sourceEvents' streams, so concurrent client -- history replay cannot hold statements open on the connection rotation -- runs VACUUM INTO on (see module header). @@ -161,11 +181,11 @@ mkSQLiteEventStore dbFile = do decodeRow :: (Word64, ByteString) -> IO e decodeRow (eid, evData) = - case Aeson.eitherDecodeStrict' evData of + case decodeFull' evData of Right evt -> pure evt -- NOTE: This will prevent the node from starting, which is intentional — -- starting with missing events would silently corrupt the head state. - Left err -> throwIO EventDecodingException{eventId = eid, decodeError = err} + Left err -> throwIO EventDecodingException{eventId = eid, decodeError = show err} sourceEvents :: ConduitT () e (ResourceT IO) () sourceEvents = do @@ -215,7 +235,7 @@ mkSQLiteEventStore dbFile = do -- Archive the current database before removing events, so the -- pre-rotation log is retained (mirrors the old file-based backup). backupDatabase conn dbFile logId - let evData = toStrict $ Aeson.encode checkpointEvent + let evData = serialize' checkpointEvent withTransaction conn $ do deleteAllEvents conn insertEvent conn (getEventId checkpointEvent, evData) @@ -241,17 +261,17 @@ mkSQLiteEventStore dbFile = do -- | Background writer that drains the queue and batch-inserts into SQLite. -- Each iteration blocks for at least one item, then flushes everything --- available. Events are JSON-encoded here, off the caller's thread, then +-- available. Events are CBOR-encoded here, off the caller's thread, then -- batch-inserted in a single transaction, and any flush markers in the batch -- are signalled. Encode errors surface as writer thread crashes, which are -- 'link'ed to the node. -writerLoop :: ToJSON e => Connection -> TBQueue IO (WriteItem e) -> IO () +writerLoop :: ToCBOR e => Connection -> TBQueue IO (WriteItem e) -> IO () writerLoop conn queue = forever $ do first' <- atomically $ readTBQueue queue rest <- atomically $ flushTBQueue queue let allItems = first' : rest (flushSignals, events) = partitionEithers allItems - eventRows = map (second (toStrict . Aeson.encode)) events + eventRows = map (second serialize') events unless (null eventRows) $ withTransaction conn $ insertEvents conn eventRows @@ -278,7 +298,7 @@ flushWriteQueue queue = do -- subsequent node restarts skip the migration step automatically. migrateFromFileBased :: forall e. - (FromJSON e, HasEventId e) => + (FromJSON e, ToCBOR e, HasEventId e) => Proxy e -> Tracer IO SQLiteLog -> FilePath -> @@ -297,11 +317,12 @@ migrateFromFileBased _proxy tracer legacyFile conn reinitLastSeen = do .| linesUnboundedAscii .| C.filter (not . BS.null) .| C.sinkList - -- Decode each line to extract the event id, then store the original raw - -- bytes. Invalid JSON is caught here so corrupt files fail at migration. + -- Decode each JSON line (legacy files are always JSON) and store the + -- event re-encoded as CBOR. Invalid JSON is caught here so corrupt + -- files fail at migration. rowParams <- forM (zip [1 ..] rawLines) $ \(lineNo :: Int, line) -> case Aeson.eitherDecodeStrict' @e line of - Right evt -> pure (getEventId evt, line) + Right evt -> pure (getEventId evt, serialize' evt) Left err -> throwIO EventDecodingException{eventId = fromIntegral lineNo, decodeError = err} unless (null rowParams) $ withTransaction conn $ @@ -315,17 +336,26 @@ migrateFromFileBased _proxy tracer legacyFile conn reinitLastSeen = do -- Internal -- | Current schema version. Bump this and add a migration step to --- 'applyMigrations' whenever the schema changes. +-- 'migrateStep' whenever the schema changes. nextVersion :: Int -nextVersion = 1 +nextVersion = 2 + +-- | Re-encode a single event row given its event id and stored bytes, used by +-- the version 1 (JSON) to version 2 (CBOR) migration. Must throw when the row +-- cannot be decoded. +type ReencodeRow = Word64 -> ByteString -> IO ByteString -- | Initialise connection pragmas, then create or migrate the schema to -- 'nextVersion' using SQLite's built-in @user_version@ pragma. -initSchema :: Connection -> IO () -initSchema conn = do +initSchema :: Connection -> ReencodeRow -> IO () +initSchema conn reencodeRow = do configurePragmas conn v <- getSchemaVersion conn - applyMigrations conn v + applyMigrations conn reencodeRow v + -- Reclaim the space freed by the JSON -> CBOR re-encode. VACUUM cannot run + -- inside a transaction and is a space optimization only: a crash between + -- the migration commit and here costs disk space, not correctness. + when (v == 1) $ execute_ conn "VACUUM" configurePragmas :: Connection -> IO () configurePragmas conn = @@ -353,25 +383,50 @@ setSchemaVersion conn v = execute_ conn $ fromString $ "PRAGMA user_version = " <> show v -- | Apply all pending migrations from version @v@ up to 'nextVersion'. --- Each step runs in its own transaction so that a crash mid-migration leaves --- the database at a well-defined version. -applyMigrations :: Connection -> Int -> IO () -applyMigrations conn v +-- Each step runs together with its version bump in one transaction +-- (@PRAGMA user_version@ is transactional), so a crash or decoding failure +-- mid-migration rolls back to a well-defined version. +applyMigrations :: Connection -> ReencodeRow -> Int -> IO () +applyMigrations conn reencodeRow v | v > nextVersion = error $ "Database schema version " <> show v <> " is newer than supported " <> show nextVersion <> ", cannot downgrade" | v == nextVersion = pure () | otherwise = do - migrateStep conn v - setSchemaVersion conn (v + 1) - applyMigrations conn (v + 1) + withTransaction conn $ do + migrateStep conn reencodeRow v + setSchemaVersion conn (v + 1) + applyMigrations conn reencodeRow (v + 1) -- | Individual migration steps. Pattern-match on the /source/ version. -migrateStep :: Connection -> Int -> IO () -migrateStep conn = \case +migrateStep :: Connection -> ReencodeRow -> Int -> IO () +migrateStep conn reencodeRow = \case 0 -> createEventsTable conn + 1 -> reencodeAllEvents conn reencodeRow unknown -> error $ "Unknown schema version " <> show unknown <> ", cannot migrate" +-- | Re-encode all event rows using the given 'ReencodeRow' function (the +-- version 1 JSON to version 2 CBOR migration). Rows are processed in batches +-- of ascending event id so memory stays bounded for large databases. Runs +-- inside the caller's transaction. +reencodeAllEvents :: Connection -> ReencodeRow -> IO () +reencodeAllEvents conn reencodeRow = go 0 + where + batchSize = 1000 :: Int + + go :: Word64 -> IO () + go startId = do + rows :: [(Word64, ByteString)] <- + query conn "SELECT event_id, event_data FROM events WHERE event_id >= ? ORDER BY event_id LIMIT ?" (startId, batchSize) + case nonEmpty rows of + Nothing -> pure () + Just neRows -> do + updates <- forM rows $ \(eid, evData) -> do + encoded <- reencodeRow eid evData + pure (encoded, eid) + executeMany conn "UPDATE events SET event_data = ? WHERE event_id = ?" updates + go (fst (last neRows) + 1) + -- SQL queries createEventsTable :: Connection -> IO () diff --git a/hydra-node/src/Hydra/HeadLogic/Outcome.hs b/hydra-node/src/Hydra/HeadLogic/Outcome.hs index 6bb9277f629..0b15e8b7a5c 100644 --- a/hydra-node/src/Hydra/HeadLogic/Outcome.hs +++ b/hydra-node/src/Hydra/HeadLogic/Outcome.hs @@ -195,6 +195,213 @@ instance forall tx. (IsChainState tx, IsTx tx, FromJSON (NodeState tx), FromJSON Object (KeyMap.insert "mode" (toJSON (AwaitingSelection :: FanoutMode tx)) o) v -> v +-- NOTE: This codec defines the event format persisted in the hydra.db events +-- table (see "Hydra.Events.SQLiteBased"). Changing an encoding here breaks +-- decoding of existing databases and requires a schema migration. +instance IsChainState tx => ToCBOR (StateChanged tx) where + toCBOR = \case + NetworkConnected -> + toCBOR ("NetworkConnected" :: Text) + NetworkDisconnected -> + toCBOR ("NetworkDisconnected" :: Text) + PeerConnected{peer} -> + toCBOR ("PeerConnected" :: Text) <> toCBOR peer + PeerDisconnected{peer} -> + toCBOR ("PeerDisconnected" :: Text) <> toCBOR peer + NetworkVersionMismatch{ourVersion, theirVersion} -> + toCBOR ("NetworkVersionMismatch" :: Text) <> toCBOR ourVersion <> toCBOR theirVersion + NetworkClusterIDMismatch{clusterPeers, misconfiguredPeers} -> + toCBOR ("NetworkClusterIDMismatch" :: Text) <> toCBOR clusterPeers <> toCBOR misconfiguredPeers + HeadOpened{parameters, chainState, headId, headSeed, parties} -> + toCBOR ("HeadOpened" :: Text) + <> toCBOR parameters + <> toCBOR chainState + <> toCBOR headId + <> toCBOR headSeed + <> toCBOR parties + TransactionReceived{tx} -> + toCBOR ("TransactionReceived" :: Text) <> toCBOR tx + TransactionAppliedToLocalUTxO{headId, tx} -> + toCBOR ("TransactionAppliedToLocalUTxO" :: Text) <> toCBOR headId <> toCBOR tx + SnapshotRequestDecided{snapshotNumber} -> + toCBOR ("SnapshotRequestDecided" :: Text) <> toCBOR snapshotNumber + SnapshotRequested{requestedSnapshot, newLocalTxs, newCurrentDepositTxId} -> + toCBOR ("SnapshotRequested" :: Text) + <> toCBOR requestedSnapshot + <> toCBOR newLocalTxs + <> toCBOR newCurrentDepositTxId + PartySignedSnapshot{snapshotNumber, party, signature} -> + toCBOR ("PartySignedSnapshot" :: Text) + <> toCBOR snapshotNumber + <> toCBOR party + <> toCBOR signature + SnapshotConfirmed{headId, snapshot, signatures} -> + toCBOR ("SnapshotConfirmed" :: Text) + <> toCBOR headId + <> toCBOR snapshot + <> toCBOR signatures + DepositRecorded{chainState, headId, depositTxId, deposited, created, deadline} -> + toCBOR ("DepositRecorded" :: Text) + <> toCBOR chainState + <> toCBOR headId + <> toCBOR depositTxId + <> toCBOR deposited + <> toCBOR created + <> toCBOR deadline + DepositActivated{depositTxId, chainTime, deposit} -> + toCBOR ("DepositActivated" :: Text) + <> toCBOR depositTxId + <> toCBOR chainTime + <> toCBOR deposit + DepositExpired{depositTxId, chainTime, deposit} -> + toCBOR ("DepositExpired" :: Text) + <> toCBOR depositTxId + <> toCBOR chainTime + <> toCBOR deposit + DepositRecovered{chainState, headId, depositTxId, recovered} -> + toCBOR ("DepositRecovered" :: Text) + <> toCBOR chainState + <> toCBOR headId + <> toCBOR depositTxId + <> toCBOR recovered + CommitApproved{headId, utxoToCommit} -> + toCBOR ("CommitApproved" :: Text) <> toCBOR headId <> toCBOR utxoToCommit + CommitFinalized{chainState, headId, newVersion, depositTxId} -> + toCBOR ("CommitFinalized" :: Text) + <> toCBOR chainState + <> toCBOR headId + <> toCBOR newVersion + <> toCBOR depositTxId + DecommitRecorded{headId, decommitTx} -> + toCBOR ("DecommitRecorded" :: Text) <> toCBOR headId <> toCBOR decommitTx + DecommitApproved{headId, decommitTxId, utxoToDecommit} -> + toCBOR ("DecommitApproved" :: Text) + <> toCBOR headId + <> toCBOR decommitTxId + <> toCBOR utxoToDecommit + DecommitInvalid{headId, decommitTx, decommitInvalidReason} -> + toCBOR ("DecommitInvalid" :: Text) + <> toCBOR headId + <> toCBOR decommitTx + <> toCBOR decommitInvalidReason + DecommitFinalized{chainState, headId, distributedUTxO, newVersion} -> + toCBOR ("DecommitFinalized" :: Text) + <> toCBOR chainState + <> toCBOR headId + <> toCBOR distributedUTxO + <> toCBOR newVersion + HeadClosed{headId, snapshotNumber, chainState, contestationDeadline} -> + toCBOR ("HeadClosed" :: Text) + <> toCBOR headId + <> toCBOR snapshotNumber + <> toCBOR chainState + <> toCBOR contestationDeadline + HeadContested{headId, chainState, contestationDeadline, snapshotNumber} -> + toCBOR ("HeadContested" :: Text) + <> toCBOR headId + <> toCBOR chainState + <> toCBOR contestationDeadline + <> toCBOR snapshotNumber + HeadIsReadyToFanout{headId} -> + toCBOR ("HeadIsReadyToFanout" :: Text) <> toCBOR headId + HeadFanoutInitiated{headId, remainingOutputs} -> + toCBOR ("HeadFanoutInitiated" :: Text) + <> toCBOR headId + <> toCBOR remainingOutputs + HeadPartialFanoutSelected{headId, remainingOutputs, selection} -> + toCBOR ("HeadPartialFanoutSelected" :: Text) + <> toCBOR headId + <> toCBOR remainingOutputs + <> toCBOR selection + HeadFanoutReverted{headId} -> + toCBOR ("HeadFanoutReverted" :: Text) <> toCBOR headId + HeadFannedOut{headId, finalizedOutputs, chainState} -> + toCBOR ("HeadFannedOut" :: Text) + <> toCBOR headId + <> toCBOR finalizedOutputs + <> toCBOR chainState + HeadPartialFannedOut{headId, distributedOutputs, remainingOutputs, chainState, mode} -> + toCBOR ("HeadPartialFannedOut" :: Text) + <> toCBOR headId + <> toCBOR distributedOutputs + <> toCBOR remainingOutputs + <> toCBOR chainState + <> toCBOR mode + ChainRolledBack{chainState} -> + toCBOR ("ChainRolledBack" :: Text) <> toCBOR chainState + TickObserved{chainPoint, chainTime} -> + toCBOR ("TickObserved" :: Text) <> toCBOR chainPoint <> toCBOR chainTime + IgnoredHeadInitializing{headId, contestationPeriod, parties, participants} -> + toCBOR ("IgnoredHeadInitializing" :: Text) + <> toCBOR headId + <> toCBOR contestationPeriod + <> toCBOR parties + <> toCBOR participants + TxInvalid{headId, utxo, transaction, validationError} -> + toCBOR ("TxInvalid" :: Text) + <> toCBOR headId + <> toCBOR utxo + <> toCBOR transaction + <> toCBOR validationError + LocalStateCleared{headId, snapshotNumber} -> + toCBOR ("LocalStateCleared" :: Text) <> toCBOR headId <> toCBOR snapshotNumber + Checkpoint{state = nodeState} -> + toCBOR ("Checkpoint" :: Text) <> toCBOR nodeState + NodeUnsynced{chainSlot, chainTime, drift} -> + toCBOR ("NodeUnsynced" :: Text) + <> toCBOR chainSlot + <> toCBOR chainTime + <> toCBOR drift + NodeSynced{chainSlot, chainTime, drift} -> + toCBOR ("NodeSynced" :: Text) + <> toCBOR chainSlot + <> toCBOR chainTime + <> toCBOR drift + +instance IsChainState tx => FromCBOR (StateChanged tx) where + fromCBOR = + fromCBOR >>= \case + ("NetworkConnected" :: Text) -> pure NetworkConnected + "NetworkDisconnected" -> pure NetworkDisconnected + "PeerConnected" -> PeerConnected <$> fromCBOR + "PeerDisconnected" -> PeerDisconnected <$> fromCBOR + "NetworkVersionMismatch" -> NetworkVersionMismatch <$> fromCBOR <*> fromCBOR + "NetworkClusterIDMismatch" -> NetworkClusterIDMismatch <$> fromCBOR <*> fromCBOR + "HeadOpened" -> HeadOpened <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "TransactionReceived" -> TransactionReceived <$> fromCBOR + "TransactionAppliedToLocalUTxO" -> TransactionAppliedToLocalUTxO <$> fromCBOR <*> fromCBOR + "SnapshotRequestDecided" -> SnapshotRequestDecided <$> fromCBOR + "SnapshotRequested" -> SnapshotRequested <$> fromCBOR <*> fromCBOR <*> fromCBOR + "PartySignedSnapshot" -> PartySignedSnapshot <$> fromCBOR <*> fromCBOR <*> fromCBOR + "SnapshotConfirmed" -> SnapshotConfirmed <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DepositRecorded" -> DepositRecorded <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "DepositActivated" -> DepositActivated <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DepositExpired" -> DepositExpired <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DepositRecovered" -> DepositRecovered <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "CommitApproved" -> CommitApproved <$> fromCBOR <*> fromCBOR + "CommitFinalized" -> CommitFinalized <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "DecommitRecorded" -> DecommitRecorded <$> fromCBOR <*> fromCBOR + "DecommitApproved" -> DecommitApproved <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DecommitInvalid" -> DecommitInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR + "DecommitFinalized" -> DecommitFinalized <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "HeadClosed" -> HeadClosed <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "HeadContested" -> HeadContested <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "HeadIsReadyToFanout" -> HeadIsReadyToFanout <$> fromCBOR + "HeadFanoutInitiated" -> HeadFanoutInitiated <$> fromCBOR <*> fromCBOR + "HeadPartialFanoutSelected" -> HeadPartialFanoutSelected <$> fromCBOR <*> fromCBOR <*> fromCBOR + "HeadFanoutReverted" -> HeadFanoutReverted <$> fromCBOR + "HeadFannedOut" -> HeadFannedOut <$> fromCBOR <*> fromCBOR <*> fromCBOR + "HeadPartialFannedOut" -> HeadPartialFannedOut <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "ChainRolledBack" -> ChainRolledBack <$> fromCBOR + "TickObserved" -> TickObserved <$> fromCBOR <*> fromCBOR + "IgnoredHeadInitializing" -> IgnoredHeadInitializing <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "TxInvalid" -> TxInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR + "LocalStateCleared" -> LocalStateCleared <$> fromCBOR <*> fromCBOR + "Checkpoint" -> Checkpoint <$> fromCBOR + "NodeUnsynced" -> NodeUnsynced <$> fromCBOR <*> fromCBOR <*> fromCBOR + "NodeSynced" -> NodeSynced <$> fromCBOR <*> fromCBOR <*> fromCBOR + tag -> fail $ show tag <> " is not a proper CBOR-encoded StateChanged" + data Outcome tx = -- | Continue with the given state updates and side effects. Continue {stateChanges :: [StateChanged tx], effects :: [Effect tx]} diff --git a/hydra-node/src/Hydra/HeadLogic/StateEvent.hs b/hydra-node/src/Hydra/HeadLogic/StateEvent.hs index 6d4b7339c45..afc3d2d109b 100644 --- a/hydra-node/src/Hydra/HeadLogic/StateEvent.hs +++ b/hydra-node/src/Hydra/HeadLogic/StateEvent.hs @@ -28,6 +28,16 @@ deriving stock instance IsChainState tx => Eq (StateEvent tx) deriving anyclass instance IsChainState tx => ToJSON (StateEvent tx) deriving anyclass instance IsChainState tx => FromJSON (StateEvent tx) +-- NOTE: This codec defines the row format persisted in the hydra.db events +-- table (see "Hydra.Events.SQLiteBased"). Changing it breaks decoding of +-- existing databases and requires a schema migration. +instance IsChainState tx => ToCBOR (StateEvent tx) where + toCBOR StateEvent{eventId, stateChanged, time} = + toCBOR eventId <> toCBOR stateChanged <> toCBOR time + +instance IsChainState tx => FromCBOR (StateEvent tx) where + fromCBOR = StateEvent <$> fromCBOR <*> fromCBOR <*> fromCBOR + mkCheckpoint :: NodeState tx -> EventId -> UTCTime -> StateEvent tx mkCheckpoint nodeState eventId time = StateEvent diff --git a/hydra-node/test/Hydra/CBORSpec.hs b/hydra-node/test/Hydra/CBORSpec.hs index 33bc6d029f6..3610fe9f91b 100644 --- a/hydra-node/test/Hydra/CBORSpec.hs +++ b/hydra-node/test/Hydra/CBORSpec.hs @@ -22,7 +22,9 @@ import Hydra.API.ServerOutput ( ) import Hydra.Chain (ChainEvent, OnChainTx, PostChainTx, PostTxError) import Hydra.Chain.Direct.State (ChainStateAt) +import Hydra.HeadLogic.Outcome (StateChanged) import Hydra.HeadLogic.State (HeadState, SeenSnapshot) +import Hydra.HeadLogic.StateEvent (StateEvent (..)) import Hydra.Ledger.Cardano (Tx) import Hydra.Network (Connectivity) import Hydra.Node.Environment (Environment) @@ -30,9 +32,13 @@ import Hydra.Node.State (Deposit, NodeState) import Hydra.Tx (ConfirmedSnapshot, Snapshot) import Test.Hydra.API.ClientInput () import Test.Hydra.API.ServerOutput () -import Test.Hydra.CBOR (roundtripCBOR) +import Test.Hydra.CBOR (goldenCBOR, roundtripCBOR) import Test.Hydra.Chain.Direct.State () +import Test.Hydra.HeadLogic.Outcome () +import Test.Hydra.HeadLogic.StateEvent () import Test.Hydra.Node.Environment () +import Test.QuickCheck (resize) +import Test.QuickCheck.Arbitrary.ADT (ADTArbitrary (..), ConstructorArbitraryPair (..), toADTArbitrary) instance Arbitrary InvalidInput where arbitrary = InvalidInput <$> arbitrary <*> arbitrary @@ -63,3 +69,21 @@ spec = parallel $ do roundtripCBOR $ Proxy @(Deposit Tx) roundtripCBOR $ Proxy @Environment roundtripCBOR $ Proxy @Connectivity + + describe "persisted types" $ do + roundtripCBOR $ Proxy @(StateChanged Tx) + roundtripCBOR $ Proxy @(StateEvent Tx) + -- Locks the on-disk format of hydra.db events: one sample per + -- 'StateChanged' constructor, stored as raw CBOR. + goldenCBOR "StateEvent Tx" "golden/StateEvent.cbor" genGoldenStateEvents + +-- | One 'StateEvent' per 'StateChanged' constructor, in declaration order: +-- 'ToADTArbitrary' enumerates the constructors generically, so coverage of +-- every constructor holds by construction and new constructors are included +-- automatically. Samples are generated small (resized): the golden file +-- locks tags and field order, which small values exercise just as well. +genGoldenStateEvents :: Gen [StateEvent Tx] +genGoldenStateEvents = do + ADTArbitrary{adtCAPs} <- resize 5 $ toADTArbitrary (Proxy @(StateChanged Tx)) + forM (zip [0 ..] adtCAPs) $ \(i, ConstructorArbitraryPair{capArbitrary}) -> + StateEvent i capArbitrary <$> arbitrary diff --git a/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs b/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs index 449d2085e04..76c9d600dff 100644 --- a/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs +++ b/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs @@ -12,14 +12,15 @@ import Database.SQLite.Simple (close, execute, execute_, open) import Hydra.Events (EventSink (..), EventSource (..), getEvents, putEvent) import Hydra.Events.Rotation (EventStore (..)) import Hydra.Events.SQLiteBased (EventDecodingException, SQLiteLog (..), getSchemaVersion, nextVersion, withSQLiteEventStore) +import Hydra.HeadLogic.Outcome (StateChanged) import Hydra.HeadLogic.StateEvent (StateEvent (..)) import Hydra.Ledger.Simple (SimpleTx) import Hydra.Logging (Envelope (..), nullTracer) -import System.Directory (doesFileExist) +import System.Directory (doesFileExist, getFileSize) import Test.Hydra.Chain.Direct.State () import Test.Hydra.HeadLogic.StateEvent () import Test.Hydra.Ledger.Simple () -import Test.QuickCheck (forAllShrink, generate, ioProperty, sublistOf, suchThat, (===)) +import Test.QuickCheck (forAllShrink, generate, ioProperty, sublistOf, suchThat, vectorOf, (===)) import Test.QuickCheck.Gen (listOf) import Test.Util (captureTracer) @@ -71,9 +72,9 @@ spec = do let dbFile = tmpDir <> "/hydra.db" stateFile = tmpDir <> "/state" withSQLiteEventStore @(StateEvent SimpleTx) nullTracer dbFile stateFile $ \store -> do - -- Insert a row with invalid JSON directly via a separate connection + -- Insert a row with undecodable data directly via a separate connection bracket (open dbFile) close $ \conn -> - execute conn "INSERT INTO events (event_id, event_data) VALUES (?, ?)" (1 :: Word64, "not valid json" :: ByteString) + execute conn "INSERT INTO events (event_id, event_data) VALUES (?, ?)" (1 :: Word64, "not valid cbor" :: ByteString) getEvents (eventSource store) `shouldThrow` \(_ :: EventDecodingException) -> True @@ -112,6 +113,46 @@ spec = do withSQLiteEventStore @(StateEvent SimpleTx) nullTracer dbFile stateFile (\_ -> pure ()) `shouldThrow` anyErrorCall + it "migrates a v1 JSON database to v2 CBOR" $ do + withTempDir "hydra-sqlite-persistence" $ \tmpDir -> do + let dbFile = tmpDir <> "/hydra.db" + stateFile = tmpDir <> "/state" + events <- generate $ mkContinuousEvents <$> vectorOf 300 arbitrary <*> vectorOf 300 arbitrary + -- Hand-create a version 1 database with JSON-encoded rows, as written + -- by hydra-node versions before the CBOR switch. + bracket (open dbFile) close $ \conn -> do + execute_ conn "CREATE TABLE events (event_id INTEGER NOT NULL PRIMARY KEY, event_data BLOB NOT NULL)" + execute_ conn "PRAGMA user_version = 1" + forM_ events $ \e -> + execute conn "INSERT INTO events (event_id, event_data) VALUES (?, ?)" (eventId e, toStrict (Aeson.encode e)) + sizeBefore <- getFileSize dbFile + withSQLiteEventStore @(StateEvent SimpleTx) nullTracer dbFile stateFile $ \store -> do + loadedEvents <- getEvents (eventSource store) + loadedEvents `shouldBe` events + v <- bracket (open dbFile) close getSchemaVersion + v `shouldBe` nextVersion + -- Re-encoding + VACUUM must not grow the database; with a few hundred + -- events CBOR is strictly smaller, but we only assert non-growth to + -- keep this stable for edge cases. + sizeAfter <- getFileSize dbFile + sizeAfter `shouldSatisfy` (<= sizeBefore) + + it "aborts migration and keeps v1 intact on a corrupt row" $ do + withTempDir "hydra-sqlite-persistence" $ \tmpDir -> do + let dbFile = tmpDir <> "/hydra.db" + stateFile = tmpDir <> "/state" + goodEvent :: StateEvent SimpleTx <- generate arbitrary + bracket (open dbFile) close $ \conn -> do + execute_ conn "CREATE TABLE events (event_id INTEGER NOT NULL PRIMARY KEY, event_data BLOB NOT NULL)" + execute_ conn "PRAGMA user_version = 1" + execute conn "INSERT INTO events (event_id, event_data) VALUES (?, ?)" (1 :: Word64, toStrict (Aeson.encode goodEvent)) + execute conn "INSERT INTO events (event_id, event_data) VALUES (?, ?)" (2 :: Word64, "not valid json" :: ByteString) + withSQLiteEventStore @(StateEvent SimpleTx) nullTracer dbFile stateFile (\_ -> pure ()) + `shouldThrow` \(_ :: EventDecodingException) -> True + -- The failed migration must roll back: still version 1, rows untouched. + v <- bracket (open dbFile) close getSchemaVersion + v `shouldBe` 1 + prop "can migrate from file-based store" $ forAllShrink genContinuousEvents shrink $ \events -> ioProperty $ do @@ -157,7 +198,10 @@ spec = do genContinuousEvents :: Gen [StateEvent SimpleTx] genContinuousEvents = - zipWith3 StateEvent [0 ..] <$> listOf arbitrary <*> listOf arbitrary + mkContinuousEvents <$> listOf arbitrary <*> listOf arbitrary + +mkContinuousEvents :: [StateChanged SimpleTx] -> [UTCTime] -> [StateEvent SimpleTx] +mkContinuousEvents = zipWith3 StateEvent [0 ..] withEventSourceAndSink :: (EventSource (StateEvent SimpleTx) IO -> EventSink (StateEvent SimpleTx) IO -> IO b) -> IO b withEventSourceAndSink action = diff --git a/hydra-node/testlib/Test/Hydra/CBOR.hs b/hydra-node/testlib/Test/Hydra/CBOR.hs index e545ddff3b1..e2927055b92 100644 --- a/hydra-node/testlib/Test/Hydra/CBOR.hs +++ b/hydra-node/testlib/Test/Hydra/CBOR.hs @@ -4,9 +4,12 @@ module Test.Hydra.CBOR where import Hydra.Prelude import Test.Hydra.Prelude +import Cardano.Binary (decodeFull', serialize') import Codec.CBOR.Read (deserialiseFromBytes) import Codec.CBOR.Write (toLazyByteString) import Data.Typeable (typeRep) +import System.Directory (createDirectoryIfMissing, doesFileExist) +import System.FilePath (takeDirectory) import Test.QuickCheck (Property, (===)) -- | Test that a value can be roundtripped through its CBOR encoding. @@ -27,3 +30,34 @@ roundtripCBOR :: roundtripCBOR p = prop ("roundtrips CBOR encoding: " <> show (typeRep p)) $ prop_canRoundtripCBOREncoding @a + +-- | Golden test locking a persisted CBOR format. The golden file holds the +-- raw CBOR of a list of samples. When the file is missing it is created from +-- the given generator with a fixed seed — commit the result. On every other +-- run the stored bytes must decode successfully and re-encode to the exact +-- same bytes. +-- +-- This catches codec changes that would break decoding of already persisted +-- data (e.g. hydra.db events) — including symmetric encoder+decoder drift +-- (say, reordering the fields on both sides) that roundtrip properties +-- cannot see. If this fails, the change breaks existing databases and needs +-- a schema migration; only delete and regenerate the golden file alongside +-- one. +goldenCBOR :: + forall a. + (ToCBOR a, FromCBOR a) => + String -> + FilePath -> + Gen [a] -> + Spec +goldenCBOR name path gen = + it ("golden CBOR: " <> name) $ do + unlessM (doesFileExist path) $ do + createDirectoryIfMissing True (takeDirectory path) + writeFileBS path . serialize' $ generateWith gen 42 + bytes <- readFileBS path + case decodeFull' @[a] bytes of + Left err -> + expectationFailure $ + "failed to decode golden file " <> path <> ": " <> show err + Right samples -> serialize' samples `shouldBe` bytes diff --git a/hydra-node/testlib/Test/Hydra/HeadLogic/Outcome.hs b/hydra-node/testlib/Test/Hydra/HeadLogic/Outcome.hs index 93ba301ee9f..143d04a137e 100644 --- a/hydra-node/testlib/Test/Hydra/HeadLogic/Outcome.hs +++ b/hydra-node/testlib/Test/Hydra/HeadLogic/Outcome.hs @@ -6,12 +6,14 @@ module Test.Hydra.HeadLogic.Outcome where import Hydra.Prelude import Test.Hydra.Prelude -import Hydra.Chain.ChainState (ChainStateType (..), IsChainState) +import Hydra.Chain.ChainState (ChainPointType (..), ChainStateType (..), IsChainState) import Hydra.HeadLogic.Outcome (StateChanged (..)) import Hydra.Node.Environment (Environment (..), mkHeadParameters) import Test.Hydra.API.ServerOutput () import Test.Hydra.Chain () import Test.Hydra.HeadLogic.State () +import Test.Hydra.Network () +import Test.Hydra.Node.State () import Test.Hydra.Tx.Gen (ArbitraryIsTx) import Test.QuickCheck (oneof) import Test.QuickCheck.Arbitrary.ADT (ToADTArbitrary) From c4925c848725fe5104fe06d9dc682291414791ca Mon Sep 17 00:00:00 2001 From: Sasha Bogicevic Date: Mon, 3 Aug 2026 18:28:15 +0200 Subject: [PATCH 3/4] Further enforce the migration Signed-off-by: Sasha Bogicevic --- .../test/Hydra/Events/SQLiteBasedSpec.hs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs b/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs index 76c9d600dff..3e57e891c28 100644 --- a/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs +++ b/hydra-node/test/Hydra/Events/SQLiteBasedSpec.hs @@ -4,11 +4,12 @@ module Hydra.Events.SQLiteBasedSpec where import Hydra.Prelude hiding (label) import Test.Hydra.Prelude +import Cardano.Binary (decodeFull') import Data.Aeson qualified as Aeson import Data.ByteString qualified as BS import Data.List (zipWith3) import Data.List qualified as List -import Database.SQLite.Simple (close, execute, execute_, open) +import Database.SQLite.Simple (Only (..), close, execute, execute_, open, query) import Hydra.Events (EventSink (..), EventSource (..), getEvents, putEvent) import Hydra.Events.Rotation (EventStore (..)) import Hydra.Events.SQLiteBased (EventDecodingException, SQLiteLog (..), getSchemaVersion, nextVersion, withSQLiteEventStore) @@ -131,6 +132,24 @@ spec = do loadedEvents `shouldBe` events v <- bracket (open dbFile) close getSchemaVersion v `shouldBe` nextVersion + -- Random events from the original JSON database must be found in the + -- migrated database under the same event_id, with the row blob + -- decoding (as CBOR) to exactly the event that was inserted. + picked <- generate $ sublistOf events `suchThat` (not . null) + bracket (open dbFile) close $ \conn -> + forM_ picked $ \e -> do + rows :: [Only ByteString] <- + query conn "SELECT event_data FROM events WHERE event_id = ?" (Only (eventId e)) + case rows of + [Only bytes] -> + case decodeFull' bytes of + Left err -> + expectationFailure $ + "failed to decode migrated row " <> show (eventId e) <> " as CBOR: " <> show err + Right (decoded :: StateEvent SimpleTx) -> decoded `shouldBe` e + _ -> + expectationFailure $ + "expected exactly one row for event_id " <> show (eventId e) <> ", got " <> show (length rows) -- Re-encoding + VACUUM must not grow the database; with a few hundred -- events CBOR is strictly smaller, but we only assert non-growth to -- keep this stable for edge cases. From b7f19da4dfe6ac57989e9ac71fc516534a22da49 Mon Sep 17 00:00:00 2001 From: Sasha Bogicevic Date: Thu, 13 Aug 2026 17:36:03 +0200 Subject: [PATCH 4/4] Derive constructor-tagged CBOR instances generically Add Hydra.CBOR.Generic to hydra-prelude, providing genericToCBOR / genericFromCBOR (re-exported from Hydra.Prelude). The derived format is exactly the hand-written one: the constructor name as a CBOR text tag followed by the fields in declaration order, with no list framing. Every constructor is tagged, including newtypes and single-constructor records, and unknown tags fail with the same error text as before. Name-based tags keep persisted data decodable when constructors are added or reordered, unlike the index-based generic encoding of the serialise package. Swap 22 hand-written instance pairs to the deriver. Byte compatibility is locked by new per-constructor golden files (golden/*.cbor, via ToADTArbitrary): they were generated from the old hand-written encoders and must decode and re-encode identically with the derived ones. Deliberate format changes in this commit: NetworkInfo now carries its constructor tag like the other API types (Greetings bytes change transitively); both goldens regenerated. The golden enumeration also exposed that the hand-written ToCBOR (PostTxError tx) missed the DepositTooLarge constructor, crashing with PatternMatchFail on encode; the derived instance covers it by construction. Also add a typos.toml exception: the typos formatter rewrote GHC.TypeLits into the nonexistent GHC.TypeList on every nix fmt. Signed-off-by: Sasha Bogicevic --- docs/docs/dev/architecture/event-sourcing.md | 11 + hydra-node/golden/ChainEvent.cbor | Bin 0 -> 8315 bytes hydra-node/golden/ClientInput.cbor | Bin 0 -> 9799 bytes hydra-node/golden/ClientMessage.cbor | Bin 0 -> 52582 bytes hydra-node/golden/ConfirmedSnapshot.cbor | Bin 0 -> 14696 bytes hydra-node/golden/Connectivity.cbor | Bin 0 -> 137 bytes hydra-node/golden/DecommitInvalidReason.cbor | Bin 0 -> 778 bytes hydra-node/golden/Deposit.cbor | Bin 0 -> 705 bytes hydra-node/golden/DepositStatus.cbor | 1 + hydra-node/golden/FanoutMode.cbor | Bin 0 -> 824 bytes hydra-node/golden/FanoutProgressMode.cbor | 1 + hydra-node/golden/Greetings.cbor | Bin 0 -> 742 bytes hydra-node/golden/HeadState.cbor | Bin 0 -> 45257 bytes hydra-node/golden/HeadStatus.cbor | 1 + hydra-node/golden/InvalidInput.cbor | 1 + hydra-node/golden/Message.cbor | Bin 0 -> 15887 bytes hydra-node/golden/NetworkInfo.cbor | Bin 0 -> 56 bytes hydra-node/golden/NodeState.cbor | Bin 0 -> 10666 bytes hydra-node/golden/OnChainTx.cbor | Bin 0 -> 3698 bytes hydra-node/golden/PostChainTx.cbor | Bin 0 -> 16104 bytes hydra-node/golden/PostTxError.cbor | Bin 0 -> 22405 bytes hydra-node/golden/RequirementFailure.cbor | Bin 0 -> 708 bytes hydra-node/golden/SeenSnapshot.cbor | Bin 0 -> 6899 bytes hydra-node/golden/ServerOutput.cbor | Bin 0 -> 42296 bytes .../golden/SideLoadRequirementFailure.cbor | Bin 0 -> 2888 bytes hydra-node/golden/SyncedStatus.cbor | 1 + hydra-node/golden/TimedServerOutput.cbor | Bin 0 -> 681 bytes hydra-node/golden/WhichEtcd.cbor | 1 + hydra-node/src/Hydra/API/ClientInput.hs | 26 +- hydra-node/src/Hydra/API/ServerOutput.hs | 206 +--------------- hydra-node/src/Hydra/Chain.hs | 224 +----------------- hydra-node/src/Hydra/HeadLogic/Error.hs | 90 +------ hydra-node/src/Hydra/HeadLogic/Outcome.hs | 202 +--------------- hydra-node/src/Hydra/HeadLogic/State.hs | 26 +- hydra-node/src/Hydra/Network.hs | 30 +-- hydra-node/src/Hydra/Network/Message.hs | 14 +- hydra-node/src/Hydra/Node/State.hs | 40 +--- hydra-node/test/Hydra/CBORSpec.hs | 154 +++++++++++- hydra-node/testlib/Test/Hydra/CBOR.hs | 13 +- hydra-prelude/hydra-prelude.cabal | 1 + hydra-prelude/src/Hydra/CBOR/Generic.hs | 133 +++++++++++ hydra-prelude/src/Hydra/Prelude.hs | 3 + hydra-tx/src/Hydra/Tx/Snapshot.hs | 12 +- typos.toml | 2 + 44 files changed, 361 insertions(+), 832 deletions(-) create mode 100644 hydra-node/golden/ChainEvent.cbor create mode 100644 hydra-node/golden/ClientInput.cbor create mode 100644 hydra-node/golden/ClientMessage.cbor create mode 100644 hydra-node/golden/ConfirmedSnapshot.cbor create mode 100644 hydra-node/golden/Connectivity.cbor create mode 100644 hydra-node/golden/DecommitInvalidReason.cbor create mode 100644 hydra-node/golden/Deposit.cbor create mode 100644 hydra-node/golden/DepositStatus.cbor create mode 100644 hydra-node/golden/FanoutMode.cbor create mode 100644 hydra-node/golden/FanoutProgressMode.cbor create mode 100644 hydra-node/golden/Greetings.cbor create mode 100644 hydra-node/golden/HeadState.cbor create mode 100644 hydra-node/golden/HeadStatus.cbor create mode 100644 hydra-node/golden/InvalidInput.cbor create mode 100644 hydra-node/golden/Message.cbor create mode 100644 hydra-node/golden/NetworkInfo.cbor create mode 100644 hydra-node/golden/NodeState.cbor create mode 100644 hydra-node/golden/OnChainTx.cbor create mode 100644 hydra-node/golden/PostChainTx.cbor create mode 100644 hydra-node/golden/PostTxError.cbor create mode 100644 hydra-node/golden/RequirementFailure.cbor create mode 100644 hydra-node/golden/SeenSnapshot.cbor create mode 100644 hydra-node/golden/ServerOutput.cbor create mode 100644 hydra-node/golden/SideLoadRequirementFailure.cbor create mode 100644 hydra-node/golden/SyncedStatus.cbor create mode 100644 hydra-node/golden/TimedServerOutput.cbor create mode 100644 hydra-node/golden/WhichEtcd.cbor create mode 100644 hydra-prelude/src/Hydra/CBOR/Generic.hs diff --git a/docs/docs/dev/architecture/event-sourcing.md b/docs/docs/dev/architecture/event-sourcing.md index 12560805bcf..9e72d5b4bdf 100644 --- a/docs/docs/dev/architecture/event-sourcing.md +++ b/docs/docs/dev/architecture/event-sourcing.md @@ -23,6 +23,17 @@ The database contains a single `events` table: | `event_id` | INTEGER | Primary key, matches the in-memory event id | | `event_data` | BLOB | CBOR-encoded event payload (`ToCBOR`/`FromCBOR`) | +Event payloads use a constructor-name-tagged CBOR format: the constructor name +as a CBOR text string, followed by the constructor fields in declaration order. +Most instances are derived generically via `genericToCBOR` / `genericFromCBOR` +from `Hydra.CBOR.Generic` (hydra-prelude), which makes the data type +declaration itself the on-disk format: changing the order or types of an +existing constructor's fields is a breaking change that requires a schema +migration, while adding, removing or reordering constructors keeps existing +data decodable thanks to the name tags. The golden tests in `Hydra.CBORSpec` +lock the concrete bytes per constructor and fail on any accidental format +change. + The following connection pragmas are set on every open: - `journal_mode=WAL` — write-ahead logging for concurrent reads during writes diff --git a/hydra-node/golden/ChainEvent.cbor b/hydra-node/golden/ChainEvent.cbor new file mode 100644 index 0000000000000000000000000000000000000000..a3050a3b69c999b59d06036fb14f54e788bfe21b GIT binary patch literal 8315 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 literal 0 HcmV?d00001 diff --git a/hydra-node/golden/ConfirmedSnapshot.cbor b/hydra-node/golden/ConfirmedSnapshot.cbor new file mode 100644 index 0000000000000000000000000000000000000000..8dfc55ee4a500ff3c65585224b4a4b7c23d727f9 GIT binary patch literal 14696 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 bBO0@#_A(1G4fVdTZz@~@|5LqyK=^+E`?NJE literal 0 HcmV?d00001 diff --git a/hydra-node/golden/Connectivity.cbor b/hydra-node/golden/Connectivity.cbor new file mode 100644 index 0000000000000000000000000000000000000000..fbc2995fde50d8f717ac209a0ea03ff547ad6734 GIT binary patch literal 137 zcmbQA8<3h> v)S}|d{5;>x;@rfNnxrSC*KQnXsy7x{EpA%Wk5zQSG6b$2Oii*t56G z;ue(9|#E~n14nfFX}k~;U=D4TzKiJhn2 z4*jbO1NZibzbx3;E4Fg`jo$jeKRWunV+tI)h(F&=*J_$ z?ZInO>;ALp0o@G@P*5Y@Ju={*(*Rik~J+ zScqLP?bvYURSRPV<8N{6K6NFw4H{>If^y?dM$BI4>;B`2n*$?H#+10n$`2<=G4P!c z1K9$KFlJz2FfxN|U|<1K%s?xdS-{q_NCW_FVQ9|pxSX)g-(=;%eH-`Oyfx!YQ2T9X z2VPg@s`s0hlq)TruUx0X@gPB_DbL*0t?J9ikfftuDhrDwQ9{RB%qu|XKHtx->D-Hs zRU2rE9%pU$nfdru&4)&=g^p&7K-Vy{0KE!y5+e%`FflU#y$eiV%*+fR0w^vK2J-VU zBR+*}*5(I}TFWypPptKLq%gIwiC=!%B7xibi^R{i960veT|fWVhcn?vr_SeOIe6(B z+ru55E%IPLr!f?WzgV-Y9G03LbBa&_r!8+oP6G> z({c6-c;)c_KhX8eKo7A1eZ>U!1p^Zk6Oae=AJ9iYFR(C4STsdg3dG8OFut#8%Ddje z{Ac;|8HzENk|*4M#(y^aLRr0#)sigr8!MO<#10Bi(CeLk@mawk^-VhS*{#-YlxC3s zS$yhO0|Ue+7GSV2voL}K3P>`5!ia^Li3x-lBs@Sio!hNmr&TfOe0KG`8F`}YJh2;_ z?#r1PW?U#*5@39`=ca|!?Y^a!s_C6(MK`kMtzUg-!K1@n5*rhN!6ckE3m8m4=XEN@ z`AUaq?^AZV!5-4uB%9J5ad3NuwBw2Ng%MIhpSe>cXZ~I5FLUG1zK^dtZnQ29<1dO2 zl4_Iox9ZkO_G)wmx|IbO!7RX-W(I}>Fkl#g5egJyW@G_687RsuA<+~eo8V=6`R46= z3MV5U#pUX~K3c!hgwZ!M{8+8dvi%>BeGbyX2-XV@0A^s?0s5K&NP`S!U=#p)T;X<; zhvoEn<}+KCn;u_sSfwKT(OWxBFX?%cqP2Qv*G}#7Vcfv>(Q!T7^0ls!^Xe6XvKOU@ y+w-LqNHOr85o?xY_mF6jX5iX9`PV*vAuzXInn7k>$*-1MI_a(z1(`*uDgObng6V7k literal 0 HcmV?d00001 diff --git a/hydra-node/golden/DepositStatus.cbor b/hydra-node/golden/DepositStatus.cbor new file mode 100644 index 00000000000..b064bce4df0 --- /dev/null +++ b/hydra-node/golden/DepositStatus.cbor @@ -0,0 +1 @@ +ŸhInactivefActivegExpiredÿ \ No newline at end of file diff --git a/hydra-node/golden/FanoutMode.cbor b/hydra-node/golden/FanoutMode.cbor new file mode 100644 index 0000000000000000000000000000000000000000..da3787a34c659096305d3bc0368266e25f5af5e7 GIT binary patch literal 824 zcmY+Ddq|UE6vp41mu_m7wp48Uwj`2dl_1eS`{q|_HPe(_kfiC*sckxEW=5AukY%R* zVW#HPZDz7)VVNa)L1JZQMWQQ&!bT=mlx5zkuXjN~hnK^F7oOjF9ymrdTcFo+x>U8M zfK%t|b?So!dbK7aL6xIQ)2p=_i$N&_7$8It0tnEK0EVvTzD%FiBt`(E~jNV_&3VmgvDp5G3)0?#K=x}($Q;-P%`Oebma9V@^4`9So_$AtmF zYrx+HA%N;bj0i160HG920T4q-@knrhQhaprJAI4l_6Yg|clgV1+*`TSuG?Oc*4Hpp zU7q^xu&s96;{ipdpVqzMrDCM$l_R+BgCffo>a+%eHFNp>^Ctl%Au5Ow2AC45I)NC| zfsTYwPZ;^S>^y4DnYuH-dFzS9)ZS3brIM7EY-3E+#rS|C?URy}{olvE1v8TuW0pNk zIh1Xmn2{zGKU?MB7FqGv4#G4lS45alT^EZ!;$qPl=_FV~DN;qaMB1LWsOMwu>2x1a z4Qs9cqRRE~dslyI@7-@M!PHfY_FfrrJLU<1!vwI+nm8ZDrSaU_(M2}^3L^C>=k+fEC<3Dju>)3N) zXr}dN^7;!KuR}us2)Z9jS8|5+3ny;gnp=@2dXP3e@7dyATa#Yh-#nmck84z}R<(sG zrpw}1*sBI-J3ZHFUOi|k+??`ad>SGHbO@B?CO1d%NwW#KNw_7p(kYn;I*u6;ZxI71 zlZbI-Y}RohW0cp~ct^33@FuR*-`yb;6A??yY$kC743aTM6XBR`5mzDG3W1rC$=I_J zHbKmYd81KA<{9 literal 0 HcmV?d00001 diff --git a/hydra-node/golden/FanoutProgressMode.cbor b/hydra-node/golden/FanoutProgressMode.cbor new file mode 100644 index 00000000000..436b8036e44 --- /dev/null +++ b/hydra-node/golden/FanoutProgressMode.cbor @@ -0,0 +1 @@ +ŸnAutoFanningOutwAwaitingFanoutSelectionÿ \ No newline at end of file diff --git a/hydra-node/golden/Greetings.cbor b/hydra-node/golden/Greetings.cbor new file mode 100644 index 0000000000000000000000000000000000000000..ad3c86ba85c338082ad644c69f4b19029940bdff GIT binary patch literal 742 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

4mlviHd%0bAy>?H=)}hcHMr8H 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 DSxM(o literal 0 HcmV?d00001 diff --git a/hydra-node/golden/NetworkInfo.cbor b/hydra-node/golden/NetworkInfo.cbor new file mode 100644 index 0000000000000000000000000000000000000000..ee533d064214e6f3ccad1b74642abeebdb1b0561 GIT binary patch literal 56 qcmbQA?U!0oo?n#hnU|LTWl6e$9vHHGg|dtp;4GFeP%#sRum1ry=MupH literal 0 HcmV?d00001 diff --git a/hydra-node/golden/NodeState.cbor b/hydra-node/golden/NodeState.cbor new file mode 100644 index 0000000000000000000000000000000000000000..d74f3dbcdc28bce8b027d5840864673b9df29e06 GIT binary patch literal 10666 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 E9}F#(82|tP literal 0 HcmV?d00001 diff --git a/hydra-node/golden/OnChainTx.cbor b/hydra-node/golden/OnChainTx.cbor new file mode 100644 index 0000000000000000000000000000000000000000..09e078c9211bf11df3e877e2191ab30113cc1923 GIT binary patch literal 3698 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 Xfj*V@qOtP*vazPoB>vx!wHW^ox21;` literal 0 HcmV?d00001 diff --git a/hydra-node/golden/PostChainTx.cbor b/hydra-node/golden/PostChainTx.cbor new file mode 100644 index 0000000000000000000000000000000000000000..9cbbc2c3cd886ef07be5b3fdae1af651706e6024 GIT binary patch literal 16104 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 JBozb0{{!`z91s8i literal 0 HcmV?d00001 diff --git a/hydra-node/golden/RequirementFailure.cbor b/hydra-node/golden/RequirementFailure.cbor new file mode 100644 index 0000000000000000000000000000000000000000..5f7ba2c2ec96b91b6e4bdd6d386191ed11409edf GIT binary patch literal 708 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

?#X&aXb$ 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

nUYl$Z?5b_Od;y5drP$BE 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 literal 0 HcmV?d00001 diff --git a/hydra-node/golden/SideLoadRequirementFailure.cbor b/hydra-node/golden/SideLoadRequirementFailure.cbor new file mode 100644 index 0000000000000000000000000000000000000000..9e008ae69456a2ac708396485dba2a892ef5c29c GIT binary patch literal 2888 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 ToJSON (ClientInput tx) deriving anyclass instance IsTx tx => FromJSON (ClientInput tx) instance IsTx tx => ToCBOR (ClientInput tx) where - toCBOR = \case - Init -> toCBOR ("Init" :: Text) - NewTx{transaction} -> toCBOR ("NewTx" :: Text) <> toCBOR transaction - Recover{recoverTxId} -> toCBOR ("Recover" :: Text) <> toCBOR recoverTxId - Decommit{decommitTx} -> toCBOR ("Decommit" :: Text) <> toCBOR decommitTx - Close -> toCBOR ("Close" :: Text) - SafeClose -> toCBOR ("SafeClose" :: Text) - Contest -> toCBOR ("Contest" :: Text) - Fanout -> toCBOR ("Fanout" :: Text) - PartialFanout{utxoToFanout} -> toCBOR ("PartialFanout" :: Text) <> toCBOR utxoToFanout - SideLoadSnapshot{snapshot} -> toCBOR ("SideLoadSnapshot" :: Text) <> toCBOR snapshot + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (ClientInput tx) where - fromCBOR = - fromCBOR >>= \case - ("Init" :: Text) -> pure Init - "NewTx" -> NewTx <$> fromCBOR - "Recover" -> Recover <$> fromCBOR - "Decommit" -> Decommit <$> fromCBOR - "Close" -> pure Close - "SafeClose" -> pure SafeClose - "Contest" -> pure Contest - "Fanout" -> pure Fanout - "PartialFanout" -> PartialFanout <$> fromCBOR - "SideLoadSnapshot" -> SideLoadSnapshot <$> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded ClientInput" + fromCBOR = genericFromCBOR diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index 23551c63392..6aa424a1b01 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -87,18 +87,10 @@ instance (FromJSON (TxIdType tx), FromJSON (UTxOType tx)) => FromJSON (DecommitI parseJSON = genericParseJSON defaultOptions instance IsTx tx => ToCBOR (DecommitInvalidReason tx) where - toCBOR = \case - DecommitTxInvalid{localUTxO, validationError} -> - toCBOR ("DecommitTxInvalid" :: Text) <> toCBOR localUTxO <> toCBOR validationError - DecommitAlreadyInFlight{otherDecommitTxId} -> - toCBOR ("DecommitAlreadyInFlight" :: Text) <> toCBOR otherDecommitTxId + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (DecommitInvalidReason tx) where - fromCBOR = - fromCBOR >>= \case - ("DecommitTxInvalid" :: Text) -> DecommitTxInvalid <$> fromCBOR <*> fromCBOR - "DecommitAlreadyInFlight" -> DecommitAlreadyInFlight <$> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded DecommitInvalidReason" + fromCBOR = genericFromCBOR -- | Individual messages as produced by the 'Hydra.HeadLogic' in -- the 'ClientEffect'. @@ -124,28 +116,10 @@ instance IsChainState tx => FromJSON (ClientMessage tx) where } instance IsChainState tx => ToCBOR (ClientMessage tx) where - toCBOR = \case - CommandFailed{clientInput, state = headState} -> - toCBOR ("CommandFailed" :: Text) <> toCBOR clientInput <> toCBOR headState - PostTxOnChainFailed{postChainTx, postTxError} -> - toCBOR ("PostTxOnChainFailed" :: Text) <> toCBOR postChainTx <> toCBOR postTxError - RejectedInputBecauseUnsynced{clientInput, drift} -> - toCBOR ("RejectedInputBecauseUnsynced" :: Text) <> toCBOR clientInput <> toCBOR drift - SideLoadSnapshotRejected{clientInput, requirementFailure} -> - toCBOR ("SideLoadSnapshotRejected" :: Text) <> toCBOR clientInput <> toCBOR requirementFailure + toCBOR = genericToCBOR instance IsChainState tx => FromCBOR (ClientMessage tx) where - fromCBOR = fromCBOR >>= decodeClientMessageBody - --- | Decode a 'ClientMessage' given its already-decoded constructor tag (used --- for tag-based dispatch). -decodeClientMessageBody :: IsChainState tx => Text -> Decoder s (ClientMessage tx) -decodeClientMessageBody = \case - "CommandFailed" -> CommandFailed <$> fromCBOR <*> fromCBOR - "PostTxOnChainFailed" -> PostTxOnChainFailed <$> fromCBOR <*> fromCBOR - "RejectedInputBecauseUnsynced" -> RejectedInputBecauseUnsynced <$> fromCBOR <*> fromCBOR - "SideLoadSnapshotRejected" -> SideLoadSnapshotRejected <$> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded ClientMessage" + fromCBOR = genericFromCBOR -- | A friendly welcome message which tells a client something about the -- node. Currently used for knowing what signing key the server uses (it @@ -334,147 +308,10 @@ deriving anyclass instance IsChainState tx => FromJSON (ServerOutput tx) deriving anyclass instance IsChainState tx => ToJSON (ServerOutput tx) instance IsChainState tx => ToCBOR (ServerOutput tx) where - toCBOR = \case - NetworkConnected -> - toCBOR ("NetworkConnected" :: Text) - NetworkDisconnected -> - toCBOR ("NetworkDisconnected" :: Text) - NetworkVersionMismatch{ourVersion, theirVersion} -> - toCBOR ("NetworkVersionMismatch" :: Text) <> toCBOR ourVersion <> toCBOR theirVersion - NetworkClusterIDMismatch{clusterPeers, misconfiguredPeers} -> - toCBOR ("NetworkClusterIDMismatch" :: Text) <> toCBOR clusterPeers <> toCBOR misconfiguredPeers - PeerConnected{peer} -> - toCBOR ("PeerConnected" :: Text) <> toCBOR peer - PeerDisconnected{peer} -> - toCBOR ("PeerDisconnected" :: Text) <> toCBOR peer - HeadIsOpen{headId, parties} -> - toCBOR ("HeadIsOpen" :: Text) <> toCBOR headId <> toCBOR parties - HeadIsClosed{headId, snapshotNumber, contestationDeadline} -> - toCBOR ("HeadIsClosed" :: Text) - <> toCBOR headId - <> toCBOR snapshotNumber - <> toCBOR contestationDeadline - HeadIsContested{headId, snapshotNumber, contestationDeadline} -> - toCBOR ("HeadIsContested" :: Text) - <> toCBOR headId - <> toCBOR snapshotNumber - <> toCBOR contestationDeadline - ReadyToFanout{headId} -> - toCBOR ("ReadyToFanout" :: Text) <> toCBOR headId - HeadPartiallyFannedOut{headId, distributedUTxO, remainingUTxO, fanoutMode} -> - toCBOR ("HeadPartiallyFannedOut" :: Text) - <> toCBOR headId - <> toCBOR distributedUTxO - <> toCBOR remainingUTxO - <> toCBOR fanoutMode - HeadIsFinalized{headId, finalizedUTxO} -> - toCBOR ("HeadIsFinalized" :: Text) <> toCBOR headId <> toCBOR finalizedUTxO - TxValid{headId, transactionId} -> - toCBOR ("TxValid" :: Text) <> toCBOR headId <> toCBOR transactionId - TxInvalid{headId, utxo, transaction, validationError} -> - toCBOR ("TxInvalid" :: Text) - <> toCBOR headId - <> toCBOR utxo - <> toCBOR transaction - <> toCBOR validationError - SnapshotConfirmed{headId, snapshot, signatures} -> - toCBOR ("SnapshotConfirmed" :: Text) - <> toCBOR headId - <> toCBOR snapshot - <> toCBOR signatures - IgnoredHeadInitializing{headId, contestationPeriod, parties, participants} -> - toCBOR ("IgnoredHeadInitializing" :: Text) - <> toCBOR headId - <> toCBOR contestationPeriod - <> toCBOR parties - <> toCBOR participants - DecommitRequested{headId, decommitTx, utxoToDecommit} -> - toCBOR ("DecommitRequested" :: Text) - <> toCBOR headId - <> toCBOR decommitTx - <> toCBOR utxoToDecommit - DecommitInvalid{headId, decommitTx, decommitInvalidReason} -> - toCBOR ("DecommitInvalid" :: Text) - <> toCBOR headId - <> toCBOR decommitTx - <> toCBOR decommitInvalidReason - DecommitApproved{headId, decommitTxId, utxoToDecommit} -> - toCBOR ("DecommitApproved" :: Text) - <> toCBOR headId - <> toCBOR decommitTxId - <> toCBOR utxoToDecommit - DecommitFinalized{headId, distributedUTxO} -> - toCBOR ("DecommitFinalized" :: Text) <> toCBOR headId <> toCBOR distributedUTxO - CommitRecorded{headId, utxoToCommit, pendingDeposit, deadline} -> - toCBOR ("CommitRecorded" :: Text) - <> toCBOR headId - <> toCBOR utxoToCommit - <> toCBOR pendingDeposit - <> toCBOR deadline - DepositActivated{headId, depositTxId, deadline, chainTime} -> - toCBOR ("DepositActivated" :: Text) - <> toCBOR headId - <> toCBOR depositTxId - <> toCBOR deadline - <> toCBOR chainTime - DepositExpired{headId, depositTxId, deadline, chainTime} -> - toCBOR ("DepositExpired" :: Text) - <> toCBOR headId - <> toCBOR depositTxId - <> toCBOR deadline - <> toCBOR chainTime - CommitApproved{headId, utxoToCommit} -> - toCBOR ("CommitApproved" :: Text) <> toCBOR headId <> toCBOR utxoToCommit - CommitFinalized{headId, depositTxId} -> - toCBOR ("CommitFinalized" :: Text) <> toCBOR headId <> toCBOR depositTxId - CommitRecovered{headId, recoveredUTxO, recoveredTxId} -> - toCBOR ("CommitRecovered" :: Text) - <> toCBOR headId - <> toCBOR recoveredUTxO - <> toCBOR recoveredTxId - SnapshotSideLoaded{headId, snapshotNumber} -> - toCBOR ("SnapshotSideLoaded" :: Text) <> toCBOR headId <> toCBOR snapshotNumber - EventLogRotated{checkpoint} -> - toCBOR ("EventLogRotated" :: Text) <> toCBOR checkpoint - NodeUnsynced{chainSlot, chainTime, drift} -> - toCBOR ("NodeUnsynced" :: Text) <> toCBOR chainSlot <> toCBOR chainTime <> toCBOR drift - NodeSynced{chainSlot, chainTime, drift} -> - toCBOR ("NodeSynced" :: Text) <> toCBOR chainSlot <> toCBOR chainTime <> toCBOR drift + toCBOR = genericToCBOR instance IsChainState tx => FromCBOR (ServerOutput tx) where - fromCBOR = - fromCBOR >>= \case - ("NetworkConnected" :: Text) -> pure NetworkConnected - "NetworkDisconnected" -> pure NetworkDisconnected - "NetworkVersionMismatch" -> NetworkVersionMismatch <$> fromCBOR <*> fromCBOR - "NetworkClusterIDMismatch" -> NetworkClusterIDMismatch <$> fromCBOR <*> fromCBOR - "PeerConnected" -> PeerConnected <$> fromCBOR - "PeerDisconnected" -> PeerDisconnected <$> fromCBOR - "HeadIsOpen" -> HeadIsOpen <$> fromCBOR <*> fromCBOR - "HeadIsClosed" -> HeadIsClosed <$> fromCBOR <*> fromCBOR <*> fromCBOR - "HeadIsContested" -> HeadIsContested <$> fromCBOR <*> fromCBOR <*> fromCBOR - "ReadyToFanout" -> ReadyToFanout <$> fromCBOR - "HeadPartiallyFannedOut" -> HeadPartiallyFannedOut <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "HeadIsFinalized" -> HeadIsFinalized <$> fromCBOR <*> fromCBOR - "TxValid" -> TxValid <$> fromCBOR <*> fromCBOR - "TxInvalid" -> TxInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "SnapshotConfirmed" -> SnapshotConfirmed <$> fromCBOR <*> fromCBOR <*> fromCBOR - "IgnoredHeadInitializing" -> IgnoredHeadInitializing <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "DecommitRequested" -> DecommitRequested <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DecommitInvalid" -> DecommitInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DecommitApproved" -> DecommitApproved <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DecommitFinalized" -> DecommitFinalized <$> fromCBOR <*> fromCBOR - "CommitRecorded" -> CommitRecorded <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "DepositActivated" -> DepositActivated <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "DepositExpired" -> DepositExpired <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "CommitApproved" -> CommitApproved <$> fromCBOR <*> fromCBOR - "CommitFinalized" -> CommitFinalized <$> fromCBOR <*> fromCBOR - "CommitRecovered" -> CommitRecovered <$> fromCBOR <*> fromCBOR <*> fromCBOR - "SnapshotSideLoaded" -> SnapshotSideLoaded <$> fromCBOR <*> fromCBOR - "EventLogRotated" -> EventLogRotated <$> fromCBOR - "NodeUnsynced" -> NodeUnsynced <$> fromCBOR <*> fromCBOR <*> fromCBOR - "NodeSynced" -> NodeSynced <$> fromCBOR <*> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded ServerOutput" + fromCBOR = genericFromCBOR -- | Whether or not to include full UTxO in server outputs. data WithUTxO = WithUTxO | WithoutUTxO @@ -561,22 +398,10 @@ data HeadStatus deriving anyclass (ToJSON, FromJSON) instance ToCBOR HeadStatus where - toCBOR = \case - Idle -> toCBOR ("Idle" :: Text) - Open -> toCBOR ("Open" :: Text) - Closed -> toCBOR ("Closed" :: Text) - FanoutPossible -> toCBOR ("FanoutPossible" :: Text) - FanningOut -> toCBOR ("FanningOut" :: Text) + toCBOR = genericToCBOR instance FromCBOR HeadStatus where - fromCBOR = - fromCBOR >>= \case - ("Idle" :: Text) -> pure Idle - "Open" -> pure Open - "Closed" -> pure Closed - "FanoutPossible" -> pure FanoutPossible - "FanningOut" -> pure FanningOut - tag -> fail $ show tag <> " is not a proper CBOR-encoded HeadStatus" + fromCBOR = genericFromCBOR -- | Client-facing projection of the node's fanout 'FanoutMode': whether a -- fanning-out head will continue draining on its own or is waiting for the @@ -602,16 +427,10 @@ fanoutProgressMode = \case AwaitingSelection -> AwaitingFanoutSelection instance ToCBOR FanoutProgressMode where - toCBOR = \case - AutoFanningOut -> toCBOR ("AutoFanningOut" :: Text) - AwaitingFanoutSelection -> toCBOR ("AwaitingFanoutSelection" :: Text) + toCBOR = genericToCBOR instance FromCBOR FanoutProgressMode where - fromCBOR = - fromCBOR >>= \case - ("AutoFanningOut" :: Text) -> pure AutoFanningOut - "AwaitingFanoutSelection" -> pure AwaitingFanoutSelection - tag -> fail $ show tag <> " is not a proper CBOR-encoded FanoutProgressMode" + fromCBOR = genericFromCBOR -- | All information needed to distinguish behavior of the commit endpoint. data CommitInfo @@ -627,11 +446,10 @@ data NetworkInfo = NetworkInfo deriving anyclass (ToJSON, FromJSON) instance ToCBOR NetworkInfo where - toCBOR NetworkInfo{networkConnected, peersInfo} = - toCBOR networkConnected <> toCBOR peersInfo + toCBOR = genericToCBOR instance FromCBOR NetworkInfo where - fromCBOR = NetworkInfo <$> fromCBOR <*> fromCBOR + fromCBOR = genericFromCBOR -- | Get latest confirmed snapshot UTxO from 'HeadState'. getSnapshotUtxo :: IsTx tx => HeadState tx -> Maybe (UTxOType tx) diff --git a/hydra-node/src/Hydra/Chain.hs b/hydra-node/src/Hydra/Chain.hs index 532a724f7ab..12e107421c6 100644 --- a/hydra-node/src/Hydra/Chain.hs +++ b/hydra-node/src/Hydra/Chain.hs @@ -114,74 +114,10 @@ deriving anyclass instance IsTx tx => ToJSON (PostChainTx tx) deriving anyclass instance IsTx tx => FromJSON (PostChainTx tx) instance IsTx tx => ToCBOR (PostChainTx tx) where - toCBOR = \case - InitTx{participants, headParameters} -> - toCBOR ("InitTx" :: Text) <> toCBOR participants <> toCBOR headParameters - IncrementTx{headSeed, headId, headParameters, incrementingSnapshot, depositTxId} -> - toCBOR ("IncrementTx" :: Text) - <> toCBOR headSeed - <> toCBOR headId - <> toCBOR headParameters - <> toCBOR incrementingSnapshot - <> toCBOR depositTxId - RecoverTx{headId, recoverTxId, deadline, recoverUTxO} -> - toCBOR ("RecoverTx" :: Text) - <> toCBOR headId - <> toCBOR recoverTxId - <> toCBOR deadline - <> toCBOR recoverUTxO - DecrementTx{headSeed, headId, headParameters, decrementingSnapshot} -> - toCBOR ("DecrementTx" :: Text) - <> toCBOR headSeed - <> toCBOR headId - <> toCBOR headParameters - <> toCBOR decrementingSnapshot - CloseTx{headId, headParameters, openVersion, closingSnapshot} -> - toCBOR ("CloseTx" :: Text) - <> toCBOR headId - <> toCBOR headParameters - <> toCBOR openVersion - <> toCBOR closingSnapshot - ContestTx{headId, headParameters, openVersion, contestingSnapshot} -> - toCBOR ("ContestTx" :: Text) - <> toCBOR headId - <> toCBOR headParameters - <> toCBOR openVersion - <> toCBOR contestingSnapshot - FanoutTx{utxo, utxoToCommit, utxoToDecommit, utxoForProof, headSeed, contestationDeadline} -> - toCBOR ("FanoutTx" :: Text) - <> toCBOR utxo - <> toCBOR utxoToCommit - <> toCBOR utxoToDecommit - <> toCBOR utxoForProof - <> toCBOR headSeed - <> toCBOR contestationDeadline - PartialFanoutTx{utxoToDistribute, utxoForProof, headSeed, contestationDeadline} -> - toCBOR ("PartialFanoutTx" :: Text) - <> toCBOR utxoToDistribute - <> toCBOR utxoForProof - <> toCBOR headSeed - <> toCBOR contestationDeadline - FinalPartialFanoutTx{utxoToDistribute, presettledUTxO, headSeed, contestationDeadline} -> - toCBOR ("FinalPartialFanoutTx" :: Text) - <> toCBOR utxoToDistribute - <> toCBOR presettledUTxO - <> toCBOR headSeed - <> toCBOR contestationDeadline + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (PostChainTx tx) where - fromCBOR = - fromCBOR >>= \case - ("InitTx" :: Text) -> InitTx <$> fromCBOR <*> fromCBOR - "IncrementTx" -> IncrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "RecoverTx" -> RecoverTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "DecrementTx" -> DecrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "CloseTx" -> CloseTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "ContestTx" -> ContestTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "FanoutTx" -> FanoutTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "PartialFanoutTx" -> PartialFanoutTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "FinalPartialFanoutTx" -> FinalPartialFanoutTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded PostChainTx" + fromCBOR = genericFromCBOR -- | Describes transactions as seen on chain. Holds as minimal information as -- possible to simplify observing the chain. @@ -234,63 +170,10 @@ deriving anyclass instance IsTx tx => ToJSON (OnChainTx tx) deriving anyclass instance IsTx tx => FromJSON (OnChainTx tx) instance IsTx tx => ToCBOR (OnChainTx tx) where - toCBOR = \case - OnInitTx{headId, headSeed, headParameters, participants} -> - toCBOR ("OnInitTx" :: Text) - <> toCBOR headId - <> toCBOR headSeed - <> toCBOR headParameters - <> toCBOR participants - OnDepositTx{headId, depositTxId, deposited, created, deadline} -> - toCBOR ("OnDepositTx" :: Text) - <> toCBOR headId - <> toCBOR depositTxId - <> toCBOR deposited - <> toCBOR created - <> toCBOR deadline - OnRecoverTx{headId, recoveredTxId, recoveredUTxO} -> - toCBOR ("OnRecoverTx" :: Text) - <> toCBOR headId - <> toCBOR recoveredTxId - <> toCBOR recoveredUTxO - OnIncrementTx{headId, newVersion, depositTxId} -> - toCBOR ("OnIncrementTx" :: Text) - <> toCBOR headId - <> toCBOR newVersion - <> toCBOR depositTxId - OnDecrementTx{headId, newVersion, distributedUTxO} -> - toCBOR ("OnDecrementTx" :: Text) - <> toCBOR headId - <> toCBOR newVersion - <> toCBOR distributedUTxO - OnCloseTx{headId, snapshotNumber, contestationDeadline} -> - toCBOR ("OnCloseTx" :: Text) - <> toCBOR headId - <> toCBOR snapshotNumber - <> toCBOR contestationDeadline - OnContestTx{headId, snapshotNumber, contestationDeadline} -> - toCBOR ("OnContestTx" :: Text) - <> toCBOR headId - <> toCBOR snapshotNumber - <> toCBOR contestationDeadline - OnFanoutTx{headId, fanoutUTxO} -> - toCBOR ("OnFanoutTx" :: Text) <> toCBOR headId <> toCBOR fanoutUTxO - OnPartialFanoutTx{headId, distributedOutputs} -> - toCBOR ("OnPartialFanoutTx" :: Text) <> toCBOR headId <> toCBOR distributedOutputs + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (OnChainTx tx) where - fromCBOR = - fromCBOR >>= \case - ("OnInitTx" :: Text) -> OnInitTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "OnDepositTx" -> OnDepositTx <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "OnRecoverTx" -> OnRecoverTx <$> fromCBOR <*> fromCBOR <*> fromCBOR - "OnIncrementTx" -> OnIncrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR - "OnDecrementTx" -> OnDecrementTx <$> fromCBOR <*> fromCBOR <*> fromCBOR - "OnCloseTx" -> OnCloseTx <$> fromCBOR <*> fromCBOR <*> fromCBOR - "OnContestTx" -> OnContestTx <$> fromCBOR <*> fromCBOR <*> fromCBOR - "OnFanoutTx" -> OnFanoutTx <$> fromCBOR <*> fromCBOR - "OnPartialFanoutTx" -> OnPartialFanoutTx <$> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded OnChainTx" + fromCBOR = genericFromCBOR -- | Exceptions thrown by 'postTx'. data PostTxError tx @@ -344,84 +227,10 @@ deriving anyclass instance IsChainState tx => FromJSON (PostTxError tx) instance IsChainState tx => Exception (PostTxError tx) instance IsChainState tx => ToCBOR (PostTxError tx) where - toCBOR = \case - NoSeedInput -> - toCBOR ("NoSeedInput" :: Text) - InvalidSeed{headSeed} -> - toCBOR ("InvalidSeed" :: Text) <> toCBOR headSeed - InvalidHeadId{headId} -> - toCBOR ("InvalidHeadId" :: Text) <> toCBOR headId - UnsupportedLegacyOutput{byronAddress} -> - toCBOR ("UnsupportedLegacyOutput" :: Text) <> toCBOR byronAddress - DepositTooLow{providedValue, minimumValue} -> - toCBOR ("DepositTooLow" :: Text) <> toCBOR providedValue <> toCBOR minimumValue - InvalidStateToPost{txTried, chainState} -> - toCBOR ("InvalidStateToPost" :: Text) <> toCBOR txTried <> toCBOR chainState - NotEnoughFuel{failingTx} -> - toCBOR ("NotEnoughFuel" :: Text) <> toCBOR failingTx - NoFuelUTXOFound{failingTx} -> - toCBOR ("NoFuelUTXOFound" :: Text) <> toCBOR failingTx - ScriptFailedInWallet{redeemerPtr, failureReason, failingTx} -> - toCBOR ("ScriptFailedInWallet" :: Text) - <> toCBOR redeemerPtr - <> toCBOR failureReason - <> toCBOR failingTx - InternalWalletError{headUTxO, reason, failingTx} -> - toCBOR ("InternalWalletError" :: Text) - <> toCBOR headUTxO - <> toCBOR reason - <> toCBOR failingTx - FailedToPostTx{failureReason, failingTx} -> - toCBOR ("FailedToPostTx" :: Text) <> toCBOR failureReason <> toCBOR failingTx - FailedToConstructCloseTx -> - toCBOR ("FailedToConstructCloseTx" :: Text) - FailedToConstructContestTx -> - toCBOR ("FailedToConstructContestTx" :: Text) - FailedToConstructDepositTx{failureReason} -> - toCBOR ("FailedToConstructDepositTx" :: Text) <> toCBOR failureReason - FailedToConstructRecoverTx{failureReason} -> - toCBOR ("FailedToConstructRecoverTx" :: Text) <> toCBOR failureReason - FailedToConstructIncrementTx{failureReason} -> - toCBOR ("FailedToConstructIncrementTx" :: Text) <> toCBOR failureReason - FailedToConstructDecrementTx{failureReason} -> - toCBOR ("FailedToConstructDecrementTx" :: Text) <> toCBOR failureReason - FailedToConstructFanoutTx -> - toCBOR ("FailedToConstructFanoutTx" :: Text) - FailedToConstructPartialFanoutTx -> - toCBOR ("FailedToConstructPartialFanoutTx" :: Text) - StalePartialFanoutTx -> - toCBOR ("StalePartialFanoutTx" :: Text) - ContestationDeadlineOutsideTimeHorizon{failureReason} -> - toCBOR ("ContestationDeadlineOutsideTimeHorizon" :: Text) <> toCBOR failureReason - InvalidTokenRequest tokens -> - toCBOR ("InvalidTokenRequest" :: Text) <> toCBOR tokens + toCBOR = genericToCBOR instance IsChainState tx => FromCBOR (PostTxError tx) where - fromCBOR = - fromCBOR >>= \case - ("NoSeedInput" :: Text) -> pure NoSeedInput - "InvalidSeed" -> InvalidSeed <$> fromCBOR - "InvalidHeadId" -> InvalidHeadId <$> fromCBOR - "UnsupportedLegacyOutput" -> UnsupportedLegacyOutput <$> fromCBOR - "DepositTooLow" -> DepositTooLow <$> fromCBOR <*> fromCBOR - "InvalidStateToPost" -> InvalidStateToPost <$> fromCBOR <*> fromCBOR - "NotEnoughFuel" -> NotEnoughFuel <$> fromCBOR - "NoFuelUTXOFound" -> NoFuelUTXOFound <$> fromCBOR - "ScriptFailedInWallet" -> ScriptFailedInWallet <$> fromCBOR <*> fromCBOR <*> fromCBOR - "InternalWalletError" -> InternalWalletError <$> fromCBOR <*> fromCBOR <*> fromCBOR - "FailedToPostTx" -> FailedToPostTx <$> fromCBOR <*> fromCBOR - "FailedToConstructCloseTx" -> pure FailedToConstructCloseTx - "FailedToConstructContestTx" -> pure FailedToConstructContestTx - "FailedToConstructDepositTx" -> FailedToConstructDepositTx <$> fromCBOR - "FailedToConstructRecoverTx" -> FailedToConstructRecoverTx <$> fromCBOR - "FailedToConstructIncrementTx" -> FailedToConstructIncrementTx <$> fromCBOR - "FailedToConstructDecrementTx" -> FailedToConstructDecrementTx <$> fromCBOR - "FailedToConstructFanoutTx" -> pure FailedToConstructFanoutTx - "FailedToConstructPartialFanoutTx" -> pure FailedToConstructPartialFanoutTx - "StalePartialFanoutTx" -> pure StalePartialFanoutTx - "ContestationDeadlineOutsideTimeHorizon" -> ContestationDeadlineOutsideTimeHorizon <$> fromCBOR - "InvalidTokenRequest" -> InvalidTokenRequest <$> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded PostTxError" + fromCBOR = genericFromCBOR -- | A non empty sequence of chain states that can be rolled back. -- This is expected to be constructed by using the smart constructor @@ -565,27 +374,10 @@ deriving anyclass instance (IsTx tx, IsChainState tx) => ToJSON (ChainEvent tx) deriving anyclass instance (IsTx tx, IsChainState tx) => FromJSON (ChainEvent tx) instance IsChainState tx => ToCBOR (ChainEvent tx) where - toCBOR = \case - Observation{observedTx, newChainState} -> - toCBOR ("Observation" :: Text) <> toCBOR observedTx <> toCBOR newChainState - Rollback{chainTime, rolledBackChainState} -> - toCBOR ("Rollback" :: Text) <> toCBOR chainTime <> toCBOR rolledBackChainState - Tick{chainTime, chainPoint} -> - toCBOR ("Tick" :: Text) <> toCBOR chainTime <> toCBOR chainPoint - PostTxError{postChainTx, postTxError, failingTx} -> - toCBOR ("PostTxError" :: Text) - <> toCBOR postChainTx - <> toCBOR postTxError - <> toCBOR failingTx + toCBOR = genericToCBOR instance IsChainState tx => FromCBOR (ChainEvent tx) where - fromCBOR = - fromCBOR >>= \case - ("Observation" :: Text) -> Observation <$> fromCBOR <*> fromCBOR - "Rollback" -> Rollback <$> fromCBOR <*> fromCBOR - "Tick" -> Tick <$> fromCBOR <*> fromCBOR - "PostTxError" -> PostTxError <$> fromCBOR <*> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded ChainEvent" + fromCBOR = genericFromCBOR -- | A callback indicating a 'ChainEvent tx' happened. Most importantly the -- 'Observation' of a relevant Hydra transaction. diff --git a/hydra-node/src/Hydra/HeadLogic/Error.hs b/hydra-node/src/Hydra/HeadLogic/Error.hs index a8a8c5cc18f..9ca10d6b3d8 100644 --- a/hydra-node/src/Hydra/HeadLogic/Error.hs +++ b/hydra-node/src/Hydra/HeadLogic/Error.hs @@ -51,27 +51,10 @@ deriving anyclass instance ToJSON (LogicError tx) instance (IsChainState tx, ToCBOR (Input tx)) => ToCBOR (LogicError tx) where - toCBOR = \case - UnhandledInput{input, currentHeadState} -> - toCBOR ("UnhandledInput" :: Text) <> toCBOR input <> toCBOR currentHeadState - RequireFailed{requirementFailure} -> - toCBOR ("RequireFailed" :: Text) <> toCBOR requirementFailure - AssertionFailed{message} -> - toCBOR ("AssertionFailed" :: Text) <> toCBOR message - NotOurHead{ourHeadId, otherHeadId} -> - toCBOR ("NotOurHead" :: Text) <> toCBOR ourHeadId <> toCBOR otherHeadId - SideLoadSnapshotFailed{sideLoadRequirementFailure} -> - toCBOR ("SideLoadSnapshotFailed" :: Text) <> toCBOR sideLoadRequirementFailure + toCBOR = genericToCBOR instance (IsChainState tx, FromCBOR (Input tx)) => FromCBOR (LogicError tx) where - fromCBOR = - fromCBOR >>= \case - ("UnhandledInput" :: Text) -> UnhandledInput <$> fromCBOR <*> fromCBOR - "RequireFailed" -> RequireFailed <$> fromCBOR - "AssertionFailed" -> AssertionFailed <$> fromCBOR - "NotOurHead" -> NotOurHead <$> fromCBOR <*> fromCBOR - "SideLoadSnapshotFailed" -> SideLoadSnapshotFailed <$> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded LogicError" + fromCBOR = genericFromCBOR data RequirementFailure tx = ReqSnNumberInvalid {requestedSn :: SnapshotNumber, lastSeenSn :: SnapshotNumber} @@ -94,51 +77,10 @@ deriving stock instance Show (TxIdType tx) => Show (RequirementFailure tx) deriving anyclass instance ToJSON (TxIdType tx) => ToJSON (RequirementFailure tx) instance IsTx tx => ToCBOR (RequirementFailure tx) where - toCBOR = \case - ReqSnNumberInvalid{requestedSn, lastSeenSn} -> - toCBOR ("ReqSnNumberInvalid" :: Text) <> toCBOR requestedSn <> toCBOR lastSeenSn - ReqSvNumberInvalid{requestedSv, lastSeenSv} -> - toCBOR ("ReqSvNumberInvalid" :: Text) <> toCBOR requestedSv <> toCBOR lastSeenSv - ReqSnNotLeader{requestedSn, leader} -> - toCBOR ("ReqSnNotLeader" :: Text) <> toCBOR requestedSn <> toCBOR leader - ReqSnDecommitNotSettled -> - toCBOR ("ReqSnDecommitNotSettled" :: Text) - ReqSnCommitNotSettled -> - toCBOR ("ReqSnCommitNotSettled" :: Text) - InvalidMultisignature{multisig, vkeys} -> - toCBOR ("InvalidMultisignature" :: Text) <> toCBOR multisig <> toCBOR vkeys - SnapshotAlreadySigned{knownSignatures, receivedSignature} -> - toCBOR ("SnapshotAlreadySigned" :: Text) <> toCBOR knownSignatures <> toCBOR receivedSignature - AckSnNumberInvalid{requestedSn, lastSeenSn} -> - toCBOR ("AckSnNumberInvalid" :: Text) <> toCBOR requestedSn <> toCBOR lastSeenSn - SnapshotDoesNotApply{requestedSn, txid, error = validationError} -> - toCBOR ("SnapshotDoesNotApply" :: Text) <> toCBOR requestedSn <> toCBOR txid <> toCBOR validationError - NoMatchingDeposit -> - toCBOR ("NoMatchingDeposit" :: Text) - RequestedDepositExpired{depositTxId} -> - toCBOR ("RequestedDepositExpired" :: Text) <> toCBOR depositTxId - RequestedDepositNotFoundLocally{depositTxId} -> - toCBOR ("RequestedDepositNotFoundLocally" :: Text) <> toCBOR depositTxId - ReqSnUTxOSetTooLarge{utxoCount, maxAllowed} -> - toCBOR ("ReqSnUTxOSetTooLarge" :: Text) <> toCBOR utxoCount <> toCBOR maxAllowed + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (RequirementFailure tx) where - fromCBOR = - fromCBOR >>= \case - ("ReqSnNumberInvalid" :: Text) -> ReqSnNumberInvalid <$> fromCBOR <*> fromCBOR - "ReqSvNumberInvalid" -> ReqSvNumberInvalid <$> fromCBOR <*> fromCBOR - "ReqSnNotLeader" -> ReqSnNotLeader <$> fromCBOR <*> fromCBOR - "ReqSnDecommitNotSettled" -> pure ReqSnDecommitNotSettled - "ReqSnCommitNotSettled" -> pure ReqSnCommitNotSettled - "InvalidMultisignature" -> InvalidMultisignature <$> fromCBOR <*> fromCBOR - "SnapshotAlreadySigned" -> SnapshotAlreadySigned <$> fromCBOR <*> fromCBOR - "AckSnNumberInvalid" -> AckSnNumberInvalid <$> fromCBOR <*> fromCBOR - "SnapshotDoesNotApply" -> SnapshotDoesNotApply <$> fromCBOR <*> fromCBOR <*> fromCBOR - "NoMatchingDeposit" -> pure NoMatchingDeposit - "RequestedDepositExpired" -> RequestedDepositExpired <$> fromCBOR - "RequestedDepositNotFoundLocally" -> RequestedDepositNotFoundLocally <$> fromCBOR - "ReqSnUTxOSetTooLarge" -> ReqSnUTxOSetTooLarge <$> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded RequirementFailure" + fromCBOR = genericFromCBOR data SideLoadRequirementFailure tx = SideLoadInitialSnapshotMismatch @@ -155,27 +97,7 @@ deriving anyclass instance ToJSON (UTxOType tx) => ToJSON (SideLoadRequirementFa deriving anyclass instance FromJSON (UTxOType tx) => FromJSON (SideLoadRequirementFailure tx) instance IsTx tx => ToCBOR (SideLoadRequirementFailure tx) where - toCBOR = \case - SideLoadInitialSnapshotMismatch -> - toCBOR ("SideLoadInitialSnapshotMismatch" :: Text) - SideLoadSnNumberInvalid{requestedSn, lastSeenSn} -> - toCBOR ("SideLoadSnNumberInvalid" :: Text) <> toCBOR requestedSn <> toCBOR lastSeenSn - SideLoadSvNumberInvalid{requestedSv, lastSeenSv} -> - toCBOR ("SideLoadSvNumberInvalid" :: Text) <> toCBOR requestedSv <> toCBOR lastSeenSv - SideLoadUTxOToCommitInvalid{requestedSc, lastSeenSc} -> - toCBOR ("SideLoadUTxOToCommitInvalid" :: Text) <> toCBOR requestedSc <> toCBOR lastSeenSc - SideLoadUTxOToDecommitInvalid{requestedSd, lastSeenSd} -> - toCBOR ("SideLoadUTxOToDecommitInvalid" :: Text) <> toCBOR requestedSd <> toCBOR lastSeenSd - SideLoadInvalidMultisignature{multisig, vkeys} -> - toCBOR ("SideLoadInvalidMultisignature" :: Text) <> toCBOR multisig <> toCBOR vkeys + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (SideLoadRequirementFailure tx) where - fromCBOR = - fromCBOR >>= \case - ("SideLoadInitialSnapshotMismatch" :: Text) -> pure SideLoadInitialSnapshotMismatch - "SideLoadSnNumberInvalid" -> SideLoadSnNumberInvalid <$> fromCBOR <*> fromCBOR - "SideLoadSvNumberInvalid" -> SideLoadSvNumberInvalid <$> fromCBOR <*> fromCBOR - "SideLoadUTxOToCommitInvalid" -> SideLoadUTxOToCommitInvalid <$> fromCBOR <*> fromCBOR - "SideLoadUTxOToDecommitInvalid" -> SideLoadUTxOToDecommitInvalid <$> fromCBOR <*> fromCBOR - "SideLoadInvalidMultisignature" -> SideLoadInvalidMultisignature <$> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded SideLoadRequirementFailure" + fromCBOR = genericFromCBOR diff --git a/hydra-node/src/Hydra/HeadLogic/Outcome.hs b/hydra-node/src/Hydra/HeadLogic/Outcome.hs index 0b15e8b7a5c..bffcba398fc 100644 --- a/hydra-node/src/Hydra/HeadLogic/Outcome.hs +++ b/hydra-node/src/Hydra/HeadLogic/Outcome.hs @@ -199,208 +199,10 @@ instance forall tx. (IsChainState tx, IsTx tx, FromJSON (NodeState tx), FromJSON -- table (see "Hydra.Events.SQLiteBased"). Changing an encoding here breaks -- decoding of existing databases and requires a schema migration. instance IsChainState tx => ToCBOR (StateChanged tx) where - toCBOR = \case - NetworkConnected -> - toCBOR ("NetworkConnected" :: Text) - NetworkDisconnected -> - toCBOR ("NetworkDisconnected" :: Text) - PeerConnected{peer} -> - toCBOR ("PeerConnected" :: Text) <> toCBOR peer - PeerDisconnected{peer} -> - toCBOR ("PeerDisconnected" :: Text) <> toCBOR peer - NetworkVersionMismatch{ourVersion, theirVersion} -> - toCBOR ("NetworkVersionMismatch" :: Text) <> toCBOR ourVersion <> toCBOR theirVersion - NetworkClusterIDMismatch{clusterPeers, misconfiguredPeers} -> - toCBOR ("NetworkClusterIDMismatch" :: Text) <> toCBOR clusterPeers <> toCBOR misconfiguredPeers - HeadOpened{parameters, chainState, headId, headSeed, parties} -> - toCBOR ("HeadOpened" :: Text) - <> toCBOR parameters - <> toCBOR chainState - <> toCBOR headId - <> toCBOR headSeed - <> toCBOR parties - TransactionReceived{tx} -> - toCBOR ("TransactionReceived" :: Text) <> toCBOR tx - TransactionAppliedToLocalUTxO{headId, tx} -> - toCBOR ("TransactionAppliedToLocalUTxO" :: Text) <> toCBOR headId <> toCBOR tx - SnapshotRequestDecided{snapshotNumber} -> - toCBOR ("SnapshotRequestDecided" :: Text) <> toCBOR snapshotNumber - SnapshotRequested{requestedSnapshot, newLocalTxs, newCurrentDepositTxId} -> - toCBOR ("SnapshotRequested" :: Text) - <> toCBOR requestedSnapshot - <> toCBOR newLocalTxs - <> toCBOR newCurrentDepositTxId - PartySignedSnapshot{snapshotNumber, party, signature} -> - toCBOR ("PartySignedSnapshot" :: Text) - <> toCBOR snapshotNumber - <> toCBOR party - <> toCBOR signature - SnapshotConfirmed{headId, snapshot, signatures} -> - toCBOR ("SnapshotConfirmed" :: Text) - <> toCBOR headId - <> toCBOR snapshot - <> toCBOR signatures - DepositRecorded{chainState, headId, depositTxId, deposited, created, deadline} -> - toCBOR ("DepositRecorded" :: Text) - <> toCBOR chainState - <> toCBOR headId - <> toCBOR depositTxId - <> toCBOR deposited - <> toCBOR created - <> toCBOR deadline - DepositActivated{depositTxId, chainTime, deposit} -> - toCBOR ("DepositActivated" :: Text) - <> toCBOR depositTxId - <> toCBOR chainTime - <> toCBOR deposit - DepositExpired{depositTxId, chainTime, deposit} -> - toCBOR ("DepositExpired" :: Text) - <> toCBOR depositTxId - <> toCBOR chainTime - <> toCBOR deposit - DepositRecovered{chainState, headId, depositTxId, recovered} -> - toCBOR ("DepositRecovered" :: Text) - <> toCBOR chainState - <> toCBOR headId - <> toCBOR depositTxId - <> toCBOR recovered - CommitApproved{headId, utxoToCommit} -> - toCBOR ("CommitApproved" :: Text) <> toCBOR headId <> toCBOR utxoToCommit - CommitFinalized{chainState, headId, newVersion, depositTxId} -> - toCBOR ("CommitFinalized" :: Text) - <> toCBOR chainState - <> toCBOR headId - <> toCBOR newVersion - <> toCBOR depositTxId - DecommitRecorded{headId, decommitTx} -> - toCBOR ("DecommitRecorded" :: Text) <> toCBOR headId <> toCBOR decommitTx - DecommitApproved{headId, decommitTxId, utxoToDecommit} -> - toCBOR ("DecommitApproved" :: Text) - <> toCBOR headId - <> toCBOR decommitTxId - <> toCBOR utxoToDecommit - DecommitInvalid{headId, decommitTx, decommitInvalidReason} -> - toCBOR ("DecommitInvalid" :: Text) - <> toCBOR headId - <> toCBOR decommitTx - <> toCBOR decommitInvalidReason - DecommitFinalized{chainState, headId, distributedUTxO, newVersion} -> - toCBOR ("DecommitFinalized" :: Text) - <> toCBOR chainState - <> toCBOR headId - <> toCBOR distributedUTxO - <> toCBOR newVersion - HeadClosed{headId, snapshotNumber, chainState, contestationDeadline} -> - toCBOR ("HeadClosed" :: Text) - <> toCBOR headId - <> toCBOR snapshotNumber - <> toCBOR chainState - <> toCBOR contestationDeadline - HeadContested{headId, chainState, contestationDeadline, snapshotNumber} -> - toCBOR ("HeadContested" :: Text) - <> toCBOR headId - <> toCBOR chainState - <> toCBOR contestationDeadline - <> toCBOR snapshotNumber - HeadIsReadyToFanout{headId} -> - toCBOR ("HeadIsReadyToFanout" :: Text) <> toCBOR headId - HeadFanoutInitiated{headId, remainingOutputs} -> - toCBOR ("HeadFanoutInitiated" :: Text) - <> toCBOR headId - <> toCBOR remainingOutputs - HeadPartialFanoutSelected{headId, remainingOutputs, selection} -> - toCBOR ("HeadPartialFanoutSelected" :: Text) - <> toCBOR headId - <> toCBOR remainingOutputs - <> toCBOR selection - HeadFanoutReverted{headId} -> - toCBOR ("HeadFanoutReverted" :: Text) <> toCBOR headId - HeadFannedOut{headId, finalizedOutputs, chainState} -> - toCBOR ("HeadFannedOut" :: Text) - <> toCBOR headId - <> toCBOR finalizedOutputs - <> toCBOR chainState - HeadPartialFannedOut{headId, distributedOutputs, remainingOutputs, chainState, mode} -> - toCBOR ("HeadPartialFannedOut" :: Text) - <> toCBOR headId - <> toCBOR distributedOutputs - <> toCBOR remainingOutputs - <> toCBOR chainState - <> toCBOR mode - ChainRolledBack{chainState} -> - toCBOR ("ChainRolledBack" :: Text) <> toCBOR chainState - TickObserved{chainPoint, chainTime} -> - toCBOR ("TickObserved" :: Text) <> toCBOR chainPoint <> toCBOR chainTime - IgnoredHeadInitializing{headId, contestationPeriod, parties, participants} -> - toCBOR ("IgnoredHeadInitializing" :: Text) - <> toCBOR headId - <> toCBOR contestationPeriod - <> toCBOR parties - <> toCBOR participants - TxInvalid{headId, utxo, transaction, validationError} -> - toCBOR ("TxInvalid" :: Text) - <> toCBOR headId - <> toCBOR utxo - <> toCBOR transaction - <> toCBOR validationError - LocalStateCleared{headId, snapshotNumber} -> - toCBOR ("LocalStateCleared" :: Text) <> toCBOR headId <> toCBOR snapshotNumber - Checkpoint{state = nodeState} -> - toCBOR ("Checkpoint" :: Text) <> toCBOR nodeState - NodeUnsynced{chainSlot, chainTime, drift} -> - toCBOR ("NodeUnsynced" :: Text) - <> toCBOR chainSlot - <> toCBOR chainTime - <> toCBOR drift - NodeSynced{chainSlot, chainTime, drift} -> - toCBOR ("NodeSynced" :: Text) - <> toCBOR chainSlot - <> toCBOR chainTime - <> toCBOR drift + toCBOR = genericToCBOR instance IsChainState tx => FromCBOR (StateChanged tx) where - fromCBOR = - fromCBOR >>= \case - ("NetworkConnected" :: Text) -> pure NetworkConnected - "NetworkDisconnected" -> pure NetworkDisconnected - "PeerConnected" -> PeerConnected <$> fromCBOR - "PeerDisconnected" -> PeerDisconnected <$> fromCBOR - "NetworkVersionMismatch" -> NetworkVersionMismatch <$> fromCBOR <*> fromCBOR - "NetworkClusterIDMismatch" -> NetworkClusterIDMismatch <$> fromCBOR <*> fromCBOR - "HeadOpened" -> HeadOpened <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "TransactionReceived" -> TransactionReceived <$> fromCBOR - "TransactionAppliedToLocalUTxO" -> TransactionAppliedToLocalUTxO <$> fromCBOR <*> fromCBOR - "SnapshotRequestDecided" -> SnapshotRequestDecided <$> fromCBOR - "SnapshotRequested" -> SnapshotRequested <$> fromCBOR <*> fromCBOR <*> fromCBOR - "PartySignedSnapshot" -> PartySignedSnapshot <$> fromCBOR <*> fromCBOR <*> fromCBOR - "SnapshotConfirmed" -> SnapshotConfirmed <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DepositRecorded" -> DepositRecorded <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "DepositActivated" -> DepositActivated <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DepositExpired" -> DepositExpired <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DepositRecovered" -> DepositRecovered <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "CommitApproved" -> CommitApproved <$> fromCBOR <*> fromCBOR - "CommitFinalized" -> CommitFinalized <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "DecommitRecorded" -> DecommitRecorded <$> fromCBOR <*> fromCBOR - "DecommitApproved" -> DecommitApproved <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DecommitInvalid" -> DecommitInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR - "DecommitFinalized" -> DecommitFinalized <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "HeadClosed" -> HeadClosed <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "HeadContested" -> HeadContested <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "HeadIsReadyToFanout" -> HeadIsReadyToFanout <$> fromCBOR - "HeadFanoutInitiated" -> HeadFanoutInitiated <$> fromCBOR <*> fromCBOR - "HeadPartialFanoutSelected" -> HeadPartialFanoutSelected <$> fromCBOR <*> fromCBOR <*> fromCBOR - "HeadFanoutReverted" -> HeadFanoutReverted <$> fromCBOR - "HeadFannedOut" -> HeadFannedOut <$> fromCBOR <*> fromCBOR <*> fromCBOR - "HeadPartialFannedOut" -> HeadPartialFannedOut <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "ChainRolledBack" -> ChainRolledBack <$> fromCBOR - "TickObserved" -> TickObserved <$> fromCBOR <*> fromCBOR - "IgnoredHeadInitializing" -> IgnoredHeadInitializing <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "TxInvalid" -> TxInvalid <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "LocalStateCleared" -> LocalStateCleared <$> fromCBOR <*> fromCBOR - "Checkpoint" -> Checkpoint <$> fromCBOR - "NodeUnsynced" -> NodeUnsynced <$> fromCBOR <*> fromCBOR <*> fromCBOR - "NodeSynced" -> NodeSynced <$> fromCBOR <*> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded StateChanged" + fromCBOR = genericFromCBOR data Outcome tx = -- | Continue with the given state updates and side effects. diff --git a/hydra-node/src/Hydra/HeadLogic/State.hs b/hydra-node/src/Hydra/HeadLogic/State.hs index 16f53d6e66b..8bcc192843b 100644 --- a/hydra-node/src/Hydra/HeadLogic/State.hs +++ b/hydra-node/src/Hydra/HeadLogic/State.hs @@ -53,20 +53,10 @@ deriving anyclass instance (IsTx tx, ToJSON (ChainStateType tx)) => ToJSON (Head deriving anyclass instance (IsTx tx, FromJSON (ChainStateType tx)) => FromJSON (HeadState tx) instance IsChainState tx => ToCBOR (HeadState tx) where - toCBOR = \case - Idle st -> toCBOR ("Idle" :: Text) <> toCBOR st - Open st -> toCBOR ("Open" :: Text) <> toCBOR st - Closed st -> toCBOR ("Closed" :: Text) <> toCBOR st - FanoutProgress st -> toCBOR ("FanoutProgress" :: Text) <> toCBOR st + toCBOR = genericToCBOR instance IsChainState tx => FromCBOR (HeadState tx) where - fromCBOR = - fromCBOR >>= \case - ("Idle" :: Text) -> Idle <$> fromCBOR - "Open" -> Open <$> fromCBOR - "Closed" -> Closed <$> fromCBOR - "FanoutProgress" -> FanoutProgress <$> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded HeadState" + fromCBOR = genericFromCBOR -- | Update the chain state in any 'HeadState'. setChainState :: ChainStateType tx -> HeadState tx -> HeadState tx @@ -402,18 +392,10 @@ deriving anyclass instance IsTx tx => ToJSON (FanoutMode tx) deriving anyclass instance IsTx tx => FromJSON (FanoutMode tx) instance IsTx tx => ToCBOR (FanoutMode tx) where - toCBOR = \case - AutoDrain -> toCBOR ("AutoDrain" :: Text) - DistributingSelection utxo -> toCBOR ("DistributingSelection" :: Text) <> toCBOR utxo - AwaitingSelection -> toCBOR ("AwaitingSelection" :: Text) + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (FanoutMode tx) where - fromCBOR = - fromCBOR >>= \case - ("AutoDrain" :: Text) -> pure AutoDrain - "DistributingSelection" -> DistributingSelection <$> fromCBOR - "AwaitingSelection" -> pure AwaitingSelection - tag -> fail $ show tag <> " is not a proper CBOR-encoded FanoutMode" + fromCBOR = genericFromCBOR -- | A closed head whose UTxO is being distributed across multiple fanout -- transactions (on-chain @FanoutProgress@). Holds the partial-fanout bookkeeping diff --git a/hydra-node/src/Hydra/Network.hs b/hydra-node/src/Hydra/Network.hs index bed57a245dd..0f82771dda5 100644 --- a/hydra-node/src/Hydra/Network.hs +++ b/hydra-node/src/Hydra/Network.hs @@ -62,16 +62,10 @@ data WhichEtcd = EmbeddedEtcd | SystemEtcd deriving anyclass (ToJSON, FromJSON) instance ToCBOR WhichEtcd where - toCBOR = \case - EmbeddedEtcd -> toCBOR ("EmbeddedEtcd" :: Text) - SystemEtcd -> toCBOR ("SystemEtcd" :: Text) + toCBOR = genericToCBOR instance FromCBOR WhichEtcd where - fromCBOR = - fromCBOR >>= \case - ("EmbeddedEtcd" :: Text) -> pure EmbeddedEtcd - "SystemEtcd" -> pure SystemEtcd - tag -> fail $ show tag <> " is not a proper CBOR-encoded WhichEtcd" + fromCBOR = genericFromCBOR -- | Configuration for a `Node` network layer. data NetworkConfiguration = NetworkConfiguration @@ -196,26 +190,10 @@ data Connectivity deriving anyclass (ToJSON, FromJSON) instance ToCBOR Connectivity where - toCBOR = \case - PeerConnected{peer} -> toCBOR ("PeerConnected" :: Text) <> toCBOR peer - PeerDisconnected{peer} -> toCBOR ("PeerDisconnected" :: Text) <> toCBOR peer - NetworkConnected -> toCBOR ("NetworkConnected" :: Text) - NetworkDisconnected -> toCBOR ("NetworkDisconnected" :: Text) - VersionMismatch{ourVersion, theirVersion} -> - toCBOR ("VersionMismatch" :: Text) <> toCBOR ourVersion <> toCBOR theirVersion - ClusterIDMismatch{clusterPeers} -> - toCBOR ("ClusterIDMismatch" :: Text) <> toCBOR clusterPeers + toCBOR = genericToCBOR instance FromCBOR Connectivity where - fromCBOR = - fromCBOR >>= \case - ("PeerConnected" :: Text) -> PeerConnected <$> fromCBOR - "PeerDisconnected" -> PeerDisconnected <$> fromCBOR - "NetworkConnected" -> pure NetworkConnected - "NetworkDisconnected" -> pure NetworkDisconnected - "VersionMismatch" -> VersionMismatch <$> fromCBOR <*> fromCBOR - "ClusterIDMismatch" -> ClusterIDMismatch <$> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded Connectivity" + fromCBOR = genericFromCBOR newtype ProtocolVersion = ProtocolVersion Natural deriving stock (Eq, Show, Generic, Ord) diff --git a/hydra-node/src/Hydra/Network/Message.hs b/hydra-node/src/Hydra/Network/Message.hs index 0ddf8ee1707..adff23244c6 100644 --- a/hydra-node/src/Hydra/Network/Message.hs +++ b/hydra-node/src/Hydra/Network/Message.hs @@ -45,20 +45,10 @@ deriving anyclass instance IsTx tx => ToJSON (Message tx) deriving anyclass instance IsTx tx => FromJSON (Message tx) instance (ToCBOR tx, ToCBOR (UTxOType tx), ToCBOR (TxIdType tx)) => ToCBOR (Message tx) where - toCBOR = \case - ReqTx tx -> toCBOR ("ReqTx" :: Text) <> toCBOR tx - ReqSn sv sn txs decommitTx incrementUTxO -> toCBOR ("ReqSn" :: Text) <> toCBOR sv <> toCBOR sn <> toCBOR txs <> toCBOR decommitTx <> toCBOR incrementUTxO - AckSn sig sn -> toCBOR ("AckSn" :: Text) <> toCBOR sig <> toCBOR sn - ReqDec utxo -> toCBOR ("ReqDec" :: Text) <> toCBOR utxo + toCBOR = genericToCBOR instance (FromCBOR tx, FromCBOR (UTxOType tx), FromCBOR (TxIdType tx)) => FromCBOR (Message tx) where - fromCBOR = - fromCBOR >>= \case - ("ReqTx" :: Text) -> ReqTx <$> fromCBOR - "ReqSn" -> ReqSn <$> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR <*> fromCBOR - "AckSn" -> AckSn <$> fromCBOR <*> fromCBOR - "ReqDec" -> ReqDec <$> fromCBOR - msg -> fail $ show msg <> " is not a proper CBOR-encoded Message" + fromCBOR = genericFromCBOR instance IsTx tx => SignableRepresentation (Message tx) where getSignableRepresentation = serialize' diff --git a/hydra-node/src/Hydra/Node/State.hs b/hydra-node/src/Hydra/Node/State.hs index 4a75a62f990..c8f584e1e8a 100644 --- a/hydra-node/src/Hydra/Node/State.hs +++ b/hydra-node/src/Hydra/Node/State.hs @@ -62,24 +62,10 @@ deriving anyclass instance (IsTx tx, ToJSON (ChainStateType tx)) => ToJSON (Node deriving anyclass instance (IsTx tx, FromJSON (ChainStateType tx)) => FromJSON (NodeState tx) instance IsChainState tx => ToCBOR (NodeState tx) where - toCBOR = \case - NodeInSync{headState, pendingDeposits, chainPointTime} -> - toCBOR ("NodeInSync" :: Text) - <> toCBOR headState - <> toCBOR pendingDeposits - <> toCBOR chainPointTime - NodeCatchingUp{headState, pendingDeposits, chainPointTime} -> - toCBOR ("NodeCatchingUp" :: Text) - <> toCBOR headState - <> toCBOR pendingDeposits - <> toCBOR chainPointTime + toCBOR = genericToCBOR instance IsChainState tx => FromCBOR (NodeState tx) where - fromCBOR = - fromCBOR >>= \case - ("NodeInSync" :: Text) -> NodeInSync <$> fromCBOR <*> fromCBOR <*> fromCBOR - "NodeCatchingUp" -> NodeCatchingUp <$> fromCBOR <*> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded NodeState" + fromCBOR = genericFromCBOR initNodeState :: IsChainState tx => ChainStateType tx -> NodeState tx initNodeState chainState = @@ -105,16 +91,10 @@ data SyncedStatus = InSync | CatchingUp deriving anyclass (ToJSON, FromJSON) instance ToCBOR SyncedStatus where - toCBOR = \case - InSync -> toCBOR ("InSync" :: Text) - CatchingUp -> toCBOR ("CatchingUp" :: Text) + toCBOR = genericToCBOR instance FromCBOR SyncedStatus where - fromCBOR = - fromCBOR >>= \case - ("InSync" :: Text) -> pure InSync - "CatchingUp" -> pure CatchingUp - tag -> fail $ show tag <> " is not a proper CBOR-encoded SyncedStatus" + fromCBOR = genericFromCBOR syncedStatus :: NodeState tx -> SyncedStatus syncedStatus NodeInSync{} = InSync @@ -152,18 +132,10 @@ data DepositStatus = Inactive | Active | Expired deriving anyclass (ToJSON, FromJSON) instance ToCBOR DepositStatus where - toCBOR = \case - Inactive -> toCBOR ("Inactive" :: Text) - Active -> toCBOR ("Active" :: Text) - Expired -> toCBOR ("Expired" :: Text) + toCBOR = genericToCBOR instance FromCBOR DepositStatus where - fromCBOR = - fromCBOR >>= \case - ("Inactive" :: Text) -> pure Inactive - "Active" -> pure Active - "Expired" -> pure Expired - tag -> fail $ show tag <> " is not a proper CBOR-encoded DepositStatus" + fromCBOR = genericFromCBOR depositsForHead :: HeadId -> PendingDeposits tx -> PendingDeposits tx depositsForHead targetHeadId = diff --git a/hydra-node/test/Hydra/CBORSpec.hs b/hydra-node/test/Hydra/CBORSpec.hs index 3610fe9f91b..ee4bcd29894 100644 --- a/hydra-node/test/Hydra/CBORSpec.hs +++ b/hydra-node/test/Hydra/CBORSpec.hs @@ -1,18 +1,36 @@ {-# OPTIONS_GHC -Wno-orphans #-} --- | Roundtrip tests for all the hand-written 'ToCBOR' / 'FromCBOR' codecs. +-- | Tests for the 'ToCBOR' / 'FromCBOR' codecs of hydra-node types. -- --- This is the CI guard that keeps encoder/decoder pairs in sync: adding a --- constructor to any of these types without a matching codec (or with fields --- decoded in the wrong order) fails here. +-- Three layers of protection: +-- +-- * Unit tests for 'genericToCBOR' / 'genericFromCBOR' pinning down the +-- constructor-name-tagged format they produce (including that newtypes +-- and single-constructor records carry the tag). +-- +-- * Roundtrip properties keeping encoder/decoder pairs in sync: adding a +-- constructor without a matching codec (or with fields decoded in the +-- wrong order) fails here. +-- +-- * Golden tests locking the concrete byte-level formats, one sample per +-- constructor. These catch changes that roundtrip properties cannot see, +-- e.g. reordering fields in a data declaration of a generically derived +-- codec, or symmetric encoder+decoder drift. If one fails, the change +-- breaks decoding of persisted data (hydra.db) or the API wire format; +-- only delete and regenerate a golden file as a deliberate, documented +-- format change. module Hydra.CBORSpec where import Hydra.Prelude import Test.Hydra.Prelude +import Cardano.Binary (decodeFull', serialize') +import Codec.CBOR.Write (toStrictByteString) import Hydra.API.ClientInput (ClientInput) import Hydra.API.ServerOutput ( ClientMessage, + DecommitInvalidReason, + FanoutProgressMode, Greetings, HeadStatus, InvalidInput (..), @@ -22,29 +40,115 @@ import Hydra.API.ServerOutput ( ) import Hydra.Chain (ChainEvent, OnChainTx, PostChainTx, PostTxError) import Hydra.Chain.Direct.State (ChainStateAt) +import Hydra.HeadLogic.Error (RequirementFailure, SideLoadRequirementFailure) import Hydra.HeadLogic.Outcome (StateChanged) -import Hydra.HeadLogic.State (HeadState, SeenSnapshot) +import Hydra.HeadLogic.State (FanoutMode, HeadState, SeenSnapshot) import Hydra.HeadLogic.StateEvent (StateEvent (..)) import Hydra.Ledger.Cardano (Tx) -import Hydra.Network (Connectivity) +import Hydra.Network (Connectivity, WhichEtcd) +import Hydra.Network.Message (Message) import Hydra.Node.Environment (Environment) -import Hydra.Node.State (Deposit, NodeState) +import Hydra.Node.State (Deposit, DepositStatus, NodeState, SyncedStatus) import Hydra.Tx (ConfirmedSnapshot, Snapshot) import Test.Hydra.API.ClientInput () import Test.Hydra.API.ServerOutput () -import Test.Hydra.CBOR (goldenCBOR, roundtripCBOR) +import Test.Hydra.CBOR (genGoldenSamples, goldenCBOR, roundtripCBOR) import Test.Hydra.Chain.Direct.State () import Test.Hydra.HeadLogic.Outcome () import Test.Hydra.HeadLogic.StateEvent () +import Test.Hydra.Network.Message () import Test.Hydra.Node.Environment () import Test.QuickCheck (resize) -import Test.QuickCheck.Arbitrary.ADT (ADTArbitrary (..), ConstructorArbitraryPair (..), toADTArbitrary) +import Test.QuickCheck.Arbitrary.ADT (ADTArbitrary (..), ConstructorArbitraryPair (..), ToADTArbitrary, toADTArbitrary) instance Arbitrary InvalidInput where arbitrary = InvalidInput <$> arbitrary <*> arbitrary +-- * ToADTArbitrary instances for per-constructor golden samples + +instance ToADTArbitrary (ClientMessage Tx) +instance ToADTArbitrary (TimedServerOutput Tx) +instance ToADTArbitrary InvalidInput +instance ToADTArbitrary HeadStatus +instance ToADTArbitrary FanoutProgressMode +instance ToADTArbitrary NetworkInfo +instance ToADTArbitrary (DecommitInvalidReason Tx) +instance ToADTArbitrary (ConfirmedSnapshot Tx) +instance ToADTArbitrary (PostChainTx Tx) +instance ToADTArbitrary (OnChainTx Tx) +instance ToADTArbitrary (PostTxError Tx) +instance ToADTArbitrary (ChainEvent Tx) +instance ToADTArbitrary (HeadState Tx) +instance ToADTArbitrary (SeenSnapshot Tx) +instance ToADTArbitrary (FanoutMode Tx) +instance ToADTArbitrary (NodeState Tx) +instance ToADTArbitrary SyncedStatus +instance ToADTArbitrary DepositStatus +instance ToADTArbitrary (Deposit Tx) +instance ToADTArbitrary Connectivity +instance ToADTArbitrary WhichEtcd +instance ToADTArbitrary (RequirementFailure Tx) +instance ToADTArbitrary (SideLoadRequirementFailure Tx) + +-- * Test types for the generic codec + +data GenericCBORSum + = GenNullary + | GenPositional Int Text + | GenRecord {genA :: Word64, genB :: [Int], genC :: Maybe Text} + deriving stock (Eq, Show, Generic) + +instance ToCBOR GenericCBORSum where + toCBOR = genericToCBOR + +instance FromCBOR GenericCBORSum where + fromCBOR = genericFromCBOR + +newtype GenericCBORNewtype = GenericCBORNewtype Int + deriving stock (Eq, Show, Generic) + +instance ToCBOR GenericCBORNewtype where + toCBOR = genericToCBOR + +instance FromCBOR GenericCBORNewtype where + fromCBOR = genericFromCBOR + spec :: Spec spec = parallel $ do + describe "genericToCBOR / genericFromCBOR" $ do + it "encodes a nullary constructor as just its name tag" $ + serialize' GenNullary `shouldBe` serialize' ("GenNullary" :: Text) + + it "encodes the name tag followed by fields in declaration order" $ + serialize' (GenPositional 42 "hi") + `shouldBe` toStrictByteString + (toCBOR ("GenPositional" :: Text) <> toCBOR (42 :: Int) <> toCBOR ("hi" :: Text)) + + it "encodes record fields in declaration order" $ + serialize' GenRecord{genA = 7, genB = [1, 2], genC = Just "x"} + `shouldBe` toStrictByteString + ( toCBOR ("GenRecord" :: Text) + <> toCBOR (7 :: Word64) + <> toCBOR ([1, 2] :: [Int]) + <> toCBOR (Just ("x" :: Text)) + ) + + it "tags newtypes with their constructor name too" $ + serialize' (GenericCBORNewtype 7) + `shouldBe` toStrictByteString (toCBOR ("GenericCBORNewtype" :: Text) <> toCBOR (7 :: Int)) + + it "roundtrips all constructor shapes" $ do + decodeFull' (serialize' GenNullary) `shouldBe` Right GenNullary + decodeFull' (serialize' (GenPositional 42 "hi")) `shouldBe` Right (GenPositional 42 "hi") + decodeFull' (serialize' GenRecord{genA = 7, genB = [1, 2], genC = Nothing}) + `shouldBe` Right GenRecord{genA = 7, genB = [1, 2], genC = Nothing} + decodeFull' (serialize' (GenericCBORNewtype 7)) `shouldBe` Right (GenericCBORNewtype 7) + + it "fails decoding an unknown tag naming the type" $ + case decodeFull' @GenericCBORSum (serialize' ("Bogus" :: Text)) of + Left err -> show err `shouldContain` "is not a proper CBOR-encoded GenericCBORSum" + Right v -> expectationFailure $ "unexpectedly decoded: " <> show v + describe "API types" $ do roundtripCBOR $ Proxy @(ClientInput Tx) roundtripCBOR $ Proxy @(ServerOutput Tx) @@ -77,6 +181,38 @@ spec = parallel $ do -- 'StateChanged' constructor, stored as raw CBOR. goldenCBOR "StateEvent Tx" "golden/StateEvent.cbor" genGoldenStateEvents + -- One golden per CBOR-encoded type, one sample per constructor. Locks the + -- byte-level formats of the hydra.db event payloads and the API/network + -- wire messages. + describe "golden formats" $ do + goldenCBOR "ClientInput Tx" "golden/ClientInput.cbor" (genGoldenSamples @(ClientInput Tx)) + goldenCBOR "ServerOutput Tx" "golden/ServerOutput.cbor" (genGoldenSamples @(ServerOutput Tx)) + goldenCBOR "TimedServerOutput Tx" "golden/TimedServerOutput.cbor" (genGoldenSamples @(TimedServerOutput Tx)) + goldenCBOR "ClientMessage Tx" "golden/ClientMessage.cbor" (genGoldenSamples @(ClientMessage Tx)) + goldenCBOR "Greetings Tx" "golden/Greetings.cbor" (genGoldenSamples @(Greetings Tx)) + goldenCBOR "InvalidInput" "golden/InvalidInput.cbor" (genGoldenSamples @InvalidInput) + goldenCBOR "HeadStatus" "golden/HeadStatus.cbor" (genGoldenSamples @HeadStatus) + goldenCBOR "FanoutProgressMode" "golden/FanoutProgressMode.cbor" (genGoldenSamples @FanoutProgressMode) + goldenCBOR "NetworkInfo" "golden/NetworkInfo.cbor" (genGoldenSamples @NetworkInfo) + goldenCBOR "DecommitInvalidReason Tx" "golden/DecommitInvalidReason.cbor" (genGoldenSamples @(DecommitInvalidReason Tx)) + goldenCBOR "Message Tx" "golden/Message.cbor" (genGoldenSamples @(Message Tx)) + goldenCBOR "ConfirmedSnapshot Tx" "golden/ConfirmedSnapshot.cbor" (genGoldenSamples @(ConfirmedSnapshot Tx)) + goldenCBOR "PostChainTx Tx" "golden/PostChainTx.cbor" (genGoldenSamples @(PostChainTx Tx)) + goldenCBOR "OnChainTx Tx" "golden/OnChainTx.cbor" (genGoldenSamples @(OnChainTx Tx)) + goldenCBOR "PostTxError Tx" "golden/PostTxError.cbor" (genGoldenSamples @(PostTxError Tx)) + goldenCBOR "ChainEvent Tx" "golden/ChainEvent.cbor" (genGoldenSamples @(ChainEvent Tx)) + goldenCBOR "HeadState Tx" "golden/HeadState.cbor" (genGoldenSamples @(HeadState Tx)) + goldenCBOR "SeenSnapshot Tx" "golden/SeenSnapshot.cbor" (genGoldenSamples @(SeenSnapshot Tx)) + goldenCBOR "FanoutMode Tx" "golden/FanoutMode.cbor" (genGoldenSamples @(FanoutMode Tx)) + goldenCBOR "NodeState Tx" "golden/NodeState.cbor" (genGoldenSamples @(NodeState Tx)) + goldenCBOR "SyncedStatus" "golden/SyncedStatus.cbor" (genGoldenSamples @SyncedStatus) + goldenCBOR "DepositStatus" "golden/DepositStatus.cbor" (genGoldenSamples @DepositStatus) + goldenCBOR "Deposit Tx" "golden/Deposit.cbor" (genGoldenSamples @(Deposit Tx)) + goldenCBOR "Connectivity" "golden/Connectivity.cbor" (genGoldenSamples @Connectivity) + goldenCBOR "WhichEtcd" "golden/WhichEtcd.cbor" (genGoldenSamples @WhichEtcd) + goldenCBOR "RequirementFailure Tx" "golden/RequirementFailure.cbor" (genGoldenSamples @(RequirementFailure Tx)) + goldenCBOR "SideLoadRequirementFailure Tx" "golden/SideLoadRequirementFailure.cbor" (genGoldenSamples @(SideLoadRequirementFailure Tx)) + -- | One 'StateEvent' per 'StateChanged' constructor, in declaration order: -- 'ToADTArbitrary' enumerates the constructors generically, so coverage of -- every constructor holds by construction and new constructors are included diff --git a/hydra-node/testlib/Test/Hydra/CBOR.hs b/hydra-node/testlib/Test/Hydra/CBOR.hs index e2927055b92..9d61d0b5f06 100644 --- a/hydra-node/testlib/Test/Hydra/CBOR.hs +++ b/hydra-node/testlib/Test/Hydra/CBOR.hs @@ -10,7 +10,8 @@ import Codec.CBOR.Write (toLazyByteString) import Data.Typeable (typeRep) import System.Directory (createDirectoryIfMissing, doesFileExist) import System.FilePath (takeDirectory) -import Test.QuickCheck (Property, (===)) +import Test.QuickCheck (Property, resize, (===)) +import Test.QuickCheck.Arbitrary.ADT (ADTArbitrary (..), ConstructorArbitraryPair (..), ToADTArbitrary, toADTArbitrary) -- | Test that a value can be roundtripped through its CBOR encoding. prop_canRoundtripCBOREncoding :: @@ -43,6 +44,16 @@ roundtripCBOR p = -- cannot see. If this fails, the change breaks existing databases and needs -- a schema migration; only delete and regenerate the golden file alongside -- one. +-- | One sample per constructor of @a@, in declaration order, enumerated +-- generically by 'ToADTArbitrary': coverage of every constructor holds by +-- construction and new constructors are included automatically. Samples are +-- generated small (resized): golden files lock tags and field order, which +-- small values exercise just as well. +genGoldenSamples :: forall a. ToADTArbitrary a => Gen [a] +genGoldenSamples = do + ADTArbitrary{adtCAPs} <- resize 5 $ toADTArbitrary (Proxy @a) + pure $ capArbitrary <$> adtCAPs + goldenCBOR :: forall a. (ToCBOR a, FromCBOR a) => diff --git a/hydra-prelude/hydra-prelude.cabal b/hydra-prelude/hydra-prelude.cabal index 4cd3cc62859..9ab4eedf6ad 100644 --- a/hydra-prelude/hydra-prelude.cabal +++ b/hydra-prelude/hydra-prelude.cabal @@ -17,6 +17,7 @@ library hs-source-dirs: src c-sources: cbits/revision.c exposed-modules: + Hydra.CBOR.Generic Hydra.Prelude Hydra.Version diff --git a/hydra-prelude/src/Hydra/CBOR/Generic.hs b/hydra-prelude/src/Hydra/CBOR/Generic.hs new file mode 100644 index 00000000000..c26e2163a70 --- /dev/null +++ b/hydra-prelude/src/Hydra/CBOR/Generic.hs @@ -0,0 +1,133 @@ +{-# LANGUAGE DataKinds #-} + +-- | Generic derivation of 'ToCBOR' / 'FromCBOR' instances in the +-- constructor-name-tagged format used for hydra-node persistence and API +-- messages. +-- +-- The encoding of a value is the constructor name as a CBOR text string, +-- followed by the encodings of the constructor fields in declaration order, +-- concatenated without any list framing: +-- +-- @ +-- toCBOR ("ConstructorName" :: Text) <> toCBOR field1 <> toCBOR field2 <> ... +-- @ +-- +-- Every constructor is tagged, including the single constructor of records +-- and newtypes. Tagging by name (instead of by declaration index like the +-- @serialise@ package) means adding or reordering constructors does not +-- change the encoding of existing data; removing or renaming a constructor, +-- or changing the order or type of its fields, does and requires a migration +-- of persisted data. +-- +-- Since the field encodings come from the data type declaration, the +-- declaration itself becomes the wire format: reordering record fields is a +-- format change that the type checker will not flag. Golden tests (see +-- 'Hydra.CBORSpec' in hydra-node) are the guard against that. +-- +-- Decoding fails with @"\" is not a proper CBOR-encoded \@ +-- when the decoded tag matches no constructor. +module Hydra.CBOR.Generic ( + genericToCBOR, + genericFromCBOR, +) where + +import Relude + +import Cardano.Binary (Decoder, Encoding, FromCBOR (..), ToCBOR (..)) +import GHC.Generics +import GHC.TypeLits (KnownSymbol, symbolVal) + +-- | Encode a value in the constructor-name-tagged CBOR format: +-- +-- @ +-- instance ToCBOR MyType where +-- toCBOR = genericToCBOR +-- @ +genericToCBOR :: (Generic a, GToCBOR (Rep a)) => a -> Encoding +genericToCBOR = gToCBOR . from + +-- | Decode a value from the constructor-name-tagged CBOR format produced by +-- 'genericToCBOR': +-- +-- @ +-- instance FromCBOR MyType where +-- fromCBOR = genericFromCBOR +-- @ +genericFromCBOR :: (Generic a, GFromCBOR (Rep a)) => Decoder s a +genericFromCBOR = to <$> gFromCBOR + +-- * Encoding + +class GToCBOR f where + gToCBOR :: f p -> Encoding + +instance GToCBOR f => GToCBOR (D1 meta f) where + gToCBOR (M1 x) = gToCBOR x + +instance (GToCBOR f, GToCBOR g) => GToCBOR (f :+: g) where + gToCBOR = \case + L1 x -> gToCBOR x + R1 x -> gToCBOR x + +instance (KnownSymbol name, GFieldsToCBOR f) => GToCBOR (C1 ('MetaCons name fixity hasSelectors) f) where + gToCBOR (M1 x) = toCBOR (conNameText (Proxy @name)) <> gFieldsToCBOR x + +class GFieldsToCBOR f where + gFieldsToCBOR :: f p -> Encoding + +instance GFieldsToCBOR U1 where + gFieldsToCBOR U1 = mempty + +instance (GFieldsToCBOR f, GFieldsToCBOR g) => GFieldsToCBOR (f :*: g) where + gFieldsToCBOR (x :*: y) = gFieldsToCBOR x <> gFieldsToCBOR y + +instance GFieldsToCBOR f => GFieldsToCBOR (S1 meta f) where + gFieldsToCBOR (M1 x) = gFieldsToCBOR x + +instance ToCBOR a => GFieldsToCBOR (K1 i a) where + gFieldsToCBOR (K1 a) = toCBOR a + +-- * Decoding + +class GFromCBOR f where + gFromCBOR :: Decoder s (f p) + +instance (KnownSymbol typeName, GConsFromCBOR f) => GFromCBOR (D1 ('MetaData typeName moduleName package isNewtype) f) where + gFromCBOR = do + tag <- fromCBOR + case gConsFromCBOR tag of + Just decodeFields -> M1 <$> decodeFields + Nothing -> + fail $ show tag <> " is not a proper CBOR-encoded " <> symbolVal (Proxy @typeName) + +class GConsFromCBOR f where + -- | The field decoder of the constructor matching the given tag, if any. + gConsFromCBOR :: Text -> Maybe (Decoder s (f p)) + +instance (GConsFromCBOR f, GConsFromCBOR g) => GConsFromCBOR (f :+: g) where + gConsFromCBOR tag = + (fmap L1 <$> gConsFromCBOR tag) <|> (fmap R1 <$> gConsFromCBOR tag) + +instance (KnownSymbol name, GFieldsFromCBOR f) => GConsFromCBOR (C1 ('MetaCons name fixity hasSelectors) f) where + gConsFromCBOR tag + | tag == conNameText (Proxy @name) = Just (M1 <$> gFieldsFromCBOR) + | otherwise = Nothing + +class GFieldsFromCBOR f where + gFieldsFromCBOR :: Decoder s (f p) + +instance GFieldsFromCBOR U1 where + gFieldsFromCBOR = pure U1 + +instance (GFieldsFromCBOR f, GFieldsFromCBOR g) => GFieldsFromCBOR (f :*: g) where + gFieldsFromCBOR = (:*:) <$> gFieldsFromCBOR <*> gFieldsFromCBOR + +instance GFieldsFromCBOR f => GFieldsFromCBOR (S1 meta f) where + gFieldsFromCBOR = M1 <$> gFieldsFromCBOR + +instance FromCBOR a => GFieldsFromCBOR (K1 i a) where + gFieldsFromCBOR = K1 <$> fromCBOR + +-- | The constructor name from generic metadata, as 'Text'. +conNameText :: KnownSymbol name => Proxy name -> Text +conNameText = toText . symbolVal diff --git a/hydra-prelude/src/Hydra/Prelude.hs b/hydra-prelude/src/Hydra/Prelude.hs index 5afeee997fa..03e6c4805ca 100644 --- a/hydra-prelude/src/Hydra/Prelude.hs +++ b/hydra-prelude/src/Hydra/Prelude.hs @@ -30,6 +30,8 @@ module Hydra.Prelude ( elems, FromCBOR (..), ToCBOR (..), + genericToCBOR, + genericFromCBOR, FromJSON (..), ToJSON (..), encodePretty, @@ -113,6 +115,7 @@ import Data.ByteString.Base16 qualified as Base16 import Data.Fixed (Fixed (..)) import Data.Text qualified as T import Data.Time.Clock (nominalDiffTimeToSeconds, secondsToNominalDiffTime) +import Hydra.CBOR.Generic (genericFromCBOR, genericToCBOR) import Relude hiding ( MVar, Nat, diff --git a/hydra-tx/src/Hydra/Tx/Snapshot.hs b/hydra-tx/src/Hydra/Tx/Snapshot.hs index d560400a832..00396b18328 100644 --- a/hydra-tx/src/Hydra/Tx/Snapshot.hs +++ b/hydra-tx/src/Hydra/Tx/Snapshot.hs @@ -180,18 +180,10 @@ data ConfirmedSnapshot tx deriving anyclass (ToJSON, FromJSON) instance IsTx tx => ToCBOR (ConfirmedSnapshot tx) where - toCBOR = \case - InitialSnapshot{headId} -> - toCBOR ("InitialSnapshot" :: Text) <> toCBOR headId - ConfirmedSnapshot{snapshot, signatures} -> - toCBOR ("ConfirmedSnapshot" :: Text) <> toCBOR snapshot <> toCBOR signatures + toCBOR = genericToCBOR instance IsTx tx => FromCBOR (ConfirmedSnapshot tx) where - fromCBOR = - fromCBOR >>= \case - ("InitialSnapshot" :: Text) -> InitialSnapshot <$> fromCBOR - "ConfirmedSnapshot" -> ConfirmedSnapshot <$> fromCBOR <*> fromCBOR - tag -> fail $ show tag <> " is not a proper CBOR-encoded ConfirmedSnapshot" + fromCBOR = genericFromCBOR -- | Safely get a 'Snapshot' from a confirmed snapshot. -- diff --git a/typos.toml b/typos.toml index d4607882b62..211e6c8e0fe 100644 --- a/typos.toml +++ b/typos.toml @@ -9,6 +9,8 @@ "Compactible" = "Compactible" # 2[nd] ordinal suffix macro in spec/src/macros.tex "nd" = "nd" +# GHC.Type[Lits] module name +"Lits" = "Lits" [default] # Git commit hashes in docs get tokenized into letter runs (e.g. "ba" in

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 literal 0 HcmV?d00001 diff --git a/hydra-node/golden/ClientMessage.cbor b/hydra-node/golden/ClientMessage.cbor new file mode 100644 index 0000000000000000000000000000000000000000..1ceb12b48e2bccd7e9f8a5f6bb6fcaa76afc6107 GIT binary patch literal 52582 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