@aztec/p2p 5.0.0-rc.1 → 5.0.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 +3 -5
- package/dest/bootstrap/bootstrap.d.ts +1 -1
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +20 -12
- package/dest/client/factory.d.ts +1 -1
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +2 -1
- package/dest/client/interface.d.ts +14 -2
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +4 -2
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +10 -2
- package/dest/config.d.ts +7 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +10 -5
- package/dest/errors/reqresp.error.d.ts +13 -1
- package/dest/errors/reqresp.error.d.ts.map +1 -1
- package/dest/errors/reqresp.error.js +14 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +6 -2
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +43 -0
- package/dest/mem_pools/instrumentation.d.ts +9 -4
- package/dest/mem_pools/instrumentation.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.js +11 -8
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +12 -1
- 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_pool_indices.d.ts +9 -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 +21 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +2 -1
- 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 +12 -4
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +10 -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 +56 -8
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +10 -6
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +25 -12
- package/dest/msg_validators/tx_validator/phases_validator.js +2 -2
- package/dest/services/discv5/discV5_service.d.ts +12 -2
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +84 -15
- package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
- package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
- package/dest/services/discv5/persisted_enr_store.js +145 -0
- package/dest/services/dummy_service.d.ts +6 -2
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +3 -0
- package/dest/services/encoding.d.ts +19 -6
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +25 -15
- package/dest/services/libp2p/libp2p_service.d.ts +11 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +98 -34
- package/dest/services/peer-manager/peer_manager.d.ts +1 -1
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +21 -12
- package/dest/services/reqresp/config.d.ts +13 -1
- package/dest/services/reqresp/config.d.ts.map +1 -1
- package/dest/services/reqresp/config.js +11 -0
- package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
- 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 +34 -3
- package/dest/services/service.d.ts +17 -1
- package/dest/services/service.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.d.ts +2 -1
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +3 -0
- package/package.json +16 -17
- package/src/bootstrap/bootstrap.ts +15 -9
- package/src/client/factory.ts +1 -0
- package/src/client/interface.ts +15 -0
- package/src/client/p2p_client.ts +14 -3
- package/src/config.ts +17 -4
- package/src/errors/reqresp.error.ts +19 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +6 -2
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -1
- package/src/mem_pools/instrumentation.ts +13 -11
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
- package/src/mem_pools/tx_pool_v2/interfaces.ts +13 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +23 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +14 -4
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +71 -7
- package/src/msg_validators/proposal_validator/proposal_validator.ts +22 -15
- package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
- package/src/services/discv5/discV5_service.ts +88 -9
- package/src/services/discv5/persisted_enr_store.ts +158 -0
- package/src/services/dummy_service.ts +6 -0
- package/src/services/encoding.ts +28 -20
- package/src/services/libp2p/libp2p_service.ts +107 -37
- package/src/services/peer-manager/peer_manager.ts +18 -11
- package/src/services/reqresp/README.md +2 -23
- package/src/services/reqresp/config.ts +17 -0
- package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
- package/src/services/reqresp/reqresp.ts +42 -2
- package/src/services/service.ts +19 -0
- package/src/test-helpers/testbench-utils.ts +4 -0
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
TxPoolV2Events,
|
|
21
21
|
} from './interfaces.js';
|
|
22
22
|
import type { TxState } from './tx_metadata.js';
|
|
23
|
+
import type { MinedTxInfo } from './tx_pool_v2_impl.js';
|
|
23
24
|
import { TxPoolV2Impl } from './tx_pool_v2_impl.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -56,17 +57,22 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
|
|
|
56
57
|
// Create callbacks that the impl uses to notify us about events and metrics
|
|
57
58
|
const callbacks = {
|
|
58
59
|
onTxsAdded: (txs: Tx[], opts: { source?: string }) => {
|
|
59
|
-
this.#metrics?.transactionsAdded(txs);
|
|
60
60
|
this.emit('txs-added', { txs, ...opts });
|
|
61
61
|
},
|
|
62
62
|
onTxsRemoved: (txHashes: string[] | bigint[]) => {
|
|
63
|
-
this.#metrics?.transactionsRemoved(txHashes);
|
|
64
63
|
// Convert to TxHash objects for the event
|
|
65
64
|
const hashes = txHashes.map(h => (typeof h === 'string' ? TxHash.fromString(h) : TxHash.fromBigInt(h)));
|
|
66
65
|
this.emit('txs-removed', { txHashes: hashes });
|
|
67
66
|
},
|
|
68
|
-
onTxsMined: (
|
|
69
|
-
|
|
67
|
+
onTxsMined: (minedTxs: MinedTxInfo[]) => {
|
|
68
|
+
// Pending-to-mined delay is derived from the tx's persisted receivedAt at the mined
|
|
69
|
+
// transition (see TxPoolV2Impl.handleMinedBlock), not the add/remove timestamp map —
|
|
70
|
+
// so eviction no longer pollutes MEMPOOL_TX_MINED_DELAY.
|
|
71
|
+
for (const { minedDelayMs } of minedTxs) {
|
|
72
|
+
if (minedDelayMs !== undefined) {
|
|
73
|
+
this.#metrics?.recordMinedDelay(minedDelayMs);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
70
76
|
},
|
|
71
77
|
};
|
|
72
78
|
|
|
@@ -156,6 +162,10 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
|
|
|
156
162
|
return this.#queue.put(() => Promise.resolve(this.#impl.getPendingTxCount()));
|
|
157
163
|
}
|
|
158
164
|
|
|
165
|
+
hasEligiblePendingTxs(minCount: number): Promise<boolean> {
|
|
166
|
+
return this.#queue.put(() => Promise.resolve(this.#impl.hasEligiblePendingTxs(minCount)));
|
|
167
|
+
}
|
|
168
|
+
|
|
159
169
|
getMinedTxHashes(): Promise<[TxHash, L2BlockId][]> {
|
|
160
170
|
return this.#queue.put(() => Promise.resolve(this.#impl.getMinedTxHashes()));
|
|
161
171
|
}
|
|
@@ -51,10 +51,18 @@ const FINALIZE_BLOCK_CHUNK_SIZE = 100;
|
|
|
51
51
|
/**
|
|
52
52
|
* Callbacks for the implementation to notify the outer class about events and metrics.
|
|
53
53
|
*/
|
|
54
|
+
/** A tx that has just transitioned to mined, with the time it spent in the pool. */
|
|
55
|
+
export interface MinedTxInfo {
|
|
56
|
+
txHash: string;
|
|
57
|
+
/** Wall-clock ms from receipt into the pool to being marked mined. Undefined when the
|
|
58
|
+
* receive time is unknown (e.g. a tx hydrated from the DB on restart, receivedAt === 0). */
|
|
59
|
+
minedDelayMs?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
export interface TxPoolV2Callbacks {
|
|
55
63
|
onTxsAdded: (txs: Tx[], opts: { source?: string }) => void;
|
|
56
64
|
onTxsRemoved: (txHashes: string[] | bigint[]) => void;
|
|
57
|
-
onTxsMined: (
|
|
65
|
+
onTxsMined: (minedTxs: MinedTxInfo[]) => void;
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
/**
|
|
@@ -560,7 +568,15 @@ export class TxPoolV2Impl {
|
|
|
560
568
|
});
|
|
561
569
|
|
|
562
570
|
if (found.length > 0) {
|
|
563
|
-
|
|
571
|
+
// receivedAt is 0 for txs hydrated from the DB on restart (true receive time lost) — leave
|
|
572
|
+
// their delay undefined so the metric isn't polluted by epoch-sized values.
|
|
573
|
+
const now = this.#dateProvider.now();
|
|
574
|
+
this.#callbacks.onTxsMined(
|
|
575
|
+
found.map(m => ({
|
|
576
|
+
txHash: m.txHash,
|
|
577
|
+
minedDelayMs: m.receivedAt > 0 ? now - m.receivedAt : undefined,
|
|
578
|
+
})),
|
|
579
|
+
);
|
|
564
580
|
}
|
|
565
581
|
|
|
566
582
|
this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
|
|
@@ -712,8 +728,12 @@ export class TxPoolV2Impl {
|
|
|
712
728
|
async handleFinalizedBlock(block: BlockHeader): Promise<void> {
|
|
713
729
|
const blockNumber = block.globalVariables.blockNumber;
|
|
714
730
|
|
|
715
|
-
//
|
|
716
|
-
|
|
731
|
+
// Hold finalized txs for a configurable margin behind the finalized tip so a prover still
|
|
732
|
+
// proving an epoch with already-finalized blocks isn't starved of its txs. 0 deletes at the finalized tip.
|
|
733
|
+
const cutoffBlock = await this.#finalizationCutoffBlock(block);
|
|
734
|
+
|
|
735
|
+
// Step 1: Find mined txs at or before the cutoff block
|
|
736
|
+
const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(cutoffBlock);
|
|
717
737
|
|
|
718
738
|
// Step 2: Archive in chunks if archiving is enabled. Hydrating an entire epoch's worth of
|
|
719
739
|
// mined txs at once would OOM under load. When archiving is disabled there is no need to hydrate the txs at all.
|
|
@@ -737,16 +757,52 @@ export class TxPoolV2Impl {
|
|
|
737
757
|
// transaction. Only tx hashes are touched here, so memory is bounded and atomicity is preserved.
|
|
738
758
|
await this.#store.transactionAsync(async () => {
|
|
739
759
|
await this.#deleteTxsBatch(minedTxsToFinalize);
|
|
740
|
-
await this.#deletedPool.finalizeBlock(
|
|
760
|
+
await this.#deletedPool.finalizeBlock(cutoffBlock);
|
|
741
761
|
});
|
|
742
762
|
|
|
743
763
|
if (minedTxsToFinalize.length > 0) {
|
|
744
|
-
this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${
|
|
764
|
+
this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${cutoffBlock}`, {
|
|
745
765
|
txHashes: minedTxsToFinalize,
|
|
766
|
+
finalizedBlockNumber: blockNumber,
|
|
767
|
+
cutoffBlock,
|
|
746
768
|
});
|
|
747
769
|
}
|
|
748
770
|
}
|
|
749
771
|
|
|
772
|
+
/**
|
|
773
|
+
* Resolves the block up to which finalized txs may be deleted, given `keepFinalizedTxsForSlots`.
|
|
774
|
+
* With a margin of 0 this is just the finalized block. Otherwise we keep txs whose block is within
|
|
775
|
+
* the margin (in slots) of the finalized tip: take the target slot `finalizedSlot - margin`, find the
|
|
776
|
+
* latest checkpoint at or before it with a single reverse range query (`limit: 1`), and use that
|
|
777
|
+
* checkpoint's last block as the cutoff. This rounds to a checkpoint boundary, so it can retain slightly
|
|
778
|
+
* more than the configured margin but never less, and never deletes past the finalized block.
|
|
779
|
+
*/
|
|
780
|
+
async #finalizationCutoffBlock(block: BlockHeader): Promise<BlockNumber> {
|
|
781
|
+
const keepSlots = this.#config.keepFinalizedTxsForSlots;
|
|
782
|
+
if (keepSlots <= 0) {
|
|
783
|
+
return block.globalVariables.blockNumber;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// The goal here really is to ensure we keep transactions long enough for them to be proven.
|
|
787
|
+
// So we could be smart and calculate the number of slots until the end of the proof submission window.
|
|
788
|
+
// Our approach here is a lot simpler however and we just keep transactions for the configured number
|
|
789
|
+
// of slots past L1 finalisation. This means we may keep transactions in the mined pool for longer
|
|
790
|
+
// than strictly necessary.
|
|
791
|
+
const targetSlot = block.getSlot() - keepSlots;
|
|
792
|
+
if (targetSlot < 0) {
|
|
793
|
+
// The margin reaches past genesis: keep everything.
|
|
794
|
+
return BlockNumber(0);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const [checkpoint] = await this.#l2BlockSource.getCheckpointsData({
|
|
798
|
+
fromSlot: SlotNumber(targetSlot),
|
|
799
|
+
limit: 1,
|
|
800
|
+
reverse: true,
|
|
801
|
+
});
|
|
802
|
+
// No checkpoint at or before the target slot (e.g. early chain): keep everything.
|
|
803
|
+
return checkpoint ? BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1) : BlockNumber(0);
|
|
804
|
+
}
|
|
805
|
+
|
|
750
806
|
// === Query Methods ===
|
|
751
807
|
|
|
752
808
|
async getTxByHash(txHash: TxHash, opts: { includeProof?: boolean } = {}): Promise<Tx | undefined> {
|
|
@@ -814,6 +870,11 @@ export class TxPoolV2Impl {
|
|
|
814
870
|
return this.#indices.getPendingTxCount();
|
|
815
871
|
}
|
|
816
872
|
|
|
873
|
+
hasEligiblePendingTxs(minCount: number): boolean {
|
|
874
|
+
const maxReceivedAt = this.#dateProvider.now() - this.#config.minTxPoolAgeMs;
|
|
875
|
+
return this.#indices.hasEligiblePendingTxs(maxReceivedAt, minCount);
|
|
876
|
+
}
|
|
877
|
+
|
|
817
878
|
getMinedTxHashes(): [TxHash, L2BlockId][] {
|
|
818
879
|
return this.#indices.getMinedTxs().map(([hash, blockId]) => [TxHash.fromString(hash), blockId]);
|
|
819
880
|
}
|
|
@@ -857,6 +918,9 @@ export class TxPoolV2Impl {
|
|
|
857
918
|
if (config.minTxPoolAgeMs !== undefined) {
|
|
858
919
|
this.#config.minTxPoolAgeMs = config.minTxPoolAgeMs;
|
|
859
920
|
}
|
|
921
|
+
if (config.keepFinalizedTxsForSlots !== undefined) {
|
|
922
|
+
this.#config.keepFinalizedTxsForSlots = config.keepFinalizedTxsForSlots;
|
|
923
|
+
}
|
|
860
924
|
// Update eviction rules with new config
|
|
861
925
|
this.#evictionManager.updateConfig(config);
|
|
862
926
|
}
|
|
@@ -1139,7 +1203,7 @@ export class TxPoolV2Impl {
|
|
|
1139
1203
|
const publicStateSource = new DatabasePublicStateSource(db);
|
|
1140
1204
|
const balance = await publicStateSource.storageRead(
|
|
1141
1205
|
ProtocolContractAddress.FeeJuice,
|
|
1142
|
-
await computeFeePayerBalanceStorageSlot(AztecAddress.
|
|
1206
|
+
await computeFeePayerBalanceStorageSlot(AztecAddress.fromStringUnsafe(feePayer)),
|
|
1143
1207
|
);
|
|
1144
1208
|
return balance.toBigInt();
|
|
1145
1209
|
},
|
|
@@ -19,7 +19,6 @@ export class ProposalValidator {
|
|
|
19
19
|
private logger: Logger;
|
|
20
20
|
private txsPermitted: boolean;
|
|
21
21
|
private maxTxsPerBlock?: number;
|
|
22
|
-
private maxBlocksPerCheckpoint?: number;
|
|
23
22
|
private skipSlotValidation: boolean;
|
|
24
23
|
private signatureContext: CoordinationSignatureContext;
|
|
25
24
|
private clockDisparityMs: number;
|
|
@@ -41,7 +40,6 @@ export class ProposalValidator {
|
|
|
41
40
|
this.timetable = timetable;
|
|
42
41
|
this.txsPermitted = opts.txsPermitted;
|
|
43
42
|
this.maxTxsPerBlock = opts.maxTxsPerBlock;
|
|
44
|
-
this.maxBlocksPerCheckpoint = opts.maxBlocksPerCheckpoint;
|
|
45
43
|
this.skipSlotValidation = opts.skipSlotValidation ?? false;
|
|
46
44
|
this.signatureContext = opts.signatureContext;
|
|
47
45
|
this.clockDisparityMs = opts.clockDisparityMs;
|
|
@@ -99,7 +97,11 @@ export class ProposalValidator {
|
|
|
99
97
|
|
|
100
98
|
// Proposer check
|
|
101
99
|
const expectedProposer = await this.epochCache.getProposerAttesterAddressInSlot(slotNumber);
|
|
102
|
-
if (expectedProposer
|
|
100
|
+
if (expectedProposer === undefined) {
|
|
101
|
+
this.logger.warn(`Penalizing peer for proposal with no expected proposer for current slot ${slotNumber}`);
|
|
102
|
+
return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
|
|
103
|
+
}
|
|
104
|
+
if (!proposer.equals(expectedProposer)) {
|
|
103
105
|
this.logger.warn(`Penalizing peer for invalid proposer for current slot ${slotNumber}`, {
|
|
104
106
|
expectedProposer,
|
|
105
107
|
proposer: proposer.toString(),
|
|
@@ -107,8 +109,12 @@ export class ProposalValidator {
|
|
|
107
109
|
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
// A block proposal whose index lands beyond the
|
|
111
|
-
//
|
|
112
|
+
// A block proposal whose index lands at or beyond the hard attestable ceiling is structurally
|
|
113
|
+
// impossible garbage, so reject it immediately at ingress. Indices in
|
|
114
|
+
// `[maxBlocksPerCheckpoint, MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT)` are over the consensus limit
|
|
115
|
+
// but structurally valid proposer misbehavior; they pass gossip validation here so the offending
|
|
116
|
+
// proposal can be retained and re-broadcast as slashing evidence (handled downstream in the p2p
|
|
117
|
+
// service), rather than penalizing the relaying peer.
|
|
112
118
|
if ('indexWithinCheckpoint' in proposal) {
|
|
113
119
|
const indexResult = this.validateBlockIndexWithinCheckpoint(proposal);
|
|
114
120
|
if (indexResult.result !== 'accept') {
|
|
@@ -126,20 +132,21 @@ export class ProposalValidator {
|
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/**
|
|
129
|
-
* Rejects a block proposal whose index within its checkpoint lands at or beyond the
|
|
130
|
-
*
|
|
131
|
-
* `
|
|
132
|
-
*
|
|
135
|
+
* Rejects a block proposal whose index within its checkpoint lands at or beyond the hard attestable
|
|
136
|
+
* ceiling `MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT` (a structurally impossible index that can never be
|
|
137
|
+
* part of any checkpoint we could attest to). `indexWithinCheckpoint` is 0-based, so a ceiling of 72
|
|
138
|
+
* rejects the 73rd block.
|
|
139
|
+
*
|
|
140
|
+
* Indices in `[maxBlocksPerCheckpoint, MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT)` are over the configured
|
|
141
|
+
* consensus limit but still structurally valid: they are proposer misbehavior, not relaying-peer
|
|
142
|
+
* fault, so they are *not* rejected here. The p2p service retains and re-broadcasts the first such
|
|
143
|
+
* proposal per (slot, proposer) as slashing evidence and skips processing it. Applies to standalone
|
|
133
144
|
* block proposals and to the terminal block embedded in a checkpoint proposal.
|
|
134
145
|
*/
|
|
135
146
|
public validateBlockIndexWithinCheckpoint(proposal: BlockProposal): ValidationResult {
|
|
136
|
-
|
|
137
|
-
this.maxBlocksPerCheckpoint ?? MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT,
|
|
138
|
-
MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT,
|
|
139
|
-
);
|
|
140
|
-
if (proposal.indexWithinCheckpoint >= maxBlocksPerCheckpoint) {
|
|
147
|
+
if (proposal.indexWithinCheckpoint >= MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT) {
|
|
141
148
|
this.logger.warn(
|
|
142
|
-
`Penalizing peer for proposal with indexWithinCheckpoint ${proposal.indexWithinCheckpoint} >=
|
|
149
|
+
`Penalizing peer for proposal with indexWithinCheckpoint ${proposal.indexWithinCheckpoint} >= attestable ceiling ${MAX_ATTESTABLE_BLOCKS_PER_CHECKPOINT}`,
|
|
143
150
|
);
|
|
144
151
|
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
145
152
|
}
|
|
@@ -97,7 +97,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
|
|
|
97
97
|
}
|
|
98
98
|
if (
|
|
99
99
|
entry.rejectNullMsgSender &&
|
|
100
|
-
publicCall.request.msgSender.equals(AztecAddress.
|
|
100
|
+
publicCall.request.msgSender.equals(AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS))
|
|
101
101
|
) {
|
|
102
102
|
return TX_ERROR_SETUP_NULL_MSG_SENDER;
|
|
103
103
|
}
|
|
@@ -130,7 +130,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
|
|
|
130
130
|
}
|
|
131
131
|
if (
|
|
132
132
|
entry.rejectNullMsgSender &&
|
|
133
|
-
publicCall.request.msgSender.equals(AztecAddress.
|
|
133
|
+
publicCall.request.msgSender.equals(AztecAddress.fromBigIntUnsafe(NULL_MSG_SENDER_CONTRACT_ADDRESS))
|
|
134
134
|
) {
|
|
135
135
|
return TX_ERROR_SETUP_NULL_MSG_SENDER;
|
|
136
136
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
2
|
import { sleep } from '@aztec/foundation/sleep';
|
|
3
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
4
|
+
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
3
5
|
import { type ComponentsVersions, checkCompressedComponentVersion } from '@aztec/stdlib/versioning';
|
|
4
6
|
import { OtelMetricsAdapter, type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
5
7
|
|
|
@@ -14,9 +16,13 @@ import { createNodeENR } from '../../enr/generate-enr.js';
|
|
|
14
16
|
import { AZTEC_ENR_KEY, Discv5Event, PeerEvent } from '../../types/index.js';
|
|
15
17
|
import { convertToMultiaddr } from '../../util.js';
|
|
16
18
|
import { type PeerDiscoveryService, PeerDiscoveryState } from '../service.js';
|
|
19
|
+
import { PersistedEnrStore } from './persisted_enr_store.js';
|
|
17
20
|
|
|
18
21
|
const delayBeforeStart = 2000; // 2sec
|
|
19
22
|
|
|
23
|
+
/** Upper bound on persisted peer ENRs, to keep the store bounded as peers churn. */
|
|
24
|
+
const MAX_PERSISTED_PEER_ENRS = 100;
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* Peer discovery service using Discv5.
|
|
22
28
|
*/
|
|
@@ -35,14 +41,19 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
35
41
|
private bootstrapNodePeerIds: PeerId[] = [];
|
|
36
42
|
public bootstrapNodeEnrs: ENR[] = [];
|
|
37
43
|
private trustedPeerEnrs: ENR[] = [];
|
|
44
|
+
/** Node IDs of all config-provided peers (bootstrap, trusted, private); these are never persisted. */
|
|
45
|
+
private readonly configProvidedNodeIds: Set<string>;
|
|
38
46
|
|
|
39
47
|
private startTime = 0;
|
|
40
48
|
|
|
41
49
|
private currentIp: string | undefined;
|
|
42
50
|
|
|
51
|
+
/** Persistent store of discovered peer ENRs, used to re-seed discovery after a restart. */
|
|
52
|
+
private readonly persistedEnrs?: PersistedEnrStore;
|
|
53
|
+
|
|
43
54
|
private handlers = {
|
|
44
55
|
onMultiaddrUpdated: this.onMultiaddrUpdated.bind(this),
|
|
45
|
-
|
|
56
|
+
onPeerDiscovered: this.onPeerDiscovered.bind(this),
|
|
46
57
|
onEnrAdded: this.onEnrAdded.bind(this),
|
|
47
58
|
};
|
|
48
59
|
|
|
@@ -52,16 +63,27 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
52
63
|
private readonly packageVersion: string,
|
|
53
64
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
54
65
|
private logger = createLogger('p2p:discv5_service'),
|
|
66
|
+
store?: AztecAsyncKVStore,
|
|
55
67
|
configOverrides: Partial<IDiscv5CreateOptions> = {},
|
|
68
|
+
private readonly dateProvider: DateProvider = new DateProvider(),
|
|
56
69
|
) {
|
|
57
70
|
super();
|
|
58
71
|
|
|
72
|
+
this.persistedEnrs = store
|
|
73
|
+
? new PersistedEnrStore(store, MAX_PERSISTED_PEER_ENRS, createLogger(`${this.logger.module}:enr-store`))
|
|
74
|
+
: undefined;
|
|
75
|
+
|
|
59
76
|
const { p2pIp, p2pPort, p2pBroadcastPort, bootstrapNodes, trustedPeers, privatePeers } = config;
|
|
60
77
|
|
|
61
78
|
this.currentIp = p2pIp;
|
|
62
79
|
this.bootstrapNodeEnrs = bootstrapNodes.map(x => ENR.decodeTxt(x));
|
|
63
80
|
const privatePeerEnrs = new Set(privatePeers);
|
|
64
81
|
this.trustedPeerEnrs = trustedPeers.filter(x => !privatePeerEnrs.has(x)).map(x => ENR.decodeTxt(x));
|
|
82
|
+
// Peers supplied via config are re-injected from config on every start, so they must never end up
|
|
83
|
+
// in the persisted (discovered-peer) store — private peers especially must not be written there.
|
|
84
|
+
this.configProvidedNodeIds = new Set(
|
|
85
|
+
[...bootstrapNodes, ...trustedPeers, ...privatePeers].map(x => ENR.decodeTxt(x).nodeId),
|
|
86
|
+
);
|
|
65
87
|
|
|
66
88
|
// If no overridden broadcast port is provided, use the p2p port as the broadcast port
|
|
67
89
|
if (!p2pBroadcastPort) {
|
|
@@ -126,7 +148,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
126
148
|
}
|
|
127
149
|
};
|
|
128
150
|
|
|
129
|
-
this.discv5.on(Discv5Event.DISCOVERED, this.handlers.
|
|
151
|
+
this.discv5.on(Discv5Event.DISCOVERED, this.handlers.onPeerDiscovered);
|
|
130
152
|
this.discv5.on(Discv5Event.ENR_ADDED, this.handlers.onEnrAdded);
|
|
131
153
|
this.discv5.on(Discv5Event.MULTIADDR_UPDATED, this.handlers.onMultiaddrUpdated);
|
|
132
154
|
}
|
|
@@ -168,7 +190,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
168
190
|
}
|
|
169
191
|
this.logger.debug('Starting DiscV5');
|
|
170
192
|
await this.discv5.start();
|
|
171
|
-
this.startTime =
|
|
193
|
+
this.startTime = this.dateProvider.now();
|
|
172
194
|
|
|
173
195
|
const enrUpdateEnabled = this.config.queryForIp || !this.config.p2pIp;
|
|
174
196
|
this.logger.info(`DiscV5 service started`, {
|
|
@@ -220,6 +242,26 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
220
242
|
this.discv5.addEnr(enr);
|
|
221
243
|
}
|
|
222
244
|
}
|
|
245
|
+
|
|
246
|
+
// Re-seed discovery from peers persisted on previous runs. This lets a node rejoin the network
|
|
247
|
+
// after a restart even when no bootstrap node is reachable: discv5 has no on-disk routing table,
|
|
248
|
+
// so without this its DHT would start empty and rediscovery would depend on inbound dials alone.
|
|
249
|
+
if (this.persistedEnrs) {
|
|
250
|
+
// Drop peers that are now config-provided (re-injected above) or fail version checks.
|
|
251
|
+
const enrs = await this.persistedEnrs.load(
|
|
252
|
+
enr => this.validateEnr(enr) && !this.configProvidedNodeIds.has(enr.nodeId),
|
|
253
|
+
);
|
|
254
|
+
for (const enr of enrs) {
|
|
255
|
+
try {
|
|
256
|
+
this.discv5.addEnr(enr);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
this.logger.debug(`Error re-seeding persisted ENR ${enr.nodeId}`, err);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (enrs.length > 0) {
|
|
262
|
+
this.logger.info(`Re-seeded discovery with ${enrs.length} persisted peer ENRs`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
223
265
|
}
|
|
224
266
|
|
|
225
267
|
public async runRandomNodesQuery(): Promise<void> {
|
|
@@ -229,8 +271,8 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
229
271
|
|
|
230
272
|
// First, wait some time before starting the peer discovery
|
|
231
273
|
// reference: https://github.com/ChainSafe/lodestar/issues/3423
|
|
232
|
-
const msSinceStart =
|
|
233
|
-
if (
|
|
274
|
+
const msSinceStart = this.dateProvider.now() - this.startTime;
|
|
275
|
+
if (this.dateProvider.now() - this.startTime <= delayBeforeStart) {
|
|
234
276
|
await sleep(delayBeforeStart - msSinceStart);
|
|
235
277
|
}
|
|
236
278
|
|
|
@@ -265,7 +307,7 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
265
307
|
if (this.currentState !== PeerDiscoveryState.RUNNING) {
|
|
266
308
|
return;
|
|
267
309
|
}
|
|
268
|
-
await this.discv5.off(Discv5Event.DISCOVERED, this.handlers.
|
|
310
|
+
await this.discv5.off(Discv5Event.DISCOVERED, this.handlers.onPeerDiscovered);
|
|
269
311
|
await this.discv5.off(Discv5Event.ENR_ADDED, this.handlers.onEnrAdded);
|
|
270
312
|
await this.discv5.off(Discv5Event.MULTIADDR_UPDATED, this.handlers.onMultiaddrUpdated);
|
|
271
313
|
|
|
@@ -275,12 +317,38 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
275
317
|
}
|
|
276
318
|
|
|
277
319
|
private async onEnrAdded(enr: ENR) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
320
|
+
try {
|
|
321
|
+
const multiAddrTcp = await enr.getFullMultiaddr('tcp');
|
|
322
|
+
const multiAddrUdp = await enr.getFullMultiaddr('udp');
|
|
323
|
+
this.logger.debug(`Added ENR ${enr.encodeTxt()}`, { multiAddrTcp, multiAddrUdp, nodeId: enr.nodeId });
|
|
324
|
+
// A peer just entered the routing table — persist it so we can re-seed discovery after a restart.
|
|
325
|
+
if (this.persistedEnrs && this.shouldPersist(enr)) {
|
|
326
|
+
await this.persistedEnrs.persist(enr);
|
|
327
|
+
}
|
|
328
|
+
this.onDiscovered(enr);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
// A malformed ENR address field (e.g. a 3-byte TCP port) makes @nethermindeth/enr throw while
|
|
331
|
+
// parsing the multiaddr. This is an async event listener, so an unhandled rejection would crash
|
|
332
|
+
// the process — catch, log, and drop the ENR instead.
|
|
333
|
+
this.logger.warn(`Dropping ENR with unparseable address`, { nodeId: enr.nodeId, err });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private async onPeerDiscovered(enr: ENR) {
|
|
338
|
+
// discv5 updates an existing routing-table entry on an ENR sequence bump without emitting enrAdded,
|
|
339
|
+
// so DISCOVERED is our only signal that a peer we already track may have changed address. Refresh
|
|
340
|
+
// the persisted copy (a no-op for peers we don't already store) so we don't keep dialing a stale ENR.
|
|
341
|
+
if (this.persistedEnrs && this.shouldPersist(enr)) {
|
|
342
|
+
await this.persistedEnrs.refresh(enr);
|
|
343
|
+
}
|
|
281
344
|
this.onDiscovered(enr);
|
|
282
345
|
}
|
|
283
346
|
|
|
347
|
+
/** Whether an ENR belongs in the persisted store: a validated, non-config peer that isn't ourselves. */
|
|
348
|
+
private shouldPersist(enr: ENR): boolean {
|
|
349
|
+
return !this.configProvidedNodeIds.has(enr.nodeId) && enr.nodeId !== this.enr.nodeId && this.validateEnr(enr);
|
|
350
|
+
}
|
|
351
|
+
|
|
284
352
|
private isOurBootnode(enr: ENR) {
|
|
285
353
|
return this.bootstrapNodeEnrs.some(x => x.nodeId === enr.nodeId);
|
|
286
354
|
}
|
|
@@ -314,6 +382,17 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
314
382
|
return false;
|
|
315
383
|
}
|
|
316
384
|
|
|
385
|
+
// A malformed TCP field (e.g. a non-2-byte port) makes @nethermindeth/enr throw while building the
|
|
386
|
+
// multiaddr. Reject such an ENR here so it's never emitted as a full peer or re-parsed by an
|
|
387
|
+
// unguarded listener (which would crash the process). A missing TCP addr is allowed — those peers
|
|
388
|
+
// are simply skipped at dial time.
|
|
389
|
+
try {
|
|
390
|
+
enr.getLocationMultiaddr('tcp');
|
|
391
|
+
} catch (err) {
|
|
392
|
+
this.logger.debug(`Peer node ${enr.nodeId} has an unparseable TCP address`, { err });
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
317
396
|
// And check it has the correct version
|
|
318
397
|
let compressedVersion;
|
|
319
398
|
try {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
|
|
3
|
+
|
|
4
|
+
import { ENR } from '@nethermindeth/enr';
|
|
5
|
+
|
|
6
|
+
/** Map name under which discovered peer ENRs are persisted so discovery can be re-seeded after a restart. */
|
|
7
|
+
const PERSISTED_ENRS_MAP_NAME = 'discovered_peer_enrs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A persisted peer entry: the ENR text plus a monotonic sequence number recording when the peer was
|
|
11
|
+
* last seen. The sequence orders the store as a FIFO so eviction drops the oldest-seen peers first.
|
|
12
|
+
*/
|
|
13
|
+
interface PersistedEnr {
|
|
14
|
+
enr: string;
|
|
15
|
+
seq: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parsePersistedEnr(value: string): PersistedEnr | undefined {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(value);
|
|
21
|
+
if (typeof parsed?.enr === 'string' && typeof parsed?.seq === 'number') {
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// Unparseable entry; caller treats it as stale.
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A bounded, transactional store of discovered peer ENRs used to re-seed discovery after a restart.
|
|
32
|
+
*
|
|
33
|
+
* Entries are keyed by nodeId (so re-seeing a peer dedups in place) and carry a monotonic sequence so
|
|
34
|
+
* the store behaves as a FIFO: when it exceeds its cap, the oldest-seen peers are evicted first. All
|
|
35
|
+
* writes run in a transaction so concurrent discovery events can't race the size check.
|
|
36
|
+
*/
|
|
37
|
+
export class PersistedEnrStore {
|
|
38
|
+
private readonly enrs: AztecAsyncMap<string, string>;
|
|
39
|
+
/** Monotonic sequence stamped on each entry; seeded from the store's max in {@link load}. */
|
|
40
|
+
private seq = 0;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
private readonly store: AztecAsyncKVStore,
|
|
44
|
+
private readonly maxEntries: number,
|
|
45
|
+
private readonly logger: Logger = createLogger('p2p:discv5:enr-store'),
|
|
46
|
+
) {
|
|
47
|
+
this.enrs = store.openMap(PERSISTED_ENRS_MAP_NAME);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Adds or replaces a peer's ENR, stamping it as most-recently-seen and evicting the oldest if over cap. */
|
|
51
|
+
public async persist(enr: ENR): Promise<void> {
|
|
52
|
+
const nodeId = enr.nodeId;
|
|
53
|
+
const enrTxt = enr.encodeTxt();
|
|
54
|
+
try {
|
|
55
|
+
await this.store.transactionAsync(async () => {
|
|
56
|
+
const seq = ++this.seq;
|
|
57
|
+
const others: { nodeId: string; seq: number }[] = [];
|
|
58
|
+
for await (const [id, value] of this.enrs.entriesAsync()) {
|
|
59
|
+
if (id !== nodeId) {
|
|
60
|
+
others.push({ nodeId: id, seq: parsePersistedEnr(value)?.seq ?? 0 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const overflow = others.length + 1 - this.maxEntries;
|
|
64
|
+
if (overflow > 0) {
|
|
65
|
+
others.sort((a, b) => a.seq - b.seq); // oldest-seen (lowest sequence) first
|
|
66
|
+
for (let i = 0; i < overflow; i++) {
|
|
67
|
+
await this.enrs.delete(others[i].nodeId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await this.enrs.set(nodeId, JSON.stringify({ enr: enrTxt, seq }));
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
this.logger.warn(`Failed to persist discovered ENR ${nodeId}`, err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Refreshes an already-persisted peer's stored ENR when a newer one (higher ENR sequence) is seen.
|
|
79
|
+
* A no-op for peers we don't already track — we never add new peers via this path, only update.
|
|
80
|
+
*
|
|
81
|
+
* This exists because discv5 updates an existing routing-table entry on an ENR sequence bump without
|
|
82
|
+
* emitting an `enrAdded` event, so a peer that changes address would otherwise keep its stale ENR
|
|
83
|
+
* persisted until evicted.
|
|
84
|
+
*/
|
|
85
|
+
public async refresh(enr: ENR): Promise<void> {
|
|
86
|
+
const nodeId = enr.nodeId;
|
|
87
|
+
try {
|
|
88
|
+
await this.store.transactionAsync(async () => {
|
|
89
|
+
const existing = await this.enrs.getAsync(nodeId);
|
|
90
|
+
if (!existing) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Two unrelated sequence numbers are in play:
|
|
94
|
+
// - the ENR sequence (`enr.seq` / `storedEnrSeq`) is set by the *peer* and bumped whenever it
|
|
95
|
+
// revises its record (e.g. changes address); we compare these to decide whether the incoming
|
|
96
|
+
// ENR is genuinely newer and worth keeping.
|
|
97
|
+
// - our storage sequence (`this.seq`) is an internal counter used only for FIFO eviction; we
|
|
98
|
+
// stamp a fresh one so this entry counts as most-recently-seen.
|
|
99
|
+
const storedEnrSeq = this.decodeStoredEnr(existing)?.seq;
|
|
100
|
+
if (storedEnrSeq !== undefined && enr.seq <= storedEnrSeq) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await this.enrs.set(nodeId, JSON.stringify({ enr: enr.encodeTxt(), seq: ++this.seq }));
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
this.logger.warn(`Failed to refresh persisted ENR ${nodeId}`, err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Loads all persisted ENRs, decoding each and keeping those the `accept` predicate allows. Entries
|
|
112
|
+
* that fail to decode or are rejected are removed from the store. The FIFO sequence resumes from the
|
|
113
|
+
* highest persisted value so eviction order survives restarts.
|
|
114
|
+
*/
|
|
115
|
+
public async load(accept: (enr: ENR) => boolean): Promise<ENR[]> {
|
|
116
|
+
const valid: ENR[] = [];
|
|
117
|
+
const stale: string[] = [];
|
|
118
|
+
let maxSeq = 0;
|
|
119
|
+
for await (const [nodeId, value] of this.enrs.entriesAsync()) {
|
|
120
|
+
const parsed = parsePersistedEnr(value);
|
|
121
|
+
let enr: ENR | undefined;
|
|
122
|
+
if (parsed) {
|
|
123
|
+
try {
|
|
124
|
+
enr = ENR.decodeTxt(parsed.enr);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
this.logger.debug(`Dropping undecodable persisted ENR ${nodeId}`, err);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!parsed || !enr || !accept(enr)) {
|
|
130
|
+
stale.push(nodeId);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
maxSeq = Math.max(maxSeq, parsed.seq);
|
|
134
|
+
valid.push(enr);
|
|
135
|
+
}
|
|
136
|
+
this.seq = maxSeq;
|
|
137
|
+
if (stale.length > 0) {
|
|
138
|
+
await this.store.transactionAsync(async () => {
|
|
139
|
+
for (const nodeId of stale) {
|
|
140
|
+
await this.enrs.delete(nodeId);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return valid;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private decodeStoredEnr(value: string): ENR | undefined {
|
|
148
|
+
const parsed = parsePersistedEnr(value);
|
|
149
|
+
if (!parsed) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
return ENR.decodeTxt(parsed.enr);
|
|
154
|
+
} catch {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type P2PCheckpointReceivedCallback,
|
|
29
29
|
type P2PDuplicateAttestationCallback,
|
|
30
30
|
type P2PDuplicateProposalCallback,
|
|
31
|
+
type P2POversizedProposalCallback,
|
|
31
32
|
type P2PService,
|
|
32
33
|
type PeerDiscoveryService,
|
|
33
34
|
PeerDiscoveryState,
|
|
@@ -98,6 +99,11 @@ export class DummyP2PService implements P2PService {
|
|
|
98
99
|
*/
|
|
99
100
|
public registerDuplicateProposalCallback(_callback: P2PDuplicateProposalCallback): void {}
|
|
100
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Register a callback for when an oversized proposal is stored
|
|
104
|
+
*/
|
|
105
|
+
public registerOversizedProposalCallback(_callback: P2POversizedProposalCallback): void {}
|
|
106
|
+
|
|
101
107
|
/**
|
|
102
108
|
* Register a callback for when a duplicate attestation is detected
|
|
103
109
|
*/
|