@aztec/sequencer-client 0.7.9 → 0.8.6

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.
Files changed (73) hide show
  1. package/README.md +1 -1
  2. package/dest/client/sequencer-client.d.ts +3 -3
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +6 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +14 -7
  7. package/dest/global_variable_builder/config.d.ts +5 -5
  8. package/dest/global_variable_builder/config.d.ts.map +1 -1
  9. package/dest/global_variable_builder/viem-reader.js +3 -3
  10. package/dest/publisher/config.d.ts +6 -2
  11. package/dest/publisher/config.d.ts.map +1 -1
  12. package/dest/publisher/index.d.ts +27 -0
  13. package/dest/publisher/index.d.ts.map +1 -1
  14. package/dest/publisher/index.js +1 -1
  15. package/dest/publisher/l1-publisher.d.ts +26 -11
  16. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  17. package/dest/publisher/l1-publisher.js +18 -4
  18. package/dest/publisher/viem-tx-sender.d.ts +2 -1
  19. package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
  20. package/dest/publisher/viem-tx-sender.js +27 -7
  21. package/dest/sequencer/public_processor.d.ts +4 -2
  22. package/dest/sequencer/public_processor.d.ts.map +1 -1
  23. package/dest/sequencer/public_processor.js +11 -5
  24. package/dest/sequencer/sequencer.d.ts +13 -9
  25. package/dest/sequencer/sequencer.d.ts.map +1 -1
  26. package/dest/sequencer/sequencer.js +53 -34
  27. package/dest/simulator/index.d.ts +0 -1
  28. package/dest/simulator/index.d.ts.map +1 -1
  29. package/dest/simulator/index.js +2 -2
  30. package/dest/simulator/public_executor.d.ts +28 -4
  31. package/dest/simulator/public_executor.d.ts.map +1 -1
  32. package/dest/simulator/public_executor.js +46 -7
  33. package/dest/simulator/public_kernel.d.ts +1 -0
  34. package/dest/simulator/public_kernel.d.ts.map +1 -1
  35. package/dest/simulator/public_kernel.js +26 -5
  36. package/dest/simulator/rollup.d.ts +2 -8
  37. package/dest/simulator/rollup.d.ts.map +1 -1
  38. package/dest/simulator/rollup.js +37 -18
  39. package/package.json +70 -11
  40. package/src/client/sequencer-client.ts +7 -6
  41. package/src/config.ts +14 -5
  42. package/src/global_variable_builder/config.ts +6 -5
  43. package/src/global_variable_builder/viem-reader.ts +2 -2
  44. package/src/publisher/config.ts +7 -2
  45. package/src/publisher/index.ts +28 -0
  46. package/src/publisher/l1-publisher.ts +46 -13
  47. package/src/publisher/viem-tx-sender.ts +33 -13
  48. package/src/sequencer/public_processor.ts +13 -4
  49. package/src/sequencer/sequencer.ts +59 -45
  50. package/src/simulator/index.ts +0 -2
  51. package/src/simulator/public_executor.ts +53 -7
  52. package/src/simulator/public_kernel.ts +24 -4
  53. package/src/simulator/rollup.ts +38 -21
  54. package/.eslintrc.cjs +0 -1
  55. package/.tsbuildinfo +0 -1
  56. package/dest/block_builder/solo_block_builder.test.d.ts +0 -3
  57. package/dest/block_builder/solo_block_builder.test.d.ts.map +0 -1
  58. package/dest/block_builder/solo_block_builder.test.js +0 -277
  59. package/dest/publisher/l1-publisher.test.d.ts +0 -2
  60. package/dest/publisher/l1-publisher.test.d.ts.map +0 -1
  61. package/dest/publisher/l1-publisher.test.js +0 -58
  62. package/dest/sequencer/public_processor.test.d.ts +0 -2
  63. package/dest/sequencer/public_processor.test.d.ts.map +0 -1
  64. package/dest/sequencer/public_processor.test.js +0 -164
  65. package/dest/sequencer/sequencer.test.d.ts +0 -2
  66. package/dest/sequencer/sequencer.test.d.ts.map +0 -1
  67. package/dest/sequencer/sequencer.test.js +0 -99
  68. package/jest.integration.config.json +0 -13
  69. package/src/block_builder/solo_block_builder.test.ts +0 -425
  70. package/src/publisher/l1-publisher.test.ts +0 -79
  71. package/src/sequencer/public_processor.test.ts +0 -265
  72. package/src/sequencer/sequencer.test.ts +0 -160
  73. package/tsconfig.json +0 -38
@@ -2,21 +2,36 @@ import { createDebugLogger } from '@aztec/foundation/log';
2
2
  import { InterruptableSleep } from '@aztec/foundation/sleep';
