@aztec/p2p 0.75.0 → 0.76.1
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.map +1 -1
- package/dest/bootstrap/bootstrap.js +9 -4
- package/dest/client/factory.d.ts +4 -2
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +4 -4
- package/dest/config.d.ts +30 -14
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +30 -14
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +2 -2
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +1 -1
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +2 -2
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +1 -1
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts +2 -2
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts.map +1 -1
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.js +1 -1
- package/dest/services/discv5/discV5_service.d.ts +5 -1
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +65 -18
- package/dest/services/dummy_service.d.ts +1 -0
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +2 -1
- package/dest/services/libp2p/libp2p_logger.d.ts +7 -0
- package/dest/services/libp2p/libp2p_logger.d.ts.map +1 -0
- package/dest/services/libp2p/libp2p_logger.js +67 -0
- package/dest/services/libp2p/libp2p_service.d.ts +3 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +42 -10
- package/dest/services/reqresp/interface.d.ts +9 -0
- package/dest/services/reqresp/interface.d.ts.map +1 -1
- package/dest/services/reqresp/interface.js +1 -1
- package/dest/services/reqresp/protocols/goodbye.js +2 -2
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limiter.js +4 -2
- package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
- package/dest/services/reqresp/reqresp.d.ts +7 -2
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +90 -21
- package/dest/services/reqresp/status.d.ts +31 -0
- package/dest/services/reqresp/status.d.ts.map +1 -0
- package/dest/services/reqresp/status.js +52 -0
- package/dest/services/service.d.ts +1 -0
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/types.d.ts +1 -7
- package/dest/services/types.d.ts.map +1 -1
- package/dest/services/types.js +2 -10
- package/dest/test-helpers/generate-peer-id-private-keys.d.ts +7 -0
- package/dest/test-helpers/generate-peer-id-private-keys.d.ts.map +1 -0
- package/dest/test-helpers/generate-peer-id-private-keys.js +15 -0
- package/dest/test-helpers/get-ports.d.ts +7 -0
- package/dest/test-helpers/get-ports.d.ts.map +1 -0
- package/dest/test-helpers/get-ports.js +8 -0
- package/dest/test-helpers/index.d.ts +6 -0
- package/dest/test-helpers/index.d.ts.map +1 -0
- package/dest/test-helpers/index.js +6 -0
- package/dest/test-helpers/make-enrs.d.ts +16 -0
- package/dest/test-helpers/make-enrs.d.ts.map +1 -0
- package/dest/test-helpers/make-enrs.js +35 -0
- package/dest/test-helpers/make-test-p2p-clients.d.ts +37 -0
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -0
- package/dest/test-helpers/make-test-p2p-clients.js +71 -0
- package/dest/{mocks/index.d.ts → test-helpers/reqresp-nodes.d.ts} +6 -5
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -0
- package/dest/test-helpers/reqresp-nodes.js +183 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -0
- package/dest/testbench/p2p_client_testbench_worker.js +125 -0
- package/dest/versioning.d.ts +12 -0
- package/dest/versioning.d.ts.map +1 -0
- package/dest/versioning.js +38 -0
- package/package.json +10 -8
- package/src/bootstrap/bootstrap.ts +9 -3
- package/src/client/factory.ts +12 -5
- package/src/config.ts +56 -29
- package/src/msg_validators/attestation_validator/attestation_validator.ts +3 -3
- package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +3 -3
- package/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts +3 -3
- package/src/services/discv5/discV5_service.ts +67 -18
- package/src/services/dummy_service.ts +2 -0
- package/src/services/libp2p/libp2p_logger.ts +78 -0
- package/src/services/libp2p/libp2p_service.ts +47 -10
- package/src/services/reqresp/interface.ts +11 -0
- package/src/services/reqresp/protocols/goodbye.ts +1 -1
- package/src/services/reqresp/rate-limiter/rate_limiter.ts +3 -1
- package/src/services/reqresp/rate-limiter/rate_limits.ts +2 -2
- package/src/services/reqresp/reqresp.ts +120 -25
- package/src/services/reqresp/status.ts +59 -0
- package/src/services/service.ts +2 -0
- package/src/services/types.ts +2 -10
- package/src/test-helpers/generate-peer-id-private-keys.ts +15 -0
- package/src/test-helpers/get-ports.ts +8 -0
- package/src/test-helpers/index.ts +5 -0
- package/src/test-helpers/make-enrs.ts +44 -0
- package/src/test-helpers/make-test-p2p-clients.ts +124 -0
- package/src/{mocks/index.ts → test-helpers/reqresp-nodes.ts} +10 -5
- package/src/testbench/README.md +20 -0
- package/src/testbench/p2p_client_testbench_worker.ts +156 -0
- package/src/testbench/scripts/run_testbench.sh +7 -0
- package/src/versioning.ts +50 -0
- package/dest/mocks/index.d.ts.map +0 -1
- package/dest/mocks/index.js +0 -181
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A testbench worker that creates a p2p client and listens for commands from the parent.
|
|
3
|
+
*
|
|
4
|
+
* Used when running testbench commands
|
|
5
|
+
*/
|
|
6
|
+
import { MockL2BlockSource } from '@aztec/archiver/test';
|
|
7
|
+
import { P2PClientType, Tx, TxStatus, type WorldStateSynchronizer } from '@aztec/circuit-types';
|
|
8
|
+
import { type EpochCacheInterface } from '@aztec/epoch-cache';
|
|
9
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
11
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
12
|
+
import { type DataStoreConfig } from '@aztec/kv-store/config';
|
|
13
|
+
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
|
|
14
|
+
|
|
15
|
+
import { type P2PConfig } from '../config.js';
|
|
16
|
+
import { createP2PClient } from '../index.js';
|
|
17
|
+
import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js';
|
|
18
|
+
import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js';
|
|
19
|
+
import { type TxPool } from '../mem_pools/tx_pool/index.js';
|
|
20
|
+
import { AlwaysTrueCircuitVerifier } from '../test-helpers/reqresp-nodes.js';
|
|
21
|
+
|
|
22
|
+
// Simple mock implementation
|
|
23
|
+
function mockTxPool(): TxPool {
|
|
24
|
+
// Mock all methods
|
|
25
|
+
return {
|
|
26
|
+
addTxs: () => Promise.resolve(),
|
|
27
|
+
getTxByHash: () => Promise.resolve(undefined),
|
|
28
|
+
getArchivedTxByHash: () => Promise.resolve(undefined),
|
|
29
|
+
markAsMined: () => Promise.resolve(),
|
|
30
|
+
markMinedAsPending: () => Promise.resolve(),
|
|
31
|
+
deleteTxs: () => Promise.resolve(),
|
|
32
|
+
getAllTxs: () => Promise.resolve([]),
|
|
33
|
+
getAllTxHashes: () => Promise.resolve([]),
|
|
34
|
+
getPendingTxHashes: () => Promise.resolve([]),
|
|
35
|
+
getMinedTxHashes: () => Promise.resolve([]),
|
|
36
|
+
getTxStatus: () => Promise.resolve(TxStatus.PENDING),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mockAttestationPool(): AttestationPool {
|
|
41
|
+
return {
|
|
42
|
+
addAttestations: () => Promise.resolve(),
|
|
43
|
+
deleteAttestations: () => Promise.resolve(),
|
|
44
|
+
deleteAttestationsOlderThan: () => Promise.resolve(),
|
|
45
|
+
deleteAttestationsForSlot: () => Promise.resolve(),
|
|
46
|
+
deleteAttestationsForSlotAndProposal: () => Promise.resolve(),
|
|
47
|
+
getAttestationsForSlot: () => Promise.resolve([]),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function mockEpochProofQuotePool(): EpochProofQuotePool {
|
|
52
|
+
return {
|
|
53
|
+
addQuote: () => {},
|
|
54
|
+
getQuotes: () => [],
|
|
55
|
+
deleteQuotesToEpoch: () => {},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mockEpochCache(): EpochCacheInterface {
|
|
60
|
+
return {
|
|
61
|
+
getCommittee: () => Promise.resolve([] as EthAddress[]),
|
|
62
|
+
getProposerIndexEncoding: () => '0x' as `0x${string}`,
|
|
63
|
+
getEpochAndSlotNow: () => ({ epoch: 0n, slot: 0n, ts: 0n }),
|
|
64
|
+
computeProposerIndex: () => 0n,
|
|
65
|
+
getProposerInCurrentOrNextSlot: () =>
|
|
66
|
+
Promise.resolve({
|
|
67
|
+
currentProposer: EthAddress.ZERO,
|
|
68
|
+
nextProposer: EthAddress.ZERO,
|
|
69
|
+
currentSlot: 0n,
|
|
70
|
+
nextSlot: 0n,
|
|
71
|
+
}),
|
|
72
|
+
isInCommittee: () => Promise.resolve(false),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
77
|
+
process.on('message', async msg => {
|
|
78
|
+
const { type, config, clientIndex } = msg as { type: string; config: P2PConfig; clientIndex: number };
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
if (type === 'START') {
|
|
82
|
+
const txPool = mockTxPool();
|
|
83
|
+
const attestationPool = mockAttestationPool();
|
|
84
|
+
const epochProofQuotePool = mockEpochProofQuotePool();
|
|
85
|
+
const epochCache = mockEpochCache();
|
|
86
|
+
const worldState = {} as WorldStateSynchronizer;
|
|
87
|
+
const l2BlockSource = new MockL2BlockSource();
|
|
88
|
+
await l2BlockSource.createBlocks(100);
|
|
89
|
+
|
|
90
|
+
const proofVerifier = new AlwaysTrueCircuitVerifier();
|
|
91
|
+
const kvStore = await openTmpStore(`test-${clientIndex}`);
|
|
92
|
+
const logger = createLogger(`p2p:${clientIndex}`);
|
|
93
|
+
|
|
94
|
+
const deps = {
|
|
95
|
+
txPool,
|
|
96
|
+
attestationPool,
|
|
97
|
+
epochProofQuotePool,
|
|
98
|
+
store: kvStore,
|
|
99
|
+
logger,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const client = await createP2PClient(
|
|
103
|
+
P2PClientType.Full,
|
|
104
|
+
config as P2PConfig & DataStoreConfig,
|
|
105
|
+
l2BlockSource,
|
|
106
|
+
proofVerifier,
|
|
107
|
+
worldState,
|
|
108
|
+
epochCache,
|
|
109
|
+
undefined,
|
|
110
|
+
deps,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Create spy for gossip messages
|
|
114
|
+
let gossipMessageCount = 0;
|
|
115
|
+
(client as any).p2pService.handleNewGossipMessage = (...args: any[]) => {
|
|
116
|
+
gossipMessageCount++;
|
|
117
|
+
process.send!({ type: 'GOSSIP_RECEIVED', count: gossipMessageCount });
|
|
118
|
+
return (client as any).p2pService.constructor.prototype.handleNewGossipMessage.apply(
|
|
119
|
+
(client as any).p2pService,
|
|
120
|
+
args,
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
await client.start();
|
|
125
|
+
// Wait until the client is ready
|
|
126
|
+
for (let i = 0; i < 100; i++) {
|
|
127
|
+
const isReady = client.isReady();
|
|
128
|
+
logger.debug(`Client ${clientIndex} isReady: ${isReady}`);
|
|
129
|
+
if (isReady) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
await sleep(1000);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Listen for commands from parent
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
137
|
+
process.on('message', async (cmd: any) => {
|
|
138
|
+
switch (cmd.type) {
|
|
139
|
+
case 'STOP':
|
|
140
|
+
await client.stop();
|
|
141
|
+
process.exit(0);
|
|
142
|
+
break;
|
|
143
|
+
case 'SEND_TX':
|
|
144
|
+
await client.sendTx(Tx.fromBuffer(Buffer.from(cmd.tx)));
|
|
145
|
+
process.send!({ type: 'TX_SENT' });
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
process.send!({ type: 'READY' });
|
|
151
|
+
}
|
|
152
|
+
} catch (err: any) {
|
|
153
|
+
process.send!({ type: 'ERROR', error: err.message });
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentsVersions,
|
|
3
|
+
checkCompressedComponentVersion,
|
|
4
|
+
compressComponentVersions,
|
|
5
|
+
getComponentsVersionsFromConfig,
|
|
6
|
+
} from '@aztec/circuit-types';
|
|
7
|
+
import { type ChainConfig } from '@aztec/circuit-types/config';
|
|
8
|
+
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
|
|
9
|
+
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vks';
|
|
10
|
+
import { protocolContractTreeRoot } from '@aztec/protocol-contracts';
|
|
11
|
+
|
|
12
|
+
import { type SignableENR } from '@chainsafe/enr';
|
|
13
|
+
import xxhashFactory from 'xxhash-wasm';
|
|
14
|
+
|
|
15
|
+
import { AZTEC_ENR_KEY } from './services/types.js';
|
|
16
|
+
|
|
17
|
+
const USE_XX_HASH = false; // Enable to reduce the size of the ENR record for production
|
|
18
|
+
const XX_HASH_LEN = 8;
|
|
19
|
+
const xxhash = await xxhashFactory();
|
|
20
|
+
|
|
21
|
+
/** Returns the component versions based on config and this build. */
|
|
22
|
+
export function getVersions(config: ChainConfig) {
|
|
23
|
+
return getComponentsVersionsFromConfig(config, protocolContractTreeRoot, getVKTreeRoot());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Sets the aztec key on the ENR record with versioning info. */
|
|
27
|
+
export function setAztecEnrKey(enr: SignableENR, config: ChainConfig, useXxHash = USE_XX_HASH) {
|
|
28
|
+
const versions = getVersions(config);
|
|
29
|
+
const value = versionsToEnrValue(versions, useXxHash);
|
|
30
|
+
enr.set(AZTEC_ENR_KEY, value);
|
|
31
|
+
return versions;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Checks the given value from an ENR record against the expected versions. */
|
|
35
|
+
export function checkAztecEnrVersion(enrValue: Buffer, expectedVersions: ComponentsVersions) {
|
|
36
|
+
if (enrValue.length === XX_HASH_LEN) {
|
|
37
|
+
const expected = versionsToEnrValue(expectedVersions, true);
|
|
38
|
+
if (!Buffer.from(enrValue).equals(expected)) {
|
|
39
|
+
throw new Error(`Expected ENR version ${expected.toString('hex')} but received ${enrValue.toString('hex')}`);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
const actual = Buffer.from(enrValue).toString();
|
|
43
|
+
checkCompressedComponentVersion(actual, expectedVersions);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function versionsToEnrValue(versions: ComponentsVersions, useXxHash: boolean) {
|
|
48
|
+
const compressed = compressComponentVersions(versions);
|
|
49
|
+
return useXxHash ? toBufferBE(xxhash.h64(compressed), XX_HASH_LEN) : Buffer.from(compressed);
|
|
50
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mocks/index.ts"],"names":[],"mappings":"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"}
|
package/dest/mocks/index.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { timesParallel } from '@aztec/foundation/collection';
|
|
2
|
-
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
|
|
3
|
-
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
4
|
-
import { gossipsub } from '@chainsafe/libp2p-gossipsub';
|
|
5
|
-
import { noise } from '@chainsafe/libp2p-noise';
|
|
6
|
-
import { yamux } from '@chainsafe/libp2p-yamux';
|
|
7
|
-
import { bootstrap } from '@libp2p/bootstrap';
|
|
8
|
-
import { identify } from '@libp2p/identify';
|
|
9
|
-
import { createSecp256k1PeerId } from '@libp2p/peer-id-factory';
|
|
10
|
-
import { tcp } from '@libp2p/tcp';
|
|
11
|
-
import getPort from 'get-port';
|
|
12
|
-
import { createLibp2p } from 'libp2p';
|
|
13
|
-
import { BootstrapNode } from '../bootstrap/bootstrap.js';
|
|
14
|
-
import { DiscV5Service } from '../services/discv5/discV5_service.js';
|
|
15
|
-
import { LibP2PService } from '../services/libp2p/libp2p_service.js';
|
|
16
|
-
import { ReqRespSubProtocol, noopValidator, } from '../services/reqresp/interface.js';
|
|
17
|
-
import { pingHandler, statusHandler } from '../services/reqresp/protocols/index.js';
|
|
18
|
-
import { ReqResp } from '../services/reqresp/reqresp.js';
|
|
19
|
-
/**
|
|
20
|
-
* Creates a libp2p node, pre configured.
|
|
21
|
-
* @param boostrapAddrs - an optional list of bootstrap addresses
|
|
22
|
-
* @returns Lip2p node
|
|
23
|
-
*/
|
|
24
|
-
export async function createLibp2pNode(boostrapAddrs = [], peerId, port, enableGossipSub = false, start = true) {
|
|
25
|
-
port = port ?? (await getPort());
|
|
26
|
-
const options = {
|
|
27
|
-
start,
|
|
28
|
-
addresses: {
|
|
29
|
-
listen: [`/ip4/0.0.0.0/tcp/${port}`],
|
|
30
|
-
announce: [`/ip4/0.0.0.0/tcp/${port}`],
|
|
31
|
-
},
|
|
32
|
-
connectionEncryption: [noise()],
|
|
33
|
-
streamMuxers: [yamux()],
|
|
34
|
-
transports: [tcp()],
|
|
35
|
-
services: {
|
|
36
|
-
identify: identify({
|
|
37
|
-
protocolPrefix: 'aztec',
|
|
38
|
-
}),
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
if (boostrapAddrs.length > 0) {
|
|
42
|
-
options.peerDiscovery = [
|
|
43
|
-
bootstrap({
|
|
44
|
-
list: boostrapAddrs,
|
|
45
|
-
}),
|
|
46
|
-
];
|
|
47
|
-
}
|
|
48
|
-
if (peerId) {
|
|
49
|
-
options.peerId = peerId;
|
|
50
|
-
}
|
|
51
|
-
if (enableGossipSub) {
|
|
52
|
-
options.services.pubsub = gossipsub({
|
|
53
|
-
allowPublishToZeroTopicPeers: true,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return await createLibp2p(options);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Test Libp2p service
|
|
60
|
-
* P2P functionality is operational, however everything else is default
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*/
|
|
64
|
-
export async function createTestLibP2PService(clientType, boostrapAddrs = [], l2BlockSource, worldStateSynchronizer, epochCache, mempools, telemetry, port = 0, peerId) {
|
|
65
|
-
peerId = peerId ?? (await createSecp256k1PeerId());
|
|
66
|
-
const config = {
|
|
67
|
-
tcpAnnounceAddress: `127.0.0.1:${port}`,
|
|
68
|
-
udpAnnounceAddress: `127.0.0.1:${port}`,
|
|
69
|
-
tcpListenAddress: `0.0.0.0:${port}`,
|
|
70
|
-
udpListenAddress: `0.0.0.0:${port}`,
|
|
71
|
-
bootstrapNodes: boostrapAddrs,
|
|
72
|
-
peerCheckIntervalMS: 1000,
|
|
73
|
-
minPeerCount: 1,
|
|
74
|
-
maxPeerCount: 5,
|
|
75
|
-
p2pEnabled: true,
|
|
76
|
-
peerIdPrivateKey: Buffer.from(peerId.privateKey).toString('hex'),
|
|
77
|
-
};
|
|
78
|
-
const discoveryService = new DiscV5Service(peerId, config, telemetry);
|
|
79
|
-
const proofVerifier = new AlwaysTrueCircuitVerifier();
|
|
80
|
-
// No bootstrap nodes provided as the libp2p service will register them in the constructor
|
|
81
|
-
const p2pNode = await createLibp2pNode([], peerId, port, /*enable gossip */ true, /**start */ false);
|
|
82
|
-
return new LibP2PService(clientType, config, p2pNode, discoveryService, mempools, l2BlockSource, epochCache, proofVerifier, worldStateSynchronizer, telemetry);
|
|
83
|
-
}
|
|
84
|
-
// Mock sub protocol handlers
|
|
85
|
-
export const MOCK_SUB_PROTOCOL_HANDLERS = {
|
|
86
|
-
[ReqRespSubProtocol.PING]: pingHandler,
|
|
87
|
-
[ReqRespSubProtocol.STATUS]: statusHandler,
|
|
88
|
-
[ReqRespSubProtocol.TX]: (_msg) => Promise.resolve(Buffer.from('tx')),
|
|
89
|
-
[ReqRespSubProtocol.GOODBYE]: (_msg) => Promise.resolve(Buffer.from('goodbye')),
|
|
90
|
-
[ReqRespSubProtocol.BLOCK]: (_msg) => Promise.resolve(Buffer.from('block')),
|
|
91
|
-
};
|
|
92
|
-
// By default, all requests are valid
|
|
93
|
-
// If you want to test an invalid response, you can override the validator
|
|
94
|
-
export const MOCK_SUB_PROTOCOL_VALIDATORS = {
|
|
95
|
-
[ReqRespSubProtocol.PING]: noopValidator,
|
|
96
|
-
[ReqRespSubProtocol.STATUS]: noopValidator,
|
|
97
|
-
[ReqRespSubProtocol.TX]: noopValidator,
|
|
98
|
-
[ReqRespSubProtocol.GOODBYE]: noopValidator,
|
|
99
|
-
[ReqRespSubProtocol.BLOCK]: noopValidator,
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* @param numberOfNodes - the number of nodes to create
|
|
103
|
-
* @returns An array of the created nodes
|
|
104
|
-
*/
|
|
105
|
-
export const createNodes = (peerScoring, numberOfNodes) => {
|
|
106
|
-
return timesParallel(numberOfNodes, () => createReqResp(peerScoring));
|
|
107
|
-
};
|
|
108
|
-
export const startNodes = async (nodes, subProtocolHandlers = MOCK_SUB_PROTOCOL_HANDLERS, subProtocolValidators = MOCK_SUB_PROTOCOL_VALIDATORS) => {
|
|
109
|
-
for (const node of nodes) {
|
|
110
|
-
await node.req.start(subProtocolHandlers, subProtocolValidators);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
export const stopNodes = async (nodes) => {
|
|
114
|
-
const stopPromises = nodes.flatMap(node => [node.req.stop(), node.p2p.stop()]);
|
|
115
|
-
await Promise.all(stopPromises);
|
|
116
|
-
};
|
|
117
|
-
// Create a req resp node, exposing the underlying p2p node
|
|
118
|
-
export const createReqResp = async (peerScoring) => {
|
|
119
|
-
const p2p = await createLibp2pNode();
|
|
120
|
-
const config = {
|
|
121
|
-
overallRequestTimeoutMs: 4000,
|
|
122
|
-
individualRequestTimeoutMs: 2000,
|
|
123
|
-
};
|
|
124
|
-
const req = new ReqResp(config, p2p, peerScoring);
|
|
125
|
-
return {
|
|
126
|
-
p2p,
|
|
127
|
-
req,
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
// Given a node list; hand shake all of the nodes with each other
|
|
131
|
-
export const connectToPeers = async (nodes) => {
|
|
132
|
-
for (const node of nodes) {
|
|
133
|
-
for (const otherNode of nodes) {
|
|
134
|
-
if (node === otherNode) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
const addr = otherNode.p2p.getMultiaddrs()[0];
|
|
138
|
-
await node.p2p.dial(addr);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
// Mock circuit verifier for testing - reimplementation from bb to avoid dependency
|
|
143
|
-
export class AlwaysTrueCircuitVerifier {
|
|
144
|
-
verifyProof(_tx) {
|
|
145
|
-
return Promise.resolve(true);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
export class AlwaysFalseCircuitVerifier {
|
|
149
|
-
verifyProof(_tx) {
|
|
150
|
-
return Promise.resolve(false);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// Bootnodes
|
|
154
|
-
export function createBootstrapNodeConfig(privateKey, port) {
|
|
155
|
-
return {
|
|
156
|
-
udpListenAddress: `0.0.0.0:${port}`,
|
|
157
|
-
udpAnnounceAddress: `127.0.0.1:${port}`,
|
|
158
|
-
peerIdPrivateKey: privateKey,
|
|
159
|
-
minPeerCount: 10,
|
|
160
|
-
maxPeerCount: 100,
|
|
161
|
-
dataDirectory: undefined,
|
|
162
|
-
dataStoreMapSizeKB: 0,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
export function createBootstrapNodeFromPrivateKey(privateKey, port, telemetry = getTelemetryClient()) {
|
|
166
|
-
const config = createBootstrapNodeConfig(privateKey, port);
|
|
167
|
-
return startBootstrapNode(config, telemetry);
|
|
168
|
-
}
|
|
169
|
-
export async function createBootstrapNode(port, telemetry = getTelemetryClient()) {
|
|
170
|
-
const peerId = await createSecp256k1PeerId();
|
|
171
|
-
const config = createBootstrapNodeConfig(Buffer.from(peerId.privateKey).toString('hex'), port);
|
|
172
|
-
return startBootstrapNode(config, telemetry);
|
|
173
|
-
}
|
|
174
|
-
async function startBootstrapNode(config, telemetry) {
|
|
175
|
-
// Open an ephemeral store that will only exist in memory
|
|
176
|
-
const store = await openTmpStore('bootstrap-node', true);
|
|
177
|
-
const bootstrapNode = new BootstrapNode(store, telemetry);
|
|
178
|
-
await bootstrapNode.start(config);
|
|
179
|
-
return bootstrapNode;
|
|
180
|
-
}
|
|
181
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9ja3MvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBUUEsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRTdELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEVBQXdCLGtCQUFrQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFbkYsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3hELE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNoRCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDaEQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUU1QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNoRSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ2xDLE9BQU8sT0FBTyxNQUFNLFVBQVUsQ0FBQztBQUMvQixPQUFPLEVBQW1DLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUV2RSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFHMUQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUdyRSxPQUFPLEVBQ0wsa0JBQWtCLEVBR2xCLGFBQWEsR0FDZCxNQUFNLGtDQUFrQyxDQUFDO0FBQzFDLE9BQU8sRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFDcEYsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBR3pE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
|