@aztec/aztec-node 0.87.1 → 0.87.2-nightly.20250524

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAE9F,qBAAa,aAAa;IAMtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IANhB,gBAAuB,cAAc,KAAK;IAE1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuC;gBAGjD,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE;IAKpC,gBAAgB;IAIV,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC;YAUhG,0BAA0B;IAU3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC;YAQrE,UAAU;IAKxB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,gBAAgB;CAgBzB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sentinel/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,0BAA0B,CAAC;AAIlC,qBAAa,aAAa;IAWtB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAXhB,gBAAuB,cAAc,KAAK;IAG1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAIlE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;gBAGvD,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE;IAMpC,gBAAgB;IAIV,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,0BAA0B;IAW9E,oBAAoB,CAAC,GAAG,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;YAKpG,sCAAsC;IA6BvC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC;YAUhG,0BAA0B;IAU3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC;YAQrE,UAAU;IAKxB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,gBAAgB;CAgBzB"}
@@ -1,17 +1,65 @@
1
1
  import { BufferReader, numToUInt8, numToUInt32BE, serializeToBuffer } from '@aztec/foundation/serialize';
2
+ import { isAddress } from 'viem';
2
3
  export class SentinelStore {
3
4
  store;
4
5
  config;
5
- static SCHEMA_VERSION = 1;
6
- map;
6
+ static SCHEMA_VERSION = 2;
7
+ // a map from validator address to their ValidatorStatusHistory
8
+ historyMap;
9
+ // a map from validator address to their historical proven epoch performance
10
+ // e.g. { validator: [{ epoch: 1, missed: 1, total: 10 }, { epoch: 2, missed: 3, total: 7 }, ...] }
11
+ provenMap;
7
12
  constructor(store, config){
8
13
  this.store = store;
9
14
  this.config = config;
10
- this.map = store.openMap('sentinel-validator-status');
15
+ this.historyMap = store.openMap('sentinel-validator-status');
16
+ this.provenMap = store.openMap('sentinel-validator-proven');
11
17
  }
12
18
  getHistoryLength() {
13
19
  return this.config.historyLength;
14
20
  }
21
+ async updateProvenPerformance(epoch, performance) {
22
+ await this.store.transactionAsync(async ()=>{
23
+ for (const [who, { missed, total }] of Object.entries(performance)){
24
+ if (!isAddress(who)) {
25
+ continue;
26
+ }
27
+ await this.pushValidatorProvenPerformanceForEpoch({
28
+ who,
29
+ missed,
30
+ total,
31
+ epoch
32
+ });
33
+ }
34
+ });
35
+ }
36
+ async getProvenPerformance(who) {
37
+ const currentPerformanceBuffer = await this.provenMap.getAsync(who);
38
+ return currentPerformanceBuffer ? this.deserializePerformance(currentPerformanceBuffer) : [];
39
+ }
40
+ async pushValidatorProvenPerformanceForEpoch({ who, missed, total, epoch }) {
41
+ const currentPerformance = await this.getProvenPerformance(who);
42
+ const existingIndex = currentPerformance.findIndex((p)=>p.epoch === epoch);
43
+ if (existingIndex !== -1) {
44
+ currentPerformance[existingIndex] = {
45
+ missed,
46
+ total,
47
+ epoch
48
+ };
49
+ } else {
50
+ currentPerformance.push({
51
+ missed,
52
+ total,
53
+ epoch
54
+ });
55
+ }
56
+ // This should be sorted by epoch, but just in case.
57
+ // Since we keep the size small, this is not a big deal.
58
+ currentPerformance.sort((a, b)=>Number(a.epoch - b.epoch));
59
+ // keep the most recent `historyLength` entries.
60
+ const performanceToKeep = currentPerformance.slice(-this.config.historyLength);
61
+ await this.provenMap.set(who, this.serializePerformance(performanceToKeep));
62
+ }
15
63
  async updateValidators(slot, statuses) {
16
64
  await this.store.transactionAsync(async ()=>{
17
65
  for (const [who, status] of Object.entries(statuses)){
@@ -30,19 +78,38 @@ export class SentinelStore {
30
78
  status
31
79
  }
32
80
  ].slice(-this.config.historyLength);
33
- await this.map.set(who, this.serializeHistory(newHistory));
81
+ await this.historyMap.set(who, this.serializeHistory(newHistory));
34
82
  }
35
83
  async getHistories() {
36
84
  const histories = {};
37
- for await (const [address, history] of this.map.entriesAsync()){
85
+ for await (const [address, history] of this.historyMap.entriesAsync()){
38
86
  histories[address] = this.deserializeHistory(history);
39
87
  }
40
88
  return histories;
41
89
  }
42
90
  async getHistory(address) {
43
- const data = await this.map.getAsync(address);
91
+ const data = await this.historyMap.getAsync(address);
44
92
  return data && this.deserializeHistory(data);
45
93
  }
94
+ serializePerformance(performance) {
95
+ return serializeToBuffer(performance.map((p)=>[
96
+ numToUInt32BE(Number(p.epoch)),
97
+ numToUInt32BE(p.missed),
98
+ numToUInt32BE(p.total)
99
+ ]));
100
+ }
101
+ deserializePerformance(buffer) {
102
+ const reader = new BufferReader(buffer);
103
+ const performance = [];
104
+ while(!reader.isEmpty()){
105
+ performance.push({
106
+ epoch: BigInt(reader.readNumber()),
107
+ missed: reader.readNumber(),
108
+ total: reader.readNumber()
109
+ });
110
+ }
111
+ return performance;
112
+ }
46
113
  serializeHistory(history) {
47
114
  return serializeToBuffer(history.map((h)=>[
48
115
  numToUInt32BE(Number(h.slot)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/aztec-node",
3
- "version": "0.87.1",
3
+ "version": "0.87.2-nightly.20250524",
4
4
  "main": "dest/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -61,27 +61,28 @@
61
61
  ]
62
62
  },
63
63
  "dependencies": {
64
- "@aztec/archiver": "0.87.1",
65
- "@aztec/bb-prover": "0.87.1",
66
- "@aztec/blob-sink": "0.87.1",
67
- "@aztec/constants": "0.87.1",
68
- "@aztec/epoch-cache": "0.87.1",
69
- "@aztec/ethereum": "0.87.1",
70
- "@aztec/foundation": "0.87.1",
71
- "@aztec/kv-store": "0.87.1",
72
- "@aztec/l1-artifacts": "0.87.1",
73
- "@aztec/merkle-tree": "0.87.1",
74
- "@aztec/node-lib": "0.87.1",
75
- "@aztec/noir-protocol-circuits-types": "0.87.1",
76
- "@aztec/p2p": "0.87.1",
77
- "@aztec/protocol-contracts": "0.87.1",
78
- "@aztec/prover-client": "0.87.1",
79
- "@aztec/sequencer-client": "0.87.1",
80
- "@aztec/simulator": "0.87.1",
81
- "@aztec/stdlib": "0.87.1",
82
- "@aztec/telemetry-client": "0.87.1",
83
- "@aztec/validator-client": "0.87.1",
84
- "@aztec/world-state": "0.87.1",
64
+ "@aztec/archiver": "0.87.2-nightly.20250524",
65
+ "@aztec/bb-prover": "0.87.2-nightly.20250524",
66
+ "@aztec/blob-sink": "0.87.2-nightly.20250524",
67
+ "@aztec/constants": "0.87.2-nightly.20250524",
68
+ "@aztec/epoch-cache": "0.87.2-nightly.20250524",
69
+ "@aztec/ethereum": "0.87.2-nightly.20250524",
70
+ "@aztec/foundation": "0.87.2-nightly.20250524",
71
+ "@aztec/kv-store": "0.87.2-nightly.20250524",
72
+ "@aztec/l1-artifacts": "0.87.2-nightly.20250524",
73
+ "@aztec/merkle-tree": "0.87.2-nightly.20250524",
74
+ "@aztec/node-lib": "0.87.2-nightly.20250524",
75
+ "@aztec/noir-protocol-circuits-types": "0.87.2-nightly.20250524",
76
+ "@aztec/p2p": "0.87.2-nightly.20250524",
77
+ "@aztec/protocol-contracts": "0.87.2-nightly.20250524",
78
+ "@aztec/prover-client": "0.87.2-nightly.20250524",
79
+ "@aztec/sequencer-client": "0.87.2-nightly.20250524",
80
+ "@aztec/simulator": "0.87.2-nightly.20250524",
81
+ "@aztec/slasher": "0.87.2-nightly.20250524",
82
+ "@aztec/stdlib": "0.87.2-nightly.20250524",
83
+ "@aztec/telemetry-client": "0.87.2-nightly.20250524",
84
+ "@aztec/validator-client": "0.87.2-nightly.20250524",
85
+ "@aztec/world-state": "0.87.2-nightly.20250524",
85
86
  "koa": "^2.16.1",
86
87
  "koa-router": "^12.0.0",
87
88
  "tslib": "^2.4.0",
@@ -11,6 +11,7 @@ import { type SharedNodeConfig, sharedNodeConfigMappings } from '@aztec/node-lib
11
11
  import { type P2PConfig, p2pConfigMappings } from '@aztec/p2p/config';
12
12
  import { type ProverClientUserConfig, proverClientConfigMappings } from '@aztec/prover-client/config';
13
13
  import { type SequencerClientConfig, sequencerClientConfigMappings } from '@aztec/sequencer-client/config';
14
+ import { type SlasherConfig, slasherConfigMappings } from '@aztec/slasher';
14
15
  import { type NodeRPCConfig, nodeRpcConfigMappings } from '@aztec/stdlib/config';
15
16
  import { type ValidatorClientConfig, validatorClientConfigMappings } from '@aztec/validator-client/config';
16
17
  import { type WorldStateConfig, worldStateConfigMappings } from '@aztec/world-state/config';
@@ -33,7 +34,8 @@ export type AztecNodeConfig = ArchiverConfig &
33
34
  SentinelConfig &
34
35
  SharedNodeConfig &
35
36
  GenesisStateConfig &
36
- NodeRPCConfig & {
37
+ NodeRPCConfig &
38
+ SlasherConfig & {
37
39
  /** L1 contracts addresses */
38
40
  l1Contracts: L1ContractAddresses;
39
41
  /** Whether the validator is disabled for this node */
@@ -52,6 +54,7 @@ export const aztecNodeConfigMappings: ConfigMappingsType<AztecNodeConfig> = {
52
54
  ...sharedNodeConfigMappings,
53
55
  ...genesisStateConfigMappings,
54
56
  ...nodeRpcConfigMappings,
57
+ ...slasherConfigMappings,
55
58
  l1Contracts: {
56
59
  description: 'The deployed L1 contract addresses',
57
60
  nested: l1ContractAddressesMapping,
@@ -10,7 +10,13 @@ import {
10
10
  type PUBLIC_DATA_TREE_HEIGHT,
11
11
  } from '@aztec/constants';
12
12
  import { EpochCache } from '@aztec/epoch-cache';
13
- import { type L1ContractAddresses, RegistryContract, createEthereumChain } from '@aztec/ethereum';
13
+ import {
14
+ type L1ContractAddresses,
15
+ RegistryContract,
16
+ createEthereumChain,
17
+ createExtendedL1Client,
18
+ } from '@aztec/ethereum';
19
+ import { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
14
20
  import { compactArray } from '@aztec/foundation/collection';
15
21
  import { EthAddress } from '@aztec/foundation/eth-address';
16
22
  import { Fr } from '@aztec/foundation/fields';
@@ -20,7 +26,6 @@ import { SerialQueue } from '@aztec/foundation/queue';
20
26
  import { count } from '@aztec/foundation/string';
21
27
  import { DateProvider, Timer } from '@aztec/foundation/timer';
22
28
  import { SiblingPath } from '@aztec/foundation/trees';
23
- import type { AztecKVStore } from '@aztec/kv-store';
24
29
  import { openTmpStore } from '@aztec/kv-store/lmdb';
25
30
  import { RollupAbi } from '@aztec/l1-artifacts';
26
31
  import { SHA256Trunc, StandardTree, UnbalancedTree } from '@aztec/merkle-tree';
@@ -31,10 +36,10 @@ import {
31
36
  GlobalVariableBuilder,
32
37
  SequencerClient,
33
38
  type SequencerPublisher,
34
- createSlasherClient,
35
39
  createValidatorForAcceptingTxs,
36
40
  } from '@aztec/sequencer-client';
37
41
  import { PublicProcessorFactory } from '@aztec/simulator/server';
42
+ import { EpochPruneWatcher, SlasherClient, type Watcher } from '@aztec/slasher';
38
43
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
39
44
  import type { InBlock, L2Block, L2BlockNumber, L2BlockSource, PublishedL2Block } from '@aztec/stdlib/block';
40
45
  import type {
@@ -182,7 +187,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
182
187
 
183
188
  const publicClient = createPublicClient({
184
189
  chain: ethereumChain.chainInfo,
185
- transport: fallback(config.l1RpcUrls.map(url => http(url))),
190
+ transport: fallback(config.l1RpcUrls.map((url: string) => http(url))),
186
191
  pollingInterval: config.viemPollingIntervalMS,
187
192
  });
188
193
 
@@ -195,6 +200,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
195
200
  // Overwrite the passed in vars.
196
201
  config.l1Contracts = { ...config.l1Contracts, ...l1ContractsAddresses };
197
202
 
203
+ const l1Client = createExtendedL1Client(config.l1RpcUrls, config.publisherPrivateKey, ethereumChain.chainInfo);
204
+ const l1TxUtils = new L1TxUtilsWithBlobs(l1Client, log, config);
205
+
198
206
  const rollup = getContract({
199
207
  address: l1ContractsAddresses.rollupAddress.toString(),
200
208
  abi: RollupAbi,
@@ -248,8 +256,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
248
256
  // Start p2p. Note that it depends on world state to be running.
249
257
  await p2pClient.start();
250
258
 
251
- const slasherClient = createSlasherClient(config, archiver, telemetry);
252
- slasherClient.start();
259
+ const watchers: Watcher[] = [];
260
+
261
+ const validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
262
+ if (validatorsSentinel) {
263
+ watchers.push(validatorsSentinel);
264
+ }
265
+ const epochPruneWatcher = new EpochPruneWatcher(archiver, epochCache, config.slashPrunePenalty);
266
+ watchers.push(epochPruneWatcher);
267
+
268
+ const slasherClient = await SlasherClient.new(config, config.l1Contracts, l1TxUtils, watchers, dateProvider);
269
+ await slasherClient.start();
253
270
 
254
271
  const validatorClient = createValidatorClient(config, {
255
272
  p2pClient,
@@ -259,16 +276,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
259
276
  blockSource: archiver,
260
277
  });
261
278
 
262
- const validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
263
- await validatorsSentinel?.start();
264
-
265
279
  log.verbose(`All Aztec Node subsystems synced`);
266
280
 
267
281
  // now create the sequencer
268
282
  const sequencer = config.disableValidator
269
283
  ? undefined
270
284
  : await SequencerClient.new(config, {
285
+ // if deps were provided, they should override the defaults,
286
+ // or things that we created in this function
271
287
  ...deps,
288
+ epochCache,
289
+ l1TxUtils,
272
290
  validatorClient,
273
291
  p2pClient,
274
292
  worldStateSynchronizer,
@@ -529,7 +547,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
529
547
  public async stop() {
530
548
  this.log.info(`Stopping`);
531
549
  await this.txQueue.end();
532
- await this.validatorsSentinel?.stop();
550
+ // await this.validatorsSentinel?.stop(); <- The slasher client will stop this
533
551
  await this.sequencer?.stop();
534
552
  await this.p2pClient.stop();
535
553
  await this.worldStateSynchronizer.stop();
@@ -699,14 +717,20 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
699
717
  }
700
718
 
701
719
  /**
702
- * Returns the index of a l2ToL1Message in a ephemeral l2 to l1 data tree as well as its sibling path.
703
- * @remarks This tree is considered ephemeral because it is created on-demand by: taking all the l2ToL1 messages
704
- * in a single block, and then using them to make a variable depth append-only tree with these messages as leaves.
705
- * The tree is discarded immediately after calculating what we need from it.
720
+ * Returns the index and sibling path for a L2->L1 message in a block's message tree.
721
+ * @remarks The message tree is constructed on-demand by taking all L2->L1 messages in a block
722
+ * and creating a variable depth append-only tree with the messages as leaves.
723
+ * The tree is constructed in two layers:
724
+ * 1. Subtree - For each transaction in the block, a subtree is created containing all L2->L1 messages from that
725
+ * transaction.
726
+ * 2. Top tree - A tree containing the roots of all the subtrees as leaves
727
+ * The final path is constructed by concatenating the path in the subtree with the path in the top tree.
728
+ * When there is only one transaction in the block, the subtree itself becomes the block's L2->L1 message tree,
729
+ * and no top tree is needed. The out hash is the root of the the block's L2->L1 message tree.
706
730
  * TODO: Handle the case where two messages in the same tx have the same hash.
707
- * @param blockNumber - The block number at which to get the data.
708
- * @param l2ToL1Message - The l2ToL1Message get the index / sibling path for.
709
- * @returns A tuple of the index and the sibling path of the L2ToL1Message.
731
+ * @param blockNumber - Block number to get data from
732
+ * @param l2ToL1Message - Message to get index/path for
733
+ * @returns [index, siblingPath] for the message
710
734
  */
711
735
  public async getL2ToL1MessageMembershipWitness(
712
736
  blockNumber: L2BlockNumber,
@@ -715,70 +739,98 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
715
739
  const block = await this.blockSource.getBlock(blockNumber === 'latest' ? await this.getBlockNumber() : blockNumber);
716
740
 
717
741
  if (block === undefined) {
718
- throw new Error('Block is not defined');
742
+ throw new Error('Block not found in getL2ToL1MessageMembershipWitness');
719
743
  }
720
744
 
721
- const l2ToL1Messages = block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
745
+ const messagesPerTx = block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
722
746
 
723
- // Find index of message
724
- let indexOfMsgInSubtree = -1;
725
- const indexOfMsgTx = l2ToL1Messages.findIndex(msgs => {
726
- const idx = msgs.findIndex(msg => msg.equals(l2ToL1Message));
727
- indexOfMsgInSubtree = Math.max(indexOfMsgInSubtree, idx);
728
- return idx !== -1;
729
- });
747
+ // Find index of message in subtree and index of tx in a block
748
+ let messageIndexInTx = -1,
749
+ txIndex = -1;
750
+ {
751
+ txIndex = messagesPerTx.findIndex(messages => {
752
+ const idx = messages.findIndex(msg => msg.equals(l2ToL1Message));
753
+ messageIndexInTx = Math.max(messageIndexInTx, idx);
754
+ return idx !== -1;
755
+ });
756
+ }
730
757
 
731
- if (indexOfMsgTx === -1) {
758
+ if (txIndex === -1) {
732
759
  throw new Error('The L2ToL1Message you are trying to prove inclusion of does not exist');
733
760
  }
734
761
 
735
- const tempStores: AztecKVStore[] = [];
762
+ // Get the message path in subtree and message subtree height
763
+ let messagePathInSubtree: SiblingPath<number>;
764
+ let messageSubtreeHeight: number;
765
+ {
766
+ const subtreeStore = openTmpStore(true);
767
+ const txMessages = messagesPerTx[txIndex];
768
+ messageSubtreeHeight = txMessages.length <= 1 ? 1 : Math.ceil(Math.log2(txMessages.length));
769
+ const subtree = new StandardTree(
770
+ subtreeStore,
771
+ new SHA256Trunc(),
772
+ `subtree_${txIndex}`,
773
+ messageSubtreeHeight,
774
+ 0n,
775
+ Fr,
776
+ );
777
+ subtree.appendLeaves(txMessages);
778
+ messagePathInSubtree = await subtree.getSiblingPath(BigInt(messageIndexInTx), true);
779
+ await subtreeStore.delete();
780
+ }
736
781
 
737
- // Construct message subtrees
738
- const l2toL1Subtrees = l2ToL1Messages.map((msgs, i) => {
739
- const store = openTmpStore(true);
740
- tempStores.push(store);
741
- const treeHeight = msgs.length <= 1 ? 1 : Math.ceil(Math.log2(msgs.length));
742
- const tree = new StandardTree(store, new SHA256Trunc(), `temp_msgs_subtrees_${i}`, treeHeight, 0n, Fr);
743
- tree.appendLeaves(msgs);
744
- return tree;
745
- });
782
+ // If the number of txs is 1 we are dealing with a special case where the tx subtree itself is the whole block's
783
+ // l2 to l1 message tree.
784
+ const numTransactions = block.body.txEffects.length;
785
+ if (numTransactions === 1) {
786
+ return [BigInt(messageIndexInTx), messagePathInSubtree];
787
+ }
746
788
 
747
- // path of the input msg from leaf -> first out hash calculated in base rolllup
748
- const subtreePathOfL2ToL1Message = await l2toL1Subtrees[indexOfMsgTx].getSiblingPath(
749
- BigInt(indexOfMsgInSubtree),
750
- true,
789
+ // Calculate roots for all tx subtrees
790
+ const txSubtreeRoots = await Promise.all(
791
+ messagesPerTx.map(async (messages, txIdx) => {
792
+ // For a tx with no messages, we have to set an out hash of 0 to match what the circuit does.
793
+ if (messages.length === 0) {
794
+ return Fr.ZERO;
795
+ }
796
+
797
+ const txStore = openTmpStore(true);
798
+ const txTreeHeight = messages.length <= 1 ? 1 : Math.ceil(Math.log2(messages.length));
799
+ const txTree = new StandardTree(
800
+ txStore,
801
+ new SHA256Trunc(),
802
+ `tx_messages_subtree_${txIdx}`,
803
+ txTreeHeight,
804
+ 0n,
805
+ Fr,
806
+ );
807
+ txTree.appendLeaves(messages);
808
+ const root = Fr.fromBuffer(txTree.getRoot(true));
809
+ await txStore.delete();
810
+ return root;
811
+ }),
751
812
  );
752
813
 
753
- const numTxs = block.body.txEffects.length;
754
- if (numTxs === 1) {
755
- return [BigInt(indexOfMsgInSubtree), subtreePathOfL2ToL1Message];
814
+ // Construct the top tree and compute the combined path
815
+ let combinedPath: Buffer[];
816
+ {
817
+ const topTreeHeight = Math.ceil(Math.log2(txSubtreeRoots.length));
818
+ // The root of this tree is the out_hash calculated in Noir => we truncate to match Noir's SHA
819
+ const topTree = new UnbalancedTree(new SHA256Trunc(), 'top_tree', topTreeHeight, Fr);
820
+ await topTree.appendLeaves(txSubtreeRoots);
821
+
822
+ const txPathInTopTree = await topTree.getSiblingPath(txSubtreeRoots[txIndex].toBigInt());
823
+ // Append subtree path to top tree path
824
+ combinedPath = messagePathInSubtree.toBufferArray().concat(txPathInTopTree.toBufferArray());
756
825
  }
757
826
 
758
- // For a tx with no messages, we have to set an out hash of 0 to match the circuit/contract behavior
759
- const l2toL1SubtreeRoots = l2toL1Subtrees.map(t =>
760
- t.getNumLeaves(true) == 0n ? Fr.ZERO : Fr.fromBuffer(t.getRoot(true)),
761
- );
762
- const maxTreeHeight = Math.ceil(Math.log2(l2toL1SubtreeRoots.length));
763
- // The root of this tree is the out_hash calculated in Noir => we truncate to match Noir's SHA
764
- const outHashTree = new UnbalancedTree(new SHA256Trunc(), 'temp_outhash_sibling_path', maxTreeHeight, Fr);
765
- await outHashTree.appendLeaves(l2toL1SubtreeRoots);
766
-
767
- const pathOfTxInOutHashTree = await outHashTree.getSiblingPath(l2toL1SubtreeRoots[indexOfMsgTx].toBigInt());
768
- // Append subtree path to out hash tree path
769
- const mergedPath = subtreePathOfL2ToL1Message.toBufferArray().concat(pathOfTxInOutHashTree.toBufferArray());
770
- // Append binary index of subtree path to binary index of out hash tree path
771
- const mergedIndex = parseInt(
772
- indexOfMsgTx
773
- .toString(2)
774
- .concat(indexOfMsgInSubtree.toString(2).padStart(l2toL1Subtrees[indexOfMsgTx].getDepth(), '0')),
827
+ // Append binary index of subtree path to binary index of top tree path
828
+ const combinedIndex = parseInt(
829
+ txIndex.toString(2).concat(messageIndexInTx.toString(2).padStart(messageSubtreeHeight, '0')),
775
830
  2,
776
831
  );
777
832
 
778
- // clear the tmp stores
779
- await Promise.all(tempStores.map(store => store.delete()));
780
-
781
- return [BigInt(mergedIndex), new SiblingPath(mergedPath.length, mergedPath)];
833
+ return [BigInt(combinedIndex), new SiblingPath(combinedPath.length, combinedPath)];
782
834
  }
783
835
 
784
836
  /**
@@ -3,6 +3,7 @@ import { createLogger } from '@aztec/foundation/log';
3
3
  import type { DataStoreConfig } from '@aztec/kv-store/config';
4
4
  import { createStore } from '@aztec/kv-store/lmdb-v2';
5
5
  import type { P2PClient } from '@aztec/p2p';
6
+ import type { SlasherConfig } from '@aztec/slasher/config';
6
7
  import type { L2BlockSource } from '@aztec/stdlib/block';
7
8
 
8
9
  import type { SentinelConfig } from './config.js';
@@ -13,7 +14,7 @@ export async function createSentinel(
13
14
  epochCache: EpochCache,
14
15
  archiver: L2BlockSource,
15
16
  p2p: P2PClient,
16
- config: SentinelConfig & DataStoreConfig,
17
+ config: SentinelConfig & DataStoreConfig & SlasherConfig,
17
18
  logger = createLogger('node:sentinel'),
18
19
  ): Promise<Sentinel | undefined> {
19
20
  if (!config.sentinelEnabled) {
@@ -27,5 +28,5 @@ export async function createSentinel(
27
28
  );
28
29
  const storeHistoryLength = config.sentinelHistoryLengthInEpochs * epochCache.getL1Constants().epochDuration;
29
30
  const sentinelStore = new SentinelStore(kvStore, { historyLength: storeHistoryLength });
30
- return new Sentinel(epochCache, archiver, p2p, sentinelStore, logger);
31
+ return new Sentinel(epochCache, archiver, p2p, sentinelStore, config, logger);
31
32
  }