Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No processing of transactions during a decommit #1540

Merged
merged 5 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 27 additions & 5 deletions hydra-node/json-schemas/logs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,16 @@ definitions:
$ref: "api.yaml#/components/schemas/SnapshotNumber"
leader:
$ref: "api.yaml#/components/schemas/Party"
- title: "ReqSnDecommitNotSettled"
description: >-
Received a ReqSn message with specified new decommit but the previous one was not settled.
additionalProperties: false
required:
- tag
properties:
tag:
type: string
enum: ["ReqSnDecommitNotSettled"]
- title: "InvalidMultisignature"
description: >-
Multisignature computed for a snapshot from individual parties signature is invalid.
Expand Down Expand Up @@ -2202,21 +2212,33 @@ definitions:
Description of the cause of the validation failure.
- title: WaitOnSnapshotNumber
description: >-
Current observed snapshot is not the right one, waiting for some other
Current observed snapshot number is not the right one, waiting for some other
number.
type: object
additionalProperties: false
required:
- tag
- waitingFor
- waitingForNumber
properties:
tag:
type: string
enum: ["WaitOnSnapshotNumber"]
waitingFor:
waitingForNumber:
"$ref": "api.yaml#/components/schemas/SnapshotNumber"
description: >-
The expected number.
- title: WaitOnSnapshotVersion
description: >-
Requested snapshot version is not up to date, waiting for the next version number.
type: object
additionalProperties: false
required:
- tag
- waitingForVersion
properties:
tag:
type: string
enum: ["WaitOnSnapshotVersion"]
waitingForVersion:
"$ref": "api.yaml#/components/schemas/SnapshotVersion"
- title: WaitOnSeenSnapshot
description: >-
No current snapshot is available, waiting for some snapshot to start.
Expand Down
121 changes: 72 additions & 49 deletions hydra-node/src/Hydra/HeadLogic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -405,50 +405,49 @@ onOpenNetworkReqSn ::
Maybe tx ->
Outcome tx
onOpenNetworkReqSn env ledger st otherParty sv sn requestedTxIds mDecommitTx =
-- Spec: require v = v ∧ s = ŝ + 1 ∧ leader(s) = j
-- Spec: require s = ŝ + 1 ∧ leader(s) = j
requireReqSn $
-- Spec: wait ŝ = ̅S.s
waitNoSnapshotInFlight $
-- Spec: require ̅S.𝑈 ◦ txω ≠ ⊥
-- ηω ← combine(outputs(txω))
-- 𝑈_active ← ̅S.𝑈 ◦ txω \ outputs(txω)
requireApplicableDecommitTx $ \(activeUTxO, mUtxoToDecommit) ->
-- Resolve transactions by-id
waitResolvableTxs $ \requestedTxs -> do
-- Spec: require 𝑈_active ◦ Treq ≠ ⊥
-- 𝑈 ← 𝑈_active ◦ Treq
requireApplyTxs activeUTxO requestedTxs $ \u -> do
-- Spec: ŝ ← ̅S.s + 1
-- NOTE: confSn == seenSn == sn here
let nextSnapshot =
Snapshot
{ headId
, version = version
, number = sn
, confirmed = requestedTxIds
, utxo = u
, utxoToDecommit = mUtxoToDecommit
-- Spec: wait v = v̂
waitOnSnapshotVersion $
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved
requireApplicableDecommitTx $ \(activeUTxO, mUtxoToDecommit) ->
-- Resolve transactions by-id
waitResolvableTxs $ \requestedTxs -> do
-- Spec: require 𝑈_active ◦ Treq ≠ ⊥
-- 𝑈 ← 𝑈_active ◦ Treq
requireApplyTxs activeUTxO requestedTxs $ \u -> do
-- Spec: ŝ ← ̅S.s + 1
-- NOTE: confSn == seenSn == sn here
let nextSnapshot =
Snapshot
{ headId
, version = version
, number = sn
, confirmed = requestedTxIds
, utxo = u
, utxoToDecommit = mUtxoToDecommit
}
-- Spec: η ← combine(𝑈)
-- σᵢ ← MS-Sign(kₕˢⁱᵍ, (cid‖v‖ŝ‖η‖ηω))
let snapshotSignature = sign signingKey nextSnapshot
-- Spec: multicast (ackSn, ŝ, σᵢ)
(cause (NetworkEffect $ AckSn snapshotSignature sn) <>) $ do
-- Spec: ̂Σ ← ∅
-- L̂ ← 𝑈
-- 𝑋 ← T
-- T̂ ← ∅
-- for tx ∈ 𝑋 : L̂ ◦ tx ≠ ⊥
-- T̂ ← T̂ ⋃ {tx}
-- L̂ ← L̂ ◦ tx
let (newLocalTxs, newLocalUTxO) = pruneTransactions u
newState
SnapshotRequested
{ snapshot = nextSnapshot
, requestedTxIds
, newLocalUTxO
, newLocalTxs
}
-- Spec: η ← combine(𝑈)
-- σᵢ ← MS-Sign(kₕˢⁱᵍ, (cid‖v‖ŝ‖η‖ηω))
let snapshotSignature = sign signingKey nextSnapshot
-- Spec: multicast (ackSn, ŝ, σᵢ)
(cause (NetworkEffect $ AckSn snapshotSignature sn) <>) $ do
-- Spec: ̂Σ ← ∅
-- L̂ ← 𝑈
-- 𝑋 ← T
-- T̂ ← ∅
-- for tx ∈ 𝑋 : L̂ ◦ tx ≠ ⊥
-- T̂ ← T̂ ⋃ {tx}
-- L̂ ← L̂ ◦ tx
let (newLocalTxs, newLocalUTxO) = pruneTransactions u
newState
SnapshotRequested
{ snapshot = nextSnapshot
, requestedTxIds
, newLocalUTxO
, newLocalTxs
}
where
requireReqSn continue
| sv /= version =
Expand All @@ -466,6 +465,12 @@ onOpenNetworkReqSn env ledger st otherParty sv sn requestedTxIds mDecommitTx =
| otherwise =
wait $ WaitOnSnapshotNumber seenSn

waitOnSnapshotVersion continue
| version == sv =
continue
| otherwise =
wait $ WaitOnSnapshotVersion sv

waitResolvableTxs continue =
case toList (fromList requestedTxIds \\ Map.keysSet allTxs) of
[] -> continue $ mapMaybe (`Map.lookup` allTxs) requestedTxIds
Expand All @@ -475,15 +480,27 @@ onOpenNetworkReqSn env ledger st otherParty sv sn requestedTxIds mDecommitTx =
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)
-- Spec:
-- if v = S̄.v ∧ S̄.txω ̸= ⊥
-- require S̄.txω = txω
-- Uactive ← S̄.U
-- Uω ← S̄.Uω
-- else
-- require S̄.U ◦ txω ̸= ⊥
-- Uactive ← S̄.U ◦ txω \ outputs(txω )
-- Uω ← outputs(txω )
if sv == confVersion && isJust confUTxOToDecommit
then
if confUTxOToDecommit == Just (utxoFromTx decommitTx)
then cont (confirmedUTxO, confUTxOToDecommit)
else Error $ RequireFailed ReqSnDecommitNotSettled
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved
else case applyTransactions ledger currentSlot confirmedUTxO [decommitTx] of
Left (_, err) ->
Error $ RequireFailed $ SnapshotDoesNotApply sn (txId decommitTx) err
Right newConfirmedUTxO -> do
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 +529,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
3 changes: 2 additions & 1 deletion hydra-node/src/Hydra/HeadLogic/Outcome.hs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ causes = Continue []

