@aztec/validator-client 0.0.1-commit.7d4e6cd → 0.0.1-commit.7ffbba4
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 +95 -24
- package/dest/block_proposal_handler.d.ts +10 -10
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +76 -76
- package/dest/checkpoint_builder.d.ts +31 -25
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +114 -41
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +33 -14
- package/dest/duties/validation_service.d.ts +20 -7
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +69 -22
- package/dest/factory.d.ts +2 -2
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +3 -2
- package/dest/index.d.ts +1 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +0 -1
- package/dest/key_store/ha_key_store.d.ts +99 -0
- package/dest/key_store/ha_key_store.d.ts.map +1 -0
- package/dest/key_store/ha_key_store.js +208 -0
- package/dest/key_store/index.d.ts +2 -1
- package/dest/key_store/index.d.ts.map +1 -1
- package/dest/key_store/index.js +1 -0
- package/dest/key_store/interface.d.ts +36 -6
- package/dest/key_store/interface.d.ts.map +1 -1
- package/dest/key_store/local_key_store.d.ts +10 -5
- package/dest/key_store/local_key_store.d.ts.map +1 -1
- package/dest/key_store/local_key_store.js +8 -4
- package/dest/key_store/node_keystore_adapter.d.ts +18 -5
- package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
- package/dest/key_store/node_keystore_adapter.js +18 -4
- package/dest/key_store/web3signer_key_store.d.ts +10 -5
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.js +8 -4
- package/dest/metrics.d.ts +12 -3
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +46 -5
- package/dest/validator.d.ts +45 -18
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +262 -98
- package/package.json +21 -17
- package/src/block_proposal_handler.ts +93 -95
- package/src/checkpoint_builder.ts +171 -48
- package/src/config.ts +32 -13
- package/src/duties/validation_service.ts +94 -25
- package/src/factory.ts +2 -0
- package/src/index.ts +0 -1
- package/src/key_store/ha_key_store.ts +269 -0
- package/src/key_store/index.ts +1 -0
- package/src/key_store/interface.ts +44 -5
- package/src/key_store/local_key_store.ts +13 -4
- package/src/key_store/node_keystore_adapter.ts +27 -4
- package/src/key_store/web3signer_key_store.ts +17 -4
- package/src/metrics.ts +63 -6
- package/src/validator.ts +326 -116
- package/dest/tx_validator/index.d.ts +0 -3
- package/dest/tx_validator/index.d.ts.map +0 -1
- package/dest/tx_validator/index.js +0 -2
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/tx_validator/index.ts +0 -2
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -133
package/README.md
CHANGED
|
@@ -77,6 +77,8 @@ These rules must always hold:
|
|
|
77
77
|
2. **Global variables match within checkpoint**: All blocks within the same checkpoint must have identical global variables (except `blockNumber`), which includes the slot number
|
|
78
78
|
3. **inHash is constant**: All blocks in a checkpoint share the same L1-to-L2 messages hash
|
|
79
79
|
4. **Sequential indexWithinCheckpoint**: Block N must have `indexWithinCheckpoint = parent.indexWithinCheckpoint + 1`
|
|
80
|
+
5. **One proposer per slot**: Each slot has exactly one designated proposer. Sending multiple proposals for the same position (slot, indexWithinCheckpoint) with different content is equivocation and slashable
|
|
81
|
+
6. **One attestation per slot**: Validators should only attest to one checkpoint per slot. Attesting to different proposals (different archives) for the same slot is equivocation and slashable
|
|
80
82
|
|
|
81
83
|
## Validation Flow
|
|
82
84
|
|
|
@@ -87,15 +89,14 @@ When a `BlockProposal` is received via P2P, the `BlockProposalHandler` performs:
|
|
|
87
89
|
```
|
|
88
90
|
1. Verify proposer signature
|
|
89
91
|
2. Check proposal is from current/next slot proposer (via BlockProposalValidator)
|
|
90
|
-
3.
|
|
91
|
-
4.
|
|
92
|
-
5.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
9. Compare re-execution result with proposal
|
|
92
|
+
3. Detect duplicate proposals (same slot + indexWithinCheckpoint, different archive) slashing proposer on equivocation
|
|
93
|
+
4. Find parent block by archive root (wait/retry if not synced)
|
|
94
|
+
5. Compute checkpoint number from parent
|
|
95
|
+
6. If indexWithinCheckpoint > 0, then validate global variables match parent (chainId, version, slotNumber, timestamp, coinbase, feeRecipient, gasFees)
|
|
96
|
+
7. Verify inHash matches computed from L1-to-L2 messages
|
|
97
|
+
8. Collect transactions from pool/network/proposal
|
|
98
|
+
9. Re-execute transactions (if enabled)
|
|
99
|
+
10. Compare re-execution result with proposal
|
|
99
100
|
```
|
|
100
101
|
|
|
101
102
|
### Checkpoint Proposal Validation
|
|
@@ -155,19 +156,44 @@ Time | Proposer | Validator
|
|
|
155
156
|
|
|
156
157
|
## Configuration
|
|
157
158
|
|
|
158
|
-
| Flag
|
|
159
|
-
|
|
160
|
-
| `validatorReexecute`
|
|
161
|
-
| `fishermanMode`
|
|
162
|
-
| `alwaysReexecuteBlockProposals`
|
|
163
|
-
| `slashBroadcastedInvalidBlockPenalty` | Penalty amount for invalid proposals (0 = disabled)
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
159
|
+
| Flag | Purpose |
|
|
160
|
+
| ------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
161
|
+
| `validatorReexecute` | Re-execute transactions to verify proposals |
|
|
162
|
+
| `fishermanMode` | Validate proposals but don't broadcast attestations (monitoring only) |
|
|
163
|
+
| `alwaysReexecuteBlockProposals` | Force re-execution even when not in committee |
|
|
164
|
+
| `slashBroadcastedInvalidBlockPenalty` | Penalty amount for invalid proposals (0 = disabled) |
|
|
165
|
+
| `slashDuplicateProposalPenalty` | Penalty amount for duplicate proposals (0 = disabled) |
|
|
166
|
+
| `slashDuplicateAttestationPenalty` | Penalty amount for duplicate attestations (0 = disabled) |
|
|
167
|
+
| `validatorReexecuteDeadlineMs` | Time reserved at end of slot for propagation/publishing |
|
|
168
|
+
| `attestationPollingIntervalMs` | How often to poll for attestations when collecting |
|
|
169
|
+
| `disabledValidators` | Validator addresses to exclude from duties |
|
|
170
|
+
|
|
171
|
+
### High Availability (HA) Keystore
|
|
172
|
+
|
|
173
|
+
When running multiple validator nodes with the same validator keys in a high-availability setup, enable HA signing to prevent double-signing:
|
|
174
|
+
|
|
175
|
+
| Environment Variable | Purpose |
|
|
176
|
+
| -------------------------------------- | ---------------------------------------------------------------------- |
|
|
177
|
+
| `VALIDATOR_HA_SIGNING_ENABLED` | Enable HA signing / slashing protection (default: false) |
|
|
178
|
+
| `VALIDATOR_HA_DATABASE_URL` | PostgreSQL connection string for coordination (required when enabled) |
|
|
179
|
+
| `VALIDATOR_HA_NODE_ID` | Unique identifier for this validator node (required when enabled) |
|
|
180
|
+
| `VALIDATOR_HA_POLLING_INTERVAL_MS` | How often to check duty status (default: 100) |
|
|
181
|
+
| `VALIDATOR_HA_SIGNING_TIMEOUT_MS` | Max wait for in-progress signing (default: 3000) |
|
|
182
|
+
| `VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS` | Max age of stuck duties before cleanup (default: 2\*aztecSlotDuration) |
|
|
183
|
+
|
|
184
|
+
When `VALIDATOR_HA_SIGNING_ENABLED=true`, the validator client automatically:
|
|
185
|
+
|
|
186
|
+
- Creates an HA signer using the provided configuration
|
|
187
|
+
- Wraps the base keystore with `HAKeyStore` for HA-protected signing
|
|
188
|
+
- Coordinates signing across nodes via PostgreSQL to prevent double-signing
|
|
189
|
+
- Provides slashing protection to block conflicting signatures
|
|
190
|
+
|
|
191
|
+
See [`@aztec/validator-ha-signer`](../validator-ha-signer/README.md) for more details.
|
|
167
192
|
|
|
168
193
|
### Fisherman Mode
|
|
169
194
|
|
|
170
195
|
When `fishermanMode: true`, the validator:
|
|
196
|
+
|
|
171
197
|
- Validates all proposals (block and checkpoint)
|
|
172
198
|
- Re-executes transactions
|
|
173
199
|
- Creates attestations internally for validation
|
|
@@ -179,6 +205,7 @@ This is useful for monitoring network health without participating in consensus.
|
|
|
179
205
|
### Key Methods
|
|
180
206
|
|
|
181
207
|
**ValidatorClient** (`validator.ts`):
|
|
208
|
+
|
|
182
209
|
- `validateBlockProposal(proposal, sender)` → `boolean`: Validates block, optionally re-executes, emits slash events
|
|
183
210
|
- `attestToCheckpointProposal(proposal, sender)` → `CheckpointAttestation[]?`: Validates checkpoint and creates attestations
|
|
184
211
|
- `collectAttestations(proposal, required, deadline)` → `CheckpointAttestation[]`: Waits for attestations from other validators
|
|
@@ -186,14 +213,58 @@ This is useful for monitoring network health without participating in consensus.
|
|
|
186
213
|
- `createCheckpointProposal(...)` → `CheckpointProposal`: Creates and signs a checkpoint proposal
|
|
187
214
|
|
|
188
215
|
**BlockProposalHandler** (`block_proposal_handler.ts`):
|
|
216
|
+
|
|
189
217
|
- `handleBlockProposal(proposal, sender, shouldReexecute)` → `ValidationResult`: Full block validation pipeline
|
|
190
218
|
- `reexecuteTransactions(proposal, blockNumber, txs, messages)` → `ReexecutionResult`: Re-runs transactions and compares state
|
|
191
219
|
|
|
192
220
|
**ValidationService** (`duties/validation_service.ts`):
|
|
221
|
+
|
|
193
222
|
- `createBlockProposal(...)` → `BlockProposal`: Signs block proposal with validator key
|
|
194
223
|
- `createCheckpointProposal(...)` → `CheckpointProposal`: Signs checkpoint proposal
|
|
195
224
|
- `attestToCheckpointProposal(proposal, attestors)` → `CheckpointAttestation[]`: Creates attestations for given addresses
|
|
196
225
|
|
|
226
|
+
## Block Building Limits
|
|
227
|
+
|
|
228
|
+
L1 enforces gas and blob capacity per checkpoint. The node enforces these during block building to avoid L1 rejection. Three dimensions are metered: L2 gas (mana), DA gas, and blob fields. DA gas maps to blob fields today (`daGas = blobFields * 32`) but both are tracked independently.
|
|
229
|
+
|
|
230
|
+
### Checkpoint limits
|
|
231
|
+
|
|
232
|
+
| Dimension | Source | Budget |
|
|
233
|
+
| --- | --- | --- |
|
|
234
|
+
| L2 gas (mana) | `rollup.getManaLimit()` | Fetched from L1 at startup |
|
|
235
|
+
| DA gas | `MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT` | 786,432 (6 blobs × 4096 fields × 32 gas/field) |
|
|
236
|
+
| Blob fields | `BLOBS_PER_CHECKPOINT × FIELDS_PER_BLOB` | 24,576 minus checkpoint/block-end overhead |
|
|
237
|
+
|
|
238
|
+
### Per-block budgets
|
|
239
|
+
|
|
240
|
+
Per-block budgets prevent one block from consuming the entire checkpoint budget.
|
|
241
|
+
|
|
242
|
+
**Proposer**: `computeBlockLimits()` derives budgets at startup as `min(checkpointLimit, ceil(checkpointLimit / maxBlocks * multiplier))`, where `maxBlocks` comes from the timetable and `multiplier` defaults to 2. The multiplier greater than 1 allows early blocks to use more than their even share of the checkpoint budget, since different blocks hit different limit dimensions (L2 gas, DA gas, blob fields) — a strict even split would waste capacity. Operators can override via `SEQ_MAX_L2_BLOCK_GAS` / `SEQ_MAX_DA_BLOCK_GAS` / `SEQ_MAX_TX_PER_BLOCK` (capped at checkpoint limits). Per-block TX limits follow the same derivation pattern when `SEQ_MAX_TX_PER_CHECKPOINT` is set.
|
|
243
|
+
|
|
244
|
+
**Validator**: Optionally enforces per-block limits via `VALIDATOR_MAX_L2_BLOCK_GAS`, `VALIDATOR_MAX_DA_BLOCK_GAS`, and `VALIDATOR_MAX_TX_PER_BLOCK`. When set, these are passed to `buildBlock` during re-execution and to `validateCheckpoint` for final validation. When unset, no per-block limit is enforced for that dimension (checkpoint-level protocol limits still apply). These are independent of the `SEQ_` vars so operators can tune proposer and validation limits separately.
|
|
245
|
+
|
|
246
|
+
**Checkpoint-level capping**: `CheckpointBuilder.capLimitsByCheckpointBudgets()` always runs before tx processing, capping per-block limits by `checkpointBudget - sum(used by prior blocks)` for all three gas dimensions and for transaction count (when `SEQ_MAX_TX_PER_CHECKPOINT` is set). This applies to both proposer and validator paths.
|
|
247
|
+
|
|
248
|
+
### Per-transaction enforcement
|
|
249
|
+
|
|
250
|
+
**Mempool entry** (`GasLimitsValidator`): L2 gas must be ≤ `MAX_PROCESSABLE_L2_GAS` (6,540,000) and ≥ fixed minimums.
|
|
251
|
+
|
|
252
|
+
**Block building** (`PublicProcessor.process`): Before processing, txs are skipped if their estimated blob fields or gas limits would exceed the block budget. After processing, actual values are checked and the tx is reverted if limits are exceeded.
|
|
253
|
+
|
|
254
|
+
### Gas limit configuration
|
|
255
|
+
|
|
256
|
+
| Variable | Default | Description |
|
|
257
|
+
| --- | --- | --- |
|
|
258
|
+
| `SEQ_MAX_L2_BLOCK_GAS` | *auto* | Per-block L2 gas. Auto-derived from `rollupManaLimit / maxBlocks * multiplier`. |
|
|
259
|
+
| `SEQ_MAX_DA_BLOCK_GAS` | *auto* | Per-block DA gas. Auto-derived from checkpoint DA limit / maxBlocks * multiplier. |
|
|
260
|
+
| `SEQ_MAX_TX_PER_BLOCK` | *none* | Per-block tx count. If `SEQ_MAX_TX_PER_CHECKPOINT` is set and per-block is not, derived as `ceil(checkpointLimit / maxBlocks * multiplier)`. |
|
|
261
|
+
| `SEQ_MAX_TX_PER_CHECKPOINT` | *none* | Total txs across all blocks in a checkpoint. When set, per-block tx limit is derived from it (unless explicitly overridden) and checkpoint-level capping is enforced. |
|
|
262
|
+
| `SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER` | 2 | Multiplier for per-block budget computation. |
|
|
263
|
+
| `VALIDATOR_MAX_L2_BLOCK_GAS` | *none* | Per-block L2 gas limit for validation. Proposals exceeding this are rejected. |
|
|
264
|
+
| `VALIDATOR_MAX_DA_BLOCK_GAS` | *none* | Per-block DA gas limit for validation. Proposals exceeding this are rejected. |
|
|
265
|
+
| `VALIDATOR_MAX_TX_PER_BLOCK` | *none* | Per-block tx count limit for validation. Proposals exceeding this are rejected. |
|
|
266
|
+
| `VALIDATOR_MAX_TX_PER_CHECKPOINT` | *none* | Per-checkpoint tx count limit for validation. Proposals exceeding this are rejected. |
|
|
267
|
+
|
|
197
268
|
## Testing Patterns
|
|
198
269
|
|
|
199
270
|
### Common Mocks
|
|
@@ -204,7 +275,7 @@ Tests typically mock these dependencies:
|
|
|
204
275
|
let epochCache: MockProxy<EpochCache>;
|
|
205
276
|
let blockSource: MockProxy<L2BlockSource>;
|
|
206
277
|
let txProvider: MockProxy<TxProvider>;
|
|
207
|
-
let
|
|
278
|
+
let checkpointsBuilder: MockProxy<FullNodeCheckpointsBuilder>;
|
|
208
279
|
let p2pClient: MockProxy<P2P>;
|
|
209
280
|
|
|
210
281
|
beforeEach(() => {
|
|
@@ -219,19 +290,19 @@ beforeEach(() => {
|
|
|
219
290
|
Use factory functions from `@aztec/stdlib/testing`:
|
|
220
291
|
|
|
221
292
|
```typescript
|
|
222
|
-
import { makeBlockProposal,
|
|
293
|
+
import { makeBlockHeader, makeBlockProposal, makeCheckpointHeader, makeCheckpointProposal } from '@aztec/stdlib/testing';
|
|
223
294
|
|
|
224
295
|
// These are async - always await
|
|
225
296
|
const blockProposal = await makeBlockProposal({
|
|
226
|
-
blockHeader:
|
|
297
|
+
blockHeader: makeBlockHeader(1, { blockNumber: BlockNumber(100), slotNumber: SlotNumber(100) }),
|
|
227
298
|
indexWithinCheckpoint: 0,
|
|
228
299
|
signer: Secp256k1Signer.random(),
|
|
229
300
|
});
|
|
230
301
|
|
|
231
302
|
const checkpointProposal = await makeCheckpointProposal({
|
|
232
|
-
checkpointHeader:
|
|
303
|
+
checkpointHeader: makeCheckpointHeader(1, { slotNumber: SlotNumber(100) }),
|
|
233
304
|
signer: proposer,
|
|
234
|
-
lastBlock: { blockHeader, txs },
|
|
305
|
+
lastBlock: { blockHeader: makeBlockHeader(1), txs },
|
|
235
306
|
});
|
|
236
307
|
```
|
|
237
308
|
|
|
@@ -242,7 +313,7 @@ For tests that exercise re-execution:
|
|
|
242
313
|
```typescript
|
|
243
314
|
// Mock parent block lookup
|
|
244
315
|
blockSource.getBlockHeaderByArchive.mockResolvedValue(parentBlockHeader);
|
|
245
|
-
blockSource.
|
|
316
|
+
blockSource.getL2Block.mockResolvedValue({
|
|
246
317
|
checkpointNumber: CheckpointNumber(1),
|
|
247
318
|
indexWithinCheckpoint: 0,
|
|
248
319
|
header: { globalVariables: parentGlobalVariables },
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
+
import type { EpochCache } from '@aztec/epoch-cache';
|
|
1
2
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
4
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
4
5
|
import type { P2P, PeerId } from '@aztec/p2p';
|
|
5
|
-
import { TxProvider } from '@aztec/p2p';
|
|
6
6
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
7
|
-
import type {
|
|
8
|
-
import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
7
|
+
import type { L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
8
|
+
import type { ITxProvider, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
9
9
|
import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
10
10
|
import type { BlockProposal } from '@aztec/stdlib/p2p';
|
|
11
|
-
import {
|
|
11
|
+
import type { FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
12
12
|
import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
|
|
13
13
|
import type { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
14
14
|
import type { ValidatorMetrics } from './metrics.js';
|
|
15
15
|
export type BlockProposalValidationFailureReason = 'invalid_proposal' | 'parent_block_not_found' | 'parent_block_wrong_slot' | 'in_hash_mismatch' | 'global_variables_mismatch' | 'block_number_already_exists' | 'txs_not_available' | 'state_mismatch' | 'failed_txs' | 'timeout' | 'unknown_error';
|
|
16
16
|
type ReexecuteTransactionsResult = {
|
|
17
|
-
block:
|
|
17
|
+
block: L2Block;
|
|
18
18
|
failedTxs: FailedTx[];
|
|
19
19
|
reexecutionTimeMs: number;
|
|
20
20
|
totalManaUsed: number;
|
|
@@ -38,13 +38,14 @@ export declare class BlockProposalHandler {
|
|
|
38
38
|
private l1ToL2MessageSource;
|
|
39
39
|
private txProvider;
|
|
40
40
|
private blockProposalValidator;
|
|
41
|
+
private epochCache;
|
|
41
42
|
private config;
|
|
42
43
|
private metrics?;
|
|
43
44
|
private dateProvider;
|
|
44
45
|
private log;
|
|
45
46
|
readonly tracer: Tracer;
|
|
46
|
-
constructor(checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, blockSource: L2BlockSource & L2BlockSink, l1ToL2MessageSource: L1ToL2MessageSource, txProvider:
|
|
47
|
-
|
|
47
|
+
constructor(checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, blockSource: L2BlockSource & L2BlockSink, l1ToL2MessageSource: L1ToL2MessageSource, txProvider: ITxProvider, blockProposalValidator: BlockProposalValidator, epochCache: EpochCache, config: ValidatorClientFullConfig, metrics?: ValidatorMetrics | undefined, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
|
|
48
|
+
register(p2pClient: P2P, shouldReexecute: boolean): BlockProposalHandler;
|
|
48
49
|
handleBlockProposal(proposal: BlockProposal, proposalSender: PeerId, shouldReexecute: boolean): Promise<BlockProposalValidationResult>;
|
|
49
50
|
private getParentBlock;
|
|
50
51
|
private computeCheckpointNumber;
|
|
@@ -55,9 +56,8 @@ export declare class BlockProposalHandler {
|
|
|
55
56
|
*/
|
|
56
57
|
private validateNonFirstBlockInCheckpoint;
|
|
57
58
|
private getReexecutionDeadline;
|
|
58
|
-
private getBlocksInCheckpoint;
|
|
59
59
|
private getReexecuteFailureReason;
|
|
60
|
-
reexecuteTransactions(proposal: BlockProposal, blockNumber: BlockNumber, checkpointNumber: CheckpointNumber, txs: Tx[], l1ToL2Messages: Fr[]): Promise<ReexecuteTransactionsResult>;
|
|
60
|
+
reexecuteTransactions(proposal: BlockProposal, blockNumber: BlockNumber, checkpointNumber: CheckpointNumber, txs: Tx[], l1ToL2Messages: Fr[], previousCheckpointOutHashes: Fr[]): Promise<ReexecuteTransactionsResult>;
|
|
61
61
|
}
|
|
62
62
|
export {};
|
|
63
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
63
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvY2tfcHJvcG9zYWxfaGFuZGxlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Jsb2NrX3Byb3Bvc2FsX2hhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBYyxNQUFNLGlDQUFpQyxDQUFDO0FBRTVGLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUlwRCxPQUFPLEVBQUUsWUFBWSxFQUFTLE1BQU0seUJBQXlCLENBQUM7QUFDOUQsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQztBQUM5QyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNuRSxPQUFPLEtBQUssRUFBYSxPQUFPLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRzFGLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSx5QkFBeUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3RILE9BQU8sRUFBRSxLQUFLLG1CQUFtQixFQUFtQyxNQUFNLHlCQUF5QixDQUFDO0FBQ3BHLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxFQUE2QixRQUFRLEVBQUUsRUFBRSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFPaEYsT0FBTyxFQUFFLEtBQUssZUFBZSxFQUFFLEtBQUssTUFBTSxFQUFzQixNQUFNLHlCQUF5QixDQUFDO0FBRWhHLE9BQU8sS0FBSyxFQUFFLDBCQUEwQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDMUUsT0FBTyxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFckQsTUFBTSxNQUFN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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_proposal_handler.d.ts","sourceRoot":"","sources":["../src/block_proposal_handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAc,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"block_proposal_handler.d.ts","sourceRoot":"","sources":["../src/block_proposal_handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -65,12 +65,14 @@ function _ts_dispose_resources(env) {
|
|
|
65
65
|
}
|
|
66
66
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
67
67
|
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
68
|
+
import { pick } from '@aztec/foundation/collection';
|
|
68
69
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
69
70
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
70
71
|
import { createLogger } from '@aztec/foundation/log';
|
|
71
72
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
72
73
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
73
|
-
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
74
|
+
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
75
|
+
import { Gas } from '@aztec/stdlib/gas';
|
|
74
76
|
import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
75
77
|
import { ReExFailedTxsError, ReExStateMismatchError, ReExTimeoutError, TransactionsNotAvailableError } from '@aztec/stdlib/validators';
|
|
76
78
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
@@ -81,18 +83,20 @@ export class BlockProposalHandler {
|
|
|
81
83
|
l1ToL2MessageSource;
|
|
82
84
|
txProvider;
|
|
83
85
|
blockProposalValidator;
|
|
86
|
+
epochCache;
|
|
84
87
|
config;
|
|
85
88
|
metrics;
|
|
86
89
|
dateProvider;
|
|
87
90
|
log;
|
|
88
91
|
tracer;
|
|
89
|
-
constructor(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, config, metrics, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator:block-proposal-handler')){
|
|
92
|
+
constructor(checkpointsBuilder, worldState, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, epochCache, config, metrics, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator:block-proposal-handler')){
|
|
90
93
|
this.checkpointsBuilder = checkpointsBuilder;
|
|
91
94
|
this.worldState = worldState;
|
|
92
95
|
this.blockSource = blockSource;
|
|
93
96
|
this.l1ToL2MessageSource = l1ToL2MessageSource;
|
|
94
97
|
this.txProvider = txProvider;
|
|
95
98
|
this.blockProposalValidator = blockProposalValidator;
|
|
99
|
+
this.epochCache = epochCache;
|
|
96
100
|
this.config = config;
|
|
97
101
|
this.metrics = metrics;
|
|
98
102
|
this.dateProvider = dateProvider;
|
|
@@ -102,23 +106,27 @@ export class BlockProposalHandler {
|
|
|
102
106
|
}
|
|
103
107
|
this.tracer = telemetry.getTracer('BlockProposalHandler');
|
|
104
108
|
}
|
|
105
|
-
|
|
106
|
-
// Non-validator handler that re-executes for monitoring but does not attest.
|
|
109
|
+
register(p2pClient, shouldReexecute) {
|
|
110
|
+
// Non-validator handler that processes or re-executes for monitoring but does not attest.
|
|
107
111
|
// Returns boolean indicating whether the proposal was valid.
|
|
108
112
|
const handler = async (proposal, proposalSender)=>{
|
|
109
113
|
try {
|
|
110
|
-
const
|
|
114
|
+
const { slotNumber, blockNumber } = proposal;
|
|
115
|
+
const result = await this.handleBlockProposal(proposal, proposalSender, shouldReexecute);
|
|
111
116
|
if (result.isValid) {
|
|
112
|
-
this.log.info(`Non-validator
|
|
117
|
+
this.log.info(`Non-validator block proposal ${blockNumber} at slot ${slotNumber} handled`, {
|
|
113
118
|
blockNumber: result.blockNumber,
|
|
119
|
+
slotNumber,
|
|
114
120
|
reexecutionTimeMs: result.reexecutionResult?.reexecutionTimeMs,
|
|
115
121
|
totalManaUsed: result.reexecutionResult?.totalManaUsed,
|
|
116
|
-
numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0
|
|
122
|
+
numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0,
|
|
123
|
+
reexecuted: shouldReexecute
|
|
117
124
|
});
|
|
118
125
|
return true;
|
|
119
126
|
} else {
|
|
120
|
-
this.log.warn(`Non-validator
|
|
127
|
+
this.log.warn(`Non-validator block proposal ${blockNumber} at slot ${slotNumber} failed processing with ${result.reason}`, {
|
|
121
128
|
blockNumber: result.blockNumber,
|
|
129
|
+
slotNumber,
|
|
122
130
|
reason: result.reason
|
|
123
131
|
});
|
|
124
132
|
return false;
|
|
@@ -153,8 +161,8 @@ export class BlockProposalHandler {
|
|
|
153
161
|
});
|
|
154
162
|
// Check that the proposal is from the current proposer, or the next proposer
|
|
155
163
|
// This should have been handled by the p2p layer, but we double check here out of caution
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
164
|
+
const validationResult = await this.blockProposalValidator.validate(proposal);
|
|
165
|
+
if (validationResult.result !== 'accept') {
|
|
158
166
|
this.log.warn(`Proposal is not valid, skipping processing`, proposalInfo);
|
|
159
167
|
return {
|
|
160
168
|
isValid: false,
|
|
@@ -162,18 +170,18 @@ export class BlockProposalHandler {
|
|
|
162
170
|
};
|
|
163
171
|
}
|
|
164
172
|
// Check that the parent proposal is a block we know, otherwise reexecution would fail
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
173
|
+
const parentBlock = await this.getParentBlock(proposal);
|
|
174
|
+
if (parentBlock === undefined) {
|
|
167
175
|
this.log.warn(`Parent block for proposal not found, skipping processing`, proposalInfo);
|
|
168
176
|
return {
|
|
169
177
|
isValid: false,
|
|
170
178
|
reason: 'parent_block_not_found'
|
|
171
179
|
};
|
|
172
180
|
}
|
|
173
|
-
// Check that the parent block's slot is
|
|
174
|
-
if (
|
|
175
|
-
this.log.warn(`Parent block slot is greater than
|
|
176
|
-
parentBlockSlot:
|
|
181
|
+
// Check that the parent block's slot is not greater than the proposal's slot.
|
|
182
|
+
if (parentBlock !== 'genesis' && parentBlock.header.getSlot() > slotNumber) {
|
|
183
|
+
this.log.warn(`Parent block slot is greater than proposal slot, skipping processing`, {
|
|
184
|
+
parentBlockSlot: parentBlock.header.getSlot().toString(),
|
|
177
185
|
proposalSlot: slotNumber.toString(),
|
|
178
186
|
...proposalInfo
|
|
179
187
|
});
|
|
@@ -183,7 +191,7 @@ export class BlockProposalHandler {
|
|
|
183
191
|
};
|
|
184
192
|
}
|
|
185
193
|
// Compute the block number based on the parent block
|
|
186
|
-
const blockNumber =
|
|
194
|
+
const blockNumber = parentBlock === 'genesis' ? BlockNumber(INITIAL_L2_BLOCK_NUM) : BlockNumber(parentBlock.header.getBlockNumber() + 1);
|
|
187
195
|
// Check that this block number does not exist already
|
|
188
196
|
const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
|
|
189
197
|
if (existingBlock) {
|
|
@@ -200,8 +208,16 @@ export class BlockProposalHandler {
|
|
|
200
208
|
pinnedPeer: proposalSender,
|
|
201
209
|
deadline: this.getReexecutionDeadline(slotNumber, config)
|
|
202
210
|
});
|
|
211
|
+
// If reexecution is disabled, bail. We are just interested in triggering tx collection.
|
|
212
|
+
if (!shouldReexecute) {
|
|
213
|
+
this.log.info(`Received valid block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, proposalInfo);
|
|
214
|
+
return {
|
|
215
|
+
isValid: true,
|
|
216
|
+
blockNumber
|
|
217
|
+
};
|
|
218
|
+
}
|
|
203
219
|
// Compute the checkpoint number for this block and validate checkpoint consistency
|
|
204
|
-
const checkpointResult =
|
|
220
|
+
const checkpointResult = this.computeCheckpointNumber(proposal, parentBlock, proposalInfo);
|
|
205
221
|
if (checkpointResult.reason) {
|
|
206
222
|
return {
|
|
207
223
|
isValid: false,
|
|
@@ -238,29 +254,32 @@ export class BlockProposalHandler {
|
|
|
238
254
|
reason: 'txs_not_available'
|
|
239
255
|
};
|
|
240
256
|
}
|
|
257
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
258
|
+
const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
|
|
259
|
+
const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch)).filter((c)=>c.checkpointNumber < checkpointNumber).map((c)=>c.checkpointOutHash);
|
|
241
260
|
// Try re-executing the transactions in the proposal if needed
|
|
242
261
|
let reexecutionResult;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
};
|
|
256
|
-
}
|
|
262
|
+
try {
|
|
263
|
+
this.log.verbose(`Re-executing transactions in the proposal`, proposalInfo);
|
|
264
|
+
reexecutionResult = await this.reexecuteTransactions(proposal, blockNumber, checkpointNumber, txs, l1ToL2Messages, previousCheckpointOutHashes);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
267
|
+
const reason = this.getReexecuteFailureReason(error);
|
|
268
|
+
return {
|
|
269
|
+
isValid: false,
|
|
270
|
+
blockNumber,
|
|
271
|
+
reason,
|
|
272
|
+
reexecutionResult
|
|
273
|
+
};
|
|
257
274
|
}
|
|
258
275
|
// If we succeeded, push this block into the archiver (unless disabled)
|
|
259
|
-
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
260
276
|
if (reexecutionResult?.block && this.config.skipPushProposedBlocksToArchiver === false) {
|
|
261
277
|
await this.blockSource.addBlock(reexecutionResult?.block);
|
|
262
278
|
}
|
|
263
|
-
this.log.info(`Successfully
|
|
279
|
+
this.log.info(`Successfully re-executed block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, {
|
|
280
|
+
...proposalInfo,
|
|
281
|
+
...pick(reexecutionResult, 'reexecutionTimeMs', 'totalManaUsed')
|
|
282
|
+
});
|
|
264
283
|
return {
|
|
265
284
|
isValid: true,
|
|
266
285
|
blockNumber,
|
|
@@ -279,7 +298,7 @@ export class BlockProposalHandler {
|
|
|
279
298
|
const currentTime = this.dateProvider.now();
|
|
280
299
|
const timeoutDurationMs = deadline.getTime() - currentTime;
|
|
281
300
|
try {
|
|
282
|
-
return await this.blockSource.
|
|
301
|
+
return await this.blockSource.getBlockDataByArchive(parentArchive) ?? (timeoutDurationMs <= 0 ? undefined : await retryUntil(()=>this.blockSource.syncImmediate().then(()=>this.blockSource.getBlockDataByArchive(parentArchive)), 'force archiver sync', timeoutDurationMs / 1000, 0.5));
|
|
283
302
|
} catch (err) {
|
|
284
303
|
if (err instanceof TimeoutError) {
|
|
285
304
|
this.log.debug(`Timed out getting parent block by archive root`, {
|
|
@@ -293,8 +312,8 @@ export class BlockProposalHandler {
|
|
|
293
312
|
return undefined;
|
|
294
313
|
}
|
|
295
314
|
}
|
|
296
|
-
|
|
297
|
-
if (
|
|
315
|
+
computeCheckpointNumber(proposal, parentBlock, proposalInfo) {
|
|
316
|
+
if (parentBlock === 'genesis') {
|
|
298
317
|
// First block is in checkpoint 1
|
|
299
318
|
if (proposal.indexWithinCheckpoint !== 0) {
|
|
300
319
|
this.log.warn(`First block proposal has non-zero indexWithinCheckpoint`, proposalInfo);
|
|
@@ -306,20 +325,9 @@ export class BlockProposalHandler {
|
|
|
306
325
|
checkpointNumber: CheckpointNumber.INITIAL
|
|
307
326
|
};
|
|
308
327
|
}
|
|
309
|
-
// Get the parent block to find its checkpoint number
|
|
310
|
-
// TODO(palla/mbps): The block header should include the checkpoint number to avoid this lookup,
|
|
311
|
-
// or at least the L2BlockSource should return a different struct that includes it.
|
|
312
|
-
const parentBlockNumber = parentBlockHeader.getBlockNumber();
|
|
313
|
-
const parentBlock = await this.blockSource.getL2BlockNew(parentBlockNumber);
|
|
314
|
-
if (!parentBlock) {
|
|
315
|
-
this.log.warn(`Parent block ${parentBlockNumber} not found in archiver`, proposalInfo);
|
|
316
|
-
return {
|
|
317
|
-
reason: 'invalid_proposal'
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
328
|
if (proposal.indexWithinCheckpoint === 0) {
|
|
321
329
|
// If this is the first block in a new checkpoint, increment the checkpoint number
|
|
322
|
-
if (!(proposal.blockHeader.getSlot() >
|
|
330
|
+
if (!(proposal.blockHeader.getSlot() > parentBlock.header.getSlot())) {
|
|
323
331
|
this.log.warn(`Slot should be greater than parent block slot for first block in checkpoint`, proposalInfo);
|
|
324
332
|
return {
|
|
325
333
|
reason: 'invalid_proposal'
|
|
@@ -336,7 +344,7 @@ export class BlockProposalHandler {
|
|
|
336
344
|
reason: 'invalid_proposal'
|
|
337
345
|
};
|
|
338
346
|
}
|
|
339
|
-
if (proposal.blockHeader.getSlot() !==
|
|
347
|
+
if (proposal.blockHeader.getSlot() !== parentBlock.header.getSlot()) {
|
|
340
348
|
this.log.warn(`Slot should be equal to parent block slot for non-first block in checkpoint`, proposalInfo);
|
|
341
349
|
return {
|
|
342
350
|
reason: 'invalid_proposal'
|
|
@@ -434,23 +442,7 @@ export class BlockProposalHandler {
|
|
|
434
442
|
}
|
|
435
443
|
getReexecutionDeadline(slot, config) {
|
|
436
444
|
const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
|
|
437
|
-
|
|
438
|
-
return new Date(nextSlotTimestampSeconds * 1000 - msNeededForPropagationAndPublishing);
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Gets all prior blocks in the same checkpoint (same slot and checkpoint number) up to but not including upToBlockNumber.
|
|
442
|
-
*/ async getBlocksInCheckpoint(slot, upToBlockNumber, checkpointNumber) {
|
|
443
|
-
const blocks = [];
|
|
444
|
-
let currentBlockNumber = BlockNumber(upToBlockNumber - 1);
|
|
445
|
-
while(currentBlockNumber >= INITIAL_L2_BLOCK_NUM){
|
|
446
|
-
const block = await this.blockSource.getL2BlockNew(currentBlockNumber);
|
|
447
|
-
if (!block || block.header.getSlot() !== slot || block.checkpointNumber !== checkpointNumber) {
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
blocks.unshift(block);
|
|
451
|
-
currentBlockNumber = BlockNumber(currentBlockNumber - 1);
|
|
452
|
-
}
|
|
453
|
-
return blocks;
|
|
445
|
+
return new Date(nextSlotTimestampSeconds * 1000);
|
|
454
446
|
}
|
|
455
447
|
getReexecuteFailureReason(err) {
|
|
456
448
|
if (err instanceof ReExStateMismatchError) {
|
|
@@ -463,7 +455,7 @@ export class BlockProposalHandler {
|
|
|
463
455
|
return 'unknown_error';
|
|
464
456
|
}
|
|
465
457
|
}
|
|
466
|
-
async reexecuteTransactions(proposal, blockNumber, checkpointNumber, txs, l1ToL2Messages) {
|
|
458
|
+
async reexecuteTransactions(proposal, blockNumber, checkpointNumber, txs, l1ToL2Messages, previousCheckpointOutHashes) {
|
|
467
459
|
const env = {
|
|
468
460
|
stack: [],
|
|
469
461
|
error: void 0,
|
|
@@ -480,34 +472,41 @@ export class BlockProposalHandler {
|
|
|
480
472
|
const timer = new Timer();
|
|
481
473
|
const slot = proposal.slotNumber;
|
|
482
474
|
const config = this.checkpointsBuilder.getConfig();
|
|
483
|
-
// Get prior blocks in this checkpoint (same slot
|
|
484
|
-
const
|
|
475
|
+
// Get prior blocks in this checkpoint (same slot before current block)
|
|
476
|
+
const allBlocksInSlot = await this.blockSource.getBlocksForSlot(slot);
|
|
477
|
+
const priorBlocks = allBlocksInSlot.filter((b)=>b.number < blockNumber && b.header.getSlot() === slot);
|
|
485
478
|
// Fork before the block to be built
|
|
486
479
|
const parentBlockNumber = BlockNumber(blockNumber - 1);
|
|
487
|
-
|
|
488
|
-
|
|
480
|
+
await this.worldState.syncImmediate(parentBlockNumber);
|
|
481
|
+
const fork = _ts_add_disposable_resource(env, await this.worldState.fork(parentBlockNumber), true);
|
|
482
|
+
// Build checkpoint constants from proposal (excludes blockNumber which is per-block)
|
|
489
483
|
const constants = {
|
|
490
484
|
chainId: new Fr(config.l1ChainId),
|
|
491
485
|
version: new Fr(config.rollupVersion),
|
|
492
486
|
slotNumber: slot,
|
|
487
|
+
timestamp: blockHeader.globalVariables.timestamp,
|
|
493
488
|
coinbase: blockHeader.globalVariables.coinbase,
|
|
494
489
|
feeRecipient: blockHeader.globalVariables.feeRecipient,
|
|
495
490
|
gasFees: blockHeader.globalVariables.gasFees
|
|
496
491
|
};
|
|
497
492
|
// Create checkpoint builder with prior blocks
|
|
498
|
-
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, l1ToL2Messages, fork, priorBlocks);
|
|
493
|
+
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, 0n, l1ToL2Messages, previousCheckpointOutHashes, fork, priorBlocks, this.log.getBindings());
|
|
499
494
|
// Build the new block
|
|
500
495
|
const deadline = this.getReexecutionDeadline(slot, config);
|
|
496
|
+
const maxBlockGas = this.config.validateMaxL2BlockGas !== undefined || this.config.validateMaxDABlockGas !== undefined ? new Gas(this.config.validateMaxDABlockGas ?? Infinity, this.config.validateMaxL2BlockGas ?? Infinity) : undefined;
|
|
501
497
|
const result = await checkpointBuilder.buildBlock(txs, blockNumber, blockHeader.globalVariables.timestamp, {
|
|
502
498
|
deadline,
|
|
503
|
-
expectedEndState: blockHeader.state
|
|
499
|
+
expectedEndState: blockHeader.state,
|
|
500
|
+
maxTransactions: this.config.validateMaxTxsPerBlock,
|
|
501
|
+
maxBlockGas
|
|
504
502
|
});
|
|
505
503
|
const { block, failedTxs } = result;
|
|
506
504
|
const numFailedTxs = failedTxs.length;
|
|
507
|
-
this.log.verbose(`
|
|
505
|
+
this.log.verbose(`Block proposal ${blockNumber} at slot ${slot} transaction re-execution complete`, {
|
|
508
506
|
numFailedTxs,
|
|
509
507
|
numProposalTxs: txHashes.length,
|
|
510
508
|
numProcessedTxs: block.body.txEffects.length,
|
|
509
|
+
blockNumber,
|
|
511
510
|
slot
|
|
512
511
|
});
|
|
513
512
|
if (numFailedTxs > 0) {
|
|
@@ -545,7 +544,8 @@ export class BlockProposalHandler {
|
|
|
545
544
|
env.error = e;
|
|
546
545
|
env.hasError = true;
|
|
547
546
|
} finally{
|
|
548
|
-
_ts_dispose_resources(env);
|
|
547
|
+
const result = _ts_dispose_resources(env);
|
|
548
|
+
if (result) await result;
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
}
|