Skip to content

Commit 0d14f10

Browse files
authored
Merge pull request #1227 from input-output-hk/ensemble/survive-conway-fork-2
Survive Conway fork
2 parents c59505c + bd6e763 commit 0d14f10

File tree

8 files changed

+315
-82
lines changed

8 files changed

+315
-82
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ changes.
4040
- **BREAKING** Changes the `NodeOptions` log output because of internal
4141
restructuring of chain layer configuration.
4242

43+
- Adapt cardano client and the chain-sync client to survive after the fork to Conway era.
44+
4345
## [0.14.0] - 2023-12-04
4446

4547
- **BREAKING** Multiple changes to the Hydra Head protocol on-chain:

hydra-cardano-api/src/Cardano/Api/UTxO.hs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ module Cardano.Api.UTxO where
1010

1111
import Cardano.Api hiding (UTxO, toLedgerUTxO)
1212
import Cardano.Api qualified
13+
import Cardano.Api.Shelley (ReferenceScript (..))
14+
import Data.Bifunctor (second)
1315
import Data.Coerce (coerce)
1416
import Data.List qualified as List
1517
import Data.Map (Map)
@@ -84,8 +86,36 @@ min = UTxO . uncurry Map.singleton . Map.findMin . toMap
8486

8587
-- * Type Conversions
8688

87-
fromApi :: Cardano.Api.UTxO Era -> UTxO
88-
fromApi = coerce
89+
-- | Transforms a UTxO containing tx outs from any era into Babbage era.
90+
fromApi :: Cardano.Api.UTxO era -> UTxO
91+
fromApi (Cardano.Api.UTxO eraUTxO) =
92+
let eraPairs = Map.toList eraUTxO
93+
babbagePairs = second coerceOutputToEra <$> eraPairs
94+
in fromPairs babbagePairs
95+
where
96+
coerceOutputToEra :: TxOut CtxUTxO era -> TxOut CtxUTxO Era
97+
coerceOutputToEra (TxOut eraAddress eraValue eraDatum eraRefScript) =
98+
TxOut
99+
(coerceAddressToEra eraAddress)
100+
(coerceValueToEra eraValue)
101+
(coerceDatumToEra eraDatum)
102+
(coerceRefScriptToEra eraRefScript)
103+
104+
coerceAddressToEra :: AddressInEra era -> AddressInEra Era
105+
coerceAddressToEra (AddressInEra _ eraAddress) = anyAddressInShelleyBasedEra ShelleyBasedEraBabbage (toAddressAny eraAddress)
106+
107+
coerceValueToEra :: TxOutValue era -> TxOutValue Era
108+
coerceValueToEra (TxOutAdaOnly _ eraLovelace) = lovelaceToTxOutValue BabbageEra eraLovelace
109+
coerceValueToEra (TxOutValue _ value) = TxOutValue MaryEraOnwardsBabbage value
110+
111+
coerceDatumToEra :: TxOutDatum CtxUTxO era -> TxOutDatum CtxUTxO Era
112+
coerceDatumToEra TxOutDatumNone = TxOutDatumNone
113+
coerceDatumToEra (TxOutDatumHash _ hashScriptData) = TxOutDatumHash AlonzoEraOnwardsBabbage hashScriptData
114+
coerceDatumToEra (TxOutDatumInline _ hashableScriptData) = TxOutDatumInline BabbageEraOnwardsBabbage hashableScriptData
115+
116+
coerceRefScriptToEra :: ReferenceScript era -> ReferenceScript Era
117+
coerceRefScriptToEra ReferenceScriptNone = ReferenceScriptNone
118+
coerceRefScriptToEra (ReferenceScript _ scriptInAnyLang) = ReferenceScript BabbageEraOnwardsBabbage scriptInAnyLang
89119

90120
toApi :: UTxO -> Cardano.Api.UTxO Era
91121
toApi = coerce

