Skip to content

Commit

Permalink
No processing of transactions during decommit
Browse files Browse the repository at this point in the history
Signed-off-by: Sasha Bogicevic <[email protected]>
  • Loading branch information
ch1bo authored and v0d1ch committed Aug 6, 2024
1 parent 1754eff commit 0178e16
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 23 deletions.
45 changes: 35 additions & 10 deletions hydra-node/src/Hydra/HeadLogic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -474,16 +474,35 @@ onOpenNetworkReqSn env ledger st otherParty sv sn requestedTxIds mDecommitTx =
requireApplicableDecommitTx cont =
case mDecommitTx of
Nothing -> cont (confirmedUTxO, Nothing)
Just decommitTx ->
-- Spec: require ̅S.𝑈 ◦ txω /= ⊥
case applyTransactions ledger currentSlot confirmedUTxO [decommitTx] of
Left (_, err) ->
Error $ RequireFailed $ SnapshotDoesNotApply sn (txId decommitTx) err
Right newConfirmedUTxO -> do
-- Spec: 𝑈_active ← ̅S.𝑈 ◦ txω \ outputs(txω)
let utxoToDecommit = utxoFromTx decommitTx
let activeUTxO = newConfirmedUTxO `withoutUTxO` utxoToDecommit
cont (activeUTxO, Just utxoToDecommit)
Just decommitTx
-- Spec: if v = S̄.v
| sv == confVersion ->
case confUTxOToDecommit of
Nothing ->
-- Spec: require ̅S.𝑈 ◦ txω /= ⊥
case applyTransactions ledger currentSlot (spy confirmedUTxO) [decommitTx] of
Left (_, err) ->
Error $ RequireFailed $ SnapshotDoesNotApply sn (txId (spy decommitTx)) err
Right newConfirmedUTxO -> do
-- Spec: 𝑈_active ← ̅S.𝑈 ◦ txω \ outputs(txω)
let utxoToDecommit = utxoFromTx decommitTx
let activeUTxO = newConfirmedUTxO `withoutUTxO` utxoToDecommit
cont (activeUTxO, Just utxoToDecommit)
Just pendingUtxOToDecommit
| pendingUtxOToDecommit /= utxoFromTx decommitTx ->
Error $ RequireFailed ReqSnDecommitNotSettled
| otherwise ->
cont (confirmedUTxO, Just $ utxoFromTx decommitTx)
| otherwise ->
-- Spec: require ̅S.𝑈 ◦ txω /= ⊥
case applyTransactions ledger currentSlot (spy confirmedUTxO) [decommitTx] of
Left (_, err) ->
Error $ RequireFailed $ SnapshotDoesNotApply sn (txId (spy decommitTx)) err
Right newConfirmedUTxO -> do
-- Spec: 𝑈_active ← ̅S.𝑈 ◦ txω \ outputs(txω)
let utxoToDecommit = utxoFromTx decommitTx
let activeUTxO = newConfirmedUTxO `withoutUTxO` utxoToDecommit
cont (activeUTxO, Just utxoToDecommit)

-- NOTE: at this point we know those transactions apply on the localUTxO because they
-- are part of the localTxs. The snapshot can contain less transactions than the ones
Expand Down Expand Up @@ -512,6 +531,12 @@ onOpenNetworkReqSn env ledger st otherParty sv sn requestedTxIds mDecommitTx =
InitialSnapshot{} -> 0
ConfirmedSnapshot{snapshot = Snapshot{number}} -> number

Snapshot{version = confVersion} = getSnapshot confirmedSnapshot

confUTxOToDecommit = case confirmedSnapshot of
InitialSnapshot{} -> Nothing
ConfirmedSnapshot{snapshot = Snapshot{utxoToDecommit}} -> utxoToDecommit

seenSn = seenSnapshotNumber seenSnapshot

confirmedUTxO = case confirmedSnapshot of
Expand Down
1 change: 1 addition & 0 deletions hydra-node/src/Hydra/HeadLogic/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data RequirementFailure tx
= ReqSnNumberInvalid {requestedSn :: SnapshotNumber, lastSeenSn :: SnapshotNumber}
| ReqSvNumberInvalid {requestedSv :: SnapshotVersion, lastSeenSv :: SnapshotVersion}
| ReqSnNotLeader {requestedSn :: SnapshotNumber, leader :: Party}
| ReqSnDecommitNotSettled
| InvalidMultisignature {multisig :: Text, vkeys :: [VerificationKey HydraKey]}
| SnapshotAlreadySigned {knownSignatures :: [Party], receivedSignature :: Party}
| AckSnNumberInvalid {requestedSn :: SnapshotNumber, lastSeenSn :: SnapshotNumber}
Expand Down
37 changes: 31 additions & 6 deletions hydra-node/test/Hydra/BehaviorSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,29 @@ spec = parallel $ do
waitUntil [n1, n2] $ DecommitApproved{headId = testHeadId, decommitTxId = txId decommitTx2, utxoToDecommit = utxoRefs [22]}
waitUntil [n1, n2] $ DecommitFinalized{headId = testHeadId, decommitTxId = txId decommitTx2}

