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.
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].
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.