@@ -26,10 +26,10 @@ import CardanoClient (
26
26
)
27
27
import CardanoNode (NodeLog )
28
28
import Control.Concurrent.Async (mapConcurrently_ )
29
- import Control.Lens ((.~) , (^.) , (^..) , (^?) )
29
+ import Control.Lens ((.~) , (?~) , ( ^.) , (^..) , (^?) )
30
30
import Data.Aeson (Value , object , (.=) )
31
31
import Data.Aeson qualified as Aeson
32
- import Data.Aeson.Lens (key , values , _JSON , _String )
32
+ import Data.Aeson.Lens (atKey , key , values , _JSON , _String )
33
33
import Data.Aeson.Types (parseMaybe )
34
34
import Data.ByteString (isInfixOf )
35
35
import Data.ByteString qualified as B
@@ -108,6 +108,7 @@ import HydraNode (
108
108
input ,
109
109
output ,
110
110
postDecommit ,
111
+ prepareHydraNode ,
111
112
requestCommitTx ,
112
113
send ,
113
114
waitFor ,
@@ -116,6 +117,7 @@ import HydraNode (
116
117
waitMatch ,
117
118
withHydraCluster ,
118
119
withHydraNode ,
120
+ withPreparedHydraNode ,
119
121
)
120
122
import Network.HTTP.Conduit (parseUrlThrow )
121
123
import Network.HTTP.Conduit qualified as L
@@ -1316,6 +1318,84 @@ canDecommit tracer workDir node hydraScriptsTxId =
1316
1318
1317
1319
RunningNode {networkId, nodeSocket, blockTime} = node
1318
1320
1321
+ -- | Can side load snapshot and resume agreement after a peer comes back online with healthy configuration
1322
+ canSideLoadSnapshot :: Tracer IO EndToEndLog -> FilePath -> RunningNode -> [TxId ] -> IO ()
1323
+ canSideLoadSnapshot tracer workDir cardanoNode hydraScriptsTxId = do
1324
+ let clients = [Alice , Bob , Carol ]
1325
+ [(aliceCardanoVk, aliceCardanoSk), (bobCardanoVk, _), (carolCardanoVk, _)] <- forM clients keysFor
1326
+ seedFromFaucet_ cardanoNode aliceCardanoVk 100_000_000 (contramap FromFaucet tracer)
1327
+ seedFromFaucet_ cardanoNode bobCardanoVk 100_000_000 (contramap FromFaucet tracer)
1328
+ seedFromFaucet_ cardanoNode carolCardanoVk 100_000_000 (contramap FromFaucet tracer)
1329
+
1330
+ let contestationPeriod = UnsafeContestationPeriod 1
1331
+ let depositDeadline = UnsafeDepositDeadline 200
1332
+ aliceChainConfig <-
1333
+ chainConfigFor Alice workDir nodeSocket hydraScriptsTxId [Bob , Carol ] contestationPeriod depositDeadline
1334
+ <&> setNetworkId networkId
1335
+ bobChainConfig <-
1336
+ chainConfigFor Bob workDir nodeSocket hydraScriptsTxId [Alice , Carol ] contestationPeriod depositDeadline
1337
+ <&> setNetworkId networkId
1338
+ carolChainConfig <-
1339
+ chainConfigFor Carol workDir nodeSocket hydraScriptsTxId [Alice , Bob ] contestationPeriod depositDeadline
1340
+ <&> setNetworkId networkId
1341
+
1342
+ withHydraNode hydraTracer aliceChainConfig workDir 1 aliceSk [bobVk, carolVk] [1 , 2 , 3 ] $ \ n1 -> do
1343
+ aliceUTxO <- seedFromFaucet cardanoNode aliceCardanoVk 1_000_000 (contramap FromFaucet tracer)
1344
+ withHydraNode hydraTracer bobChainConfig workDir 2 bobSk [aliceVk, carolVk] [1 , 2 , 3 ] $ \ n2 -> do
1345
+ -- Carol starts its node missconfigured
1346
+ let pparamsDecorator = atKey " maxTxSize" ?~ toJSON (Aeson. Number 0 )
1347
+ wrongOptions <- prepareHydraNode carolChainConfig workDir 3 carolSk [aliceVk, bobVk] [1 , 2 , 3 ] pparamsDecorator
1348
+ withPreparedHydraNode hydraTracer workDir 3 wrongOptions $ \ n3 -> do
1349
+ -- Init
1350
+ send n1 $ input " Init" []
1351
+ headId <- waitForAllMatch (10 * blockTime) [n1, n2, n3] $ headIsInitializingWith (Set. fromList [alice, bob, carol])
1352
+
1353
+ -- Alice commits something
1354
+ requestCommitTx n1 aliceUTxO >>= submitTx cardanoNode
1355
+
1356
+ -- Everyone else commits nothing
1357
+ mapConcurrently_ (\ n -> requestCommitTx n mempty >>= submitTx cardanoNode) [n2, n3]
1358
+
1359
+ -- Observe open with the relevant UTxOs
1360
+ waitFor hydraTracer (20 * blockTime) [n1, n2, n3] $
1361
+ output " HeadIsOpen" [" utxo" .= toJSON aliceUTxO, " headId" .= headId]
1362
+
1363
+ -- Alice submits a new transaction
1364
+ utxo <- getSnapshotUTxO n1
1365
+ tx <- mkTransferTx testNetworkId utxo aliceCardanoSk aliceCardanoVk
1366
+ send n1 $ input " NewTx" [" transaction" .= tx]
1367
+
1368
+ -- Alice and Bob accept it
1369
+ waitForAllMatch (200 * blockTime) [n1, n2] $ \ v -> do
1370
+ guard $ v ^? key " tag" == Just " TxValid"
1371
+ guard $ v ^? key " transactionId" == Just (toJSON $ txId tx)
1372
+
1373
+ -- Carol does not because of its node being missconfigured
1374
+ waitMatch 3 n3 $ \ v -> do
1375
+ guard $ v ^? key " tag" == Just " TxInvalid"
1376
+ guard $ v ^? key " transaction" . key " txId" == Just (toJSON $ txId tx)
1377
+
1378
+ -- Carol disconnects and the others observe it
1379
+ waitForAllMatch (100 * blockTime) [n1, n2] $ \ v -> do
1380
+ guard $ v ^? key " tag" == Just " PeerDisconnected"
1381
+
1382
+ -- Carol reconnects with reconfigured node
1383
+ withHydraNode hydraTracer carolChainConfig workDir 3 carolSk [aliceVk, bobVk] [1 , 2 , 3 ] $ \ n3 -> do
1384
+ -- Everyone confirms it
1385
+ -- Note: We can't use `waitForAlMatch` here as it expects them to
1386
+ -- emit the exact same datatype; but Carol will be behind in sequence
1387
+ -- numbers as she was offline.
1388
+ flip mapConcurrently_ [n1, n2, n3] $ \ n ->
1389
+ waitMatch (200 * blockTime) n $ \ v -> do
1390
+ guard $ v ^? key " tag" == Just " SnapshotConfirmed"
1391
+ guard $ v ^? key " snapshot" . key " number" == Just (toJSON (2 :: Integer ))
1392
+ -- Just check that everyone signed it.
1393
+ let sigs = v ^.. key " signatures" . key " multiSignature" . values
1394
+ guard $ length sigs == 3
1395
+ where
1396
+ RunningNode {nodeSocket, networkId, blockTime} = cardanoNode
1397
+ hydraTracer = contramap FromHydraNode tracer
1398
+
1319
1399
-- * L2 scenarios
1320
1400
1321
1401
-- | Finds UTxO owned by given key in the head and creates transactions
0 commit comments