Files
pezkuwi-api/packages/metadata-builders/dist/esm/checksum-builder.mjs
T
pezkuwichain 31467f90d4 feat: add PAPI rebrand packages
- @pezkuwi/papi-utils (rebrand of @polkadot-api/utils)
- @pezkuwi/bizinikiwi-bindings (rebrand of @polkadot-api/substrate-bindings)
- @pezkuwi/metadata-builders (rebrand of @polkadot-api/metadata-builders)
- @pezkuwi/merkleize-metadata (rebrand of @polkadot-api/merkleize-metadata)

All @polkadot-api references replaced with @pezkuwi equivalents.
2026-01-22 15:40:12 +03:00

371 lines
13 KiB
JavaScript

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