data WaitReason tx
= WaitOnNotApplicableTx {validationError :: ValidationError}
| WaitOnSnapshotNumber {waitingFor :: SnapshotNumber}
| WaitOnSnapshotNumber {waitingForNumber :: SnapshotNumber}
| WaitOnSnapshotVersion {waitingForVersion :: SnapshotVersion}
| WaitOnSeenSnapshot
| WaitOnTxs {waitingForTxIds :: [TxIdType tx]}
| WaitOnContestationDeadline
Expand Down
55 changes: 42 additions & 13 deletions hydra-node/test/Hydra/BehaviorSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -418,13 +418,13 @@ spec = parallel $ do
withHydraNode bobSk [alice] chain $ \n2 -> do
openHead chain n1 n2
let decommitTx1 = SimpleTx 1 (utxoRef 1) (utxoRef 42)
send n2 (Decommit{decommitTx = decommitTx1})
send n1 (Decommit{decommitTx = decommitTx1})
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved
waitUntil [n1, n2] $
DecommitRequested{headId = testHeadId, decommitTx = decommitTx1, utxoToDecommit = utxoRefs [42]}

let decommitTx2 = SimpleTx 2 (utxoRef 2) (utxoRef 22)
send n1 (Decommit{decommitTx = decommitTx2})
waitUntil [n1] $
send n2 (Decommit{decommitTx = decommitTx2})
waitUntil [n2] $
DecommitInvalid
{ headId = testHeadId
, decommitTx = decommitTx2
Expand All @@ -433,10 +433,32 @@ spec = parallel $ do

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

send n1 (Decommit{decommitTx = decommitTx2})
send n2 (Decommit{decommitTx = decommitTx2})
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
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
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved
_ -> False

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

it "can close with decommit in flight" $
shouldRunInSim $ do
withSimulatedChainAndNetwork $ \chain ->
Expand All @@ -448,7 +470,12 @@ spec = parallel $ do
send n1 Close
waitUntil [n1, n2] $ ReadyToFanout{headId = testHeadId}
send n1 Fanout
waitUntil [n1, n2] $ HeadIsFinalized{headId = testHeadId, utxo = utxoRefs [1, 2]}

waitMatch n2 $ \case
HeadIsContested{headId, snapshotNumber} -> guard $ headId == testHeadId && snapshotNumber == 1
_ -> Nothing
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved

waitUntil [n1, n2] $ HeadIsFinalized{headId = testHeadId, utxo = utxoRefs [1]}

it "fanout utxo is correct after a decommit" $
shouldRunInSim $ do
Expand Down Expand Up @@ -535,12 +562,11 @@ spec = parallel $ do
withHydraNode aliceSk [] chain $ \n1 -> do
send n1 Init
waitUntil [n1] $ HeadIsInitializing testHeadId (fromList [alice])
simulateCommit chain (alice, utxoRef 1)
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved

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 +626,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 @@ -663,6 +689,7 @@ dummySimulatedChainNetwork =
-- | With-pattern wrapper around 'simulatedChainAndNetwork' which does 'cancel'
-- the 'tickThread'. Also, this will fix tx to 'SimpleTx' so that it can pick an
-- initial chain state to play back to our test nodes.
-- NOTE: The simulated network has a block time of 20 (simulated) seconds.
withSimulatedChainAndNetwork ::
(MonadTime m, MonadDelay m, MonadAsync m) =>
(SimulatedChainNetwork SimpleTx m -> m ()) ->
Expand Down Expand Up @@ -710,7 +737,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
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved
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 +841,7 @@ toOnChainTx now = \case
DecrementTx{headId, decrementingSnapshot} ->
OnDecrementTx
{ headId
, newVersion = version
, newVersion = version + 1
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved
, distributedOutputs = maybe mempty outputsOfUTxO utxoToDecommit
}
where
Expand All @@ -831,9 +861,8 @@ toOnChainTx now = \case
FanoutTx{} ->
OnFanoutTx{headId = testHeadId}

-- NOTE(SN): Deliberately long to emphasize that we run these tests in IOSim.
v0d1ch marked this conversation as resolved.
Show resolved Hide resolved
testContestationPeriod :: ContestationPeriod
testContestationPeriod = UnsafeContestationPeriod 3600
testContestationPeriod = UnsafeContestationPeriod 10

nothingHappensFor ::
(MonadTimer m, MonadThrow m, IsChainState tx) =>
Expand Down
Loading