From 68e21576f52d727641590a4a55205a922517380d Mon Sep 17 00:00:00 2001 From: Kyle Cox Date: Wed, 14 Oct 2026 14:07:48 +0000 Subject: [PATCH] fix(provider): accept Ogmios variants in Kupmios protocol parameters Two protocol-parameter decoding failures in the Kupmios provider (issues #568 and #559): - Ogmios builds plutusCostModels from costModelsValid, so it sends only the languages that have a cost model, and its JSON schema has no required keys. ProtocolParametersSchema required plutus:v1, plutus:v2 and plutus:v3, so getProtocolParameters failed on any network without one of the three. Each language key is now optional and a missing one maps to an empty cost model in toProtocolParameters. - Ogmios v7 renamed maxReferenceScriptsSize to maxReferenceScriptsSizePerTransaction and added maxReferenceScriptsSizePerBlock. The schema required the old key, so getProtocolParameters failed against an Ogmios v7 server. The value is not mapped into the provider's ProtocolParameters, so all three spellings are now optional and either server generation decodes. Tests: three new cases in Kupmios.test.ts (one cost model language missing, no cost models at all, Ogmios v7 renamed keys), each verified failing with a ProviderError before the fix. Kupmios file 19/19; full package suite 1681 passed / 75 skipped / 0 failed; tsc --noEmit and build clean. Changeset: patch. --- .changeset/kupmios-ogmios-params-variants.md | 5 ++ .../sdk/provider/internal/KupmiosEffects.ts | 7 +-- .../src/sdk/provider/internal/Ogmios.ts | 30 +++++++++--- .../evolution/test/provider/Kupmios.test.ts | 46 +++++++++++++++++++ 4 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 .changeset/kupmios-ogmios-params-variants.md diff --git a/.changeset/kupmios-ogmios-params-variants.md b/.changeset/kupmios-ogmios-params-variants.md new file mode 100644 index 00000000..d394f40a --- /dev/null +++ b/.changeset/kupmios-ogmios-params-variants.md @@ -0,0 +1,5 @@ +--- +"@evolution-sdk/evolution": patch +--- + +The Kupmios provider's `getProtocolParameters` no longer fails against Ogmios servers whose protocol-parameter reply differs from the preprod shape it was written against. The Ogmios schema makes each `plutusCostModels` language optional — Ogmios builds that map from the cost models that exist on the network, so a language with no cost model is omitted entirely — and a missing language now maps to an empty cost model instead of failing the whole call (#568). The schema also accepts the Ogmios v7 spelling of the reference-script size limit: `maxReferenceScriptsSize` was renamed to `maxReferenceScriptsSizePerTransaction` (with a new `maxReferenceScriptsSizePerBlock` alongside), so all three keys are optional and either server generation decodes (#559). diff --git a/packages/evolution/src/sdk/provider/internal/KupmiosEffects.ts b/packages/evolution/src/sdk/provider/internal/KupmiosEffects.ts index 7b1f5094..d0af272c 100644 --- a/packages/evolution/src/sdk/provider/internal/KupmiosEffects.ts +++ b/packages/evolution/src/sdk/provider/internal/KupmiosEffects.ts @@ -61,14 +61,15 @@ const toProtocolParameters = (result: Ogmios.ProtocolParameters): Provider.Proto maxCollateralInputs: Number(result.maxCollateralInputs), minFeeRefScriptCostPerByte: result.minFeeReferenceScripts.base, costModels: { + // A language Ogmios omitted has no cost model on this network: {} PlutusV1: Object.fromEntries( - result.plutusCostModels["plutus:v1"].map((value, index) => [index.toString(), Number(value)]) + (result.plutusCostModels["plutus:v1"] ?? []).map((value, index) => [index.toString(), Number(value)]) ), PlutusV2: Object.fromEntries( - result.plutusCostModels["plutus:v2"].map((value, index) => [index.toString(), Number(value)]) + (result.plutusCostModels["plutus:v2"] ?? []).map((value, index) => [index.toString(), Number(value)]) ), PlutusV3: Object.fromEntries( - result.plutusCostModels["plutus:v3"].map((value, index) => [index.toString(), Number(value)]) + (result.plutusCostModels["plutus:v3"] ?? []).map((value, index) => [index.toString(), Number(value)]) ) } } diff --git a/packages/evolution/src/sdk/provider/internal/Ogmios.ts b/packages/evolution/src/sdk/provider/internal/Ogmios.ts index 71ced59d..017934d2 100644 --- a/packages/evolution/src/sdk/provider/internal/Ogmios.ts +++ b/packages/evolution/src/sdk/provider/internal/Ogmios.ts @@ -35,9 +35,25 @@ export const ProtocolParametersSchema = Schema.Struct({ range: Schema.BigInt, multiplier: Schema.NumberFromString }), - maxReferenceScriptsSize: Schema.Struct({ - bytes: Schema.BigInt - }), + // Ogmios v7 renamed this to maxReferenceScriptsSizePerTransaction and + // added maxReferenceScriptsSizePerBlock; deployed servers run both major + // versions, so every spelling is optional (the value is not mapped into + // the provider's ProtocolParameters). + maxReferenceScriptsSize: Schema.optional( + Schema.Struct({ + bytes: Schema.BigInt + }) + ), + maxReferenceScriptsSizePerTransaction: Schema.optional( + Schema.Struct({ + bytes: Schema.BigInt + }) + ), + maxReferenceScriptsSizePerBlock: Schema.optional( + Schema.Struct({ + bytes: Schema.BigInt + }) + ), stakePoolVotingThresholds: Schema.Struct({ noConfidence: TupleNumberFromString, constitutionalCommittee: Schema.Struct({ @@ -89,10 +105,12 @@ export const ProtocolParametersSchema = Schema.Struct({ minStakePoolCost: Schema.Struct({ ada: LovelaceAsset }), minUtxoDepositConstant: Schema.Struct({ ada: LovelaceAsset }), minUtxoDepositCoefficient: Schema.BigInt, + // Ogmios builds this map from the cost models that exist on the network, + // so a language with no cost model is omitted entirely, not sent empty. plutusCostModels: Schema.Struct({ - "plutus:v1": Schema.Array(Schema.BigInt), - "plutus:v2": Schema.Array(Schema.BigInt), - "plutus:v3": Schema.Array(Schema.BigInt) + "plutus:v1": Schema.optional(Schema.Array(Schema.BigInt)), + "plutus:v2": Schema.optional(Schema.Array(Schema.BigInt)), + "plutus:v3": Schema.optional(Schema.Array(Schema.BigInt)) }), scriptExecutionPrices: Schema.Struct({ memory: TupleNumberFromString, diff --git a/packages/evolution/test/provider/Kupmios.test.ts b/packages/evolution/test/provider/Kupmios.test.ts index 264cf92c..27f6662e 100644 --- a/packages/evolution/test/provider/Kupmios.test.ts +++ b/packages/evolution/test/provider/Kupmios.test.ts @@ -129,6 +129,52 @@ describe("Kupmios read paths", () => { expect(Object.values(params.costModels.PlutusV3)).toEqual(expected.plutusCostModels["plutus:v3"]) }) + it("getProtocolParameters with an Ogmios cost model language missing (#568)", async () => { + // Ogmios builds plutusCostModels from costModelsValid, so a network + // without a Plutus V1 cost model omits the "plutus:v1" key entirely. + const withoutV1 = protocolParameters.replace(/"plutus:v1": \[[^\]]*\],\s*/, "") + expect(withoutV1).not.toContain('"plutus:v1"') + stubKupmios({ ogmios: { "queryLedgerState/protocolParameters": withoutV1 } }) + const { result: expected } = JSON.parse(withoutV1) + + const params = await Effect.runPromise(KupmiosEffects.getProtocolParametersEffect(OGMIOS_URL)) + + // A missing language maps to an empty cost model; the others are intact + expect(params.costModels.PlutusV1).toEqual({}) + expect(Object.values(params.costModels.PlutusV2)).toEqual(expected.plutusCostModels["plutus:v2"]) + expect(Object.values(params.costModels.PlutusV3)).toEqual(expected.plutusCostModels["plutus:v3"]) + expect(params.minFeeA).toBe(44) + }) + + it("getProtocolParameters with no Ogmios cost models at all (#568)", async () => { + const noModels = protocolParameters.replace( + /"plutusCostModels": \{[\s\S]*?\},\s*"scriptExecutionPrices"/, + `"plutusCostModels": {},\n "scriptExecutionPrices"` + ) + expect(JSON.parse(noModels).result.plutusCostModels).toEqual({}) + stubKupmios({ ogmios: { "queryLedgerState/protocolParameters": noModels } }) + + const params = await Effect.runPromise(KupmiosEffects.getProtocolParametersEffect(OGMIOS_URL)) + + expect(params.costModels).toEqual({ PlutusV1: {}, PlutusV2: {}, PlutusV3: {} }) + }) + + it("getProtocolParameters against Ogmios v7 (maxReferenceScriptsSize renamed, #559)", async () => { + // Ogmios v7 renames maxReferenceScriptsSize to + // maxReferenceScriptsSizePerTransaction and adds a PerBlock key. + const v7 = protocolParameters.replace( + /"maxReferenceScriptsSize": \{\s*"bytes": 204800\s*\}/, + `"maxReferenceScriptsSizePerTransaction": {"bytes": 204800},\n "maxReferenceScriptsSizePerBlock": {"bytes": 1048576}` + ) + expect(v7).toContain('"maxReferenceScriptsSizePerTransaction"') + expect(v7).not.toContain('"maxReferenceScriptsSize":') + stubKupmios({ ogmios: { "queryLedgerState/protocolParameters": v7 } }) + + const params = await Effect.runPromise(KupmiosEffects.getProtocolParametersEffect(OGMIOS_URL)) + + expect(params).toMatchObject({ minFeeA: 44, minFeeB: 155381, maxTxSize: 16384 }) + }) + it("getUtxos by address", async () => { stubKupmios({ kupo: kupo() })