@aztec/p2p 0.49.2 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/attestation_pool/attestation_pool.d.ts +39 -0
- package/dest/attestation_pool/attestation_pool.d.ts.map +1 -0
- package/dest/attestation_pool/attestation_pool.js +2 -0
- package/dest/attestation_pool/index.d.ts +3 -0
- package/dest/attestation_pool/index.d.ts.map +1 -0
- package/dest/attestation_pool/index.js +3 -0
- package/dest/attestation_pool/memory_attestation_pool.d.ts +12 -0
- package/dest/attestation_pool/memory_attestation_pool.d.ts.map +1 -0
- package/dest/attestation_pool/memory_attestation_pool.js +56 -0
- package/dest/attestation_pool/mocks.d.ts +16 -0
- package/dest/attestation_pool/mocks.d.ts.map +1 -0
- package/dest/attestation_pool/mocks.js +29 -0
- package/dest/client/index.d.ts +2 -1
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +11 -5
- package/dest/client/p2p_client.d.ts +33 -2
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +20 -5
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/service/discV5_service.d.ts.map +1 -1
- package/dest/service/discV5_service.js +4 -3
- package/dest/service/dummy_service.d.ts +10 -4
- package/dest/service/dummy_service.d.ts.map +1 -1
- package/dest/service/dummy_service.js +14 -4
- package/dest/service/libp2p_service.d.ts +25 -6
- package/dest/service/libp2p_service.d.ts.map +1 -1
- package/dest/service/libp2p_service.js +55 -10
- package/dest/service/reqresp/handlers.d.ts +3 -0
- package/dest/service/reqresp/handlers.d.ts.map +1 -0
- package/dest/service/reqresp/handlers.js +7 -0
- package/dest/service/reqresp/index.d.ts +6 -0
- package/dest/service/reqresp/index.d.ts.map +1 -0
- package/dest/service/reqresp/index.js +6 -0
- package/dest/service/reqresp/interface.d.ts +11 -0
- package/dest/service/reqresp/interface.d.ts.map +1 -0
- package/dest/service/reqresp/interface.js +10 -0
- package/dest/service/reqresp/reqresp.d.ts +48 -0
- package/dest/service/reqresp/reqresp.d.ts.map +1 -0
- package/dest/service/reqresp/reqresp.js +112 -0
- package/dest/service/service.d.ts +6 -3
- package/dest/service/service.d.ts.map +1 -1
- package/dest/util.d.ts +1 -1
- package/dest/util.d.ts.map +1 -1
- package/dest/util.js +31 -12
- package/package.json +8 -7
- package/src/attestation_pool/attestation_pool.ts +42 -0
- package/src/attestation_pool/index.ts +2 -0
- package/src/attestation_pool/memory_attestation_pool.ts +70 -0
- package/src/attestation_pool/mocks.ts +33 -0
- package/src/client/index.ts +12 -3
- package/src/client/p2p_client.ts +63 -3
- package/src/index.ts +1 -0
- package/src/service/discV5_service.ts +3 -2
- package/src/service/dummy_service.ts +17 -4
- package/src/service/libp2p_service.ts +74 -7
- package/src/service/reqresp/handlers.ts +7 -0
- package/src/service/reqresp/index.ts +4 -0
- package/src/service/reqresp/interface.ts +13 -0
- package/src/service/reqresp/reqresp.ts +130 -0
- package/src/service/service.ts +10 -3
- package/src/util.ts +30 -10
package/dest/util.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
|
+
import { resolve } from 'dns/promises';
|
|
1
2
|
/**
|
|
2
3
|
* Converts an address string to a multiaddr string.
|
|
3
4
|
* Example usage:
|
|
4
5
|
* const tcpAddr = '123.456.7.8:80' -> /ip4/123.456.7.8/tcp/80
|
|
5
6
|
* const udpAddr = '[2001:db8::1]:8080' -> /ip6/2001:db8::1/udp/8080
|
|
6
|
-
* const dnsAddr = 'example.com:443' -> /dns4/example.com/tcp/443
|
|
7
7
|
* @param address - The address string to convert. Has to be in the format <addr>:<port>.
|
|
8
8
|
* @param protocol - The protocol to use in the multiaddr string.
|
|
9
9
|
* @returns A multiaddr compliant string.
|
|
10
10
|
*/
|
|
11
11
|
export function convertToMultiaddr(address, protocol) {
|
|
12
12
|
const [addr, port] = splitAddressPort(address, false);
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
// IPv6 address
|
|
16
|
-
multiaddrPrefix = 'ip6';
|
|
17
|
-
}
|
|
18
|
-
else if (addr.match(/^[\d.]+$/)) {
|
|
19
|
-
// IPv4 address
|
|
20
|
-
multiaddrPrefix = 'ip4';
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
13
|
+
const multiaddrPrefix = addressToMultiAddressType(addr);
|
|
14
|
+
if (multiaddrPrefix === 'dns') {
|
|
23
15
|
throw new Error('Invalid address format. Expected an IPv4 or IPv6 address.');
|
|
24
16
|
}
|
|
25
17
|
return `/${multiaddrPrefix}/${addr}/${protocol}/${port}`;
|
|
@@ -56,4 +48,31 @@ export async function getPublicIp() {
|
|
|
56
48
|
const text = await resp.text();
|
|
57
49
|
return text.trim();
|
|
58
50
|
}
|
|
59
|
-
|
|
51
|
+
export async function resolveAddressIfNecessary(address) {
|
|
52
|
+
const [addr, port] = splitAddressPort(address, false);
|
|
53
|
+
const multiaddrPrefix = addressToMultiAddressType(addr);
|
|
54
|
+
if (multiaddrPrefix === 'dns') {
|
|
55
|
+
const resolvedAddresses = await resolve(addr);
|
|
56
|
+
if (resolvedAddresses.length === 0) {
|
|
57
|
+
throw new Error(`Could not resolve address: ${addr}`);
|
|
58
|
+
}
|
|
59
|
+
return `${resolvedAddresses[0]}:${port}`;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return address;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Not public because it is not used outside of this file.
|
|
66
|
+
// Plus, it relies on `splitAddressPort` being called on the address first.
|
|
67
|
+
function addressToMultiAddressType(address) {
|
|
68
|
+
if (address.includes(':')) {
|
|
69
|
+
return 'ip6';
|
|
70
|
+
}
|
|
71
|
+
else if (address.match(/^[\d.]+$/)) {
|
|
72
|
+
return 'ip4';
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return 'dns';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFdkM7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFNLFVBQVUsa0JBQWtCLENBQUMsT0FBZSxFQUFFLFFBQXVCO0lBQ3pFLE1BQU0sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsZ0JBQWdCLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBRXRELE1BQU0sZUFBZSxHQUFHLHlCQUF5QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3hELElBQUksZUFBZSxLQUFLLEtBQUssRUFBRSxDQUFDO1FBQzlCLE1BQU0sSUFBSSxLQUFLLENBQUMsMkRBQTJELENBQUMsQ0FBQztJQUMvRSxDQUFDO0lBRUQsT0FBTyxJQUFJLGVBQWUsSUFBSSxJQUFJLElBQUksUUFBUSxJQUFJLElBQUksRUFBRSxDQUFDO0FBQzNELENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCLENBQUMsT0FBZSxFQUFFLGlCQUEwQjtJQUMxRSxJQUFJLElBQVksQ0FBQztJQUNqQixJQUFJLElBQVksQ0FBQztJQUVqQixJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUM1QiwyQ0FBMkM7UUFDM0MsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3BELElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNYLE1BQU0sSUFBSSxLQUFLLENBQUMsK0JBQStCLE9BQU8sb0NBQW9DLENBQUMsQ0FBQztRQUM5RixDQUFDO1FBQ0QsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDekIsQ0FBQztTQUFNLENBQUM7UUFDTixlQUFlO1FBQ2YsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNsQyxJQUFJLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDM0MsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsT0FBTyxrQ0FBa0MsQ0FBQyxDQUFDO1FBQ3hGLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztBQUN0QixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVc7SUFDL0IsTUFBTSxJQUFJLEdBQUcsTUFBTSxLQUFLLENBQUMsK0JBQStCLENBQUMsQ0FBQztJQUMxRCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUMvQixPQUFPLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSx5QkFBeUIsQ0FBQyxPQUFlO0lBQzdELE1BQU0sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsZ0JBQWdCLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ3RELE1BQU0sZUFBZSxHQUFHLHlCQUF5QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3hELElBQUksZUFBZSxLQUFLLEtBQUssRUFBRSxDQUFDO1FBQzlCLE1BQU0saUJBQWlCLEdBQUcsTUFBTSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDOUMsSUFBSSxpQkFBaUIsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDbkMsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN4RCxDQUFDO1FBQ0QsT0FBTyxHQUFHLGlCQUFpQixDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDO0lBQzNDLENBQUM7U0FBTSxDQUFDO1FBQ04sT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztBQUNILENBQUM7QUFFRCwwREFBMEQ7QUFDMUQsMkVBQTJFO0FBQzNFLFNBQVMseUJBQXlCLENBQUMsT0FBZTtJQUNoRCxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUMxQixPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7U0FBTSxJQUFJLE9BQU8sQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztRQUNyQyxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7U0FBTSxDQUFDO1FBQ04sT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0FBQ0gsQ0FBQyJ9
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/p2p",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": "./dest/index.js",
|
|
6
6
|
"typedocOptions": {
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"testTimeout": 15000
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@aztec/circuit-types": "0.
|
|
60
|
-
"@aztec/circuits.js": "0.
|
|
61
|
-
"@aztec/foundation": "0.
|
|
62
|
-
"@aztec/kv-store": "0.
|
|
63
|
-
"@aztec/telemetry-client": "0.
|
|
59
|
+
"@aztec/circuit-types": "0.51.0",
|
|
60
|
+
"@aztec/circuits.js": "0.51.0",
|
|
61
|
+
"@aztec/foundation": "0.51.0",
|
|
62
|
+
"@aztec/kv-store": "0.51.0",
|
|
63
|
+
"@aztec/telemetry-client": "0.51.0",
|
|
64
64
|
"@chainsafe/discv5": "9.0.0",
|
|
65
65
|
"@chainsafe/enr": "3.0.0",
|
|
66
66
|
"@chainsafe/libp2p-gossipsub": "13.0.0",
|
|
@@ -95,7 +95,8 @@
|
|
|
95
95
|
"jest-mock-extended": "^3.0.4",
|
|
96
96
|
"ts-node": "^10.9.1",
|
|
97
97
|
"typescript": "^5.0.4",
|
|
98
|
-
"uint8arrays": "^5.0.3"
|
|
98
|
+
"uint8arrays": "^5.0.3",
|
|
99
|
+
"viem": "^2.7.15"
|
|
99
100
|
},
|
|
100
101
|
"files": [
|
|
101
102
|
"dest",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type BlockAttestation } from '@aztec/circuit-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An Attestation Pool contains attestations collected by a validator
|
|
5
|
+
*
|
|
6
|
+
* Attestations that are observed via the p2p network are stored for requests
|
|
7
|
+
* from the validator to produce a block, or to serve to other peers.
|
|
8
|
+
*/
|
|
9
|
+
export interface AttestationPool {
|
|
10
|
+
/**
|
|
11
|
+
* AddAttestation
|
|
12
|
+
*
|
|
13
|
+
* @param attestations - Attestations to add into the pool
|
|
14
|
+
*/
|
|
15
|
+
addAttestations(attestations: BlockAttestation[]): Promise<void>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* DeleteAttestation
|
|
19
|
+
*
|
|
20
|
+
* @param attestations - Attestations to remove from the pool
|
|
21
|
+
*/
|
|
22
|
+
deleteAttestations(attestations: BlockAttestation[]): Promise<void>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Delete Attestations for slot
|
|
26
|
+
*
|
|
27
|
+
* Removes all attestations associated with a slot
|
|
28
|
+
*
|
|
29
|
+
* @param slot - The slot to delete.
|
|
30
|
+
*/
|
|
31
|
+
deleteAttestationsForSlot(slot: bigint): Promise<void>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get Attestations for slot
|
|
35
|
+
*
|
|
36
|
+
* Retrieve all of the attestations observed pertaining to a given slot
|
|
37
|
+
*
|
|
38
|
+
* @param slot - The slot to query
|
|
39
|
+
* @return BlockAttestations
|
|
40
|
+
*/
|
|
41
|
+
getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type BlockAttestation } from '@aztec/circuit-types';
|
|
2
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
3
|
+
|
|
4
|
+
import { type AttestationPool } from './attestation_pool.js';
|
|
5
|
+
|
|
6
|
+
export class InMemoryAttestationPool implements AttestationPool {
|
|
7
|
+
private attestations: Map</*slot=*/ bigint, Map</*address=*/ string, BlockAttestation>>;
|
|
8
|
+
|
|
9
|
+
constructor(private log = createDebugLogger('aztec:attestation_pool')) {
|
|
10
|
+
this.attestations = new Map();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
|
|
14
|
+
const slotAttestationMap = this.attestations.get(slot);
|
|
15
|
+
if (slotAttestationMap) {
|
|
16
|
+
return Promise.resolve(Array.from(slotAttestationMap.values()));
|
|
17
|
+
} else {
|
|
18
|
+
return Promise.resolve([]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async addAttestations(attestations: BlockAttestation[]): Promise<void> {
|
|
23
|
+
for (const attestation of attestations) {
|
|
24
|
+
// Perf: order and group by slot before insertion
|
|
25
|
+
const slotNumber = attestation.header.globalVariables.slotNumber;
|
|
26
|
+
|
|
27
|
+
const address = await attestation.getSender();
|
|
28
|
+
|
|
29
|
+
const slotAttestationMap = getSlotOrDefault(this.attestations, slotNumber.toBigInt());
|
|
30
|
+
slotAttestationMap.set(address.toString(), attestation);
|
|
31
|
+
|
|
32
|
+
this.log.verbose(`Added attestation for slot ${slotNumber} from ${address}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public deleteAttestationsForSlot(slot: bigint): Promise<void> {
|
|
37
|
+
// TODO(md): check if this will free the memory of the inner hash map
|
|
38
|
+
this.attestations.delete(slot);
|
|
39
|
+
this.log.verbose(`Removed attestation for slot ${slot}`);
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async deleteAttestations(attestations: BlockAttestation[]): Promise<void> {
|
|
44
|
+
for (const attestation of attestations) {
|
|
45
|
+
const slotNumber = attestation.header.globalVariables.slotNumber;
|
|
46
|
+
const slotAttestationMap = this.attestations.get(slotNumber.toBigInt());
|
|
47
|
+
if (slotAttestationMap) {
|
|
48
|
+
const address = await attestation.getSender();
|
|
49
|
+
slotAttestationMap.delete(address.toString());
|
|
50
|
+
this.log.verbose(`Deleted attestation for slot ${slotNumber} from ${address}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return Promise.resolve();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get Slot or Default
|
|
59
|
+
*
|
|
60
|
+
* Fetch the slot mapping, if it does not exist, then create a mapping and return it
|
|
61
|
+
*/
|
|
62
|
+
function getSlotOrDefault(
|
|
63
|
+
map: Map<bigint, Map<string, BlockAttestation>>,
|
|
64
|
+
slot: bigint,
|
|
65
|
+
): Map<string, BlockAttestation> {
|
|
66
|
+
if (!map.has(slot)) {
|
|
67
|
+
map.set(slot, new Map<string, BlockAttestation>());
|
|
68
|
+
}
|
|
69
|
+
return map.get(slot)!;
|
|
70
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BlockAttestation, Signature } from '@aztec/circuit-types';
|
|
2
|
+
import { makeHeader } from '@aztec/circuits.js/testing';
|
|
3
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
4
|
+
|
|
5
|
+
import { type PrivateKeyAccount } from 'viem';
|
|
6
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
7
|
+
|
|
8
|
+
/** Generate Account
|
|
9
|
+
*
|
|
10
|
+
* Create a random signer
|
|
11
|
+
* @returns A random viem signer
|
|
12
|
+
*/
|
|
13
|
+
export const generateAccount = () => {
|
|
14
|
+
const privateKey = generatePrivateKey();
|
|
15
|
+
return privateKeyToAccount(privateKey);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Mock Attestation
|
|
19
|
+
*
|
|
20
|
+
* @param signer A viem signer to create a signature
|
|
21
|
+
* @param slot The slot number the attestation is for
|
|
22
|
+
* @returns A Block Attestation
|
|
23
|
+
*/
|
|
24
|
+
export const mockAttestation = async (signer: PrivateKeyAccount, slot: number = 0): Promise<BlockAttestation> => {
|
|
25
|
+
// Use arbitrary numbers for all other than slot
|
|
26
|
+
const header = makeHeader(1, 2, slot);
|
|
27
|
+
const archive = Fr.random();
|
|
28
|
+
const message = archive.toString();
|
|
29
|
+
const sigString = await signer.signMessage({ message });
|
|
30
|
+
|
|
31
|
+
const signature = Signature.from0xString(sigString);
|
|
32
|
+
return new BlockAttestation(header, archive, signature);
|
|
33
|
+
};
|
package/src/client/index.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type L2BlockSource } from '@aztec/circuit-types';
|
|
2
2
|
import { type AztecKVStore } from '@aztec/kv-store';
|
|
3
3
|
|
|
4
|
+
import { type AttestationPool } from '../attestation_pool/attestation_pool.js';
|
|
4
5
|
import { P2PClient } from '../client/p2p_client.js';
|
|
5
6
|
import { type P2PConfig } from '../config.js';
|
|
6
7
|
import { DiscV5Service } from '../service/discV5_service.js';
|
|
7
8
|
import { DummyP2PService } from '../service/dummy_service.js';
|
|
8
9
|
import { LibP2PService, createLibP2PPeerId } from '../service/index.js';
|
|
9
10
|
import { type TxPool } from '../tx_pool/index.js';
|
|
10
|
-
import { getPublicIp, splitAddressPort } from '../util.js';
|
|
11
|
+
import { getPublicIp, resolveAddressIfNecessary, splitAddressPort } from '../util.js';
|
|
11
12
|
|
|
12
13
|
export * from './p2p_client.js';
|
|
13
14
|
|
|
@@ -15,6 +16,7 @@ export const createP2PClient = async (
|
|
|
15
16
|
config: P2PConfig,
|
|
16
17
|
store: AztecKVStore,
|
|
17
18
|
txPool: TxPool,
|
|
19
|
+
attestationsPool: AttestationPool,
|
|
18
20
|
l2BlockSource: L2BlockSource,
|
|
19
21
|
) => {
|
|
20
22
|
let p2pService;
|
|
@@ -27,6 +29,13 @@ export const createP2PClient = async (
|
|
|
27
29
|
queryForIp,
|
|
28
30
|
} = config;
|
|
29
31
|
|
|
32
|
+
config.tcpAnnounceAddress = configTcpAnnounceAddress
|
|
33
|
+
? await resolveAddressIfNecessary(configTcpAnnounceAddress)
|
|
34
|
+
: undefined;
|
|
35
|
+
config.udpAnnounceAddress = configUdpAnnounceAddress
|
|
36
|
+
? await resolveAddressIfNecessary(configUdpAnnounceAddress)
|
|
37
|
+
: undefined;
|
|
38
|
+
|
|
30
39
|
// create variable for re-use if needed
|
|
31
40
|
let publicIp;
|
|
32
41
|
|
|
@@ -59,9 +68,9 @@ export const createP2PClient = async (
|
|
|
59
68
|
// Create peer discovery service
|
|
60
69
|
const peerId = await createLibP2PPeerId(config.peerIdPrivateKey);
|
|
61
70
|
const discoveryService = new DiscV5Service(peerId, config);
|
|
62
|
-
p2pService = await LibP2PService.new(config, discoveryService, peerId, txPool, store);
|
|
71
|
+
p2pService = await LibP2PService.new(config, discoveryService, peerId, txPool, attestationsPool, store);
|
|
63
72
|
} else {
|
|
64
73
|
p2pService = new DummyP2PService();
|
|
65
74
|
}
|
|
66
|
-
return new P2PClient(store, l2BlockSource, txPool, p2pService, config.keepProvenTxsInPoolFor);
|
|
75
|
+
return new P2PClient(store, l2BlockSource, txPool, attestationsPool, p2pService, config.keepProvenTxsInPoolFor);
|
|
67
76
|
};
|
package/src/client/p2p_client.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BlockAttestation,
|
|
3
|
+
type BlockProposal,
|
|
4
|
+
type L2Block,
|
|
5
|
+
L2BlockDownloader,
|
|
6
|
+
type L2BlockSource,
|
|
7
|
+
type Tx,
|
|
8
|
+
type TxHash,
|
|
9
|
+
} from '@aztec/circuit-types';
|
|
2
10
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js/constants';
|
|
3
11
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
12
|
import { type AztecKVStore, type AztecSingleton } from '@aztec/kv-store';
|
|
5
13
|
|
|
14
|
+
import { type ENR } from '@chainsafe/enr';
|
|
15
|
+
|
|
16
|
+
import { type AttestationPool } from '../attestation_pool/attestation_pool.js';
|
|
6
17
|
import { getP2PConfigEnvVars } from '../config.js';
|
|
7
18
|
import type { P2PService } from '../service/service.js';
|
|
8
19
|
import { type TxPool } from '../tx_pool/index.js';
|
|
@@ -35,6 +46,31 @@ export interface P2PSyncState {
|
|
|
35
46
|
* Interface of a P2P client.
|
|
36
47
|
**/
|
|
37
48
|
export interface P2P {
|
|
49
|
+
/**
|
|
50
|
+
* Broadcasts a block proposal to other peers.
|
|
51
|
+
*
|
|
52
|
+
* @param proposal - the block proposal
|
|
53
|
+
*/
|
|
54
|
+
broadcastProposal(proposal: BlockProposal): void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Queries the Attestation pool for attestations for the given slot
|
|
58
|
+
*
|
|
59
|
+
* @param slot - the slot to query
|
|
60
|
+
* @returns BlockAttestations
|
|
61
|
+
*/
|
|
62
|
+
getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Registers a callback from the validator client that determines how to behave when
|
|
66
|
+
* foreign block proposals are received
|
|
67
|
+
*
|
|
68
|
+
* @param handler - A function taking a received block proposal and producing an attestation
|
|
69
|
+
*/
|
|
70
|
+
// REVIEW: https://github.com/AztecProtocol/aztec-packages/issues/7963
|
|
71
|
+
// ^ This pattern is not my favorite (md)
|
|
72
|
+
registerBlockProposalHandler(handler: (block: BlockProposal) => Promise<BlockAttestation>): void;
|
|
73
|
+
|
|
38
74
|
/**
|
|
39
75
|
* Verifies the 'tx' and, if valid, adds it to local tx pool and forwards it to other peers.
|
|
40
76
|
* @param tx - The transaction.
|
|
@@ -90,6 +126,11 @@ export interface P2P {
|
|
|
90
126
|
* Returns the current status of the p2p client.
|
|
91
127
|
*/
|
|
92
128
|
getStatus(): Promise<P2PSyncState>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the ENR for this node, if any.
|
|
132
|
+
*/
|
|
133
|
+
getEnr(): ENR | undefined;
|
|
93
134
|
}
|
|
94
135
|
|
|
95
136
|
/**
|
|
@@ -130,6 +171,7 @@ export class P2PClient implements P2P {
|
|
|
130
171
|
store: AztecKVStore,
|
|
131
172
|
private l2BlockSource: L2BlockSource,
|
|
132
173
|
private txPool: TxPool,
|
|
174
|
+
private attestationPool: AttestationPool,
|
|
133
175
|
private p2pService: P2PService,
|
|
134
176
|
private keepProvenTxsFor: number,
|
|
135
177
|
private log = createDebugLogger('aztec:p2p'),
|
|
@@ -220,6 +262,20 @@ export class P2PClient implements P2P {
|
|
|
220
262
|
this.log.info('P2P client stopped.');
|
|
221
263
|
}
|
|
222
264
|
|
|
265
|
+
public broadcastProposal(proposal: BlockProposal): void {
|
|
266
|
+
return this.p2pService.propagate(proposal);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
|
|
270
|
+
return Promise.resolve(this.attestationPool.getAttestationsForSlot(slot));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// REVIEW: https://github.com/AztecProtocol/aztec-packages/issues/7963
|
|
274
|
+
// ^ This pattern is not my favorite (md)
|
|
275
|
+
public registerBlockProposalHandler(handler: (block: BlockProposal) => Promise<BlockAttestation>): void {
|
|
276
|
+
this.p2pService.registerBlockReceivedCallback(handler);
|
|
277
|
+
}
|
|
278
|
+
|
|
223
279
|
/**
|
|
224
280
|
* Returns all transactions in the transaction pool.
|
|
225
281
|
* @returns An array of Txs.
|
|
@@ -263,7 +319,7 @@ export class P2PClient implements P2P {
|
|
|
263
319
|
throw new Error('P2P client not ready');
|
|
264
320
|
}
|
|
265
321
|
await this.txPool.addTxs([tx]);
|
|
266
|
-
this.p2pService.
|
|
322
|
+
this.p2pService.propagate(tx);
|
|
267
323
|
}
|
|
268
324
|
|
|
269
325
|
/**
|
|
@@ -275,6 +331,10 @@ export class P2PClient implements P2P {
|
|
|
275
331
|
return this.txPool.getTxStatus(txHash);
|
|
276
332
|
}
|
|
277
333
|
|
|
334
|
+
public getEnr(): ENR | undefined {
|
|
335
|
+
return this.p2pService.getEnr();
|
|
336
|
+
}
|
|
337
|
+
|
|
278
338
|
/**
|
|
279
339
|
* Deletes the 'txs' from the pool.
|
|
280
340
|
* NOT used if we use sendTx as reconcileTxPool will handle this.
|
|
@@ -425,7 +485,7 @@ export class P2PClient implements P2P {
|
|
|
425
485
|
const txs = this.txPool.getAllTxs();
|
|
426
486
|
if (txs.length > 0) {
|
|
427
487
|
this.log.debug(`Publishing ${txs.length} previously stored txs`);
|
|
428
|
-
await Promise.all(txs.map(tx => this.p2pService.
|
|
488
|
+
await Promise.all(txs.map(tx => this.p2pService.propagate(tx)));
|
|
429
489
|
}
|
|
430
490
|
}
|
|
431
491
|
}
|
package/src/index.ts
CHANGED
|
@@ -84,12 +84,11 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
84
84
|
const multiAddrTcp = await enr.getFullMultiaddr('tcp');
|
|
85
85
|
const multiAddrUdp = await enr.getFullMultiaddr('udp');
|
|
86
86
|
this.logger.debug(`ENR multiaddr: ${multiAddrTcp?.toString()}, ${multiAddrUdp?.toString()}`);
|
|
87
|
+
this.onDiscovered(enr);
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
public async start(): Promise<void> {
|
|
91
|
-
// Do this conversion once since it involves an async function call
|
|
92
|
-
this.bootstrapNodePeerIds = await Promise.all(this.bootstrapNodes.map(enr => ENR.decodeTxt(enr).peerId()));
|
|
93
92
|
if (this.currentState === PeerDiscoveryState.RUNNING) {
|
|
94
93
|
throw new Error('DiscV5Service already started');
|
|
95
94
|
}
|
|
@@ -102,6 +101,8 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
102
101
|
|
|
103
102
|
// Add bootnode ENR if provided
|
|
104
103
|
if (this.bootstrapNodes?.length) {
|
|
104
|
+
// Do this conversion once since it involves an async function call
|
|
105
|
+
this.bootstrapNodePeerIds = await Promise.all(this.bootstrapNodes.map(enr => ENR.decodeTxt(enr).peerId()));
|
|
105
106
|
this.logger.info(`Adding bootstrap ENRs: ${this.bootstrapNodes.join(', ')}`);
|
|
106
107
|
try {
|
|
107
108
|
this.bootstrapNodes.forEach(enr => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BlockAttestation, BlockProposal, Gossipable, TxHash } from '@aztec/circuit-types';
|
|
2
2
|
|
|
3
3
|
import type { PeerId } from '@libp2p/interface';
|
|
4
4
|
import EventEmitter from 'events';
|
|
@@ -26,16 +26,25 @@ export class DummyP2PService implements P2PService {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Called to have the given
|
|
30
|
-
* @param _ - The
|
|
29
|
+
* Called to have the given message propagated through the P2P network.
|
|
30
|
+
* @param _ - The message to be propagated.
|
|
31
31
|
*/
|
|
32
|
-
public
|
|
32
|
+
public propagate<T extends Gossipable>(_: T) {}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Called upon receipt of settled transactions.
|
|
36
36
|
* @param _ - The hashes of the settled transactions.
|
|
37
37
|
*/
|
|
38
38
|
public settledTxs(_: TxHash[]) {}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register a callback into the validator client for when a block proposal is received
|
|
42
|
+
*/
|
|
43
|
+
public registerBlockReceivedCallback(_: (block: BlockProposal) => Promise<BlockAttestation>) {}
|
|
44
|
+
|
|
45
|
+
public getEnr(): undefined {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
/**
|
|
@@ -78,4 +87,8 @@ export class DummyPeerDiscoveryService extends EventEmitter implements PeerDisco
|
|
|
78
87
|
public getStatus(): PeerDiscoveryState {
|
|
79
88
|
return this.currentState;
|
|
80
89
|
}
|
|
90
|
+
|
|
91
|
+
public getEnr(): undefined {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
81
94
|
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BlockAttestation,
|
|
3
|
+
BlockProposal,
|
|
4
|
+
type Gossipable,
|
|
5
|
+
type RawGossipMessage,
|
|
6
|
+
TopicType,
|
|
7
|
+
TopicTypeMap,
|
|
8
|
+
Tx,
|
|
9
|
+
} from '@aztec/circuit-types';
|
|
2
10
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
3
11
|
import { SerialQueue } from '@aztec/foundation/queue';
|
|
4
12
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
5
13
|
import type { AztecKVStore } from '@aztec/kv-store';
|
|
6
14
|
|
|
15
|
+
import { type ENR } from '@chainsafe/enr';
|
|
7
16
|
import { type GossipsubEvents, gossipsub } from '@chainsafe/libp2p-gossipsub';
|
|
8
17
|
import { noise } from '@chainsafe/libp2p-noise';
|
|
9
18
|
import { yamux } from '@chainsafe/libp2p-yamux';
|
|
@@ -15,6 +24,7 @@ import { createFromJSON, createSecp256k1PeerId } from '@libp2p/peer-id-factory';
|
|
|
15
24
|
import { tcp } from '@libp2p/tcp';
|
|
16
25
|
import { type Libp2p, createLibp2p } from 'libp2p';
|
|
17
26
|
|
|
27
|
+
import { type AttestationPool } from '../attestation_pool/attestation_pool.js';
|
|
18
28
|
import { type P2PConfig } from '../config.js';
|
|
19
29
|
import { type TxPool } from '../tx_pool/index.js';
|
|
20
30
|
import { convertToMultiaddr } from '../util.js';
|
|
@@ -50,14 +60,25 @@ export class LibP2PService implements P2PService {
|
|
|
50
60
|
private jobQueue: SerialQueue = new SerialQueue();
|
|
51
61
|
private peerManager: PeerManager;
|
|
52
62
|
private discoveryRunningPromise?: RunningPromise;
|
|
63
|
+
|
|
64
|
+
private blockReceivedCallback: (block: BlockProposal) => Promise<BlockAttestation | undefined>;
|
|
65
|
+
|
|
53
66
|
constructor(
|
|
54
67
|
private config: P2PConfig,
|
|
55
68
|
private node: PubSubLibp2p,
|
|
56
69
|
private peerDiscoveryService: PeerDiscoveryService,
|
|
57
70
|
private txPool: TxPool,
|
|
71
|
+
private attestationPool: AttestationPool,
|
|
58
72
|
private logger = createDebugLogger('aztec:libp2p_service'),
|
|
59
73
|
) {
|
|
60
74
|
this.peerManager = new PeerManager(node, peerDiscoveryService, config, logger);
|
|
75
|
+
|
|
76
|
+
this.blockReceivedCallback = (block: BlockProposal): Promise<BlockAttestation | undefined> => {
|
|
77
|
+
this.logger.verbose(
|
|
78
|
+
`[WARNING] handler not yet registered: Block received callback not set. Received block ${block.p2pMessageIdentifier()} from peer.`,
|
|
79
|
+
);
|
|
80
|
+
return Promise.resolve(undefined);
|
|
81
|
+
};
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
/**
|
|
@@ -132,6 +153,7 @@ export class LibP2PService implements P2PService {
|
|
|
132
153
|
peerDiscoveryService: PeerDiscoveryService,
|
|
133
154
|
peerId: PeerId,
|
|
134
155
|
txPool: TxPool,
|
|
156
|
+
attestationPool: AttestationPool,
|
|
135
157
|
store: AztecKVStore,
|
|
136
158
|
) {
|
|
137
159
|
const { tcpListenAddress, tcpAnnounceAddress, minPeerCount, maxPeerCount } = config;
|
|
@@ -184,7 +206,16 @@ export class LibP2PService implements P2PService {
|
|
|
184
206
|
},
|
|
185
207
|
});
|
|
186
208
|
|
|
187
|
-
return new LibP2PService(config, node, peerDiscoveryService, txPool);
|
|
209
|
+
return new LibP2PService(config, node, peerDiscoveryService, txPool, attestationPool);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public getEnr(): ENR | undefined {
|
|
213
|
+
return this.peerDiscoveryService.getEnr();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public registerBlockReceivedCallback(callback: (block: BlockProposal) => Promise<BlockAttestation | undefined>) {
|
|
217
|
+
this.blockReceivedCallback = callback;
|
|
218
|
+
this.logger.verbose('Block received callback registered');
|
|
188
219
|
}
|
|
189
220
|
|
|
190
221
|
/**
|
|
@@ -223,16 +254,52 @@ export class LibP2PService implements P2PService {
|
|
|
223
254
|
const tx = Tx.fromBuffer(Buffer.from(message.data));
|
|
224
255
|
await this.processTxFromPeer(tx);
|
|
225
256
|
}
|
|
257
|
+
if (message.topic === BlockAttestation.p2pTopic) {
|
|
258
|
+
const attestation = BlockAttestation.fromBuffer(Buffer.from(message.data));
|
|
259
|
+
await this.processAttestationFromPeer(attestation);
|
|
260
|
+
}
|
|
261
|
+
if (message.topic == BlockProposal.p2pTopic) {
|
|
262
|
+
const block = BlockProposal.fromBuffer(Buffer.from(message.data));
|
|
263
|
+
await this.processBlockFromPeer(block);
|
|
264
|
+
}
|
|
226
265
|
|
|
227
266
|
return;
|
|
228
267
|
}
|
|
229
268
|
|
|
269
|
+
/**Process Attestation From Peer
|
|
270
|
+
* When a proposal is received from a peer, we add it to the attestation pool, so it can be accessed by other services.
|
|
271
|
+
*
|
|
272
|
+
* @param attestation - The attestation to process.
|
|
273
|
+
*/
|
|
274
|
+
private async processAttestationFromPeer(attestation: BlockAttestation): Promise<void> {
|
|
275
|
+
this.logger.verbose(`Received attestation ${attestation.p2pMessageIdentifier()} from external peer.`);
|
|
276
|
+
await this.attestationPool.addAttestations([attestation]);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**Process block from peer
|
|
280
|
+
*
|
|
281
|
+
* Pass the received block to the validator client
|
|
282
|
+
*
|
|
283
|
+
* @param block - The block to process.
|
|
284
|
+
*/
|
|
285
|
+
// REVIEW: callback pattern https://github.com/AztecProtocol/aztec-packages/issues/7963
|
|
286
|
+
private async processBlockFromPeer(block: BlockProposal): Promise<void> {
|
|
287
|
+
this.logger.verbose(`Received block ${block.p2pMessageIdentifier()} from external peer.`);
|
|
288
|
+
const attestation = await this.blockReceivedCallback(block);
|
|
289
|
+
|
|
290
|
+
// TODO: fix up this pattern - the abstraction is not nice
|
|
291
|
+
// The attestation can be undefined if no handler is registered / the validator deems the block invalid
|
|
292
|
+
if (attestation != undefined) {
|
|
293
|
+
this.propagate(attestation);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
230
297
|
/**
|
|
231
|
-
* Propagates
|
|
232
|
-
* @param
|
|
298
|
+
* Propagates provided message to peers.
|
|
299
|
+
* @param message - The message to propagate.
|
|
233
300
|
*/
|
|
234
|
-
public
|
|
235
|
-
void this.jobQueue.put(() => Promise.resolve(this.sendToPeers(
|
|
301
|
+
public propagate<T extends Gossipable>(message: T): void {
|
|
302
|
+
void this.jobQueue.put(() => Promise.resolve(this.sendToPeers(message)));
|
|
236
303
|
}
|
|
237
304
|
|
|
238
305
|
private async processTxFromPeer(tx: Tx): Promise<void> {
|
|
@@ -246,7 +313,7 @@ export class LibP2PService implements P2PService {
|
|
|
246
313
|
const parent = message.constructor as typeof Gossipable;
|
|
247
314
|
|
|
248
315
|
const identifier = message.p2pMessageIdentifier().toString();
|
|
249
|
-
this.logger.verbose(`Sending
|
|
316
|
+
this.logger.verbose(`Sending message ${identifier} to peers`);
|
|
250
317
|
|
|
251
318
|
const recipientsNum = await this.publishToTopic(parent.p2pTopic, message.toBuffer());
|
|
252
319
|
this.logger.verbose(`Sent tx ${identifier} to ${recipientsNum} peers`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export enum ReqRespType {
|
|
2
|
+
Status = 'status',
|
|
3
|
+
Ping = 'ping',
|
|
4
|
+
/** Ask peers for specific transactions */
|
|
5
|
+
TxsByHash = 'txs_by_hash',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const PING_PROTOCOL = '/aztec/ping/0.1.0';
|
|
9
|
+
export const STATUS_PROTOCOL = '/aztec/status/0.1.0';
|
|
10
|
+
|
|
11
|
+
export type SubProtocol = typeof PING_PROTOCOL | typeof STATUS_PROTOCOL;
|
|
12
|
+
|
|
13
|
+
export type SubProtocolHandler = (msg: string) => Uint8Array;
|