@aztec/p2p 4.0.0-devnet.2-patch.3 → 4.0.0-devnet.3-patch.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/README.md +129 -3
- package/dest/client/factory.d.ts +4 -5
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +30 -28
- package/dest/client/interface.d.ts +8 -13
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +6 -13
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +22 -88
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +2 -4
- package/dest/config.d.ts +29 -10
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +80 -31
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
- package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
- package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/priority.js +4 -4
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -2
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +2 -0
- package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
- package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +10 -6
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
- package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/index.js +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +9 -5
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +46 -8
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +81 -17
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +9 -10
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +5 -3
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +179 -151
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +6 -4
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +6 -4
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -8
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +48 -36
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
- package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
- package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/allowed_public_setup.js +24 -20
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
- package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
- package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
- package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
- package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/data_validator.js +35 -2
- package/dest/msg_validators/tx_validator/factory.d.ts +133 -6
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +247 -60
- package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
- package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
- package/dest/msg_validators/tx_validator/gas_validator.d.ts +67 -3
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +104 -37
- package/dest/msg_validators/tx_validator/index.d.ts +3 -1
- package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/index.js +2 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
- package/dest/msg_validators/tx_validator/phases_validator.d.ts +22 -2
- package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/phases_validator.js +72 -24
- package/dest/services/dummy_service.d.ts +4 -4
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +4 -4
- package/dest/services/encoding.d.ts +6 -2
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +14 -8
- package/dest/services/gossipsub/topic_score_params.d.ts +18 -6
- package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
- package/dest/services/gossipsub/topic_score_params.js +32 -10
- package/dest/services/libp2p/libp2p_service.d.ts +16 -13
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +97 -93
- package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
- package/dest/services/reqresp/reqresp.d.ts +1 -1
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +16 -8
- package/dest/services/service.d.ts +5 -3
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/file_store_tx_source.d.ts +5 -4
- package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
- package/dest/services/tx_collection/file_store_tx_source.js +39 -29
- package/dest/services/tx_collection/tx_source.d.ts +6 -5
- package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.js +9 -7
- package/dest/services/tx_provider.d.ts +3 -3
- package/dest/services/tx_provider.d.ts.map +1 -1
- package/dest/services/tx_provider.js +4 -4
- package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +1 -2
- package/dest/test-helpers/mock-pubsub.d.ts +4 -4
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +8 -2
- package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +2 -2
- package/dest/test-helpers/testbench-utils.d.ts +2 -2
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +2 -1
- package/dest/testbench/p2p_client_testbench_worker.js +7 -6
- package/dest/testbench/worker_client_manager.d.ts +3 -1
- package/dest/testbench/worker_client_manager.d.ts.map +1 -1
- package/dest/testbench/worker_client_manager.js +4 -1
- package/dest/util.d.ts +2 -2
- package/dest/util.d.ts.map +1 -1
- package/package.json +14 -14
- package/src/client/factory.ts +49 -45
- package/src/client/interface.ts +8 -13
- package/src/client/p2p_client.ts +24 -117
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +2 -3
- package/src/config.ts +115 -33
- package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
- package/src/mem_pools/tx_pool/priority.ts +4 -4
- package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
- package/src/mem_pools/tx_pool_v2/README.md +9 -1
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +3 -2
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -0
- package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -2
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +10 -6
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
- package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
- package/src/mem_pools/tx_pool_v2/index.ts +1 -1
- package/src/mem_pools/tx_pool_v2/interfaces.ts +9 -4
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +113 -18
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +11 -11
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +14 -2
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +188 -153
- package/src/msg_validators/attestation_validator/README.md +49 -0
- package/src/msg_validators/proposal_validator/README.md +123 -0
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +14 -4
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +20 -7
- package/src/msg_validators/proposal_validator/proposal_validator.ts +63 -40
- package/src/msg_validators/tx_validator/README.md +119 -0
- package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
- package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
- package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
- package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
- package/src/msg_validators/tx_validator/data_validator.ts +42 -1
- package/src/msg_validators/tx_validator/factory.ts +394 -78
- package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
- package/src/msg_validators/tx_validator/gas_validator.ts +123 -27
- package/src/msg_validators/tx_validator/index.ts +2 -0
- package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
- package/src/msg_validators/tx_validator/phases_validator.ts +82 -27
- package/src/services/dummy_service.ts +6 -6
- package/src/services/encoding.ts +14 -7
- package/src/services/gossipsub/README.md +29 -14
- package/src/services/gossipsub/topic_score_params.ts +49 -13
- package/src/services/libp2p/libp2p_service.ts +111 -101
- package/src/services/reqresp/README.md +229 -0
- package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
- package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
- package/src/services/reqresp/reqresp.ts +18 -10
- package/src/services/service.ts +11 -2
- package/src/services/tx_collection/file_store_tx_source.ts +43 -31
- package/src/services/tx_collection/tx_source.ts +8 -7
- package/src/services/tx_provider.ts +2 -2
- package/src/test-helpers/make-test-p2p-clients.ts +0 -2
- package/src/test-helpers/mock-pubsub.ts +13 -6
- package/src/test-helpers/reqresp-nodes.ts +2 -5
- package/src/test-helpers/testbench-utils.ts +2 -1
- package/src/testbench/p2p_client_testbench_worker.ts +3 -6
- package/src/testbench/worker_client_manager.ts +11 -4
- package/src/util.ts +7 -1
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -23
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -212
- package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
|
@@ -61,6 +61,7 @@ export class TxPoolV2Impl {
|
|
|
61
61
|
#l2BlockSource: L2BlockSource;
|
|
62
62
|
#worldStateSynchronizer: WorldStateSynchronizer;
|
|
63
63
|
#createTxValidator: TxPoolV2Dependencies['createTxValidator'];
|
|
64
|
+
#checkAllowedSetupCalls: TxPoolV2Dependencies['checkAllowedSetupCalls'];
|
|
64
65
|
|
|
65
66
|
// === In-Memory Indices ===
|
|
66
67
|
#indices: TxPoolIndices = new TxPoolIndices();
|
|
@@ -92,6 +93,7 @@ export class TxPoolV2Impl {
|
|
|
92
93
|
this.#l2BlockSource = deps.l2BlockSource;
|
|
93
94
|
this.#worldStateSynchronizer = deps.worldStateSynchronizer;
|
|
94
95
|
this.#createTxValidator = deps.createTxValidator;
|
|
96
|
+
this.#checkAllowedSetupCalls = deps.checkAllowedSetupCalls;
|
|
95
97
|
|
|
96
98
|
this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
|
|
97
99
|
this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
|
|
@@ -187,9 +189,35 @@ export class TxPoolV2Impl {
|
|
|
187
189
|
const errors = new Map<string, TxPoolRejectionError>();
|
|
188
190
|
const acceptedPending = new Set<string>();
|
|
189
191
|
|
|
192
|
+
// Phase 1: Pre-compute all throwable I/O outside the transaction.
|
|
193
|
+
// If any pre-computation throws, the entire call fails before mutations happen.
|
|
194
|
+
const precomputed = new Map<string, { meta: TxMetaData; minedBlockId: L2BlockId | undefined; isValid: boolean }>();
|
|
195
|
+
|
|
196
|
+
const validator = await this.#createTxValidator();
|
|
197
|
+
|
|
198
|
+
for (const tx of txs) {
|
|
199
|
+
const txHash = tx.getTxHash();
|
|
200
|
+
const txHashStr = txHash.toString();
|
|
201
|
+
|
|
202
|
+
const meta = await buildTxMetaData(tx);
|
|
203
|
+
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
204
|
+
|
|
205
|
+
// Validate non-mined txs (mined and pre-protected txs bypass validation inside the transaction)
|
|
206
|
+
let isValid = true;
|
|
207
|
+
if (!minedBlockId) {
|
|
208
|
+
isValid = await this.#validateMeta(meta, validator);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
precomputed.set(txHashStr, { meta, minedBlockId, isValid });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Phase 2: Apply mutations inside the transaction using only pre-computed results,
|
|
215
|
+
// in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
|
|
190
216
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
191
217
|
const preAddContext: PreAddContext | undefined =
|
|
192
|
-
opts.feeComparisonOnly !== undefined
|
|
218
|
+
opts.feeComparisonOnly !== undefined
|
|
219
|
+
? { feeComparisonOnly: opts.feeComparisonOnly, priceBumpPercentage: this.#config.priceBumpPercentage }
|
|
220
|
+
: undefined;
|
|
193
221
|
|
|
194
222
|
await this.#store.transactionAsync(async () => {
|
|
195
223
|
for (const tx of txs) {
|
|
@@ -202,22 +230,25 @@ export class TxPoolV2Impl {
|
|
|
202
230
|
continue;
|
|
203
231
|
}
|
|
204
232
|
|
|
205
|
-
|
|
206
|
-
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
233
|
+
const { meta, minedBlockId, isValid } = precomputed.get(txHashStr)!;
|
|
207
234
|
const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
|
|
208
235
|
|
|
209
236
|
if (minedBlockId) {
|
|
210
237
|
// Already mined - add directly (protection already set if pre-protected)
|
|
211
|
-
await this.#addTx(tx, { mined: minedBlockId }, opts);
|
|
238
|
+
await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
|
|
212
239
|
accepted.push(txHash);
|
|
213
240
|
} else if (preProtectedSlot !== undefined) {
|
|
214
241
|
// Pre-protected and not mined - add as protected (bypass validation)
|
|
215
|
-
await this.#addTx(tx, { protected: preProtectedSlot }, opts);
|
|
242
|
+
await this.#addTx(tx, { protected: preProtectedSlot }, opts, meta);
|
|
216
243
|
accepted.push(txHash);
|
|
244
|
+
} else if (!isValid) {
|
|
245
|
+
// Failed pre-computed validation
|
|
246
|
+
rejected.push(txHash);
|
|
217
247
|
} else {
|
|
218
|
-
// Regular pending tx -
|
|
248
|
+
// Regular pending tx - run pre-add rules using pre-computed metadata
|
|
219
249
|
const result = await this.#tryAddRegularPendingTx(
|
|
220
250
|
tx,
|
|
251
|
+
meta,
|
|
221
252
|
opts,
|
|
222
253
|
poolAccess,
|
|
223
254
|
acceptedPending,
|
|
@@ -227,13 +258,18 @@ export class TxPoolV2Impl {
|
|
|
227
258
|
);
|
|
228
259
|
if (result.status === 'accepted') {
|
|
229
260
|
acceptedPending.add(txHashStr);
|
|
230
|
-
} else if (result.status === 'rejected') {
|
|
231
|
-
rejected.push(txHash);
|
|
232
261
|
} else {
|
|
233
262
|
ignored.push(txHash);
|
|
234
263
|
}
|
|
235
264
|
}
|
|
236
265
|
}
|
|
266
|
+
|
|
267
|
+
// Run post-add eviction rules for pending txs (inside transaction for atomicity)
|
|
268
|
+
if (acceptedPending.size > 0) {
|
|
269
|
+
const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
|
|
270
|
+
const uniqueFeePayers = new Set<string>(feePayers);
|
|
271
|
+
await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
|
|
272
|
+
}
|
|
237
273
|
});
|
|
238
274
|
|
|
239
275
|
// Build final accepted list for pending txs (excludes intra-batch evictions)
|
|
@@ -249,37 +285,24 @@ export class TxPoolV2Impl {
|
|
|
249
285
|
this.#instrumentation.recordRejected(rejected.length);
|
|
250
286
|
}
|
|
251
287
|
|
|
252
|
-
// Run post-add eviction rules for pending txs
|
|
253
|
-
if (acceptedPending.size > 0) {
|
|
254
|
-
const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
|
|
255
|
-
const uniqueFeePayers = new Set<string>(feePayers);
|
|
256
|
-
await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
288
|
return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
|
|
260
289
|
}
|
|
261
290
|
|
|
262
|
-
/**
|
|
291
|
+
/** Adds a validated pending tx, running pre-add rules and evicting conflicts. */
|
|
263
292
|
async #tryAddRegularPendingTx(
|
|
264
293
|
tx: Tx,
|
|
294
|
+
precomputedMeta: TxMetaData,
|
|
265
295
|
opts: { source?: string },
|
|
266
296
|
poolAccess: PreAddPoolAccess,
|
|
267
297
|
acceptedPending: Set<string>,
|
|
268
298
|
ignored: TxHash[],
|
|
269
299
|
errors: Map<string, TxPoolRejectionError>,
|
|
270
300
|
preAddContext?: PreAddContext,
|
|
271
|
-
): Promise<{ status: 'accepted' | 'ignored'
|
|
272
|
-
const
|
|
273
|
-
const txHashStr = txHash.toString();
|
|
274
|
-
|
|
275
|
-
// Build metadata and validate using metadata
|
|
276
|
-
const meta = await buildTxMetaData(tx);
|
|
277
|
-
if (!(await this.#validateMeta(meta))) {
|
|
278
|
-
return { status: 'rejected' };
|
|
279
|
-
}
|
|
301
|
+
): Promise<{ status: 'accepted' | 'ignored' }> {
|
|
302
|
+
const txHashStr = tx.getTxHash().toString();
|
|
280
303
|
|
|
281
304
|
// Run pre-add rules
|
|
282
|
-
const preAddResult = await this.#evictionManager.runPreAddRules(
|
|
305
|
+
const preAddResult = await this.#evictionManager.runPreAddRules(precomputedMeta, poolAccess, preAddContext);
|
|
283
306
|
|
|
284
307
|
if (preAddResult.shouldIgnore) {
|
|
285
308
|
this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`);
|
|
@@ -317,11 +340,11 @@ export class TxPoolV2Impl {
|
|
|
317
340
|
}
|
|
318
341
|
|
|
319
342
|
// Add the transaction
|
|
320
|
-
await this.#addTx(tx, 'pending', opts);
|
|
343
|
+
await this.#addTx(tx, 'pending', opts, precomputedMeta);
|
|
321
344
|
return { status: 'accepted' };
|
|
322
345
|
}
|
|
323
346
|
|
|
324
|
-
async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'
|
|
347
|
+
async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
|
|
325
348
|
const txHashStr = tx.getTxHash().toString();
|
|
326
349
|
|
|
327
350
|
// Check if already in pool
|
|
@@ -329,14 +352,8 @@ export class TxPoolV2Impl {
|
|
|
329
352
|
return 'ignored';
|
|
330
353
|
}
|
|
331
354
|
|
|
332
|
-
// Build metadata and
|
|
355
|
+
// Build metadata and check pre-add rules
|
|
333
356
|
const meta = await buildTxMetaData(tx);
|
|
334
|
-
const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
|
|
335
|
-
if (validationResult !== true) {
|
|
336
|
-
return 'rejected';
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Use pre-add rules
|
|
340
357
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
341
358
|
const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
|
|
342
359
|
|
|
@@ -346,20 +363,25 @@ export class TxPoolV2Impl {
|
|
|
346
363
|
async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
|
|
347
364
|
const slotNumber = block.globalVariables.slotNumber;
|
|
348
365
|
|
|
366
|
+
// Precompute setup-call allow-list flags outside the store transaction
|
|
367
|
+
const allowedFlags = await Promise.all(txs.map(tx => this.#checkAllowedSetupCalls(tx)));
|
|
368
|
+
|
|
349
369
|
await this.#store.transactionAsync(async () => {
|
|
350
|
-
for (
|
|
370
|
+
for (let i = 0; i < txs.length; i++) {
|
|
371
|
+
const tx = txs[i];
|
|
351
372
|
const txHash = tx.getTxHash();
|
|
352
373
|
const txHashStr = txHash.toString();
|
|
353
374
|
const isNew = !this.#indices.has(txHashStr);
|
|
354
375
|
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
355
376
|
|
|
356
377
|
if (isNew) {
|
|
378
|
+
const meta = await buildTxMetaData(tx, allowedFlags[i]);
|
|
357
379
|
// New tx - add as mined or protected (callback emitted by #addTx)
|
|
358
380
|
if (minedBlockId) {
|
|
359
|
-
await this.#addTx(tx, { mined: minedBlockId }, opts);
|
|
381
|
+
await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
|
|
360
382
|
this.#indices.setProtection(txHashStr, slotNumber);
|
|
361
383
|
} else {
|
|
362
|
-
await this.#addTx(tx, { protected: slotNumber }, opts);
|
|
384
|
+
await this.#addTx(tx, { protected: slotNumber }, opts, meta);
|
|
363
385
|
}
|
|
364
386
|
} else {
|
|
365
387
|
// Existing tx - update protection and mined status
|
|
@@ -379,33 +401,35 @@ export class TxPoolV2Impl {
|
|
|
379
401
|
let softDeletedHits = 0;
|
|
380
402
|
let missingPreviouslyEvicted = 0;
|
|
381
403
|
|
|
382
|
-
|
|
383
|
-
const
|
|
404
|
+
await this.#store.transactionAsync(async () => {
|
|
405
|
+
for (const txHash of txHashes) {
|
|
406
|
+
const txHashStr = txHash.toString();
|
|
384
407
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
408
|
+
if (this.#indices.has(txHashStr)) {
|
|
409
|
+
// Update protection for existing tx
|
|
410
|
+
this.#indices.updateProtection(txHashStr, slotNumber);
|
|
411
|
+
} else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
|
|
412
|
+
// Resurrect soft-deleted tx as protected
|
|
413
|
+
const buffer = await this.#txsDB.getAsync(txHashStr);
|
|
414
|
+
if (buffer) {
|
|
415
|
+
const tx = Tx.fromBuffer(buffer);
|
|
416
|
+
await this.#addTx(tx, { protected: slotNumber });
|
|
417
|
+
softDeletedHits++;
|
|
418
|
+
} else {
|
|
419
|
+
// Data missing despite soft-delete flag — treat as truly missing
|
|
420
|
+
this.#indices.setProtection(txHashStr, slotNumber);
|
|
421
|
+
missing.push(txHash);
|
|
422
|
+
}
|
|
395
423
|
} else {
|
|
396
|
-
//
|
|
424
|
+
// Truly missing — pre-record protection for tx we don't have yet
|
|
397
425
|
this.#indices.setProtection(txHashStr, slotNumber);
|
|
398
426
|
missing.push(txHash);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.#indices.setProtection(txHashStr, slotNumber);
|
|
403
|
-
missing.push(txHash);
|
|
404
|
-
if (this.#evictedTxHashes.has(txHashStr)) {
|
|
405
|
-
missingPreviouslyEvicted++;
|
|
427
|
+
if (this.#evictedTxHashes.has(txHashStr)) {
|
|
428
|
+
missingPreviouslyEvicted++;
|
|
429
|
+
}
|
|
406
430
|
}
|
|
407
431
|
}
|
|
408
|
-
}
|
|
432
|
+
});
|
|
409
433
|
|
|
410
434
|
// Record metrics
|
|
411
435
|
if (softDeletedHits > 0) {
|
|
@@ -466,56 +490,60 @@ export class TxPoolV2Impl {
|
|
|
466
490
|
}
|
|
467
491
|
}
|
|
468
492
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
493
|
+
await this.#store.transactionAsync(async () => {
|
|
494
|
+
// Step 4: Mark txs as mined (only those we have in the pool)
|
|
495
|
+
for (const meta of found) {
|
|
496
|
+
this.#indices.markAsMined(meta, blockId);
|
|
497
|
+
await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
|
|
498
|
+
}
|
|
474
499
|
|
|
475
|
-
|
|
476
|
-
|
|
500
|
+
// Step 5: Run post-event eviction rules (inside transaction for atomicity)
|
|
501
|
+
await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
|
|
502
|
+
});
|
|
477
503
|
|
|
478
504
|
this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
|
|
479
505
|
}
|
|
480
506
|
|
|
481
507
|
async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
|
|
482
|
-
|
|
483
|
-
|
|
508
|
+
await this.#store.transactionAsync(async () => {
|
|
509
|
+
// Step 0: Clean up slot-deleted txs from previous slots
|
|
510
|
+
await this.#deletedPool.cleanupSlotDeleted(slotNumber);
|
|
484
511
|
|
|
485
|
-
|
|
486
|
-
|
|
512
|
+
// Step 1: Find expired protected txs
|
|
513
|
+
const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
|
|
487
514
|
|
|
488
|
-
|
|
489
|
-
|
|
515
|
+
// Step 2: Clear protection for all expired entries (including those without metadata)
|
|
516
|
+
this.#indices.clearProtection(expiredProtected);
|
|
490
517
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
518
|
+
// Step 3: Filter to only txs that have metadata and are not mined
|
|
519
|
+
const txsToRestore = this.#indices.filterRestorable(expiredProtected);
|
|
520
|
+
if (txsToRestore.length === 0) {
|
|
521
|
+
this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
497
524
|
|
|
498
|
-
|
|
525
|
+
this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
|
|
499
526
|
|
|
500
|
-
|
|
501
|
-
|
|
527
|
+
// Step 4: Validate for pending pool
|
|
528
|
+
const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
|
|
502
529
|
|
|
503
|
-
|
|
504
|
-
|
|
530
|
+
// Step 5: Resolve nullifier conflicts and add winners to pending indices
|
|
531
|
+
const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
|
|
505
532
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
533
|
+
// Step 6: Delete invalid txs and evict conflict losers
|
|
534
|
+
await this.#deleteTxsBatch(invalid);
|
|
535
|
+
await this.#evictTxs(toEvict, 'NullifierConflict');
|
|
509
536
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
537
|
+
// Step 7: Run eviction rules (enforce pool size limit)
|
|
538
|
+
if (added.length > 0) {
|
|
539
|
+
const feePayers = added.map(meta => meta.feePayer);
|
|
540
|
+
const uniqueFeePayers = new Set<string>(feePayers);
|
|
541
|
+
await this.#evictionManager.evictAfterNewTxs(
|
|
542
|
+
added.map(m => m.txHash),
|
|
543
|
+
[...uniqueFeePayers],
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
519
547
|
}
|
|
520
548
|
|
|
521
549
|
async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
|
|
@@ -528,57 +556,60 @@ export class TxPoolV2Impl {
|
|
|
528
556
|
|
|
529
557
|
this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
|
|
530
558
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
559
|
+
await this.#store.transactionAsync(async () => {
|
|
560
|
+
// Step 2: Mark ALL un-mined txs with their original mined block number
|
|
561
|
+
// This ensures they get soft-deleted if removed later, and only hard-deleted
|
|
562
|
+
// when their original mined block is finalized
|
|
563
|
+
await this.#deletedPool.markFromPrunedBlock(
|
|
564
|
+
txsToUnmine.map(m => ({
|
|
565
|
+
txHash: m.txHash,
|
|
566
|
+
minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
|
|
567
|
+
})),
|
|
568
|
+
);
|
|
540
569
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
570
|
+
// Step 3: Unmine - clear mined status from metadata
|
|
571
|
+
for (const meta of txsToUnmine) {
|
|
572
|
+
this.#indices.markAsUnmined(meta);
|
|
573
|
+
}
|
|
545
574
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
575
|
+
// If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
|
|
576
|
+
if (options?.deleteAllTxs) {
|
|
577
|
+
const allTxHashes = txsToUnmine.map(m => m.txHash);
|
|
578
|
+
await this.#deleteTxsBatch(allTxHashes);
|
|
579
|
+
this.#log.info(
|
|
580
|
+
`Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
|
|
581
|
+
);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
555
584
|
|
|
556
|
-
|
|
557
|
-
|
|
585
|
+
// Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
|
|
586
|
+
const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
|
|
558
587
|
|
|
559
|
-
|
|
560
|
-
|
|
588
|
+
// Step 5: Validate for pending pool
|
|
589
|
+
const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
|
|
561
590
|
|
|
562
|
-
|
|
563
|
-
|
|
591
|
+
// Step 6: Resolve nullifier conflicts and add winners to pending indices
|
|
592
|
+
const { toEvict } = this.#applyNullifierConflictResolution(valid);
|
|
564
593
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
594
|
+
// Step 7: Delete invalid txs and evict conflict losers
|
|
595
|
+
await this.#deleteTxsBatch(invalid);
|
|
596
|
+
await this.#evictTxs(toEvict, 'NullifierConflict');
|
|
568
597
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
598
|
+
this.#log.info(
|
|
599
|
+
`Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
|
|
600
|
+
{ txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
|
|
601
|
+
);
|
|
573
602
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
603
|
+
// Step 8: Run eviction rules for ALL pending txs (not just restored ones)
|
|
604
|
+
// This handles cases like existing pending txs with invalid fee payer balances
|
|
605
|
+
await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
|
|
606
|
+
});
|
|
577
607
|
}
|
|
578
608
|
|
|
579
609
|
async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
|
|
580
|
-
|
|
581
|
-
|
|
610
|
+
await this.#store.transactionAsync(async () => {
|
|
611
|
+
await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
|
|
612
|
+
});
|
|
582
613
|
|
|
583
614
|
this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
|
|
584
615
|
}
|
|
@@ -589,27 +620,29 @@ export class TxPoolV2Impl {
|
|
|
589
620
|
// Step 1: Find mined txs at or before finalized block
|
|
590
621
|
const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
|
|
591
622
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
623
|
+
await this.#store.transactionAsync(async () => {
|
|
624
|
+
// Step 2: Collect mined txs for archiving (before deletion)
|
|
625
|
+
const txsToArchive: Tx[] = [];
|
|
626
|
+
if (this.#archive.isEnabled()) {
|
|
627
|
+
for (const txHashStr of minedTxsToFinalize) {
|
|
628
|
+
const buffer = await this.#txsDB.getAsync(txHashStr);
|
|
629
|
+
if (buffer) {
|
|
630
|
+
txsToArchive.push(Tx.fromBuffer(buffer));
|
|
631
|
+
}
|
|
599
632
|
}
|
|
600
633
|
}
|
|
601
|
-
}
|
|
602
634
|
|
|
603
|
-
|
|
604
|
-
|
|
635
|
+
// Step 3: Delete mined txs from active pool
|
|
636
|
+
await this.#deleteTxsBatch(minedTxsToFinalize);
|
|
605
637
|
|
|
606
|
-
|
|
607
|
-
|
|
638
|
+
// Step 4: Finalize soft-deleted txs
|
|
639
|
+
await this.#deletedPool.finalizeBlock(blockNumber);
|
|
608
640
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
641
|
+
// Step 5: Archive mined txs
|
|
642
|
+
if (txsToArchive.length > 0) {
|
|
643
|
+
await this.#archive.archiveTxs(txsToArchive);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
613
646
|
|
|
614
647
|
if (minedTxsToFinalize.length > 0) {
|
|
615
648
|
this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
|
|
@@ -754,9 +787,10 @@ export class TxPoolV2Impl {
|
|
|
754
787
|
tx: Tx,
|
|
755
788
|
state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
|
|
756
789
|
opts: { source?: string } = {},
|
|
790
|
+
precomputedMeta?: TxMetaData,
|
|
757
791
|
): Promise<TxMetaData> {
|
|
758
792
|
const txHashStr = tx.getTxHash().toString();
|
|
759
|
-
const meta = await buildTxMetaData(tx);
|
|
793
|
+
const meta = precomputedMeta ?? (await buildTxMetaData(tx));
|
|
760
794
|
meta.receivedAt = this.#dateProvider.now();
|
|
761
795
|
|
|
762
796
|
await this.#txsDB.set(txHashStr, tx.toBuffer());
|
|
@@ -938,7 +972,8 @@ export class TxPoolV2Impl {
|
|
|
938
972
|
|
|
939
973
|
try {
|
|
940
974
|
const tx = Tx.fromBuffer(buffer);
|
|
941
|
-
const
|
|
975
|
+
const allowedSetupCalls = await this.#checkAllowedSetupCalls(tx);
|
|
976
|
+
const meta = await buildTxMetaData(tx, allowedSetupCalls);
|
|
942
977
|
loaded.push({ tx, meta });
|
|
943
978
|
} catch (err) {
|
|
944
979
|
this.#log.warn(`Failed to deserialize tx ${txHashStr}, deleting`, { err });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Attestation Validation
|
|
2
|
+
|
|
3
|
+
This module validates `CheckpointAttestation` gossipsub messages. Attestations are signatures from committee members endorsing a checkpoint proposal.
|
|
4
|
+
|
|
5
|
+
**Topic**: `checkpoint_attestation` | **Snappy size limit**: 5 KB
|
|
6
|
+
|
|
7
|
+
## Stage 1: AttestationValidator (Gossipsub Validation)
|
|
8
|
+
|
|
9
|
+
| # | Rule | Consequence | Severity | File |
|
|
10
|
+
|---|------|-------------|----------|------|
|
|
11
|
+
| 1 | **Slot timeliness**: `currentSlot` or `nextSlot`. Previous slot within 500ms: IGNORE. Older: REJECT. | REJECT or IGNORE | HighToleranceError | `attestation_validator.ts` |
|
|
12
|
+
| 2 | **Attester signature**: `getSender()` must recover valid address | REJECT | LowToleranceError | same |
|
|
13
|
+
| 3 | **Attester in committee**: recovered address in committee for slot | REJECT | HighToleranceError | same |
|
|
14
|
+
| 4 | **Proposer exists**: `getProposerAttesterAddressInSlot` must return defined | REJECT | HighToleranceError | same |
|
|
15
|
+
| 5 | **Proposer signature**: `getProposer()` must recover valid address | REJECT | LowToleranceError | same |
|
|
16
|
+
| 6 | **Proposer matches expected**: recovered proposer = expected for slot | REJECT | HighToleranceError | same |
|
|
17
|
+
| 7 | **NoCommitteeError**: committee unavailable | REJECT | LowToleranceError | same |
|
|
18
|
+
|
|
19
|
+
**Fisherman mode extension** (`FishermanAttestationValidator`): if a checkpoint proposal for the same archive exists in pool, the attestation's `ConsensusPayload` must `.equals()` the stored proposal's payload. On mismatch: REJECT + LowToleranceError.
|
|
20
|
+
|
|
21
|
+
## Stage 2: Pool Admission
|
|
22
|
+
|
|
23
|
+
| # | Rule | Consequence |
|
|
24
|
+
|---|------|-------------|
|
|
25
|
+
| 8 | Sender recoverable (pool-side) | Silent drop |
|
|
26
|
+
| 9 | Not a duplicate (same slot + proposalId + signer) | IGNORE |
|
|
27
|
+
| 10 | Per-signer cap: `MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER` = 3 | IGNORE |
|
|
28
|
+
|
|
29
|
+
Own attestations added via `addOwnCheckpointAttestations` bypass the per-signer cap.
|
|
30
|
+
|
|
31
|
+
## Stage 3: Equivocation Detection
|
|
32
|
+
|
|
33
|
+
When a signer's attestation count for a slot reaches exactly 2 (different proposals): `duplicateAttestationCallback` fires -> `WANT_TO_SLASH_EVENT` with `OffenseType.DUPLICATE_ATTESTATION`. Attestation still ACCEPTED and rebroadcast. Callback fires once (not again at count 3+).
|
|
34
|
+
|
|
35
|
+
## Validation at L1 Checkpoint Submission (Archiver)
|
|
36
|
+
|
|
37
|
+
| Rule | Consequence | File |
|
|
38
|
+
|------|-------------|------|
|
|
39
|
+
| Each attestation must have recoverable signature (or address-only is allowed but does not count toward quorum) | Checkpoint rejected as invalid | `archiver/src/modules/validation.ts` |
|
|
40
|
+
| Attestation at index `i` must correspond to committee member at index `i` | Checkpoint rejected as invalid | same |
|
|
41
|
+
| Valid attestation count >= floor(committee * 2/3) + 1 | Checkpoint rejected as invalid | same |
|
|
42
|
+
| No committee / escape hatch open | Accepted unconditionally | same |
|
|
43
|
+
|
|
44
|
+
Note: `skipValidateCheckpointAttestations` config flag bypasses all archiver attestation validation.
|
|
45
|
+
|
|
46
|
+
## Gossipsub Topic Scoring
|
|
47
|
+
|
|
48
|
+
P3 enabled with expected messages per slot = `targetCommitteeSize`. Conservative threshold (30% of convergence value). Max P3 penalty = -34 per topic.
|
|
49
|
+
|