import { h64 } from '@pezkuwi/bizinikiwi-bindings'; import { buildLookupGraph, getSubgraph, getStronglyConnectedComponents, mergeSCCsWithCommonNodes } from './lookup-graph.mjs'; const textEncoder = new TextEncoder(); const encodeText = textEncoder.encode.bind(textEncoder); const getChecksum = (values) => { const res = new Uint8Array(values.length * 8); const dv = new DataView(res.buffer); for (let i = 0; i < values.length; i++) dv.setBigUint64(i * 8, values[i]); return h64(res); }; const getStringChecksum = (values) => getChecksum(values.map((v) => h64(encodeText(v)))); const shapeIds = { primitive: 0n, vector: 1n, tuple: 2n, struct: 3n, option: 4n, result: 5n, enum: 6n, void: 7n }; const runtimePrimitiveIds = { undefined: 0n, number: 1n, string: 2n, bigint: 3n, boolean: 4n, bitSequence: 5n, // {bitsLen: number, bytes: Uint8Array} byteSequence: 6n, // Binary accountId32: 7n, // SS58String accountId20: 8n // EthAccount }; const metadataPrimitiveIds = { bool: runtimePrimitiveIds.boolean, char: runtimePrimitiveIds.string, str: runtimePrimitiveIds.string, u8: runtimePrimitiveIds.number, u16: runtimePrimitiveIds.number, u32: runtimePrimitiveIds.number, u64: runtimePrimitiveIds.bigint, u128: runtimePrimitiveIds.bigint, u256: runtimePrimitiveIds.bigint, i8: runtimePrimitiveIds.number, i16: runtimePrimitiveIds.number, i32: runtimePrimitiveIds.number, i64: runtimePrimitiveIds.bigint, i128: runtimePrimitiveIds.bigint, i256: runtimePrimitiveIds.bigint }; const structLikeBuilder = (shapeId, input, innerChecksum) => { const sortedEntries = Object.entries(input).sort( ([a], [b]) => a.localeCompare(b) ); const keysChecksum = getStringChecksum(sortedEntries.map(([key]) => key)); const valuesChecksum = getChecksum( sortedEntries.map(([, entry]) => innerChecksum(entry)) ); return getChecksum([shapeId, keysChecksum, valuesChecksum]); }; const _buildChecksum = (input, buildNextChecksum) => { if (input.type === "primitive") return getChecksum([shapeIds.primitive, metadataPrimitiveIds[input.value]]); if (input.type === "void") return getChecksum([shapeIds.void]); if (input.type === "compact") return getChecksum([ shapeIds.primitive, runtimePrimitiveIds[input.isBig ? "bigint" : "number"] ]); if (input.type === "bitSequence") return getChecksum([shapeIds.primitive, runtimePrimitiveIds.bitSequence]); if (input.type === "AccountId32") { return getChecksum([shapeIds.primitive, runtimePrimitiveIds.accountId32]); } if (input.type === "AccountId20") { return getChecksum([shapeIds.primitive, runtimePrimitiveIds.accountId20]); } const buildVector = (entry, length) => { const innerChecksum = buildNextChecksum(entry); return getChecksum( length !== void 0 ? [shapeIds.vector, innerChecksum, BigInt(length)] : [shapeIds.vector, innerChecksum] ); }; if (input.type === "array") { const innerValue = input.value; if (innerValue.type === "primitive" && innerValue.value === "u8") { return getChecksum([ shapeIds.primitive, runtimePrimitiveIds.byteSequence, BigInt(input.len) ]); } return buildVector(innerValue, input.len); } if (input.type === "sequence") { const innerValue = input.value; if (innerValue.type === "primitive" && innerValue.value === "u8") { return getChecksum([shapeIds.primitive, runtimePrimitiveIds.byteSequence]); } return buildVector(innerValue); } const buildTuple = (entries) => getChecksum([shapeIds.tuple, ...entries.map(buildNextChecksum)]); const buildStruct = (entries) => structLikeBuilder(shapeIds.struct, entries, buildNextChecksum); if (input.type === "tuple") return buildTuple(input.value); if (input.type === "struct") return buildStruct(input.value); if (input.type === "option") return getChecksum([shapeIds.option, buildNextChecksum(input.value)]); if (input.type === "result") return getChecksum([ shapeIds.result, buildNextChecksum(input.value.ok), buildNextChecksum(input.value.ko) ]); return structLikeBuilder(shapeIds.enum, input.value, (entry) => { if (entry.type === "lookupEntry") return buildNextChecksum(entry.value); switch (entry.type) { case "void": return getChecksum([shapeIds.void]); case "tuple": return buildTuple(entry.value); case "struct": return buildStruct(entry.value); case "array": return buildVector(entry.value, entry.len); } }); }; const sortCyclicGroups = (groups, graph) => { const getReachableNodes = (group) => { const result2 = /* @__PURE__ */ new Set(); const toVisit = Array.from(group); while (toVisit.length) { const id = toVisit.pop(); if (result2.has(id)) continue; result2.add(id); graph.get(id)?.refs.forEach((id2) => toVisit.push(id2)); } return Array.from(result2); }; const result = new Array(); function dependentsFirst(group) { if (result.includes(group)) return; const dependents = groups.filter( (candidate) => candidate !== group && getReachableNodes(group).some((node) => candidate.has(node)) ); dependents.forEach((group2) => dependentsFirst(group2)); if (result.includes(group)) return; result.push(group); } groups.forEach((group) => dependentsFirst(group)); return result; }; function iterateChecksums(group, iterations, cache, graph) { const groupReadCache = new Map([...group].map((id) => [id, 0n])); const groupWriteCache = /* @__PURE__ */ new Map(); const recursiveBuildChecksum = (entry, skipCache = true) => { if (!skipCache && (groupReadCache.has(entry.id) || cache.has(entry.id))) { return groupReadCache.get(entry.id) ?? cache.get(entry.id); } const result = _buildChecksum( entry, (nextEntry) => recursiveBuildChecksum(nextEntry, false) ); if (group.has(entry.id)) { groupWriteCache.set(entry.id, result); } else { cache.set(entry.id, result); } return result; }; for (let i = 0; i < iterations; i++) { group.forEach((id) => recursiveBuildChecksum(graph.get(id).entry)); group.forEach((id) => groupReadCache.set(id, groupWriteCache.get(id))); } return groupReadCache; } function getMirroredNodes(cyclicGroups, graph) { const maxSize = cyclicGroups.reduce( (acc, group) => Math.max(acc, group.size), 0 ); const allEntries = new Set([...graph.values()].map((v) => v.entry.id)); const resultingChecksums = iterateChecksums( allEntries, maxSize, // Cache won't be used, since it's using the internal one for every node. /* @__PURE__ */ new Map(), graph ); const checksumToNodes = /* @__PURE__ */ new Map(); for (const id of allEntries) { const checksum = resultingChecksums.get(id); if (checksum == void 0) throw new Error("Unreachable"); if (!checksumToNodes.has(checksum)) { checksumToNodes.set(checksum, []); } checksumToNodes.get(checksum).push(id); } const checksumsWithDuplicates = [...checksumToNodes.entries()].filter( ([, nodes]) => nodes.length > 1 ); const duplicatesMap = {}; checksumsWithDuplicates.forEach(([, nodes]) => { nodes.forEach((n) => duplicatesMap[n] = nodes); }); return duplicatesMap; } const buildChecksum = (entry, cache, graph) => { if (cache.has(entry.id)) return cache.get(entry.id); const subGraph = getSubgraph(entry.id, graph); const cycles = getStronglyConnectedComponents(subGraph).filter( // SCCs can be of length=1, but for those we're only interested with those that are circular with themselves (group) => group.size > 1 || isSelfCircular(group, subGraph) ); const cyclicGroups = mergeSCCsWithCommonNodes(cycles).filter((group) => { return !cache.has(group.values().next().value); }); const mirrored = getMirroredNodes(cyclicGroups, subGraph); const sortedCyclicGroups = sortCyclicGroups(cyclicGroups, subGraph); sortedCyclicGroups.forEach((group) => { if (cache.has(group.values().next().value)) { return; } const result = iterateChecksums(group, group.size, cache, graph); group.forEach((id) => { const checksum = result.get(id); if (id in mirrored) { mirrored[id].forEach((id2) => cache.set(id2, checksum)); } else { cache.set(id, checksum); } }); }); const getChecksum2 = (entry2) => { if (cache.has(entry2.id)) return cache.get(entry2.id); return _buildChecksum(entry2, getChecksum2); }; return getChecksum2(entry); }; const isSelfCircular = (group, graph) => { if (group.size !== 1) return false; const [id] = group; return graph.get(id).refs.has(id); }; const getChecksumBuilder = (getLookupEntryDef) => { const { metadata } = getLookupEntryDef; const graph = buildLookupGraph(getLookupEntryDef, metadata.lookup.length); const cache = /* @__PURE__ */ new Map(); const buildDefinition = (id) => buildChecksum(getLookupEntryDef(id), cache, graph); const buildStorage = (pallet, entry) => { try { const storageEntry = metadata.pallets.find((x) => x.name === pallet).storage.items.find((s) => s.name === entry); if (storageEntry.type.tag === "plain") return buildDefinition(storageEntry.type.value); const { key, value } = storageEntry.type.value; const val = buildDefinition(value); const returnKey = buildDefinition(key); return getChecksum([val, returnKey]); } catch (_) { return null; } }; const buildViewFns = (pallet, entry) => { try { const viewFn = metadata.pallets.find((x) => x.name === pallet)?.viewFns.find((x) => x.name === entry); if (!viewFn) throw null; const argNamesChecksum = getStringChecksum( viewFn.inputs.map((x) => x.name) ); const argValuesChecksum = getChecksum( viewFn.inputs.map((x) => buildDefinition(x.type)) ); const outputChecksum = buildDefinition(viewFn.output); return getChecksum([argNamesChecksum, argValuesChecksum, outputChecksum]); } catch (_) { return null; } }; const buildRuntimeCall = (api, method) => { try { const entry = metadata.apis.find((x) => x.name === api)?.methods.find((x) => x.name === method); if (!entry) throw null; const argNamesChecksum = getStringChecksum( entry.inputs.map((x) => x.name) ); const argValuesChecksum = getChecksum( entry.inputs.map((x) => buildDefinition(x.type)) ); const outputChecksum = buildDefinition(entry.output); return getChecksum([argNamesChecksum, argValuesChecksum, outputChecksum]); } catch (_) { return null; } }; const buildComposite = (input) => { if (input.type === "void") return getChecksum([0n]); if (input.type === "tuple") { const values = Object.values(input.value).map( (entry) => buildDefinition(entry.id) ); return getChecksum([shapeIds.tuple, ...values]); } if (input.type === "array") { return getChecksum([ shapeIds.vector, buildDefinition(input.value.id), BigInt(input.len) ]); } return structLikeBuilder( shapeIds.struct, input.value, (entry) => buildDefinition(entry.id) ); }; const buildNamedTuple = (input) => { return structLikeBuilder( shapeIds.tuple, input.value, (entry) => buildDefinition(entry.id) ); }; const variantShapeId = { errors: 1n, events: 2n, calls: 3n }; const buildVariant = (variantType) => (pallet, name) => { try { const palletEntry = metadata.pallets.find((x) => x.name === pallet); const enumLookup = getLookupEntryDef(palletEntry[variantType].type); buildDefinition(enumLookup.id); if (enumLookup.type !== "enum") throw null; const entry = enumLookup.value[name]; const valueChecksum = entry.type === "lookupEntry" ? buildDefinition(entry.value.id) : buildComposite(entry); return getChecksum([variantShapeId[variantType], valueChecksum]); } catch (_) { return null; } }; const buildConstant = (pallet, constantName) => { try { const storageEntry = metadata.pallets.find((x) => x.name === pallet).constants.find((s) => s.name === constantName); return buildDefinition(storageEntry.type); } catch (_) { return null; } }; const toStringEnhancer = (fn) => (...args) => fn(...args)?.toString(32) ?? null; return { buildDefinition: toStringEnhancer(buildDefinition), buildRuntimeCall: toStringEnhancer(buildRuntimeCall), buildStorage: toStringEnhancer(buildStorage), buildViewFns: toStringEnhancer(buildViewFns), buildCall: toStringEnhancer(buildVariant("calls")), buildEvent: toStringEnhancer(buildVariant("events")), buildError: toStringEnhancer(buildVariant("errors")), buildConstant: toStringEnhancer(buildConstant), buildComposite: toStringEnhancer(buildComposite), buildNamedTuple: toStringEnhancer(buildNamedTuple), getAllGeneratedChecksums: () => Array.from(cache.values()).map((v) => v.toString(32)) }; }; export { getChecksumBuilder }; //# sourceMappingURL=checksum-builder.mjs.map