@aztec/p2p 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/bootstrap/bootstrap.d.ts +1 -1
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +9 -1
- package/dest/client/factory.d.ts +3 -3
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +12 -9
- package/dest/client/interface.d.ts +6 -4
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +11 -8
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +53 -17
- package/dest/config.d.ts +43 -17
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +48 -8
- package/dest/errors/reqresp.error.d.ts +1 -20
- package/dest/errors/reqresp.error.d.ts.map +1 -1
- package/dest/errors/reqresp.error.js +0 -21
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +93 -55
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +258 -195
- 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 +175 -59
- package/dest/mem_pools/attestation_pool/mocks.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/mocks.js +6 -4
- package/dest/mem_pools/instrumentation.d.ts +1 -1
- package/dest/mem_pools/instrumentation.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.js +17 -1
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts +1 -5
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +1 -7
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
- 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 +73 -83
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +5 -2
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +18 -2
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +5 -3
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +3 -4
- package/dest/msg_validators/clock_tolerance.d.ts +1 -1
- package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
- package/dest/msg_validators/clock_tolerance.js +13 -11
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +5 -2
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +5 -2
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +8 -2
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +28 -2
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +1 -1
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +8 -15
- package/dest/msg_validators/tx_validator/allowed_public_setup.js +3 -3
- package/dest/msg_validators/tx_validator/cached_tx_validator.d.ts +15 -0
- package/dest/msg_validators/tx_validator/cached_tx_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/cached_tx_validator.js +19 -0
- package/dest/msg_validators/tx_validator/data_validator.d.ts +2 -1
- package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/data_validator.js +1 -0
- package/dest/msg_validators/tx_validator/factory.d.ts +5 -4
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +11 -7
- 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/tx_proof_validator.d.ts +2 -1
- package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/tx_proof_validator.js +2 -0
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts +48 -0
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/tx_validation_cache.js +68 -0
- package/dest/services/discv5/discV5_service.d.ts +2 -1
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +35 -8
- package/dest/services/dummy_service.d.ts +10 -17
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +9 -18
- package/dest/services/libp2p/libp2p_service.d.ts +19 -21
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +147 -148
- 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 +1 -0
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -8
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +35 -44
- package/dest/services/reqresp/batch-tx-requester/interface.d.ts +8 -5
- package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/tx_validator.d.ts +5 -14
- package/dest/services/reqresp/batch-tx-requester/tx_validator.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/tx_validator.js +6 -20
- package/dest/services/reqresp/interface.d.ts +3 -10
- package/dest/services/reqresp/interface.d.ts.map +1 -1
- package/dest/services/reqresp/interface.js +0 -9
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +4 -3
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/bitvector.js +3 -2
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +31 -28
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +12 -7
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +42 -24
- package/dest/services/reqresp/reqresp.d.ts +5 -29
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +16 -206
- package/dest/services/service.d.ts +7 -13
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/config.d.ts +2 -23
- package/dest/services/tx_collection/config.d.ts.map +1 -1
- package/dest/services/tx_collection/config.js +2 -55
- package/dest/services/tx_collection/file_store_tx_collection.d.ts +12 -28
- package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/file_store_tx_collection.js +43 -83
- package/dest/services/tx_collection/index.d.ts +2 -3
- package/dest/services/tx_collection/index.d.ts.map +1 -1
- package/dest/services/tx_collection/index.js +0 -1
- package/dest/services/tx_collection/instrumentation.d.ts +1 -1
- package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
- package/dest/services/tx_collection/instrumentation.js +0 -2
- package/dest/services/tx_collection/tx_collection.d.ts +35 -51
- package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_collection.js +275 -119
- package/dest/services/tx_collection/tx_collection_sink.d.ts +1 -1
- package/dest/services/tx_collection/tx_collection_sink.js +2 -2
- package/dest/services/tx_file_store/tx_file_store.d.ts +1 -3
- package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
- package/dest/services/tx_file_store/tx_file_store.js +4 -14
- package/dest/services/tx_provider.d.ts +3 -1
- package/dest/services/tx_provider.d.ts.map +1 -1
- package/dest/services/tx_provider.js +3 -0
- package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +1 -1
- package/dest/test-helpers/mock-pubsub.d.ts +13 -7
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +12 -37
- package/dest/test-helpers/reqresp-nodes.d.ts +4 -5
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +11 -15
- package/dest/test-helpers/test_tx_provider.d.ts +3 -1
- package/dest/test-helpers/test_tx_provider.d.ts.map +1 -1
- package/dest/test-helpers/test_tx_provider.js +3 -0
- package/dest/test-helpers/testbench-utils.d.ts +12 -14
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +19 -14
- package/dest/testbench/p2p_client_testbench_worker.d.ts +3 -5
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +13 -24
- package/dest/testbench/worker_client_manager.d.ts +3 -6
- package/dest/testbench/worker_client_manager.d.ts.map +1 -1
- package/dest/testbench/worker_client_manager.js +2 -8
- package/dest/util.d.ts +11 -6
- package/dest/util.d.ts.map +1 -1
- package/dest/util.js +35 -14
- package/dest/versioning.d.ts +3 -6
- package/dest/versioning.d.ts.map +1 -1
- package/dest/versioning.js +3 -24
- package/package.json +15 -14
- package/src/bootstrap/bootstrap.ts +9 -1
- package/src/client/factory.ts +20 -5
- package/src/client/interface.ts +6 -10
- package/src/client/p2p_client.ts +61 -36
- package/src/client/test/{tx_proposal_collector/README.md → p2p_client.batch_tx_requester.bench.README.md} +23 -53
- package/src/config.ts +94 -6
- package/src/errors/reqresp.error.ts +0 -25
- package/src/mem_pools/attestation_pool/attestation_pool.ts +309 -238
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +198 -62
- package/src/mem_pools/attestation_pool/mocks.ts +13 -8
- package/src/mem_pools/instrumentation.ts +5 -1
- package/src/mem_pools/tx_pool_v2/archive/tx_archive.ts +1 -9
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +73 -90
- package/src/msg_validators/attestation_validator/attestation_validator.ts +21 -1
- package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +11 -7
- package/src/msg_validators/clock_tolerance.ts +13 -12
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +9 -2
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +14 -2
- package/src/msg_validators/proposal_validator/proposal_validator.ts +39 -2
- package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -12
- package/src/msg_validators/tx_validator/allowed_public_setup.ts +3 -3
- package/src/msg_validators/tx_validator/cached_tx_validator.ts +31 -0
- package/src/msg_validators/tx_validator/data_validator.ts +2 -0
- package/src/msg_validators/tx_validator/factory.ts +15 -6
- package/src/msg_validators/tx_validator/index.ts +2 -0
- package/src/msg_validators/tx_validator/tx_proof_validator.ts +2 -0
- package/src/msg_validators/tx_validator/tx_validation_cache.ts +101 -0
- package/src/services/discv5/discV5_service.ts +38 -5
- package/src/services/dummy_service.ts +12 -43
- package/src/services/libp2p/libp2p_service.ts +169 -167
- package/src/services/peer-manager/peer_manager.ts +1 -0
- package/src/services/reqresp/README.md +0 -14
- package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +39 -52
- package/src/services/reqresp/batch-tx-requester/interface.ts +11 -4
- package/src/services/reqresp/batch-tx-requester/tx_validator.ts +12 -25
- package/src/services/reqresp/interface.ts +2 -38
- package/src/services/reqresp/protocols/block_txs/bitvector.ts +3 -2
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +34 -30
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +47 -20
- package/src/services/reqresp/reqresp.ts +11 -251
- package/src/services/service.ts +9 -30
- package/src/services/tx_collection/config.ts +3 -80
- package/src/services/tx_collection/file_store_tx_collection.ts +54 -103
- package/src/services/tx_collection/index.ts +1 -6
- package/src/services/tx_collection/instrumentation.ts +1 -7
- package/src/services/tx_collection/tx_collection.ts +330 -173
- package/src/services/tx_collection/tx_collection_sink.ts +2 -2
- package/src/services/tx_file_store/tx_file_store.ts +5 -17
- package/src/services/tx_provider.ts +5 -0
- package/src/test-helpers/make-test-p2p-clients.ts +1 -0
- package/src/test-helpers/mock-pubsub.ts +17 -60
- package/src/test-helpers/reqresp-nodes.ts +8 -20
- package/src/test-helpers/test_tx_provider.ts +5 -0
- package/src/test-helpers/testbench-utils.ts +23 -27
- package/src/testbench/p2p_client_testbench_worker.ts +16 -42
- package/src/testbench/worker_client_manager.ts +4 -19
- package/src/util.ts +32 -17
- package/src/versioning.ts +3 -33
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.d.ts +0 -2
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.d.ts.map +0 -1
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +0 -318
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.d.ts +0 -73
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.d.ts.map +0 -1
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.js +0 -8
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +0 -64
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +0 -1
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +0 -151
- package/dest/services/tx_collection/fast_tx_collection.d.ts +0 -51
- package/dest/services/tx_collection/fast_tx_collection.d.ts.map +0 -1
- package/dest/services/tx_collection/fast_tx_collection.js +0 -311
- package/dest/services/tx_collection/proposal_tx_collector.d.ts +0 -48
- package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +0 -1
- package/dest/services/tx_collection/proposal_tx_collector.js +0 -50
- package/dest/services/tx_collection/slow_tx_collection.d.ts +0 -57
- package/dest/services/tx_collection/slow_tx_collection.d.ts.map +0 -1
- package/dest/services/tx_collection/slow_tx_collection.js +0 -211
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +0 -357
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.ts +0 -43
- package/src/services/reqresp/connection-sampler/batch_connection_sampler.ts +0 -161
- package/src/services/tx_collection/fast_tx_collection.ts +0 -361
- package/src/services/tx_collection/proposal_tx_collector.ts +0 -108
- package/src/services/tx_collection/slow_tx_collection.ts +0 -266
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
1
|
import { toArray } from '@aztec/foundation/iterable';
|
|
3
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
3
|
import { BlockProposal, CheckpointAttestation, CheckpointProposal } from '@aztec/stdlib/p2p';
|
|
@@ -12,83 +11,109 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
12
11
|
*
|
|
13
12
|
* Attestations and proposals observed via the p2p network are stored for requests
|
|
14
13
|
* from the validator to produce a block, or to serve to other peers.
|
|
14
|
+
*
|
|
15
|
+
* Equivocation detection: distinct *signed payload hashes* arriving at the same
|
|
16
|
+
* position are tracked in the matching index multimap so the equivocation count
|
|
17
|
+
* reaches 2 even when archive collides on `feeAssetPriceModifier` variants.
|
|
18
|
+
* Proposal bytes are retained per accepted payload hash, up to the same equivocation
|
|
19
|
+
* caps, for slashing watchers that need signed P2P proposals.
|
|
15
20
|
*/ export class AttestationPool {
|
|
16
21
|
store;
|
|
17
22
|
log;
|
|
18
23
|
metrics;
|
|
19
|
-
// Checkpoint attestations from
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
// Key:
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
// Checkpoint attestations from `${paddedSlot}-${signer}` to serialized CheckpointAttestation.
|
|
25
|
+
// Stores the first attestation seen per (slot, signer); subsequent distinct payload
|
|
26
|
+
// hashes from the same signer are tracked only in `attestationHashesPerSlotAndSigner`
|
|
27
|
+
// for equivocation detection.
|
|
28
|
+
attestationPerSlotAndSigner;
|
|
29
|
+
// Distinct payload hashes seen per (slot, signer) for tracking attestation equivocations.
|
|
30
|
+
// Key: `${paddedSlot}-${signerAddress}`, Value: CheckpointProposalHash (`0x`-prefixed hex)
|
|
31
|
+
attestationHashesPerSlotAndSigner;
|
|
32
|
+
// Checkpoint proposals from `${paddedSlot}-${payloadHash}` to serialized CheckpointProposalCore.
|
|
33
|
+
// Stores every accepted distinct payload up to MAX_CHECKPOINT_PROPOSALS_PER_SLOT.
|
|
34
|
+
checkpointProposalsPerSlotAndHash;
|
|
35
|
+
// Distinct payload hashes seen per slot. Hash collision = duplicate.
|
|
36
|
+
// Hash count reaching 2 = equivocation.
|
|
37
|
+
// Key: slot number, Value: CheckpointProposalHash (`0x`-prefixed hex)
|
|
38
|
+
checkpointProposalHashesPerSlot;
|
|
39
|
+
// Block proposals from `${paddedSlot}-${paddedIndex}-${payloadHash}` to serialized BlockProposal.
|
|
40
|
+
// Stores every accepted distinct payload up to MAX_BLOCK_PROPOSALS_PER_POSITION.
|
|
41
|
+
blockProposalsPerSlotIndexAndHash;
|
|
42
|
+
// Distinct payload hashes seen per (slot, indexWithinCheckpoint).
|
|
43
|
+
// Key: slot * (1 << INDEX_BITS) + indexWithinCheckpoint, Value: BlockProposalHash (`0x`-prefixed hex)
|
|
44
|
+
blockProposalHashesPerSlotAndIndex;
|
|
45
|
+
// Secondary index from archive root to all retained block proposal keys.
|
|
46
|
+
blockProposalKeysPerArchive;
|
|
35
47
|
constructor(store, telemetry = getTelemetryClient(), log = createLogger('aztec:attestation_pool')){
|
|
36
48
|
this.store = store;
|
|
37
49
|
this.log = log;
|
|
38
50
|
this.poolStats = async ()=>{
|
|
39
51
|
return {
|
|
40
|
-
itemCount: await this.
|
|
52
|
+
itemCount: await this.attestationPerSlotAndSigner.sizeAsync()
|
|
41
53
|
};
|
|
42
54
|
};
|
|
43
55
|
// Initialize block proposal storage
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
56
|
+
this.blockProposalsPerSlotIndexAndHash = store.openMap('block_proposals_by_slot_index_and_hash');
|
|
57
|
+
this.blockProposalHashesPerSlotAndIndex = store.openMultiMap('block_proposals_for_slot_and_index');
|
|
58
|
+
this.blockProposalKeysPerArchive = store.openMultiMap('block_proposals_by_archive');
|
|
46
59
|
// Initialize checkpoint attestations storage
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
60
|
+
this.attestationPerSlotAndSigner = store.openMap('checkpoint_attestations');
|
|
61
|
+
this.attestationHashesPerSlotAndSigner = store.openMultiMap('checkpoint_attestations_per_slot_and_signer');
|
|
49
62
|
// Initialize checkpoint proposal storage
|
|
50
|
-
this.
|
|
51
|
-
this.
|
|
63
|
+
this.checkpointProposalsPerSlotAndHash = store.openMap('checkpoint_proposals_by_slot_and_hash');
|
|
64
|
+
this.checkpointProposalHashesPerSlot = store.openMultiMap('checkpoint_proposals_for_slot');
|
|
52
65
|
this.metrics = new PoolInstrumentation(telemetry, PoolName.ATTESTATION_POOL, this.poolStats);
|
|
53
66
|
}
|
|
54
67
|
poolStats;
|
|
55
68
|
/** Returns whether the pool is empty. */ async isEmpty() {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
const [attestationCount, blockProposalCount, checkpointProposalCount] = await Promise.all([
|
|
70
|
+
this.attestationPerSlotAndSigner.sizeAsync(),
|
|
71
|
+
this.blockProposalsPerSlotIndexAndHash.sizeAsync(),
|
|
72
|
+
this.checkpointProposalsPerSlotAndHash.sizeAsync()
|
|
73
|
+
]);
|
|
74
|
+
return attestationCount === 0 && blockProposalCount === 0 && checkpointProposalCount === 0;
|
|
75
|
+
}
|
|
76
|
+
/** Number of bits reserved for indexWithinCheckpoint in position keys. */ static INDEX_BITS = 10;
|
|
77
|
+
/** Maximum indexWithinCheckpoint value (2^10 - 1 = 1023). */ static MAX_INDEX = (1 << AttestationPool.INDEX_BITS) - 1;
|
|
78
|
+
/** Decimal digits used to left-pad slot numbers in string keys.
|
|
79
|
+
* 10 digits ≈ 3500 years at 36 s/slot, leaving ample headroom. */ static SLOT_PAD_DIGITS = 10;
|
|
80
|
+
/** Fixed-width decimal slot string for use in composite string keys. */ slotPaddedKey(slot) {
|
|
81
|
+
return slot.toString().padStart(AttestationPool.SLOT_PAD_DIGITS, '0');
|
|
63
82
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const proposalIdStr = typeof proposalId === 'string' ? proposalId : Buffer.isBuffer(proposalId) ? Fr.fromBuffer(proposalId).toString() : proposalId.toString();
|
|
67
|
-
return `${slotStr}-${proposalIdStr}`;
|
|
83
|
+
/** Fixed-width decimal index string for use in composite string keys. */ indexPaddedKey(indexWithinCheckpoint) {
|
|
84
|
+
return indexWithinCheckpoint.toString().padStart(4, '0');
|
|
68
85
|
}
|
|
69
|
-
|
|
70
|
-
return `${this.
|
|
86
|
+
/** Key for retained block proposals. */ getBlockProposalKey(slot, indexWithinCheckpoint, payloadHash) {
|
|
87
|
+
return `${this.slotPaddedKey(slot)}-${this.indexPaddedKey(indexWithinCheckpoint)}-${payloadHash}`;
|
|
71
88
|
}
|
|
72
|
-
/**
|
|
73
|
-
const slotStr = new Fr(slot).toString();
|
|
89
|
+
/** Range bounds for all retained block proposals in a slot. */ getBlockProposalKeyRangeForSlot(slot) {
|
|
74
90
|
return {
|
|
75
|
-
start: `${
|
|
76
|
-
end: `${
|
|
77
|
-
};
|
|
91
|
+
start: `${this.slotPaddedKey(slot)}-`,
|
|
92
|
+
end: `${this.slotPaddedKey(slot + 1)}-`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** Key for retained checkpoint proposals. */ getCheckpointProposalKey(slot, payloadHash) {
|
|
96
|
+
return `${this.slotPaddedKey(slot)}-${payloadHash}`;
|
|
78
97
|
}
|
|
79
|
-
/**
|
|
80
|
-
const proposalKey = this.getProposalKey(slot, proposalId);
|
|
98
|
+
/** Range bounds for all retained checkpoint proposals in a slot. */ getCheckpointProposalKeyRangeForSlot(slot) {
|
|
81
99
|
return {
|
|
82
|
-
start: `${
|
|
83
|
-
end: `${
|
|
100
|
+
start: `${this.slotPaddedKey(slot)}-`,
|
|
101
|
+
end: `${this.slotPaddedKey(slot + 1)}-`
|
|
84
102
|
};
|
|
85
103
|
}
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
/** Key for the per-(slot, signer) attestation main store and equivocation index. */ getSlotSignerKey(slot, signerAddress) {
|
|
105
|
+
return `${this.slotPaddedKey(slot)}-${signerAddress}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns range bounds for querying all attestations for a given slot.
|
|
109
|
+
* Fixed-width padding ensures the slot prefix sorts cleanly, so using the next
|
|
110
|
+
* slot's prefix as the upper bound captures exactly the current slot's entries.
|
|
111
|
+
*/ getAttestationKeyRangeForSlot(slot) {
|
|
112
|
+
return {
|
|
113
|
+
start: `${this.slotPaddedKey(slot)}-`,
|
|
114
|
+
end: `${this.slotPaddedKey(slot + 1)}-`
|
|
115
|
+
};
|
|
89
116
|
}
|
|
90
|
-
/** Number of bits reserved for indexWithinCheckpoint in position keys. */ static INDEX_BITS = 10;
|
|
91
|
-
/** Maximum indexWithinCheckpoint value (2^10 - 1 = 1023). */ static MAX_INDEX = (1 << AttestationPool.INDEX_BITS) - 1;
|
|
92
117
|
/** Creates a position key for block proposals: slot * 1024 + indexWithinCheckpoint.
|
|
93
118
|
* Uses multiplication instead of bit-shift to avoid 32-bit signed integer overflow
|
|
94
119
|
* (bit-shift overflows after slot ~2^21, roughly 278 days of uptime). */ getBlockPositionKey(slot, indexWithinCheckpoint) {
|
|
@@ -97,31 +122,35 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
97
122
|
}
|
|
98
123
|
return slot * (1 << AttestationPool.INDEX_BITS) + indexWithinCheckpoint;
|
|
99
124
|
}
|
|
125
|
+
/** Returns true if the multimap already contains the given value for the given key. */ async multimapHasValue(map, key, value) {
|
|
126
|
+
const values = await toArray(map.getValuesAsync(key));
|
|
127
|
+
return values.includes(value);
|
|
128
|
+
}
|
|
100
129
|
/**
|
|
101
130
|
* Attempts to add a block proposal to the pool.
|
|
102
131
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* -
|
|
106
|
-
*
|
|
132
|
+
* - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the
|
|
133
|
+
* exact same signed payload returns `alreadyExists: true`.
|
|
134
|
+
* - Distinct payload hashes at the same `(slot, indexWithinCheckpoint)` are tracked
|
|
135
|
+
* in the equivocation index and retained up to the cap.
|
|
107
136
|
*
|
|
108
137
|
* @param blockProposal - The block proposal to add
|
|
109
138
|
* @returns Result indicating whether the proposal was added and duplicate detection info
|
|
110
139
|
*/ async tryAddBlockProposal(blockProposal) {
|
|
111
140
|
return await this.store.transactionAsync(async ()=>{
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
const count = await this.
|
|
141
|
+
const positionKey = this.getBlockPositionKey(blockProposal.slotNumber, blockProposal.indexWithinCheckpoint);
|
|
142
|
+
const payloadHash = blockProposal.getPayloadHash();
|
|
143
|
+
// Hash already tracked => exact same signed payload was already received.
|
|
144
|
+
if (await this.multimapHasValue(this.blockProposalHashesPerSlotAndIndex, positionKey, payloadHash)) {
|
|
145
|
+
const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey);
|
|
117
146
|
return {
|
|
118
147
|
added: false,
|
|
119
148
|
alreadyExists: true,
|
|
120
149
|
count
|
|
121
150
|
};
|
|
122
151
|
}
|
|
123
|
-
//
|
|
124
|
-
const count = await this.
|
|
152
|
+
// Cap reached for this position (no more new payload hashes accepted).
|
|
153
|
+
const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey);
|
|
125
154
|
if (count >= MAX_BLOCK_PROPOSALS_PER_POSITION) {
|
|
126
155
|
return {
|
|
127
156
|
added: false,
|
|
@@ -129,10 +158,14 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
129
158
|
count
|
|
130
159
|
};
|
|
131
160
|
}
|
|
132
|
-
//
|
|
133
|
-
await this.
|
|
161
|
+
// Track the new payload hash for equivocation detection.
|
|
162
|
+
await this.blockProposalHashesPerSlotAndIndex.set(positionKey, payloadHash);
|
|
163
|
+
const proposalKey = this.getBlockProposalKey(blockProposal.slotNumber, blockProposal.indexWithinCheckpoint, payloadHash);
|
|
164
|
+
await this.blockProposalsPerSlotIndexAndHash.set(proposalKey, blockProposal.withoutSignedTxs().toBuffer());
|
|
165
|
+
await this.blockProposalKeysPerArchive.set(blockProposal.archive.toString(), proposalKey);
|
|
134
166
|
this.log.debug(`Added block proposal for slot ${blockProposal.slotNumber} and index ${blockProposal.indexWithinCheckpoint}`, {
|
|
135
|
-
|
|
167
|
+
archive: blockProposal.archive.toString(),
|
|
168
|
+
payloadHash,
|
|
136
169
|
slotNumber: blockProposal.slotNumber,
|
|
137
170
|
indexWithinCheckpoint: blockProposal.indexWithinCheckpoint
|
|
138
171
|
});
|
|
@@ -143,47 +176,67 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
143
176
|
};
|
|
144
177
|
});
|
|
145
178
|
}
|
|
146
|
-
/** Gets the count of block proposals for a given position (slot, indexWithinCheckpoint). */ getBlockProposalCountForPosition(slot, indexWithinCheckpoint) {
|
|
147
|
-
const positionKey = this.getBlockPositionKey(slot, indexWithinCheckpoint);
|
|
148
|
-
return this.blockProposalsForSlotAndIndex.getValueCountAsync(positionKey);
|
|
149
|
-
}
|
|
150
|
-
/** Internal method - must be called within a transaction. */ async addBlockProposal(blockProposal) {
|
|
151
|
-
const proposalId = blockProposal.archive.toString();
|
|
152
|
-
// Strip signedTxs before storing to avoid persisting full tx data
|
|
153
|
-
await this.blockProposals.set(proposalId, blockProposal.withoutSignedTxs().toBuffer());
|
|
154
|
-
// Index by slot and position for duplicate detection
|
|
155
|
-
const positionKey = this.getBlockPositionKey(blockProposal.slotNumber, blockProposal.indexWithinCheckpoint);
|
|
156
|
-
await this.blockProposalsForSlotAndIndex.set(positionKey, proposalId);
|
|
157
|
-
}
|
|
158
179
|
/**
|
|
159
|
-
* Get block proposal by
|
|
180
|
+
* Get block proposal by archive root.
|
|
160
181
|
*
|
|
161
|
-
*
|
|
182
|
+
* Resolves the archive root through the archive index and returns the first
|
|
183
|
+
* retained proposal for that archive. This lookup is used by block-txs req/resp,
|
|
184
|
+
* where any retained proposal for the requested archive gives the tx hash list.
|
|
162
185
|
*
|
|
163
|
-
* @
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
186
|
+
* @param archiveRoot - The archive root to look up
|
|
187
|
+
* @return The block proposal if it exists and its archive matches, otherwise undefined.
|
|
188
|
+
*/ async getBlockProposalByArchive(archiveRoot) {
|
|
189
|
+
for await (const proposalKey of this.blockProposalKeysPerArchive.getValuesAsync(archiveRoot)){
|
|
190
|
+
const buffer = await this.blockProposalsPerSlotIndexAndHash.getAsync(proposalKey);
|
|
191
|
+
if (!buffer || buffer.length === 0) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const proposal = BlockProposal.fromBuffer(buffer);
|
|
196
|
+
if (proposal.archive.toString() === archiveRoot) {
|
|
197
|
+
return proposal;
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
continue;
|
|
169
201
|
}
|
|
170
|
-
} catch {
|
|
171
|
-
return undefined;
|
|
172
202
|
}
|
|
173
203
|
return undefined;
|
|
174
204
|
}
|
|
205
|
+
/** Returns retained signed proposals for a slot. */ async getProposalsForSlot(slot) {
|
|
206
|
+
const blockProposals = [];
|
|
207
|
+
const checkpointProposals = [];
|
|
208
|
+
for await (const [_, buffer] of this.blockProposalsPerSlotIndexAndHash.entriesAsync(this.getBlockProposalKeyRangeForSlot(slot))){
|
|
209
|
+
try {
|
|
210
|
+
blockProposals.push(BlockProposal.fromBuffer(buffer));
|
|
211
|
+
} catch {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for await (const [_, buffer] of this.checkpointProposalsPerSlotAndHash.entriesAsync(this.getCheckpointProposalKeyRangeForSlot(slot))){
|
|
216
|
+
try {
|
|
217
|
+
checkpointProposals.push(CheckpointProposal.fromBuffer(buffer));
|
|
218
|
+
} catch {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
blockProposals,
|
|
224
|
+
checkpointProposals
|
|
225
|
+
};
|
|
226
|
+
}
|
|
175
227
|
/** Checks if any block proposals exist for a given slot (at index 0). */ async hasBlockProposalsForSlot(slot) {
|
|
176
228
|
const positionKey = this.getBlockPositionKey(slot, 0);
|
|
177
|
-
const count = await this.
|
|
229
|
+
const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey);
|
|
178
230
|
return count > 0;
|
|
179
231
|
}
|
|
180
232
|
/**
|
|
181
233
|
* Attempts to add a checkpoint proposal to the pool.
|
|
182
234
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* -
|
|
186
|
-
*
|
|
235
|
+
* - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the
|
|
236
|
+
* exact same signed payload returns `alreadyExists: true`.
|
|
237
|
+
* - Distinct payload hashes at the same slot are tracked in the equivocation index.
|
|
238
|
+
* Distinct payload bytes are retained up to the same cap so slashing watchers
|
|
239
|
+
* can recover signed proposals.
|
|
187
240
|
*
|
|
188
241
|
* Note: This method only handles the CheckpointProposalCore. If the original
|
|
189
242
|
* CheckpointProposal contains a lastBlock, the caller should extract it via
|
|
@@ -192,20 +245,18 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
192
245
|
* @param proposal - The checkpoint proposal core to add
|
|
193
246
|
* @returns Result indicating whether the proposal was added and duplicate detection info
|
|
194
247
|
*/ async tryAddCheckpointProposal(proposal) {
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const count = await this.checkpointProposalsForSlot.getValueCountAsync(proposal.slotNumber);
|
|
248
|
+
return await this.store.transactionAsync(async ()=>{
|
|
249
|
+
const slot = proposal.slotNumber;
|
|
250
|
+
const payloadHash = proposal.getPayloadHash();
|
|
251
|
+
if (await this.multimapHasValue(this.checkpointProposalHashesPerSlot, slot, payloadHash)) {
|
|
252
|
+
const count = await this.checkpointProposalHashesPerSlot.getValueCountAsync(slot);
|
|
201
253
|
return {
|
|
202
254
|
added: false,
|
|
203
255
|
alreadyExists: true,
|
|
204
256
|
count
|
|
205
257
|
};
|
|
206
258
|
}
|
|
207
|
-
|
|
208
|
-
const count = await this.checkpointProposalsForSlot.getValueCountAsync(proposal.slotNumber);
|
|
259
|
+
const count = await this.checkpointProposalHashesPerSlot.getValueCountAsync(slot);
|
|
209
260
|
if (count >= MAX_CHECKPOINT_PROPOSALS_PER_SLOT) {
|
|
210
261
|
return {
|
|
211
262
|
added: false,
|
|
@@ -213,11 +264,13 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
213
264
|
count
|
|
214
265
|
};
|
|
215
266
|
}
|
|
216
|
-
//
|
|
217
|
-
await this.
|
|
218
|
-
this.
|
|
219
|
-
|
|
220
|
-
|
|
267
|
+
// Track the new payload hash for equivocation detection.
|
|
268
|
+
await this.checkpointProposalHashesPerSlot.set(slot, payloadHash);
|
|
269
|
+
await this.checkpointProposalsPerSlotAndHash.set(this.getCheckpointProposalKey(slot, payloadHash), proposal.toBuffer());
|
|
270
|
+
this.log.debug(`Added checkpoint proposal for slot ${slot}`, {
|
|
271
|
+
archive: proposal.archive.toString(),
|
|
272
|
+
payloadHash,
|
|
273
|
+
slotNumber: slot
|
|
221
274
|
});
|
|
222
275
|
return {
|
|
223
276
|
added: true,
|
|
@@ -225,60 +278,63 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
225
278
|
count: count + 1
|
|
226
279
|
};
|
|
227
280
|
});
|
|
228
|
-
return result;
|
|
229
|
-
}
|
|
230
|
-
/** Internal method - must be called within a transaction. */ async addCheckpointProposal(proposal) {
|
|
231
|
-
const slotKey = proposal.slotNumber;
|
|
232
|
-
const proposalId = proposal.archive.toString();
|
|
233
|
-
await this.checkpointProposalsForSlot.set(slotKey, proposalId);
|
|
234
|
-
await this.checkpointProposals.set(proposalId, proposal.toBuffer());
|
|
235
281
|
}
|
|
236
282
|
/**
|
|
237
|
-
* Get checkpoint proposal
|
|
283
|
+
* Get a retained checkpoint proposal stored for the given slot.
|
|
284
|
+
* If multiple proposals were retained for an equivocation, returns the lowest
|
|
285
|
+
* payload hash deterministically.
|
|
238
286
|
*
|
|
239
287
|
* Returns a CheckpointProposalCore (without lastBlock info) since the lastBlock
|
|
240
288
|
* is extracted and stored separately as a BlockProposal when added.
|
|
241
289
|
*
|
|
242
|
-
* @param
|
|
243
|
-
* @return The checkpoint proposal core if
|
|
244
|
-
*/ async getCheckpointProposal(
|
|
245
|
-
const buffer
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
290
|
+
* @param slot - The slot to look up
|
|
291
|
+
* @return The checkpoint proposal core if one is stored, otherwise undefined.
|
|
292
|
+
*/ async getCheckpointProposal(slot) {
|
|
293
|
+
for await (const [_, buffer] of this.checkpointProposalsPerSlotAndHash.entriesAsync(this.getCheckpointProposalKeyRangeForSlot(slot))){
|
|
294
|
+
try {
|
|
295
|
+
if (buffer && buffer.length > 0) {
|
|
296
|
+
return CheckpointProposal.fromBuffer(buffer);
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
continue;
|
|
249
300
|
}
|
|
250
|
-
} catch {
|
|
251
|
-
return undefined;
|
|
252
301
|
}
|
|
253
302
|
return undefined;
|
|
254
303
|
}
|
|
255
304
|
/**
|
|
256
305
|
* Adds own checkpoint attestations to the pool.
|
|
257
|
-
* Skips
|
|
306
|
+
* Skips per-signer cap and equivocation tracking; the caller is trusted.
|
|
307
|
+
* Each (slot, signer) gets a single stored attestation; later additions overwrite.
|
|
258
308
|
*/ async addOwnCheckpointAttestations(attestations) {
|
|
259
309
|
await this.store.transactionAsync(async ()=>{
|
|
260
310
|
for (const attestation of attestations){
|
|
261
311
|
const slotNumber = attestation.payload.header.slotNumber;
|
|
262
|
-
const proposalId = attestation.archive.toString();
|
|
263
312
|
const sender = attestation.getSender();
|
|
264
313
|
// Skip attestations with invalid signatures
|
|
265
314
|
if (!sender) {
|
|
266
315
|
this.log.warn(`Skipping own checkpoint attestation with invalid signature for slot ${slotNumber}`, {
|
|
267
316
|
signature: attestation.signature.toString(),
|
|
268
317
|
slotNumber,
|
|
269
|
-
|
|
318
|
+
archive: attestation.archive.toString()
|
|
270
319
|
});
|
|
271
320
|
continue;
|
|
272
321
|
}
|
|
273
322
|
const address = sender.toString();
|
|
274
|
-
const ownKey = this.
|
|
275
|
-
|
|
323
|
+
const ownKey = this.getSlotSignerKey(slotNumber, address);
|
|
324
|
+
const payloadHash = attestation.getPayloadHash();
|
|
325
|
+
await this.attestationPerSlotAndSigner.set(ownKey, attestation.toBuffer());
|
|
276
326
|
this.metrics.trackMempoolItemAdded(ownKey);
|
|
327
|
+
// Track our own payload hash so that an equivocating attestation from another
|
|
328
|
+
// peer at the same (slot, signer) is detected as a duplicate.
|
|
329
|
+
if (!await this.multimapHasValue(this.attestationHashesPerSlotAndSigner, ownKey, payloadHash)) {
|
|
330
|
+
await this.attestationHashesPerSlotAndSigner.set(ownKey, payloadHash);
|
|
331
|
+
}
|
|
277
332
|
this.log.debug(`Added own checkpoint attestation for slot ${slotNumber} from ${address}`, {
|
|
278
333
|
signature: attestation.signature.toString(),
|
|
279
334
|
slotNumber,
|
|
280
335
|
address,
|
|
281
|
-
|
|
336
|
+
archive: attestation.archive.toString(),
|
|
337
|
+
payloadHash
|
|
282
338
|
});
|
|
283
339
|
}
|
|
284
340
|
});
|
|
@@ -286,29 +342,30 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
286
342
|
/**
|
|
287
343
|
* Get all checkpoint attestations for a given slot.
|
|
288
344
|
*
|
|
345
|
+
* Returns one attestation per (slot, signer) — the first seen for each signer.
|
|
346
|
+
* Later equivocating attestations from the same signer are tracked in the index
|
|
347
|
+
* but their bytes are not retained.
|
|
348
|
+
*
|
|
289
349
|
* @param slot - The slot to query
|
|
290
350
|
* @return CheckpointAttestations
|
|
291
351
|
*/ async getCheckpointAttestationsForSlot(slot) {
|
|
292
352
|
const range = this.getAttestationKeyRangeForSlot(slot);
|
|
293
353
|
const attestations = [];
|
|
294
|
-
for await (const [_, buf] of this.
|
|
354
|
+
for await (const [_, buf] of this.attestationPerSlotAndSigner.entriesAsync(range)){
|
|
295
355
|
attestations.push(CheckpointAttestation.fromBuffer(buf));
|
|
296
356
|
}
|
|
297
357
|
return attestations;
|
|
298
358
|
}
|
|
299
359
|
/**
|
|
300
|
-
* Get checkpoint attestations for slot
|
|
360
|
+
* Get checkpoint attestations for a slot whose signed payload matches the given
|
|
361
|
+
* proposal payload hash.
|
|
301
362
|
*
|
|
302
363
|
* @param slot - The slot to query
|
|
303
|
-
* @param
|
|
304
|
-
* @return CheckpointAttestations
|
|
305
|
-
*/ async getCheckpointAttestationsForSlotAndProposal(slot,
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
for await (const [_, buf] of this.checkpointAttestations.entriesAsync(range)){
|
|
309
|
-
attestations.push(CheckpointAttestation.fromBuffer(buf));
|
|
310
|
-
}
|
|
311
|
-
return attestations;
|
|
364
|
+
* @param proposalPayloadHash - Hex-encoded keccak256 of the target proposal's signed payload
|
|
365
|
+
* @return CheckpointAttestations whose `getPayloadHash()` matches `proposalPayloadHash`
|
|
366
|
+
*/ async getCheckpointAttestationsForSlotAndProposal(slot, proposalPayloadHash) {
|
|
367
|
+
const all = await this.getCheckpointAttestationsForSlot(slot);
|
|
368
|
+
return all.filter((att)=>att.getPayloadHash() === proposalPayloadHash);
|
|
312
369
|
}
|
|
313
370
|
/**
|
|
314
371
|
* Delete all pool data (attestations, proposals) older than the given slot.
|
|
@@ -319,48 +376,53 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
319
376
|
let numberOfCheckpointProposals = 0;
|
|
320
377
|
let numberOfBlockProposals = 0;
|
|
321
378
|
await this.store.transactionAsync(async ()=>{
|
|
322
|
-
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
for await (const key of this.
|
|
326
|
-
end:
|
|
379
|
+
const oldestSlotPadded = this.slotPaddedKey(oldestSlot);
|
|
380
|
+
// Delete checkpoint attestations whose key < `${oldestSlotPadded}-`. Fixed-width
|
|
381
|
+
// decimal padding means the slot prefix sorts strictly before any key at that slot.
|
|
382
|
+
for await (const key of this.attestationPerSlotAndSigner.keysAsync({
|
|
383
|
+
end: `${oldestSlotPadded}-`
|
|
327
384
|
})){
|
|
328
|
-
await this.
|
|
385
|
+
await this.attestationPerSlotAndSigner.delete(key);
|
|
329
386
|
this.metrics.trackMempoolItemRemoved(key);
|
|
330
387
|
numberOfAttestations++;
|
|
331
388
|
}
|
|
332
|
-
// Clean up per-signer-per-slot index
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const slotSignerEndKey = new Fr(oldestSlot).toString();
|
|
336
|
-
for await (const key of this.checkpointAttestationsPerSlotAndSigner.keysAsync({
|
|
337
|
-
end: slotSignerEndKey
|
|
389
|
+
// Clean up per-signer-per-slot index using the same end bound.
|
|
390
|
+
for await (const key of this.attestationHashesPerSlotAndSigner.keysAsync({
|
|
391
|
+
end: `${oldestSlotPadded}-`
|
|
338
392
|
})){
|
|
339
|
-
await this.
|
|
393
|
+
await this.attestationHashesPerSlotAndSigner.delete(key);
|
|
340
394
|
}
|
|
341
|
-
// Delete checkpoint proposals for slots < oldestSlot
|
|
342
|
-
for await (const slot of this.
|
|
395
|
+
// Delete checkpoint proposals for slots < oldestSlot.
|
|
396
|
+
for await (const slot of this.checkpointProposalHashesPerSlot.keysAsync({
|
|
343
397
|
end: oldestSlot
|
|
344
398
|
})){
|
|
345
|
-
|
|
346
|
-
for (const proposalId of proposalIds){
|
|
347
|
-
await this.checkpointProposals.delete(proposalId);
|
|
348
|
-
numberOfCheckpointProposals++;
|
|
349
|
-
}
|
|
350
|
-
await this.checkpointProposalsForSlot.delete(slot);
|
|
399
|
+
await this.checkpointProposalHashesPerSlot.delete(slot);
|
|
351
400
|
}
|
|
352
|
-
|
|
353
|
-
|
|
401
|
+
for await (const key of this.checkpointProposalsPerSlotAndHash.keysAsync({
|
|
402
|
+
end: `${oldestSlotPadded}-`
|
|
403
|
+
})){
|
|
404
|
+
await this.checkpointProposalsPerSlotAndHash.delete(key);
|
|
405
|
+
numberOfCheckpointProposals++;
|
|
406
|
+
}
|
|
407
|
+
// Delete block proposals for slots < oldestSlot, using blockProposalHashesPerSlotAndIndex as index.
|
|
408
|
+
// Key format: slot * (1 << INDEX_BITS) + indexWithinCheckpoint
|
|
354
409
|
const blockPositionEndKey = oldestSlot * (1 << AttestationPool.INDEX_BITS);
|
|
355
|
-
for await (const positionKey of this.
|
|
410
|
+
for await (const positionKey of this.blockProposalHashesPerSlotAndIndex.keysAsync({
|
|
356
411
|
end: blockPositionEndKey
|
|
357
412
|
})){
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
413
|
+
await this.blockProposalHashesPerSlotAndIndex.delete(positionKey);
|
|
414
|
+
}
|
|
415
|
+
for await (const [key, buffer] of this.blockProposalsPerSlotIndexAndHash.entriesAsync({
|
|
416
|
+
end: `${oldestSlotPadded}-`
|
|
417
|
+
})){
|
|
418
|
+
try {
|
|
419
|
+
const proposal = BlockProposal.fromBuffer(buffer);
|
|
420
|
+
await this.blockProposalKeysPerArchive.deleteValue(proposal.archive.toString(), key);
|
|
421
|
+
} catch {
|
|
422
|
+
// ignore decode errors when cleaning up
|
|
362
423
|
}
|
|
363
|
-
await this.
|
|
424
|
+
await this.blockProposalsPerSlotIndexAndHash.delete(key);
|
|
425
|
+
numberOfBlockProposals++;
|
|
364
426
|
}
|
|
365
427
|
});
|
|
366
428
|
this.log.verbose(`Deleted old pool data`, {
|
|
@@ -373,17 +435,18 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
373
435
|
/**
|
|
374
436
|
* Attempts to add a checkpoint attestation to the pool.
|
|
375
437
|
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
* -
|
|
379
|
-
*
|
|
438
|
+
* - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the
|
|
439
|
+
* exact same signed payload from the same signer returns `alreadyExists: true`.
|
|
440
|
+
* - Distinct payload hashes from the same (slot, signer) are tracked in the
|
|
441
|
+
* equivocation index. The first one's bytes are stored; later distinct hashes
|
|
442
|
+
* bump `count` so libp2p can fire its duplicate callback.
|
|
380
443
|
*
|
|
381
444
|
* @param attestation - The checkpoint attestation to add
|
|
382
|
-
* @returns Result indicating whether the attestation was added, existence info,
|
|
383
|
-
*
|
|
445
|
+
* @returns Result indicating whether the attestation was added, existence info,
|
|
446
|
+
* and number of distinct payload hashes by this signer for this slot
|
|
447
|
+
* (for equivocation detection).
|
|
384
448
|
*/ async tryAddCheckpointAttestation(attestation) {
|
|
385
449
|
const slotNumber = attestation.payload.header.slotNumber;
|
|
386
|
-
const proposalId = attestation.archive.toString();
|
|
387
450
|
const sender = attestation.getSender();
|
|
388
451
|
if (!sender) {
|
|
389
452
|
return {
|
|
@@ -393,24 +456,24 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
393
456
|
};
|
|
394
457
|
}
|
|
395
458
|
const signerAddress = sender.toString();
|
|
459
|
+
const slotSignerKey = this.getSlotSignerKey(slotNumber, signerAddress);
|
|
460
|
+
const payloadHash = attestation.getPayloadHash();
|
|
396
461
|
return await this.store.transactionAsync(async ()=>{
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
// Get count of attestations by this signer for this slot (for duplicate detection)
|
|
400
|
-
const signerAttestationCount = await this.getSignerAttestationCountForSlot(slotNumber, signerAddress);
|
|
401
|
-
if (alreadyExists) {
|
|
462
|
+
if (await this.multimapHasValue(this.attestationHashesPerSlotAndSigner, slotSignerKey, payloadHash)) {
|
|
463
|
+
const count = await this.attestationHashesPerSlotAndSigner.getValueCountAsync(slotSignerKey);
|
|
402
464
|
return {
|
|
403
465
|
added: false,
|
|
404
466
|
alreadyExists: true,
|
|
405
|
-
count
|
|
467
|
+
count
|
|
406
468
|
};
|
|
407
469
|
}
|
|
408
|
-
|
|
470
|
+
const signerAttestationCount = await this.attestationHashesPerSlotAndSigner.getValueCountAsync(slotSignerKey);
|
|
409
471
|
if (signerAttestationCount >= MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER) {
|
|
410
472
|
this.log.debug(`Rejecting attestation: signer ${signerAddress} exceeded per-slot cap for slot ${slotNumber}`, {
|
|
411
473
|
slotNumber,
|
|
412
474
|
signerAddress,
|
|
413
|
-
|
|
475
|
+
archive: attestation.archive.toString(),
|
|
476
|
+
payloadHash,
|
|
414
477
|
signerAttestationCount
|
|
415
478
|
});
|
|
416
479
|
return {
|
|
@@ -419,19 +482,23 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
419
482
|
count: signerAttestationCount
|
|
420
483
|
};
|
|
421
484
|
}
|
|
422
|
-
//
|
|
423
|
-
await this.
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
const
|
|
427
|
-
|
|
485
|
+
// Track the new payload hash for equivocation detection.
|
|
486
|
+
await this.attestationHashesPerSlotAndSigner.set(slotSignerKey, payloadHash);
|
|
487
|
+
// Only the first distinct payload at (slot, signer) is stored; later
|
|
488
|
+
// equivocations are detected via the multimap but their bytes are not retained.
|
|
489
|
+
const alreadyHasStored = await this.attestationPerSlotAndSigner.hasAsync(slotSignerKey);
|
|
490
|
+
if (!alreadyHasStored) {
|
|
491
|
+
await this.attestationPerSlotAndSigner.set(slotSignerKey, attestation.toBuffer());
|
|
492
|
+
this.metrics.trackMempoolItemAdded(slotSignerKey);
|
|
493
|
+
}
|
|
428
494
|
this.log.debug(`Added checkpoint attestation for slot ${slotNumber} from ${signerAddress}`, {
|
|
429
495
|
signature: attestation.signature.toString(),
|
|
430
496
|
slotNumber,
|
|
431
497
|
address: signerAddress,
|
|
432
|
-
|
|
498
|
+
archive: attestation.archive.toString(),
|
|
499
|
+
payloadHash,
|
|
500
|
+
stored: !alreadyHasStored
|
|
433
501
|
});
|
|
434
|
-
// Return the new count
|
|
435
502
|
return {
|
|
436
503
|
added: true,
|
|
437
504
|
alreadyExists: false,
|
|
@@ -439,10 +506,6 @@ export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
|
439
506
|
};
|
|
440
507
|
});
|
|
441
508
|
}
|
|
442
|
-
/** Gets the count of attestations by a specific signer for a given slot. */ async getSignerAttestationCountForSlot(slot, signerAddress) {
|
|
443
|
-
const slotSignerKey = this.getSlotSignerKey(slot, signerAddress);
|
|
444
|
-
return await this.checkpointAttestationsPerSlotAndSigner.getValueCountAsync(slotSignerKey);
|
|
445
|
-
}
|
|
446
509
|
}
|
|
447
510
|
/** Creates an AttestationPool backed by a temporary store for testing. */ export async function createTestAttestationPool(telemetry) {
|
|
448
511
|
const { openTmpStore } = await import('@aztec/kv-store/lmdb-v2');
|