3
3
  import { ExtendedContractData, L2Block } from '@aztec/types';
4
4
 
5
+ import pick from 'lodash.pick';
6
+
5
7
  import { L2BlockReceiver } from '../receiver.js';
6
8
  import { PublisherConfig } from './config.js';
9
+ import { L1PublishStats } from './index.js';
10
+
11
+ /**
12
+ * Stats for a sent transaction.
13
+ */
14
+ export type TransactionStats = {
15
+ /** Hash of the transaction. */
16
+ transactionHash: string;
17
+ /** Size in bytes of the tx calldata */
18
+ calldataSize: number;
19
+ /** Gas required to pay for the calldata inclusion (depends on size and number of zeros) */
20
+ calldataGas: number;
21
+ };
7
22
 
8
23
  /**
9
24
  * Minimal information from a tx receipt returned by an L1PublisherTxSender.
10
25
  */
11
26
  export type MinimalTransactionReceipt = {
12
- /**
13
- * True if the tx was successful, false if reverted.
14
- */
27
+ /** True if the tx was successful, false if reverted. */
15
28
  status: boolean;
16
- /**
17
- * Hash of the transaction.
18
- */
29
+ /** Hash of the transaction. */
19
30
  transactionHash: string;
31
+ /** Effective gas used by the tx */
32
+ gasUsed: bigint;
33
+ /** Effective gas price paid by the tx */
34
+ gasPrice: bigint;
20
35
  };
21
36
 
22
37
  /**
@@ -38,10 +53,7 @@ export interface L1PublisherTxSender {
38
53
  * @param publicKeys - The public keys of the deployed contract
39
54
  * @param newExtendedContractData - Data to publish.
40
55
  * @returns The hash of the mined tx.
41
- * @remarks Partial addresses, public keys and contract data has to be in the same order.
42
- * @remarks See the link bellow for more info on partial address and public key:
43
- * https://github.com/AztecProtocol/aztec-packages/blob/master/docs/docs/concepts/foundation/accounts/keys.md#addresses-partial-addresses-and-public-keys
44
- * TODO: replace the link above with the link to deployed docs
56
+ * @remarks Partial addresses, public keys and contract data has to be in the same order. Read more {@link https://docs.aztec.network/concepts/foundation/accounts/keys#addresses-partial-addresses-and-public-keys | here}.
45
57
  */
