@aztec/sequencer-client 0.41.0 → 0.43.0
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/dest/client/sequencer-client.js +2 -2
- package/dest/config.d.ts +13 -0
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +53 -45
- package/dest/publisher/l1-publisher.d.ts +7 -1
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +11 -3
- package/dest/publisher/viem-tx-sender.d.ts +3 -0
- package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
- package/dest/publisher/viem-tx-sender.js +16 -1
- package/dest/receiver.d.ts +4 -1
- package/dest/receiver.d.ts.map +1 -1
- package/dest/sequencer/sequencer.d.ts +7 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +57 -20
- package/dest/tx_validator/gas_validator.d.ts +2 -1
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +36 -8
- package/dest/tx_validator/phases_validator.d.ts +4 -4
- package/dest/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/tx_validator/phases_validator.js +34 -22
- package/dest/tx_validator/test_utils.d.ts +21 -0
- package/dest/tx_validator/test_utils.d.ts.map +1 -0
- package/dest/tx_validator/test_utils.js +19 -0
- package/dest/tx_validator/tx_validator_factory.d.ts +5 -5
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +5 -5
- package/package.json +14 -14
- package/src/client/sequencer-client.ts +1 -1
- package/src/config.ts +56 -51
- package/src/publisher/l1-publisher.ts +18 -2
- package/src/publisher/viem-tx-sender.ts +16 -0
- package/src/receiver.ts +4 -1
- package/src/sequencer/sequencer.ts +73 -24
- package/src/tx_validator/gas_validator.ts +41 -7
- package/src/tx_validator/phases_validator.ts +41 -24
- package/src/tx_validator/test_utils.ts +37 -0
- package/src/tx_validator/tx_validator_factory.ts +6 -6
package/src/config.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type AllowedElement } from '@aztec/circuit-types';
|
|
2
2
|
import { AztecAddress, Fr, FunctionSelector, getContractClassFromArtifact } from '@aztec/circuits.js';
|
|
3
3
|
import { type L1ContractAddresses, NULL_KEY } from '@aztec/ethereum';
|
|
4
4
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
|
-
import { EcdsaAccountContractArtifact } from '@aztec/noir-contracts.js/EcdsaAccount';
|
|
6
5
|
import { FPCContract } from '@aztec/noir-contracts.js/FPC';
|
|
7
|
-
import { GasTokenContract } from '@aztec/noir-contracts.js/GasToken';
|
|
8
|
-
import { SchnorrAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrAccount';
|
|
9
|
-
import { SchnorrHardcodedAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrHardcodedAccount';
|
|
10
|
-
import { SchnorrSingleKeyAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrSingleKeyAccount';
|
|
11
6
|
import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
|
|
7
|
+
import { AuthRegistryAddress } from '@aztec/protocol-contracts/auth-registry';
|
|
8
|
+
import { GasTokenAddress } from '@aztec/protocol-contracts/gas-token';
|
|
12
9
|
|
|
13
10
|
import { type Hex } from 'viem';
|
|
14
11
|
|
|
@@ -50,6 +47,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
|
|
|
50
47
|
SEQ_MIN_TX_PER_BLOCK,
|
|
51
48
|
SEQ_ALLOWED_SETUP_FN,
|
|
52
49
|
SEQ_ALLOWED_TEARDOWN_FN,
|
|
50
|
+
SEQ_MAX_BLOCK_SIZE_IN_BYTES,
|
|
53
51
|
AVAILABILITY_ORACLE_CONTRACT_ADDRESS,
|
|
54
52
|
ROLLUP_CONTRACT_ADDRESS,
|
|
55
53
|
REGISTRY_CONTRACT_ADDRESS,
|
|
@@ -61,6 +59,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
|
|
|
61
59
|
FEE_RECIPIENT,
|
|
62
60
|
ACVM_WORKING_DIRECTORY,
|
|
63
61
|
ACVM_BINARY_PATH,
|
|
62
|
+
ENFORCE_FEES = '',
|
|
64
63
|
} = process.env;
|
|
65
64
|
|
|
66
65
|
const publisherPrivateKey: Hex = SEQ_PUBLISHER_PRIVATE_KEY
|
|
@@ -82,6 +81,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
|
|
|
82
81
|
};
|
|
83
82
|
|
|
84
83
|
return {
|
|
84
|
+
enforceFees: ['1', 'true'].includes(ENFORCE_FEES),
|
|
85
85
|
rpcUrl: ETHEREUM_HOST ? ETHEREUM_HOST : '',
|
|
86
86
|
chainId: CHAIN_ID ? +CHAIN_ID : 31337, // 31337 is the default chain id for anvil
|
|
87
87
|
version: VERSION ? +VERSION : 1, // 1 is our default version
|
|
@@ -89,6 +89,7 @@ export function getConfigEnvVars(): SequencerClientConfig {
|
|
|
89
89
|
requiredConfirmations: SEQ_REQUIRED_CONFIRMATIONS ? +SEQ_REQUIRED_CONFIRMATIONS : 1,
|
|
90
90
|
l1BlockPublishRetryIntervalMS: SEQ_PUBLISH_RETRY_INTERVAL_MS ? +SEQ_PUBLISH_RETRY_INTERVAL_MS : 1_000,
|
|
91
91
|
transactionPollingIntervalMS: SEQ_TX_POLLING_INTERVAL_MS ? +SEQ_TX_POLLING_INTERVAL_MS : 1_000,
|
|
92
|
+
maxBlockSizeInBytes: SEQ_MAX_BLOCK_SIZE_IN_BYTES ? +SEQ_MAX_BLOCK_SIZE_IN_BYTES : undefined,
|
|
92
93
|
l1Contracts: addresses,
|
|
93
94
|
publisherPrivateKey,
|
|
94
95
|
maxTxsPerBlock: SEQ_MAX_TX_PER_BLOCK ? +SEQ_MAX_TX_PER_BLOCK : 32,
|
|
@@ -98,73 +99,81 @@ export function getConfigEnvVars(): SequencerClientConfig {
|
|
|
98
99
|
feeRecipient: FEE_RECIPIENT ? AztecAddress.fromString(FEE_RECIPIENT) : undefined,
|
|
99
100
|
acvmWorkingDirectory: ACVM_WORKING_DIRECTORY ? ACVM_WORKING_DIRECTORY : undefined,
|
|
100
101
|
acvmBinaryPath: ACVM_BINARY_PATH ? ACVM_BINARY_PATH : undefined,
|
|
101
|
-
|
|
102
|
+
allowedInSetup: SEQ_ALLOWED_SETUP_FN
|
|
102
103
|
? parseSequencerAllowList(SEQ_ALLOWED_SETUP_FN)
|
|
103
104
|
: getDefaultAllowedSetupFunctions(),
|
|
104
|
-
|
|
105
|
+
allowedInTeardown: SEQ_ALLOWED_TEARDOWN_FN
|
|
105
106
|
? parseSequencerAllowList(SEQ_ALLOWED_TEARDOWN_FN)
|
|
106
107
|
: getDefaultAllowedTeardownFunctions(),
|
|
107
108
|
};
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Parses a string to a list of allowed elements.
|
|
113
|
+
* Each encoded is expected to be of one of the following formats
|
|
114
|
+
* `I:${address}`
|
|
115
|
+
* `I:${address}:${selector}`
|
|
116
|
+
* `C:${classId}`
|
|
117
|
+
* `C:${classId}:${selector}`
|
|
118
|
+
*
|
|
119
|
+
* @param value The string to parse
|
|
120
|
+
* @returns A list of allowed elements
|
|
121
|
+
*/
|
|
122
|
+
export function parseSequencerAllowList(value: string): AllowedElement[] {
|
|
123
|
+
const entries: AllowedElement[] = [];
|
|
112
124
|
|
|
113
125
|
if (!value) {
|
|
114
126
|
return entries;
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
for (const val of value.split(',')) {
|
|
118
|
-
const [identifierString, selectorString] = val.split(':');
|
|
119
|
-
const selector = FunctionSelector.fromString(selectorString);
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
const [typeString, identifierString, selectorString] = val.split(':');
|
|
131
|
+
const selector = selectorString !== undefined ? FunctionSelector.fromString(selectorString) : undefined;
|
|
132
|
+
|
|
133
|
+
if (typeString === 'I') {
|
|
134
|
+
if (selector) {
|
|
135
|
+
entries.push({
|
|
136
|
+
address: AztecAddress.fromString(identifierString),
|
|
137
|
+
selector,
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
entries.push({
|
|
141
|
+
address: AztecAddress.fromString(identifierString),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} else if (typeString === 'C') {
|
|
145
|
+
if (selector) {
|
|
146
|
+
entries.push({
|
|
147
|
+
classId: Fr.fromString(identifierString),
|
|
148
|
+
selector,
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
entries.push({
|
|
152
|
+
classId: Fr.fromString(identifierString),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
131
155
|
}
|
|
132
156
|
}
|
|
133
157
|
|
|
134
158
|
return entries;
|
|
135
159
|
}
|
|
136
160
|
|
|
137
|
-
function getDefaultAllowedSetupFunctions():
|
|
161
|
+
function getDefaultAllowedSetupFunctions(): AllowedElement[] {
|
|
138
162
|
return [
|
|
163
|
+
// needed for authwit support
|
|
139
164
|
{
|
|
140
|
-
|
|
141
|
-
selector: FunctionSelector.fromSignature('approve_public_authwit(Field)'),
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
classId: getContractClassFromArtifact(SchnorrHardcodedAccountContractArtifact).id,
|
|
145
|
-
selector: FunctionSelector.fromSignature('approve_public_authwit(Field)'),
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
classId: getContractClassFromArtifact(SchnorrSingleKeyAccountContractArtifact).id,
|
|
149
|
-
selector: FunctionSelector.fromSignature('approve_public_authwit(Field)'),
|
|
165
|
+
address: AuthRegistryAddress,
|
|
150
166
|
},
|
|
167
|
+
// needed for claiming on the same tx as a spend
|
|
151
168
|
{
|
|
152
|
-
|
|
153
|
-
selector: FunctionSelector.fromSignature('
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
// needed for native payments while they are not yet enshrined
|
|
157
|
-
{
|
|
158
|
-
classId: getContractClassFromArtifact(GasTokenContract.artifact).id,
|
|
159
|
-
selector: FunctionSelector.fromSignature('pay_fee(Field)'),
|
|
169
|
+
address: GasTokenAddress,
|
|
170
|
+
selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
|
|
160
171
|
},
|
|
161
|
-
|
|
162
172
|
// needed for private transfers via FPC
|
|
163
173
|
{
|
|
164
174
|
classId: getContractClassFromArtifact(TokenContractArtifact).id,
|
|
165
175
|
selector: FunctionSelector.fromSignature('_increase_public_balance((Field),Field)'),
|
|
166
176
|
},
|
|
167
|
-
|
|
168
177
|
{
|
|
169
178
|
classId: getContractClassFromArtifact(FPCContract.artifact).id,
|
|
170
179
|
selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'),
|
|
@@ -172,19 +181,15 @@ function getDefaultAllowedSetupFunctions(): AllowedFunction[] {
|
|
|
172
181
|
];
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
function getDefaultAllowedTeardownFunctions():
|
|
184
|
+
function getDefaultAllowedTeardownFunctions(): AllowedElement[] {
|
|
176
185
|
return [
|
|
177
|
-
{
|
|
178
|
-
classId: getContractClassFromArtifact(GasTokenContract.artifact).id,
|
|
179
|
-
selector: FunctionSelector.fromSignature('pay_fee(Field)'),
|
|
180
|
-
},
|
|
181
186
|
{
|
|
182
187
|
classId: getContractClassFromArtifact(FPCContract.artifact).id,
|
|
183
|
-
selector: FunctionSelector.fromSignature('
|
|
188
|
+
selector: FunctionSelector.fromSignature('pay_refund((Field),Field,(Field))'),
|
|
184
189
|
},
|
|
185
190
|
{
|
|
186
191
|
classId: getContractClassFromArtifact(FPCContract.artifact).id,
|
|
187
|
-
selector: FunctionSelector.fromSignature('
|
|
192
|
+
selector: FunctionSelector.fromSignature('pay_refund_with_shielded_rebate(Field,(Field),Field)'),
|
|
188
193
|
},
|
|
189
194
|
];
|
|
190
195
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type L2Block } from '@aztec/circuit-types';
|
|
2
2
|
import { type L1PublishStats } from '@aztec/circuit-types/stats';
|
|
3
|
+
import { type EthAddress, type Fr, type Proof } from '@aztec/circuits.js';
|
|
3
4
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
4
6
|
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
5
7
|
|
|
6
8
|
import pick from 'lodash.pick';
|
|
@@ -40,6 +42,10 @@ export type MinimalTransactionReceipt = {
|
|
|
40
42
|
* Pushes txs to the L1 chain and waits for their completion.
|
|
41
43
|
*/
|
|
42
44
|
export interface L1PublisherTxSender {
|
|
45
|
+
getSenderAddress(): Promise<EthAddress>;
|
|
46
|
+
|
|
47
|
+
getSubmitterAddressForBlock(blockNumber: number): Promise<EthAddress>;
|
|
48
|
+
|
|
43
49
|
/**
|
|
44
50
|
* Publishes tx effects to Availability Oracle.
|
|
45
51
|
* @param encodedBody - Encoded block body.
|
|
@@ -91,6 +97,8 @@ export type L1ProcessArgs = {
|
|
|
91
97
|
archive: Buffer;
|
|
92
98
|
/** L2 block body. */
|
|
93
99
|
body: Buffer;
|
|
100
|
+
/** Aggregation object needed to verify the proof */
|
|
101
|
+
aggregationObject: Buffer;
|
|
94
102
|
/** Root rollup proof of the L2 block. */
|
|
95
103
|
proof: Buffer;
|
|
96
104
|
};
|
|
@@ -113,12 +121,18 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
113
121
|
this.sleepTimeMs = config?.l1BlockPublishRetryIntervalMS ?? 60_000;
|
|
114
122
|
}
|
|
115
123
|
|
|
124
|
+
public async isItMyTurnToSubmit(blockNumber: number): Promise<boolean> {
|
|
125
|
+
const submitter = await this.txSender.getSubmitterAddressForBlock(blockNumber);
|
|
126
|
+
const sender = await this.txSender.getSenderAddress();
|
|
127
|
+
return submitter.isZero() || submitter.equals(sender);
|
|
128
|
+
}
|
|
129
|
+
|
|
116
130
|
/**
|
|
117
131
|
* Publishes L2 block on L1.
|
|
118
132
|
* @param block - L2 block to publish.
|
|
119
133
|
* @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
|
|
120
134
|
*/
|
|
121
|
-
public async processL2Block(block: L2Block): Promise<boolean> {
|
|
135
|
+
public async processL2Block(block: L2Block, aggregationObject: Fr[], proof: Proof): Promise<boolean> {
|
|
122
136
|
// TODO(#4148) Remove this block number check, it's here because we don't currently have proper genesis state on the contract
|
|
123
137
|
const lastArchive = block.header.lastArchive.root.toBuffer();
|
|
124
138
|
if (block.number != 1 && !(await this.checkLastArchiveHash(lastArchive))) {
|
|
@@ -166,7 +180,8 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
166
180
|
header: block.header.toBuffer(),
|
|
167
181
|
archive: block.archive.root.toBuffer(),
|
|
168
182
|
body: encodedBody,
|
|
169
|
-
|
|
183
|
+
aggregationObject: serializeToBuffer(aggregationObject),
|
|
184
|
+
proof: proof.withoutPublicInputs(),
|
|
170
185
|
};
|
|
171
186
|
|
|
172
187
|
// Process block
|
|
@@ -242,6 +257,7 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
242
257
|
private async sendPublishTx(encodedBody: Buffer): Promise<string | undefined> {
|
|
243
258
|
while (!this.interrupted) {
|
|
244
259
|
try {
|
|
260
|
+
this.log.info(`TxEffects size=${encodedBody.length} bytes`);
|
|
245
261
|
return await this.txSender.sendPublishTx(encodedBody);
|
|
246
262
|
} catch (err) {
|
|
247
263
|
this.log.error(`TxEffects publish failed`, err);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type L2Block } from '@aztec/circuit-types';
|
|
2
|
+
import { EthAddress } from '@aztec/circuits.js';
|
|
2
3
|
import { createEthereumChain } from '@aztec/ethereum';
|
|
3
4
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
5
|
import { AvailabilityOracleAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -71,6 +72,20 @@ export class ViemTxSender implements L1PublisherTxSender {
|
|
|
71
72
|
});
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
getSenderAddress(): Promise<EthAddress> {
|
|
76
|
+
return Promise.resolve(EthAddress.fromString(this.account.address));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getSubmitterAddressForBlock(blockNumber: number): Promise<EthAddress> {
|
|
80
|
+
try {
|
|
81
|
+
const submitter = await this.rollupContract.read.whoseTurnIsIt([BigInt(blockNumber)]);
|
|
82
|
+
return EthAddress.fromString(submitter);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
this.log.warn(`Failed to get submitter for block ${blockNumber}: ${err}`);
|
|
85
|
+
return EthAddress.ZERO;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
74
89
|
async getCurrentArchive(): Promise<Buffer> {
|
|
75
90
|
const archive = await this.rollupContract.read.archive();
|
|
76
91
|
return Buffer.from(archive.replace('0x', ''), 'hex');
|
|
@@ -145,6 +160,7 @@ export class ViemTxSender implements L1PublisherTxSender {
|
|
|
145
160
|
const args = [
|
|
146
161
|
`0x${encodedData.header.toString('hex')}`,
|
|
147
162
|
`0x${encodedData.archive.toString('hex')}`,
|
|
163
|
+
`0x${encodedData.aggregationObject.toString('hex')}`,
|
|
148
164
|
`0x${encodedData.proof.toString('hex')}`,
|
|
149
165
|
] as const;
|
|
150
166
|
|
package/src/receiver.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type L2Block } from '@aztec/circuit-types';
|
|
2
|
+
import type { Fr, Proof } from '@aztec/circuits.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Given the necessary rollup data, verifies it, and updates the underlying state accordingly to advance the state of the system.
|
|
@@ -8,6 +9,8 @@ export interface L2BlockReceiver {
|
|
|
8
9
|
/**
|
|
9
10
|
* Receive and L2 block and process it, returns true if successful.
|
|
10
11
|
* @param l2BlockData - L2 block to process.
|
|
12
|
+
* @param aggregationObject - The aggregation object for the block's proof.
|
|
13
|
+
* @param proof - The proof for the block.
|
|
11
14
|
*/
|
|
12
|
-
processL2Block(l2BlockData: L2Block): Promise<boolean>;
|
|
15
|
+
processL2Block(l2BlockData: L2Block, aggregationObject: Fr[], proof: Proof): Promise<boolean>;
|
|
13
16
|
}
|
|
@@ -6,9 +6,14 @@ import {
|
|
|
6
6
|
Tx,
|
|
7
7
|
type TxValidator,
|
|
8
8
|
} from '@aztec/circuit-types';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type AllowedElement,
|
|
11
|
+
BlockProofError,
|
|
12
|
+
type BlockProver,
|
|
13
|
+
PROVING_STATUS,
|
|
14
|
+
} from '@aztec/circuit-types/interfaces';
|
|
10
15
|
import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
|
|
11
|
-
import { AztecAddress, EthAddress } from '@aztec/circuits.js';
|
|
16
|
+
import { AztecAddress, EthAddress, type Proof } from '@aztec/circuits.js';
|
|
12
17
|
import { Fr } from '@aztec/foundation/fields';
|
|
13
18
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
14
19
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
@@ -41,8 +46,9 @@ export class Sequencer {
|
|
|
41
46
|
private _feeRecipient = AztecAddress.ZERO;
|
|
42
47
|
private lastPublishedBlock = 0;
|
|
43
48
|
private state = SequencerState.STOPPED;
|
|
44
|
-
private
|
|
45
|
-
private
|
|
49
|
+
private allowedInSetup: AllowedElement[] = [];
|
|
50
|
+
private allowedInTeardown: AllowedElement[] = [];
|
|
51
|
+
private maxBlockSizeInBytes: number = 1024 * 1024;
|
|
46
52
|
|
|
47
53
|
constructor(
|
|
48
54
|
private publisher: L1Publisher,
|
|
@@ -81,12 +87,15 @@ export class Sequencer {
|
|
|
81
87
|
if (config.feeRecipient) {
|
|
82
88
|
this._feeRecipient = config.feeRecipient;
|
|
83
89
|
}
|
|
84
|
-
if (config.
|
|
85
|
-
this.
|
|
90
|
+
if (config.allowedInSetup) {
|
|
91
|
+
this.allowedInSetup = config.allowedInSetup;
|
|
92
|
+
}
|
|
93
|
+
if (config.maxBlockSizeInBytes) {
|
|
94
|
+
this.maxBlockSizeInBytes = config.maxBlockSizeInBytes;
|
|
86
95
|
}
|
|
87
96
|
// TODO(#5917) remove this. it is no longer needed since we don't need to whitelist functions in teardown
|
|
88
|
-
if (config.
|
|
89
|
-
this.
|
|
97
|
+
if (config.allowedInTeardown) {
|
|
98
|
+
this.allowedInTeardown = config.allowedInTeardown;
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
|
|
@@ -153,6 +162,18 @@ export class Sequencer {
|
|
|
153
162
|
return;
|
|
154
163
|
}
|
|
155
164
|
|
|
165
|
+
const historicalHeader = (await this.l2BlockSource.getBlock(-1))?.header;
|
|
166
|
+
const newBlockNumber =
|
|
167
|
+
(historicalHeader === undefined
|
|
168
|
+
? await this.l2BlockSource.getBlockNumber()
|
|
169
|
+
: Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
|
|
170
|
+
|
|
171
|
+
// Do not go forward with new block if not my turn
|
|
172
|
+
if (!(await this.publisher.isItMyTurnToSubmit(newBlockNumber))) {
|
|
173
|
+
this.log.verbose('Not my turn to submit block');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
156
177
|
const workTimer = new Timer();
|
|
157
178
|
this.state = SequencerState.WAITING_FOR_TXS;
|
|
158
179
|
|
|
@@ -163,12 +184,6 @@ export class Sequencer {
|
|
|
163
184
|
}
|
|
164
185
|
this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
|
|
165
186
|
|
|
166
|
-
const historicalHeader = (await this.l2BlockSource.getBlock(-1))?.header;
|
|
167
|
-
const newBlockNumber =
|
|
168
|
-
(historicalHeader === undefined
|
|
169
|
-
? await this.l2BlockSource.getBlockNumber()
|
|
170
|
-
: Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
|
|
171
|
-
|
|
172
187
|
/**
|
|
173
188
|
* We'll call this function before running expensive operations to avoid wasted work.
|
|
174
189
|
*/
|
|
@@ -177,6 +192,9 @@ export class Sequencer {
|
|
|
177
192
|
if (currentBlockNumber + 1 !== newBlockNumber) {
|
|
178
193
|
throw new Error('New block was emitted while building block');
|
|
179
194
|
}
|
|
195
|
+
if (!(await this.publisher.isItMyTurnToSubmit(newBlockNumber))) {
|
|
196
|
+
throw new Error(`Not this sequencer turn to submit block`);
|
|
197
|
+
}
|
|
180
198
|
};
|
|
181
199
|
|
|
182
200
|
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
|
|
@@ -186,10 +204,18 @@ export class Sequencer {
|
|
|
186
204
|
);
|
|
187
205
|
|
|
188
206
|
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
|
|
189
|
-
const
|
|
207
|
+
const allValidTxs = await this.takeValidTxs(
|
|
190
208
|
pendingTxs,
|
|
191
|
-
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.
|
|
209
|
+
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
|
|
192
210
|
);
|
|
211
|
+
|
|
212
|
+
// TODO: We are taking the size of the tx from private-land, but we should be doing this after running
|
|
213
|
+
// public functions. Only reason why we do it here now is because the public processor and orchestrator
|
|
214
|
+
// are set up such that they require knowing the total number of txs in advance. Still, main reason for
|
|
215
|
+
// exceeding max block size in bytes is contract class registration, which happens in private-land. This
|
|
216
|
+
// may break if we start emitting lots of log data from public-land.
|
|
217
|
+
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
|
|
218
|
+
|
|
193
219
|
if (validTxs.length < this.minTxsPerBLock) {
|
|
194
220
|
return;
|
|
195
221
|
}
|
|
@@ -205,16 +231,15 @@ export class Sequencer {
|
|
|
205
231
|
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
206
232
|
const processor = await this.publicProcessorFactory.create(historicalHeader, newGlobalVariables);
|
|
207
233
|
|
|
208
|
-
const emptyTx = processor.makeEmptyProcessedTx();
|
|
209
|
-
|
|
210
234
|
const blockBuildingTimer = new Timer();
|
|
211
235
|
|
|
212
236
|
// We must initialise the block to be a power of 2 in size
|
|
213
237
|
const numRealTxs = validTxs.length;
|
|
214
238
|
const pow2 = Math.log2(numRealTxs);
|
|
239
|
+
// TODO turn this back into a Math.ceil once we can pad blocks to the next-power-of-2 with empty txs
|
|
215
240
|
const totalTxs = 2 ** Math.ceil(pow2);
|
|
216
241
|
const blockSize = Math.max(2, totalTxs);
|
|
217
|
-
const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages
|
|
242
|
+
const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages);
|
|
218
243
|
|
|
219
244
|
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
220
245
|
processor.process(validTxs, blockSize, this.prover, this.txValidatorFactory.validatorForProcessedTxs()),
|
|
@@ -247,8 +272,7 @@ export class Sequencer {
|
|
|
247
272
|
await assertBlockHeight();
|
|
248
273
|
|
|
249
274
|
// Block is proven, now finalise and publish!
|
|
250
|
-
const
|
|
251
|
-
const block = blockResult.block;
|
|
275
|
+
const { block, aggregationObject, proof } = await this.prover.finaliseBlock();
|
|
252
276
|
|
|
253
277
|
await assertBlockHeight();
|
|
254
278
|
|
|
@@ -260,9 +284,14 @@ export class Sequencer {
|
|
|
260
284
|
...block.getStats(),
|
|
261
285
|
} satisfies L2BlockBuiltStats);
|
|
262
286
|
|
|
263
|
-
await this.publishL2Block(block);
|
|
287
|
+
await this.publishL2Block(block, aggregationObject, proof);
|
|
264
288
|
this.log.info(`Submitted rollup block ${block.number} with ${processedTxs.length} transactions`);
|
|
265
289
|
} catch (err) {
|
|
290
|
+
if (BlockProofError.isBlockProofError(err)) {
|
|
291
|
+
const txHashes = err.txHashes.filter(h => !h.isZero());
|
|
292
|
+
this.log.warn(`Proving block failed, removing ${txHashes.length} txs from pool`);
|
|
293
|
+
await this.p2pClient.deleteTxs(txHashes);
|
|
294
|
+
}
|
|
266
295
|
this.log.error(`Rolling back world state DB due to error assembling block`, (err as any).stack);
|
|
267
296
|
// Cancel any further proving on the block
|
|
268
297
|
this.prover?.cancelBlock();
|
|
@@ -274,10 +303,10 @@ export class Sequencer {
|
|
|
274
303
|
* Publishes the L2Block to the rollup contract.
|
|
275
304
|
* @param block - The L2Block to be published.
|
|
276
305
|
*/
|
|
277
|
-
protected async publishL2Block(block: L2Block) {
|
|
306
|
+
protected async publishL2Block(block: L2Block, aggregationObject: Fr[], proof: Proof) {
|
|
278
307
|
// Publishes new block to the network and awaits the tx to be mined
|
|
279
308
|
this.state = SequencerState.PUBLISHING_BLOCK;
|
|
280
|
-
const publishedL2Block = await this.publisher.processL2Block(block);
|
|
309
|
+
const publishedL2Block = await this.publisher.processL2Block(block, aggregationObject, proof);
|
|
281
310
|
if (publishedL2Block) {
|
|
282
311
|
this.lastPublishedBlock = block.number;
|
|
283
312
|
} else {
|
|
@@ -295,6 +324,26 @@ export class Sequencer {
|
|
|
295
324
|
return valid.slice(0, this.maxTxsPerBlock);
|
|
296
325
|
}
|
|
297
326
|
|
|
327
|
+
protected takeTxsWithinMaxSize(txs: Tx[]): Tx[] {
|
|
328
|
+
const maxSize = this.maxBlockSizeInBytes;
|
|
329
|
+
let totalSize = 0;
|
|
330
|
+
|
|
331
|
+
const toReturn: Tx[] = [];
|
|
332
|
+
for (const tx of txs) {
|
|
333
|
+
const txSize = tx.getSize() - tx.proof.toBuffer().length;
|
|
334
|
+
if (totalSize + txSize > maxSize) {
|
|
335
|
+
this.log.warn(
|
|
336
|
+
`Dropping tx ${tx.getTxHash()} with estimated size ${txSize} due to exceeding ${maxSize} block size limit (currently at ${totalSize})`,
|
|
337
|
+
);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
toReturn.push(tx);
|
|
341
|
+
totalSize += txSize;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return toReturn;
|
|
345
|
+
}
|
|
346
|
+
|
|
298
347
|
/**
|
|
299
348
|
* Returns whether the previous block sent has been mined, and all dependencies have caught up with it.
|
|
300
349
|
* @returns Boolean indicating if our dependencies are synced to the latest block.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { type Tx, type TxValidator } from '@aztec/circuit-types';
|
|
1
|
+
import { PublicKernelType, type Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
2
|
import { type AztecAddress, type Fr } from '@aztec/circuits.js';
|
|
3
3
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { GasTokenArtifact } from '@aztec/protocol-contracts/gas-token';
|
|
5
|
+
import { AbstractPhaseManager, computeFeePayerBalanceStorageSlot } from '@aztec/simulator';
|
|
4
6
|
|
|
5
7
|
/** Provides a view into public contract state */
|
|
6
8
|
export interface PublicStateSource {
|
|
@@ -11,12 +13,10 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
11
13
|
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_gas');
|
|
12
14
|
#publicDataSource: PublicStateSource;
|
|
13
15
|
#gasTokenAddress: AztecAddress;
|
|
14
|
-
#requireFees: boolean;
|
|
15
16
|
|
|
16
|
-
constructor(publicDataSource: PublicStateSource, gasTokenAddress: AztecAddress,
|
|
17
|
+
constructor(publicDataSource: PublicStateSource, gasTokenAddress: AztecAddress, public enforceFees: boolean) {
|
|
17
18
|
this.#publicDataSource = publicDataSource;
|
|
18
19
|
this.#gasTokenAddress = gasTokenAddress;
|
|
19
|
-
this.#requireFees = requireFees;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
|
|
@@ -34,9 +34,43 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
34
34
|
return [validTxs, invalidTxs];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
#validateTxFee(
|
|
38
|
-
|
|
37
|
+
async #validateTxFee(tx: Tx): Promise<boolean> {
|
|
38
|
+
const feePayer = tx.data.feePayer;
|
|
39
|
+
// TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx
|
|
40
|
+
if (feePayer.isZero()) {
|
|
41
|
+
if (this.enforceFees) {
|
|
42
|
+
this.#log.warn(`Rejecting transaction ${tx.getTxHash()} due to missing fee payer`);
|
|
43
|
+
} else {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Compute the maximum fee that this tx may pay, based on its gasLimits and maxFeePerGas
|
|
49
|
+
const feeLimit = tx.data.constants.txContext.gasSettings.getFeeLimit();
|
|
39
50
|
|
|
40
|
-
//
|
|
51
|
+
// Read current balance of the feePayer
|
|
52
|
+
const initialBalance = await this.#publicDataSource.storageRead(
|
|
53
|
+
this.#gasTokenAddress,
|
|
54
|
+
computeFeePayerBalanceStorageSlot(feePayer),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// If there is a claim in this tx that increases the fee payer balance in gas token, add it to balance
|
|
58
|
+
const { [PublicKernelType.SETUP]: setupFns } = AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(tx);
|
|
59
|
+
const claimFunctionCall = setupFns.find(
|
|
60
|
+
fn =>
|
|
61
|
+
fn.contractAddress.equals(this.#gasTokenAddress) &&
|
|
62
|
+
fn.callContext.msgSender.equals(this.#gasTokenAddress) &&
|
|
63
|
+
fn.functionSelector.equals(GasTokenArtifact.functions.find(f => f.name === '_increase_public_balance')!) &&
|
|
64
|
+
fn.args[0].equals(feePayer) &&
|
|
65
|
+
!fn.callContext.isStaticCall &&
|
|
66
|
+
!fn.callContext.isDelegateCall,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const balance = claimFunctionCall ? initialBalance.add(claimFunctionCall.args[1]) : initialBalance;
|
|
70
|
+
if (balance.lt(feeLimit)) {
|
|
71
|
+
this.#log.info(`Rejecting transaction due to not enough fee payer balance`, { feePayer, balance, feeLimit });
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
41
75
|
}
|
|
42
76
|
}
|