@aztec/validator-client 0.0.1-commit.3469e52 → 0.0.1-commit.35158ae7e
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 +63 -19
- package/dest/block_proposal_handler.d.ts +9 -10
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +135 -82
- package/dest/checkpoint_builder.d.ts +27 -15
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +136 -45
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +30 -7
- package/dest/duties/validation_service.d.ts +2 -2
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +6 -12
- package/dest/factory.d.ts +3 -1
- 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 +1 -1
- package/dest/key_store/ha_key_store.d.ts.map +1 -1
- package/dest/key_store/ha_key_store.js +3 -3
- 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 +41 -15
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +230 -69
- package/package.json +19 -17
- package/src/block_proposal_handler.ts +165 -109
- package/src/checkpoint_builder.ts +185 -52
- package/src/config.ts +30 -7
- package/src/duties/validation_service.ts +12 -11
- package/src/factory.ts +4 -0
- package/src/index.ts +0 -1
- package/src/key_store/ha_key_store.ts +3 -3
- package/src/metrics.ts +63 -6
- package/src/validator.ts +294 -86
- 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 -54
- 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 -135
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,15 +156,17 @@ Time | Proposer | Validator
|
|
|
155
156
|
|
|
156
157
|
## Configuration
|
|
157
158
|
|
|
158
|
-
| Flag | Purpose
|
|
159
|
-
| ------------------------------------- |
|
|
160
|
-
| `validatorReexecute` | Re-execute transactions to verify proposals
|
|
161
|
-
| `fishermanMode` | Validate proposals but don't broadcast attestations (monitoring only)
|
|
162
|
-
| `alwaysReexecuteBlockProposals` | Force re-execution even when not in committee
|
|
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 |
|
|
167
170
|
|
|
168
171
|
### High Availability (HA) Keystore
|
|
169
172
|
|
|
@@ -220,6 +223,47 @@ This is useful for monitoring network health without participating in consensus.
|
|
|
220
223
|
- `createCheckpointProposal(...)` → `CheckpointProposal`: Signs checkpoint proposal
|
|
221
224
|
- `attestToCheckpointProposal(proposal, attestors)` → `CheckpointAttestation[]`: Creates attestations for given addresses
|
|
222
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. The checkpoint builder dynamically computes per-block limits before each block based on the remaining checkpoint budget and the number of remaining blocks.
|
|
241
|
+
|
|
242
|
+
**Proposer**: When building a proposal (`isBuildingProposal: true`), the `CheckpointProposalJob` passes `maxBlocksPerCheckpoint` (from the timetable) and `perBlockAllocationMultiplier` (default 1.2) via opts to `CheckpointBuilder.buildBlock`. The builder computes a fair share as `min(perBlockLimit, ceil(remainingBudget / remainingBlocks * multiplier), remainingBudget)`. The multiplier greater than 1 allows early blocks to use more than their even share, since different blocks hit different limit dimensions (L2 gas, DA gas, blob fields) — a strict even split would waste capacity. As prior blocks consume budget, later blocks see tightened limits. This applies to all four dimensions (L2 gas, DA gas, blob fields, transaction count). Operators can set hard per-block caps via `SEQ_MAX_L2_BLOCK_GAS` / `SEQ_MAX_DA_BLOCK_GAS` / `SEQ_MAX_TX_PER_BLOCK` (capped at checkpoint limits at startup); these act as additional upper bounds alongside the redistribution.
|
|
243
|
+
|
|
244
|
+
**Validator**: When re-executing a proposal (`isBuildingProposal` unset), `capLimitsByCheckpointBudgets` only caps by the per-block limit and the total remaining checkpoint budget — no redistribution or multiplier is applied. This avoids false rejections due to differences between proposer and validator fair-share calculations. Validators can optionally set hard per-block limits via `VALIDATOR_MAX_L2_BLOCK_GAS`, `VALIDATOR_MAX_DA_BLOCK_GAS`, and `VALIDATOR_MAX_TX_PER_BLOCK`. When unset, no per-block limit is enforced (checkpoint-level protocol limits still apply). These are independent of the `SEQ_` vars so operators can tune proposer and validation limits separately.
|
|
245
|
+
|
|
246
|
+
### Per-transaction enforcement
|
|
247
|
+
|
|
248
|
+
**Mempool entry** (`GasLimitsValidator`): L2 gas must be ≤ `MAX_PROCESSABLE_L2_GAS` (6,540,000) and ≥ fixed minimums.
|
|
249
|
+
|
|
250
|
+
**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.
|
|
251
|
+
|
|
252
|
+
### Gas limit configuration
|
|
253
|
+
|
|
254
|
+
| Variable | Default | Description |
|
|
255
|
+
| --- | --- | --- |
|
|
256
|
+
| `SEQ_MAX_L2_BLOCK_GAS` | *none* | Hard per-block L2 gas cap. Capped at `rollupManaLimit` at startup. When unset, redistribution dynamically computes per-block limits. |
|
|
257
|
+
| `SEQ_MAX_DA_BLOCK_GAS` | *none* | Hard per-block DA gas cap. Capped at `MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT` at startup. When unset, redistribution handles it. |
|
|
258
|
+
| `SEQ_MAX_TX_PER_BLOCK` | *none* | Hard per-block tx count cap. Capped at `SEQ_MAX_TX_PER_CHECKPOINT` at startup (if set). |
|
|
259
|
+
| `SEQ_MAX_TX_PER_CHECKPOINT` | *none* | Total txs across all blocks in a checkpoint. When set, checkpoint-level capping and redistribution are enforced for tx count. |
|
|
260
|
+
| `SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER` | 1.2 | Multiplier for per-block budget redistribution. Passed via opts to the checkpoint builder during proposal building. |
|
|
261
|
+
| `SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET` | true | Legacy flag; redistribution is now always active during proposal building and inactive during validation. |
|
|
262
|
+
| `VALIDATOR_MAX_L2_BLOCK_GAS` | *none* | Per-block L2 gas limit for validation. Proposals exceeding this are rejected. |
|
|
263
|
+
| `VALIDATOR_MAX_DA_BLOCK_GAS` | *none* | Per-block DA gas limit for validation. Proposals exceeding this are rejected. |
|
|
264
|
+
| `VALIDATOR_MAX_TX_PER_BLOCK` | *none* | Per-block tx count limit for validation. Proposals exceeding this are rejected. |
|
|
265
|
+
| `VALIDATOR_MAX_TX_PER_CHECKPOINT` | *none* | Per-checkpoint tx count limit for validation. Proposals exceeding this are rejected. |
|
|
266
|
+
|
|
223
267
|
## Testing Patterns
|
|
224
268
|
|
|
225
269
|
### Common Mocks
|
|
@@ -268,7 +312,7 @@ For tests that exercise re-execution:
|
|
|
268
312
|
```typescript
|
|
269
313
|
// Mock parent block lookup
|
|
270
314
|
blockSource.getBlockHeaderByArchive.mockResolvedValue(parentBlockHeader);
|
|
271
|
-
blockSource.
|
|
315
|
+
blockSource.getL2Block.mockResolvedValue({
|
|
272
316
|
checkpointNumber: CheckpointNumber(1),
|
|
273
317
|
indexWithinCheckpoint: 0,
|
|
274
318
|
header: { globalVariables: parentGlobalVariables },
|
|
@@ -3,19 +3,18 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
|
3
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
4
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
5
5
|
import type { P2P, PeerId } from '@aztec/p2p';
|
|
6
|
-
import { TxProvider } from '@aztec/p2p';
|
|
7
6
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
8
|
-
import type {
|
|
9
|
-
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';
|
|
10
9
|
import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
11
10
|
import type { BlockProposal } from '@aztec/stdlib/p2p';
|
|
12
|
-
import {
|
|
11
|
+
import type { FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
13
12
|
import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
|
|
14
13
|
import type { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
15
14
|
import type { ValidatorMetrics } from './metrics.js';
|
|
16
|
-
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';
|
|
15
|
+
export type BlockProposalValidationFailureReason = 'invalid_proposal' | 'parent_block_not_found' | 'block_source_not_synced' | 'parent_block_wrong_slot' | 'in_hash_mismatch' | 'global_variables_mismatch' | 'block_number_already_exists' | 'txs_not_available' | 'state_mismatch' | 'failed_txs' | 'initial_state_mismatch' | 'timeout' | 'unknown_error';
|
|
17
16
|
type ReexecuteTransactionsResult = {
|
|
18
|
-
block:
|
|
17
|
+
block: L2Block;
|
|
19
18
|
failedTxs: FailedTx[];
|
|
20
19
|
reexecutionTimeMs: number;
|
|
21
20
|
totalManaUsed: number;
|
|
@@ -45,8 +44,8 @@ export declare class BlockProposalHandler {
|
|
|
45
44
|
private dateProvider;
|
|
46
45
|
private log;
|
|
47
46
|
readonly tracer: Tracer;
|
|
48
|
-
constructor(checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, blockSource: L2BlockSource & L2BlockSink, l1ToL2MessageSource: L1ToL2MessageSource, txProvider:
|
|
49
|
-
|
|
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;
|
|
50
49
|
handleBlockProposal(proposal: BlockProposal, proposalSender: PeerId, shouldReexecute: boolean): Promise<BlockProposalValidationResult>;
|
|
51
50
|
private getParentBlock;
|
|
52
51
|
private computeCheckpointNumber;
|
|
@@ -57,9 +56,9 @@ export declare class BlockProposalHandler {
|
|
|
57
56
|
*/
|
|
58
57
|
private validateNonFirstBlockInCheckpoint;
|
|
59
58
|
private getReexecutionDeadline;
|
|
60
|
-
private
|
|
59
|
+
private waitForBlockSourceSync;
|
|
61
60
|
private getReexecuteFailureReason;
|
|
62
61
|
reexecuteTransactions(proposal: BlockProposal, blockNumber: BlockNumber, checkpointNumber: CheckpointNumber, txs: Tx[], l1ToL2Messages: Fr[], previousCheckpointOutHashes: Fr[]): Promise<ReexecuteTransactionsResult>;
|
|
63
62
|
}
|
|
64
63
|
export {};
|
|
65
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
64
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmxvY2tfcHJvcG9zYWxfaGFuZGxlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2Jsb2NrX3Byb3Bvc2FsX2hhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBYyxNQUFNLGlDQUFpQyxDQUFDO0FBRTVGLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUlwRCxPQUFPLEVBQUUsWUFBWSxFQUFTLE1BQU0seUJBQXlCLENBQUM7QUFDOUQsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQztBQUM5QyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNuRSxPQUFPLEtBQUssRUFBYSxPQUFPLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRzFGLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSx5QkFBeUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3RILE9BQU8sRUFBRSxLQUFLLG1CQUFtQixFQUFtQyxNQUFNLHlCQUF5QixDQUFDO0FBQ3BHLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRXZELE9BQU8sS0FBSyxFQUE2QixRQUFRLEVBQUUsRUFBRSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFRaEYsT0FBTyxFQUFFLEtBQUssZUFBZSxFQUFFLEtBQUssTUFBTSxFQUFzQixNQUFNLHlCQUF5QixDQUFDO0FBRWhHLE9BQU8sS0FBSyxFQUFFLDBCQUEwQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDMUUsT0FBTyxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFckQsTUFBTSxNQUFN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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,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,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":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAc,MAAM,iCAAiC,CAAC;AAE5F,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AAIpD,OAAO,EAAE,YAAY,EAAS,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EAAa,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG1F,OAAO,KAAK,EAAE,WAAW,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACtH,OAAO,EAAE,KAAK,mBAAmB,EAAmC,MAAM,yBAAyB,CAAC;AACpG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAA6B,QAAQ,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAQhF,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,MAAM,EAAsB,MAAM,yBAAyB,CAAC;AAEhG,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,MAAM,oCAAoC,GAC5C,kBAAkB,GAClB,wBAAwB,GACxB,yBAAyB,GACzB,yBAAyB,GACzB,kBAAkB,GAClB,2BAA2B,GAC3B,6BAA6B,GAC7B,mBAAmB,GACnB,gBAAgB,GAChB,YAAY,GACZ,wBAAwB,GACxB,SAAS,GACT,eAAe,CAAC;AAEpB,KAAK,2BAA2B,GAAG;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,IAAI,CAAC;IACd,WAAW,EAAE,WAAW,CAAC;IACzB,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,oCAAoC,CAAC;IAC7C,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,oCAAoC,GAAG,oCAAoC,CAAC;AAMxH,qBAAa,oBAAoB;IAI7B,OAAO,CAAC,kBAAkB;IAC1B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,OAAO,CAAC;IAChB,OAAO,CAAC,YAAY;IAEpB,OAAO,CAAC,GAAG;IAdb,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B,YACU,kBAAkB,EAAE,0BAA0B,EAC9C,UAAU,EAAE,sBAAsB,EAClC,WAAW,EAAE,aAAa,GAAG,WAAW,EACxC,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,WAAW,EACvB,sBAAsB,EAAE,sBAAsB,EAC9C,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,yBAAyB,EACjC,OAAO,CAAC,8BAAkB,EAC1B,YAAY,GAAE,YAAiC,EACvD,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAAmD,EAM/D;IAED,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,eAAe,EAAE,OAAO,GAAG,oBAAoB,CAgCvE;IAEK,mBAAmB,CACvB,QAAQ,EAAE,aAAa,EACvB,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,OAAO,GACvB,OAAO,CAAC,6BAA6B,CAAC,CA6JxC;YAEa,cAAc;IAoC5B,OAAO,CAAC,uBAAuB;IA0C/B;;;;OAIG;IACH,OAAO,CAAC,iCAAiC;IA4EzC,OAAO,CAAC,sBAAsB;YAMhB,sBAAsB;IAmCpC,OAAO,CAAC,yBAAyB;IAgB3B,qBAAqB,CACzB,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,gBAAgB,EAClC,GAAG,EAAE,EAAE,EAAE,EACT,cAAc,EAAE,EAAE,EAAE,EACpB,2BAA2B,EAAE,EAAE,EAAE,GAChC,OAAO,CAAC,2BAA2B,CAAC,CAmHtC;CACF"}
|
|
@@ -65,14 +65,17 @@ 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
74
|
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
75
|
+
import { Gas } from '@aztec/stdlib/gas';
|
|
76
|
+
import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
77
|
+
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
78
|
+
import { ReExFailedTxsError, ReExInitialStateMismatchError, ReExStateMismatchError, ReExTimeoutError, TransactionsNotAvailableError } from '@aztec/stdlib/validators';
|
|
76
79
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
77
80
|
export class BlockProposalHandler {
|
|
78
81
|
checkpointsBuilder;
|
|
@@ -104,23 +107,27 @@ export class BlockProposalHandler {
|
|
|
104
107
|
}
|
|
105
108
|
this.tracer = telemetry.getTracer('BlockProposalHandler');
|
|
106
109
|
}
|
|
107
|
-
|
|
108
|
-
// Non-validator handler that re-executes for monitoring but does not attest.
|
|
110
|
+
register(p2pClient, shouldReexecute) {
|
|
111
|
+
// Non-validator handler that processes or re-executes for monitoring but does not attest.
|
|
109
112
|
// Returns boolean indicating whether the proposal was valid.
|
|
110
113
|
const handler = async (proposal, proposalSender)=>{
|
|
111
114
|
try {
|
|
112
|
-
const
|
|
115
|
+
const { slotNumber, blockNumber } = proposal;
|
|
116
|
+
const result = await this.handleBlockProposal(proposal, proposalSender, shouldReexecute);
|
|
113
117
|
if (result.isValid) {
|
|
114
|
-
this.log.info(`Non-validator
|
|
118
|
+
this.log.info(`Non-validator block proposal ${blockNumber} at slot ${slotNumber} handled`, {
|
|
115
119
|
blockNumber: result.blockNumber,
|
|
120
|
+
slotNumber,
|
|
116
121
|
reexecutionTimeMs: result.reexecutionResult?.reexecutionTimeMs,
|
|
117
122
|
totalManaUsed: result.reexecutionResult?.totalManaUsed,
|
|
118
|
-
numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0
|
|
123
|
+
numTxs: result.reexecutionResult?.block?.body?.txEffects?.length ?? 0,
|
|
124
|
+
reexecuted: shouldReexecute
|
|
119
125
|
});
|
|
120
126
|
return true;
|
|
121
127
|
} else {
|
|
122
|
-
this.log.warn(`Non-validator
|
|
128
|
+
this.log.warn(`Non-validator block proposal ${blockNumber} at slot ${slotNumber} failed processing with ${result.reason}`, {
|
|
123
129
|
blockNumber: result.blockNumber,
|
|
130
|
+
slotNumber,
|
|
124
131
|
reason: result.reason
|
|
125
132
|
});
|
|
126
133
|
return false;
|
|
@@ -147,7 +154,9 @@ export class BlockProposalHandler {
|
|
|
147
154
|
}
|
|
148
155
|
const proposalInfo = {
|
|
149
156
|
...proposal.toBlockInfo(),
|
|
150
|
-
proposer: proposer.toString()
|
|
157
|
+
proposer: proposer.toString(),
|
|
158
|
+
blockNumber: undefined,
|
|
159
|
+
checkpointNumber: undefined
|
|
151
160
|
};
|
|
152
161
|
this.log.info(`Processing proposal for slot ${slotNumber}`, {
|
|
153
162
|
...proposalInfo,
|
|
@@ -155,27 +164,46 @@ export class BlockProposalHandler {
|
|
|
155
164
|
});
|
|
156
165
|
// Check that the proposal is from the current proposer, or the next proposer
|
|
157
166
|
// This should have been handled by the p2p layer, but we double check here out of caution
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
167
|
+
const validationResult = await this.blockProposalValidator.validate(proposal);
|
|
168
|
+
if (validationResult.result !== 'accept') {
|
|
160
169
|
this.log.warn(`Proposal is not valid, skipping processing`, proposalInfo);
|
|
161
170
|
return {
|
|
162
171
|
isValid: false,
|
|
163
172
|
reason: 'invalid_proposal'
|
|
164
173
|
};
|
|
165
174
|
}
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
// Ensure the block source is synced before checking for existing blocks,
|
|
176
|
+
// since a pending checkpoint prune may remove blocks we'd otherwise find.
|
|
177
|
+
// This affects mostly the block_number_already_exists check, since a pending
|
|
178
|
+
// checkpoint prune could remove a block that would conflict with this proposal.
|
|
179
|
+
// When pipelining is enabled, the proposer builds ahead of L1 submission, so the
|
|
180
|
+
// block source won't have synced to the proposed slot yet. Skip the sync wait to
|
|
181
|
+
// avoid eating into the attestation window.
|
|
182
|
+
if (!this.epochCache.isProposerPipeliningEnabled()) {
|
|
183
|
+
const blockSourceSync = await this.waitForBlockSourceSync(slotNumber);
|
|
184
|
+
if (!blockSourceSync) {
|
|
185
|
+
this.log.warn(`Block source is not synced, skipping processing`, proposalInfo);
|
|
186
|
+
return {
|
|
187
|
+
isValid: false,
|
|
188
|
+
reason: 'block_source_not_synced'
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Check that the parent proposal is a block we know, otherwise reexecution would fail.
|
|
193
|
+
// If we don't find it immediately, we keep retrying for a while; it may be we still
|
|
194
|
+
// need to process other block proposals to get to it.
|
|
195
|
+
const parentBlock = await this.getParentBlock(proposal);
|
|
196
|
+
if (parentBlock === undefined) {
|
|
169
197
|
this.log.warn(`Parent block for proposal not found, skipping processing`, proposalInfo);
|
|
170
198
|
return {
|
|
171
199
|
isValid: false,
|
|
172
200
|
reason: 'parent_block_not_found'
|
|
173
201
|
};
|
|
174
202
|
}
|
|
175
|
-
// Check that the parent block's slot is
|
|
176
|
-
if (
|
|
177
|
-
this.log.warn(`Parent block slot is greater than
|
|
178
|
-
parentBlockSlot:
|
|
203
|
+
// Check that the parent block's slot is not greater than the proposal's slot.
|
|
204
|
+
if (parentBlock !== 'genesis' && parentBlock.header.getSlot() > slotNumber) {
|
|
205
|
+
this.log.warn(`Parent block slot is greater than proposal slot, skipping processing`, {
|
|
206
|
+
parentBlockSlot: parentBlock.header.getSlot().toString(),
|
|
179
207
|
proposalSlot: slotNumber.toString(),
|
|
180
208
|
...proposalInfo
|
|
181
209
|
});
|
|
@@ -185,7 +213,8 @@ export class BlockProposalHandler {
|
|
|
185
213
|
};
|
|
186
214
|
}
|
|
187
215
|
// Compute the block number based on the parent block
|
|
188
|
-
const blockNumber =
|
|
216
|
+
const blockNumber = parentBlock === 'genesis' ? BlockNumber(INITIAL_L2_BLOCK_NUM) : BlockNumber(parentBlock.header.getBlockNumber() + 1);
|
|
217
|
+
proposalInfo.blockNumber = blockNumber;
|
|
189
218
|
// Check that this block number does not exist already
|
|
190
219
|
const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
|
|
191
220
|
if (existingBlock) {
|
|
@@ -202,8 +231,16 @@ export class BlockProposalHandler {
|
|
|
202
231
|
pinnedPeer: proposalSender,
|
|
203
232
|
deadline: this.getReexecutionDeadline(slotNumber, config)
|
|
204
233
|
});
|
|
234
|
+
// If reexecution is disabled, bail. We were just interested in triggering tx collection.
|
|
235
|
+
if (!shouldReexecute) {
|
|
236
|
+
this.log.info(`Received valid block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, proposalInfo);
|
|
237
|
+
return {
|
|
238
|
+
isValid: true,
|
|
239
|
+
blockNumber
|
|
240
|
+
};
|
|
241
|
+
}
|
|
205
242
|
// Compute the checkpoint number for this block and validate checkpoint consistency
|
|
206
|
-
const checkpointResult =
|
|
243
|
+
const checkpointResult = this.computeCheckpointNumber(proposal, parentBlock, proposalInfo);
|
|
207
244
|
if (checkpointResult.reason) {
|
|
208
245
|
return {
|
|
209
246
|
isValid: false,
|
|
@@ -212,6 +249,7 @@ export class BlockProposalHandler {
|
|
|
212
249
|
};
|
|
213
250
|
}
|
|
214
251
|
const checkpointNumber = checkpointResult.checkpointNumber;
|
|
252
|
+
proposalInfo.checkpointNumber = checkpointNumber;
|
|
215
253
|
// Check that I have the same set of l1ToL2Messages as the proposal
|
|
216
254
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
217
255
|
const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
@@ -240,38 +278,32 @@ export class BlockProposalHandler {
|
|
|
240
278
|
reason: 'txs_not_available'
|
|
241
279
|
};
|
|
242
280
|
}
|
|
281
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
282
|
+
const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
|
|
283
|
+
const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch)).filter((c)=>c.checkpointNumber < checkpointNumber).map((c)=>c.checkpointOutHash);
|
|
243
284
|
// Try re-executing the transactions in the proposal if needed
|
|
244
285
|
let reexecutionResult;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
reexecutionResult = await this.reexecuteTransactions(proposal, blockNumber, checkpointNumber, txs, l1ToL2Messages, previousCheckpointOutHashes);
|
|
258
|
-
} catch (error) {
|
|
259
|
-
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
260
|
-
const reason = this.getReexecuteFailureReason(error);
|
|
261
|
-
return {
|
|
262
|
-
isValid: false,
|
|
263
|
-
blockNumber,
|
|
264
|
-
reason,
|
|
265
|
-
reexecutionResult
|
|
266
|
-
};
|
|
267
|
-
}
|
|
286
|
+
try {
|
|
287
|
+
this.log.verbose(`Re-executing transactions in the proposal`, proposalInfo);
|
|
288
|
+
reexecutionResult = await this.reexecuteTransactions(proposal, blockNumber, checkpointNumber, txs, l1ToL2Messages, previousCheckpointOutHashes);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
291
|
+
const reason = this.getReexecuteFailureReason(error);
|
|
292
|
+
return {
|
|
293
|
+
isValid: false,
|
|
294
|
+
blockNumber,
|
|
295
|
+
reason,
|
|
296
|
+
reexecutionResult
|
|
297
|
+
};
|
|
268
298
|
}
|
|
269
299
|
// If we succeeded, push this block into the archiver (unless disabled)
|
|
270
|
-
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
271
300
|
if (reexecutionResult?.block && this.config.skipPushProposedBlocksToArchiver === false) {
|
|
272
301
|
await this.blockSource.addBlock(reexecutionResult?.block);
|
|
273
302
|
}
|
|
274
|
-
this.log.info(`Successfully
|
|
303
|
+
this.log.info(`Successfully re-executed block ${blockNumber} proposal at index ${proposal.indexWithinCheckpoint} on slot ${slotNumber}`, {
|
|
304
|
+
...proposalInfo,
|
|
305
|
+
...pick(reexecutionResult, 'reexecutionTimeMs', 'totalManaUsed')
|
|
306
|
+
});
|
|
275
307
|
return {
|
|
276
308
|
isValid: true,
|
|
277
309
|
blockNumber,
|
|
@@ -290,7 +322,7 @@ export class BlockProposalHandler {
|
|
|
290
322
|
const currentTime = this.dateProvider.now();
|
|
291
323
|
const timeoutDurationMs = deadline.getTime() - currentTime;
|
|
292
324
|
try {
|
|
293
|
-
return await this.blockSource.
|
|
325
|
+
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));
|
|
294
326
|
} catch (err) {
|
|
295
327
|
if (err instanceof TimeoutError) {
|
|
296
328
|
this.log.debug(`Timed out getting parent block by archive root`, {
|
|
@@ -304,8 +336,8 @@ export class BlockProposalHandler {
|
|
|
304
336
|
return undefined;
|
|
305
337
|
}
|
|
306
338
|
}
|
|
307
|
-
|
|
308
|
-
if (
|
|
339
|
+
computeCheckpointNumber(proposal, parentBlock, proposalInfo) {
|
|
340
|
+
if (parentBlock === 'genesis') {
|
|
309
341
|
// First block is in checkpoint 1
|
|
310
342
|
if (proposal.indexWithinCheckpoint !== 0) {
|
|
311
343
|
this.log.warn(`First block proposal has non-zero indexWithinCheckpoint`, proposalInfo);
|
|
@@ -317,20 +349,9 @@ export class BlockProposalHandler {
|
|
|
317
349
|
checkpointNumber: CheckpointNumber.INITIAL
|
|
318
350
|
};
|
|
319
351
|
}
|
|
320
|
-
// Get the parent block to find its checkpoint number
|
|
321
|
-
// TODO(palla/mbps): The block header should include the checkpoint number to avoid this lookup,
|
|
322
|
-
// or at least the L2BlockSource should return a different struct that includes it.
|
|
323
|
-
const parentBlockNumber = parentBlockHeader.getBlockNumber();
|
|
324
|
-
const parentBlock = await this.blockSource.getL2BlockNew(parentBlockNumber);
|
|
325
|
-
if (!parentBlock) {
|
|
326
|
-
this.log.warn(`Parent block ${parentBlockNumber} not found in archiver`, proposalInfo);
|
|
327
|
-
return {
|
|
328
|
-
reason: 'invalid_proposal'
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
352
|
if (proposal.indexWithinCheckpoint === 0) {
|
|
332
353
|
// If this is the first block in a new checkpoint, increment the checkpoint number
|
|
333
|
-
if (!(proposal.blockHeader.getSlot() >
|
|
354
|
+
if (!(proposal.blockHeader.getSlot() > parentBlock.header.getSlot())) {
|
|
334
355
|
this.log.warn(`Slot should be greater than parent block slot for first block in checkpoint`, proposalInfo);
|
|
335
356
|
return {
|
|
336
357
|
reason: 'invalid_proposal'
|
|
@@ -347,7 +368,7 @@ export class BlockProposalHandler {
|
|
|
347
368
|
reason: 'invalid_proposal'
|
|
348
369
|
};
|
|
349
370
|
}
|
|
350
|
-
if (proposal.blockHeader.getSlot() !==
|
|
371
|
+
if (proposal.blockHeader.getSlot() !== parentBlock.header.getSlot()) {
|
|
351
372
|
this.log.warn(`Slot should be equal to parent block slot for non-first block in checkpoint`, proposalInfo);
|
|
352
373
|
return {
|
|
353
374
|
reason: 'invalid_proposal'
|
|
@@ -447,23 +468,39 @@ export class BlockProposalHandler {
|
|
|
447
468
|
const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
|
|
448
469
|
return new Date(nextSlotTimestampSeconds * 1000);
|
|
449
470
|
}
|
|
450
|
-
/**
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
471
|
+
/** Waits for the block source to sync L1 data up to at least the slot before the given one. */ async waitForBlockSourceSync(slot) {
|
|
472
|
+
const deadline = this.getReexecutionDeadline(slot, this.checkpointsBuilder.getConfig());
|
|
473
|
+
const timeoutMs = deadline.getTime() - this.dateProvider.now();
|
|
474
|
+
if (slot === 0) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
// Make a quick check before triggering an archiver sync
|
|
478
|
+
const syncedSlot = await this.blockSource.getSyncedL2SlotNumber();
|
|
479
|
+
if (syncedSlot !== undefined && syncedSlot + 1 >= slot) {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
// Trigger an immediate sync of the block source, and wait until it reports being synced to the required slot
|
|
484
|
+
return await retryUntil(async ()=>{
|
|
485
|
+
await this.blockSource.syncImmediate();
|
|
486
|
+
const syncedSlot = await this.blockSource.getSyncedL2SlotNumber();
|
|
487
|
+
return syncedSlot !== undefined && syncedSlot + 1 >= slot;
|
|
488
|
+
}, 'wait for block source sync', timeoutMs / 1000, 0.5);
|
|
489
|
+
} catch (err) {
|
|
490
|
+
if (err instanceof TimeoutError) {
|
|
491
|
+
this.log.warn(`Timed out waiting for block source to sync to slot ${slot}`);
|
|
492
|
+
return false;
|
|
493
|
+
} else {
|
|
494
|
+
throw err;
|
|
459
495
|
}
|
|
460
|
-
blocks.unshift(block);
|
|
461
|
-
currentBlockNumber = BlockNumber(currentBlockNumber - 1);
|
|
462
496
|
}
|
|
463
|
-
return blocks;
|
|
464
497
|
}
|
|
465
498
|
getReexecuteFailureReason(err) {
|
|
466
|
-
if (err instanceof
|
|
499
|
+
if (err instanceof TransactionsNotAvailableError) {
|
|
500
|
+
return 'txs_not_available';
|
|
501
|
+
} else if (err instanceof ReExInitialStateMismatchError) {
|
|
502
|
+
return 'initial_state_mismatch';
|
|
503
|
+
} else if (err instanceof ReExStateMismatchError) {
|
|
467
504
|
return 'state_mismatch';
|
|
468
505
|
} else if (err instanceof ReExFailedTxsError) {
|
|
469
506
|
return 'failed_txs';
|
|
@@ -490,34 +527,49 @@ export class BlockProposalHandler {
|
|
|
490
527
|
const timer = new Timer();
|
|
491
528
|
const slot = proposal.slotNumber;
|
|
492
529
|
const config = this.checkpointsBuilder.getConfig();
|
|
493
|
-
// Get prior blocks in this checkpoint (same slot
|
|
494
|
-
const
|
|
530
|
+
// Get prior blocks in this checkpoint (same slot before current block)
|
|
531
|
+
const allBlocksInSlot = await this.blockSource.getBlocksForSlot(slot);
|
|
532
|
+
const priorBlocks = allBlocksInSlot.filter((b)=>b.number < blockNumber && b.header.getSlot() === slot);
|
|
495
533
|
// Fork before the block to be built
|
|
496
534
|
const parentBlockNumber = BlockNumber(blockNumber - 1);
|
|
497
|
-
|
|
498
|
-
|
|
535
|
+
await this.worldState.syncImmediate(parentBlockNumber);
|
|
536
|
+
const fork = _ts_add_disposable_resource(env, await this.worldState.fork(parentBlockNumber), true);
|
|
537
|
+
// Verify the fork's archive root matches the proposal's expected last archive.
|
|
538
|
+
// If they don't match, our world state synced to a different chain and reexecution would fail.
|
|
539
|
+
const forkArchiveRoot = new Fr((await fork.getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
540
|
+
if (!forkArchiveRoot.equals(proposal.blockHeader.lastArchive.root)) {
|
|
541
|
+
throw new ReExInitialStateMismatchError(proposal.blockHeader.lastArchive.root, forkArchiveRoot);
|
|
542
|
+
}
|
|
543
|
+
// Build checkpoint constants from proposal (excludes blockNumber which is per-block)
|
|
499
544
|
const constants = {
|
|
500
545
|
chainId: new Fr(config.l1ChainId),
|
|
501
546
|
version: new Fr(config.rollupVersion),
|
|
502
547
|
slotNumber: slot,
|
|
548
|
+
timestamp: blockHeader.globalVariables.timestamp,
|
|
503
549
|
coinbase: blockHeader.globalVariables.coinbase,
|
|
504
550
|
feeRecipient: blockHeader.globalVariables.feeRecipient,
|
|
505
551
|
gasFees: blockHeader.globalVariables.gasFees
|
|
506
552
|
};
|
|
507
553
|
// Create checkpoint builder with prior blocks
|
|
508
|
-
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes, fork, priorBlocks);
|
|
554
|
+
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(checkpointNumber, constants, 0n, l1ToL2Messages, previousCheckpointOutHashes, fork, priorBlocks, this.log.getBindings());
|
|
509
555
|
// Build the new block
|
|
510
556
|
const deadline = this.getReexecutionDeadline(slot, config);
|
|
557
|
+
const maxBlockGas = this.config.validateMaxL2BlockGas !== undefined || this.config.validateMaxDABlockGas !== undefined ? new Gas(this.config.validateMaxDABlockGas ?? Infinity, this.config.validateMaxL2BlockGas ?? Infinity) : undefined;
|
|
511
558
|
const result = await checkpointBuilder.buildBlock(txs, blockNumber, blockHeader.globalVariables.timestamp, {
|
|
559
|
+
isBuildingProposal: false,
|
|
560
|
+
minValidTxs: 0,
|
|
512
561
|
deadline,
|
|
513
|
-
expectedEndState: blockHeader.state
|
|
562
|
+
expectedEndState: blockHeader.state,
|
|
563
|
+
maxTransactions: this.config.validateMaxTxsPerBlock,
|
|
564
|
+
maxBlockGas
|
|
514
565
|
});
|
|
515
566
|
const { block, failedTxs } = result;
|
|
516
567
|
const numFailedTxs = failedTxs.length;
|
|
517
|
-
this.log.verbose(`
|
|
568
|
+
this.log.verbose(`Block proposal ${blockNumber} at slot ${slot} transaction re-execution complete`, {
|
|
518
569
|
numFailedTxs,
|
|
519
570
|
numProposalTxs: txHashes.length,
|
|
520
571
|
numProcessedTxs: block.body.txEffects.length,
|
|
572
|
+
blockNumber,
|
|
521
573
|
slot
|
|
522
574
|
});
|
|
523
575
|
if (numFailedTxs > 0) {
|
|
@@ -555,7 +607,8 @@ export class BlockProposalHandler {
|
|
|
555
607
|
env.error = e;
|
|
556
608
|
env.hasError = true;
|
|
557
609
|
} finally{
|
|
558
|
-
_ts_dispose_resources(env);
|
|
610
|
+
const result = _ts_dispose_resources(env);
|
|
611
|
+
if (result) await result;
|
|
559
612
|
}
|
|
560
613
|
}
|
|
561
614
|
}
|