Home /
Input Output /
hydra
May 17, 3-4 AM (0)
May 17, 4-5 AM (0)
May 17, 5-6 AM (0)
May 17, 6-7 AM (0)
May 17, 7-8 AM (0)
May 17, 8-9 AM (0)
May 17, 9-10 AM (0)
May 17, 10-11 AM (0)
May 17, 11-12 PM (0)
May 17, 12-1 PM (0)
May 17, 1-2 PM (0)
May 17, 2-3 PM (0)
May 17, 3-4 PM (0)
May 17, 4-5 PM (0)
May 17, 5-6 PM (0)
May 17, 6-7 PM (0)
May 17, 7-8 PM (0)
May 17, 8-9 PM (0)
May 17, 9-10 PM (0)
May 17, 10-11 PM (0)
May 17, 11-12 AM (0)
May 18, 12-1 AM (0)
May 18, 1-2 AM (0)
May 18, 2-3 AM (0)
May 18, 3-4 AM (0)
May 18, 4-5 AM (0)
May 18, 5-6 AM (0)
May 18, 6-7 AM (0)
May 18, 7-8 AM (0)
May 18, 8-9 AM (0)
May 18, 9-10 AM (0)
May 18, 10-11 AM (3)
May 18, 11-12 PM (2)
May 18, 12-1 PM (75)
May 18, 1-2 PM (1)
May 18, 2-3 PM (6)
May 18, 3-4 PM (0)
May 18, 4-5 PM (1)
May 18, 5-6 PM (1)
May 18, 6-7 PM (0)
May 18, 7-8 PM (0)
May 18, 8-9 PM (1)
May 18, 9-10 PM (0)
May 18, 10-11 PM (0)
May 18, 11-12 AM (0)
May 19, 12-1 AM (0)
May 19, 1-2 AM (0)
May 19, 2-3 AM (0)
May 19, 3-4 AM (0)
May 19, 4-5 AM (0)
May 19, 5-6 AM (0)
May 19, 6-7 AM (1)
May 19, 7-8 AM (1)
May 19, 8-9 AM (0)
May 19, 9-10 AM (0)
May 19, 10-11 AM (0)
May 19, 11-12 PM (1)
May 19, 12-1 PM (0)
May 19, 1-2 PM (0)
May 19, 2-3 PM (0)
May 19, 3-4 PM (1)
May 19, 4-5 PM (0)
May 19, 5-6 PM (0)
May 19, 6-7 PM (0)
May 19, 7-8 PM (2)
May 19, 8-9 PM (2)
May 19, 9-10 PM (0)
May 19, 10-11 PM (0)
May 19, 11-12 AM (0)
May 20, 12-1 AM (0)
May 20, 1-2 AM (0)
May 20, 2-3 AM (0)
May 20, 3-4 AM (0)
May 20, 4-5 AM (0)
May 20, 5-6 AM (0)
May 20, 6-7 AM (0)
May 20, 7-8 AM (0)
May 20, 8-9 AM (1)
May 20, 9-10 AM (0)
May 20, 10-11 AM (0)
May 20, 11-12 PM (0)
May 20, 12-1 PM (0)
May 20, 1-2 PM (0)
May 20, 2-3 PM (0)
May 20, 3-4 PM (1)
May 20, 4-5 PM (2)
May 20, 5-6 PM (0)
May 20, 6-7 PM (0)
May 20, 7-8 PM (0)
May 20, 8-9 PM (0)
May 20, 9-10 PM (0)
May 20, 10-11 PM (0)
May 20, 11-12 AM (0)
May 21, 12-1 AM (0)
May 21, 1-2 AM (0)
May 21, 2-3 AM (0)
May 21, 3-4 AM (0)
May 21, 4-5 AM (0)
May 21, 5-6 AM (0)
May 21, 6-7 AM (0)
May 21, 7-8 AM (0)
May 21, 8-9 AM (0)
May 21, 9-10 AM (0)
May 21, 10-11 AM (0)
May 21, 11-12 PM (0)
May 21, 12-1 PM (0)
May 21, 1-2 PM (1)
May 21, 2-3 PM (1)
May 21, 3-4 PM (0)
May 21, 4-5 PM (0)
May 21, 5-6 PM (0)
May 21, 6-7 PM (0)
May 21, 7-8 PM (0)
May 21, 8-9 PM (0)
May 21, 9-10 PM (0)
May 21, 10-11 PM (0)
May 21, 11-12 AM (0)
May 22, 12-1 AM (0)
May 22, 1-2 AM (0)
May 22, 2-3 AM (0)
May 22, 3-4 AM (0)
May 22, 4-5 AM (0)
May 22, 5-6 AM (0)
May 22, 6-7 AM (0)
May 22, 7-8 AM (0)
May 22, 8-9 AM (0)
May 22, 9-10 AM (0)
May 22, 10-11 AM (0)
May 22, 11-12 PM (0)
May 22, 12-1 PM (0)
May 22, 1-2 PM (0)
May 22, 2-3 PM (0)
May 22, 3-4 PM (0)
May 22, 4-5 PM (0)
May 22, 5-6 PM (0)
May 22, 6-7 PM (0)
May 22, 7-8 PM (0)
May 22, 8-9 PM (0)
May 22, 9-10 PM (0)
May 22, 10-11 PM (0)
May 22, 11-12 AM (0)
May 23, 12-1 AM (0)
May 23, 1-2 AM (0)
May 23, 2-3 AM (0)
May 23, 3-4 AM (0)
May 23, 4-5 AM (0)
May 23, 5-6 AM (0)
May 23, 6-7 AM (0)
May 23, 7-8 AM (0)
May 23, 8-9 AM (0)
May 23, 9-10 AM (0)
May 23, 10-11 AM (0)
May 23, 11-12 PM (0)
May 23, 12-1 PM (0)
May 23, 1-2 PM (0)
May 23, 2-3 PM (0)
May 23, 3-4 PM (0)
May 23, 4-5 PM (0)
May 23, 5-6 PM (0)
May 23, 6-7 PM (0)
May 23, 7-8 PM (0)
May 23, 8-9 PM (0)
May 23, 9-10 PM (0)
May 23, 10-11 PM (0)
May 23, 11-12 AM (0)
May 24, 12-1 AM (0)
May 24, 1-2 AM (0)
May 24, 2-3 AM (0)
May 24, 3-4 AM (0)
104 commits this week
May 17, 2026
-
May 24, 2026
Replace UTxO hash verification with BLS accumulator commitment
Remove utxoHash/alphaUTxOHash/omegaUTxOHash from ClosedDatum, all Close/Contest redeemers, and the snapshot signing tuple. The BLS accumulator already commits to the full UTxO set (utxo ∪ alpha ∪ omega), making the three separate SHA256 hashes redundant. Full fanout now verifies outputs via a KZG membership proof (same as partial fanout) rather than hash comparison. The Fanout redeemer gains proof and crsRef fields; the three output-count fields are dropped. Snapshot signing shrinks from a 7-tuple to a 4-tuple (headId, version, snapshotNumber, accumulatorHash). Signed-off-by: Sasha Bogicevic <[email protected]>
Consolidate fanout test constants and fix uncaught exception in postTx
fanoutChunkSize and fanoutOutputThreshold are now defined once in Test.Hydra.Tx.Fixture and imported wherever needed, removing the per-file duplicates. prepareTxToPost gains explicit FanoutTx/FinalPartialFanoutTx branches (error) so GHC's exhaustiveness checker catches any future unhandled constructor, instead of silently falling through a wildcard. The two deadline-slot conversions in mkChain.postTx now throw FailedToConstructFanoutTx (a PostTxError Tx) instead of userError, so the exception is caught by Node.hs's PostTxError handler rather than propagating uncaught and crashing the node.
Benchmark partial fanout with native-token outputs
Add test for partial fanout resumption after insufficient funds
fix(tui): keep dynamically-joined peers in the alive-peers list
When the TUI receives the 'Greetings' message on (re)connect it rebuilt 'peersL' from the static 'configuredPeers' string only - dynamic peers admitted via 'AddParty' (issue #1813) were not in that list and would vanish from the alive-peers panel as soon as Greetings replayed. Merge 'configuredPeers' with the host set from 'peersInfo' so dynamically admitted peers stay visible.
fix(headlogic): allow joining-party catch-up for snapshot side-load and connectivity
Two issues observed when running the dynamic-head-participants demo manually (issue #1813): 1. Side-load rejection at the join step. The AddParty snapshot the inviter hands the joining party is signed at version N-1 (just before the UpdateParametersTx that admitted them), but the joining party's coordinatedHeadState.version is already N (after observing that L1 transaction). The existing 'requireVerifiedL1Snapshot' check rejected this as SideLoadSvNumberInvalid. Bypass the version / commit / decommit checks when the snapshot is an AddParty for us at currentVersion - 1; the multi-signature is still verified. 2. 'Network Disconnected' in the joining party's TUI. Connectivity events (NetworkConnected, PeerConnected, ...) describe the L2 mesh and are orthogonal to L2 state. 'updateCatchingUpHead' was waiting on every NetworkInput, so a late-joining party whose etcd fires NetworkConnected during chain replay never sees that event - it gets dropped after the TTL expires and 'networkInfoP' stays at the initial 'NetworkInfo False mempty'. Process ConnectivityEvents during catch-up; keep waiting only on ReceivedMessage.
demo: fix grep instructions for double-escaped log files
process-compose wraps each hydra-node log line in its own JSON envelope (via 'log_location'), so the inner JSON ends up double-escaped: \"tag\":\"PeerConnected\" rather than "tag":"PeerConnected" A literal 'grep "\"PeerConnected\""' against devnet/*-logs.txt therefore matches nothing — even when the protocol-level event is present. Switch the doc to the form that actually works: grep PeerConnected devnet/alice-logs.txt | grep 5003 plus a one-paragraph note explaining the escaping so the next runner isn't confused by their own zero-count grep result.
demo: clarify process-compose foreground TUIs and L1 race noise
Two pieces of behavior were tripping up first-time runs:
* 'is_foreground = true' in process-compose means "launch on demand"
— both TUIs are defined but only the one the user actively brings
to foreground actually starts. If only alice's TUI is launched,
bob's hydra-node has zero API subscribers and his log never
contains 'JoinFinalized' / 'HeadIsOpen' even though the
underlying 'ParametersChanged' state-change is aggregated.
* After every multi-signed snapshot, all parties race to post the
resulting L1 tx ('IncrementTx', 'UpdateParametersTx'); one wins
and the others surface 'BadInputsUTxO' in
'PostingFailed'/'PostTxOnChainFailed' traces. Net L1 activity is
visible via 'PostedTx'.
Documented both, including 'grep -c ParametersChanged
devnet/bob-logs.txt' as the protocol-level smoke test for the join,
plus a 'PostedTx' invariant for L1 activity. No behavior changes.
feat(#1813): new-party state sync via SideLoadSnapshot
After the on-chain join finalizes, the joining party's local snapshot
is still the empty 'InitialSnapshot' while the existing parties have
already confirmed the 'AddParty' snapshot. The next 'ReqSn' would be
rejected as 'ReqSnNumberInvalid'. Closes the loop by:
- Relaxing 'onOpenClientSideLoadSnapshot's multi-signature check:
when the requested snapshot's 'parameterUpdate' is 'AddParty p',
the signers were the pre-update parties, so we verify the
multi-sig against 'parties \\ {p}'. This lets the joining party
(or any node that lost state) side-load the original two-of-two
signature even though their local 'parties' now has three
members.
- Fixing 'UpdateParameters' validator/builder mismatch around
'utxoHash':
- Tx builder: 'utxoHash = hashUTxO snapshot.utxo' (back to
the same shape Increment / Decrement use).
- Validator: 'checkSnapshotSignature' now signs over
'nextUtxoHash' (matches what the off-chain
'getSignableRepresentation' emits), and
'mustOnlyChangeParties' no longer enforces
'prevUtxoHash == nextUtxoHash'. UpdateParameters may
advance the on-chain utxoHash the same way Increment/
Decrement do — without this, a Leave/Add following an
Increment failed with 'H57' (utxoHash mismatch) or 'H12'
(signature mismatch).
E2E (real cardano-node):
- 'canJoinHead' (carol joins the open head): fetches alice's
confirmed snapshot, side-loads it on carol via the WebSocket
'SideLoadSnapshot' input, deposits funds, submits a 'NewTx',
then asserts the 'SnapshotConfirmed' event on all three nodes
carries three multi-signatures and the new tx in 'confirmed'.
- 'canLeaveHead' (carol leaves the open head): deposits funds
first so the head has L2 UTxO, lets carol leave via 'DELETE
/participants/me', then alice submits a 'NewTx' and we assert
a 'SnapshotConfirmed' with /two/ multi-signatures on the
remaining two-party head.
Other:
- Updated 'demo/dynamic-head-participants.md' with a step-5 that
shows carol side-loading alice's confirmed snapshot via
websocat, removing the previous "out of scope" note.
- Plutus golden hashes regenerated (vHead / mHead) because the
validator changed.
Tests passing:
cabal test hydra-tx → 52/52
cabal test hydra-plutus → 4/4
cabal test hydra-node → 538/538
hydra-cluster e2e (this PR) → 2/2 ('dynamic head participants')
feat(network): automate etcd member-add on JoinFinalized (#1813)
When the protocol observes 'OnUpdateParametersTx (AddParty p oid host)', existing nodes now emit a 'NetworkMemberAddEffect host' and the etcd network layer dispatches an 'etcdctl member add' (well, the underlying 'Cluster.MemberAdd' gRPC call) — making the L2 cluster reconfiguration automatic where it was previously a manual ops step. All existing nodes issue the call; the second-and-onwards see 'Peer URLs already exists' / 'member ID already exist' and treat it as success, so the flow is leader-less and idempotent (matches etcd's runtime-reconf design where MemberAdd is cluster-wide via raft — see https://etcd.io/docs/v3.5/op-guide/runtime-reconf-design/). Required plumbing: - 'ParameterUpdate.AddParty' gains a 'joiningHost :: Text' field. It is /off-chain only/ — 'toOnChain' drops it before producing snapshot signable bytes or the on-chain redeemer, keeping the multi-sig authorization shape unchanged. - 'AddParticipant' client input, 'POST /participants' body, and 'ReqAddParty' network message all carry the joining host. The ServerOutput 'JoinRequested' / 'JoinFinalized' echo it back so external consumers can observe. - A new 'Network.memberAdd :: Text -> m ()' interface (in-memory test networks use 'pure ()'); 'Hydra.Network.Etcd' provides the real implementation via 'Cluster.MemberAdd' over the existing gRPC connection, swallowing 'GrpcFailedPrecondition' "already exists" errors. - 'NetworkConfiguration.joinExistingCluster :: Bool' (CLI: '--join-existing-cluster'). When set, etcd starts with '--initial-cluster-state existing' instead of 'new', so the new member joins the already-running raft cluster rather than trying to bootstrap a fresh one. - 'etcd' is started with '--strict-reconfig-check=false' so a 2→3 member-add does not get rejected as "unhealthy cluster" before the new member is online (quorum stays at 2 regardless, so the relaxed check is safe for this transition). - 'Hydra.Chain.Direct' honours a user-requested 'startChainFrom = Just ChainPointAtGenesis' (previously silently overridden to chain tip); a joining party needs this so her chain observer replays the historical 'InitTx' / 'UpdateParametersTx'. - 'Environment' gains 'joinExistingCluster :: Bool' and 'onIdleChainInitTx' grows a "joining-party" branch that opens a HeadState from an 'InitTx' whose 'parties' is a non-empty subset of our configured parties (gated on the flag, so non-joining nodes still ignore unrelated init txs). - 'Hydra.Node.Run' threads 'TVar [Party]' / 'TVar (Maybe Party)' into 'withAuthentication' and updates both via an event-sink that watches 'JoinRecorded' / 'ParametersChanged'. End-to-end (real cardano-node) test in 'hydra-cluster' extends 'canJoinHead' to start carol's hydra-node with '--join-existing-cluster' after the invite, and asserts alice + bob's etcd reports 'PeerConnected' for carol's port — proving the 'etcdctl member add' was processed and her etcd joined the existing raft cluster. Note: full L2 participation by the joining party (signing snapshots on the same number/version as alice and bob) still needs a "new-party state-sync" step (the inviter handing carol the latest confirmed snapshot, or carol reconstructing it on-chain). That work is flagged in the plan and remains a follow-up. Demo + script: - 'nix/hydra/demo.nix': carol's hydra-node command now includes '--start-chain-from 0' and '--join-existing-cluster' so the manual flow 'process-compose process start hydra-node-carol' works after 'bash demo/invite-carol.sh'. - 'demo/invite-carol.sh' passes 'joiningHost' (defaults to '127.0.0.1:5003', override via 'JOINING_HOST'). - 'demo/dynamic-head-participants.md': removed the etcd-membership caveat (it's now automated).
demo: define carol's hydra-node + TUI as disabled processes
Wire up carol's hydra-node-carol / hydra-tui-carol process-compose entries (keys, peers, ports, persistence dir) but mark them 'disabled = true' so the demo opens as a 2-party head (alice + bob). After running 'bash demo/invite-carol.sh' the user can manually start carol with: process-compose process start hydra-node-carol process-compose process start hydra-tui-carol Also add demo/dynamic-head-participants.md with the user-facing steps, including the etcd-membership caveat (carol's L2 participation requires manual etcd member add, deferred per the design plan).
fix(chain): observe UpdateParametersTx on-chain + relax value preservation
Two production bugs in the dynamic-head-participants flow surfaced via two
new hydra-cluster end-to-end tests (Join + Leave):
1. Validator rejected the post-balancing head output. The wallet's
coverFee_ calls 'ensureMinCoinTxOut' on every output, which inflates
the head output's lovelace to cover the higher min-utxo cost caused
by adding a new participation token. The 'mustPreserveHeadValueAdjustedForPT'
check used '==' which was too strict; switched to 'geq' to mirror
the existing 'mustPreserveHeadValue' rationale in Hydra.Contract.Util.
2. The chain observer never recognized UpdateParametersTx after it was
posted, so neither node emitted 'JoinFinalized' / 'LeaveFinalized'
(and the HTTP request blocked). Added 'observeUpdateParametersTx' +
'UpdateParametersObservation' + 'HeadObservation.UpdateParameters'
arm + 'convertObservation' mapping to 'OnUpdateParametersTx'.
Also registered 'FailedToConstructUpdateParametersTx' in the API JSON
schema and regenerated the head validator + minting policy script hashes
(they change because of the validator-source edit).
Tests:
hydra-cluster/test/Test/EndToEndSpec.hs
- can invite a new party to join an open two-party head
- can let a party leave an open three-party head
fix(chain): post UpdateParametersTx to L1 + cover all dyn-head outputs
Fixes the manual demo path: after alice (or bob) multi-signed the
'AddParty' snapshot, both nodes crashed when actually constructing
the L1 'UpdateParametersTx', and the API server crashed when encoding
the join-related 'ServerOutput' events. The user's POST /participants
returned an empty reply because the API thread died before responding.
Root causes were two stale 'case' expressions whose 'StateChanged' /
'PostChainTx' / 'ServerOutput' constructor sets predated Phase 1+2:
1. 'Hydra.Chain.Direct.Handlers.prepareTxToPost' had no arm for
'UpdateParametersTx', so the chain handler thread crashed with
"Non-exhaustive patterns in case" when the post-tx effect fired.
2. 'Hydra.API.ServerOutput.prepareServerOutput' had no arm for any
of 'LeaveRequested' / 'LeaveApproved' / 'LeaveFinalized' /
'LeaveInvalid' / 'JoinRequested' / 'JoinApproved' / 'JoinFinalized'
/ 'JoinInvalid', so encoding any of them in the WS server crashed
the API listener thread.
Both spots had been emitting '-Wincomplete-patterns' warnings — caught
now and the wider sweep of test scaffolding / visualizer that was also
incomplete:
- 'Hydra.Chain.Direct.State.updateParameters' is the new wrapper that
resolves the head output from spendable UTxO and the head minting
policy from the seed input, then delegates to the off-chain builder
'Hydra.Tx.UpdateParameters.updateParametersTx'. Mirrors 'decrement'.
- 'PostTxError.FailedToConstructUpdateParametersTx' is the
corresponding 'Hydra.Chain' error variant.
- 'Hydra.Chain.Direct.Handlers' imports + dispatches the new wrapper.
- 'Hydra.API.ServerOutput.prepareServerOutput' now handles all 8 new
constructors (pass-throughs to 'encodedResponse').
- 'visualize-logs/src/VisualizeLogs.hs' labels the new 'StateChanged'
events.
- Test scaffolding ('Test.Hydra.API.ClientInput', 'Test.Hydra.Chain',
'Hydra.Chain.Direct.HandlersSpec') gets exhaustive 'shrink' / case
coverage for the new variants.
With this fix the manual demo flow works end-to-end:
'nix run .#demo', drive alice+bob into 'HeadIsOpen', then in another
terminal: 'bash demo/invite-carol.sh'. Both nodes log 'JoinApproved'
+ post 'UpdateParametersTx', observe 'OnUpdateParametersTx', and
report 'JoinFinalized' with [alice, bob, carol].
Set partial fanout chunks to 6
feat(dynamic-head-participants): Phase 2 — Join
Implements the joining-party side of issue #1813. Symmetric to Phase 1:
Types & encoding
- 'ParameterUpdate' gains 'AddParty {joiningParty, joiningOnChainId}' with a
stable CBOR tag (1). 'toOnChain' maps to a new 'AddPartyOC' constructor on
'OnChainParameterUpdate'.
On-chain validator (hydra-plutus)
- 'checkUpdateParameters' handles both Remove and Add: 'AddPartyOC' appends
the new party to the on-chain datum's parties, mints exactly one PT under
the joiner's 'OnChainId' asset name, and grows the head output value by
that PT. The previous-parties multi-signature still authorizes the change
(joiner's consent is captured out-of-band; see design note in 'Head.hs').
- New 'MintParticipant' redeemer on the head minting policy: lets the
policy permit a single PT mint exclusively when the head input is being
spent with the 'UpdateParameters' redeemer.
- New error code 'H58' (FailedUpdateParametersBadPTMint).
Off-chain tx builder
- 'updateParametersTx' branches on Remove (burn) / Add (mint), mirrors the
validator's value-delta enforcement.
- Healthy contract spec 'healthyAddPartyTx' validates end-to-end on chain.
Network protocol
- New 'ReqAddParty {joiningParty, joiningOnChainId}' network message
alongside 'ReqLeave'. CBOR encoding hand-rolled to keep the existing tags
stable.
HeadLogic
- New handlers 'onOpenClientAddParticipant' (broadcasts 'ReqAddParty') and
'onOpenNetworkReqAddParty' (records 'JoinRecorded', triggers a 'ReqSn'
carrying the 'AddParty' update when leader).
- 'maybePostUpdateParametersTx' fires 'JoinApproved' + 'OnChainEffect
UpdateParametersTx' when an AddParty snapshot completes.
- 'onOpenChainUpdateParametersTx' applies the Add branch to 'parties'.
- New 'StateChanged' events: 'JoinRecorded', 'JoinApproved'.
ServerOutput & API
- New 'ClientInput.AddParticipant'; new 'ServerOutput' events
'JoinRequested', 'JoinApproved', 'JoinFinalized', 'JoinInvalid' with
'JoinInvalidReason'.
- New HTTP endpoint 'POST /participants' with 'AddParticipantRequest' body;
same response convention as 'DELETE /participants/me' (200 / 400 / 503 /
202 on timeout).
- 'api.yaml' picks up the new schemas, messages, ServerOutput / ClientInput
/ PostChainTx variants, and the new HTTP path.
- 'LeaveInvalid.reason' renamed to 'LeaveInvalid.leaveReason' to free the
'reason' field name for 'JoinInvalid.joinReason' (DuplicateRecordFields
caveat).
Network auth (Hydra.Network.Authenticate)
- 'withAuthentication' takes a new 'STM m (Maybe Party)' "joining party"
accessor. While it returns 'Just p', signed messages from 'p' are
accepted even if 'p' is not in the live party set — needed for the
joiner's 'AckSn' during the join window. 'Hydra.Node.Run' supplies
@pure Nothing@ for the production wiring; Node-shell write-through is
follow-up work.
Tests (all green)
- 'hydra-tx': 52/52 (new 'UpdateParameters (AddParty)' healthy spec).
- 'hydra-plutus': 4/4 (golden hashes regenerated for the larger validator).
- 'hydra-node': 538/538:
- 5 new HeadLogic specs covering 'AddParticipant'/'ReqAddParty' /
'JoinRecorded' / aggregate / rejecting already-member.
- 4 new HTTPServer specs for 'POST /participants'.
- 1 new Authenticate spec exercising the speculative-accept rule.
- 1 new behavior spec: open a 2-party head, alice invites carol, both
existing parties report 'JoinFinalized' with the grown parties list.
test(behavior): end-to-end leave scenario with three nodes
Adds an IOSim-based behavior spec for the full leave flow (issue #1813, dynamic-head-participants): - Three nodes (alice, bob, carol) open a head together. - Carol broadcasts 'Leave'. The simulated network delivers 'ReqLeave' to alice and bob; the snapshot leader builds a 'ReqSn' carrying the 'RemoveParty' update; all three nodes sign the snapshot. - An 'UpdateParametersTx' is posted to the simulated chain; the chain observes 'OnUpdateParametersTx'; alice and bob emit 'LeaveFinalized' whose 'newParties' contains only alice and bob. Exercises the complete loop: 'ClientInput Leave' -> 'ReqLeave' broadcast -> 'ReqSn' with 'parameterUpdate' -> multi-signed 'AckSn' -> on-chain 'UpdateParametersTx' -> 'OnUpdateParametersTx' -> 'ParametersChanged' -> 'LeaveFinalized' server output. New 'openHead3' helper added alongside existing 'openHead2'. Hydra-node suite: 522/522 passing.
chore(persistence): bump SQLite schema version 1 -> 2
The snapshot signable representation gained an optional 'parameterUpdate' field in this branch (issue #1813, dynamic-head-participants). For ordinary L2 snapshots the bytes are unchanged, so existing event logs remain interpretable — but parameter-update snapshots are a new on-the-wire shape, so the persistence schema is bumped as a marker. The migration step is a no-op.
feat(node): refuse client inputs from a departed leaver
After the leave snapshot is multi-signed and the 'UpdateParameters' L1 transaction is observed, the leaver's 'OpenState.parameters.parties' no longer contains their own party. Any subsequent client input is now answered with 'CommandFailed' rather than processed against a head the leaver is no longer a member of (issue #1813, dynamic-head-participants). New 'HeadLogicSpec' test: after applying 'ParametersChanged' that removes the local party, a 'NewTx' is rejected with 'CommandFailed'. Hydra-node suite: 521/521 passing.
feat(api): DELETE /participants/me handler
Adds the HTTP entry point for the dynamic-head-participants flow (issue #1813, Phase 1): - 'DELETE /participants/me' fires a 'Leave' 'ClientInput' and waits for the result. Returns 200 on 'LeaveFinalized', 400 on 'LeaveInvalid' (body is the 'LeaveInvalidReason' JSON) or 'CommandFailed', 503 on 'RejectedInputBecauseUnsynced', 202 on timeout (with 'LeaveSubmitted' tag). - 'api.yaml' picks up the new route + bindings. - 4 new HTTPServerSpec test cases cover the timeout, finalized, invalid, and unsynced paths. Hydra-node suite: 520/520 passing.
feat(network): dynamic accepted-parties set via STM accessor
The 'withAuthentication' middleware no longer closes over a fixed '[Party]' list; instead it takes an 'STM m [Party]' action that is read once per inbound message. This lets the live party set shrink (Phase 1) or grow (Phase 2) at runtime as 'ParametersChanged' events arrive — see issue #1813. - 'withAuthentication' (Hydra.Network.Authenticate) signature changes from '[Party]' to 'STM m [Party]'. The signature-verification path reads a fresh snapshot every call; the per-message cost is one uncontended STM read. - 'withNetwork' (Hydra.Node.Network) threads the accessor through. - 'Hydra.Node.Run' currently supplies @pure otherParties@ — a static accessor sourced from 'Environment.otherParties'. The TVar that observes 'ParametersChanged' and write-throughs is the next milestone. - New 'AuthenticateSpec' test exercises the dynamic case: after the accepted set shrinks, messages from the removed party are dropped. - All 5 existing 'AuthenticateSpec' fixtures updated to lift the static list into 'pure'. Hydra-node suite: 516/516 passing.
feat(node): wire UpdateParametersTx through snapshot + chain observation
Completes the off-chain head-logic plumbing for dynamic-head-participants (issue #1813, Phase 1): - 'onOpenNetworkReqSn' now carries the optional 'parameterUpdate' field from 'ReqSn' all the way into the constructed 'Snapshot'. Every node signs the same snapshot bytes, including the parameter-update payload. - 'maybeRequestSnapshot' in 'onOpenClientNewTx' propagates the local 'pendingParameterUpdate' into the next 'ReqSn' if there is one pending, so a leave-snapshot rolls naturally into the next snapshot round. - 'onOpenNetworkReqLeave' now triggers a 'ReqSn' carrying the 'RemoveParty' update when the recipient is the leader of the next snapshot (mirroring 'onOpenNetworkReqDec'). - New 'maybePostUpdateParametersTx' helper fires when 'AckSn' completes a parameter-update snapshot: emits 'LeaveApproved' + 'OnChainEffect UpdateParametersTx'. - New 'onOpenChainUpdateParametersTx' handler observes the L1 'OnUpdateParametersTx' and emits 'ParametersChanged' so the aggregate can rewrite 'OpenState.parameters.parties'. - 'onOpenClientLeave' now only broadcasts 'ReqLeave' (state recording + snapshot-leader trigger happens via the network loopback through 'onOpenNetworkReqLeave'), mirroring 'onOpenClientDecommit'. - 'BehaviorSpec' mock-chain 'toOnChainTx' extended to translate 'UpdateParametersTx' into 'OnUpdateParametersTx', so end-to-end behavior spec sims can drive the new flow when scenarios are added. New 'HeadLogicSpec' test case asserts that a leader receiving 'ReqLeave' emits a 'ReqSn' whose 'parameterUpdate' carries the leaving party. Full hydra-node suite: 515/515 passing.
feat(node): leave handlers + StateChanged/ServerOutput + Chain variants
Implements the off-chain state-machine surface of the dynamic-head-participants feature (issue #1813, Phase 1): - New 'PostChainTx' / 'OnChainTx' variants: 'UpdateParametersTx', 'OnUpdateParametersTx'. - New 'StateChanged' events: 'LeaveRecorded' (carries the leaving party's 'OnChainId' so all nodes can build the same on-chain parameter update), 'LeaveApproved', 'ParametersChanged'. Aggregate cases write the pending update into 'CoordinatedHeadState.pendingParameterUpdate' and, on observation, rewrite 'OpenState.parameters.parties', bump the version, and clear the pending update. - 'ReqLeave' now carries the leaver's 'OnChainId' alongside the 'Party' so the multi-signed parameter update is identical across nodes. - New head-logic handlers 'onOpenClientLeave' and 'onOpenNetworkReqLeave'. Both reject when the head is not open, when a decommit / commit / parameter update is already in flight, when the head has only one party, or when the leaving party isn't a member. - New 'ServerOutput' events: 'LeaveRequested', 'LeaveApproved', 'LeaveFinalized', 'LeaveInvalid' (with explicit 'LeaveInvalidReason'). 'Hydra.API.Server' projects the new 'StateChanged' events into them. - 'api.yaml' picks up the new ServerOutput messages and schemas, the 'UpdateParametersTx' arm of 'PostChainTx', and 'ParameterUpdate' is reused across both surfaces. Hydra-node test suite is at 514/514 passing, including 5 new specs in 'HeadLogicSpec' that exercise the leave handlers and the 'ParametersChanged' aggregation. The remaining wiring — threading 'parameterUpdate' through 'onOpenNetworkReqSn'/'onOpenNetworkAckSn' so an 'OnChainEffect UpdateParametersTx' is fired when the leave snapshot is signed, plus the chain-observation path that turns an observed 'UpdateParametersTx' into 'ParametersChanged' — lands in a follow-up commit.
feat(api): add Leave ClientInput + schema
Adds the off-chain entry point for the dynamic-head-participants flow (issue #1813, Phase 1): - 'ClientInput.Leave' for a node to request its own departure. Wiring up the 'HeadLogic' handler that consumes this client input is the next milestone. - 'api.yaml' picks up the 'Leave' message + schema and threads it through 'CommandFailed' / 'RejectedInputBecauseUnsynced' / 'SideLoadSnapshotRejected' so any of those reasons can also surface for a 'Leave' request. - 'Arbitrary'/'shrink' instance updated to cover the new constructor. - Golden 'ClientInput/Leave.json' lands via the existing round-trip generator.