Skip to content

Commit

Permalink
chore: Persist initial header in local store (#7555)
Browse files Browse the repository at this point in the history
The `buildInitialHeader` method from the merkle tree db just used the
current state reference to build the header and return it, so it was
only valid when called if the merkle tree db was still empty.

This PR fixes the issue by computing the initial header when `init` is
called, and persisting it to the local store, so it can be retrieved
even when loading an empty db.

---

An alternative approach would have been something in the lines of:

```diff
if (!fromDb) {
  const initialState = await this.getStateReference(true);
-  await this.#saveInitialStateReference(initialState);
+  this.initialState = initialState;
  await this.#updateArchive(this.getInitialHeader(), true);
+ } else {
+  const emptyTrees = MerkleTrees.new(openTmpStore()).init();
+  this.initialState = emptyTrees.getStateReference(true);
+ }
```

However, a downside of this approach is that, if the shape of the trees
state change at some point during the chain history, then we'd be
returning a wrong initial hash. Though, to be fair, a lot more things
would probably break.
  • Loading branch information
spalladino authored Jul 23, 2024
1 parent fad37a7 commit d65c692
Show file tree
Hide file tree
Showing 25 changed files with 88 additions and 152 deletions.
13 changes: 3 additions & 10 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,18 +689,11 @@ export class AztecNodeService implements AztecNode {
}

/**
* Returns the currently committed block header.
* Returns the currently committed block header, or the initial header if no blocks have been produced.
* @returns The current committed block header.
*/
public async getHeader(): Promise<Header> {
const block = await this.getBlock(-1);
if (block) {
return block.header;
}

// No block was not found so we build the initial header.
const committedDb = await this.#getWorldState('latest');
return await committedDb.buildInitialHeader();
return (await this.getBlock(-1))?.header ?? (await this.#getWorldState('latest')).getInitialHeader();
}

/**
Expand Down Expand Up @@ -733,7 +726,7 @@ export class AztecNodeService implements AztecNode {
new WASMSimulator(),
this.telemetry,
);
const processor = await publicProcessorFactory.create(prevHeader, newGlobalVariables);
const processor = publicProcessorFactory.create(prevHeader, newGlobalVariables);
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
const [processedTxs, failedTxs, returns] = await processor.process([tx]);
// REFACTOR: Consider returning the error/revert rather than throwing
Expand Down
21 changes: 13 additions & 8 deletions yarn-project/circuits.js/src/structs/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export class Header {
] as const;
}

static from(fields: FieldsOf<Header>) {
return new Header(...Header.getFields(fields));
}

getSize() {
return (
this.lastArchive.getSize() +
Expand Down Expand Up @@ -85,14 +89,15 @@ export class Header {
);
}

static empty(): Header {
return new Header(
AppendOnlyTreeSnapshot.zero(),
ContentCommitment.empty(),
StateReference.empty(),
GlobalVariables.empty(),
Fr.ZERO,
);
static empty(fields: Partial<FieldsOf<Header>> = {}): Header {
return Header.from({
lastArchive: AppendOnlyTreeSnapshot.zero(),
contentCommitment: ContentCommitment.empty(),
state: StateReference.empty(),
globalVariables: GlobalVariables.empty(),
totalFees: Fr.ZERO,
...fields,
});
}

isEmpty(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,18 +158,11 @@ describe('L1Publisher integration', () => {
coinbase = config.coinbase || EthAddress.random();
feeRecipient = config.feeRecipient || AztecAddress.random();

prevHeader = await builderDb.buildInitialHeader(false);
prevHeader = builderDb.getInitialHeader();
});

const makeEmptyProcessedTx = () => {
const tx = makeEmptyProcessedTxFromHistoricalTreeRoots(
prevHeader,
new Fr(chainId),
new Fr(config.version),
getVKTreeRoot(),
);
return tx;
};
const makeEmptyProcessedTx = () =>
makeEmptyProcessedTxFromHistoricalTreeRoots(prevHeader, new Fr(chainId), new Fr(config.version), getVKTreeRoot());

const makeBloatedProcessedTx = (seed = 0x1): ProcessedTx => {
const tx = mockTx(seed);
Expand Down
13 changes: 6 additions & 7 deletions yarn-project/prover-client/src/mocks/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ export async function getSimulationProvider(
return new WASMSimulator();
}

export const makeBloatedProcessedTx = async (builderDb: MerkleTreeOperations, seed = 0x1) => {
export const makeBloatedProcessedTx = (builderDb: MerkleTreeOperations, seed = 0x1) => {
seed *= MAX_NULLIFIERS_PER_TX; // Ensure no clashing given incremental seeds
const tx = mockTx(seed);
const kernelOutput = KernelCircuitPublicInputs.empty();
kernelOutput.constants.vkTreeRoot = getVKTreeRoot();
kernelOutput.constants.historicalHeader = await builderDb.buildInitialHeader();
kernelOutput.constants.historicalHeader = builderDb.getInitialHeader();
kernelOutput.end.publicDataUpdateRequests = makeTuple(
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
i => new PublicDataUpdateRequest(fr(i), fr(i + 10), i + 20),
Expand All @@ -127,8 +127,8 @@ export const makeBloatedProcessedTx = async (builderDb: MerkleTreeOperations, se
return processedTx;
};

export const makeEmptyProcessedTx = async (builderDb: MerkleTreeOperations, chainId: Fr, version: Fr) => {
const header = await builderDb.buildInitialHeader();
export const makeEmptyProcessedTx = (builderDb: MerkleTreeOperations, chainId: Fr, version: Fr) => {
const header = builderDb.getInitialHeader();
return makeEmptyProcessedTxFromHistoricalTreeRoots(header, chainId, version, getVKTreeRoot());
};

Expand Down Expand Up @@ -178,6 +178,5 @@ export const makeGlobals = (blockNumber: number) => {
);
};

export const makeEmptyProcessedTestTx = (builderDb: MerkleTreeOperations): Promise<ProcessedTx> => {
return makeEmptyProcessedTx(builderDb, Fr.ZERO, Fr.ZERO);
};
export const makeEmptyProcessedTestTx = (builderDb: MerkleTreeOperations): ProcessedTx =>
makeEmptyProcessedTx(builderDb, Fr.ZERO, Fr.ZERO);
17 changes: 3 additions & 14 deletions yarn-project/prover-client/src/orchestrator/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
type BaseRollupInputs,
Fr,
type GlobalVariables,
type Header,
type KernelCircuitPublicInputs,
L1_TO_L2_MSG_SUBTREE_HEIGHT,
L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH,
Expand Down Expand Up @@ -98,12 +97,7 @@ export class ProvingOrchestrator {

public readonly tracer: Tracer;

constructor(
private db: MerkleTreeOperations,
private prover: ServerCircuitProver,
telemetryClient: TelemetryClient,
private initialHeader?: Header,
) {
constructor(private db: MerkleTreeOperations, private prover: ServerCircuitProver, telemetryClient: TelemetryClient) {
this.tracer = telemetryClient.getTracer('ProvingOrchestrator');
}

Expand Down Expand Up @@ -131,11 +125,6 @@ export class ProvingOrchestrator {
globalVariables: GlobalVariables,
l1ToL2Messages: Fr[],
): Promise<ProvingTicket> {
// Create initial header if not done so yet
if (!this.initialHeader) {
this.initialHeader = await this.db.getInitialHeader();
}

if (!Number.isInteger(numTxs) || numTxs < 2) {
throw new Error(`Length of txs for the block should be at least two (got ${numTxs})`);
}
Expand Down Expand Up @@ -268,7 +257,7 @@ export class ProvingOrchestrator {
// base rollup inputs
// Then enqueue the proving of all the transactions
const unprovenPaddingTx = makeEmptyProcessedTx(
this.initialHeader ?? (await this.db.getInitialHeader()),
this.db.getInitialHeader(),
this.provingState.globalVariables.chainId,
this.provingState.globalVariables.version,
getVKTreeRoot(),
Expand Down Expand Up @@ -564,7 +553,7 @@ export class ProvingOrchestrator {

const getBaseInputsEmptyTx = async () => {
const inputs = {
header: await this.db.getInitialHeader(),
header: this.db.getInitialHeader(),
chainId: tx.data.constants.globalVariables.chainId,
version: tx.data.constants.globalVariables.version,
vkTreeRoot: tx.data.constants.vkTreeRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ describe('prover/orchestrator/errors', () => {

describe('errors', () => {
it('throws if adding too many transactions', async () => {
const txs = await Promise.all([
const txs = [
makeBloatedProcessedTx(context.actualDb, 1),
makeBloatedProcessedTx(context.actualDb, 2),
makeBloatedProcessedTx(context.actualDb, 3),
makeBloatedProcessedTx(context.actualDb, 4),
]);
];

const blockTicket = await context.orchestrator.startNewBlock(txs.length, context.globalVariables, []);

Expand All @@ -36,7 +36,7 @@ describe('prover/orchestrator/errors', () => {
}

await expect(
async () => await context.orchestrator.addNewTx(await makeEmptyProcessedTestTx(context.actualDb)),
async () => await context.orchestrator.addNewTx(makeEmptyProcessedTestTx(context.actualDb)),
).rejects.toThrow('Rollup not accepting further transactions');

const result = await blockTicket.provingPromise;
Expand All @@ -48,7 +48,7 @@ describe('prover/orchestrator/errors', () => {

it('throws if adding a transaction before start', async () => {
await expect(
async () => await context.orchestrator.addNewTx(await makeEmptyProcessedTestTx(context.actualDb)),
async () => await context.orchestrator.addNewTx(makeEmptyProcessedTestTx(context.actualDb)),
).rejects.toThrow(`Invalid proving state, call startNewBlock before adding transactions`);
});

Expand Down Expand Up @@ -94,7 +94,7 @@ describe('prover/orchestrator/errors', () => {
context.orchestrator.cancelBlock();

await expect(
async () => await context.orchestrator.addNewTx(await makeEmptyProcessedTestTx(context.actualDb)),
async () => await context.orchestrator.addNewTx(makeEmptyProcessedTestTx(context.actualDb)),
).rejects.toThrow('Rollup not accepting further transactions');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ describe('prover/orchestrator/failures', () => {
],
] as const)('handles a %s error', async (message: string, fn: () => void) => {
fn();
const txs = await Promise.all([
const txs = [
makeBloatedProcessedTx(context.actualDb, 1),
makeBloatedProcessedTx(context.actualDb, 2),
makeBloatedProcessedTx(context.actualDb, 3),
]);
];

const blockTicket = await orchestrator.startNewBlock(txs.length, context.globalVariables, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,9 @@ describe('prover/orchestrator/lifecycle', () => {

describe('lifecycle', () => {
it('cancels current block and switches to new ones', async () => {
const txs1 = await Promise.all([
makeBloatedProcessedTx(context.actualDb, 1),
makeBloatedProcessedTx(context.actualDb, 2),
]);
const txs1 = [makeBloatedProcessedTx(context.actualDb, 1), makeBloatedProcessedTx(context.actualDb, 2)];

const txs2 = await Promise.all([
makeBloatedProcessedTx(context.actualDb, 3),
makeBloatedProcessedTx(context.actualDb, 4),
]);
const txs2 = [makeBloatedProcessedTx(context.actualDb, 3), makeBloatedProcessedTx(context.actualDb, 4)];

const globals1: GlobalVariables = makeGlobals(100);
const globals2: GlobalVariables = makeGlobals(101);
Expand Down Expand Up @@ -79,15 +73,8 @@ describe('prover/orchestrator/lifecycle', () => {
});

it('automatically cancels an incomplete block when starting a new one', async () => {
const txs1 = await Promise.all([
makeBloatedProcessedTx(context.actualDb, 1),
makeBloatedProcessedTx(context.actualDb, 2),
]);

const txs2 = await Promise.all([
makeBloatedProcessedTx(context.actualDb, 3),
makeBloatedProcessedTx(context.actualDb, 4),
]);
const txs1 = [makeBloatedProcessedTx(context.actualDb, 1), makeBloatedProcessedTx(context.actualDb, 2)];
const txs2 = [makeBloatedProcessedTx(context.actualDb, 3), makeBloatedProcessedTx(context.actualDb, 4)];

const globals1: GlobalVariables = makeGlobals(100);
const globals2: GlobalVariables = makeGlobals(101);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ describe('prover/orchestrator/mixed-blocks', () => {

describe('blocks', () => {
it('builds an unbalanced L2 block', async () => {
const txs = await Promise.all([
const txs = [
makeBloatedProcessedTx(context.actualDb, 1),
makeBloatedProcessedTx(context.actualDb, 2),
makeBloatedProcessedTx(context.actualDb, 3),
]);
];

const l1ToL2Messages = range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 1 + 0x400).map(fr);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ describe('prover/orchestrator/mixed-blocks', () => {

describe('blocks', () => {
it.each([2, 4, 5, 8] as const)('builds an L2 block with %i bloated txs', async (totalCount: number) => {
const txs = [
...(await Promise.all(times(totalCount, (i: number) => makeBloatedProcessedTx(context.actualDb, i)))),
];
const txs = times(totalCount, (i: number) => makeBloatedProcessedTx(context.actualDb, i));

const l1ToL2Messages = range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 1 + 0x400).map(fr);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('prover/orchestrator/public-functions', () => {
}),
);
for (const tx of txs) {
tx.data.constants.historicalHeader = await context.actualDb.buildInitialHeader();
tx.data.constants.historicalHeader = context.actualDb.getInitialHeader();
tx.data.constants.vkTreeRoot = getVKTreeRoot();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ describe('prover/orchestrator/multi-block', () => {
describe('multiple blocks', () => {
it('builds multiple blocks in sequence', async () => {
const numBlocks = 5;
let header = await context.actualDb.buildInitialHeader();
let header = context.actualDb.getInitialHeader();

for (let i = 0; i < numBlocks; i++) {
const tx = await makeBloatedProcessedTx(context.actualDb, i + 1);
const tx = makeBloatedProcessedTx(context.actualDb, i + 1);
tx.data.constants.historicalHeader = header;
tx.data.constants.vkTreeRoot = getVKTreeRoot();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('prover/orchestrator/public-functions', () => {
numberOfNonRevertiblePublicCallRequests,
numberOfRevertiblePublicCallRequests,
});
tx.data.constants.historicalHeader = await context.actualDb.buildInitialHeader();
tx.data.constants.historicalHeader = context.actualDb.getInitialHeader();
tx.data.constants.vkTreeRoot = getVKTreeRoot();

const [processed, _] = await context.processPublicFunctions([tx], 1, undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('prover/orchestrator/blocks', () => {
});

it('builds a block with 1 transaction', async () => {
const txs = await Promise.all([makeBloatedProcessedTx(context.actualDb, 1)]);
const txs = [makeBloatedProcessedTx(context.actualDb, 1)];

await updateExpectedTreesFromTxs(expectsDb, txs);

Expand All @@ -61,12 +61,12 @@ describe('prover/orchestrator/blocks', () => {
});

it('builds a block concurrently with transaction simulation', async () => {
const txs = await Promise.all([
const txs = [
makeBloatedProcessedTx(context.actualDb, 1),
makeBloatedProcessedTx(context.actualDb, 2),
makeBloatedProcessedTx(context.actualDb, 3),
makeBloatedProcessedTx(context.actualDb, 4),
]);
];

const l1ToL2Messages = range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 1 + 0x400).map(fr);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('prover/bb_prover/base-rollup', () => {
});

it('proves the base rollup', async () => {
const header = await context.actualDb.buildInitialHeader();
const header = context.actualDb.getInitialHeader();
const chainId = context.globalVariables.chainId;
const version = context.globalVariables.version;
const vkTreeRoot = getVKTreeRoot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('prover/bb_prover/full-rollup', () => {
const nonEmptyTxs = 0;

logger.info(`Proving a private-only full rollup with ${nonEmptyTxs}/${totalTxs} non-empty transactions`);
const initialHeader = await context.actualDb.buildInitialHeader();
const initialHeader = context.actualDb.getInitialHeader();
const txs = times(nonEmptyTxs, (i: number) => {
const tx = mockTx(1000 * (i + 1), {
numberOfNonRevertiblePublicCallRequests: 0,
Expand Down Expand Up @@ -79,7 +79,7 @@ describe('prover/bb_prover/full-rollup', () => {
}),
);
for (const tx of txs) {
tx.data.constants.historicalHeader = await context.actualDb.buildInitialHeader();
tx.data.constants.historicalHeader = context.actualDb.getInitialHeader();
}

const l1ToL2Messages = makeTuple<Fr, typeof NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP>(
Expand Down
Loading

0 comments on commit d65c692

Please sign in to comment.