Skip to content

Commit 8ce4829

Browse files
authored
fix: Add version to gossipsub protocol (#13567)
This PR adds the protocol version string to the gossipsub topic strings. This ensures that nodes only communicate with nodes at the same version.
1 parent 593f810 commit 8ce4829

File tree

9 files changed

+388
-86
lines changed

9 files changed

+388
-86
lines changed

yarn-project/p2p/src/client/p2p_client.integration.test.ts

Lines changed: 321 additions & 34 deletions
Large diffs are not rendered by default.

yarn-project/p2p/src/services/libp2p/libp2p_service.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import {
1515
type Gossipable,
1616
P2PClientType,
1717
PeerErrorSeverity,
18-
TopicTypeMap,
18+
TopicType,
19+
createTopicString,
1920
getTopicTypeForClientType,
2021
metricsTopicStrToLabels,
2122
} from '@aztec/stdlib/p2p';
2223
import { DatabasePublicStateSource, MerkleTreeId } from '@aztec/stdlib/trees';
2324
import { Tx, type TxHash, type TxValidationResult } from '@aztec/stdlib/tx';
25+
import { compressComponentVersions } from '@aztec/stdlib/versioning';
2426
import { Attributes, OtelMetricsAdapter, type TelemetryClient, WithTracer, trackSpan } from '@aztec/telemetry-client';
2527

2628
import type { ENR } from '@chainsafe/enr';
@@ -57,6 +59,7 @@ import {
5759
} from '../../msg_validators/tx_validator/index.js';
5860
import { GossipSubEvent } from '../../types/index.js';
5961
import { type PubSubLibp2p, convertToMultiaddr } from '../../util.js';
62+
import { getVersions } from '../../versioning.js';
6063
import { AztecDatastore } from '../data_store.js';
6164
import { SnappyTransform, fastMsgIdFn, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
6265
import { gossipScoreThresholds } from '../gossipsub/scoring.js';
@@ -95,6 +98,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
9598
private attestationValidator: AttestationValidator;
9699
private blockProposalValidator: BlockProposalValidator;
97100

101+
private protocolVersion = '';
102+
private topicStrings: Record<TopicType, string> = {} as Record<TopicType, string>;
103+
98104
// Request and response sub service
99105
public reqresp: ReqResp;
100106

@@ -127,6 +133,17 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
127133
) {
128134
super(telemetry, 'LibP2PService');
129135

136+
const versions = getVersions(config);
137+
this.protocolVersion = compressComponentVersions(versions);
138+
logger.info(`Started libp2p service with protocol version ${this.protocolVersion}`);
139+
140+
this.topicStrings[TopicType.tx] = createTopicString(TopicType.tx, this.protocolVersion);
141+
this.topicStrings[TopicType.block_proposal] = createTopicString(TopicType.block_proposal, this.protocolVersion);
142+
this.topicStrings[TopicType.block_attestation] = createTopicString(
143+
TopicType.block_attestation,
144+
this.protocolVersion,
145+
);
146+
130147
const peerScoring = new PeerScoring(config);
131148
this.reqresp = new ReqResp(config, node, peerScoring);
132149

@@ -198,6 +215,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
198215
peerDiscovery.push(bootstrap({ list: bootstrapNodes }));
199216
}
200217

218+
const versions = getVersions(config);
219+
const protocolVersion = compressComponentVersions(versions);
220+
221+
const txTopic = createTopicString(TopicType.tx, protocolVersion);
222+
const blockProposalTopic = createTopicString(TopicType.block_proposal, protocolVersion);
223+
const blockAttestationTopic = createTopicString(TopicType.block_attestation, protocolVersion);
224+
201225
const node = await createLibp2p({
202226
start: false,
203227
peerId,
@@ -251,24 +275,24 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
251275
fastMsgIdFn: fastMsgIdFn,
252276
dataTransform: new SnappyTransform(),
253277
metricsRegister: otelMetricsAdapter,
254-
metricsTopicStrToLabel: metricsTopicStrToLabels(),
278+
metricsTopicStrToLabel: metricsTopicStrToLabels(protocolVersion),
255279
asyncValidation: true,
256280
scoreThresholds: gossipScoreThresholds,
257281
scoreParams: createPeerScoreParams({
258282
// IPColocation factor can be disabled for local testing - default to -5
259283
IPColocationFactorWeight: config.debugDisableColocationPenalty ? 0 : -5.0,
260284
topics: {
261-
[Tx.p2pTopic]: createTopicScoreParams({
285+
[txTopic]: createTopicScoreParams({
262286
topicWeight: 1,
263287
invalidMessageDeliveriesWeight: -20,
264288
invalidMessageDeliveriesDecay: 0.5,
265289
}),
266-
[BlockAttestation.p2pTopic]: createTopicScoreParams({
290+
[blockAttestationTopic]: createTopicScoreParams({
267291
topicWeight: 1,
268292
invalidMessageDeliveriesWeight: -20,
269293
invalidMessageDeliveriesDecay: 0.5,
270294
}),
271-
[BlockProposal.p2pTopic]: createTopicScoreParams({
295+
[blockProposalTopic]: createTopicScoreParams({
272296
topicWeight: 1,
273297
invalidMessageDeliveriesWeight: -20,
274298
invalidMessageDeliveriesDecay: 0.5,
@@ -324,7 +348,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
324348

325349
// Subscribe to standard GossipSub topics by default
326350
for (const topic of getTopicTypeForClientType(this.clientType)) {
327-
this.subscribeToTopic(TopicTypeMap[topic].p2pTopic);
351+
this.subscribeToTopic(this.topicStrings[topic]);
328352
}
329353

330354
// Create request response protocol handlers
@@ -483,13 +507,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
483507
* @param data - The message data
484508
*/
485509
protected async handleNewGossipMessage(msg: Message, msgId: string, source: PeerId) {
486-
if (msg.topic === Tx.p2pTopic) {
510+
if (msg.topic === this.topicStrings[TopicType.tx]) {
487511
await this.handleGossipedTx(msg, msgId, source);
488512
}
489-
if (msg.topic === BlockAttestation.p2pTopic && this.clientType === P2PClientType.Full) {
513+
if (msg.topic === this.topicStrings[TopicType.block_attestation] && this.clientType === P2PClientType.Full) {
490514
await this.processAttestationFromPeer(msg, msgId, source);
491515
}
492-
if (msg.topic == BlockProposal.p2pTopic) {
516+
if (msg.topic === this.topicStrings[TopicType.block_proposal]) {
493517
await this.processBlockFromPeer(msg, msgId, source);
494518
}
495519

@@ -901,7 +925,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
901925
const identifier = await message.p2pMessageIdentifier().then(i => i.toString());
902926
this.logger.trace(`Sending message ${identifier}`, { p2pMessageIdentifier: identifier });
903927

904-
const recipientsNum = await this.publishToTopic(parent.p2pTopic, message.toBuffer());
928+
const recipientsNum = await this.publishToTopic(this.topicStrings[parent.p2pTopic], message.toBuffer());
905929
this.logger.debug(`Sent message ${identifier} to ${recipientsNum} peers`, {
906930
p2pMessageIdentifier: identifier,
907931
sourcePeer: this.node.peerId.toString(),

yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { MockL2BlockSource } from '@aztec/archiver/test';
22
import type { EpochCache } from '@aztec/epoch-cache';
33
import { type Logger, createLogger } from '@aztec/foundation/log';
4+
import { sleep } from '@aztec/foundation/sleep';
45
import type { DataStoreConfig } from '@aztec/kv-store/config';
56
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
67
import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
@@ -57,6 +58,7 @@ export async function makeTestP2PClient(
5758
p2pEnabled: true,
5859
peerIdPrivateKey,
5960
p2pIp: `127.0.0.1`,
61+
listenAddress: `127.0.0.1`,
6062
p2pPort: port,
6163
bootstrapNodes: peers,
6264
peerCheckIntervalMS: 1000,
@@ -101,7 +103,16 @@ export async function makeTestP2PClients(numberOfPeers: number, testConfig: Make
101103
const clients: P2PClient[] = [];
102104
const peerIdPrivateKeys = generatePeerIdPrivateKeys(numberOfPeers);
103105

104-
const ports = await getPorts(numberOfPeers);
106+
let ports = [];
107+
while (true) {
108+
try {
109+
ports = await getPorts(numberOfPeers);
110+
break;
111+
} catch (err) {
112+
await sleep(1000);
113+
}
114+
}
115+
105116
const peerEnrs = await makeEnrs(peerIdPrivateKeys, ports, testConfig.p2pBaseConfig);
106117

107118
for (let i = 0; i < numberOfPeers; i++) {
@@ -113,5 +124,12 @@ export async function makeTestP2PClients(numberOfPeers: number, testConfig: Make
113124
}
114125

115126
await Promise.all(clients.map(client => client.isReady()));
116-
return clients;
127+
return clients.map((client, index) => {
128+
return {
129+
client,
130+
peerPrivateKey: peerIdPrivateKeys[index],
131+
port: ports[index],
132+
enr: peerEnrs[index],
133+
};
134+
});
117135
}

yarn-project/stdlib/src/p2p/block_attestation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { ZodFor } from '../schemas/index.js';
1111
import { ConsensusPayload } from './consensus_payload.js';
1212
import { Gossipable } from './gossipable.js';
1313
import { SignatureDomainSeparator, getHashedSignaturePayloadEthSignedMessage } from './signature_utils.js';
14-
import { TopicType, createTopicString } from './topic_type.js';
14+
import { TopicType } from './topic_type.js';
1515

1616
export class BlockAttestationHash extends Buffer32 {
1717
constructor(hash: Buffer) {
@@ -26,7 +26,7 @@ export class BlockAttestationHash extends Buffer32 {
2626
* will produce a block attestation over the header of the block
2727
*/
2828
export class BlockAttestation extends Gossipable {
29-
static override p2pTopic = createTopicString(TopicType.block_attestation);
29+
static override p2pTopic = TopicType.block_attestation;
3030

3131
private sender: EthAddress | undefined;
3232

yarn-project/stdlib/src/p2p/block_proposal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
getHashedSignaturePayload,
1313
getHashedSignaturePayloadEthSignedMessage,
1414
} from './signature_utils.js';
15-
import { TopicType, createTopicString } from './topic_type.js';
15+
import { TopicType } from './topic_type.js';
1616

1717
export class BlockProposalHash extends Buffer32 {
1818
constructor(hash: Buffer) {
@@ -27,7 +27,7 @@ export class BlockProposalHash extends Buffer32 {
2727
* be included in the head of the chain
2828
*/
2929
export class BlockProposal extends Gossipable {
30-
static override p2pTopic = createTopicString(TopicType.block_proposal);
30+
static override p2pTopic = TopicType.block_proposal;
3131

3232
private sender: EthAddress | undefined;
3333

yarn-project/stdlib/src/p2p/gossipable.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Buffer32 } from '@aztec/foundation/buffer';
22

3+
import type { TopicType } from './topic_type.js';
4+
35
/**
46
* Gossipable
57
*
@@ -10,7 +12,7 @@ export abstract class Gossipable {
1012
*
1113
* - The p2p topic identifier, this determines how the message is handled
1214
*/
13-
static p2pTopic: string;
15+
static p2pTopic: TopicType;
1416

1517
/** p2p Message Identifier
1618
*
Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,4 @@
1-
import { Tx } from '../tx/tx.js';
2-
import { BlockAttestation } from './block_attestation.js';
3-
import { BlockProposal } from './block_proposal.js';
4-
import type { Gossipable } from './gossipable.js';
5-
import { TopicType } from './topic_type.js';
6-
71
export interface RawGossipMessage {
82
topic: string;
93
data: Uint8Array;
104
}
11-
12-
// Force casts as we know that each field here extends Gossipable, and we just want types from Gossipable
13-
export const TopicTypeMap: Record<string, typeof Gossipable> = {
14-
[TopicType.tx]: Tx as unknown as typeof Gossipable,
15-
[TopicType.block_proposal]: BlockProposal as unknown as typeof Gossipable,
16-
[TopicType.block_attestation]: BlockAttestation as unknown as typeof Gossipable,
17-
};
18-
19-
/**
20-
* Map from topic to deserialiser
21-
*
22-
* Used in msgIdFn libp2p to get the p2pMessageIdentifier from a message
23-
*/
24-
export const TopicToDeserializer = {
25-
[Tx.p2pTopic]: Tx.fromBuffer,
26-
[BlockProposal.p2pTopic]: BlockProposal.fromBuffer,
27-
[BlockAttestation.p2pTopic]: BlockAttestation.fromBuffer,
28-
};

yarn-project/stdlib/src/p2p/topic_type.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { P2PClientType } from './client_type.js';
66
* @param topicType
77
* @returns
88
*/
9-
export function createTopicString(topicType: TopicType) {
10-
return '/aztec/' + topicType + '/0.1.0';
9+
export function createTopicString(topicType: TopicType, protocolVersion: string) {
10+
return `/aztec/${TopicType[topicType]}/${protocolVersion}`;
1111
}
1212

1313
/**
@@ -35,10 +35,10 @@ export function getTopicTypeForClientType(clientType: P2PClientType) {
3535
* ...
3636
* }
3737
*/
38-
export function metricsTopicStrToLabels() {
38+
export function metricsTopicStrToLabels(protocolVersion: string) {
3939
const topicStrToLabel = new Map<string, string>();
4040
for (const topic in TopicType) {
41-
topicStrToLabel.set(createTopicString(TopicType[topic as keyof typeof TopicType]), topic);
41+
topicStrToLabel.set(createTopicString(TopicType[topic as keyof typeof TopicType], protocolVersion), topic);
4242
}
4343

4444
return topicStrToLabel;

yarn-project/stdlib/src/tx/tx.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type { ScopedLogHash } from '../kernel/log_hash.js';
1515
import { PrivateKernelTailCircuitPublicInputs } from '../kernel/private_kernel_tail_circuit_public_inputs.js';
1616
import { ContractClassLog } from '../logs/contract_class_log.js';
1717
import { Gossipable } from '../p2p/gossipable.js';
18-
import { TopicType, createTopicString } from '../p2p/topic_type.js';
18+
import { TopicType } from '../p2p/topic_type.js';
1919
import { ClientIvcProof } from '../proofs/client_ivc_proof.js';
2020
import type { TxStats } from '../stats/stats.js';
2121
import { HashedValues } from './hashed_values.js';
@@ -26,7 +26,7 @@ import { TxHash } from './tx_hash.js';
2626
* The interface of an L2 transaction.
2727
*/
2828
export class Tx extends Gossipable {
29-
static override p2pTopic: string;
29+
static override p2pTopic = TopicType.tx;
3030
// For memoization
3131
private txHash: TxHash | undefined;
3232
private calldataMap: Map<string, Fr[]> | undefined;
@@ -53,11 +53,6 @@ export class Tx extends Gossipable {
5353
super();
5454
}
5555

56-
// Gossipable method
57-
static {
58-
this.p2pTopic = createTopicString(TopicType.tx);
59-
}
60-
6156
// Gossipable method
6257
override async p2pMessageIdentifier(): Promise<Buffer32> {
6358
return new Buffer32((await this.getTxHash()).toBuffer());

0 commit comments

Comments
 (0)