@aztec/sequencer-client 0.7.10 → 0.8.7
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.
- package/README.md +1 -1
- package/dest/client/sequencer-client.d.ts +3 -3
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +6 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +14 -7
- package/dest/global_variable_builder/config.d.ts +5 -5
- package/dest/global_variable_builder/config.d.ts.map +1 -1
- package/dest/global_variable_builder/viem-reader.js +3 -3
- package/dest/publisher/config.d.ts +6 -2
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/index.d.ts +27 -0
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -1
- package/dest/publisher/l1-publisher.d.ts +26 -11
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +18 -4
- package/dest/publisher/viem-tx-sender.d.ts +2 -1
- package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
- package/dest/publisher/viem-tx-sender.js +27 -7
- package/dest/sequencer/public_processor.d.ts +4 -2
- package/dest/sequencer/public_processor.d.ts.map +1 -1
- package/dest/sequencer/public_processor.js +11 -5
- package/dest/sequencer/sequencer.d.ts +13 -9
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +53 -34
- package/dest/simulator/index.d.ts +0 -1
- package/dest/simulator/index.d.ts.map +1 -1
- package/dest/simulator/index.js +2 -2
- package/dest/simulator/public_executor.d.ts +28 -4
- package/dest/simulator/public_executor.d.ts.map +1 -1
- package/dest/simulator/public_executor.js +46 -7
- package/dest/simulator/public_kernel.d.ts +1 -0
- package/dest/simulator/public_kernel.d.ts.map +1 -1
- package/dest/simulator/public_kernel.js +26 -5
- package/dest/simulator/rollup.d.ts +2 -8
- package/dest/simulator/rollup.d.ts.map +1 -1
- package/dest/simulator/rollup.js +37 -18
- package/package.json +70 -11
- package/src/client/sequencer-client.ts +7 -6
- package/src/config.ts +14 -5
- package/src/global_variable_builder/config.ts +6 -5
- package/src/global_variable_builder/viem-reader.ts +2 -2
- package/src/publisher/config.ts +7 -2
- package/src/publisher/index.ts +28 -0
- package/src/publisher/l1-publisher.ts +46 -13
- package/src/publisher/viem-tx-sender.ts +33 -13
- package/src/sequencer/public_processor.ts +13 -4
- package/src/sequencer/sequencer.ts +59 -45
- package/src/simulator/index.ts +0 -2
- package/src/simulator/public_executor.ts +53 -7
- package/src/simulator/public_kernel.ts +24 -4
- package/src/simulator/rollup.ts +38 -21
- package/.eslintrc.cjs +0 -1
- package/.tsbuildinfo +0 -1
- package/dest/block_builder/solo_block_builder.test.d.ts +0 -3
- package/dest/block_builder/solo_block_builder.test.d.ts.map +0 -1
- package/dest/block_builder/solo_block_builder.test.js +0 -277
- package/dest/publisher/l1-publisher.test.d.ts +0 -2
- package/dest/publisher/l1-publisher.test.d.ts.map +0 -1
- package/dest/publisher/l1-publisher.test.js +0 -58
- package/dest/sequencer/public_processor.test.d.ts +0 -2
- package/dest/sequencer/public_processor.test.d.ts.map +0 -1
- package/dest/sequencer/public_processor.test.js +0 -164
- package/dest/sequencer/sequencer.test.d.ts +0 -2
- package/dest/sequencer/sequencer.test.d.ts.map +0 -1
- package/dest/sequencer/sequencer.test.js +0 -99
- package/jest.integration.config.json +0 -13
- package/src/block_builder/solo_block_builder.test.ts +0 -425
- package/src/publisher/l1-publisher.test.ts +0 -79
- package/src/sequencer/public_processor.test.ts +0 -265
- package/src/sequencer/sequencer.test.ts +0 -160
- 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)
|
|
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 `
|
|
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(`
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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
|
|
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
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
}
|
package/src/simulator/index.ts
CHANGED
|
@@ -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
|
-
|
|
20
|
+
publicContractsDB: PublicContractsDB,
|
|
21
21
|
l1toL2MessageSource: L1ToL2MessageSource,
|
|
22
22
|
blockData: HistoricBlockData,
|
|
23
23
|
) {
|
|
24
24
|
return new PublicExecutor(
|
|
25
25
|
new WorldStatePublicDB(merkleTree),
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|