it "can process transactions while decommit pending" $
shouldRunInSim $ do
-- NOTE: The simulated network has a block time of 20 (simulated) seconds.
withSimulatedChainAndNetwork $ \chain ->
withHydraNode aliceSk [bob] chain $ \n1 ->
withHydraNode bobSk [alice] chain $ \n2 -> do
openHead chain n1 n2

let decommitTx = SimpleTx 1 (utxoRef 1) (utxoRef 42)
send n2 (Decommit{decommitTx})
waitUntil [n1, n2] $
DecommitRequested{headId = testHeadId, decommitTx, utxoToDecommit = utxoRefs [42]}
waitUntil [n1, n2] $
DecommitApproved{headId = testHeadId, decommitTxId = 1, utxoToDecommit = utxoRefs [42]}

let normalTx = SimpleTx 2 (utxoRef 2) (utxoRef 3)
send n2 (NewTx normalTx)
waitUntilMatch [n1, n2] $ \case
SnapshotConfirmed{snapshot = Snapshot{confirmed}} -> 2 `elem` confirmed
_ -> False

waitUntil [n1, n2] $ DecommitFinalized{headId = testHeadId, decommitTxId = 1}

it "can close with decommit in flight" $
shouldRunInSim $ do
withSimulatedChainAndNetwork $ \chain ->
Expand Down Expand Up @@ -535,12 +558,11 @@ spec = parallel $ do
withHydraNode aliceSk [] chain $ \n1 -> do
send n1 Init
waitUntil [n1] $ HeadIsInitializing testHeadId (fromList [alice])
simulateCommit chain (alice, utxoRef 1)

logs = selectTraceEventsDynamic @_ @(HydraNodeLog SimpleTx) result

logs `shouldContain` [BeginEffect alice 1 0 (ClientEffect $ HeadIsInitializing testHeadId $ fromList [alice])]
logs `shouldContain` [EndEffect alice 1 0]
logs `shouldContain` [BeginEffect alice 2 0 (ClientEffect $ HeadIsInitializing testHeadId $ fromList [alice])]
logs `shouldContain` [EndEffect alice 2 0]

