Skip to content

Commit

Permalink
Decrement on-chain: Check value in the Head is preserved
Browse files Browse the repository at this point in the history
Add this check in the specfication too.
  • Loading branch information
v0d1ch committed May 7, 2024
1 parent 212bf5f commit 7dfd8b0
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 38 deletions.
2 changes: 1 addition & 1 deletion hydra-cluster/test/Test/ChainObserverSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ spec = do

commitTx <- requestCommitTx hydraNode commitUTxO

pure (signTx walletSk commitTx) >>= submitTx cardanoNode
submitTx cardanoNode (signTx walletSk commitTx)

waitFor hydraTracer 5 [hydraNode] $
output "HeadIsOpen" ["utxo" .= commitUTxO, "headId" .= headId]
Expand Down
19 changes: 18 additions & 1 deletion hydra-node/json-schemas/logs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1550,7 +1550,10 @@ definitions:
seenSnapshot:
$ref: "logs.yaml#/definitions/SeenSnapshot"
decommitTx:
$ref: "api.yaml#/components/schemas/Transaction"
oneOf:
- type: "null"
- type: object
$ref: "api.yaml#/components/schemas/Transaction"

SeenSnapshot:
oneOf:
Expand Down Expand Up @@ -2217,6 +2220,20 @@ definitions:
type: array
items:
$ref: "api.yaml#/components/schemas/TxId"
- title: WaitOnNotApplicableDecommitTx
description: >-
Somebody requested a Decommit but there is another one in flight already.
type: object
additionalProperties: false
required:
- tag
- waitingOnDecommitTx
properties:
tag:
type: string
enum: ["WaitOnNotApplicableDecommitTx"]
waitingOnDecommitTx:
$ref: "api.yaml#/components/schemas/Transaction"

