GuidesE2E Testing

E2E Pipeline Testing

End-to-end testing of the LightChallenge pipeline — from challenge creation through LightChain v2 worker judgment to on-chain payout.

Test scripts

ScriptPurpose
scripts/demoLightchainJudge.tsSmallest. Submits a single prompt to a LightChain v2 worker, decrypts the response. Verifies gateway + relay + ECDH wiring.
scripts/demoChallengeLeanE2E.tsMid. Adds the on-chain attest + ChallengePayLightchainAttestor.verify() step. No ChallengePay interaction.
scripts/demoChallengeFullLifecycle.tsFull. Creates a real challenge, joins, submits evidence, drives the full runner (runChallengePayLightchainJob), finalizes, and pays out via autoDistribute.

Run:

npx tsx scripts/demoChallengeFullLifecycle.ts

Requires webapp/.env.local with: PRIVATE_KEY, DATABASE_URL, LCAI_WORKER_PK, CHALLENGEPAY_LIGHTCHAIN_ATTESTOR_ADDRESS, NEXT_PUBLIC_CHALLENGEPAY_ADDR. A funded wallet on chain 8200 with ~0.15 LCAI is enough for one run.


What the full lifecycle test covers

PhaseLayerWhat happensReal or simulated
1On-chaincreateChallenge(CreateParams) on ChallengePayReal
2On-chainjoinChallengeNative(id) for a participant walletReal
3DBInsert evidence row matching what POST /api/aivm/intake would writeReal
4DBevidenceEvaluator writes a verdicts row with pass=trueReal (same code path)
5DBchallengeDispatcher writes an aivm_jobs row keyed on (challenge_id, subject)Real
6LightChainjudgeChallengeViaLightchain(...): SIWE auth → gateway → assigned worker → ECDH-encrypted relay → AES-GCM verdict decryptReal (talks to LightChain testnet v2)
7On-chainChallengePayLightchainAttestor.attest(...) records the verdictReal
8On-chainChallengePay.submitProofFor(id, subject, proof) calls attestor.verify() and marks the participant as winnerReal
9TimeWait until proofDeadlineTs (script uses a 60s window for testing)
10On-chainChallengePay.finalize(id) snapshots winners + books feesReal
11On-chainautoDistribute(id, winners[], losers[]) pushes Treasury allowancesReal
12DBclaimsIndexer records the WinnerClaimed / LoserClaimed eventsReal

Nothing is simulated — every step talks to chain 8200, the LightChain v2 gateway, and a real Postgres database. Total runtime: ~90s, mostly waiting for proofDeadlineTs.


Phase details

Phase 1: Create challenge

ChallengePay.createChallenge({
  kind: 0,                                  // Solo
  currency: 0,                              // Native LCAI
  stakeAmount: parseEther("0.001"),
  startTs: now + 60,
  duration: 60,                             // 1 min for the test
  proofDeadlineTs: startTs + duration + 60, // 60s grace
  verifier: CHALLENGEPAY_LIGHTCHAIN_ATTESTOR_ADDRESS,
  ...
})

The creator’s stake is deposited into the Treasury bucket. c.payoutsDone is false; status = Active.

Phase 6: LightChain v2 judging

judgeChallengeViaLightchain performs the SIWE → gateway → relay round trip and returns:

{
  passed: boolean,
  rawResponse: string,         // decrypted verdict JSON
  jobId: bigint,
  worker: `0x${string}`,
  responseHash: `0x${string}`,
  ciphertextHash: `0x${string}`,
  txs: { jobCompleted: `0x${string}` },  // on-chain audit trail
}

The on-chain JobCompleted event from JobRegistry is the public audit trail — anyone can verify our attestation against the worker’s signed event.

Phase 7: Attest

ChallengePayLightchainAttestor.attest(
  challengeId, subject, jobId, jobCompletedTx,
  responseHash, ciphertextHash, worker, true
);

One tx. Idempotent — re-attesting the same (challengeId, subject) reverts with AlreadyAttested.

Phase 8: Submit proof

ChallengePay.submitProofFor(challengeId, subject, abiEncodedProof)
  → calls attestor.verify(challengeId, subject, proof)
  → reads attestation row → returns true
  → c.winner[subject] = true
  → c.winnersPool += contrib
  → c.winnersCount += 1

If verify() returns false (no attestation, attestation says passed=false, or proof bytes don’t match the attestation’s hashes), submitProofFor does NOT mark the participant as winner.

Phase 10-11: Finalize + auto-distribute

After proofDeadlineTs passes:

  • finalize(id) sets c.status = Finalized, c.outcome = Success (since winnersPool > 0)
  • _snapshotAndBook books the per-winner bonus, protocol fee, creator fee
  • autoDistribute(id, [subject], []) pushes the Treasury allowance to the participant’s wallet

If winnersPool == 0 (no one passed): outcome = Fail, the entire distributable goes to the protocol multi-sig. See incident #13 for the recovery pattern.


What is real

ComponentStatus
LightChain testnet v2 (chain ID 8200)Real chain
ChallengePay contract (0xeC65…Fd0b)Real deployment
ChallengePayLightchainAttestor (0xb400…3360)Real deployment
Treasury (0xF8E3…22FF3)Real deployment
LightChain v2 gateway (chat-api.testnet.lightchain.ai)Real (LightChain Foundation)
LightChain v2 workerReal (staked node, llama3-8b)
LightChain v2 relay (relay.testnet.lightchain.ai/ws)Real
PostgreSQL database (Neon)Real
Evidence evaluatorReal code path

There’s no simulation layer in v2 — workers run real LLM compute and emit signed JobCompleted events on chain. The only thing the script “fakes” is the iOS / webapp UX — it inserts evidence directly into the DB instead of going through POST /api/aivm/intake.


Smaller demos

For unit-level testing without spending a full 0.15 LCAI per run:

# Test gateway + relay only (no on-chain writes)
npx tsx scripts/demoLightchainJudge.ts
 
# Test gateway + relay + attestor (one on-chain attest tx)
npx tsx scripts/demoChallengeLeanE2E.ts

Both write to the testnet but skip ChallengePay challenge creation, so they’re cheaper.


Contract test suite

npx hardhat test

Covers ChallengePay, ChallengePayLightchainAttestor, Treasury, MultiSigWallet, EventChallengeRouter, ChallengeAchievement, both fitness + gaming evaluators, and the lifecycle resolver (test/lifecycle.test.ts — pins the incident #13 UI fix).