46
58
  sendEmitContractDeploymentTx(
47
59
  l2BlockNum: number,
@@ -55,6 +67,12 @@ export interface L1PublisherTxSender {
55
67
  * @returns Undefined if the tx hasn't been mined yet, the receipt otherwise.
56
68
  */
57
69
  getTransactionReceipt(txHash: string): Promise<MinimalTransactionReceipt | undefined>;
70
+
71
+ /**
72
+ * Returns info on a tx by calling eth_getTransaction.
73
+ * @param txHash - Hash of the tx to look for.
74
+ */
75
+ getTransactionStats(txHash: string): Promise<TransactionStats | undefined>;
58
76
  }
59
77
 
60
78
  /**
@@ -122,7 +140,17 @@ export class L1Publisher implements L2BlockReceiver {
122
140
  if (!receipt) break;
123
141
 
124
142
  // Tx was mined successfully
125
- if (receipt.status) return true;
143
+ if (receipt.status) {
144
+ const tx = await this.txSender.getTransactionStats(txHash);
145
+ const stats: L1PublishStats = {
146
+ ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'),
147
+ ...pick(tx!, 'calldataGas', 'calldataSize'),
148
+ ...l2BlockData.getStats(),
149
+ eventName: 'rollup-published-to-l1',
150
+ };
151
+ this.log.info(`Published L2 block to L1 rollup contract`, stats);
152
+ return true;
153
+ }
126
154
 
127
155
  // Check if someone else incremented the block number
128
156
  if (!(await this.checkNextL2BlockNum(l2BlockData.number))) {
@@ -185,13 +213,18 @@ export class L1Publisher implements L2BlockReceiver {
185
213
  * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
186
214
  * Be warned, the call may return false even if the tx subsequently gets successfully mined.
187
215
  * In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
188
- * A call to `clearInterrupt` is required before you can continue publishing.
216
+ * A call to `restart` is required before you can continue publishing.
189
217
  */
190
218
  public interrupt() {
191
219
  this.interrupted = true;
192
220
  this.interruptableSleep.interrupt();
193
221
  }
194
222
 
223
+ /** Restarts the publisher after calling `interrupt`. */
224
+ public restart() {
225
+ this.interrupted = false;
226
+ }
227
+
195
228
  // TODO: Check fee distributor has at least 0.5 ETH.
196
229
  // Related to https://github.com/AztecProtocol/aztec-packages/issues/1588
197
230
  // eslint-disable-next-line require-await
@@ -210,7 +243,7 @@ export class L1Publisher implements L2BlockReceiver {
210
243
  try {
211
244
  return await this.txSender.sendProcessTx(encodedData);
212
245
  } catch (err) {
213
- this.log(`ROLLUP PUBLISH FAILED`, err);
246
+ this.log.error(`Rollup publish failed`, err);
214
247
  return undefined;
215
248
  }
216
249
  }
@@ -13,13 +13,19 @@ import {
13
13
  createWalletClient,
14
14
  getAddress,
15
15
  getContract,
16
+ hexToBytes,
16
17
  http,
17
18
  } from 'viem';
18
19
  import { PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts';
19
20
  import * as chains from 'viem/chains';
20
21
 
21
22
  import { TxSenderConfig } from './config.js';
22
- import { L1PublisherTxSender, MinimalTransactionReceipt, L1ProcessArgs as ProcessTxArgs } from './l1-publisher.js';
23
+ import {
24
+ L1PublisherTxSender,
25
+ MinimalTransactionReceipt,
26
+ L1ProcessArgs as ProcessTxArgs,
27
+ TransactionStats,
28
+ } from './l1-publisher.js';
23
29
 
24
30
  /**
25
31
  * Pushes transactions to the L1 rollup contract using viem.
@@ -41,13 +47,7 @@ export class ViemTxSender implements L1PublisherTxSender {
41
47
  private account: PrivateKeyAccount;
42
48
 
43
49
  constructor(config: TxSenderConfig) {
44
- const {
45
- rpcUrl,
46
- apiKey,
47
- publisherPrivateKey,
48
- rollupContract: rollupContractAddress,
49
- contractDeploymentEmitterContract: contractDeploymentEmitterContractAddress,
50
- } = config;
50
+ const { rpcUrl, apiKey, publisherPrivateKey, l1Contracts } = config;
51
51
  const chain = createEthereumChain(rpcUrl, apiKey);
52
52
  this.account = privateKeyToAccount(publisherPrivateKey);
53
53
  const walletClient = createWalletClient({
@@ -62,19 +62,30 @@ export class ViemTxSender implements L1PublisherTxSender {
62
62
  });
63
63
 
64
64
  this.rollupContract = getContract({
65
- address: getAddress(rollupContractAddress.toString()),
65
+ address: getAddress(l1Contracts.rollupAddress.toString()),
66
66
  abi: RollupAbi,
67
67
  publicClient: this.publicClient,
68
68
  walletClient,
69
69
  });
70
70
  this.contractDeploymentEmitterContract = getContract({
71
- address: getAddress(contractDeploymentEmitterContractAddress.toString()),
71
+ address: getAddress(l1Contracts.contractDeploymentEmitterAddress.toString()),
72
72
  abi: ContractDeploymentEmitterAbi,
73
73
  publicClient: this.publicClient,
74
74
  walletClient,
75
75
  });
76
76
  }
77
77
 
78
+ async getTransactionStats(txHash: string): Promise<TransactionStats | undefined> {
79
+ const tx = await this.publicClient.getTransaction({ hash: txHash as Hex });
80
+ if (!tx) return undefined;
81
+ const calldata = hexToBytes(tx.input);
82
+ return {
83
+ transactionHash: tx.hash,
84
+ calldataSize: calldata.length,
85
+ calldataGas: getCalldataGasUsage(calldata),
86
+ };
87
+ }
88
+
78
89
  /**
79
90
  * Returns a tx receipt if the tx has been mined.
80
91
  * @param txHash - Hash of the tx to look for.
@@ -85,16 +96,16 @@ export class ViemTxSender implements L1PublisherTxSender {
85
96
  hash: txHash as Hex,
86
97
  });
87
98
 
88
- // TODO: check for confirmations
89
-
90
99
  if (receipt) {
91
100
  return {
92
101
  status: receipt.status === 'success',
93
102
  transactionHash: txHash,
103
+ gasUsed: receipt.gasUsed,
104
+ gasPrice: receipt.effectiveGasPrice,
94
105
  };
95
106
  }
96
107
 
97
- this.log('Receipt not found for tx hash', txHash);
108
+ this.log(`Receipt not found for tx hash ${txHash}`);
98
109
  return undefined;
99
110
  }
100
111
 
@@ -171,3 +182,12 @@ export class ViemTxSender implements L1PublisherTxSender {
171
182
  throw new Error(`Chain with id ${chainId} not found`);
172
183
  }
173
184
  }
185
+
186
+ /**
187
+ * Returns cost of calldata usage in Ethereum.
188
+ * @param data - Calldata.
189
+ * @returns 4 for each zero byte, 16 for each nonzero.
190
+ */
191
+ function getCalldataGasUsage(data: Uint8Array) {
192
+ return data.filter(byte => byte === 0).length * 4 + data.filter(byte => byte !== 0).length * 16;
193
+ }
@@ -47,7 +47,8 @@ import { MerkleTreeOperations } from '@aztec/world-state';
47
47
  import { getVerificationKeys } from '../index.js';
48
48
  import { EmptyPublicProver } from '../prover/empty.js';
49
49
  import { PublicProver } from '../prover/index.js';
50
- import { PublicKernelCircuitSimulator, getPublicExecutor } from '../simulator/index.js';
50
+ import { PublicKernelCircuitSimulator } from '../simulator/index.js';
51
+ import { ContractsDataSourcePublicDB, getPublicExecutor } from '../simulator/public_executor.js';
51
52
  import { WasmPublicKernelCircuitSimulator } from '../simulator/public_kernel.js';
52
53
  import { FailedTx, ProcessedTx, makeEmptyProcessedTx, makeProcessedTx } from './processed_tx.js';
53
54
  import { getHistoricBlockData } from './utils.js';
@@ -66,6 +67,7 @@ export class PublicProcessorFactory {
66
67
  * Creates a new instance of a PublicProcessor.
67
68
  * @param prevGlobalVariables - The global variables for the previous block, used to calculate the prev global variables hash.
68
69
  * @param globalVariables - The global variables for the block being processed.
70
+ * @param newContracts - Provides access to contract bytecode for public executions.
69
71
  * @returns A new instance of a PublicProcessor.
70
72
  */
71
73
  public async create(
@@ -73,14 +75,15 @@ export class PublicProcessorFactory {
73
75
  globalVariables: GlobalVariables,
74
76
  ): Promise<PublicProcessor> {
75
77
  const blockData = await getHistoricBlockData(this.merkleTree, prevGlobalVariables);
78
+ const publicContractsDB = new ContractsDataSourcePublicDB(this.contractDataSource);
76
79
  return new PublicProcessor(
77
80
  this.merkleTree,
78
- getPublicExecutor(this.merkleTree, this.contractDataSource, this.l1Tol2MessagesDataSource, blockData),
81
+ getPublicExecutor(this.merkleTree, publicContractsDB, this.l1Tol2MessagesDataSource, blockData),
79
82
  new WasmPublicKernelCircuitSimulator(),
80
83
  new EmptyPublicProver(),
81
- this.contractDataSource,
82
84
  globalVariables,
83
85
  blockData,
86
+ publicContractsDB,
84
87
  );
85
88
  }
86
89
  }
@@ -95,9 +98,9 @@ export class PublicProcessor {
95
98
  protected publicExecutor: PublicExecutor,
96
99
  protected publicKernel: PublicKernelCircuitSimulator,
97
100
  protected publicProver: PublicProver,
98
- protected contractDataSource: ContractDataSource,
99
101
  protected globalVariables: GlobalVariables,
100
102
  protected blockData: HistoricBlockData,
103
+ protected publicContractsDB: ContractsDataSourcePublicDB,
101
104
 
102
105
  private log = createDebugLogger('aztec:sequencer:public-processor'),
103
106
  ) {}
@@ -116,6 +119,8 @@ export class PublicProcessor {
116
119
  for (const tx of txs) {
117
120
  this.log(`Processing tx ${await tx.getTxHash()}`);
118
121
  try {
122
+ // add new contracts to the contracts db so that their functions may be found and called
123
+ await this.publicContractsDB.addNewContracts(tx);
119
124
  result.push(await this.processTx(tx));
120
125
  } catch (err) {
121
126
  this.log.warn(`Error processing tx ${await tx.getTxHash()}: ${err}`);
@@ -123,8 +128,11 @@ export class PublicProcessor {
123
128
  tx,
124
129
  error: err instanceof Error ? err : new Error('Unknown error'),
125
130
  });
131
+ // remove contracts on failure
132
+ await this.publicContractsDB.removeNewContracts(tx);
126
133
  }
127
134
  }
135
+
128
136
  return [result, failed];
129
137
  }
130
138
 
@@ -405,6 +413,7 @@ export class PublicProcessor {
405
413
  PublicDataRead.empty(),
406
414
  MAX_PUBLIC_DATA_READS_PER_TX,
407
415
  );
416
+
408
417
  // Override kernel output
409
418
  publicInputs.end.publicDataUpdateRequests = padArrayEnd(
410
419
  [
@@ -3,8 +3,8 @@ import { Fr } from '@aztec/foundation/fields';
3
3
  import { createDebugLogger } from '@aztec/foundation/log';
4
4
  import { RunningPromise } from '@aztec/foundation/running-promise';
5
5
  import { P2P } from '@aztec/p2p';
6
- import { L1ToL2MessageSource, L2Block, L2BlockSource, MerkleTreeId, Tx } from '@aztec/types';
7
- import { WorldStateStatus, WorldStateSynchroniser } from '@aztec/world-state';
6
+ import { ContractDataSource, L1ToL2MessageSource, L2Block, L2BlockSource, MerkleTreeId, Tx } from '@aztec/types';
7
+ import { WorldStateStatus, WorldStateSynchronizer } from '@aztec/world-state';
8
8
 
9
9
  import times from 'lodash.times';
10
10
 
@@ -18,7 +18,7 @@ import { PublicProcessorFactory } from './public_processor.js';
18
18
 
19
19
  /**
20
20
  * Sequencer client
21
- * - Wins a period of time to become the sequencer (depending on finalised protocol).
21
+ * - Wins a period of time to become the sequencer (depending on finalized protocol).
22
22
  * - Chooses a set of txs from the tx pool to be in the rollup.
23
23
  * - Simulate the rollup of txs.
24
24
  * - Adds proof requests to the request pool (not for this milestone).
@@ -32,17 +32,16 @@ export class Sequencer {
32
32
  private minTxsPerBLock = 1;
33
33
  private lastPublishedBlock = 0;
34
34
  private state = SequencerState.STOPPED;
35
- private chainId: Fr;
36
- private version: Fr;
37
35
 
38
36
  constructor(
39
37
  private publisher: L1Publisher,
40
38
  private globalsBuilder: GlobalVariableBuilder,
41
39
  private p2pClient: P2P,
42
- private worldState: WorldStateSynchroniser,
40
+ private worldState: WorldStateSynchronizer,
43
41
  private blockBuilder: BlockBuilder,
44
42
  private l2BlockSource: L2BlockSource,
45
43
  private l1ToL2MessageSource: L1ToL2MessageSource,
44
+ private contractDataSource: ContractDataSource,
46
45
  private publicProcessorFactory: PublicProcessorFactory,
47
46
  config: SequencerConfig,
48
47
  private log = createDebugLogger('aztec:sequencer'),
@@ -54,8 +53,7 @@ export class Sequencer {
54
53
  if (config.minTxsPerBlock) {
55
54
  this.minTxsPerBLock = config.minTxsPerBlock;
56
55
  }
57
- this.chainId = new Fr(config.chainId);
58
- this.version = new Fr(config.version);
56
+ this.log(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
59
57
  }
60
58
 
61
59
  /**
@@ -85,6 +83,7 @@ export class Sequencer {
85
83
  */
86
84
  public restart() {
87
85
  this.log('Restarting sequencer');
86
+ this.publisher.restart();
88
87
  this.runningPromise!.start();
89
88
  this.state = SequencerState.IDLE;
90
89
  }
@@ -122,16 +121,16 @@ export class Sequencer {
122
121
  // Get txs to build the new block
123
122
  const pendingTxs = await this.p2pClient.getTxs();
124
123
  if (pendingTxs.length < this.minTxsPerBLock) return;
124
+ this.log.info(`Retrieved ${pendingTxs.length} txs from P2P pool`);
125
125
 
126
126
  // Filter out invalid txs
127
+ // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
127
128
  const validTxs = await this.takeValidTxs(pendingTxs);
128
- if (validTxs.length < this.minTxsPerBLock) {
129
- return;
130
- }
129
+ if (validTxs.length < this.minTxsPerBLock) return;
131
130
 
132
131
  const blockNumber = (await this.l2BlockSource.getBlockNumber()) + 1;
133
132
 
134
- this.log.info(`Building block ${blockNumber} with ${validTxs.length} transactions...`);
133
+ this.log.info(`Building block ${blockNumber} with ${validTxs.length} transactions`);
135
134
  this.state = SequencerState.CREATING_BLOCK;
136
135
 
137
136
  const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(new Fr(blockNumber));
@@ -147,9 +146,11 @@ export class Sequencer {
147
146
  await this.p2pClient.deleteTxs(await Tx.getHashes(failedTxData));
148
147
  }
149
148
 
150
- // Only accept processed transactions that are not double-spends
151
- // public functions emitting nullifiers would pass earlier check but fail here
152
- const processedValidTxs = await this.takeValidProcessedTxs(processedTxs);
149
+ // Only accept processed transactions that are not double-spends,
150
+ // public functions emitting nullifiers would pass earlier check but fail here.
151
+ // Note that we're checking all nullifiers generated in the private execution twice,
152
+ // we could store the ones already checked and skip them here as an optimisation.
153
+ const processedValidTxs = await this.takeValidTxs(processedTxs);
153
154
 
154
155
  if (processedValidTxs.length === 0) {
155
156
  this.log('No txs processed correctly to build block. Exiting');
@@ -173,8 +174,7 @@ export class Sequencer {
173
174
  await this.publishL2Block(block);
174
175
  this.log.info(`Submitted rollup block ${block.number} with ${processedValidTxs.length} transactions`);
175
176
  } catch (err) {
176
- this.log.error(err);
177
- this.log.error(`Rolling back world state DB`);
177
+ this.log.error(`Rolling back world state DB due to error assembling block`, err);
178
178
  await this.worldState.getLatest().rollback();
179
179
  }
180
180
  }
@@ -224,25 +224,27 @@ export class Sequencer {
224
224
  }
225
225
  }
226
226
 
227
- // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
228
- protected async takeValidTxs(txs: Tx[]) {
229
- const validTxs = [];
227
+ protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[]): Promise<T[]> {
228
+ const validTxs: T[] = [];
230
229
  const doubleSpendTxs = [];
230
+ const thisBlockNullifiers: Set<bigint> = new Set();
231
231
 
232
232
  // Process txs until we get to maxTxsPerBlock, rejecting double spends in the process
233
233
  for (const tx of txs) {
234
- // TODO(AD) - eventually we should add a limit to how many transactions we
235
- // skip in this manner and do something more DDOS-proof (like letting the transaction fail and pay a fee).
236
234
  if (await this.isTxDoubleSpend(tx)) {
237
- this.log(`Deleting double spend tx ${await tx.getTxHash()}`);
235
+ this.log(`Deleting double spend tx ${await Tx.getHash(tx)}`);
238
236
  doubleSpendTxs.push(tx);
239
237
  continue;
238
+ } else if (this.isTxDoubleSpendSameBlock(tx, thisBlockNullifiers)) {
239
+ // We don't drop these txs from the p2p pool immediately since they become valid
240
+ // again if the current block fails to be published for some reason.
241
+ this.log(`Skipping tx with double-spend for this same block ${await Tx.getHash(tx)}`);
242
+ continue;
240
243
  }
241
244
 
245
+ tx.data.end.newNullifiers.forEach(n => thisBlockNullifiers.add(n.toBigInt()));
242
246
  validTxs.push(tx);
243
- if (validTxs.length >= this.maxTxsPerBlock) {
244
- break;
245
- }
247
+ if (validTxs.length >= this.maxTxsPerBlock) break;
246
248
  }
247
249
 
248
250
  // Make sure we remove these from the tx pool so we do not consider it again
@@ -253,13 +255,6 @@ export class Sequencer {
253
255
  return validTxs;
254
256
  }
255
257
 
256
- protected async takeValidProcessedTxs(txs: ProcessedTx[]) {
257
- const isDoubleSpends = await Promise.all(txs.map(async tx => await this.isTxDoubleSpend(tx as unknown as Tx)));
258
- const doubleSpends = txs.filter((tx, index) => isDoubleSpends[index]).map(tx => tx.hash);
259
- await this.p2pClient.deleteTxs(doubleSpends);
260
- return txs.filter((tx, index) => !isDoubleSpends[index]);
261
- }
262
-
263
258
  /**
264
259
  * Returns whether the previous block sent has been mined, and all dependencies have caught up with it.
265
260
  * @returns Boolean indicating if our dependencies are synced to the latest block.
@@ -268,6 +263,8 @@ export class Sequencer {
268
263
  const syncedBlocks = await Promise.all([
269
264
  this.worldState.status().then((s: WorldStateStatus) => s.syncedToL2Block),
270
265
  this.p2pClient.getStatus().then(s => s.syncedToL2Block),
266
+ this.l2BlockSource.getBlockNumber(),
267
+ this.l1ToL2MessageSource.getBlockNumber(),
271
268
  ]);
272
269
  const min = Math.min(...syncedBlocks);
273
270
  return min >= this.lastPublishedBlock;
@@ -307,25 +304,42 @@ export class Sequencer {
307
304
  return await this.l1ToL2MessageSource.getPendingL1ToL2Messages();
308
305
  }
309
306
 
307
+ /**
308
+ * Returns true if one of the tx nullifiers exist on the block being built.
309
+ * @param tx - The tx to test.
310
+ * @param thisBlockNullifiers - The nullifiers added so far.
311
+ */
312
+ protected isTxDoubleSpendSameBlock(tx: Tx | ProcessedTx, thisBlockNullifiers: Set<bigint>): boolean {
313
+ // We only consider non-empty nullifiers
314
+ const newNullifiers = tx.data.end.newNullifiers.filter(n => !n.isZero());
315
+
316
+ for (const nullifier of newNullifiers) {
317
+ if (thisBlockNullifiers.has(nullifier.toBigInt())) {
318
+ return true;
319
+ }
320
+ }
321
+ return false;
322
+ }
323
+
310
324
  /**
311
325
  * Returns true if one of the transaction nullifiers exist.
312
326
  * Nullifiers prevent double spends in a private context.
313
327
  * @param tx - The transaction.
314
328
  * @returns Whether this is a problematic double spend that the L1 contract would reject.
315
329
  */
316
- protected async isTxDoubleSpend(tx: Tx): Promise<boolean> {
317
- // eslint-disable-next-line @typescript-eslint/await-thenable
318
- for (const nullifier of tx.data.end.newNullifiers) {
319
- // Skip nullifier if it's empty
320
- if (nullifier.isZero()) continue;
330
+ protected async isTxDoubleSpend(tx: Tx | ProcessedTx): Promise<boolean> {
331
+ // We only consider non-empty nullifiers
332
+ const newNullifiers = tx.data.end.newNullifiers.filter(n => !n.isZero());
333
+
334
+ // Ditch this tx if it has a repeated nullifiers
335
+ const uniqNullifiers = new Set(newNullifiers.map(n => n.toBigInt()));
336
+ if (uniqNullifiers.size !== newNullifiers.length) return true;
337
+
338
+ for (const nullifier of newNullifiers) {
321
339
  // TODO(AD): this is an exhaustive search currently
322
- if (
323
- (await this.worldState.getLatest().findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer())) !==
324
- undefined
325
- ) {
326
- // Our nullifier tree has this nullifier already - this transaction is a double spend / not well-formed
327
- return true;
328
- }
340
+ const db = this.worldState.getLatest();
341
+ const indexInDb = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
342
+ if (indexInDb !== undefined) return true;
329
343
  }
330
344
  return false;
331
345
  }
@@ -8,8 +8,6 @@ import {
8
8
  RootRollupPublicInputs,
9
9
  } from '@aztec/circuits.js';
10
10
 
11
- export { getPublicExecutor } from './public_executor.js';
12
-
13
11
  /**
14
12
  * Circuit simulator for the rollup circuits.
15
13
  */
@@ -6,7 +6,7 @@ import {
6
6
  PublicStateDB,
7
7
  } from '@aztec/acir-simulator';
8
8
  import { AztecAddress, CircuitsWasm, EthAddress, Fr, FunctionSelector, HistoricBlockData } from '@aztec/circuits.js';
9
- import { ContractDataSource, L1ToL2MessageSource, MerkleTreeId } from '@aztec/types';
9
+ import { ContractDataSource, ExtendedContractData, L1ToL2MessageSource, MerkleTreeId, Tx } from '@aztec/types';
10
10
  import { MerkleTreeOperations, computePublicDataTreeLeafIndex } from '@aztec/world-state';
11
11
 
12
12
  /**
@@ -17,13 +17,13 @@ import { MerkleTreeOperations, computePublicDataTreeLeafIndex } from '@aztec/wor
17
17
  */
18
18
  export function getPublicExecutor(
19
19
  merkleTree: MerkleTreeOperations,
20
- contractDataSource: ContractDataSource,
20
+ publicContractsDB: PublicContractsDB,
21
21
  l1toL2MessageSource: L1ToL2MessageSource,
22
22
  blockData: HistoricBlockData,
23
23
  ) {
24
24
  return new PublicExecutor(
25
25
  new WorldStatePublicDB(merkleTree),
26
- new ContractsDataSourcePublicDB(contractDataSource),
26
+ publicContractsDB,
27
27
  new WorldStateDB(merkleTree, l1toL2MessageSource),
28
28
  blockData,
29
29
  );
@@ -31,17 +31,63 @@ export function getPublicExecutor(
31
31
 
32
32
  /**
33
33
  * Implements the PublicContractsDB using a ContractDataSource.
34
+ * Progresively records contracts in transaction as they are processed in a block.
34
35
  */
35
- class ContractsDataSourcePublicDB implements PublicContractsDB {
36
+ export class ContractsDataSourcePublicDB implements PublicContractsDB {
37
+ cache = new Map<string, ExtendedContractData>();
38
+
36
39
  constructor(private db: ContractDataSource) {}
40
+
41
+ /**
42
+ * Add new contracts from a transaction
43
+ * @param tx - The transaction to add contracts from.
44
+ */
45
+ public addNewContracts(tx: Tx): Promise<void> {
46
+ for (const contract of tx.newContracts) {
47
+ const contractAddress = contract.contractData.contractAddress;
48
+
49
+ if (contractAddress.isZero()) {
50
+ continue;
51
+ }
52
+
53
+ this.cache.set(contractAddress.toString(), contract);
54
+ }
55
+
56
+ return Promise.resolve();
57
+ }
58
+
59
+ /**
60
+ * Removes new contracts added from transactions
61
+ * @param tx - The tx's contracts to be removed
62
+ */
63
+ public removeNewContracts(tx: Tx): Promise<void> {
64
+ for (const contract of tx.newContracts) {
65
+ const contractAddress = contract.contractData.contractAddress;
66
+
67
+ if (contractAddress.isZero()) {
68
+ continue;
69
+ }
70
+
71
+ this.cache.delete(contractAddress.toString());
72
+ }
73
+ return Promise.resolve();
74
+ }
75
+
37
76
  async getBytecode(address: AztecAddress, selector: FunctionSelector): Promise<Buffer | undefined> {
38
- return (await this.db.getPublicFunction(address, selector))?.bytecode;
77
+ const contract = await this.#getContract(address);
78
+ return contract?.getPublicFunction(selector)?.bytecode;
39
79
  }
40
80
  async getIsInternal(address: AztecAddress, selector: FunctionSelector): Promise<boolean | undefined> {
41
- return (await this.db.getPublicFunction(address, selector))?.isInternal;
81
+ const contract = await this.#getContract(address);
82
+ return contract?.getPublicFunction(selector)?.isInternal;
42
83
  }
43
84
  async getPortalContractAddress(address: AztecAddress): Promise<EthAddress | undefined> {
44
- return (await this.db.getContractData(address))?.portalContractAddress;
85
+ const contract = await this.#getContract(address);
86
+ return contract?.contractData.portalContractAddress;
87
+ }
88
+
89
+ async #getContract(address: AztecAddress): Promise<ExtendedContractData | undefined> {
90
+ return this.cache.get(address.toString()) ?? (await this.db.getExtendedContractData(address));
45
91
  }
46
92
  }
47
93
 
@@ -1,4 +1,6 @@
1
1
  import { PublicKernelInputs, PublicKernelPublicInputs, simulatePublicKernelCircuit } from '@aztec/circuits.js';
2
+ import { createDebugLogger } from '@aztec/foundation/log';
3
+ import { elapsed } from '@aztec/foundation/timer';
2
4
 
3
5
  import { PublicKernelCircuitSimulator } from './index.js';
4
6
 
@@ -6,14 +8,24 @@ import { PublicKernelCircuitSimulator } from './index.js';
6
8
  * Implements the PublicKernelCircuitSimulator by calling the wasm implementations of the circuits.
7
9
  */
8
10
  export class WasmPublicKernelCircuitSimulator implements PublicKernelCircuitSimulator {
11
+ private log = createDebugLogger('aztec:public-kernel-simulator');
12
+
9
13
  /**
10
14
  * Simulates the public kernel circuit (with a previous private kernel circuit run) from its inputs.
11
15
  * @param input - Inputs to the circuit.
12
16
  * @returns The public inputs as outputs of the simulation.
13
17
  */
14
- public publicKernelCircuitPrivateInput(input: PublicKernelInputs): Promise<PublicKernelPublicInputs> {
18
+ public async publicKernelCircuitPrivateInput(input: PublicKernelInputs): Promise<PublicKernelPublicInputs> {
15
19
  if (!input.previousKernel.publicInputs.isPrivate) throw new Error(`Expected private kernel previous inputs`);
16
- return simulatePublicKernelCircuit(input);
20
+ const [time, result] = await elapsed(() => simulatePublicKernelCircuit(input));
21
+ this.log(`Simulated public kernel circuit with private input`, {
22
+ eventName: 'circuit-simulation',
23
+ circuitName: 'public-kernel-private-input',
24
+ duration: time.ms(),
25
+ inputSize: input.toBuffer().length,
26
+ outputSize: result.toBuffer().length,
27
+ });
28
+ return result;
17
29
  }
18
30
 
19
31
  /**
@@ -21,8 +33,16 @@ export class WasmPublicKernelCircuitSimulator implements PublicKernelCircuitSimu
21
33
  * @param input - Inputs to the circuit.
22
34
  * @returns The public inputs as outputs of the simulation.
23
35
  */
24
- publicKernelCircuitNonFirstIteration(input: PublicKernelInputs): Promise<PublicKernelPublicInputs> {
36
+ public async publicKernelCircuitNonFirstIteration(input: PublicKernelInputs): Promise<PublicKernelPublicInputs> {
25
37
  if (input.previousKernel.publicInputs.isPrivate) throw new Error(`Expected public kernel previous inputs`);
26
- return simulatePublicKernelCircuit(input);
38
+ const [time, result] = await elapsed(() => simulatePublicKernelCircuit(input));
39
+ this.log(`Simulated public kernel circuit non-first iteration`, {
40
+ eventName: 'circuit-simulation',
41
+ circuitName: 'public-kernel-non-first-iteration',
42
+ duration: time.ms(),
43
+ inputSize: input.toBuffer().length,
44
+ outputSize: result.toBuffer().length,
45
+ });
46
+ return result;
27
47
  }
28
48
  }