E2E devnet tests for voucher spend (closes #15)#19
Merged
Conversation
Brings up a real cardano-node devnet inside each scenario via the new DevnetEnv.withEnv bracket. Proves the environment end-to-end: "devnet comes up with a funded genesis address" ✔ The bracket is a thin wrapper over cardano-node-clients:devnet's withDevnet: constructs the Provider + Submitter, derives a fresh reificator Ed25519 key from a fixed seed, queries the genesis UTxO set, and yields a DevnetEnv record to each scenario. Wiring: - flake.nix gains a cardano-node-clients input pinned to the current main SHA (b9fbbb5). We reuse its packages.devnet-genesis so harvest does not fork the genesis JSON set. - nix/checks.nix exports E2E_GENESIS_DIR pointing at that genesis store path so withDevnet can copy it into its temp workdir. - harvest.cabal test-suite gains cardano-ledger-api and cardano-ledger-conway deps. These currently come from ledger packages directly; follow-up is to route ledger types through cardano-node-clients re-exports (flagged with FIXME in DevnetEnv). Current scenario status: - loads the fixture bundle cleanly ✔ - devnet comes up with a funded genesis address ✔ - a customer spends at an acceptor — validator accepts: pending (script deploy + spend submit is the next chunk) - four negatives: pending Test run: 39 examples, 0 failures, 5 pending. The bracket adds ~50s to CI (cold devnet startup), amortised across all scenarios in the same spec file. Refs #15.
Replace the placeholder FIXME with the real constraint: harvest explicitly avoids cardano-api (the higher-level wrapper), not the ledger packages themselves. The ledger imports here are the same low-level seam cardano-node-clients:devnet consumes, so routing them through a re-export would add indirection without changing the dependency surface.
Adds SpendSetup.deploySpendState, which submits a single setup tx that:
1. spends a genesis UTxO,
2. pays 100 ADA to the reificator's address for tx fees,
3. creates the 5-ADA script UTxO at the applied validator's address
with an inline VoucherDatum matching the fixture's commit_S_old,
4. lets build(..) balance change back to genesisAddr,
and waits for both outputs to materialise via queryUTxOs.
DevnetSpendSpec now wires the deploy step into a new scenario:
"deploys the voucher script UTxO and funds the reificator"
which asserts both TxIns are distinct and present. This is the first
test that submits a real tx to the harness devnet and observes
confirmation.
Helpers (witnessKeyHashFromSignKey, waitForUtxos, an empty NoQ
interpreter) are copied inline from cardano-node-clients' own
TxBuildSpec — they are not on the public surface, and upstreaming a
narrow set is a later discussion.
Plumbing: test-suite deps gain cardano-ledger-api,
cardano-ledger-conway, cardano-ledger-mary, containers, microlens
(all already in the closure via cardano-node-clients).
Test run: 40 examples, 0 failures, 5 pending. Devnet cold start +
deploy tx confirmation adds ~10s to the previous bracket run.
Refs #15.
The earlier /= check between dsScriptTxIn and dsReificatorTxIn was
structurally tautological — two outputs in one tx can't share a TxIn
by construction, so the assertion couldn't fail even if the deploy
logic was wrong.
Replaces it with four load-bearing checks against the returned
DeployedSpend:
- script output's address == the applied validator's address
- script output's value == the declared script-pay coin
- script output's datum is an inline datum, not NoDatum (catches a
regression where payTo' silently degrades to payTo)
- reificator output's address == the reificator's own address
- reificator output's value == the declared funding coin
Exposes dsScriptAddr, dsReificatorPay, dsScriptPay on DeployedSpend
so the spec has all the expected values in one place rather than
hardcoding coin amounts in two modules.
40 examples, 0 failures, 5 pending.
Refs #15.
A Cardano tx can use a given UTxO as EITHER a regular input (fees, balance) OR a collateral input, never both — on a successful script run, collateral is untouched, which would leave a UTxO double-counted in the balance equation. The spend scenario therefore needs two distinct reificator outputs from the setup tx. Changes: - SpendSetup.deploySpendState now pays the reificator twice (50 ADA fee + 10 ADA collateral). DeployedSpend exposes both pairs (dsReificatorFeeTxIn/Out + dsReificatorCollateralTxIn/Out) plus their declared values. - DevnetSpendSpec asserts both outputs land at the reificator address with their declared amounts, and that the two TxIns are distinct (otherwise the spend tx can't use one as collateral and the other for fees). Still 40 examples, 0 failures, 5 pending. Setup for T065 (submit the spend tx). Refs #15.
Wires the first live-devnet happy-path scenario into DevnetSpendSpec. 'SpendScenario.submitSpend' drives the full Harvest.Transaction.spendVoucher builder against the running node: deploy the script UTxO + reificator inputs, re-sign signed_data against the live reificator fee TxOutRef, build, sign with the reificator key, submit. The 'Mutations' record exposes the four orthogonal hooks the upcoming T030-T033 negatives will need (bundle, live TxOutRef, post-re-sign signed_data) so the API stabilises with the golden path rather than being refactored later. The golden path runs on 'identityMutations' so the hooks are inert here. Adds cardano-crypto-class to the test-suite build-depends for hashToBytes / extractHash, which we use to pull the raw 32-byte txid out of the fee TxIn for the re-sign binding. Script-evaluation failures at build-time collapse into SubmitResult.Rejected so negative tests in the follow-up can assert on 'Rejected' uniformly; other build errors remain uncaught (they indicate harness bugs, not validator rejection).
Fills in the four FR-002 negative scenarios that were
pendingWith-stubbed when the golden path landed. Each 'it'
block picks the 'Mutations' hook that matches its attack vector:
* T030 (signed_data tampering): post-re-sign byte flip at
offsetAcceptorAy; Ed25519 check rejects.
* T031 (d mismatch): bundle-only bump of sbD; validator's
signed_data.d == redeemer.d check rejects.
* T032 (pk_c substitution): bundle-only byte flip of
sbCustomerPubkey; validator's byte-split check rejects.
* T033 (TxOutRef replay): override the live txid bound into
signed_data with a fabricated value; the re-sign covers it
so VerifyEd25519Signature passes, but the
signed_data.txOutRef ∈ tx.inputs check rejects.
Each scenario asserts 'Rejected' without pinning the error text
(the ledger reword-rate is high enough that matching on text
would flap across node versions). 'flipByteAt' and 'isRejected'
are small local helpers; no new test infrastructure.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end tests for the harvest voucher spend path against a real
Cardano devnet. Closes #15.
Each
itblock reads as executable narrative of the protocol (seeDevnetSpendSpecmodule haddock). The test suite exercises theapplied validator bytecode, the Haskell tx builder in
Harvest.Transaction, and the three bindings from the constitution§V (d via Groth16, acceptor/TxOutRef via Ed25519, pk_c via
byte-split cross-check).
Coverage
withDevnetbracket + funded genesis.inline datum + split reificator fee/collateral UTxOs.
accepts.
re-sign, Ed25519 rejects.
signed_data.d, validator's defence-in-depth check rejects.
flip, byte-split cross-check rejects.
into signed_data, membership check rejects.
Design
Mutationshooks (mBundle,mLiveTxid,mLiveIx,mSignedData) let each negative test corrupt exactlythe pipeline stage matching its attack vector.
SubmitResult.Rejected, so all four negatives uniformly assertshouldSatisfy isRejectedwithout pinning error text.cardano-apianywhere — ledger types +cardano-crypto-classonly.
Test plan
cabal test harvest-testlocally: 40 examples, 0 failures.fourmolu -m checkandhlintclean.