UNPKG

@tokamak-zk-evm/synthesizer

Version:

Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation

242 lines 10.1 kB
import { Hardfork } from '@synthesizer-libs/common'; import { Address, RIPEMD160_ADDRESS_STRING, bytesToHex, bytesToUnprefixedHex, stripHexPrefix, unprefixedHexToBytes, } from "@synthesizer-libs/util"; import debugDefault from 'debug'; import { hexToBytes } from 'ethereum-cryptography/utils'; export class Journal { constructor(stateManager, common) { // Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables // Additional window check is to prevent vite browser bundling (and potentially other) to break this.DEBUG = typeof window === 'undefined' ? (process?.env?.DEBUG?.includes('ethjs') ?? false) : false; this._debug = debugDefault('evm:journal'); // TODO maybe call into this.clearJournal this.cleanJournal(); this.journalHeight = 0; this.stateManager = stateManager; this.common = common; } /** * Clears the internal `accessList` map, and mark this journal to start reporting * which addresses and storages have been accessed */ startReportingAccessList() { this.accessList = new Map(); } /** * Clears the internal `preimages` map, and marks this journal to start reporting * the images (hashed addresses) of the accounts that have been accessed */ startReportingPreimages() { this.preimages = new Map(); } async putAccount(address, account) { this.touchAddress(address); return this.stateManager.putAccount(address, account); } async deleteAccount(address) { this.touchAddress(address); await this.stateManager.deleteAccount(address); } touchAddress(address) { const str = address.toString().slice(2); this.touchAccount(str); } touchAccount(address) { // If preimages are being reported, add the address to the preimages map if (this.preimages !== undefined) { const bytesAddress = unprefixedHexToBytes(address); if (this.stateManager.getAppliedKey === undefined) { throw new Error('touchAccount: stateManager.getAppliedKey can not be undefined if preimage storing is enabled'); } const hashedKey = this.stateManager.getAppliedKey(bytesAddress); this.preimages.set(bytesToHex(hashedKey), bytesAddress); } if (!this.touched.has(address)) { this.touched.add(address); const diffArr = this.journalDiff[this.journalDiff.length - 1][1]; diffArr[2].add(address); } } async commit() { this.journalHeight--; this.journalDiff.push([this.journalHeight, [new Set(), new Map(), new Set()]]); await this.stateManager.commit(); } async checkpoint() { this.journalHeight++; this.journalDiff.push([this.journalHeight, [new Set(), new Map(), new Set()]]); await this.stateManager.checkpoint(); } async revert() { // Loop backwards over the journal diff and stop if we are at a lower height than current journal height // During this process, delete all items. // TODO check this logic, if there is this array: height [4,3,4] and we revert height 4, then the final // diff arr will be reverted, but it will stop at height 3, so [4,3] are both not reverted..? let finalI; for (let i = this.journalDiff.length - 1; i >= 0; i--) { finalI = i; const [height, diff] = this.journalDiff[i]; if (height < this.journalHeight) { break; } const addressSet = diff[0]; const slotsMap = diff[1]; const touchedSet = diff[2]; for (const address of addressSet) { // Sanity check, journal should have the item if (this.journal.has(address)) { this.journal.delete(address); } } for (const [address, delSlots] of slotsMap) { // Sanity check, the address SHOULD be in the journal if (this.journal.has(address)) { const slots = this.journal.get(address); for (const delSlot of delSlots) { slots.delete(delSlot); } } } for (const address of touchedSet) { // Delete the address from the journal if (address !== RIPEMD160_ADDRESS_STRING) { // If RIPEMD160 is touched, keep it touched. // Default behavior for others. this.touched.delete(address); } } } // the final diffs are reverted and we can dispose those this.journalDiff = this.journalDiff.slice(0, finalI + 1); this.journalHeight--; await this.stateManager.revert(); } cleanJournal() { this.journalHeight = 0; this.journal = new Map(); this.alwaysWarmJournal = new Map(); this.touched = new Set(); this.journalDiff = [[0, [new Set(), new Map(), new Set()]]]; } /** * Removes accounts from the state trie that have been touched, * as defined in EIP-161 (https://eips.ethereum.org/EIPS/eip-161). * Also cleanups any other internal fields */ async cleanup() { if (this.common.gteHardfork(Hardfork.SpuriousDragon)) { for (const addressHex of this.touched) { const address = new Address(hexToBytes(`0x${addressHex}`)); const account = await this.stateManager.getAccount(address); if (account === undefined || account.isEmpty()) { await this.deleteAccount(address); if (this.DEBUG) { this._debug(`Cleanup touched account address=${address} (>= SpuriousDragon)`); } } } } this.cleanJournal(); delete this.accessList; delete this.preimages; } addAlwaysWarmAddress(addressStr, addToAccessList = false) { const address = stripHexPrefix(addressStr); if (!this.alwaysWarmJournal.has(address)) { this.alwaysWarmJournal.set(address, new Set()); } if (addToAccessList && this.accessList !== undefined) { if (!this.accessList.has(address)) { this.accessList.set(address, new Set()); } } } addAlwaysWarmSlot(addressStr, slotStr, addToAccessList = false) { const address = stripHexPrefix(addressStr); this.addAlwaysWarmAddress(address, addToAccessList); const slotsSet = this.alwaysWarmJournal.get(address); const slot = stripHexPrefix(slotStr); slotsSet.add(slot); if (addToAccessList && this.accessList !== undefined) { this.accessList.get(address).add(slot); } } /** * Returns true if the address is warm in the current context * @param address - The address (as a Uint8Array) to check */ isWarmedAddress(address) { const addressHex = bytesToUnprefixedHex(address); const warm = this.journal.has(addressHex) || this.alwaysWarmJournal.has(addressHex); return warm; } /** * Add a warm address in the current context * @param addressArr - The address (as a Uint8Array) to check */ addWarmedAddress(addressArr) { const address = bytesToUnprefixedHex(addressArr); if (!this.journal.has(address)) { this.journal.set(address, new Set()); const diffArr = this.journalDiff[this.journalDiff.length - 1][1]; diffArr[0].add(address); } if (this.accessList !== undefined) { if (!this.accessList.has(address)) { this.accessList.set(address, new Set()); } } } /** * Returns true if the slot of the address is warm * @param address - The address (as a Uint8Array) to check * @param slot - The slot (as a Uint8Array) to check */ isWarmedStorage(address, slot) { const addressHex = bytesToUnprefixedHex(address); const slots = this.journal.get(addressHex); if (slots === undefined) { if (this.alwaysWarmJournal.has(addressHex)) { return this.alwaysWarmJournal.get(addressHex).has(bytesToUnprefixedHex(slot)); } return false; } if (slots.has(bytesToUnprefixedHex(slot))) { return true; } else if (this.alwaysWarmJournal.has(addressHex)) { return this.alwaysWarmJournal.get(addressHex).has(bytesToUnprefixedHex(slot)); } return false; } /** * Mark the storage slot in the address as warm in the current context * @param address - The address (as a Uint8Array) to check * @param slot - The slot (as a Uint8Array) to check */ addWarmedStorage(address, slot) { const addressHex = bytesToUnprefixedHex(address); let slots = this.journal.get(addressHex); if (slots === undefined) { this.addWarmedAddress(address); slots = this.journal.get(addressHex); } const slotStr = bytesToUnprefixedHex(slot); if (!slots.has(slotStr)) { slots.add(slotStr); const diff = this.journalDiff[this.journalDiff.length - 1][1]; const addressSlotMap = diff[1]; if (!addressSlotMap.has(addressHex)) { addressSlotMap.set(addressHex, new Set()); } const slotsSet = addressSlotMap.get(addressHex); slotsSet.add(slotStr); } if (this.accessList !== undefined) { // Note: in `addWarmedAddress` the address is added to warm addresses const addrSet = this.accessList.get(addressHex); addrSet.add(slotStr); } } } //# sourceMappingURL=journal.js.map