@aztec/validator-client 0.0.0-test.1 → 0.0.1-commit.001888fc

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 (66) hide show
  1. package/README.md +328 -0
  2. package/dest/block_proposal_handler.d.ts +64 -0
  3. package/dest/block_proposal_handler.d.ts.map +1 -0
  4. package/dest/block_proposal_handler.js +606 -0
  5. package/dest/checkpoint_builder.d.ts +77 -0
  6. package/dest/checkpoint_builder.d.ts.map +1 -0
  7. package/dest/checkpoint_builder.js +250 -0
  8. package/dest/config.d.ts +3 -14
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +66 -8
  11. package/dest/duties/validation_service.d.ts +50 -13
  12. package/dest/duties/validation_service.d.ts.map +1 -1
  13. package/dest/duties/validation_service.js +117 -17
  14. package/dest/factory.d.ts +28 -6
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +14 -6
  17. package/dest/index.d.ts +5 -2
  18. package/dest/index.d.ts.map +1 -1
  19. package/dest/index.js +4 -1
  20. package/dest/key_store/ha_key_store.d.ts +99 -0
  21. package/dest/key_store/ha_key_store.d.ts.map +1 -0
  22. package/dest/key_store/ha_key_store.js +208 -0
  23. package/dest/key_store/index.d.ts +4 -1
  24. package/dest/key_store/index.d.ts.map +1 -1
  25. package/dest/key_store/index.js +3 -0
  26. package/dest/key_store/interface.d.ts +85 -6
  27. package/dest/key_store/interface.d.ts.map +1 -1
  28. package/dest/key_store/interface.js +3 -3
  29. package/dest/key_store/local_key_store.d.ts +46 -11
  30. package/dest/key_store/local_key_store.d.ts.map +1 -1
  31. package/dest/key_store/local_key_store.js +68 -17
  32. package/dest/key_store/node_keystore_adapter.d.ts +151 -0
  33. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -0
  34. package/dest/key_store/node_keystore_adapter.js +330 -0
  35. package/dest/key_store/web3signer_key_store.d.ts +66 -0
  36. package/dest/key_store/web3signer_key_store.d.ts.map +1 -0
  37. package/dest/key_store/web3signer_key_store.js +156 -0
  38. package/dest/metrics.d.ts +21 -5
  39. package/dest/metrics.d.ts.map +1 -1
  40. package/dest/metrics.js +75 -22
  41. package/dest/validator.d.ts +101 -59
  42. package/dest/validator.d.ts.map +1 -1
  43. package/dest/validator.js +723 -168
  44. package/package.json +37 -21
  45. package/src/block_proposal_handler.ts +624 -0
  46. package/src/checkpoint_builder.ts +412 -0
  47. package/src/config.ts +77 -22
  48. package/src/duties/validation_service.ts +194 -19
  49. package/src/factory.ts +66 -11
  50. package/src/index.ts +4 -1
  51. package/src/key_store/ha_key_store.ts +269 -0
  52. package/src/key_store/index.ts +3 -0
  53. package/src/key_store/interface.ts +100 -5
  54. package/src/key_store/local_key_store.ts +77 -18
  55. package/src/key_store/node_keystore_adapter.ts +398 -0
  56. package/src/key_store/web3signer_key_store.ts +205 -0
  57. package/src/metrics.ts +104 -23
  58. package/src/validator.ts +961 -219
  59. package/dest/errors/index.d.ts +0 -2
  60. package/dest/errors/index.d.ts.map +0 -1
  61. package/dest/errors/index.js +0 -1
  62. package/dest/errors/validator.error.d.ts +0 -29
  63. package/dest/errors/validator.error.d.ts.map +0 -1
  64. package/dest/errors/validator.error.js +0 -45
  65. package/src/errors/index.ts +0 -1
  66. package/src/errors/validator.error.ts +0 -55
