Skip to content

Commit 6f46c70

Browse files
Thunkarbenesjan
authored andcommitted
feat: default to kernelless simulations (#21575)
We're confident enough in them after ensuring expiration_timestamp is set --------- Co-authored-by: Jan Beneš <janbenes1234@gmail.com>
1 parent 7976c0d commit 6f46c70

3 files changed

Lines changed: 71 additions & 49 deletions

File tree

yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
MAX_NULLIFIERS_PER_TX,
1818
MAX_NULLIFIER_READ_REQUESTS_PER_TX,
1919
MAX_PRIVATE_LOGS_PER_TX,
20+
MAX_TX_LIFETIME,
2021
PRIVATE_TX_L2_GAS_OVERHEAD,
2122
PUBLIC_TX_L2_GAS_OVERHEAD,
2223
TX_DA_GAS_OVERHEAD,
@@ -438,12 +439,23 @@ export async function generateSimulatedProvingResult(
438439

439440
let publicTeardownCallRequest;
440441

442+
// We set expiration timestamp to anchor_block_timestamp + MAX_TX_LIFETIME (24h) just like kernels do
443+
let expirationTimestamp =
444+
privateExecutionResult.entrypoint.publicInputs.anchorBlockHeader.globalVariables.timestamp +
445+
BigInt(MAX_TX_LIFETIME);
446+
441447
const executions = [privateExecutionResult.entrypoint];
442448

443449
while (executions.length !== 0) {
444450
const execution = executions.shift()!;
445451
executions.unshift(...execution!.nestedExecutionResults);
446452

453+
// Just like kernels we overwrite the default value if the call sets it.
454+
const callExpirationTimestamp = execution.publicInputs.expirationTimestamp;
455+
if (callExpirationTimestamp !== 0n && callExpirationTimestamp < expirationTimestamp) {
456+
expirationTimestamp = callExpirationTimestamp;
457+
}
458+
447459
const { contractAddress } = execution.publicInputs.callContext;
448460

449461
scopedNoteHashes.push(
@@ -667,7 +679,7 @@ export async function generateSimulatedProvingResult(
667679
}),
668680
),
669681
/*feePayer=*/ AztecAddress.zero(),
670-
/*expirationTimestamp=*/ 0n,
682+
/*expirationTimestamp=*/ expirationTimestamp,
671683
hasPublicCalls ? inputsForPublic : undefined,
672684
!hasPublicCalls ? inputsForRollup : undefined,
673685
);

yarn-project/pxe/src/pxe.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ export type SimulateTxOpts = {
107107
skipTxValidation?: boolean;
108108
/** If false, fees are enforced. */
109109
skipFeeEnforcement?: boolean;
110-
/** State overrides for the simulation, such as contract instances and artifacts. */
110+
/** If true, kernel logic is emulated in TS for simulation */
111+
skipKernels?: boolean;
112+
/** State overrides for the simulation, such as contract instances and artifacts. Requires skipKernels: true */
111113
overrides?: SimulationOverrides;
112114
/** Addresses whose private state and keys are accessible during private execution */
113115
scopes: AccessScopes;
@@ -887,7 +889,14 @@ export class PXE {
887889
*/
888890
public simulateTx(
889891
txRequest: TxExecutionRequest,
890-
{ simulatePublic, skipTxValidation = false, skipFeeEnforcement = false, overrides, scopes }: SimulateTxOpts,
892+
{
893+
simulatePublic,
894+
skipTxValidation = false,
895+
skipFeeEnforcement = false,
896+
skipKernels = true,
897+
overrides,
898+
scopes,
899+
}: SimulateTxOpts,
891900
): Promise<TxSimulationResult> {
892901
// We disable concurrent simulations since those might execute oracles which read and write to the PXE stores (e.g.
893902
// to the capsules), and we need to prevent concurrent runs from interfering with one another (e.g. attempting to
@@ -911,13 +920,15 @@ export class PXE {
911920
await this.blockStateSynchronizer.sync();
912921
const syncTime = syncTimer.ms();
913922

914-
const contractFunctionSimulator = this.#getSimulatorForTx(overrides);
915-
// Temporary: in case there are overrides, we have to skip the kernels or validations
916-
// will fail. Consider handing control to the user/wallet on whether they want to run them
917-
// or not.
918923
const overriddenContracts = overrides?.contracts ? new Set(Object.keys(overrides.contracts)) : undefined;
919924
const hasOverriddenContracts = overriddenContracts !== undefined && overriddenContracts.size > 0;
920-
const skipKernels = hasOverriddenContracts;
925+
926+
if (hasOverriddenContracts && !skipKernels) {
927+
throw new Error(
928+
'Simulating with overridden contracts is not compatible with kernel execution. Please set skipKernels to true when simulating with overridden contracts.',
929+
);
930+
}
931+
const contractFunctionSimulator = this.#getSimulatorForTx(overrides);
921932

922933
// Set overridden contracts on the sync service so it knows to skip syncing them
923934
if (hasOverriddenContracts) {

yarn-project/wallets/src/embedded/embedded_wallet.ts

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address';
1010
import { getContractInstanceFromInstantiationParams } from '@aztec/stdlib/contract';
1111
import type { AztecNode } from '@aztec/stdlib/interfaces/client';
1212
import { deriveSigningKey } from '@aztec/stdlib/keys';
13-
import { ExecutionPayload, type TxSimulationResult, mergeExecutionPayloads } from '@aztec/stdlib/tx';
13+
import {
14+
ExecutionPayload,
15+
SimulationOverrides,
16+
type TxSimulationResult,
17+
mergeExecutionPayloads,
18+
} from '@aztec/stdlib/tx';
1419
import { BaseWallet, type FeeOptions } from '@aztec/wallet-sdk/base-wallet';
1520

1621
import type { AccountContractsProvider } from './account-contract-providers/types.js';
@@ -84,10 +89,20 @@ export class EmbeddedWallet extends BaseWallet {
8489
from: AztecAddress,
8590
feeOptions: FeeOptions,
8691
scopes: AccessScopes,
87-
_skipTxValidation?: boolean,
88-
_skipFeeEnforcement?: boolean,
92+
skipTxValidation?: boolean,
93+
skipFeeEnforcement?: boolean,
8994
): Promise<TxSimulationResult> {
90-
const { account: fromAccount, instance, artifact } = await this.getFakeAccountDataFor(from);
95+
let overrides: SimulationOverrides | undefined;
96+
let fromAccount: Account;
97+
if (!from.equals(AztecAddress.ZERO)) {
98+
const { account, instance, artifact } = await this.getFakeAccountDataFor(from);
99+
fromAccount = account;
100+
overrides = {
101+
contracts: { [from.toString()]: { instance, artifact } },
102+
};
103+
} else {
104+
fromAccount = await this.getAccountFromAddress(from);
105+
}
91106

92107
const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload();
93108
const executionOptions: DefaultAccountEntrypointOptions = {
@@ -107,49 +122,33 @@ export class EmbeddedWallet extends BaseWallet {
107122
);
108123
return this.pxe.simulateTx(txRequest, {
109124
simulatePublic: true,
110-
skipFeeEnforcement: true,
111-
skipTxValidation: true,
112-
overrides: {
113-
contracts: { [from.toString()]: { instance, artifact } },
114-
},
125+
skipFeeEnforcement,
126+
skipTxValidation,
127+
overrides,
115128
scopes,
116129
});
117130
}
118131

119132
private async getFakeAccountDataFor(address: AztecAddress) {
120-
// While we have the convention of "Zero address means no auth", and also
121-
// we don't have a way to trigger kernelless simulations without overrides,
122-
// we need to explicitly handle the zero address case here by
123-
// returning the actual multicall contract instead of trying to create a stub account for it.
124-
if (!address.equals(AztecAddress.ZERO)) {
125-
const originalAccount = await this.getAccountFromAddress(address);
126-
if (originalAccount instanceof SignerlessAccount) {
127-
throw new Error(`Cannot create fake account data for SignerlessAccount at address: ${address}`);
128-
}
129-
const originalAddress = (originalAccount as Account).getCompleteAddress();
130-
const contractInstance = await this.pxe.getContractInstance(originalAddress.address);
131-
if (!contractInstance) {
132-
throw new Error(`No contract instance found for address: ${originalAddress.address}`);
133-
}
134-
const stubAccount = await this.accountContracts.createStubAccount(originalAddress);
135-
const stubArtifact = await this.accountContracts.getStubAccountContractArtifact();
136-
const instance = await getContractInstanceFromInstantiationParams(stubArtifact, {
137-
salt: Fr.random(),
138-
});
139-
return {
140-
account: stubAccount,
141-
instance,
142-
artifact: stubArtifact,
143-
};
144-
} else {
145-
const { instance, artifact } = await this.accountContracts.getMulticallContract();
146-
const account = new SignerlessAccount();
147-
return {
148-
instance,
149-
account,
150-
artifact,
151-
};
133+
const originalAccount = await this.getAccountFromAddress(address);
134+
if (originalAccount instanceof SignerlessAccount) {
135+
throw new Error(`Cannot create fake account data for SignerlessAccount at address: ${address}`);
152136
}
137+
const originalAddress = (originalAccount as Account).getCompleteAddress();
138+
const contractInstance = await this.pxe.getContractInstance(originalAddress.address);
139+
if (!contractInstance) {
140+
throw new Error(`No contract instance found for address: ${originalAddress.address}`);
141+
}
142+
const stubAccount = await this.accountContracts.createStubAccount(originalAddress);
143+
const stubArtifact = await this.accountContracts.getStubAccountContractArtifact();
144+
const instance = await getContractInstanceFromInstantiationParams(stubArtifact, {
145+
salt: Fr.random(),
146+
});
147+
return {
148+
account: stubAccount,
149+
instance,
150+
artifact: stubArtifact,
151+
};
153152
}
154153

155154
protected async createAccountInternal(

0 commit comments

Comments
 (0)