From 019358b910f54db481879ba4c6c5322e84c28f64 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Mon, 25 Mar 2024 10:00:37 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=91=80=20private=20constrained=20?= =?UTF-8?q?messy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs_example_contract/src/main.nr | 38 +++++++++++ .../contract/contract_function_interaction.ts | 45 +++++++++++- .../aztec.js/src/wallet/base_wallet.ts | 4 ++ .../circuit-types/src/interfaces/pxe.ts | 7 +- .../end-to-end/src/e2e_state_vars.test.ts | 30 +++++++- .../pxe/src/pxe_service/pxe_service.ts | 68 +++++++++++++++++++ .../simulator/src/client/private_execution.ts | 10 ++- 7 files changed, 195 insertions(+), 7 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index 7b13ac0dc53b..0fd22fb195ef 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -108,6 +108,44 @@ contract DocsExample { assert(read.points == expected.points, "Invalid points"); } + #[aztec(private)] + fn multicall() -> pub AztecAddress { + AztecAddress::from_field( + context.call_private_function_no_args( + context.this_address(), + FunctionSelector::from_signature("get_message_sender()") + )[0] + ) + } + + #[aztec(private)] + fn multicall_public() { + context.call_public_function_no_args( + context.this_address(), + FunctionSelector::from_signature("get_message_sender_public()") + ); + } + + #[aztec(private)] + fn get_message_sender() -> pub AztecAddress { + context.msg_sender() + } + + #[aztec(public)] + fn get_message_sender_public() -> pub AztecAddress { + context.msg_sender() + } + + #[aztec(public)] + fn get_shared_immutable_constrained() -> pub Leader { + storage.shared_immutable.read_public() + } + + #[aztec(private)] + fn get_shared_immutable_constrained_private() -> pub Leader { + storage.shared_immutable.read_private() + } + unconstrained fn get_shared_immutable() -> pub Leader { storage.shared_immutable.read_public() } diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index f03b33407632..201d846f1120 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -1,5 +1,5 @@ -import { FunctionCall, TxExecutionRequest } from '@aztec/circuit-types'; -import { AztecAddress, FunctionData } from '@aztec/circuits.js'; +import { FunctionCall, PackedArguments, TxExecutionRequest } from '@aztec/circuit-types'; +import { AztecAddress, FunctionData, TxContext } from '@aztec/circuits.js'; import { FunctionAbi, FunctionType, encodeArguments } from '@aztec/foundation/abi'; import { Wallet } from '../account/wallet.js'; @@ -77,4 +77,45 @@ export class ContractFunctionInteraction extends BaseContractInteraction { const { from } = options; return this.wallet.viewTx(this.functionDao.name, this.args, this.contractAddress, from); } + + /** + * Execute a view (read-only) transaction on an unconstrained function. + * This method is used to call functions that do not modify the contract state and only return data. + * Throws an error if called on a non-unconstrained function. + * @param options - An optional object containing additional configuration for the transaction. + * @returns The result of the view transaction as returned by the contract function. + */ + public async viewConstrained(options: ViewMethodOptions = {}) { + // We need to create the request, but we need to change the entry-point slightly I think :thinking: + + const packedArgs = PackedArguments.fromArgs(encodeArguments(this.functionDao, this.args)); + + // I have forgotten what the hell origin is. + + const nodeInfo = await this.wallet.getNodeInfo(); + + // We need to figure something better around for doing the simulation to have a origin and a to that is different + // such that we can actually replace the "msg_sender" etc + + // Depending on public or not we need to do some changes. + const a = FunctionData.fromAbi(this.functionDao); + + if (a.isPrivate) { + const txRequest = TxExecutionRequest.from({ + argsHash: packedArgs.hash, + origin: this.contractAddress, + functionData: FunctionData.fromAbi(this.functionDao), + txContext: TxContext.empty(nodeInfo.chainId, nodeInfo.protocolVersion), + packedArguments: [packedArgs], + authWitnesses: [], + }); + + const vue = await this.pxe.simulateCall(txRequest, options.from ?? this.wallet.getAddress()); + return vue.rv; + } else { + await this.create(); + const vue = await this.pxe.simulateCall(this.txRequest!); + return vue.rv; + } + } } diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 3402041b15a1..4547d79546a5 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -13,6 +13,7 @@ import { TxExecutionRequest, TxHash, TxReceipt, + Vue, } from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress, Fr, GrumpkinPrivateKey, PartialAddress } from '@aztec/circuits.js'; import { ContractArtifact } from '@aztec/foundation/abi'; @@ -98,6 +99,9 @@ export abstract class BaseWallet implements Wallet { simulateTx(txRequest: TxExecutionRequest, simulatePublic: boolean): Promise { return this.pxe.simulateTx(txRequest, simulatePublic); } + simulateCall(txRequest: TxExecutionRequest, msgSender: AztecAddress): Promise { + return this.pxe.simulateCall(txRequest, msgSender); + } sendTx(tx: Tx): Promise { return this.pxe.sendTx(tx); } diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index 384ccdb14378..1b87304d9ece 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -1,5 +1,5 @@ import { AztecAddress, CompleteAddress, Fr, GrumpkinPrivateKey, PartialAddress } from '@aztec/circuits.js'; -import { ContractArtifact } from '@aztec/foundation/abi'; +import { ContractArtifact, DecodedReturn } from '@aztec/foundation/abi'; import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { NodeInfo } from '@aztec/types/interfaces'; @@ -13,6 +13,10 @@ import { TxEffect } from '../tx_effect.js'; import { TxExecutionRequest } from '../tx_execution_request.js'; import { SyncStatus } from './sync-status.js'; +export class Vue { + constructor(public tx: Tx, public rv?: DecodedReturn) {} +} + // docs:start:pxe-interface /** * Private eXecution Environment (PXE) runs locally for each user, providing functionality for all the operations @@ -140,6 +144,7 @@ export interface PXE { * Also throws if simulatePublic is true and public simulation reverts. */ simulateTx(txRequest: TxExecutionRequest, simulatePublic: boolean): Promise; + simulateCall(txRequest: TxExecutionRequest, msgSender?: AztecAddress): Promise; /** * Sends a transaction to an Aztec node to be broadcasted to the network and mined. diff --git a/yarn-project/end-to-end/src/e2e_state_vars.test.ts b/yarn-project/end-to-end/src/e2e_state_vars.test.ts index 8aebff157522..c0afc2a4f09d 100644 --- a/yarn-project/end-to-end/src/e2e_state_vars.test.ts +++ b/yarn-project/end-to-end/src/e2e_state_vars.test.ts @@ -1,4 +1,5 @@ -import { Wallet } from '@aztec/aztec.js'; +import { DebugLogger, FunctionSelector, Wallet } from '@aztec/aztec.js'; +import { decodeFunctionSignature } from '@aztec/foundation/abi'; import { DocsExampleContract } from '@aztec/noir-contracts.js'; import { setup } from './fixtures/utils.js'; @@ -8,18 +9,43 @@ describe('e2e_state_vars', () => { let teardown: () => Promise; let contract: DocsExampleContract; + let logger: DebugLogger; const POINTS = 1n; const RANDOMNESS = 2n; beforeAll(async () => { - ({ teardown, wallet } = await setup()); + ({ teardown, wallet, logger } = await setup()); contract = await DocsExampleContract.deploy(wallet).send().deployed(); }, 25_000); afterAll(() => teardown()); describe('SharedImmutable', () => { + it.only('private read of uninitialized SharedImmutable', async () => { + await contract.methods.initialize_shared_immutable(1).send().wait(); + const returnsConstrained = await contract.methods.get_shared_immutable_constrained_private().viewConstrained(); + const returnsUnconstrained = await contract.methods.get_shared_immutable().view(); + + console.log(`Return values from private constrained:`, returnsConstrained); + console.log(`Return values from unconstrained:`, returnsUnconstrained); + + console.log(await contract.methods.get_message_sender().viewConstrained()); + + console.log(await contract.methods.multicall().viewConstrained()); + }); + + it('public read of uninitialized SharedImmutable', async () => { + DocsExampleContract.artifact.functions.forEach(fn => { + const sig = decodeFunctionSignature(fn.name, fn.parameters); + logger(`Function ${sig} and the selector: ${FunctionSelector.fromNameAndParameters(fn.name, fn.parameters)}`); + }); + + // await contract.methods.initialize_shared_immutable(1).send().wait(); + // console.log(await contract.methods.get_shared_immutable_constrained().viewConstrained()); + console.log(await contract.methods.get_message_sender_public().viewConstrained()); + }); + it('private read of uninitialized SharedImmutable', async () => { const s = await contract.methods.get_shared_immutable().view(); diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index de2c170513a3..d631e086892b 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -17,11 +17,13 @@ import { TxHash, TxL2Logs, TxReceipt, + Vue, isNoirCallStackUnresolved, } from '@aztec/circuit-types'; import { TxPXEProcessingStats } from '@aztec/circuit-types/stats'; import { AztecAddress, + CallContext, CallRequest, CompleteAddress, FunctionData, @@ -30,6 +32,7 @@ import { MAX_REVERTIBLE_PUBLIC_CALL_STACK_LENGTH_PER_TX, PartialAddress, PrivateKernelTailCircuitPublicInputs, + Proof, PublicCallRequest, computeContractClassId, getContractClassFromArtifact, @@ -410,6 +413,71 @@ export class PXEService implements PXE { }); } + public async simulateCall(txRequest: TxExecutionRequest, msgSender: AztecAddress | undefined = undefined) { + if (!txRequest.functionData.isPrivate) { + throw new Error(`Public entrypoints are not allowed`); + } + return await this.jobQueue.put(async () => { + const simulatePublic = msgSender === undefined; + const vue = await this.#simulateCall(txRequest, msgSender); + if (simulatePublic) { + const returns = await this.#simulatePublicCalls(vue.tx); + console.log(returns); + /*const t = returns[0]; + vue.rv = t && t[0];*/ + } + return vue; + }); + } + + async #simulateCall(txExecutionRequest: TxExecutionRequest, msgSender?: AztecAddress) { + // TODO - Pause syncing while simulating. + // Get values that allow us to reconstruct the block hash + + // todo: We should likely do something here for the public calls when we are starting with those as well :thinking: + + const sim = async (txRequest: TxExecutionRequest): Promise => { + const { contractAddress, functionArtifact, portalContract } = await this.#getSimulationParameters(txRequest); + try { + const result = await this.simulator.run( + txRequest, + functionArtifact, + contractAddress, + portalContract, + msgSender, + ); + return result; + } catch (err) { + if (err instanceof SimulationError) { + await this.#enrichSimulationError(err); + } + throw err; + } + }; + + const executionResult = await sim(txExecutionRequest); + + const kernelOracle = new KernelOracle(this.contractDataOracle, this.keyStore, this.node); + const kernelProver = new KernelProver(kernelOracle); + this.log(`Executing kernel prover...`); + const { proof, publicInputs } = await kernelProver.prove(txExecutionRequest.toTxRequest(), executionResult); + this.log( + `Needs setup: ${publicInputs.needsSetup}, needs app logic: ${publicInputs.needsAppLogic}, needs teardown: ${publicInputs.needsTeardown}`, + ); + + const encryptedLogs = new TxL2Logs(collectEncryptedLogs(executionResult)); + const unencryptedLogs = new TxL2Logs(collectUnencryptedLogs(executionResult)); + const enqueuedPublicFunctions = collectEnqueuedPublicFunctionCalls(executionResult); + + // HACK(#1639): Manually patches the ordering of the public call stack + // TODO(#757): Enforce proper ordering of enqueued public calls + await this.patchPublicCallStackOrdering(publicInputs, enqueuedPublicFunctions); + + const tx = new Tx(publicInputs, proof, encryptedLogs, unencryptedLogs, enqueuedPublicFunctions); + + return new Vue(tx, executionResult.returnValues); + } + public async sendTx(tx: Tx): Promise { const txHash = tx.getTxHash(); if (await this.node.getTxEffect(txHash)) { diff --git a/yarn-project/simulator/src/client/private_execution.ts b/yarn-project/simulator/src/client/private_execution.ts index 75bc079900b5..8fc956ec39e2 100644 --- a/yarn-project/simulator/src/client/private_execution.ts +++ b/yarn-project/simulator/src/client/private_execution.ts @@ -1,5 +1,5 @@ import { FunctionData, PrivateCallStackItem, PrivateCircuitPublicInputs } from '@aztec/circuits.js'; -import { FunctionArtifactWithDebugMetadata, decodeReturnValues } from '@aztec/foundation/abi'; +import { ABIType, FunctionArtifactWithDebugMetadata, decodeReturnValues } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -52,7 +52,13 @@ export async function executePrivateFunction( publicInputs.unencryptedLogPreimagesLength = new Fr(unencryptedLogs.getSerializedLength()); const callStackItem = new PrivateCallStackItem(contractAddress, functionData, publicInputs); - const returnValues = decodeReturnValues(artifact, publicInputs.returnValues); + + // Mocking the return type to be an array of 4 fields + // TODO: @LHerskind must be updated as we are progressing with the macros to get the information + const returnTypes: ABIType[] = [{ kind: 'array', length: 4, type: { kind: 'field' } }]; + const mockArtifact = { ...artifact, returnTypes }; + const returnValues = decodeReturnValues(mockArtifact, publicInputs.returnValues); + const noteHashReadRequestPartialWitnesses = context.getNoteHashReadRequestPartialWitnesses( publicInputs.noteHashReadRequests, );