@aztec/p2p 0.0.1-commit.f2ce05ee → 0.0.1-commit.f8ca9b2f3
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/client/factory.d.ts +3 -3
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +38 -7
- package/dest/client/interface.d.ts +26 -15
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +31 -35
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +97 -138
- package/dest/config.d.ts +10 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +9 -0
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +18 -11
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +62 -37
- 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 +53 -53
- package/dest/mem_pools/attestation_pool/index.d.ts +2 -2
- package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/index.js +1 -1
- package/dest/mem_pools/index.d.ts +2 -1
- package/dest/mem_pools/index.d.ts.map +1 -1
- package/dest/mem_pools/interface.d.ts +3 -3
- package/dest/mem_pools/interface.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +87 -0
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool_v2/deleted_pool.js +180 -0
- package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -1
- package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/index.js +1 -0
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +3 -3
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +25 -3
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +36 -4
- 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 +77 -74
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +3 -3
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts +16 -3
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +13 -3
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/double_spend_validator.js +4 -4
- package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +20 -4
- package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/timestamp_validator.js +2 -2
- 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 +2 -2
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +2 -2
- 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 +71 -35
- package/dest/services/reqresp/interface.d.ts +10 -1
- package/dest/services/reqresp/interface.d.ts.map +1 -1
- package/dest/services/reqresp/interface.js +15 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +3 -3
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +7 -1
- 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 +15 -0
- package/dest/services/reqresp/protocols/tx.d.ts +7 -1
- package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/tx.js +20 -0
- 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 +11 -4
- package/dest/services/service.d.ts +18 -1
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/config.d.ts +7 -1
- package/dest/services/tx_collection/config.d.ts.map +1 -1
- package/dest/services/tx_collection/config.js +16 -0
- package/dest/services/tx_collection/fast_tx_collection.d.ts +3 -1
- package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/fast_tx_collection.js +17 -3
- package/dest/services/tx_collection/file_store_tx_collection.d.ts +44 -0
- package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -0
- package/dest/services/tx_collection/file_store_tx_collection.js +118 -0
- package/dest/services/tx_collection/file_store_tx_source.d.ts +27 -0
- package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -0
- package/dest/services/tx_collection/file_store_tx_source.js +57 -0
- package/dest/services/tx_collection/index.d.ts +2 -1
- package/dest/services/tx_collection/index.d.ts.map +1 -1
- package/dest/services/tx_collection/index.js +1 -0
- package/dest/services/tx_collection/slow_tx_collection.d.ts +3 -1
- package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/slow_tx_collection.js +48 -19
- package/dest/services/tx_collection/tx_collection.d.ts +17 -7
- package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_collection.js +58 -2
- package/dest/services/tx_collection/tx_collection_sink.d.ts +15 -6
- package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_collection_sink.js +13 -7
- package/dest/services/tx_file_store/config.d.ts +1 -3
- package/dest/services/tx_file_store/config.d.ts.map +1 -1
- package/dest/services/tx_file_store/config.js +0 -4
- package/dest/services/tx_file_store/tx_file_store.d.ts +3 -3
- package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
- 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 +5 -4
- package/dest/test-helpers/make-test-p2p-clients.d.ts +3 -3
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.d.ts +27 -1
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +97 -2
- package/dest/test-helpers/testbench-utils.d.ts +30 -24
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +87 -35
- package/dest/testbench/p2p_client_testbench_worker.js +1 -1
- package/package.json +14 -14
- package/src/client/factory.ts +65 -8
- package/src/client/interface.ts +26 -13
- package/src/client/p2p_client.ts +123 -158
- package/src/config.ts +16 -0
- package/src/index.ts +1 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +87 -44
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +57 -53
- package/src/mem_pools/attestation_pool/index.ts +3 -3
- package/src/mem_pools/index.ts +3 -0
- package/src/mem_pools/interface.ts +2 -2
- package/src/mem_pools/tx_pool_v2/README.md +59 -9
- package/src/mem_pools/tx_pool_v2/deleted_pool.ts +234 -0
- package/src/mem_pools/tx_pool_v2/index.ts +1 -0
- package/src/mem_pools/tx_pool_v2/interfaces.ts +2 -2
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +51 -5
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +90 -77
- package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +2 -2
- package/src/msg_validators/tx_validator/block_header_validator.ts +15 -3
- package/src/msg_validators/tx_validator/double_spend_validator.ts +11 -6
- package/src/msg_validators/tx_validator/timestamp_validator.ts +19 -14
- package/src/services/dummy_service.ts +6 -0
- package/src/services/encoding.ts +2 -2
- package/src/services/libp2p/libp2p_service.ts +70 -37
- package/src/services/reqresp/interface.ts +26 -1
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +2 -2
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +17 -0
- package/src/services/reqresp/protocols/tx.ts +22 -0
- package/src/services/reqresp/reqresp.ts +13 -3
- package/src/services/service.ts +20 -0
- package/src/services/tx_collection/config.ts +26 -0
- package/src/services/tx_collection/fast_tx_collection.ts +14 -2
- package/src/services/tx_collection/file_store_tx_collection.ts +152 -0
- package/src/services/tx_collection/file_store_tx_source.ts +70 -0
- package/src/services/tx_collection/index.ts +1 -0
- package/src/services/tx_collection/slow_tx_collection.ts +55 -26
- package/src/services/tx_collection/tx_collection.ts +78 -12
- package/src/services/tx_collection/tx_collection_sink.ts +17 -7
- package/src/services/tx_file_store/config.ts +0 -6
- package/src/services/tx_file_store/tx_file_store.ts +4 -4
- package/src/services/tx_provider.ts +8 -7
- package/src/test-helpers/make-test-p2p-clients.ts +3 -3
- package/src/test-helpers/mock-pubsub.ts +133 -3
- package/src/test-helpers/testbench-utils.ts +100 -40
- package/src/testbench/p2p_client_testbench_worker.ts +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
2
|
import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
-
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
4
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
4
|
import { type Logger, createLibp2pComponentLogger, createLogger } from '@aztec/foundation/log';
|
|
6
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
@@ -114,6 +113,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
114
113
|
import type {
|
|
115
114
|
P2PBlockReceivedCallback,
|
|
116
115
|
P2PCheckpointReceivedCallback,
|
|
116
|
+
P2PDuplicateAttestationCallback,
|
|
117
117
|
P2PService,
|
|
118
118
|
PeerDiscoveryService,
|
|
119
119
|
} from '../service.js';
|
|
@@ -156,6 +156,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
156
156
|
type: 'checkpoint' | 'block';
|
|
157
157
|
}) => void;
|
|
158
158
|
|
|
159
|
+
/** Callback invoked when a duplicate attestation is detected (triggers slashing). */
|
|
160
|
+
private duplicateAttestationCallback?: P2PDuplicateAttestationCallback;
|
|
161
|
+
|
|
159
162
|
/**
|
|
160
163
|
* Callback for when a block is received from a peer.
|
|
161
164
|
* @param block - The block received from the peer.
|
|
@@ -673,6 +676,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
673
676
|
this.duplicateProposalCallback = callback;
|
|
674
677
|
}
|
|
675
678
|
|
|
679
|
+
/**
|
|
680
|
+
* Registers a callback to be invoked when a duplicate attestation is detected.
|
|
681
|
+
* A validator signing attestations for different proposals at the same slot.
|
|
682
|
+
* This callback is triggered on the first duplicate (when count goes from 1 to 2).
|
|
683
|
+
*/
|
|
684
|
+
public registerDuplicateAttestationCallback(callback: P2PDuplicateAttestationCallback): void {
|
|
685
|
+
this.duplicateAttestationCallback = callback;
|
|
686
|
+
}
|
|
687
|
+
|
|
676
688
|
/**
|
|
677
689
|
* Subscribes to a topic.
|
|
678
690
|
* @param topic - The topic to subscribe to.
|
|
@@ -890,20 +902,33 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
890
902
|
const validationFunc: () => Promise<ReceivedMessageValidationResult<Tx>> = async () => {
|
|
891
903
|
const tx = Tx.fromBuffer(payloadData);
|
|
892
904
|
const isValid = await this.validatePropagatedTx(tx, source);
|
|
893
|
-
|
|
905
|
+
if (!isValid) {
|
|
906
|
+
this.logger.trace(`Rejecting invalid propagated tx`, {
|
|
907
|
+
[Attributes.P2P_ID]: source.toString(),
|
|
908
|
+
});
|
|
909
|
+
return { result: TopicValidatorResult.Reject };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Propagate only on pool acceptance
|
|
913
|
+
const txHash = tx.getTxHash();
|
|
914
|
+
const addResult = await this.mempools.txPool.addPendingTxs([tx], { source: 'gossip' });
|
|
915
|
+
|
|
916
|
+
const wasAccepted = addResult.accepted.some(h => h.equals(txHash));
|
|
917
|
+
const wasIgnored = addResult.ignored.some(h => h.equals(txHash));
|
|
894
918
|
|
|
895
919
|
this.logger.trace(`Validate propagated tx`, {
|
|
896
920
|
isValid,
|
|
897
|
-
|
|
921
|
+
wasAccepted,
|
|
922
|
+
wasIgnored,
|
|
898
923
|
[Attributes.P2P_ID]: source.toString(),
|
|
899
924
|
});
|
|
900
925
|
|
|
901
|
-
if (
|
|
902
|
-
return { result: TopicValidatorResult.
|
|
903
|
-
} else if (
|
|
926
|
+
if (wasAccepted) {
|
|
927
|
+
return { result: TopicValidatorResult.Accept, obj: tx };
|
|
928
|
+
} else if (wasIgnored) {
|
|
904
929
|
return { result: TopicValidatorResult.Ignore, obj: tx };
|
|
905
930
|
} else {
|
|
906
|
-
return { result: TopicValidatorResult.
|
|
931
|
+
return { result: TopicValidatorResult.Reject };
|
|
907
932
|
}
|
|
908
933
|
};
|
|
909
934
|
|
|
@@ -912,6 +937,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
912
937
|
return;
|
|
913
938
|
}
|
|
914
939
|
|
|
940
|
+
// Tx was accepted into pool and will be propagated - just log and record metrics
|
|
915
941
|
const txHash = tx.getTxHash();
|
|
916
942
|
const txHashString = txHash.toString();
|
|
917
943
|
this.logger.verbose(`Received tx ${txHashString} from external peer ${source.toString()} via gossip`, {
|
|
@@ -919,13 +945,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
919
945
|
txHash: txHashString,
|
|
920
946
|
});
|
|
921
947
|
|
|
922
|
-
if (this.config.dropTransactions && randomInt(1000) < this.config.dropTransactionsProbability * 1000) {
|
|
923
|
-
this.logger.warn(`Intentionally dropping tx ${txHashString} (probability rule)`);
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
948
|
this.instrumentation.incrementTxReceived(1);
|
|
928
|
-
await this.mempools.txPool.addTxs([tx]);
|
|
929
949
|
}
|
|
930
950
|
|
|
931
951
|
/**
|
|
@@ -979,40 +999,53 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
979
999
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
980
1000
|
}
|
|
981
1001
|
|
|
982
|
-
// Get committee size for the attestation's slot
|
|
983
|
-
const slot = attestation.payload.header.slotNumber;
|
|
984
|
-
const { committee } = await this.epochCache.getCommittee(slot);
|
|
985
|
-
const committeeSize = committee?.length ?? 0;
|
|
986
|
-
|
|
987
1002
|
// Try to add the attestation: this handles existence check, cap check, and adding in one call
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1003
|
+
// count is the number of attestations by this signer for this slot (for duplicate detection)
|
|
1004
|
+
const slot = attestation.payload.header.slotNumber;
|
|
1005
|
+
const { added, alreadyExists, count } =
|
|
1006
|
+
await this.mempools.attestationPool.tryAddCheckpointAttestation(attestation);
|
|
992
1007
|
|
|
993
1008
|
this.logger.trace(`Validate propagated checkpoint attestation`, {
|
|
994
1009
|
added,
|
|
995
1010
|
alreadyExists,
|
|
1011
|
+
count,
|
|
996
1012
|
[Attributes.SLOT_NUMBER]: slot.toString(),
|
|
997
1013
|
[Attributes.P2P_ID]: peerId.toString(),
|
|
998
1014
|
});
|
|
999
1015
|
|
|
1000
|
-
//
|
|
1016
|
+
// Exact same attestation received, no need to re-broadcast
|
|
1001
1017
|
if (alreadyExists) {
|
|
1002
1018
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
1003
1019
|
}
|
|
1004
1020
|
|
|
1005
|
-
// Could not add (cap reached), no need to re-broadcast
|
|
1021
|
+
// Could not add (cap reached for signer), no need to re-broadcast
|
|
1006
1022
|
if (!added) {
|
|
1007
|
-
this.logger.warn(`Dropping checkpoint attestation due to
|
|
1023
|
+
this.logger.warn(`Dropping checkpoint attestation due to cap`, {
|
|
1008
1024
|
slot: slot.toString(),
|
|
1009
1025
|
archive: attestation.archive.toString(),
|
|
1010
1026
|
source: peerId.toString(),
|
|
1027
|
+
attester: attestation.getSender()?.toString(),
|
|
1028
|
+
count,
|
|
1011
1029
|
});
|
|
1012
1030
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
1013
1031
|
}
|
|
1014
1032
|
|
|
1015
|
-
//
|
|
1033
|
+
// Check if this is a duplicate attestation (signer attested to a different proposal at the same slot)
|
|
1034
|
+
// count is the number of attestations by this signer for this slot
|
|
1035
|
+
if (count === 2) {
|
|
1036
|
+
const attester = attestation.getSender();
|
|
1037
|
+
if (attester) {
|
|
1038
|
+
this.logger.warn(`Detected duplicate attestation (equivocation) at slot ${slot}`, {
|
|
1039
|
+
slot: slot.toString(),
|
|
1040
|
+
archive: attestation.archive.toString(),
|
|
1041
|
+
source: peerId.toString(),
|
|
1042
|
+
attester: attester.toString(),
|
|
1043
|
+
});
|
|
1044
|
+
this.duplicateAttestationCallback?.({ slot, attester });
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Attestation was added successfully - accept it so other nodes can also detect the equivocation
|
|
1016
1049
|
return { result: TopicValidatorResult.Accept, obj: attestation };
|
|
1017
1050
|
}
|
|
1018
1051
|
|
|
@@ -1058,8 +1091,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1058
1091
|
}
|
|
1059
1092
|
|
|
1060
1093
|
// Try to add the proposal: this handles existence check, cap check, and adding in one call
|
|
1061
|
-
const { added, alreadyExists,
|
|
1062
|
-
const isEquivocated =
|
|
1094
|
+
const { added, alreadyExists, count } = await this.mempools.attestationPool.tryAddBlockProposal(block);
|
|
1095
|
+
const isEquivocated = count !== undefined && count > 1;
|
|
1063
1096
|
|
|
1064
1097
|
// Duplicate proposal received, no need to re-broadcast
|
|
1065
1098
|
if (alreadyExists) {
|
|
@@ -1078,7 +1111,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1078
1111
|
this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, {
|
|
1079
1112
|
...block.toBlockInfo(),
|
|
1080
1113
|
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
1081
|
-
|
|
1114
|
+
count,
|
|
1082
1115
|
proposer: block.getSender()?.toString(),
|
|
1083
1116
|
source: peerId.toString(),
|
|
1084
1117
|
});
|
|
@@ -1095,7 +1128,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1095
1128
|
proposer: proposer?.toString(),
|
|
1096
1129
|
});
|
|
1097
1130
|
// Invoke the duplicate callback on the first duplicate spotted only
|
|
1098
|
-
if (proposer &&
|
|
1131
|
+
if (proposer && count === 2) {
|
|
1099
1132
|
this.duplicateProposalCallback?.({ slot: block.slotNumber, proposer, type: 'block' });
|
|
1100
1133
|
}
|
|
1101
1134
|
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated } };
|
|
@@ -1120,8 +1153,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1120
1153
|
...block.toBlockInfo(),
|
|
1121
1154
|
});
|
|
1122
1155
|
|
|
1123
|
-
// Mark the txs in this proposal as
|
|
1124
|
-
await this.mempools.txPool.
|
|
1156
|
+
// Mark the txs in this proposal as protected
|
|
1157
|
+
await this.mempools.txPool.protectTxs(block.txHashes, block.blockHeader);
|
|
1125
1158
|
|
|
1126
1159
|
// Call the block received callback to validate the proposal.
|
|
1127
1160
|
// Note: Validators do NOT attest to individual blocks, only to checkpoint proposals.
|
|
@@ -1212,8 +1245,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1212
1245
|
// Try to add the checkpoint proposal core: this handles existence check, cap check, and adding in one call
|
|
1213
1246
|
const checkpointCore = checkpoint.toCore();
|
|
1214
1247
|
const tryAddResult = await this.mempools.attestationPool.tryAddCheckpointProposal(checkpointCore);
|
|
1215
|
-
const { added, alreadyExists,
|
|
1216
|
-
const isEquivocated =
|
|
1248
|
+
const { added, alreadyExists, count } = tryAddResult;
|
|
1249
|
+
const isEquivocated = count !== undefined && count > 1;
|
|
1217
1250
|
|
|
1218
1251
|
// Duplicate proposal received, do not re-broadcast
|
|
1219
1252
|
if (alreadyExists) {
|
|
@@ -1234,7 +1267,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1234
1267
|
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
1235
1268
|
this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
|
|
1236
1269
|
...checkpoint.toCheckpointInfo(),
|
|
1237
|
-
|
|
1270
|
+
count,
|
|
1238
1271
|
source: peerId.toString(),
|
|
1239
1272
|
});
|
|
1240
1273
|
return { result: TopicValidatorResult.Reject, obj: checkpoint, metadata: { isEquivocated, processBlock } };
|
|
@@ -1250,7 +1283,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1250
1283
|
proposer: proposer?.toString(),
|
|
1251
1284
|
});
|
|
1252
1285
|
// Invoke the duplicate callback on the first duplicate spotted only
|
|
1253
|
-
if (proposer &&
|
|
1286
|
+
if (proposer && count === 2) {
|
|
1254
1287
|
this.duplicateProposalCallback?.({ slot: checkpoint.slotNumber, proposer, type: 'checkpoint' });
|
|
1255
1288
|
}
|
|
1256
1289
|
return {
|
|
@@ -1508,7 +1541,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1508
1541
|
@trackSpan('Libp2pService.validatePropagatedTx', tx => ({
|
|
1509
1542
|
[Attributes.TX_HASH]: tx.getTxHash().toString(),
|
|
1510
1543
|
}))
|
|
1511
|
-
|
|
1544
|
+
protected async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
|
|
1512
1545
|
const currentBlockNumber = await this.archiver.getBlockNumber();
|
|
1513
1546
|
|
|
1514
1547
|
// We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
2
|
import { L2Block } from '@aztec/stdlib/block';
|
|
3
|
+
import { MAX_L2_BLOCK_SIZE_KB } from '@aztec/stdlib/p2p';
|
|
3
4
|
import { TxArray, TxHashArray } from '@aztec/stdlib/tx';
|
|
4
5
|
|
|
5
6
|
import type { PeerId } from '@libp2p/interface';
|
|
@@ -7,8 +8,13 @@ import type { PeerId } from '@libp2p/interface';
|
|
|
7
8
|
import type { P2PReqRespConfig } from './config.js';
|
|
8
9
|
import type { ConnectionSampler } from './connection-sampler/connection_sampler.js';
|
|
9
10
|
import { AuthRequest, AuthResponse } from './protocols/auth.js';
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
BlockTxsRequest,
|
|
13
|
+
BlockTxsResponse,
|
|
14
|
+
calculateBlockTxsResponseSize,
|
|
15
|
+
} from './protocols/block_txs/block_txs_reqresp.js';
|
|
11
16
|
import { StatusMessage } from './protocols/status.js';
|
|
17
|
+
import { calculateTxResponseSize } from './protocols/tx.js';
|
|
12
18
|
import type { ReqRespStatus } from './status.js';
|
|
13
19
|
|
|
14
20
|
/*
|
|
@@ -211,6 +217,25 @@ export const subProtocolMap = {
|
|
|
211
217
|
},
|
|
212
218
|
};
|
|
213
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Type for a function that calculates the expected response size in KB for a given request.
|
|
222
|
+
*/
|
|
223
|
+
export type ExpectedResponseSizeCalculator = (requestBuffer: Buffer) => number;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Map of sub-protocols to their expected response size calculators.
|
|
227
|
+
* These are used to validate that responses don't exceed expected sizes based on request parameters.
|
|
228
|
+
*/
|
|
229
|
+
export const subProtocolSizeCalculators: Record<ReqRespSubProtocol, ExpectedResponseSizeCalculator> = {
|
|
230
|
+
[ReqRespSubProtocol.TX]: calculateTxResponseSize,
|
|
231
|
+
[ReqRespSubProtocol.BLOCK_TXS]: calculateBlockTxsResponseSize,
|
|
232
|
+
[ReqRespSubProtocol.BLOCK]: () => MAX_L2_BLOCK_SIZE_KB,
|
|
233
|
+
[ReqRespSubProtocol.STATUS]: () => 1,
|
|
234
|
+
[ReqRespSubProtocol.PING]: () => 1,
|
|
235
|
+
[ReqRespSubProtocol.AUTH]: () => 1,
|
|
236
|
+
[ReqRespSubProtocol.GOODBYE]: () => 1, // No response expected, but provide minimal limit
|
|
237
|
+
};
|
|
238
|
+
|
|
214
239
|
export interface ReqRespInterface {
|
|
215
240
|
start(
|
|
216
241
|
subProtocolHandlers: Partial<ReqRespSubProtocolHandlers>,
|
|
@@ -5,7 +5,7 @@ import { TxArray } from '@aztec/stdlib/tx';
|
|
|
5
5
|
import type { PeerId } from '@libp2p/interface';
|
|
6
6
|
|
|
7
7
|
import type { AttestationPoolApi } from '../../../../mem_pools/attestation_pool/attestation_pool.js';
|
|
8
|
-
import type {
|
|
8
|
+
import type { TxPoolV2 } from '../../../../mem_pools/tx_pool_v2/interfaces.js';
|
|
9
9
|
import type { ReqRespSubProtocolHandler } from '../../interface.js';
|
|
10
10
|
import { ReqRespStatus, ReqRespStatusError } from '../../status.js';
|
|
11
11
|
import { BitVector } from './bitvector.js';
|
|
@@ -21,7 +21,7 @@ import { BlockTxsRequest, BlockTxsResponse } from './block_txs_reqresp.js';
|
|
|
21
21
|
export function reqRespBlockTxsHandler(
|
|
22
22
|
attestationPool: AttestationPoolApi,
|
|
23
23
|
archiver: L2BlockSource,
|
|
24
|
-
txPool:
|
|
24
|
+
txPool: TxPoolV2,
|
|
25
25
|
): ReqRespSubProtocolHandler {
|
|
26
26
|
/**
|
|
27
27
|
* Handler for block txs requests
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
2
|
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
3
|
+
import { MAX_TX_SIZE_KB } from '@aztec/stdlib/p2p';
|
|
3
4
|
import { TxArray, type TxHash, TxHashArray } from '@aztec/stdlib/tx';
|
|
4
5
|
|
|
5
6
|
import { BitVector } from './bitvector.js';
|
|
@@ -125,3 +126,19 @@ export class BlockTxsResponse {
|
|
|
125
126
|
return new BlockTxsResponse(Fr.ZERO, new TxArray(), BitVector.init(0, []));
|
|
126
127
|
}
|
|
127
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Calculate the expected response size for a BLOCK_TXS request.
|
|
132
|
+
* @param requestBuffer - The serialized request buffer containing BlockTxsRequest
|
|
133
|
+
* @returns Expected response size in KB
|
|
134
|
+
*/
|
|
135
|
+
export function calculateBlockTxsResponseSize(requestBuffer: Buffer): number {
|
|
136
|
+
try {
|
|
137
|
+
const request = BlockTxsRequest.fromBuffer(requestBuffer);
|
|
138
|
+
const requestedTxCount = request.txIndices.getTrueIndices().length;
|
|
139
|
+
return requestedTxCount * MAX_TX_SIZE_KB + 1; // +1 KB overhead for serialization
|
|
140
|
+
} catch {
|
|
141
|
+
// If we can't parse the request, fall back to allowing a single transaction response
|
|
142
|
+
return MAX_TX_SIZE_KB + 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { chunk } from '@aztec/foundation/collection';
|
|
2
|
+
import { MAX_TX_SIZE_KB } from '@aztec/stdlib/p2p';
|
|
2
3
|
import { TxArray, TxHash, TxHashArray } from '@aztec/stdlib/tx';
|
|
3
4
|
|
|
4
5
|
import type { PeerId } from '@libp2p/interface';
|
|
@@ -55,3 +56,24 @@ export function reqRespTxHandler(mempools: MemPools): ReqRespSubProtocolHandler
|
|
|
55
56
|
export function chunkTxHashesRequest(hashes: TxHash[], chunkSize = 1): Array<TxHashArray> {
|
|
56
57
|
return chunk(hashes, chunkSize).map(chunk => new TxHashArray(...chunk));
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Calculate the expected response size for a TX request.
|
|
62
|
+
* @param requestBuffer - The serialized request buffer containing TxHashArray
|
|
63
|
+
* @returns Expected response size in KB
|
|
64
|
+
*/
|
|
65
|
+
export function calculateTxResponseSize(requestBuffer: Buffer): number {
|
|
66
|
+
try {
|
|
67
|
+
const txHashes = TxHashArray.fromBuffer(requestBuffer);
|
|
68
|
+
// TxHashArray.fromBuffer returns empty array on parse failure, so check for that
|
|
69
|
+
if (txHashes.length === 0 && requestBuffer.length > 0) {
|
|
70
|
+
// If we got an empty array but had a non-empty buffer, parsing likely failed
|
|
71
|
+
// Fall back to allowing a single transaction response
|
|
72
|
+
return MAX_TX_SIZE_KB + 1;
|
|
73
|
+
}
|
|
74
|
+
return Math.max(txHashes.length, 1) * MAX_TX_SIZE_KB + 1; // +1 KB overhead, at least 1 tx
|
|
75
|
+
} catch {
|
|
76
|
+
// If we can't parse the request, fall back to allowing a single transaction response
|
|
77
|
+
return MAX_TX_SIZE_KB + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
type ReqRespSubProtocolValidators,
|
|
37
37
|
type SubProtocolMap,
|
|
38
38
|
responseFromBuffer,
|
|
39
|
+
subProtocolSizeCalculators,
|
|
39
40
|
} from './interface.js';
|
|
40
41
|
import { ReqRespMetrics } from './metrics.js';
|
|
41
42
|
import {
|
|
@@ -437,6 +438,9 @@ export class ReqResp implements ReqRespInterface {
|
|
|
437
438
|
try {
|
|
438
439
|
this.metrics.recordRequestSent(subProtocol);
|
|
439
440
|
|
|
441
|
+
// Calculate expected response size based on the request payload
|
|
442
|
+
const expectedSizeKb = subProtocolSizeCalculators[subProtocol](payload);
|
|
443
|
+
|
|
440
444
|
this.logger.trace(`Sending request to peer ${peerId.toString()} on sub protocol ${subProtocol}`);
|
|
441
445
|
stream = await this.connectionSampler.dialProtocol(peerId, subProtocol, dialTimeout);
|
|
442
446
|
this.logger.trace(
|
|
@@ -444,11 +448,14 @@ export class ReqResp implements ReqRespInterface {
|
|
|
444
448
|
);
|
|
445
449
|
|
|
446
450
|
const timeoutErr = new IndividualReqRespTimeoutError();
|
|
451
|
+
// Create a wrapper to pass the expected size to readMessage
|
|
452
|
+
const readMessageWithSizeLimit = (source: AsyncIterable<Uint8ArrayList>) =>
|
|
453
|
+
this.readMessage(source, expectedSizeKb);
|
|
447
454
|
const [_, resp] = await executeTimeout(
|
|
448
455
|
signal =>
|
|
449
456
|
Promise.all([
|
|
450
457
|
pipeline([payload], stream!.sink, { signal }),
|
|
451
|
-
pipeline(stream!.source,
|
|
458
|
+
pipeline(stream!.source, readMessageWithSizeLimit, { signal }),
|
|
452
459
|
]),
|
|
453
460
|
this.individualRequestTimeoutMs,
|
|
454
461
|
() => timeoutErr,
|
|
@@ -510,8 +517,11 @@ export class ReqResp implements ReqRespInterface {
|
|
|
510
517
|
* The message is split into two components
|
|
511
518
|
* - The first chunk should contain a control byte, indicating the status of the response see `ReqRespStatus`
|
|
512
519
|
* - The second chunk should contain the response data
|
|
520
|
+
*
|
|
521
|
+
* @param source - The async iterable source of data chunks
|
|
522
|
+
* @param maxSizeKb - Optional maximum expected size in KB for the decompressed response
|
|
513
523
|
*/
|
|
514
|
-
private async readMessage(source: AsyncIterable<Uint8ArrayList
|
|
524
|
+
private async readMessage(source: AsyncIterable<Uint8ArrayList>, maxSizeKb?: number): Promise<ReqRespResponse> {
|
|
515
525
|
let status: ReqRespStatus | undefined;
|
|
516
526
|
const chunks: Uint8Array[] = [];
|
|
517
527
|
|
|
@@ -536,7 +546,7 @@ export class ReqResp implements ReqRespInterface {
|
|
|
536
546
|
}
|
|
537
547
|
|
|
538
548
|
const messageData = Buffer.concat(chunks);
|
|
539
|
-
const message: Buffer = this.snappyTransform.inboundTransformData(messageData);
|
|
549
|
+
const message: Buffer = this.snappyTransform.inboundTransformData(messageData, undefined, maxSizeKb);
|
|
540
550
|
|
|
541
551
|
return {
|
|
542
552
|
status: status ?? ReqRespStatus.UNKNOWN,
|
package/src/services/service.ts
CHANGED
|
@@ -57,6 +57,19 @@ export type DuplicateProposalInfo = {
|
|
|
57
57
|
*/
|
|
58
58
|
export type P2PDuplicateProposalCallback = (info: DuplicateProposalInfo) => void;
|
|
59
59
|
|
|
60
|
+
/** Minimal info passed to the duplicate attestation callback. */
|
|
61
|
+
export type DuplicateAttestationInfo = {
|
|
62
|
+
slot: SlotNumber;
|
|
63
|
+
attester: EthAddress;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Callback for when a duplicate attestation is detected (equivocation).
|
|
68
|
+
* A validator signing attestations for different proposals at the same slot.
|
|
69
|
+
* Invoked on the first duplicate (when count goes from 1 to 2).
|
|
70
|
+
*/
|
|
71
|
+
export type P2PDuplicateAttestationCallback = (info: DuplicateAttestationInfo) => void;
|
|
72
|
+
|
|
60
73
|
/**
|
|
61
74
|
* The interface for a P2P service implementation.
|
|
62
75
|
*/
|
|
@@ -106,6 +119,13 @@ export interface P2PService {
|
|
|
106
119
|
*/
|
|
107
120
|
registerDuplicateProposalCallback(callback: P2PDuplicateProposalCallback): void;
|
|
108
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Registers a callback invoked when a duplicate attestation is detected (equivocation).
|
|
124
|
+
* A validator signing attestations for different proposals at the same slot.
|
|
125
|
+
* The callback is triggered on the first duplicate (when count goes from 1 to 2).
|
|
126
|
+
*/
|
|
127
|
+
registerDuplicateAttestationCallback(callback: P2PDuplicateAttestationCallback): void;
|
|
128
|
+
|
|
109
129
|
getEnr(): ENR | undefined;
|
|
110
130
|
|
|
111
131
|
getPeers(includePending?: boolean): PeerInfo[];
|
|
@@ -31,6 +31,12 @@ export type TxCollectionConfig = {
|
|
|
31
31
|
txCollectionNodeRpcMaxBatchSize: number;
|
|
32
32
|
/** Which collector implementation to use for missing txs collection */
|
|
33
33
|
txCollectionMissingTxsCollectorType: MissingTxsCollectorType;
|
|
34
|
+
/** A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection */
|
|
35
|
+
txCollectionFileStoreUrls: string[];
|
|
36
|
+
/** Delay in ms before file store collection starts after slow collection is triggered */
|
|
37
|
+
txCollectionFileStoreSlowDelayMs: number;
|
|
38
|
+
/** Delay in ms before file store collection starts after fast collection is triggered */
|
|
39
|
+
txCollectionFileStoreFastDelayMs: number;
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
export const txCollectionConfigMappings: ConfigMappingsType<TxCollectionConfig> = {
|
|
@@ -95,4 +101,24 @@ export const txCollectionConfigMappings: ConfigMappingsType<TxCollectionConfig>
|
|
|
95
101
|
description: 'Which collector implementation to use for missing txs collection (new or old)',
|
|
96
102
|
...enumConfigHelper(['new', 'old'] as const, 'new'),
|
|
97
103
|
},
|
|
104
|
+
txCollectionFileStoreUrls: {
|
|
105
|
+
env: 'TX_COLLECTION_FILE_STORE_URLS',
|
|
106
|
+
description: 'A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection',
|
|
107
|
+
parseEnv: (val: string) =>
|
|
108
|
+
val
|
|
109
|
+
.split(',')
|
|
110
|
+
.map(url => url.trim())
|
|
111
|
+
.filter(url => url.length > 0),
|
|
112
|
+
defaultValue: [],
|
|
113
|
+
},
|
|
114
|
+
txCollectionFileStoreSlowDelayMs: {
|
|
115
|
+
env: 'TX_COLLECTION_FILE_STORE_SLOW_DELAY_MS',
|
|
116
|
+
description: 'Delay before file store collection starts after slow collection',
|
|
117
|
+
...numberConfigHelper(24_000),
|
|
118
|
+
},
|
|
119
|
+
txCollectionFileStoreFastDelayMs: {
|
|
120
|
+
env: 'TX_COLLECTION_FILE_STORE_FAST_DELAY_MS',
|
|
121
|
+
description: 'Delay before file store collection starts after fast collection',
|
|
122
|
+
...numberConfigHelper(2_000),
|
|
123
|
+
},
|
|
98
124
|
};
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
SendBatchRequestCollector,
|
|
21
21
|
} from './proposal_tx_collector.js';
|
|
22
22
|
import type { FastCollectionRequest, FastCollectionRequestInput } from './tx_collection.js';
|
|
23
|
-
import type { TxCollectionSink } from './tx_collection_sink.js';
|
|
23
|
+
import type { TxAddContext, TxCollectionSink } from './tx_collection_sink.js';
|
|
24
24
|
import type { TxSource } from './tx_source.js';
|
|
25
25
|
|
|
26
26
|
export class FastTxCollection {
|
|
@@ -77,7 +77,7 @@ export class FastTxCollection {
|
|
|
77
77
|
// This promise is used to await for the collection to finish during the main collectFast method.
|
|
78
78
|
// It gets resolved in `foundTxs` when all txs have been collected, or rejected if the request is aborted or hits the deadline.
|
|
79
79
|
const promise = promiseWithResolvers<void>();
|
|
80
|
-
setTimeout(() => promise.reject(new TimeoutError(`Timed out while collecting txs`)), timeout);
|
|
80
|
+
const timeoutTimer = setTimeout(() => promise.reject(new TimeoutError(`Timed out while collecting txs`)), timeout);
|
|
81
81
|
|
|
82
82
|
const request: FastCollectionRequest = {
|
|
83
83
|
...input,
|
|
@@ -89,6 +89,7 @@ export class FastTxCollection {
|
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
const [duration] = await elapsed(() => this.collectFast(request, { ...opts }));
|
|
92
|
+
clearTimeout(timeoutTimer);
|
|
92
93
|
|
|
93
94
|
this.log.verbose(
|
|
94
95
|
`Collected ${request.foundTxs.size} txs out of ${txHashes.length} for ${input.type} at slot ${blockInfo.slotNumber}`,
|
|
@@ -234,6 +235,7 @@ export class FastTxCollection {
|
|
|
234
235
|
method: 'fast-node-rpc',
|
|
235
236
|
...request.blockInfo,
|
|
236
237
|
},
|
|
238
|
+
this.getAddContext(request),
|
|
237
239
|
);
|
|
238
240
|
|
|
239
241
|
// Clear from the active requests the txs we just requested
|
|
@@ -287,6 +289,7 @@ export class FastTxCollection {
|
|
|
287
289
|
},
|
|
288
290
|
Array.from(request.missingTxHashes).map(txHash => TxHash.fromString(txHash)),
|
|
289
291
|
{ description: `reqresp for slot ${slotNumber}`, method: 'fast-req-resp', ...opts, ...request.blockInfo },
|
|
292
|
+
this.getAddContext(request),
|
|
290
293
|
);
|
|
291
294
|
} catch (err) {
|
|
292
295
|
this.log.error(`Error sending fast reqresp request for txs`, err, {
|
|
@@ -296,6 +299,15 @@ export class FastTxCollection {
|
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
301
|
|
|
302
|
+
/** Returns the TxAddContext for the given request, used by the sink to add txs to the pool correctly. */
|
|
303
|
+
private getAddContext(request: FastCollectionRequest): TxAddContext {
|
|
304
|
+
if (request.type === 'proposal') {
|
|
305
|
+
return { type: 'proposal', blockHeader: request.blockProposal.blockHeader };
|
|
306
|
+
} else {
|
|
307
|
+
return { type: 'mined', block: request.block };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
299
311
|
/**
|
|
300
312
|
* Handle txs by marking them as found for the requests that are waiting for them, and resolves the request if all its txs have been found.
|
|
301
313
|
* Called internally and from the main tx collection manager whenever the tx pool emits a tx-added event.
|