Docs: add newcomer/build guide for cardano node emulator
Signed-off-by: thisisobate <[email protected]>
Signed-off-by: thisisobate <[email protected]>
Restores the temporary PR-local gate after Copilot review feedback and records bounded review-response tasks T014-T016 before dispatching a fresh worker. No behavior change.
Signed-off-by: Eric Torreborre <[email protected]>
Signed-off-by: Eric Torreborre <[email protected]>
Signed-off-by: Eric Torreborre <[email protected]>
Signed-off-by: Yurii Shynbuiev <[email protected]>
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
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].
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')
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.
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.
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.
Bump idna from 3.10 to 3.15 in /doc
Signed-off-by: jeluard <[email protected]>
The only non-default instance (Dijkstra-era ShelleyBlock) consumes only the Praos chain-dep state from its HeaderState argument, the tip is never used. Taking the full HeaderState forced the HFC dispatch in Cardano.Block to fabricate a HeaderState with 'headerStateTip = Origin' purely to satisfy the record. The original leios-prototype branch worked around the same smell with a partial toConwayHeaderState projection and an 'error "Must be in Conway"' arm. Narrowing the class to only taking the chain-dep state collapses the HFC instance to a single combined case on (chainDepState, block), using the existing ChainDepStateDijkstra pattern synonym. Also, the Dijkstra-era Shelley instance drops the redundant 'headerStateChainDep' projection on its argument.
Adapt the ImmDBServer tool to drive a Leios demo from a pre-recorded schedule: replay EBs / cert-blocks at slot timestamps and serve them over the LeiosNotify / LeiosFetch mini-protocols introduced in the diffusion chunk. The actual schedule generator lives at 'ouroboros-consensus/app/leios-schedule-gen.hs' (committed with the diffusion chunk for cabal layout reasons). - 'immdb-server' grows --leios-db / --leios-schedule / --initial-slot / --initial-time / --address. - 'Cardano.Tools.ImmDBServer.Diffusion' adds the LeiosSchedule type and a scheduler thread that ticks through schedule entries. - 'Cardano.Tools.ImmDBServer.MiniProtocols' grows LeiosNotify / LeiosFetch responders, send/recv tracers, and a slot-delay aware chainSyncServer. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Wire LeiosNotify and LeiosFetch mini-protocols into the node-to-node
diffusion layer, plus the consensus-side adaptation to the network
fork's BearerBytes + arrival-time 'Reception' API.
Mini-protocols (live in consensus, not network)
- LeiosDemoOnlyTestFetch — request/response for EB bodies and the
accompanying tx closures.
- LeiosDemoOnlyTestNotify — server pushes 'MsgLeiosBlockOffer' /
'MsgLeiosBlockTxsOffer' as EBs arrive, plus 'MsgLeiosVotes' which
this chunk handles as a no-op stub (the voting chunk replaces it).
- New 'NodeKernel' fields: 'getLeiosDB', 'getLeiosPeersVars',
'getLeiosOutstanding', 'getLeiosReady'. The 'leiosFetchLogic'
thread (0.5s loop) reads peers' offerings, runs the fetch-decision
iteration and dispatches fetches.
Network wiring
- 'Ouroboros.Consensus.Network.NodeToNode' grows codecs + handlers
for both protocols and threads the LeiosDbConnection through to the
inbound side.
- 'Ouroboros.Consensus.Node' opens the in-memory LeiosDb and passes
the handle + connection to the kernel / NTN / ChainDB layers.
BearerBytes + Reception migration
- All codec entry points drop the 'dataSize :: bytes -> Word'
argument (now a 'BearerBytes' constraint).
- 'recv' returns 'Maybe (Reception a)' carrying per-chunk arrival
times; the threadnet harness wraps reads via 'MkReception
IntMap.empty', the PeerSimulator strips via 'fmap received'.
- 'TraceSendRecv' grows arrival-time on the receive side
('TraceRecvMsg :: Maybe Time -> AnyMessage ps -> ...').
Threadnet harness
- 'MinimalChainDbArgs.mcdbLeiosDbVar' backs the in-memory LeiosDb
with a TVar so the test infrastructure can inspect it.
cabal
- Exposes 'LeiosDemoOnlyTestFetch' / 'LeiosDemoOnlyTestNotify' and
adds network-mux, pretty-simple, typed-protocols:cborg deps.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Plumb a 'ResolveLeiosBlock' typeclass through the block-application
pipeline so a Dijkstra-era block carrying 'SJust LeiosCert' has its
(empty) tx list spliced in from the EB closure in the LeiosDb before
validation. Plumbing only — no chain-selection rule change.
- 'Storage.LedgerDB.Forker' defines 'ResolveLeiosBlock' as a typeclass
with a no-op default, and the 'applyBlock' hook calls
'resolveLeiosBlock leiosDb hdrSt b' before
'tickThen{Apply,Reapply}'. The forker reads the 'ExtLedgerState' to
extract 'headerState' for the splice (used to look up the previous
EB announcement on the chain-dep state).
- 'Storage.LedgerDB.V2' threads 'LeiosDbConnection' alongside the
existing args so the new 'applyBlock' signature reaches the V2
pipeline.
- Explicit no-op instances for every block type that doesn't carry
Leios: 'ByronBlock', 'DualBlock', 'ShelleyBlock proto era',
'HardForkBlock xs' (OVERLAPPABLE), 'TestBlockWith ptype', the
storage-test 'TestBlock', the mock 'SimpleBlock''.
- Real-work instance: 'ShelleyBlock (Praos c) DijkstraEra' splices the
EB closure from the previously-announced point (looked up via
'praosStateLeiosAnnouncement' + 'praosStateLastSlot' on the Praos
chain-dep state).
- 'Ouroboros.Consensus.Cardano.Block' routes per-era dispatch to the
appropriate instance.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Add the voting capability on top of the diffusion layer: an EB becomes eligible for certification once enough peers have voted 'yes' on it (via the 'MsgLeiosVotes' message on the LeiosNotify protocol from the previous chunk). Voting state - 'LeiosVoteState' (new module) — aggregated vote state across all peers, with 'addVote' / 'subscribeVotes' write/read API. - 'NodeKernel.getLeiosVoteState' field plus the 'NodeKernelArgs.leiosVotingKey' / 'NodeKernel.leiosVoting' background thread that subscribes to local 'AcquiredEbTxs' notifications and emits one vote per acquired EB. LeiosNotify wiring - The 'MsgLeiosVotes' server case now calls 'addVote' on each incoming vote (replacing the chunk-2 no-op stub). - The notify server multiplexes EB offers with a 'processVote' loop that drains 'subscribeVotes leiosVoteState' and publishes 'MsgLeiosVotes' to peers. Tests - 'Test.LeiosVoteState' (foundation-level unit tests). - 'Test.ThreadNet.Leios' — end-to-end threadnet driver that asserts cert blocks happen, no vacuous runs, gap > 'minCertificationGap', all nodes converge, and the post-resolve replay matches the live ledger. cabal - Exposes 'LeiosVoteState'; wires 'Test.LeiosVoteState' / 'Test.ThreadNet.Leios' into the test suites. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Foundations for the Leios prototype's production side, plus the
Dijkstra-era forging path that produces EBs / cert blocks.
Foundations
- LeiosDemo{Types,Logic,Exception} — placeholder types (EbAnnouncement,
LeiosPoint, TxHash, LeiosVote), EB-forging logic, exception type.
- LeiosDemoDb / LeiosDemoDb.{Common,InMemory,SQLite} — pluggable
storage for forged EBs + cert lookup.
- ouroboros-consensus.cabal exposes the foundation modules; the
consensus-test suite gains Test.LeiosDemoDb and Test.LeiosDemoTypes;
new leios-db-bench benchmark.
- cabal.project pins cardano-ledger at leios-prototype-remake-on-chap
for 'Maybe LeiosCert' on the Dijkstra block body.
Mempool access (cumulative tx bytes)
- 'shelleyCumulativeTxBytes :: Word64' on ShelleyLedgerState +
TickedShelleyLedgerState, threaded through CBOR + the tick step.
- HFC mempool threads it per-era so the kernel can read the running
total of bytes that have entered the ledger.
Forging — Praos header EB announcement
- 'hbLeiosEbAnnouncement :: !(StrictMaybe EbAnnouncement)' on the
Praos block header (chunked CBOR-after the existing field set).
- 'PraosState.praosStateLeiosAnnouncement' / 'praosStateLastSlot'
on the Praos chain-dep state, written non-stickily on each tick.
- 'ProtocolHeaderSupportsKES.protocolStateLeiosInfo' projects the
pair out; TPraos returns Nothing. No certify flag — the
certificate, when forged, lives on the block body (see ledger PR).
Forging — Dijkstra-era 'decideLeios'
- 'ForgeBlockArgs' extended with fbEbTxs / fbLeiosDb /
fbCurrentTickedLedgerState / fbChainDepState / fbLeiosTracer so
the forger has read access to the EB-candidate tx pool, the
LeiosDb (to look up certificates and store newly-forged EBs),
the ticked ledger state and the chain-dep state.
- Dijkstra-only forging path: 'decideLeios' returns either a
Leios certificate (when a previously-announced EB has gap >
'minCertificationGap' and is locally available with a cert)
or a new EB announcement (forged from fbEbTxs).
- Cert-block forging emits an empty tx sequence on the wire; the
full transaction list is re-attached at apply time by
resolveLeiosBlock (see resolving chunk).
- ForgeBlockArgs adaptation propagates through Byron, ByronDual,
Mock and Shelley forging.
Plumbing
- ChainDB.Impl{,.Args} + LedgerDB{,.Args} grow a LeiosDbConnection
so downstream chunks (diffusion, resolving) can reach it via
ChainDbArgs / LedgerDbArgs.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Apply lead-dev review feedback: promote Rust client library and CLI as the primary integration path (with WASM scoped to browser-facing components), rework the finality framing so it's clear the bridge sets its own confirmation threshold under both current and upcoming Mithril certification, tighten the "compromised provider" clause to name the concrete consequence, and clarify the user-facing "aggregator" term with a 1inch example. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>