UNPKG

@silvana-one/mina-utils

Version:
171 lines (160 loc) 5.14 kB
import { Experimental, Field } from "o1js"; import { bigintToBase64, bigintFromBase64 } from "./base64.js"; import { sleep } from "./utils.js"; import { pinJSON } from "../storage/pinata.js"; const { IndexedMerkleMap } = Experimental; type IndexedMerkleMap = Experimental.IndexedMerkleMap; export interface IndexedMapSerialized { height: number; root: string; length: string; nodes: string; sortedLeaves: string; } export async function loadIndexedMerkleMap(params: { url: string; type: ReturnType<typeof IndexedMerkleMap>; timeout?: number; attempts?: number; }) { const { url, type, timeout = 60000, attempts = 5 } = params; let attempt = 0; const start = Date.now(); let response = await fetch(url); while (!response.ok && attempt < attempts && Date.now() - start < timeout) { attempt++; await sleep(5000 * attempt); // handle rate limiting response = await fetch(url); } if (!response.ok) { throw new Error("Failed to fetch IndexedMerkleMap"); } const json = await response.json(); const serializedIndexedMap = ( json as unknown as { map: IndexedMapSerialized } ).map; if (!serializedIndexedMap) throw new Error("wrong IndexedMerkleMap json format"); const map = deserializeIndexedMerkleMapInternal({ serializedIndexedMap, type, }); if (!map) { throw new Error("Failed to deserialize whitelist"); } return map; } export async function saveIndexedMerkleMap(params: { map: IndexedMerkleMap; name?: string; keyvalues?: { key: string; value: string }[]; auth: string; }): Promise<string | undefined> { const { map, name = "indexed-map", keyvalues, auth } = params; const serialized = serializeIndexedMap(map); const ipfsHash = await pinJSON({ data: { map: serialized }, name, keyvalues, auth, }); return ipfsHash; } export function serializeIndexedMap( map: IndexedMerkleMap ): IndexedMapSerialized { return { height: map.height, root: map.root.toJSON(), length: map.length.toJSON(), nodes: JSON.stringify(map.data.get().nodes, (_, v) => typeof v === "bigint" ? "n" + bigintToBase64(v) : v ), sortedLeaves: JSON.stringify( map.data .get() .sortedLeaves.map((v) => [ bigintToBase64(v.key), bigintToBase64(v.nextKey), bigintToBase64(v.value), bigintToBase64(BigInt(v.index)), ]) ), }; } export function deserializeIndexedMerkleMap(params: { serializedIndexedMap: IndexedMapSerialized; type?: ReturnType<typeof IndexedMerkleMap>; }): InstanceType<ReturnType<typeof IndexedMerkleMap>> | undefined { try { const { serializedIndexedMap, type } = params; return deserializeIndexedMerkleMapInternal({ serializedIndexedMap, type: type ?? IndexedMerkleMap(serializedIndexedMap.height), }); } catch (error: any) { console.error("Error deserializing map:", error?.message ?? error); return undefined; } } export function parseIndexedMapSerialized( serializedMap: string ): IndexedMapSerialized { const json = JSON.parse(serializedMap); if ( json.height === undefined || json.root === undefined || json.length === undefined || json.nodes === undefined || json.sortedLeaves === undefined ) throw new Error("wrong IndexedMerkleMap json format"); if (typeof json.height !== "number") throw new Error("wrong IndexedMerkleMap height format"); if (typeof json.root !== "string") throw new Error("wrong IndexedMerkleMap root format"); if (typeof json.length !== "string") throw new Error("wrong IndexedMerkleMap length format"); if (typeof json.nodes !== "string") throw new Error("wrong IndexedMerkleMap nodes format"); if (typeof json.sortedLeaves !== "string") throw new Error("wrong IndexedMerkleMap sortedLeaves format"); return json; } function deserializeIndexedMerkleMapInternal(params: { serializedIndexedMap: IndexedMapSerialized; type: ReturnType<typeof IndexedMerkleMap>; }): InstanceType<ReturnType<typeof IndexedMerkleMap>> { const { serializedIndexedMap, type } = params; const map = new type(); if (serializedIndexedMap.height !== map.height) { throw new Error("wrong IndexedMap height"); } const nodes = JSON.parse(serializedIndexedMap.nodes, (_, v) => { // Check if the value is a string that represents a BigInt if (typeof v === "string" && v[0] === "n") { // Remove the first 'n' and convert the string to a BigInt return bigintFromBase64(v.slice(1)); } return v; }); const sortedLeaves = JSON.parse(serializedIndexedMap.sortedLeaves).map( (row: any) => { return { key: bigintFromBase64(row[0]), nextKey: bigintFromBase64(row[1]), value: bigintFromBase64(row[2]), index: Number(bigintFromBase64(row[3])), }; } ); map.root = Field.fromJSON(serializedIndexedMap.root); map.length = Field.fromJSON(serializedIndexedMap.length); map.data.updateAsProver(() => { return { nodes: nodes.map((row: any) => [...row]), sortedLeaves: [...sortedLeaves], }; }); return map; }