Skip to content

Commit 4ee69ac

Browse files
authored
fix: Split stores per component and split merkle tree operations (#8299)
This PR packs two changesets: 1. Spins off a `MerkleTreeAdminOperations` from `MerkleTreeOperations`, to have an interface that does not expose methods that commit changes to the underlying store. World state now exposes a method to get an `ephemeralFork` without these operations, that can be used for answering public simulation requests. Note that we do not yet enforce that no changes go into the store, that requires more changes to have "readonly" versions of the trees and store all the way down. 2. Moves creation of the underlying data store to the factory of each component, so each creates a new db as needed, instead of sharing a single one. This allows to keep separate db files for p2p, archive, and world state. As a bonus, it makes forking world state cheaper.
1 parent 157dd11 commit 4ee69ac

File tree

25 files changed

+187
-180
lines changed

25 files changed

+187
-180
lines changed

yarn-project/archiver/src/factory.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { type AztecKVStore } from '@aztec/kv-store';
1+
import { createDebugLogger } from '@aztec/foundation/log';
2+
import { createStore } from '@aztec/kv-store/utils';
23
import { type TelemetryClient } from '@aztec/telemetry-client';
34
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
45

@@ -7,14 +8,13 @@ import { type ArchiverConfig } from './archiver/config.js';
78
import { KVArchiverDataStore } from './archiver/index.js';
89
import { createArchiverClient } from './rpc/archiver_client.js';
910

10-
export function createArchiver(
11+
export async function createArchiver(
1112
config: ArchiverConfig,
12-
store: AztecKVStore,
1313
telemetry: TelemetryClient = new NoopTelemetryClient(),
1414
opts: { blockUntilSync: boolean } = { blockUntilSync: true },
1515
) {
1616
if (!config.archiverUrl) {
17-
// first create and sync the archiver
17+
const store = await createStore('archiver', config, createDebugLogger('aztec:archiver:lmdb'));
1818
const archiverStore = new KVArchiverDataStore(store, config.maxLogs);
1919
return Archiver.createAndSync(config, archiverStore, telemetry, opts.blockUntilSync);
2020
} else {

yarn-project/aztec-node/src/aztec-node/server.test.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import {
44
type L1ToL2MessageSource,
55
type L2BlockSource,
66
type L2LogsSource,
7+
type MerkleTreeAdminOperations,
78
MerkleTreeId,
8-
type MerkleTreeOperations,
99
mockTxForRollup,
1010
} from '@aztec/circuit-types';
1111
import { AztecAddress, EthAddress, Fr, GasFees, GlobalVariables, MaxBlockNumber } from '@aztec/circuits.js';
12-
import { type AztecLmdbStore } from '@aztec/kv-store/lmdb';
1312
import { type P2P } from '@aztec/p2p';
1413
import { type GlobalVariableBuilder } from '@aztec/sequencer-client';
1514
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
@@ -24,7 +23,7 @@ import { AztecNodeService } from './server.js';
2423
describe('aztec node', () => {
2524
let p2p: MockProxy<P2P>;
2625
let globalVariablesBuilder: MockProxy<GlobalVariableBuilder>;
27-
let merkleTreeOps: MockProxy<MerkleTreeOperations>;
26+
let merkleTreeOps: MockProxy<MerkleTreeAdminOperations>;
2827

2928
let lastBlockNumber: number;
3029

@@ -42,7 +41,7 @@ describe('aztec node', () => {
4241
p2p = mock<P2P>();
4342

4443
globalVariablesBuilder = mock<GlobalVariableBuilder>();
45-
merkleTreeOps = mock<MerkleTreeOperations>();
44+
merkleTreeOps = mock<MerkleTreeAdminOperations>();
4645

4746
const worldState = mock<WorldStateSynchronizer>({
4847
getLatest: () => merkleTreeOps,
@@ -59,8 +58,6 @@ describe('aztec node', () => {
5958
// all txs use the same allowed FPC class
6059
const contractSource = mock<ContractDataSource>();
6160

62-
const store = mock<AztecLmdbStore>();
63-
6461
const aztecNodeConfig: AztecNodeConfig = getConfigEnvVars();
6562

6663
node = new AztecNodeService(
@@ -86,7 +83,6 @@ describe('aztec node', () => {
8683
31337,
8784
1,
8885
globalVariablesBuilder,
89-
store,
9086
new TestCircuitVerifier(),
9187
new NoopTelemetryClient(),
9288
);

yarn-project/aztec-node/src/aztec-node/server.ts

+7-24
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,9 @@ import { AztecAddress } from '@aztec/foundation/aztec-address';
5050
import { padArrayEnd } from '@aztec/foundation/collection';
5151
import { createDebugLogger } from '@aztec/foundation/log';
5252
import { Timer } from '@aztec/foundation/timer';
53-
import { type AztecKVStore } from '@aztec/kv-store';
54-
import { createStore, openTmpStore } from '@aztec/kv-store/utils';
53+
import { openTmpStore } from '@aztec/kv-store/utils';
5554
import { SHA256Trunc, StandardTree, UnbalancedTree } from '@aztec/merkle-tree';
56-
import { AztecKVTxPool, InMemoryAttestationPool, type P2P, createP2PClient } from '@aztec/p2p';
55+
import { InMemoryAttestationPool, type P2P, createP2PClient } from '@aztec/p2p';
5756
import { getCanonicalClassRegisterer } from '@aztec/protocol-contracts/class-registerer';
5857
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
5958
import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer';
@@ -77,7 +76,7 @@ import {
7776
type ProtocolContractAddresses,
7877
} from '@aztec/types/contracts';
7978
import { createValidatorClient } from '@aztec/validator-client';
80-
import { MerkleTrees, type WorldStateSynchronizer, createWorldStateSynchronizer } from '@aztec/world-state';
79+
import { type WorldStateSynchronizer, createWorldStateSynchronizer } from '@aztec/world-state';
8180

8281
import { type AztecNodeConfig, getPackageInfo } from './config.js';
8382
import { NodeMetrics } from './node_metrics.js';
@@ -104,7 +103,6 @@ export class AztecNodeService implements AztecNode {
104103
protected readonly l1ChainId: number,
105104
protected readonly version: number,
106105
protected readonly globalVariableBuilder: GlobalVariableBuilder,
107-
protected readonly merkleTreesDb: AztecKVStore,
108106
private proofVerifier: ClientProtocolCircuitVerifier,
109107
private telemetry: TelemetryClient,
110108
private log = createDebugLogger('aztec:node'),
@@ -131,7 +129,6 @@ export class AztecNodeService implements AztecNode {
131129
config: AztecNodeConfig,
132130
telemetry?: TelemetryClient,
133131
log = createDebugLogger('aztec:node'),
134-
storeLog = createDebugLogger('aztec:node:lmdb'),
135132
): Promise<AztecNodeService> {
136133
telemetry ??= new NoopTelemetryClient();
137134
const ethereumChain = createEthereumChain(config.l1RpcUrl, config.l1ChainId);
@@ -142,25 +139,17 @@ export class AztecNodeService implements AztecNode {
142139
);
143140
}
144141

145-
const store = await createStore(config, config.l1Contracts.rollupAddress, storeLog);
146-
147-
const archiver = await createArchiver(config, store, telemetry, { blockUntilSync: true });
142+
const archiver = await createArchiver(config, telemetry, { blockUntilSync: true });
148143

149144
// we identify the P2P transaction protocol by using the rollup contract address.
150145
// this may well change in future
151146
config.transactionProtocol = `/aztec/tx/${config.l1Contracts.rollupAddress.toString()}`;
152147

153148
// create the tx pool and the p2p client, which will need the l2 block source
154-
const p2pClient = await createP2PClient(
155-
config,
156-
store,
157-
new AztecKVTxPool(store, telemetry),
158-
new InMemoryAttestationPool(),
159-
archiver,
160-
);
149+
const p2pClient = await createP2PClient(config, new InMemoryAttestationPool(), archiver, telemetry);
161150

162151
// now create the merkle trees and the world state synchronizer
163-
const worldStateSynchronizer = await createWorldStateSynchronizer(config, store, archiver, telemetry);
152+
const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, telemetry);
164153

165154
// start both and wait for them to sync from the block source
166155
await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]);
@@ -199,7 +188,6 @@ export class AztecNodeService implements AztecNode {
199188
ethereumChain.chainInfo.id,
200189
config.version,
201190
new GlobalVariableBuilder(config),
202-
store,
203191
proofVerifier,
204192
telemetry,
205193
log,
@@ -726,13 +714,8 @@ export class AztecNodeService implements AztecNode {
726714
);
727715
const prevHeader = (await this.blockSource.getBlock(-1))?.header;
728716

729-
// Instantiate merkle trees so uncommitted updates by this simulation are local to it.
730-
// TODO we should be able to remove this after https://github.com/AztecProtocol/aztec-packages/issues/1869
731-
// So simulation of public functions doesn't affect the merkle trees.
732-
const merkleTrees = await MerkleTrees.new(this.merkleTreesDb, new NoopTelemetryClient(), this.log);
733-
734717
const publicProcessorFactory = new PublicProcessorFactory(
735-
merkleTrees.asLatest(),
718+
await this.worldStateSynchronizer.ephemeralFork(),
736719
this.contractDataSource,
737720
new WASMSimulator(),
738721
this.telemetry,

yarn-project/aztec/src/cli/cmds/start_archiver.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ export const startArchiver = async (options: any, signalHandlers: (() => Promise
2121
const archiverConfig = extractRelevantOptions<ArchiverConfig>(options, archiverConfigMappings, 'archiver');
2222

2323
const storeLog = createDebugLogger('aztec:archiver:lmdb');
24-
const rollupAddress = archiverConfig.l1Contracts.rollupAddress;
25-
const store = await createStore(archiverConfig, rollupAddress, storeLog);
24+
const store = await createStore('archiver', archiverConfig, storeLog);
2625
const archiverStore = new KVArchiverDataStore(store, archiverConfig.maxLogs);
2726

2827
const telemetry = await createAndStartTelemetryClient(getTelemetryClientConfig());

yarn-project/circuit-types/src/interfaces/merkle_tree_operations.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ type LeafTypes = {
8484

8585
export type MerkleTreeLeafType<ID extends MerkleTreeId> = LeafTypes[ID];
8686

87-
/**
88-
* Defines the interface for operations on a set of Merkle Trees.
89-
*/
87+
/** Defines the interface for operations on a set of Merkle Trees. */
9088
export interface MerkleTreeOperations {
9189
/**
9290
* Appends leaves to a given tree.
@@ -203,7 +201,10 @@ export interface MerkleTreeOperations {
203201
leaves: Buffer[],
204202
subtreeHeight: number,
205203
): Promise<BatchInsertionResult<TreeHeight, SubtreeSiblingPathHeight>>;
204+
}
206205

206+
/** Operations on merkle trees world state that can modify the underlying store. */
207+
export interface MerkleTreeAdminOperations extends MerkleTreeOperations {
207208
/**
208209
* Handles a single L2 block (i.e. Inserts the new note hashes into the merkle tree).
209210
* @param block - The L2 block to handle.

yarn-project/end-to-end/src/e2e_block_building.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ describe('e2e_block_building', () => {
339339
});
340340

341341
// Regression for https://github.com/AztecProtocol/aztec-packages/issues/8306
342-
it.skip('can simulate public txs while building a block', async () => {
342+
it('can simulate public txs while building a block', async () => {
343343
({
344344
teardown,
345345
pxe,
@@ -368,7 +368,7 @@ describe('e2e_block_building', () => {
368368
}
369369

370370
logger.info('Waiting for txs to be mined');
371-
await Promise.all(txs.map(tx => tx.wait()));
371+
await Promise.all(txs.map(tx => tx.wait({ proven: false, timeout: 600 })));
372372
});
373373
});
374374
});

yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts

-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
deployL1Contract,
1818
} from '@aztec/aztec.js';
1919
import { BBCircuitVerifier } from '@aztec/bb-prover';
20-
import { createStore } from '@aztec/kv-store/utils';
2120
import { RollupAbi } from '@aztec/l1-artifacts';
2221
import { TokenContract } from '@aztec/noir-contracts.js';
2322
import { type ProverNode, type ProverNodeConfig, createProverNode } from '@aztec/prover-node';
@@ -225,11 +224,8 @@ export class FullProverTest {
225224
// Creating temp store and archiver for fully proven prover node
226225

227226
this.logger.verbose('Starting archiver for new prover node');
228-
const store = await createStore({ dataDirectory: undefined }, this.l1Contracts.l1ContractAddresses.rollupAddress);
229-
230227
const archiver = await createArchiver(
231228
{ ...this.context.aztecNodeConfig, dataDirectory: undefined },
232-
store,
233229
new NoopTelemetryClient(),
234230
{ blockUntilSync: true },
235231
);

yarn-project/end-to-end/src/fixtures/snapshot_manager.ts

+3-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
type CompleteAddress,
99
type DebugLogger,
1010
type DeployL1Contracts,
11-
type EthAddress,
1211
EthCheatCodes,
1312
Fr,
1413
GrumpkinScalar,
@@ -23,7 +22,6 @@ import { asyncMap } from '@aztec/foundation/async-map';
2322
import { type Logger, createDebugLogger } from '@aztec/foundation/log';
2423
import { makeBackoff, retry } from '@aztec/foundation/retry';
2524
import { resolver, reviver } from '@aztec/foundation/serialize';
26-
import { createStore } from '@aztec/kv-store/utils';
2725
import { type ProverNode, type ProverNodeConfig, createProverNode } from '@aztec/prover-node';
2826
import { type PXEService, createPXEService, getPXEServiceConfig } from '@aztec/pxe';
2927
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
@@ -235,21 +233,13 @@ async function teardown(context: SubsystemsContext | undefined) {
235233
}
236234

237235
export async function createAndSyncProverNode(
238-
rollupAddress: EthAddress,
239236
proverNodePrivateKey: `0x${string}`,
240237
aztecNodeConfig: AztecNodeConfig,
241238
aztecNode: AztecNode,
242239
) {
243240
// Creating temp store and archiver for simulated prover node
244-
245-
const store = await createStore({ dataDirectory: undefined }, rollupAddress);
246-
247-
const archiver = await createArchiver(
248-
{ ...aztecNodeConfig, dataDirectory: undefined },
249-
store,
250-
new NoopTelemetryClient(),
251-
{ blockUntilSync: true },
252-
);
241+
const archiverConfig = { ...aztecNodeConfig, dataDirectory: undefined };
242+
const archiver = await createArchiver(archiverConfig, new NoopTelemetryClient(), { blockUntilSync: true });
253243

254244
// Prover node config is for simulated proofs
255245
const proverConfig: ProverNodeConfig = {
@@ -335,7 +325,6 @@ async function setupFromFresh(
335325

336326
logger.verbose('Creating and syncing a simulated prover node...');
337327
const proverNode = await createAndSyncProverNode(
338-
deployL1ContractsValues.l1ContractAddresses.rollupAddress,
339328
`0x${proverNodePrivateKey!.toString('hex')}`,
340329
aztecNodeConfig,
341330
aztecNode,
@@ -433,12 +422,7 @@ async function setupFromState(statePath: string, logger: Logger): Promise<Subsys
433422
const proverNodePrivateKey = getPrivateKeyFromIndex(2);
434423

435424
logger.verbose('Creating and syncing a simulated prover node...');
436-
const proverNode = await createAndSyncProverNode(
437-
aztecNodeConfig.l1Contracts.rollupAddress,
438-
`0x${proverNodePrivateKey!}`,
439-
aztecNodeConfig,
440-
aztecNode,
441-
);
425+
const proverNode = await createAndSyncProverNode(`0x${proverNodePrivateKey!}`, aztecNodeConfig, aztecNode);
442426

443427
logger.verbose('Creating pxe...');
444428
const pxeConfig = getPXEServiceConfig();

yarn-project/end-to-end/src/public-testnet/e2e_public_testnet_transfer.test.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,7 @@ describe(`deploys and transfers a private only token`, () => {
4444
? `0x${proverNodePrivateKey?.toString('hex')}`
4545
: proverConfig.publisherPrivateKey;
4646

47-
proverNode = await createAndSyncProverNode(
48-
config.l1Contracts.rollupAddress,
49-
proverConfig.publisherPrivateKey,
50-
config,
51-
aztecNode,
52-
);
47+
proverNode = await createAndSyncProverNode(proverConfig.publisherPrivateKey, config, aztecNode);
5348
}, 600_000);
5449

5550
afterEach(async () => {

yarn-project/kv-store/src/utils.ts

+17-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import { type EthAddress } from '@aztec/foundation/eth-address';
22
import { type Logger, createDebugLogger } from '@aztec/foundation/log';
33

4+
import { join } from 'path';
5+
46
import { type AztecKVStore } from './interfaces/store.js';
57
import { AztecLmdbStore } from './lmdb/store.js';
68

7-
export function createStore(
8-
config: { dataDirectory: string | undefined } | (string | undefined),
9-
rollupAddress: EthAddress,
10-
log: Logger = createDebugLogger('aztec:kv-store'),
11-
) {
12-
const dataDirectory = typeof config === 'string' ? config : config?.dataDirectory;
13-
log.info(dataDirectory ? `Creating data store at directory ${dataDirectory}` : 'Creating ephemeral data store');
14-
return initStoreForRollup(AztecLmdbStore.open(dataDirectory, false), rollupAddress, log);
9+
export type DataStoreConfig = { dataDirectory: string | undefined; l1Contracts: { rollupAddress: EthAddress } };
10+
11+
export function createStore(name: string, config: DataStoreConfig, log: Logger = createDebugLogger('aztec:kv-store')) {
12+
let { dataDirectory } = config;
13+
if (typeof dataDirectory !== 'undefined') {
14+
dataDirectory = join(dataDirectory, name);
15+
}
16+
17+
log.info(
18+
dataDirectory
19+
? `Creating ${name} data store at directory ${dataDirectory}`
20+
: `Creating ${name} ephemeral data store`,
21+
);
22+
return initStoreForRollup(AztecLmdbStore.open(dataDirectory, false), config.l1Contracts.rollupAddress, log);
1523
}
1624

1725
/**
@@ -21,7 +29,7 @@ export function createStore(
2129
* @param rollupAddress - The ETH address of the rollup contract
2230
* @returns A promise that resolves when the store is cleared, or rejects if the rollup address does not match
2331
*/
24-
export async function initStoreForRollup<T extends AztecKVStore>(
32+
async function initStoreForRollup<T extends AztecKVStore>(
2533
store: T,
2634
rollupAddress: EthAddress,
2735
log?: Logger,

yarn-project/p2p/src/client/index.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
import { type L2BlockSource } from '@aztec/circuit-types';
2+
import { createDebugLogger } from '@aztec/foundation/log';
23
import { type AztecKVStore } from '@aztec/kv-store';
4+
import { type DataStoreConfig, createStore } from '@aztec/kv-store/utils';
5+
import { type TelemetryClient } from '@aztec/telemetry-client';
6+
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
37

48
import { type AttestationPool } from '../attestation_pool/attestation_pool.js';
59
import { P2PClient } from '../client/p2p_client.js';
610
import { type P2PConfig } from '../config.js';
711
import { DiscV5Service } from '../service/discV5_service.js';
812
import { DummyP2PService } from '../service/dummy_service.js';
913
import { LibP2PService, createLibP2PPeerId } from '../service/index.js';
10-
import { type TxPool } from '../tx_pool/index.js';
14+
import { AztecKVTxPool, type TxPool } from '../tx_pool/index.js';
1115
import { getPublicIp, resolveAddressIfNecessary, splitAddressPort } from '../util.js';
1216

1317
export * from './p2p_client.js';
1418

1519
export const createP2PClient = async (
16-
config: P2PConfig,
17-
store: AztecKVStore,
18-
txPool: TxPool,
20+
config: P2PConfig & DataStoreConfig,
1921
attestationsPool: AttestationPool,
2022
l2BlockSource: L2BlockSource,
23+
telemetry: TelemetryClient = new NoopTelemetryClient(),
24+
deps: { txPool?: TxPool; store?: AztecKVStore } = {},
2125
) => {
26+
const store = deps.store ?? (await createStore('p2p', config, createDebugLogger('aztec:p2p:lmdb')));
27+
const txPool = deps.txPool ?? new AztecKVTxPool(store, telemetry);
28+
2229
let p2pService;
2330

2431
if (config.p2pEnabled) {

0 commit comments

Comments
 (0)