@@ -0,0 +1,205 @@
1
+ import type { Buffer32 } from '@aztec/foundation/buffer';
2
+ import { normalizeSignature } from '@aztec/foundation/crypto/secp256k1-signer';
3
+ import { EthAddress } from '@aztec/foundation/eth-address';
4
+ import { Signature } from '@aztec/foundation/eth-signature';
5
+ import type { SigningContext } from '@aztec/validator-ha-signer/types';
6
+
7
+ import type { TypedDataDefinition } from 'viem';
8
+
9
+ import type { ValidatorKeyStore } from './interface.js';
10
+
11
+ /**
12
+ * Web3Signer Key Store
13
+ *
14
+ * An implementation of the Key store using Web3Signer remote signing service.
15
+ * This implementation uses the Web3Signer JSON-RPC API for secp256k1 signatures.
16
+ */
17
+ export class Web3SignerKeyStore implements ValidatorKeyStore {
18
+ constructor(
19
+ private addresses: EthAddress[],
20
+ private baseUrl: string,
21
+ ) {}
22
+
23
+ /**
24
+ * Get the address of a signer by index
25
+ *
26
+ * @param index - The index of the signer
27
+ * @returns the address
28
+ */
29
+ public getAddress(index: number): EthAddress {
30
+ if (index >= this.addresses.length) {
31
+ throw new Error(`Index ${index} is out of bounds.`);
32
+ }
33
+ return this.addresses[index];
34
+ }
35
+
36
+ /**
37
+ * Get all addresses
38
+ *
39
+ * @returns all addresses
40
+ */
41
+ public getAddresses(): EthAddress[] {
42
+ return this.addresses;
43
+ }
44
+
45
+ /**
46
+ * Sign EIP-712 typed data with all keystore addresses
47
+ * @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
48
+ * @param _context - Signing context (ignored by Web3SignerKeyStore, used for HA protection)
49
+ * @return signatures
50
+ */
51
+ public signTypedData(typedData: TypedDataDefinition, _context: SigningContext): Promise<Signature[]> {
52
+ return Promise.all(this.addresses.map(address => this.makeJsonRpcSignTypedDataRequest(address, typedData)));
53
+ }
54
+
55
+ /**
56
+ * Sign EIP-712 typed data with a specific address
57
+ * @param address - The address of the signer to use
58
+ * @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
59
+ * @param _context - Signing context (ignored by Web3SignerKeyStore, used for HA protection)
60
+ * @returns signature for the specified address
61
+ * @throws Error if the address is not found in the keystore or signing fails
62
+ */
63
+ public async signTypedDataWithAddress(
64
+ address: EthAddress,
65
+ typedData: TypedDataDefinition,
66
+ _context: SigningContext,
67
+ ): Promise<Signature> {
68
+ if (!this.addresses.some(addr => addr.equals(address))) {
69
+ throw new Error(`Address ${address.toString()} not found in keystore`);
70
+ }
71
+
72
+ return await this.makeJsonRpcSignTypedDataRequest(address, typedData);
73
+ }
74
+
75
+ /**
76
+ * Sign a message with all keystore addresses using EIP-191 prefix
77
+ *
78
+ * @param message - The message to sign
79
+ * @param _context - Signing context (ignored by Web3SignerKeyStore, used for HA protection)
80
+ * @return signatures
81
+ */
82
+ public signMessage(message: Buffer32, _context: SigningContext): Promise<Signature[]> {
83
+ return Promise.all(this.addresses.map(address => this.makeJsonRpcSignRequest(address, message)));
84
+ }
85
+
86
+ /**
87
+ * Sign a message with a specific address using EIP-191 prefix
88
+ * @param address - The address of the signer to use
89
+ * @param message - The message to sign
90
+ * @param _context - Signing context (ignored by Web3SignerKeyStore, used for HA protection)
91
+ * @returns signature for the specified address
92
+ * @throws Error if the address is not found in the keystore or signing fails
93
+ */
94
+ public async signMessageWithAddress(
95
+ address: EthAddress,
96
+ message: Buffer32,
97
+ _context: SigningContext,
98
+ ): Promise<Signature> {
99
+ if (!this.addresses.some(addr => addr.equals(address))) {
100
+ throw new Error(`Address ${address.toString()} not found in keystore`);
101
+ }
102
+ return await this.makeJsonRpcSignRequest(address, message);
103
+ }
104
+
105
+ /**
106
+ * Make a JSON-RPC sign request to Web3Signer using eth_sign
107
+ * @param address - The Ethereum address to sign with
108
+ * @param data - The data to sign
109
+ * @returns The signature
110
+ */
111
+ private async makeJsonRpcSignRequest(address: EthAddress, data: Buffer32): Promise<Signature> {
112
+ const url = this.baseUrl;
113
+
114
+ // Use JSON-RPC eth_sign method which automatically applies Ethereum message prefixing
115
+ const body = {
116
+ jsonrpc: '2.0',
117
+ method: 'eth_sign',
118
+ params: [
119
+ address.toString(), // Ethereum address as identifier
120
+ data.toString(), // Raw data to sign (eth_sign will apply Ethereum message prefix)
121
+ ],
122
+ id: 1,
123
+ };
124
+
125
+ const response = await fetch(url, {
126
+ method: 'POST',
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ },
130
+ body: JSON.stringify(body),
131
+ });
132
+
133
+ if (!response.ok) {
134
+ const errorText = await response.text();
135
+ throw new Error(`Web3Signer request failed: ${response.status} ${response.statusText} - ${errorText}`);
136
+ }
137
+
138
+ const result = await response.json();
139
+
140
+ // Handle JSON-RPC response format
141
+ if (result.error) {
142
+ throw new Error(`Web3Signer JSON-RPC error: ${result.error.code} - ${result.error.message}`);
143
+ }
144
+
145
+ if (!result.result) {
146
+ throw new Error('Invalid response from Web3Signer: no result found');
147
+ }
148
+
149
+ let signatureHex = result.result;
150
+
151
+ // Ensure the signature has the 0x prefix
152
+ if (!signatureHex.startsWith('0x')) {
153
+ signatureHex = '0x' + signatureHex;
154
+ }
155
+
156
+ // Parse the signature from the hex string
157
+ return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
158
+ }
159
+
160
+ private async makeJsonRpcSignTypedDataRequest(
161
+ address: EthAddress,
162
+ typedData: TypedDataDefinition,
163
+ ): Promise<Signature> {
164
+ const url = this.baseUrl;
165
+
166
+ const body = {
167
+ jsonrpc: '2.0',
168
+ method: 'eth_signTypedData',
169
+ params: [address.toString(), JSON.stringify(typedData)],
170
+ id: 1,
171
+ };
172
+
173
+ const response = await fetch(url, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Content-Type': 'application/json',
177
+ },
178
+ body: JSON.stringify(body),
179
+ });
180
+
181
+ if (!response.ok) {
182
+ const errorText = await response.text();
183
+ throw new Error(`Web3Signer request failed: ${response.status} ${response.statusText} - ${errorText}`);
184
+ }
185
+
186
+ const result = await response.json();
187
+
188
+ if (result.error) {
189
+ throw new Error(`Web3Signer JSON-RPC error: ${result.error.code} - ${result.error.message}`);
190
+ }
191
+
192
+ if (!result.result) {
193
+ throw new Error('Invalid response from Web3Signer: no result found');
194
+ }
195
+
196
+ let signatureHex = result.result;
197
+
198
+ // Ensure the signature has the 0x prefix
199
+ if (!signatureHex.startsWith('0x')) {
200
+ signatureHex = '0x' + signatureHex;
201
+ }
202
+
203
+ return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
204
+ }
205
+ }
package/src/metrics.ts CHANGED
@@ -1,50 +1,131 @@
1
+ import type { EpochNumber } from '@aztec/foundation/branded-types';
2
+ import type { EthAddress } from '@aztec/foundation/eth-address';
1
3
  import type { BlockProposal } from '@aztec/stdlib/p2p';