describe "rolling back & forward does not make the node crash" $ do
it "does work for rollbacks past init" $
Expand Down Expand Up @@ -600,7 +622,7 @@ waitUntilMatch nodes predicate = do
failure $
toString $
unlines
[ "waitUntilMatch did not match a message within " <> show oneMonth
[ "waitUntilMatch did not match a message within " <> show oneMonth <> ", seen messages:"
, unlines (show <$> msgs)
]
where
Expand Down Expand Up @@ -710,7 +732,10 @@ simulatedChainAndNetwork initialChainState = do
Chain
{ postTx = \tx -> do
now <- getCurrentTime
createAndYieldEvent nodes history localChainState $ toOnChainTx now tx
-- Only observe "after one block"
void . async $ do
threadDelay blockTime
createAndYieldEvent nodes history localChainState $ toOnChainTx now tx
, draftCommitTx = \_ -> error "unexpected call to draftCommitTx"
, submitTx = \_ -> error "unexpected call to submitTx"
}
Expand Down Expand Up @@ -811,7 +836,7 @@ toOnChainTx now = \case
DecrementTx{headId, decrementingSnapshot} ->
OnDecrementTx
{ headId
, newVersion = version
, newVersion = version + 1
, distributedOutputs = maybe mempty outputsOfUTxO utxoToDecommit
}
where
Expand Down
31 changes: 30 additions & 1 deletion hydra-node/test/Hydra/HeadLogicSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import Hydra.HeadLogic (
defaultTTL,
update,
)
import Hydra.HeadLogic.State (getHeadParameters)
import Hydra.HeadLogic.State (SeenSnapshot (..), getHeadParameters)
import Hydra.Ledger (ChainSlot (..), IsTx (..), Ledger (..), ValidationError (..))
import Hydra.Ledger.Cardano (cardanoLedger, genKeyPair, genOutput, mkRangedTx)
import Hydra.Ledger.Simple (SimpleChainState (..), SimpleTx (..), aValidTx, simpleLedger, utxoRef, utxoRefs)
Expand Down Expand Up @@ -474,6 +474,35 @@ spec =
Error RequireFailed{} -> True
_ -> False

it "rejects same version snapshot requests with differring decommit txs" $ do
let decommitTx = SimpleTx 2 (utxoRef 2) (utxoRef 4)
activeUTxO = utxoRefs [2]
snapshot =
Snapshot
{ headId = testHeadId
, version = 0
, number = 1
, confirmed = []
, utxo = activeUTxO
, utxoToDecommit = Just $ utxoRefs [3]
}
-- NOTE: Signatures are not relevant here
s0 =
inOpenState' threeParties $
coordinatedHeadState
{ confirmedSnapshot = ConfirmedSnapshot snapshot (Crypto.aggregate [])
, seenSnapshot = LastSeenSnapshot 1
, localUTxO = activeUTxO
, decommitTx = Just $ SimpleTx 1 (utxoRef 1) (utxoRef 3)
}
reqSn = receiveMessageFrom bob $ ReqSn 0 2 [] (Just decommitTx)

outcome <- runHeadLogic bobEnv ledger s0 $ do
step reqSn
outcome `shouldSatisfy` \case
Error RequireFailed{} -> True
_ -> False

it "ignores in-flight ReqTx when closed" $ do
let s0 = inClosedState threeParties
input = receiveMessage $ ReqTx (aValidTx 42)
Expand Down
22 changes: 16 additions & 6 deletions spec/fig_offchain_prot.tex
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,29 @@
%%% REQ SN
\On{$(\hpRS,v,s,\mT_{\mathsf{req}} \red{, \tx_\alpha} , \tx_\omega)$ from $\party_j$}{
\red{\Req{$\tx_\omega = \bot ~ \lor ~ \tx_\alpha = \bot$}} \;
\Req{$v = \hatv ~ \land ~ s = \hats + 1 ~ \land ~ \hpLdr(s) = j$} \;
\Wait{$\hats = \bar{\mc S}.s$}{
\Req{$s = \hats + 1 ~ \land ~ \hpLdr(s) = j$} \;
\Wait{$ v = \hatv ~ \land ~ \hats = \bar{\mc S}.s$}{
% TODO: waiting for version observed is way longer than for no snapshot in flight!
\blue{
\Req{$\bar{\mc S}.U \applytx \tx_\omega \not= \bot$} \;
$U_{\mathsf{active}} \gets \bar{\mc S}.U \applytx \tx_\omega \setminus \mathsf{outputs(\tx_\omega)}$ \;
}
\If{$v = \bar{\mc S}.v ~ \land ~ \bar{\mc S}.\tx_{\omega} \neq \bot$ }{
\Req{$\bar{\mc S}.\tx_{\omega} = \tx_{\omega}$} \;
$U_{\mathsf{active}} \gets \bar{\mc S}.U$ \;
$U_{\omega} \gets \bar{\mc S}.U_{\omega}$
}
\Else{
\Req{$\bar{\mc S}.U \applytx \tx_{\omega} \not= \bot$} \;
$U_{\mathsf{active}} \gets \bar{\mc S}.U \applytx \tx_{\omega} \setminus \mathsf{outputs(\tx_{\omega})}$ \;
$U_{\omega} \gets \mathsf{outputs}(\tx_{\omega})$
}
}
\Req{$U_{\mathsf{active}} \applytx \mT_{\mathsf{req}} \not= \bot$} \;
$U \gets U_{\mathsf{active}} \applytx \mT_{\mathsf{req}}$ \;
$\hats \gets s$ \;
% TODO: DRY message creation
$\eta \gets \combine(U)$ \;
% TODO: handwavy combine/outputs here
\red{$\eta_\alpha \gets \mathsf{combine}(\mathsf{inputs}(\tx_\alpha))$ \;}
$\eta_\omega \gets \mathsf{combine}(\mathsf{outputs}(\tx_\omega))$ \;
$\eta_{\omega} \gets \combine(U_{\omega})$ \;
% NOTE: WE could make included transactions auditable by adding
% a merkle tree root to the (signed) snapshot data \eta
$\msSig_i \gets \msSign(\hydraSigningKey, (\cid || v || \hats || \eta \red{ || \eta_\alpha} || \eta_\omega))$ \;
Expand Down

0 comments on commit 0178e16

Please sign in to comment.