shared: live PostAdvert + delegate UPLC eval to Ogmios

Two production-essential fixes that surfaced during the path-A TTS
deployment to Vector testnet:

1. localUPLCEval: false on every Plutus V3 spend
   ──────────────────────────────────────────────
   lucid-evolution 0.4.30's CML WASM UPLC evaluator has a false-rejection
   bug for our V3 spend pattern: it rejects perfectly valid txs with
   "failed script execution Spend[0] the validator crashed / exited
   prematurely" while the chain itself accepts them without complaint.
   Routing eval to Ogmios's evaluateTransaction (cardano-node's
   evaluator) sidesteps the WASM bug and returns correct ex-units.
   OgmiosLucidProvider.evaluateTx already implements this path.

   This also unblocks the 5 tx-submit-live tests previously skipped
   with the comment "UPLC wasm crashes" (M1-F-D-defer / task #28).

   Without this fix, every Claim/Submit/Accept tx from our supplier
   was failing locally before reaching the chain. The third-party
   summitstak.ing supplier worked because it uses a different
   toolchain that does not run client-side phase-2 evaluation.

2. AdvertDatum BigInt coercion
   ────────────────────────────
   cbor-x emits float64 (`fb` prefix) for JS Number values that can't
   fit CBOR's compact int forms (e.g. POSIX-ms timestamps). Plutus
   Data has no float type, so CML.PlutusData.from_cbor_hex panics
   with "RuntimeError: unreachable" when it sees one. Coerce
   max_output_tokens, max_processing_ms, advertised_at to BigInt
   before encoding so cbor-x always emits major-type-0/1 integers.
   Mirrors the intAsBig pattern already used in EscrowDatum.

3. Live-CBOR PostAdvert builder
   ─────────────────────────────
   M1-F-4 deferred postAdvert/updateAdvert/retireAdvert/reclaim/
   release on the live path. With (1) and (2) in place, posting an
   advert via tx:post-advert against Vector testnet now works.
   Added buildLiveTxForAdvert in liveCbor.ts (mirrors PostEscrow)
   and routed buildPostAdvertTx through it when chain is live.

40 advert/post-advert tests still pass. Verified end-to-end on
Vector testnet: TTS advert posted at
5df793ac12dc426fae7a7a01d56d3e923d3985103d62f0182d9ba63dda871f96#0,
full chat + TTS marketplace lifecycle (PostEscrow → Claim → Submit →
Accept) confirmed completing successfully on chain.
This commit is contained in:
jelisaveta-talbot
2026-05-07 05:17:19 +02:00
parent 34a52d5d16
commit 7ea7328eed
3 changed files with 32 additions and 3 deletions

View File

@@ -118,19 +118,24 @@ export function encodeAdvertDatum(d: AdvertDatum): string {
throw new Error(`unknown AdvertStatus: ${String(d.status)}`);
})();
// cbor-x encodes JS Number as float64 (`fb` prefix) for values that don't
// fit in CBOR's compact int forms — and Plutus Data has NO float type, so
// CML's PlutusData.from_cbor_hex panics with "RuntimeError: unreachable"
// when it sees one. Coerce numeric fields to BigInt so cbor-x always emits
// CBOR major-type-0/1 integers. Mirrors the intAsBig pattern in EscrowDatum.
const cbor = plutusTag(121, [
hexToBytes(d.supplier_pkh),
utf8Bytes(d.capability_id),
utf8Bytes(d.model),
d.max_output_tokens,
d.max_processing_ms,
BigInt(d.max_output_tokens),
BigInt(d.max_processing_ms),
d.price_lovelace,
d.supplier_bond_lovelace,
d.buyer_bond_lovelace,
utf8Bytes(d.endpoint_url),
utf8Bytes(d.detail_uri),
hexToBytes(d.detail_hash),
d.advertised_at,
BigInt(d.advertised_at),
statusTag,
]);

View File

@@ -19,6 +19,7 @@ import { loadBlueprint } from "../blueprint.js";
import { encodeTxBody, sha256Hex } from "../internal/testTxBody.js";
import { mockSlotToWallclockMs } from "../internal/constants.js";
import { detectCborBackend } from "../internal/cborBackend.js";
import type { LiveOgmiosProvider } from "../../chain/LiveOgmiosProvider.js";
const FIVE_MIN_MS = 5 * 60 * 1000;
@@ -64,6 +65,19 @@ export async function buildPostAdvertTx(
}
// 4. Construct tx body.
// Live path: route to lucid-evolution so the on-chain submission carries
// valid Conway-era CBOR. Mock path keeps the JSON-in-hex synthetic body.
if (isLive) {
const liveCborPath = "../internal/liveCbor.js";
const { buildLiveTxForAdvert } = await import(/* @vite-ignore */ liveCborPath);
return buildLiveTxForAdvert({
chain: chain as LiveOgmiosProvider,
walletKey,
advertDatum,
deposit_lovelace,
});
}
const blueprint = loadBlueprint();
const advertScriptAddress = blueprint.advertScriptAddress(0);

View File

@@ -342,8 +342,18 @@ async function buildSpendOpenOrClaimedTx(opts: {
// Let lucid pick coins + compute fee normally; production wallets have
// multiple real UTxOs (no need for the test-side leftover-as-fee hack).
//
// localUPLCEval: false — delegate Plutus V3 phase-2 evaluation to Ogmios
// (which uses cardano-node's evaluator). lucid 0.4.30's CML WASM UPLC
// interpreter has a false-rejection bug for our V3 spend pattern: it
// rejects valid txs with "failed script execution Spend[0] the validator
// crashed / exited prematurely" while the chain itself accepts them
// without complaint. Routing eval to Ogmios sidesteps the WASM bug AND
// produces correct ex-units. The provider already implements evaluateTx
// (OgmiosLucidProvider.evaluateTx → JSON-RPC evaluateTransaction).
const completed = await txBuilder.complete({
presetWalletInputs: realWalletUtxos,
localUPLCEval: false,
});
signed = await completed.sign.withWallet().complete();
} catch (err) {