@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.
Files changed (63) hide show
  1. package/dest/attestation_pool/attestation_pool.d.ts +39 -0
  2. package/dest/attestation_pool/attestation_pool.d.ts.map +1 -0
  3. package/dest/attestation_pool/attestation_pool.js +2 -0
  4. package/dest/attestation_pool/index.d.ts +3 -0
  5. package/dest/attestation_pool/index.d.ts.map +1 -0
  6. package/dest/attestation_pool/index.js +3 -0
  7. package/dest/attestation_pool/memory_attestation_pool.d.ts +12 -0
  8. package/dest/attestation_pool/memory_attestation_pool.d.ts.map +1 -0
  9. package/dest/attestation_pool/memory_attestation_pool.js +56 -0
  10. package/dest/attestation_pool/mocks.d.ts +16 -0
  11. package/dest/attestation_pool/mocks.d.ts.map +1 -0
  12. package/dest/attestation_pool/mocks.js +29 -0
  13. package/dest/client/index.d.ts +2 -1
  14. package/dest/client/index.d.ts.map +1 -1
  15. package/dest/client/index.js +11 -5
  16. package/dest/client/p2p_client.d.ts +33 -2
  17. package/dest/client/p2p_client.d.ts.map +1 -1
  18. package/dest/client/p2p_client.js +20 -5
  19. package/dest/index.d.ts +1 -0
  20. package/dest/index.d.ts.map +1 -1
  21. package/dest/index.js +2 -1
  22. package/dest/service/discV5_service.d.ts.map +1 -1
  23. package/dest/service/discV5_service.js +4 -3
  24. package/dest/service/dummy_service.d.ts +10 -4
  25. package/dest/service/dummy_service.d.ts.map +1 -1
  26. package/dest/service/dummy_service.js +14 -4
  27. package/dest/service/libp2p_service.d.ts +25 -6
  28. package/dest/service/libp2p_service.d.ts.map +1 -1
  29. package/dest/service/libp2p_service.js +55 -10
  30. package/dest/service/reqresp/handlers.d.ts +3 -0
  31. package/dest/service/reqresp/handlers.d.ts.map +1 -0
  32. package/dest/service/reqresp/handlers.js +7 -0
  33. package/dest/service/reqresp/index.d.ts +6 -0
  34. package/dest/service/reqresp/index.d.ts.map +1 -0
  35. package/dest/service/reqresp/index.js +6 -0
  36. package/dest/service/reqresp/interface.d.ts +11 -0
  37. package/dest/service/reqresp/interface.d.ts.map +1 -0
  38. package/dest/service/reqresp/interface.js +10 -0
  39. package/dest/service/reqresp/reqresp.d.ts +48 -0
  40. package/dest/service/reqresp/reqresp.d.ts.map +1 -0
  41. package/dest/service/reqresp/reqresp.js +112 -0
  42. package/dest/service/service.d.ts +6 -3
  43. package/dest/service/service.d.ts.map +1 -1
  44. package/dest/util.d.ts +1 -1
  45. package/dest/util.d.ts.map +1 -1
  46. package/dest/util.js +31 -12
  47. package/package.json +8 -7
  48. package/src/attestation_pool/attestation_pool.ts +42 -0
  49. package/src/attestation_pool/index.ts +2 -0
  50. package/src/attestation_pool/memory_attestation_pool.ts +70 -0
  51. package/src/attestation_pool/mocks.ts +33 -0
  52. package/src/client/index.ts +12 -3
  53. package/src/client/p2p_client.ts +63 -3
  54. package/src/index.ts +1 -0
  55. package/src/service/discV5_service.ts +3 -2
  56. package/src/service/dummy_service.ts +17 -4
  57. package/src/service/libp2p_service.ts +74 -7
  58. package/src/service/reqresp/handlers.ts +7 -0
  59. package/src/service/reqresp/index.ts +4 -0
  60. package/src/service/reqresp/interface.ts +13 -0
  61. package/src/service/reqresp/reqresp.ts +130 -0
  62. package/src/service/service.ts +10 -3
  63. 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