hydra-cluster/src/CardanoNode.hs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,17 @@ import Data.Aeson qualified as Aeson
1212
import Data.Aeson.Lens (atKey, key, _Number)
1313
import Data.Text qualified as Text
1414
import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
15-
import Hydra.Cardano.Api (AsType (AsPaymentKey), File (..), NetworkId, PaymentKey, SigningKey, SocketPath, VerificationKey, generateSigningKey, getVerificationKey)
15+
import Hydra.Cardano.Api (
16+
AsType (AsPaymentKey),
17+
File (..),
18+
NetworkId,
19+
PaymentKey,
20+
SigningKey,
21+
SocketPath,
22+
VerificationKey,
23+
generateSigningKey,
24+
getVerificationKey,
25+
)
1626
import Hydra.Cardano.Api qualified as Api
1727
import Hydra.Chain.CardanoClient (QueryPoint (QueryTip), queryProtocolParameters)
1828
import Hydra.Cluster.Fixture (
@@ -135,8 +145,8 @@ withCardanoNodeOnKnownNetwork ::
135145
FilePath ->
136146
-- | A well-known Cardano network to connect to.
137147
KnownNetwork ->
138-
(RunningNode -> IO ()) ->
139-
IO ()
148+
(RunningNode -> IO a) ->
149+
IO a
140150
withCardanoNodeOnKnownNetwork tracer workDir knownNetwork action = do
141151
copyKnownNetworkFiles
142152
networkId <- readNetworkId

hydra-cluster/test/Test/EndToEndSpec.hs

Lines changed: 89 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,7 @@ import Data.Map qualified as Map
3737
import Data.Set qualified as Set
3838
import Data.Text qualified as Text
3939
import Data.Time (secondsToDiffTime)
40-
import Hydra.Cardano.Api (
41-
AddressInEra,
42-
GenesisParameters (..),
43-
NetworkId (Testnet),
44-
NetworkMagic (NetworkMagic),
45-
PaymentKey,
46-
SlotNo (..),
47-
ToUTxOContext (toUTxOContext),
48-
TxId,
49-
TxIn (..),
50-
VerificationKey,
51-
isVkTxOut,
52-
lovelaceToValue,
53-
mkTxIn,
54-
mkVkAddress,
55-
serialiseAddress,
56-
signTx,
57-
txOutValue,
58-
txOuts',
59-
unEpochNo,
60-
pattern TxValidityLowerBound,
61-
)
40+
import Hydra.Cardano.Api hiding (Value, cardanoEra, queryGenesisParameters)
6241
import Hydra.Chain.Direct.Fixture (defaultPParams, testNetworkId)
6342
import Hydra.Chain.Direct.State ()
6443
import Hydra.Cluster.Faucet (
@@ -527,6 +506,7 @@ spec = around (showLogsOnFailure "EndToEndSpec") $ do
527506

528507
describe "forking eras" $ do
529508
it "does report on unsupported era" $ \tracer -> do
509+
pendingWith "Currently supporting Conway era no future upcoming"
530510
withClusterTempDir "unsupported-era" $ \tmpDir -> do
531511
args <- setupCardanoDevnet tmpDir
532512
forkIntoConwayInEpoch tmpDir args 1
@@ -535,18 +515,19 @@ spec = around (showLogsOnFailure "EndToEndSpec") $ do
535515
let node = RunningNode{nodeSocket, networkId = defaultNetworkId, pparams}
536516
hydraScriptsTxId <- publishHydraScriptsAs node Faucet
537517
chainConfig <- chainConfigFor Alice tmpDir nodeSocket hydraScriptsTxId [] cperiod
538-
withHydraNode' chainConfig tmpDir 1 aliceSk [] [1] pparams Nothing $ \out mStdErr ph -> do
518+
withHydraNode' chainConfig tmpDir 1 aliceSk [] [1] pparams Nothing $ \out stdErr ph -> do
539519
-- Assert nominal startup
540520
waitForLog 5 out "missing NodeOptions" (Text.isInfixOf "NodeOptions")
541521

542522
waitUntilEpoch tmpDir args node 1
543523

544524
waitForProcess ph `shouldReturn` ExitFailure 1
545-
errorOutputs <- hGetContents mStdErr
525+
errorOutputs <- hGetContents stdErr
546526
errorOutputs `shouldContain` "Received blocks in unsupported era"
547527
errorOutputs `shouldContain` "upgrade your hydra-node"
548528

549529
it "does report on unsupported era on startup" $ \tracer -> do
530+
pendingWith "Currently supporting Conway era no future upcoming"
550531
withClusterTempDir "unsupported-era-startup" $ \tmpDir -> do
551532
args <- setupCardanoDevnet tmpDir
552533
forkIntoConwayInEpoch tmpDir args 1
@@ -558,25 +539,104 @@ spec = around (showLogsOnFailure "EndToEndSpec") $ do
558539

559540
waitUntilEpoch tmpDir args node 2
560541

561-
withHydraNode' chainConfig tmpDir 1 aliceSk [] [1] pparams Nothing $ \_out mStdErr ph -> do
542+
withHydraNode' chainConfig tmpDir 1 aliceSk [] [1] pparams Nothing $ \_out stdErr ph -> do
562543
waitForProcess ph `shouldReturn` ExitFailure 1
563-
errorOutputs <- hGetContents mStdErr
544+
errorOutputs <- hGetContents stdErr
564545
errorOutputs `shouldContain` "Connected to cardano-node in unsupported era"
565546
errorOutputs `shouldContain` "upgrade your hydra-node"
566547

548+
it "support new era" $ \tracer -> do
549+
withClusterTempDir "support-new-era" $ \tmpDir -> do
550+
args <- setupCardanoDevnet tmpDir
551+
552+
forkIntoConwayInEpoch tmpDir args 10
553+
withCardanoNode (contramap FromCardanoNode tracer) tmpDir args defaultNetworkId $
554+
\nodeSocket -> do
555+
let pparams = defaultPParams
556+
node = RunningNode{nodeSocket, networkId = defaultNetworkId, pparams}
557+
lovelaceBalanceValue = 100_000_000
558+
-- Funds to be used as fuel by Hydra protocol transactions
559+
(aliceCardanoVk, _) <- keysFor Alice
560+
seedFromFaucet_ node aliceCardanoVk lovelaceBalanceValue (contramap FromFaucet tracer)
561+
-- Get some UTXOs to commit to a head
562+
(aliceExternalVk, aliceExternalSk) <- generate genKeyPair
563+
committedUTxOByAlice <- seedFromFaucet node aliceExternalVk aliceCommittedToHead (contramap FromFaucet tracer)
564+
565+
hydraScriptsTxId <- publishHydraScriptsAs node Faucet
566+
chainConfig <- chainConfigFor Alice tmpDir nodeSocket hydraScriptsTxId [] cperiod
567+
568+
let hydraTracer = contramap FromHydraNode tracer
569+
withHydraNode hydraTracer chainConfig tmpDir 1 aliceSk [] [1] pparams $ \n1 -> do
570+
send n1 $ input "Init" []
571+
headId <- waitForAllMatch 10 [n1] $ headIsInitializingWith (Set.fromList [alice])
572+
573+
requestCommitTx n1 committedUTxOByAlice <&> signTx aliceExternalSk >>= submitTx node
574+
575+
waitFor hydraTracer 3 [n1] $ output "HeadIsOpen" ["utxo" .= committedUTxOByAlice, "headId" .= headId]
576+
577+
waitUntilEpoch tmpDir args node 10
578+
579+
send n1 $ input "Close" []
580+
waitMatch 3 n1 $ \v -> do
581+
guard $ v ^? key "tag" == Just "HeadIsClosed"
582+
guard $ v ^? key "headId" == Just (toJSON headId)
583+
snapshotNumber <- v ^? key "snapshotNumber"
584+
guard $ snapshotNumber == Aeson.Number 0
585+
586+
it "support new era on restart" $ \tracer -> do
587+
withClusterTempDir "support-new-era-restart" $ \tmpDir -> do
588+
args <- setupCardanoDevnet tmpDir
589+
590+
forkIntoConwayInEpoch tmpDir args 10
591+
withCardanoNode (contramap FromCardanoNode tracer) tmpDir args defaultNetworkId $
592+
\nodeSocket -> do
593+
let pparams = defaultPParams
594+
node = RunningNode{nodeSocket, networkId = defaultNetworkId, pparams}
595+
lovelaceBalanceValue = 100_000_000
596+
-- Funds to be used as fuel by Hydra protocol transactions
597+
(aliceCardanoVk, _) <- keysFor Alice
598+
seedFromFaucet_ node aliceCardanoVk lovelaceBalanceValue (contramap FromFaucet tracer)
599+
-- Get some UTXOs to commit to a head
600+
(aliceExternalVk, aliceExternalSk) <- generate genKeyPair
601+
committedUTxOByAlice <- seedFromFaucet node aliceExternalVk aliceCommittedToHead (contramap FromFaucet tracer)
602+
603+
hydraScriptsTxId <- publishHydraScriptsAs node Faucet
604+
chainConfig <- chainConfigFor Alice tmpDir nodeSocket hydraScriptsTxId [] cperiod
605+
606+
let hydraTracer = contramap FromHydraNode tracer
607+
headId <- withHydraNode hydraTracer chainConfig tmpDir 1 aliceSk [] [1] pparams $ \n1 -> do
608+
send n1 $ input "Init" []
609+
headId <- waitForAllMatch 10 [n1] $ headIsInitializingWith (Set.fromList [alice])
610+
611+
requestCommitTx n1 committedUTxOByAlice <&> signTx aliceExternalSk >>= submitTx node
612+
613+
waitFor hydraTracer 3 [n1] $ output "HeadIsOpen" ["utxo" .= committedUTxOByAlice, "headId" .= headId]
614+
615+
pure headId
616+
617+
waitUntilEpoch tmpDir args node 10
618+
619+
withHydraNode hydraTracer chainConfig tmpDir 1 aliceSk [] [1] pparams $ \n1 -> do
620+
send n1 $ input "Close" []
621+
waitMatch 3 n1 $ \v -> do
622+
guard $ v ^? key "tag" == Just "HeadIsClosed"
623+
guard $ v ^? key "headId" == Just (toJSON headId)
624+
snapshotNumber <- v ^? key "snapshotNumber"
625+
guard $ snapshotNumber == Aeson.Number 0
626+
567627
-- | Wait until given number of epoch. This uses the epoch and slot lengths from
568628
-- the 'ShelleyGenesisFile' of the node args passed in.
569629
waitUntilEpoch :: FilePath -> CardanoNodeArgs -> RunningNode -> Natural -> IO ()
570630
waitUntilEpoch stateDirectory args RunningNode{networkId, nodeSocket} toEpochNo = do
571631
fromEpochNo :: Natural <- fromIntegral . unEpochNo <$> queryEpochNo networkId nodeSocket QueryTip
572632
toEpochNo `shouldSatisfy` (> fromEpochNo)
573-
shellyGenesisFile :: Aeson.Value <- unsafeDecodeJsonFile (stateDirectory </> nodeShelleyGenesisFile args)
633+
shelleyGenesisFile :: Aeson.Value <- unsafeDecodeJsonFile (stateDirectory </> nodeShelleyGenesisFile args)
574634
let slotLength =
575635
fromMaybe (error "Field epochLength not found") $
576-
shellyGenesisFile ^? key "slotLength" . _Double
636+
shelleyGenesisFile ^? key "slotLength" . _Double
577637
epochLength =
578638
fromMaybe (error "Field epochLength not found") $
579-
shellyGenesisFile ^? key "epochLength" . _Double
639+
shelleyGenesisFile ^? key "epochLength" . _Double
580640
threadDelay . realToFrac $ fromIntegral (toEpochNo - fromEpochNo) * epochLength * slotLength
581641

582642
waitForLog :: DiffTime -> Handle -> Text -> (Text -> Bool) -> IO ()

0 commit comments

Comments
 (0)