2
4
  import {
3
5
  Attributes,
4
6
  type Gauge,
7
+ type Histogram,
5
8
  Metrics,
6
9
  type TelemetryClient,
7
10
  type UpDownCounter,
8
- ValueType,
11
+ createUpDownCounterWithDefault,
9
12
  } from '@aztec/telemetry-client';
10
13
 
14
+ import type { BlockProposalValidationFailureReason } from './block_proposal_handler.js';
15
+
11
16
  export class ValidatorMetrics {
12
- private reExecutionTime: Gauge;
13
17
  private failedReexecutionCounter: UpDownCounter;
18
+ private successfulAttestationsCount: UpDownCounter;
19
+ private failedAttestationsBadProposalCount: UpDownCounter;
20
+ private failedAttestationsNodeIssueCount: UpDownCounter;
21
+ private currentEpoch: Gauge;
22
+ private attestedEpochCount: UpDownCounter;
23
+
24
+ private reexMana: Histogram;
25
+ private reexTx: Histogram;
26
+ private reexDuration: Gauge;
14
27
 
15
28
  constructor(telemetryClient: TelemetryClient) {
16
29
  const meter = telemetryClient.getMeter('Validator');
17
30
 
18
- this.failedReexecutionCounter = meter.createUpDownCounter(Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT, {
19
- description: 'The number of failed re-executions',
20
- unit: 'count',
21
- valueType: ValueType.INT,
31
+ this.failedReexecutionCounter = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT, {
32
+ [Attributes.STATUS]: ['failed'],
22
33
  });
23
34
 
24
- this.reExecutionTime = meter.createGauge(Metrics.VALIDATOR_RE_EXECUTION_TIME, {
25
- description: 'The time taken to re-execute a transaction',
26
- unit: 'ms',
27
- valueType: ValueType.DOUBLE,
28
- });
29
- }
35
+ this.successfulAttestationsCount = createUpDownCounterWithDefault(
36
+ meter,
37
+ Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT,
38
+ );
39
+
40
+ this.failedAttestationsBadProposalCount = createUpDownCounterWithDefault(
41
+ meter,
42
+ Metrics.VALIDATOR_ATTESTATION_FAILED_BAD_PROPOSAL_COUNT,
43
+ {
44
+ [Attributes.ERROR_TYPE]: [
45
+ 'invalid_proposal',
46
+ 'state_mismatch',
47
+ 'failed_txs',
48
+ 'in_hash_mismatch',
49
+ 'parent_block_wrong_slot',
50
+ ],
51
+ [Attributes.IS_COMMITTEE_MEMBER]: [true, false],
52
+ },
53
+ );
54
+
55
+ this.failedAttestationsNodeIssueCount = createUpDownCounterWithDefault(
56
+ meter,
57
+ Metrics.VALIDATOR_ATTESTATION_FAILED_NODE_ISSUE_COUNT,
58
+ {
59
+ [Attributes.ERROR_TYPE]: [
60
+ 'parent_block_not_found',
61
+ 'global_variables_mismatch',
62
+ 'block_number_already_exists',
63
+ 'txs_not_available',
64
+ 'timeout',
65
+ 'unknown_error',
66
+ ],
67
+ [Attributes.IS_COMMITTEE_MEMBER]: [true, false],
68
+ },
69
+ );
30
70
 
31
- public reExecutionTimer(): () => void {
32
- const start = performance.now();
33
- return () => {
34
- const end = performance.now();
35
- this.recordReExecutionTime(end - start);
36
- };
71
+ this.currentEpoch = meter.createGauge(Metrics.VALIDATOR_CURRENT_EPOCH);
72
+
73
+ this.attestedEpochCount = createUpDownCounterWithDefault(meter, Metrics.VALIDATOR_ATTESTED_EPOCH_COUNT);
74
+
75
+ this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
76
+
77
+ this.reexTx = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_TX_COUNT);
78
+
79
+ this.reexDuration = meter.createGauge(Metrics.VALIDATOR_RE_EXECUTION_TIME);
37
80
  }
38
81
 
39
- public recordReExecutionTime(time: number) {
40
- this.reExecutionTime.record(time);
82
+ public recordReex(time: number, txs: number, mManaTotal: number) {
83
+ this.reexDuration.record(Math.ceil(time));
84
+ this.reexTx.record(txs);
85
+ this.reexMana.record(mManaTotal);
41
86
  }
42
87
 
43
- public async recordFailedReexecution(proposal: BlockProposal) {
88
+ public recordFailedReexecution(proposal: BlockProposal) {
89
+ const proposer = proposal.getSender();
44
90
  this.failedReexecutionCounter.add(1, {
45
91
  [Attributes.STATUS]: 'failed',
46
- [Attributes.BLOCK_NUMBER]: proposal.payload.header.globalVariables.blockNumber.toString(),
47
- [Attributes.BLOCK_PROPOSER]: (await proposal.getSender())?.toString(),
92
+ [Attributes.BLOCK_PROPOSER]: proposer?.toString() ?? 'unknown',
93
+ });
94
+ }
95
+
96
+ public incSuccessfulAttestations(num: number) {
97
+ this.successfulAttestationsCount.add(num);
98
+ }
99
+
100
+ public incFailedAttestationsBadProposal(
101
+ num: number,
102
+ reason: BlockProposalValidationFailureReason,
103
+ inCommittee: boolean,
104
+ ) {
105
+ this.failedAttestationsBadProposalCount.add(num, {
106
+ [Attributes.ERROR_TYPE]: reason,
107
+ [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
48
108
  });
49
109
  }
110
+
111
+ public incFailedAttestationsNodeIssue(
112
+ num: number,
113
+ reason: BlockProposalValidationFailureReason,
114
+ inCommittee: boolean,
115
+ ) {
116
+ this.failedAttestationsNodeIssueCount.add(num, {
117
+ [Attributes.ERROR_TYPE]: reason,
118
+ [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
119
+ });
120
+ }
121
+
122
+ /** Update the gauge tracking the current epoch number (proxy for total epochs elapsed). */
123
+ public setCurrentEpoch(epoch: EpochNumber) {
124
+ this.currentEpoch.record(Number(epoch));
125
+ }
126
+
127
+ /** Increment the count of epochs in which the given attester submitted at least one attestation. */
128
+ public incAttestedEpochCount(attester: EthAddress) {
129
+ this.attestedEpochCount.add(1, { [Attributes.ATTESTER_ADDRESS]: attester.toString() });
130
+ }
50
131
  }