Skip to content

Commit 69cd019

Browse files
author
Nico Chamo
committed
refactor(pxe): skip storage reads for never-updated contracts
1 parent 61de849 commit 69cd019

2 files changed

Lines changed: 88 additions & 5 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { PUBLIC_DATA_TREE_HEIGHT } from '@aztec/constants';
2+
import { Fr } from '@aztec/foundation/curves/bn254';
3+
import { SiblingPath } from '@aztec/foundation/trees';
4+
import type { KeyStore } from '@aztec/key-store';
5+
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
6+
import { AztecAddress } from '@aztec/stdlib/aztec-address';
7+
import { DelayedPublicMutableValuesWithHash } from '@aztec/stdlib/delayed-public-mutable';
8+
import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
9+
import type { AztecNode } from '@aztec/stdlib/interfaces/client';
10+
import { PublicDataTreeLeaf, PublicDataTreeLeafPreimage, PublicDataWitness } from '@aztec/stdlib/trees';
11+
import { BlockHeader } from '@aztec/stdlib/tx';
12+
13+
import { mock } from 'jest-mock-extended';
14+
15+
import type { ContractStore } from '../storage/contract_store/contract_store.js';
16+
import { PrivateKernelOracle } from './private_kernel_oracle.js';
17+
18+
describe('PrivateKernelOracle', () => {
19+
let oracle: PrivateKernelOracle;
20+
let node: ReturnType<typeof mock<AztecNode>>;
21+
22+
beforeEach(() => {
23+
node = mock<AztecNode>();
24+
oracle = new PrivateKernelOracle(mock<ContractStore>(), mock<KeyStore>(), node, BlockHeader.empty());
25+
});
26+
27+
describe('getUpdatedClassIdHints', () => {
28+
it('skips storage reads when contract class was never updated', async () => {
29+
const contractAddress = await AztecAddress.random();
30+
const hashLeafSlot = await getHashLeafSlot(contractAddress);
31+
32+
// Non-matching slot simulates a low-leaf witness (slot was never written)
33+
const unrelatedSlot = new Fr(hashLeafSlot.toBigInt() - 1n);
34+
node.getPublicDataWitness.mockResolvedValue(makeWitness(unrelatedSlot));
35+
36+
const result = await oracle.getUpdatedClassIdHints(contractAddress);
37+
38+
expect(result.updatedClassIdValues.isEmpty()).toBe(true);
39+
expect(node.getPublicStorageAt).not.toHaveBeenCalled();
40+
});
41+
42+
it('reads storage when contract class was updated', async () => {
43+
const contractAddress = await AztecAddress.random();
44+
const hashLeafSlot = await getHashLeafSlot(contractAddress);
45+
46+
// Matching slot means the contract class was updated
47+
node.getPublicDataWitness.mockResolvedValue(makeWitness(hashLeafSlot, Fr.random()));
48+
node.getPublicStorageAt.mockResolvedValue(new Fr(42));
49+
50+
const result = await oracle.getUpdatedClassIdHints(contractAddress);
51+
52+
expect(result.updatedClassIdValues.isEmpty()).toBe(false);
53+
expect(node.getPublicStorageAt).toHaveBeenCalled();
54+
});
55+
});
56+
57+
async function getHashLeafSlot(contractAddress: AztecAddress) {
58+
const { delayedPublicMutableHashSlot } =
59+
await DelayedPublicMutableValuesWithHash.getContractUpdateSlots(contractAddress);
60+
return computePublicDataTreeLeafSlot(
61+
ProtocolContractAddress.ContractInstanceRegistry,
62+
delayedPublicMutableHashSlot,
63+
);
64+
}
65+
66+
function makeWitness(slot: Fr, value: Fr = Fr.ZERO) {
67+
return new PublicDataWitness(
68+
0n,
69+
new PublicDataTreeLeafPreimage(new PublicDataTreeLeaf(slot, value), Fr.ZERO, 0n),
70+
SiblingPath.random(PUBLIC_DATA_TREE_HEIGHT),
71+
);
72+
}
73+
});

yarn-project/pxe/src/private_kernel/private_kernel_oracle.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { FUNCTION_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT, VK_TREE_HEIGHT } from '@aztec/constants';
1+
import {
2+
FUNCTION_TREE_HEIGHT,
3+
NOTE_HASH_TREE_HEIGHT,
4+
PUBLIC_DATA_TREE_HEIGHT,
5+
UPDATES_VALUE_SIZE,
6+
VK_TREE_HEIGHT,
7+
} from '@aztec/constants';
28
import type { Fr } from '@aztec/foundation/curves/bn254';
39
import type { GrumpkinScalar, Point } from '@aztec/foundation/curves/grumpkin';
410
import { MembershipWitness } from '@aztec/foundation/trees';
@@ -132,12 +138,16 @@ export class PrivateKernelOracle {
132138
throw new Error(`No public data tree witness found for ${hashLeafSlot}`);
133139
}
134140

141+
// In an indexed merkle tree, getPublicDataWitness returns a leaf whose slot matches our query
142+
// only if the slot has been written to. Otherwise, it returns the "low leaf" predecessor, whose
143+
// slot will differ. Most contracts are never updated, so we can skip the readFromTree call
144+
// (which triggers multiple RPC calls) and return empty values directly.
135145
const readStorage = (storageSlot: Fr) =>
136146
this.node.getPublicStorageAt(blockHash, ProtocolContractAddress.ContractInstanceRegistry, storageSlot);
137-
const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(
138-
delayedPublicMutableSlot,
139-
readStorage,
140-
);
147+
const slotExists = updatedClassIdWitness.leafPreimage.leaf.slot.equals(hashLeafSlot);
148+
const delayedPublicMutableValues = slotExists
149+
? await DelayedPublicMutableValues.readFromTree(delayedPublicMutableSlot, readStorage)
150+
: DelayedPublicMutableValues.empty(UPDATES_VALUE_SIZE);
141151

142152
return new UpdatedClassIdHints(
143153
new MembershipWitness(

0 commit comments

Comments
 (0)