UNPKG

autrace

Version:
226 lines 8.84 kB
import { PublicKey } from 'o1js'; export class AccountUpdateTrace { constructor() { this.snapshots = []; this.currentTree = null; this.getLeafNodeValue = (tree, key) => { if (typeof tree !== 'object' || tree === null) { return undefined; } const keyParts = key.split('.'); let current = tree; for (const part of keyParts) { if (current && typeof current === 'object' && part in current) { current = current[part]; } else { return undefined; } } return current; }; this.isLeafNode = (value) => { return value === null || value === undefined || typeof value !== 'object' || value instanceof PublicKey || Object.keys(value).length === 0; }; this.traverseNodesRecursively = (tree, parentPath = '') => { const keys = new Set(); if (typeof tree !== 'object' || tree === null) { return keys; } for (const key in tree) { if (Object.prototype.hasOwnProperty.call(tree, key)) { const currentPath = parentPath ? `${parentPath}.${key}` : key; const value = tree[key]; if (this.isLeafNode(value)) { keys.add(currentPath); } else if (typeof value === 'object' && !Array.isArray(value)) { const childKeys = this.traverseNodesRecursively(value, currentPath); for (const childKey of childKeys) { keys.add(childKey); } } } } return keys; }; this.areValuesEqual = (a, b) => { // Handle PublicKey comparison if (a instanceof PublicKey && b instanceof PublicKey) { return a.toBase58() === b.toBase58(); } // Handle primitive types if (a === b) return true; // Handle null/undefined if (a == null && b == null) return true; if (a == null || b == null) return false; // Handle BigInt if (typeof a === 'bigint' || typeof b === 'bigint') { return BigInt(a) === BigInt(b); } // Handle Date objects if (a instanceof Date && b instanceof Date) { return a.getTime() === b.getTime(); } // Handle Arrays if (Array.isArray(a) && Array.isArray(b)) { return a.length === b.length && a.every((val, idx) => this.areValuesEqual(val, b[idx])); } // Handle Objects if (typeof a === 'object' && typeof b === 'object') { const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; return keysA.every(key => Object.prototype.hasOwnProperty.call(b, key) && this.areValuesEqual(a[key], b[key])); } return false; }; this.keysUpdated = (a, b, keyList) => { const keysUpdatedList = []; for (const key of keyList) { const oldValue = this.getLeafNodeValue(a, key); const newValue = this.getLeafNodeValue(b, key); // Skip if both values are functions or if the values are equal if (typeof oldValue === 'function' || typeof newValue === 'function') { continue; } if (!this.areValuesEqual(oldValue, newValue)) { keysUpdatedList.push({ field: key, oldValue, newValue: newValue === undefined ? null : newValue }); } } return keysUpdatedList; }; this.deleteFromSet = (set, toDelete) => { const newSet = new Set(set); for (const item of toDelete) { newSet.delete(item); } return newSet; }; this.keysRemoved = (a, b) => { return Array.from(a).filter(key => !b.has(key)); }; this.keysAdded = (a, b) => { return Array.from(b).filter(key => !a.has(key)); }; this.compareAUTrees = (oldAUArray, newAUArray, path = 'accountUpdate') => { const changes = { added: [], removed: [], updated: [] }; // Handle null cases if (!oldAUArray && !newAUArray) return changes; if (!oldAUArray) { if (newAUArray) { changes.added.push({ path, node: newAUArray }); } return changes; } if (!newAUArray) { changes.removed.push({ path, node: oldAUArray }); return changes; } const oldAUMap = new Map(oldAUArray.map(node => [node.id, node])); const newAUMap = new Map(newAUArray.map(node => [node.id, node])); const compareAUItemsRecursive = (a, b, currentPath) => { const keysA = this.traverseNodesRecursively(a); const keysB = this.traverseNodesRecursively(b); // Handle removed keys for (const key of this.keysRemoved(keysA, keysB)) { const value = this.getLeafNodeValue(a, key); changes.removed.push({ path: `${currentPath}.${key}`, node: { key, value } }); } // Handle added keys for (const key of this.keysAdded(keysA, keysB)) { let value = this.getLeafNodeValue(b, key); // Truncate long proof values if (key.includes('proof') && typeof value === 'string' && value.length > 50) { value = `${value.slice(0, 50)}...`; } changes.added.push({ path: `${currentPath}.${key}`, node: { key, value } }); } // Handle updated keys const commonKeys = new Set([...keysA].filter(x => keysB.has(x))); const updatedKeys = this.keysUpdated(a, b, commonKeys); for (const { field, oldValue, newValue } of updatedKeys) { changes.updated.push({ path: `${currentPath}.${field}`, changes: [{ field, oldValue, newValue }] }); } }; // Compare existing nodes for (const oldAU of oldAUArray) { const currentPath = `${path}[${oldAUArray.indexOf(oldAU)}]`; if (!newAUMap.has(oldAU.id)) { changes.removed.push({ path: currentPath, node: oldAU }); } else { const newAU = newAUMap.get(oldAU.id); compareAUItemsRecursive(oldAU, newAU, currentPath); } } // Handle new nodes for (const newAU of newAUArray) { if (!oldAUMap.has(newAU.id)) { const currentPath = `${path}[${newAUArray.indexOf(newAU)}]`; changes.added.push({ path: currentPath, node: newAU }); } } return changes; }; } takeSnapshot(transaction, operation) { const snapshot = { operation, timestamp: Date.now(), tree: transaction, changes: this.compareAUTrees(this.currentTree, transaction) }; this.snapshots.push(snapshot); this.currentTree = transaction; return snapshot; } getSnapshots() { return this.snapshots; } } //# sourceMappingURL=AccountUpdateTrace.js.map