- let multiaddrPrefix;
14
- if (addr.includes(':')) {
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
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxPQUFlLEVBQUUsUUFBdUI7SUFDekUsTUFBTSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsR0FBRyxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFFdEQsSUFBSSxlQUF1QixDQUFDO0lBRTVCLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3ZCLGVBQWU7UUFDZixlQUFlLEdBQUcsS0FBSyxDQUFDO0lBQzFCLENBQUM7U0FBTSxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztRQUNsQyxlQUFlO1FBQ2YsZUFBZSxHQUFHLEtBQUssQ0FBQztJQUMxQixDQUFDO1NBQU0sQ0FBQztRQUNOLE1BQU0sSUFBSSxLQUFLLENBQUMsMkRBQTJELENBQUMsQ0FBQztJQUMvRSxDQUFDO0lBRUQsT0FBTyxJQUFJLGVBQWUsSUFBSSxJQUFJLElBQUksUUFBUSxJQUFJLElBQUksRUFBRSxDQUFDO0FBQzNELENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCLENBQUMsT0FBZSxFQUFFLGlCQUEwQjtJQUMxRSxJQUFJLElBQVksQ0FBQztJQUNqQixJQUFJLElBQVksQ0FBQztJQUVqQixJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUM1QiwyQ0FBMkM7UUFDM0MsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3BELElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNYLE1BQU0sSUFBSSxLQUFLLENBQUMsK0JBQStCLE9BQU8sb0NBQW9DLENBQUMsQ0FBQztRQUM5RixDQUFDO1FBQ0QsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDekIsQ0FBQztTQUFNLENBQUM7UUFDTixlQUFlO1FBQ2YsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNsQyxJQUFJLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDM0MsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsT0FBTyxrQ0FBa0MsQ0FBQyxDQUFDO1FBQ3hGLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztBQUN0QixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVc7SUFDL0IsTUFBTSxJQUFJLEdBQUcsTUFBTSxLQUFLLENBQUMsK0JBQStCLENBQUMsQ0FBQztJQUMxRCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUMvQixPQUFPLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNyQixDQUFDIn0=
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.49.2",
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.49.2",
60
- "@aztec/circuits.js": "0.49.2",
61
- "@aztec/foundation": "0.49.2",
62
- "@aztec/kv-store": "0.49.2",
63
- "@aztec/telemetry-client": "0.49.2",
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,2 @@
1
+ export * from './attestation_pool.js';
2
+ export * from './memory_attestation_pool.js';
@@ -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
+ };
@@ -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
  };
@@ -1,8 +1,19 @@
1
- import { type L2Block, L2BlockDownloader, type L2BlockSource, type Tx, type TxHash } from '@aztec/circuit-types';
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.propagateTx(tx);
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.propagateTx(tx)));
488
+ await Promise.all(txs.map(tx => this.p2pService.propagate(tx)));
429
489
  }
430
490
  }
431
491
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './client/index.js';
2
2
  export * from './config.js';
3
3
  export * from './tx_pool/index.js';
4
+ export * from './attestation_pool/index.js';
4
5
  export * from './service/index.js';
5
6
  export * from './bootstrap/bootstrap.js';
@@ -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 { Tx, TxHash } from '@aztec/circuit-types';
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 transaction propagated through the P2P network.
30
- * @param _ - The transaction to be propagated.
29
+ * Called to have the given message propagated through the P2P network.
30
+ * @param _ - The message to be propagated.
31
31
  */
32
- public propagateTx(_: Tx) {}
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 { type Gossipable, type RawGossipMessage, TopicType, TopicTypeMap, Tx } from '@aztec/circuit-types';
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 the provided transaction to peers.
232
- * @param tx - The transaction to propagate.
298
+ * Propagates provided message to peers.
299
+ * @param message - The message to propagate.
233
300
  */
234
- public propagateTx(tx: Tx): void {
235
- void this.jobQueue.put(() => Promise.resolve(this.sendToPeers(tx)));
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 tx ${identifier} to peers`);
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,7 @@
1
+ export function pingHandler(_msg: any) {
2
+ return Uint8Array.from(Buffer.from('pong'));
3
+ }
4
+
5
+ export function statusHandler(_msg: any) {
6
+ return Uint8Array.from(Buffer.from('ok'));
7
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Request Response protocol allows nodes to ask their peers for data
3
+ * that they missed via the traditional gossip protocol.
4
+ */
@@ -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;