diff --git a/Earthfile b/Earthfile index f01ebc951e14..cc7b14fb45fc 100644 --- a/Earthfile +++ b/Earthfile @@ -243,7 +243,7 @@ noir-projects-with-cache: FROM +bootstrap ENV CI=1 ENV USE_CACHE=1 - LET artifact=noir-projects-ci-tests-$(./noir-projects/bootstrap.sh hash) + LET artifact=noir-projects-ci-tests-$(./noir-projects/bootstrap.sh hash)-$(ci3/cache_content_hash yarn-project/txe) IF ci3/test_should_run $artifact # could be changed to bootstrap once txe solution found WAIT diff --git a/noir-projects/aztec-nr/aztec/src/oracle/block_header.nr b/noir-projects/aztec-nr/aztec/src/oracle/block_header.nr index 88c306dbe2d6..35cab77b550c 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/block_header.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/block_header.nr @@ -88,12 +88,12 @@ mod test { unconstrained fn fetching_a_valid_but_different_header_should_fail() { let mut env = TestEnvironment::new(); - env.advance_block_to(3); + env.advance_block_by(4); // We get our current header for the last archive values. let current_header = env.private().historical_header; - let target_block_number = 2; + let target_block_number = 3; let bad_header = get_block_header_at_internal(target_block_number - 1); // We pass in a different block number than the header received diff --git a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/test/utils.nr index 8cd27d4b3a47..46f3b1d3fe35 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/test/utils.nr @@ -11,5 +11,7 @@ pub unconstrained fn setup() -> (&mut TestEnvironment, AztecAddress, AztecAddres let voting_contract = env.deploy_self("EasyPrivateVoting").with_public_void_initializer( initializer_call_interface, ); + + env.advance_block_by(1); (&mut env, voting_contract.to_address(), admin) } diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/test/access_control.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/test/access_control.nr index 42373942d780..e5f6a8e6dcf5 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/test/access_control.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/test/access_control.nr @@ -10,6 +10,8 @@ unconstrained fn access_control() { // Set a new admin NFT::at(nft_contract_address).set_admin(recipient).call(&mut env.public()); + env.advance_block_by(1); + // Check it worked let admin = NFT::at(nft_contract_address).get_admin().view(&mut env.public()); assert(admin == recipient.to_field()); @@ -24,6 +26,8 @@ unconstrained fn access_control() { // Set admin as minter NFT::at(nft_contract_address).set_minter(recipient, true).call(&mut env.public()); + env.advance_block_by(1); + // Check it worked let is_minter = is_minter_call_interface.view(&mut env.public()); assert(is_minter == true); @@ -31,6 +35,8 @@ unconstrained fn access_control() { // Revoke minter as admin NFT::at(nft_contract_address).set_minter(recipient, false).call(&mut env.public()); + env.advance_block_by(1); + // Check it worked let is_minter = is_minter_call_interface.view(&mut env.public()); assert(is_minter == false); diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/test/minting.nr index 5302e0e397dc..71177ef99822 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/test/minting.nr @@ -9,6 +9,8 @@ unconstrained fn mint_success() { let token_id = 10000; NFT::at(nft_contract_address).mint(owner, token_id).call(&mut env.public()); + env.advance_block_by(1); + utils::assert_owns_public_nft(env, nft_contract_address, owner, token_id); } diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_in_public.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_in_public.nr index f02d84202a79..adf2529e5bbf 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_in_public.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_in_public.nr @@ -14,6 +14,8 @@ unconstrained fn transfer_in_public() { &mut env.public(), ); + env.advance_block_by(1); + utils::assert_owns_public_nft(env, nft_contract_address, recipient, token_id); } @@ -26,6 +28,8 @@ unconstrained fn transfer_in_public_to_self() { // Transfer the NFT NFT::at(nft_contract_address).transfer_in_public(user, user, token_id, 0).call(&mut env.public()); + env.advance_block_by(1); + // Check the user stayed the public owner utils::assert_owns_public_nft(env, nft_contract_address, user, token_id); } diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr index 8166555fe3e9..4bdd77deebf0 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr @@ -50,6 +50,7 @@ pub unconstrained fn setup_and_mint( let minted_token_id = 615; NFT::at(nft_contract_address).mint(owner, minted_token_id).call(&mut env.public()); + env.advance_block_by(1); (env, nft_contract_address, owner, recipient, minted_token_id) } diff --git a/noir-projects/noir-contracts/contracts/router_contract/src/test.nr b/noir-projects/noir-contracts/contracts/router_contract/src/test.nr index a708a71ad11c..300b958e36d4 100644 --- a/noir-projects/noir-contracts/contracts/router_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/router_contract/src/test.nr @@ -10,7 +10,7 @@ unconstrained fn test_check_block_number() { let router_contract_address = router_contract.to_address(); let router = Router::at(router_contract_address); - env.advance_block_by(9); + env.advance_block_by(8); // First we sanity-check that current block number is as expected let current_block_number = env.block_number(); @@ -28,7 +28,7 @@ unconstrained fn test_fail_check_block_number() { let router_contract_address = router_contract.to_address(); let router = Router::at(router_contract_address); - env.advance_block_by(9); + env.advance_block_by(8); // First we sanity-check that current block number is as expected let current_block_number = env.block_number(); diff --git a/yarn-project/txe/src/node/txe_node.ts b/yarn-project/txe/src/node/txe_node.ts index c66ab9f6543b..5b788eb156fe 100644 --- a/yarn-project/txe/src/node/txe_node.ts +++ b/yarn-project/txe/src/node/txe_node.ts @@ -11,6 +11,8 @@ import { type L2Tips, type LogFilter, type MerkleTreeId, + type MerkleTreeReadOperations, + type MerkleTreeWriteOperations, type NullifierMembershipWitness, type ProverConfig, type PublicDataWitness, @@ -44,7 +46,7 @@ import { import { type L1ContractAddresses } from '@aztec/ethereum'; import { poseidon2Hash } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; -import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; +import { type NativeWorldStateService } from '@aztec/world-state'; export class TXENode implements AztecNode { #logsByTags = new Map(); @@ -59,7 +61,8 @@ export class TXENode implements AztecNode { private blockNumber: number, private version: number, private chainId: number, - private trees: MerkleTrees, + private nativeWorldStateService: NativeWorldStateService, + private baseFork: MerkleTreeWriteOperations, ) {} /** @@ -272,14 +275,10 @@ export class TXENode implements AztecNode { // We should likely migrate this so that the trees are owned by the node. // TODO: blockNumber is being passed as undefined, figure out why - if (blockNumber === 'latest' || blockNumber === undefined) { - blockNumber = await this.getBlockNumber(); - } - - const db = - blockNumber === (await this.getBlockNumber()) - ? await this.trees.getLatest() - : new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); + const db: MerkleTreeReadOperations = + blockNumber === (await this.getBlockNumber()) || blockNumber === 'latest' || blockNumber === undefined + ? this.baseFork + : this.nativeWorldStateService.getSnapshot(blockNumber); return await db.findLeafIndices( treeId, diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index ec9cd0ee7c72..cdd706c457a9 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -1,6 +1,10 @@ import { AuthWitness, + Body, + L2Block, MerkleTreeId, + type MerkleTreeReadOperations, + type MerkleTreeWriteOperations, Note, type NoteStatus, NullifierMembershipWitness, @@ -13,6 +17,7 @@ import { } from '@aztec/circuit-types'; import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; import { + AppendOnlyTreeSnapshot, BlockHeader, CallContext, type ContractInstance, @@ -24,8 +29,11 @@ import { IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, NULLIFIER_SUBTREE_HEIGHT, type NULLIFIER_TREE_HEIGHT, + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, type NullifierLeafPreimage, PRIVATE_CONTEXT_INPUTS_LENGTH, type PUBLIC_DATA_TREE_HEIGHT, @@ -34,7 +42,7 @@ import { type PrivateLog, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, - type PublicDataWrite, + PublicDataWrite, type PublicLog, computeContractClassId, computeTaggingSecretPoint, @@ -49,6 +57,12 @@ import { siloNoteHash, siloNullifier, } from '@aztec/circuits.js/hash'; +import { + makeAppendOnlyTreeSnapshot, + makeContentCommitment, + makeGlobalVariables, + makeHeader, +} from '@aztec/circuits.js/testing'; import { type ContractArtifact, type FunctionAbi, @@ -57,6 +71,7 @@ import { countArgumentsSize, } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { padArrayEnd } from '@aztec/foundation/collection'; import { poseidon2Hash } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { type LogFn, type Logger, applyStringFormatting, createDebugOnlyLogger } from '@aztec/foundation/log'; @@ -85,7 +100,7 @@ import { createSimulationError, resolveAssertionMessageFromError, } from '@aztec/simulator/server'; -import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; +import { type NativeWorldStateService } from '@aztec/world-state'; import { TXENode } from '../node/txe_node.js'; import { type TXEDatabase } from '../util/txe_database.js'; @@ -93,7 +108,7 @@ import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_so import { TXEWorldStateDB } from '../util/txe_world_state_db.js'; export class TXE implements TypedOracle { - private blockNumber = 0; + private blockNumber = 1; private sideEffectCounter = 0; private msgSender: AztecAddress; private functionSelector = FunctionSelector.fromField(new Fr(0)); @@ -104,6 +119,7 @@ export class TXE implements TypedOracle { private contractDataOracle: ContractDataOracle; private simulatorOracle: SimulatorOracle; + private publicDataWrites: PublicDataWrite[] = []; private uniqueNoteHashesFromPublic: Fr[] = []; private siloedNullifiersFromPublic: Fr[] = []; private privateLogs: PrivateLog[] = []; @@ -124,16 +140,17 @@ export class TXE implements TypedOracle { private constructor( private logger: Logger, - private trees: MerkleTrees, private executionCache: HashedValuesCache, private keyStore: KeyStore, private txeDatabase: TXEDatabase, private contractAddress: AztecAddress, + private nativeWorldStateService: NativeWorldStateService, + private baseFork: MerkleTreeWriteOperations, ) { this.noteCache = new ExecutionNoteCache(this.getTxRequestHash()); this.contractDataOracle = new ContractDataOracle(txeDatabase); - this.node = new TXENode(this.blockNumber, this.VERSION, this.CHAIN_ID, this.trees); + this.node = new TXENode(this.blockNumber, this.VERSION, this.CHAIN_ID, nativeWorldStateService, baseFork); // Default msg_sender (for entrypoints) is now Fr.max_value rather than 0 addr (see #7190 & #7404) this.msgSender = AztecAddress.fromField(Fr.MAX_FIELD_VALUE); @@ -150,22 +167,31 @@ export class TXE implements TypedOracle { static async create( logger: Logger, - trees: MerkleTrees, executionCache: HashedValuesCache, keyStore: KeyStore, txeDatabase: TXEDatabase, + nativeWorldStateService: NativeWorldStateService, + baseFork: MerkleTreeWriteOperations, ) { - return new TXE(logger, trees, executionCache, keyStore, txeDatabase, await AztecAddress.random()); + return new TXE( + logger, + executionCache, + keyStore, + txeDatabase, + await AztecAddress.random(), + nativeWorldStateService, + baseFork, + ); } // Utils - async #getTreesAt(blockNumber: number) { - const db = - blockNumber === (await this.getBlockNumber()) - ? await this.trees.getLatest() - : new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); - return db; + getNativeWorldStateService() { + return this.nativeWorldStateService; + } + + getBaseFork() { + return this.baseFork; } getChainId(): Promise { @@ -209,10 +235,6 @@ export class TXE implements TypedOracle { this.node.setBlockNumber(blockNumber); } - getTrees() { - return this.trees; - } - getContractDataOracle() { return this.contractDataOracle; } @@ -239,10 +261,21 @@ export class TXE implements TypedOracle { sideEffectsCounter = this.sideEffectCounter, isStaticCall = false, ) { - const db = await this.#getTreesAt(blockNumber); - const previousBlockState = await this.#getTreesAt(blockNumber - 1); + if (blockNumber > this.blockNumber) { + throw new Error( + `Tried to request private context inputs for ${blockNumber}, which is greater than our current block number of ${this.blockNumber}`, + ); + } else if (blockNumber === this.blockNumber) { + this.logger.debug( + `Tried to request private context inputs for ${blockNumber}, equal to current block of ${this.blockNumber}. Clamping to current block - 1.`, + ); + blockNumber = this.blockNumber - 1; + } - const stateReference = await db.getStateReference(); + const snap = this.nativeWorldStateService.getSnapshot(blockNumber); + const previousBlockState = this.nativeWorldStateService.getSnapshot(blockNumber - 1); + + const stateReference = await snap.getStateReference(); const inputs = PrivateContextInputs.empty(); inputs.txContext.chainId = new Fr(await this.node.getChainId()); inputs.txContext.version = new Fr(await this.node.getVersion()); @@ -270,26 +303,17 @@ export class TXE implements TypedOracle { } async addPublicDataWrites(writes: PublicDataWrite[]) { - const db = await this.trees.getLatest(); - await db.batchInsert( + this.publicDataWrites.push(...writes); + + await this.baseFork.sequentialInsert( MerkleTreeId.PUBLIC_DATA_TREE, writes.map(w => new PublicDataTreeLeaf(w.leafSlot, w.value).toBuffer()), - 0, - ); - } - - async addSiloedNullifiers(siloedNullifiers: Fr[]) { - const db = await this.trees.getLatest(); - await db.batchInsert( - MerkleTreeId.NULLIFIER_TREE, - siloedNullifiers.map(n => n.toBuffer()), - NULLIFIER_SUBTREE_HEIGHT, ); } async checkNullifiersNotInTree(contractAddress: AztecAddress, nullifiers: Fr[]) { const siloedNullifiers = await Promise.all(nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier))); - const db = await this.trees.getLatest(); + const db = this.baseFork; const nullifierIndexesInTree = await db.findLeafIndices( MerkleTreeId.NULLIFIER_TREE, siloedNullifiers.map(n => n.toBuffer()), @@ -299,31 +323,12 @@ export class TXE implements TypedOracle { } } - async addSiloedNullifiersFromPublic(siloedNullifiers: Fr[]) { + addSiloedNullifiersFromPublic(siloedNullifiers: Fr[]) { this.siloedNullifiersFromPublic.push(...siloedNullifiers); - - await this.addSiloedNullifiers(siloedNullifiers); } - async addNullifiers(contractAddress: AztecAddress, nullifiers: Fr[]) { - const siloedNullifiers = await Promise.all(nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier))); - await this.addSiloedNullifiers(siloedNullifiers); - } - - async addUniqueNoteHashes(siloedNoteHashes: Fr[]) { - const db = await this.trees.getLatest(); - await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, siloedNoteHashes); - } - - async addUniqueNoteHashesFromPublic(siloedNoteHashes: Fr[]) { + addUniqueNoteHashesFromPublic(siloedNoteHashes: Fr[]) { this.uniqueNoteHashesFromPublic.push(...siloedNoteHashes); - await this.addUniqueNoteHashes(siloedNoteHashes); - } - - async addNoteHashes(contractAddress: AztecAddress, noteHashes: Fr[]) { - const siloedNoteHashes = await Promise.all(noteHashes.map(noteHash => siloNoteHash(contractAddress, noteHash))); - - await this.addUniqueNoteHashes(siloedNoteHashes); } async addPrivateLogs(contractAddress: AztecAddress, privateLogs: PrivateLog[]) { @@ -391,34 +396,36 @@ export class TXE implements TypedOracle { } async getMembershipWitness(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise { - const db = await this.#getTreesAt(blockNumber); - const index = (await db.findLeafIndices(treeId, [leafValue.toBuffer()]))[0]; + const snap = this.nativeWorldStateService.getSnapshot(blockNumber); + const index = (await snap.findLeafIndices(treeId, [leafValue.toBuffer()]))[0]; if (index === undefined) { throw new Error(`Leaf value: ${leafValue} not found in ${MerkleTreeId[treeId]} at block ${blockNumber}`); } - const siblingPath = await db.getSiblingPath(treeId, index); + const siblingPath = await snap.getSiblingPath(treeId, index); return [new Fr(index), ...siblingPath.toFields()]; } - // async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: Fr) { - // const committedDb = new MerkleTreeSnapshotOperationsFacade(this.trees, blockNumber); - // const result = await committedDb.getSiblingPath(treeId, leafIndex.toBigInt()); - // return result.toFields(); - // } + async getSiblingPath(blockNumber: number, treeId: MerkleTreeId, leafIndex: Fr) { + const snap = this.nativeWorldStateService.getSnapshot(blockNumber); + + const result = await snap.getSiblingPath(treeId, leafIndex.toBigInt()); + return result.toFields(); + } async getNullifierMembershipWitness( blockNumber: number, nullifier: Fr, ): Promise { - const db = await this.#getTreesAt(blockNumber); - const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0]; + const snap = this.nativeWorldStateService.getSnapshot(blockNumber); + + const [index] = await snap.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]); if (!index) { return undefined; } - const leafPreimagePromise = db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index); - const siblingPathPromise = db.getSiblingPath( + const leafPreimagePromise = snap.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index); + const siblingPathPromise = snap.getSiblingPath( MerkleTreeId.NULLIFIER_TREE, BigInt(index), ); @@ -433,16 +440,17 @@ export class TXE implements TypedOracle { } async getPublicDataTreeWitness(blockNumber: number, leafSlot: Fr): Promise { - const db = await this.#getTreesAt(blockNumber); - const lowLeafResult = await db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); + const snap = this.nativeWorldStateService.getSnapshot(blockNumber); + + const lowLeafResult = await snap.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); if (!lowLeafResult) { return undefined; } else { - const preimage = (await db.getLeafPreimage( + const preimage = (await snap.getLeafPreimage( MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index, )) as PublicDataTreeLeafPreimage; - const path = await db.getSiblingPath( + const path = await snap.getSiblingPath( MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index, ); @@ -454,8 +462,9 @@ export class TXE implements TypedOracle { blockNumber: number, nullifier: Fr, ): Promise { - const committedDb = await this.#getTreesAt(blockNumber); - const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt()); + const snap = this.nativeWorldStateService.getSnapshot(blockNumber); + + const findResult = await snap.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt()); if (!findResult) { return undefined; } @@ -463,9 +472,9 @@ export class TXE implements TypedOracle { if (alreadyPresent) { this.logger.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`); } - const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!; + const preimageData = (await snap.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!; - const siblingPath = await committedDb.getSiblingPath( + const siblingPath = await snap.getSiblingPath( MerkleTreeId.NULLIFIER_TREE, BigInt(index), ); @@ -473,10 +482,26 @@ export class TXE implements TypedOracle { } async getBlockHeader(blockNumber: number): Promise { - const header = BlockHeader.empty(); - const db = await this.#getTreesAt(blockNumber); - header.state = await db.getStateReference(); - header.globalVariables.blockNumber = new Fr(blockNumber); + if (blockNumber === 1) { + // TODO: Figure out why native merkle trees cannot get snapshot of 0, as it defaults to latest + throw new Error('Cannot get the block header of block number 1'); + } + + const snap = this.nativeWorldStateService.getSnapshot(blockNumber); + const stateReference = await snap.getStateReference(); + + const previousState = this.nativeWorldStateService.getSnapshot(blockNumber - 1); + const archiveInfo = await previousState.getTreeInfo(MerkleTreeId.ARCHIVE); + + const header = new BlockHeader( + new AppendOnlyTreeSnapshot(new Fr(archiveInfo.root), Number(archiveInfo.size)), + makeContentCommitment(), + stateReference, + makeGlobalVariables(), + Fr.ZERO, + Fr.ZERO, + ); + return header; } @@ -567,9 +592,10 @@ export class TXE implements TypedOracle { } async checkNullifierExists(innerNullifier: Fr): Promise { + const snap = this.nativeWorldStateService.getSnapshot(this.blockNumber - 1); + const nullifier = await siloNullifier(this.contractAddress, innerNullifier!); - const db = await this.trees.getLatest(); - const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0]; + const [index] = await snap.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]); return index !== undefined; } @@ -587,7 +613,13 @@ export class TXE implements TypedOracle { blockNumber: number, numberOfElements: number, ): Promise { - const db = await this.#getTreesAt(blockNumber); + let db: MerkleTreeReadOperations; + if (blockNumber === this.blockNumber) { + db = this.baseFork; + } else { + db = this.nativeWorldStateService.getSnapshot(blockNumber); + } + const values = []; for (let i = 0n; i < numberOfElements; i++) { const storageSlot = startStorageSlot.add(new Fr(i)); @@ -610,20 +642,15 @@ export class TXE implements TypedOracle { } async storageWrite(startStorageSlot: Fr, values: Fr[]): Promise { - const db = await this.trees.getLatest(); - const publicDataWrites = await Promise.all( values.map(async (value, i) => { const storageSlot = startStorageSlot.add(new Fr(i)); this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); - return new PublicDataTreeLeaf(await computePublicDataTreeLeafSlot(this.contractAddress, storageSlot), value); + return new PublicDataWrite(await computePublicDataTreeLeafSlot(this.contractAddress, storageSlot), value); }), ); - await db.batchInsert( - MerkleTreeId.PUBLIC_DATA_TREE, - publicDataWrites.map(write => write.toBuffer()), - 0, - ); + + await this.addPublicDataWrites(publicDataWrites); return publicDataWrites.map(write => write.value); } @@ -636,6 +663,8 @@ export class TXE implements TypedOracle { this.committedBlocks.add(blockNumber); } + const fork = this.baseFork; + const txEffect = TxEffect.empty(); const nonceGenerator = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0]; @@ -653,18 +682,72 @@ export class TXE implements TypedOracle { ); txEffect.noteHashes = [...uniqueNoteHashesFromPrivate, ...this.uniqueNoteHashesFromPublic]; - txEffect.nullifiers = this.noteCache.getAllNullifiers(); + txEffect.nullifiers = [...this.siloedNullifiersFromPublic, ...this.noteCache.getAllNullifiers()]; if (usedTxRequestHashForNonces) { txEffect.nullifiers.unshift(this.getTxRequestHash()); } + + txEffect.publicDataWrites = this.publicDataWrites; + + const body = new Body([txEffect]); + + const l2Block = new L2Block( + makeAppendOnlyTreeSnapshot(blockNumber + 1), + makeHeader(0, blockNumber, blockNumber), + body, + ); + + const paddedTxEffects = l2Block.body.txEffects; + + const l1ToL2Messages = Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(0).map(Fr.zero); + + { + const noteHashesPadded = paddedTxEffects.flatMap(txEffect => + padArrayEnd(txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX), + ); + await fork.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, noteHashesPadded); + + await fork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2Messages); + } + + { + for (const txEffect of paddedTxEffects) { + // We do not need to add public data writes because we apply them as we go. We use the sequentialInsert because + // the batchInsert was not working when updating a previously updated slot. + + const nullifiersPadded = padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX); + + await fork.batchInsert( + MerkleTreeId.NULLIFIER_TREE, + nullifiersPadded.map(nullifier => nullifier.toBuffer()), + NULLIFIER_SUBTREE_HEIGHT, + ); + } + } + await this.node.setTxEffect(blockNumber, new TxHash(new Fr(blockNumber)), txEffect); this.node.setNullifiersIndexesWithBlock(blockNumber, txEffect.nullifiers); this.node.addNoteLogsByTags(this.blockNumber, this.privateLogs); this.node.addPublicLogsByTags(this.blockNumber, this.publicLogs); - await this.addUniqueNoteHashes(txEffect.noteHashes); - await this.addSiloedNullifiers(txEffect.nullifiers); + const stateReference = await fork.getStateReference(); + const archiveInfo = await fork.getTreeInfo(MerkleTreeId.ARCHIVE); + const header = new BlockHeader( + new AppendOnlyTreeSnapshot(new Fr(archiveInfo.root), Number(archiveInfo.size)), + makeContentCommitment(), + stateReference, + makeGlobalVariables(), + Fr.ZERO, + Fr.ZERO, + ); + + l2Block.header = header; + + await fork.updateArchive(l2Block.header); + + await this.nativeWorldStateService.handleL2BlockAndMessages(l2Block, l1ToL2Messages); + this.publicDataWrites = []; this.privateLogs = []; this.publicLogs = []; this.uniqueNoteHashesFromPublic = []; @@ -804,8 +887,8 @@ export class TXE implements TypedOracle { private async executePublicFunction(args: Fr[], callContext: CallContext, isTeardown: boolean = false) { const executionRequest = new PublicExecutionRequest(callContext, args); - const db = await this.trees.getLatest(); - const worldStateDb = new TXEWorldStateDB(db, new TXEPublicContractDataSource(this)); + const db = this.baseFork; + const worldStateDb = new TXEWorldStateDB(db, new TXEPublicContractDataSource(this), this); const globalVariables = GlobalVariables.empty(); globalVariables.chainId = new Fr(await this.node.getChainId()); @@ -813,6 +896,13 @@ export class TXE implements TypedOracle { globalVariables.blockNumber = new Fr(this.blockNumber); globalVariables.gasFees = new GasFees(1, 1); + const tempFork = await this.nativeWorldStateService.fork(); + // Apply current public data writes + await tempFork.sequentialInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + this.publicDataWrites.map(p => p.toBuffer()), + ); + // If the contract instance exists in the TXE's world state, make sure its nullifier is present in the tree // so its nullifier check passes. if ((await worldStateDb.getContractInstance(callContext.contractAddress)) !== undefined) { @@ -821,13 +911,13 @@ export class TXE implements TypedOracle { callContext.contractAddress.toField(), ); if ((await worldStateDb.getNullifierIndex(contractAddressNullifier)) === undefined) { - await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0); + await tempFork.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0); } } const simulator = new PublicTxSimulator( - db, - new TXEWorldStateDB(db, new TXEPublicContractDataSource(this)), + tempFork, + new TXEWorldStateDB(tempFork, new TXEPublicContractDataSource(this), this), globalVariables, ); @@ -851,7 +941,12 @@ export class TXE implements TypedOracle { s => !s.isEmpty(), ); - await this.addUniqueNoteHashesFromPublic(noteHashes); + const publicDataWrites = result.avmProvingRequest.inputs.publicInputs.accumulatedData.publicDataWrites.filter( + s => !s.isEmpty(), + ); + await this.addPublicDataWrites(publicDataWrites); + + this.addUniqueNoteHashesFromPublic(noteHashes); this.addPublicLogs( result.avmProvingRequest.inputs.publicInputs.accumulatedData.publicLogs.filter( @@ -859,6 +954,7 @@ export class TXE implements TypedOracle { ), ); + await tempFork.close(); return Promise.resolve(result); } @@ -907,16 +1003,13 @@ export class TXE implements TypedOracle { // Apply side effects const sideEffects = executionResult.avmProvingRequest.inputs.publicInputs.accumulatedData; - const publicDataWrites = sideEffects.publicDataWrites.filter(s => !s.isEmpty()); - const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty()); const { usedTxRequestHashForNonces } = this.noteCache.finish(); const firstNullifier = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0]; const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty()).filter(s => !s.equals(firstNullifier)); - await this.addPublicDataWrites(publicDataWrites); - await this.addUniqueNoteHashesFromPublic(noteHashes); - await this.addSiloedNullifiers(nullifiers); + // For some reason we cannot move this up to 'executePublicFunction'. It gives us an error of trying to modify the same nullifier twice. + this.addSiloedNullifiersFromPublic(nullifiers); this.setContractAddress(currentContractAddress); this.setMsgSender(currentMessageSender); @@ -1033,8 +1126,8 @@ export class TXE implements TypedOracle { : this.noteCache.getAllNullifiers()[0]; const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty()).filter(s => !s.equals(firstNullifier)); await this.addPublicDataWrites(publicDataWrites); - await this.addUniqueNoteHashes(noteHashes); - await this.addSiloedNullifiers(nullifiers); + this.addUniqueNoteHashesFromPublic(noteHashes); + this.addSiloedNullifiersFromPublic(nullifiers); } this.setContractAddress(currentContractAddress); @@ -1053,36 +1146,34 @@ export class TXE implements TypedOracle { async avmOpcodeNullifierExists(innerNullifier: Fr, targetAddress: AztecAddress): Promise { const nullifier = await siloNullifier(targetAddress, innerNullifier!); - const db = await this.trees.getLatest(); + const db = this.baseFork; const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0]; return index !== undefined; } async avmOpcodeEmitNullifier(nullifier: Fr) { - const db = await this.trees.getLatest(); const siloedNullifier = await siloNullifier(this.contractAddress, nullifier); - await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()], NULLIFIER_SUBTREE_HEIGHT); + this.addSiloedNullifiersFromPublic([siloedNullifier]); + return Promise.resolve(); } + // Doesn't this need to get hashed w/ the nonce ? async avmOpcodeEmitNoteHash(noteHash: Fr) { - const db = await this.trees.getLatest(); const siloedNoteHash = await siloNoteHash(this.contractAddress, noteHash); - await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [siloedNoteHash]); + this.addUniqueNoteHashesFromPublic([siloedNoteHash]); return Promise.resolve(); } async avmOpcodeStorageRead(slot: Fr) { - const db = await this.trees.getLatest(); - const leafSlot = await computePublicDataTreeLeafSlot(this.contractAddress, slot); - const lowLeafResult = await db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); + const lowLeafResult = await this.baseFork.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt()); if (!lowLeafResult || !lowLeafResult.alreadyPresent) { return Fr.ZERO; } - const preimage = (await db.getLeafPreimage( + const preimage = (await this.baseFork.getLeafPreimage( MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index, )) as PublicDataTreeLeafPreimage; diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index a47a081cb847..245bb7e7b5d1 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -1,10 +1,9 @@ import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr'; -import { L2Block, MerkleTreeId, SimulationError } from '@aztec/circuit-types'; +import { MerkleTreeId, SimulationError } from '@aztec/circuit-types'; import { - BlockHeader, Fr, FunctionSelector, - PublicDataTreeLeaf, + PublicDataWrite, PublicKeys, computePartialAddress, getContractInstanceFromDeployParams, @@ -20,8 +19,7 @@ import { getCanonicalProtocolContract } from '@aztec/protocol-contracts/bundle'; import { enrichPublicSimulationError } from '@aztec/pxe'; import { type TypedOracle } from '@aztec/simulator/client'; import { HashedValuesCache } from '@aztec/simulator/server'; -import { getTelemetryClient } from '@aztec/telemetry-client'; -import { MerkleTrees } from '@aztec/world-state'; +import { NativeWorldStateService } from '@aztec/world-state'; import { TXE } from '../oracle/txe_oracle.js'; import { @@ -42,8 +40,10 @@ export class TXEService { static async init(logger: Logger) { const store = openTmpStore(true); - const trees = await MerkleTrees.new(store, getTelemetryClient(), logger); const executionCache = new HashedValuesCache(); + const nativeWorldStateService = await NativeWorldStateService.tmp(); + const baseFork = await nativeWorldStateService.fork(); + const keyStore = new KeyStore(store); const txeDatabase = new TXEDatabase(store); // Register protocol contracts. @@ -53,7 +53,7 @@ export class TXEService { await txeDatabase.addContractInstance(instance); } logger.debug(`TXE service initialized`); - const txe = await TXE.create(logger, trees, executionCache, keyStore, txeDatabase); + const txe = await TXE.create(logger, executionCache, keyStore, txeDatabase, nativeWorldStateService, baseFork); const service = new TXEService(logger, txe); await service.advanceBlocksBy(toSingle(new Fr(1n))); return service; @@ -69,22 +69,10 @@ export class TXEService { async advanceBlocksBy(blocks: ForeignCallSingle) { const nBlocks = fromSingle(blocks).toNumber(); this.logger.debug(`time traveling ${nBlocks} blocks`); - const trees = (this.typedOracle as TXE).getTrees(); - - await (this.typedOracle as TXE).commitState(); for (let i = 0; i < nBlocks; i++) { const blockNumber = await this.typedOracle.getBlockNumber(); - const header = BlockHeader.empty(); - const l2Block = L2Block.empty(); - header.state = await trees.getStateReference(true); - header.globalVariables.blockNumber = new Fr(blockNumber); - await trees.appendLeaves(MerkleTreeId.ARCHIVE, [await header.hash()]); - l2Block.archive.root = Fr.fromBuffer((await trees.getTreeInfo(MerkleTreeId.ARCHIVE, true)).root); - l2Block.header = header; - const headerHash = await header.hash(); - this.logger.debug(`Block ${blockNumber} created, header hash ${headerHash.toString()}`); - await trees.handleL2BlockAndMessages(l2Block, []); + await (this.typedOracle as TXE).commitState(); (this.typedOracle as TXE).setBlockNumber(blockNumber + 1); } return toForeignCallResult([]); @@ -146,24 +134,20 @@ export class TXEService { startStorageSlot: ForeignCallSingle, values: ForeignCallArray, ) { - const trees = (this.typedOracle as TXE).getTrees(); const startStorageSlotFr = fromSingle(startStorageSlot); const valuesFr = fromArray(values); const contractAddressFr = addressFromSingle(contractAddress); - const db = await trees.getLatest(); const publicDataWrites = await Promise.all( valuesFr.map(async (value, i) => { const storageSlot = startStorageSlotFr.add(new Fr(i)); this.logger.debug(`Oracle storage write: slot=${storageSlot.toString()} value=${value}`); - return new PublicDataTreeLeaf(await computePublicDataTreeLeafSlot(contractAddressFr, storageSlot), value); + return new PublicDataWrite(await computePublicDataTreeLeafSlot(contractAddressFr, storageSlot), value); }), ); - await db.batchInsert( - MerkleTreeId.PUBLIC_DATA_TREE, - publicDataWrites.map(write => write.toBuffer()), - 0, - ); + + await (this.typedOracle as TXE).addPublicDataWrites(publicDataWrites); + return toForeignCallResult([toArray(publicDataWrites.map(write => write.value))]); } @@ -532,16 +516,6 @@ export class TXEService { return toForeignCallResult([toSingle(await this.typedOracle.getVersion())]); } - async addNullifiers(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, nullifiers: ForeignCallArray) { - await (this.typedOracle as TXE).addNullifiers(addressFromSingle(contractAddress), fromArray(nullifiers)); - return toForeignCallResult([]); - } - - async addNoteHashes(contractAddress: ForeignCallSingle, _length: ForeignCallSingle, noteHashes: ForeignCallArray) { - await (this.typedOracle as TXE).addNoteHashes(addressFromSingle(contractAddress), fromArray(noteHashes)); - return toForeignCallResult([]); - } - async getBlockHeader(blockNumber: ForeignCallSingle) { const header = await this.typedOracle.getBlockHeader(fromSingle(blockNumber).toNumber()); if (!header) { diff --git a/yarn-project/txe/src/util/txe_world_state_db.ts b/yarn-project/txe/src/util/txe_world_state_db.ts index 191c30562177..5618bdd964e5 100644 --- a/yarn-project/txe/src/util/txe_world_state_db.ts +++ b/yarn-project/txe/src/util/txe_world_state_db.ts @@ -3,14 +3,16 @@ import { type AztecAddress, type ContractDataSource, Fr, - PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, + PublicDataWrite, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; import { WorldStateDB } from '@aztec/simulator/server'; +import { type TXE } from '../oracle/txe_oracle.js'; + export class TXEWorldStateDB extends WorldStateDB { - constructor(private merkleDb: MerkleTreeWriteOperations, dataSource: ContractDataSource) { + constructor(private merkleDb: MerkleTreeWriteOperations, dataSource: ContractDataSource, private txe: TXE) { super(merkleDb, dataSource); } @@ -31,11 +33,10 @@ export class TXEWorldStateDB extends WorldStateDB { } override async storageWrite(contract: AztecAddress, slot: Fr, newValue: Fr): Promise { - await this.merkleDb.batchInsert( - MerkleTreeId.PUBLIC_DATA_TREE, - [new PublicDataTreeLeaf(await computePublicDataTreeLeafSlot(contract, slot), newValue).toBuffer()], - 0, - ); + await this.txe.addPublicDataWrites([ + new PublicDataWrite(await computePublicDataTreeLeafSlot(contract, slot), newValue), + ]); + return newValue.toBigInt(); }