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() })