mirror of
https://github.com/pezkuwichain/pezkuwi-api.git
synced 2026-04-25 16:07:58 +00:00
31467f90d4
- @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.
371 lines
13 KiB
JavaScript
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
|