Skip to content

Commit b805903

Browse files
authored
feat!: add block hash to log store (#18907)
We want to expose some event metadata to users of Aztec.js (see: #16245). An important metadata attribute is block hash. Block number is not enough due to the possibliity of reorgs. Currently, in archiver nodes, logs are stored by block number, and the corresponding block hash is lost. This works at the archiver, because the archiver is designed to prune its stores and start over each time a reorg is detected. Users who download data from the node, however, have no reasonably efficient way of detecting whether the log data they downloaded is still valid unless they also get a block hash to pin the logs they get to. So we can bridge this gap in one of two ways: 1. Whenever the node gets a request for logs, it queries the block store to resolve block hashes from block numbers. This works because archiver provides strong guarantees that the data stored corresponds to only one chain. However, it it introduces more store reads 2. At the time of storing logs, we can also store the block hash corresponding to the block number at the moment of storing. this increments the size needed to store the logs of each indexed block by 1 Fr, but in exchange for that we can just unpack the block hash from the logs we're processing without needing extra reads from block store. It also makes block addition a bit more expensive since now we'll be potentially calculating one hash per block (although since `addBlocks` receives them and `hash()` is cached maybe it's not even the case?) This PR implements option 2, but if I'm hitting any no-no's please let me know Note that this PR bumps the ARCHIVER_DB_VERSION, since it modifies how we index public and contract class logs. Closes F-220
2 parents 52b6e7d + bf09a76 commit b805903

File tree

7 files changed

+210
-51
lines changed

7 files changed

+210
-51
lines changed

yarn-project/archiver/src/archiver/archiver_store_test_suite.ts

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
SerializableContractInstance,
2929
computePublicBytecodeCommitment,
3030
} from '@aztec/stdlib/contract';
31-
import { LogId, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
31+
import { ContractClassLog, LogId, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
3232
import { InboxLeaf } from '@aztec/stdlib/messaging';
3333
import {
3434
makeContractClassPublic,
@@ -905,13 +905,15 @@ export function describeArchiverDataStore(
905905
[
906906
expect.objectContaining({
907907
blockNumber: 2,
908+
blockHash: L2BlockHash.fromField(await blocks[2 - 1].block.hash()),
908909
log: makePrivateLog(tags[0]),
909910
isFromPublic: false,
910911
}),
911912
],
912913
[
913914
expect.objectContaining({
914915
blockNumber: 1,
916+
blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
915917
log: makePrivateLog(tags[1]),
916918
isFromPublic: false,
917919
}),
@@ -929,11 +931,13 @@ export function describeArchiverDataStore(
929931
[
930932
expect.objectContaining({
931933
blockNumber: 1,
934+
blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
932935
log: makePrivateLog(tags[0]),
933936
isFromPublic: false,
934937
}),
935938
expect.objectContaining({
936939
blockNumber: 1,
940+
blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
937941
log: makePublicLog(tags[0]),
938942
isFromPublic: true,
939943
}),
@@ -959,11 +963,13 @@ export function describeArchiverDataStore(
959963
[
960964
expect.objectContaining({
961965
blockNumber: 1,
966+
blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
962967
log: makePrivateLog(tags[0]),
963968
isFromPublic: false,
964969
}),
965970
expect.objectContaining({
966971
blockNumber: newBlockNumber,
972+
blockHash: L2BlockHash.fromField(await blocks[newBlockNumber - 1].block.hash()),
967973
log: newLog,
968974
isFromPublic: false,
969975
}),
@@ -983,6 +989,7 @@ export function describeArchiverDataStore(
983989
[
984990
expect.objectContaining({
985991
blockNumber: 1,
992+
blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
986993
log: makePrivateLog(tags[1]),
987994
isFromPublic: false,
988995
}),
@@ -1050,6 +1057,17 @@ export function describeArchiverDataStore(
10501057
}
10511058
});
10521059

1060+
it('returns block hash on public log ids', async () => {
1061+
const targetBlock = blocks[0].block;
1062+
const expectedBlockHash = L2BlockHash.fromField(await targetBlock.hash());
1063+
1064+
const logs = (await store.getPublicLogs({ fromBlock: targetBlock.number, toBlock: targetBlock.number + 1 }))
1065+
.logs;
1066+
1067+
expect(logs.length).toBeGreaterThan(0);
1068+
expect(logs.every(log => log.id.blockHash.equals(expectedBlockHash))).toBe(true);
1069+
});
1070+
10531071
it('"fromBlock" and "toBlock" filter params are respected', async () => {
10541072
// Set "fromBlock" and "toBlock"
10551073
const fromBlock = 3;
@@ -1092,8 +1110,14 @@ export function describeArchiverDataStore(
10921110
const targetBlockIndex = randomInt(numBlocks);
10931111
const targetTxIndex = randomInt(txsPerBlock);
10941112
const targetLogIndex = randomInt(numPublicLogs);
1113+
const targetBlockHash = L2BlockHash.fromField(await blocks[targetBlockIndex].block.hash());
10951114

1096-
const afterLog = new LogId(BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM), targetTxIndex, targetLogIndex);
1115+
const afterLog = new LogId(
1116+
BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM),
1117+
targetBlockHash,
1118+
targetTxIndex,
1119+
targetLogIndex,
1120+
);
10971121

10981122
const response = await store.getPublicLogs({ afterLog });
10991123
const logs = response.logs;
@@ -1115,7 +1139,7 @@ export function describeArchiverDataStore(
11151139
it('"txHash" filter param is ignored when "afterLog" is set', async () => {
11161140
// Get random txHash
11171141
const txHash = TxHash.random();
1118-
const afterLog = new LogId(BlockNumber(1), 0, 0);
1142+
const afterLog = new LogId(BlockNumber(1), L2BlockHash.random(), 0, 0);
11191143

11201144
const response = await store.getPublicLogs({ txHash, afterLog });
11211145
expect(response.logs.length).toBeGreaterThan(1);
@@ -1147,20 +1171,25 @@ export function describeArchiverDataStore(
11471171
await store.getPublicLogs({
11481172
fromBlock: BlockNumber(2),
11491173
toBlock: BlockNumber(5),
1150-
afterLog: new LogId(BlockNumber(4), 0, 0),
1174+
afterLog: new LogId(BlockNumber(4), L2BlockHash.random(), 0, 0),
11511175
})
11521176
).logs;
11531177
blockNumbers = new Set(logs.map(log => log.id.blockNumber));
11541178
expect(blockNumbers).toEqual(new Set([4]));
11551179

1156-
logs = (await store.getPublicLogs({ toBlock: BlockNumber(5), afterLog: new LogId(BlockNumber(5), 1, 0) })).logs;
1180+
logs = (
1181+
await store.getPublicLogs({
1182+
toBlock: BlockNumber(5),
1183+
afterLog: new LogId(BlockNumber(5), L2BlockHash.random(), 1, 0),
1184+
})
1185+
).logs;
11571186
expect(logs.length).toBe(0);
11581187

11591188
logs = (
11601189
await store.getPublicLogs({
11611190
fromBlock: BlockNumber(2),
11621191
toBlock: BlockNumber(5),
1163-
afterLog: new LogId(BlockNumber(100), 0, 0),
1192+
afterLog: new LogId(BlockNumber(100), L2BlockHash.random(), 0, 0),
11641193
})
11651194
).logs;
11661195
expect(logs.length).toBe(0);
@@ -1171,8 +1200,14 @@ export function describeArchiverDataStore(
11711200
const targetBlockIndex = randomInt(numBlocks);
11721201
const targetTxIndex = randomInt(txsPerBlock);
11731202
const targetLogIndex = randomInt(numPublicLogs);
1203+
const targetBlockHash = L2BlockHash.fromField(await blocks[targetBlockIndex].block.hash());
11741204

1175-
const afterLog = new LogId(BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM), targetTxIndex, targetLogIndex);
1205+
const afterLog = new LogId(
1206+
BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM),
1207+
targetBlockHash,
1208+
targetTxIndex,
1209+
targetLogIndex,
1210+
);
11761211

11771212
const response = await store.getPublicLogs({ afterLog, fromBlock: afterLog.blockNumber });
11781213
const logs = response.logs;
@@ -1192,6 +1227,40 @@ export function describeArchiverDataStore(
11921227
});
11931228
});
11941229

1230+
describe('getContractClassLogs', () => {
1231+
let targetBlock: L2Block;
1232+
let expectedContractClassLog: ContractClassLog;
1233+
1234+
beforeEach(async () => {
1235+
await store.addBlocks(blocks);
1236+
1237+
targetBlock = blocks[0].block;
1238+
expectedContractClassLog = await ContractClassLog.random();
1239+
targetBlock.body.txEffects.forEach((txEffect, index) => {
1240+
txEffect.contractClassLogs = index === 0 ? [expectedContractClassLog] : [];
1241+
});
1242+
1243+
await store.addLogs([targetBlock]);
1244+
});
1245+
1246+
it('returns block hash on contract class log ids', async () => {
1247+
const result = await store.getContractClassLogs({
1248+
fromBlock: targetBlock.number,
1249+
toBlock: targetBlock.number + 1,
1250+
});
1251+
1252+
expect(result.maxLogsHit).toBeFalsy();
1253+
expect(result.logs).toHaveLength(1);
1254+
1255+
const [{ id, log }] = result.logs;
1256+
const expectedBlockHash = L2BlockHash.fromField(await targetBlock.hash());
1257+
1258+
expect(id.blockHash.equals(expectedBlockHash)).toBe(true);
1259+
expect(id.blockNumber).toEqual(targetBlock.number);
1260+
expect(log).toEqual(expectedContractClassLog);
1261+
});
1262+
});
1263+
11951264
describe('pendingChainValidationStatus', () => {
11961265
it('should return undefined when no status is set', async () => {
11971266
const status = await store.getPendingChainValidationStatus();

yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { ContractInstanceStore } from './contract_instance_store.js';
3131
import { LogStore } from './log_store.js';
3232
import { MessageStore } from './message_store.js';
3333

34-
export const ARCHIVER_DB_VERSION = 4;
34+
export const ARCHIVER_DB_VERSION = 5;
3535
export const MAX_FUNCTION_SIGNATURES = 1000;
3636
export const MAX_FUNCTION_NAME_LEN = 256;
3737

0 commit comments

Comments
 (0)