IP:
type: object
Expand Down
5 changes: 2 additions & 3 deletions hydra-node/src/Hydra/Chain/Direct/Tx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ decrementTx scriptRegistry vk headId headParameters (headInput, headOutput) snap
& addInputs [(headInput, headWitness)]
& addReferenceInputs [headScriptRef]
-- NOTE: at this point 'utxoToDecommit' is populated
& addOutputs (headOutput' : map toTxContext (maybe [] (fmap snd . UTxO.pairs) utxoToDecommit))
& addOutputs (headOutput' : map toTxContext (maybe [] toList utxoToDecommit))
& addExtraRequiredSigners [verificationKeyHash vk]
where
headRedeemer = toScriptData $ Head.Decrement (toPlutusSignatures signatures)
Expand Down Expand Up @@ -552,8 +552,7 @@ closeTx scriptRegistry vk closing startSlotNo (endSlotNo, utcTime) openThreadOut
Head.Closed
{ snapshotNumber
, utxoHash = toBuiltin utxoHashBytes
, -- TODO: find a way to introduce this value
utxoToDecommitHash = toBuiltin decommitUTxOHashBytes
, utxoToDecommitHash = toBuiltin decommitUTxOHashBytes
, parties = openParties
, contestationDeadline
, contestationPeriod = openContestationPeriod
Expand Down
33 changes: 13 additions & 20 deletions hydra-node/src/Hydra/HeadLogic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -620,27 +620,25 @@ onOpenClientDecommit ::
onOpenClientDecommit env headId ledger currentSlot coordinatedHeadState decommitTx =
checkNoDecommitInFlight $
checkValidDecommitTx $
Effects
[ NetworkEffect ReqDec{transaction = decommitTx, decommitRequester = party}
]
cause (NetworkEffect ReqDec{transaction = decommitTx, decommitRequester = party})
where
checkNoDecommitInFlight continue =
case mExistingDecommitTx of
Just existingDecommitTx ->
Effects
[ ClientEffect
cause
( ClientEffect
ServerOutput.DecommitInvalid
{ headId
, decommitInvalidReason = ServerOutput.DecommitAlreadyInFlight{decommitTx = existingDecommitTx}
}
]
)
Nothing -> continue

checkValidDecommitTx cont =
case applyTransactions ledger currentSlot confirmedUTxO [decommitTx] of
Left (_, err) ->
Effects
[ ClientEffect
cause
( ClientEffect
ServerOutput.DecommitInvalid
{ headId
, decommitInvalidReason =
Expand All @@ -650,7 +648,7 @@ onOpenClientDecommit env headId ledger currentSlot coordinatedHeadState decommit
, validationError = err
}
}
]
)
Right _ -> cont

confirmedUTxO = (getSnapshot confirmedSnapshot).utxo
Expand Down Expand Up @@ -691,22 +689,18 @@ onOpenNetworkReqDec ::
onOpenNetworkReqDec env ttl openState decommitTx =
waitOnApplicableDecommit $
let decommitUTxO = utxoFromTx decommitTx
in StateChanged (DecommitRecorded decommitTx)
<> Effects
[ ClientEffect $ ServerOutput.DecommitRequested headId decommitUTxO
]
in newState (DecommitRecorded decommitTx)
<> cause (ClientEffect $ ServerOutput.DecommitRequested headId decommitUTxO)
<> if isLeader parameters party nextSn
then
Effects
[NetworkEffect (ReqSn nextSn (txId <$> localTxs) (Just decommitTx))]
else Error $ RequireFailed $ ReqSnNotLeader{requestedSn = nextSn, leader = party}
then cause (NetworkEffect (ReqSn nextSn (txId <$> localTxs) (Just decommitTx)))
else noop
where
waitOnApplicableDecommit cont =
case mExistingDecommitTx of
Nothing -> cont
Just existingDecommitTx
| ttl > 0 ->
Wait $ WaitOnNotApplicableDecommitTx decommitTx
wait $ WaitOnNotApplicableDecommitTx decommitTx
| otherwise ->
Error $ RequireFailed $ DecommitTxInFlight{decommitTx = existingDecommitTx}
Environment{party} = env
Expand Down Expand Up @@ -918,8 +912,7 @@ update env ledger st ev = case (st, ev) of
)
-- TODO: What happens if observed decrement tx get's rolled back?
| ourHeadId == headId ->
causes
[ClientEffect $ ServerOutput.DecommitFinalized{headId}]
cause (ClientEffect $ ServerOutput.DecommitFinalized{headId})
<> newState DecommitFinalized
| otherwise ->
Error NotOurHead{ourHeadId, otherHeadId = headId}
Expand Down
2 changes: 1 addition & 1 deletion hydra-node/src/Hydra/HeadLogic/Outcome.hs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ data WaitReason tx
| WaitOnSeenSnapshot
| WaitOnTxs {waitingForTxIds :: [TxIdType tx]}
| WaitOnContestationDeadline
| WaitOnNotApplicableDecommitTx { waitingOnDecommitTx :: tx}
| WaitOnNotApplicableDecommitTx {waitingOnDecommitTx :: tx}
deriving stock (Generic)

deriving stock instance IsTx tx => Eq (WaitReason tx)
Expand Down
10 changes: 4 additions & 6 deletions hydra-node/test/Hydra/Chain/Direct/ContractSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,8 @@ prop_verifyOffChainSignatures =
onChainSig = List.head . toPlutusSignatures $ aggregate [offChainSig]
onChainParty = partyToChain $ deriveParty sk
snapshotNumber = toInteger number
utxoHash =
(toBuiltin $ hashUTxO @SimpleTx utxo)
utxoToDecommitHash = maybe (toBuiltin $ hashUTxO @SimpleTx mempty) (toBuiltin . hashUTxO @SimpleTx) utxoToDecommit
utxoHash = (toBuiltin $ hashUTxO @SimpleTx utxo)
utxoToDecommitHash = (toBuiltin . hashUTxO @SimpleTx $ fromMaybe mempty utxoToDecommit)
in verifyPartySignature (headIdToCurrencySymbol headId) snapshotNumber utxoHash utxoToDecommitHash onChainParty onChainSig
& counterexample ("headId: " <> show headId)
& counterexample ("signed: " <> show onChainSig)
Expand All @@ -234,7 +233,6 @@ prop_verifySnapshotSignatures =
onChainParties = partyToChain <$> parties
signatures = toPlutusSignatures $ aggregate [sign sk snapshot | sk <- sks]
snapshotNumber = toInteger number
utxoHash =
toBuiltin (hashUTxO @SimpleTx utxo)
utxoToDecommitHash = maybe (toBuiltin $ hashUTxO @SimpleTx mempty) (toBuiltin . hashUTxO @SimpleTx) utxoToDecommit
utxoHash = toBuiltin (hashUTxO @SimpleTx utxo)
utxoToDecommitHash = (toBuiltin . hashUTxO @SimpleTx $ fromMaybe mempty utxoToDecommit)
in verifySnapshotSignature onChainParties (headIdToCurrencySymbol headId) snapshotNumber utxoHash utxoToDecommitHash signatures
9 changes: 4 additions & 5 deletions hydra-node/test/Hydra/HeadLogicSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import Hydra.HeadLogic (
aggregateState,
defaultTTL,
update,
cause,
)
import Hydra.HeadLogic.State (getHeadParameters)
import Hydra.Ledger (ChainSlot (..), IsTx (..), Ledger (..), ValidationError (..))
Expand Down Expand Up @@ -153,10 +154,8 @@ spec =
let input = NetworkInput defaultTTL alice reqDec
st <- pickBlind $ oneof $ pure <$> [inInitialState threeParties, inIdleState, inClosedState threeParties]
pure $
update aliceEnv ledger st input
`hasEffectSatisfying` \case
NetworkEffect reqDec' -> reqDec' == reqDec
_ -> False
update aliceEnv ledger st event
`shouldNotBe` cause (NetworkEffect reqDec)

it "wait for second decommit when another one is in flight" $ do
let decommitTx1 = SimpleTx 1 mempty (utxoRef 1)
Expand All @@ -174,7 +173,7 @@ spec =
let outcome = update bobEnv ledger s1 reqDecEvent2

outcome `shouldSatisfy` \case
Wait (WaitOnNotApplicableDecommitTx{waitingOnDecommitTx = decommitTx''}) ->
Wait (WaitOnNotApplicableDecommitTx{waitingOnDecommitTx = decommitTx''}) _ ->
decommitTx2 == decommitTx''
_ -> False

Expand Down
13 changes: 12 additions & 1 deletion hydra-plutus/src/Hydra/Contract/Head.hs
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,21 @@ checkDecrement ctx@ScriptContext{scriptContextTxInfo = txInfo} prevParties prevS
&& checkSnapshot
&& checkSnapshotSignature
&& mustBeSignedByParticipant ctx prevHeadId
&& mustPreserveValue
where
mustPreserveValue =
traceIfFalse $(errorCode HeadValueIsNotPreserved) $
headInValue === headOutValue
-- NOTE: head output + whatever is decommitted needs to be equal to the head input.
headOutValue = txOutValue $ head $ txInfoOutputs txInfo <> decommitOutputs

headInValue = maybe mempty (txOutValue . txInInfoResolved) $ findOwnInput ctx

decommitOutputs = tail (txInfoOutputs txInfo)

-- NOTE: we always assume Head output is the first one so we pick all other
-- outputs of a decommit tx to calculate the expected hash.
decommitUtxoHash = hashTxOuts $ tail (txInfoOutputs txInfo)
decommitUtxoHash = hashTxOuts decommitOutputs
(nextUtxoHash, nextParties, nextSnapshotNumber, nextCperiod, nextHeadId) =
case fromBuiltinData @DatumType $ getDatum (headOutputDatum ctx) of
Just
Expand Down
1 change: 1 addition & 0 deletions spec/onchain.tex
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ \subsection{Decrement Transaction}\label{sec:increment-tx}
\]
\item Transaction is signed by a participant $\exists \{\cid \mapsto \keyHash_{i} \mapsto 1\} \in \valHead' \Rightarrow \keyHash_{i} \in \txKeys$.
\todo{Need a constraint on the value in the head?}
\item Value in the head is preserved $\valHead' = \valHead$.
\end{menumerate}

\begin{figure}[h] \centering
Expand Down

0 comments on commit 7dfd8b0

Please sign in to comment.