@aztec/validator-client 0.0.0-test.1 → 0.0.1-commit.b655e406
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/block_proposal_handler.d.ts +52 -0
- package/dest/block_proposal_handler.d.ts.map +1 -0
- package/dest/block_proposal_handler.js +286 -0
- package/dest/config.d.ts +2 -13
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +31 -7
- package/dest/duties/validation_service.d.ts +16 -8
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +35 -11
- package/dest/factory.d.ts +21 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +13 -6
- package/dest/index.d.ts +3 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -1
- package/dest/key_store/index.d.ts +2 -0
- package/dest/key_store/index.d.ts.map +1 -1
- package/dest/key_store/index.js +2 -0
- package/dest/key_store/interface.d.ts +54 -5
- package/dest/key_store/interface.d.ts.map +1 -1
- package/dest/key_store/interface.js +3 -3
- package/dest/key_store/local_key_store.d.ts +40 -10
- package/dest/key_store/local_key_store.d.ts.map +1 -1
- package/dest/key_store/local_key_store.js +63 -16
- package/dest/key_store/node_keystore_adapter.d.ts +138 -0
- package/dest/key_store/node_keystore_adapter.d.ts.map +1 -0
- package/dest/key_store/node_keystore_adapter.js +316 -0
- package/dest/key_store/web3signer_key_store.d.ts +67 -0
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -0
- package/dest/key_store/web3signer_key_store.js +152 -0
- package/dest/metrics.d.ts +11 -4
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +52 -15
- package/dest/validator.d.ts +48 -61
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +262 -165
- package/package.json +25 -19
- package/src/block_proposal_handler.ts +343 -0
- package/src/config.ts +42 -22
- package/src/duties/validation_service.ts +69 -14
- package/src/factory.ts +56 -11
- package/src/index.ts +3 -1
- package/src/key_store/index.ts +2 -0
- package/src/key_store/interface.ts +61 -5
- package/src/key_store/local_key_store.ts +67 -17
- package/src/key_store/node_keystore_adapter.ts +375 -0
- package/src/key_store/web3signer_key_store.ts +192 -0
- package/src/metrics.ts +68 -17
- package/src/validator.ts +381 -233
- package/dest/errors/index.d.ts +0 -2
- package/dest/errors/index.d.ts.map +0 -1
- package/dest/errors/index.js +0 -1
- package/dest/errors/validator.error.d.ts +0 -29
- package/dest/errors/validator.error.d.ts.map +0 -1
- package/dest/errors/validator.error.js +0 -45
- package/src/errors/index.ts +0 -1
- package/src/errors/validator.error.ts +0 -55
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
|
+
import { normalizeSignature } from '@aztec/foundation/crypto';
|
|
3
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
+
import { Signature } from '@aztec/foundation/eth-signature';
|
|
5
|
+
|
|
6
|
+
import type { TypedDataDefinition } from 'viem';
|
|
7
|
+
|
|
8
|
+
import type { ValidatorKeyStore } from './interface.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Web3Signer Key Store
|
|
12
|
+
*
|
|
13
|
+
* An implementation of the Key store using Web3Signer remote signing service.
|
|
14
|
+
* This implementation uses the Web3Signer JSON-RPC API for secp256k1 signatures.
|
|
15
|
+
*/
|
|
16
|
+
export class Web3SignerKeyStore implements ValidatorKeyStore {
|
|
17
|
+
constructor(
|
|
18
|
+
private addresses: EthAddress[],
|
|
19
|
+
private baseUrl: string,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the address of a signer by index
|
|
24
|
+
*
|
|
25
|
+
* @param index - The index of the signer
|
|
26
|
+
* @returns the address
|
|
27
|
+
*/
|
|
28
|
+
public getAddress(index: number): EthAddress {
|
|
29
|
+
if (index >= this.addresses.length) {
|
|
30
|
+
throw new Error(`Index ${index} is out of bounds.`);
|
|
31
|
+
}
|
|
32
|
+
return this.addresses[index];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get all addresses
|
|
37
|
+
*
|
|
38
|
+
* @returns all addresses
|
|
39
|
+
*/
|
|
40
|
+
public getAddresses(): EthAddress[] {
|
|
41
|
+
return this.addresses;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Sign EIP-712 typed data with all keystore addresses
|
|
46
|
+
* @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
|
|
47
|
+
* @return signatures
|
|
48
|
+
*/
|
|
49
|
+
public signTypedData(typedData: TypedDataDefinition): Promise<Signature[]> {
|
|
50
|
+
return Promise.all(this.addresses.map(address => this.makeJsonRpcSignTypedDataRequest(address, typedData)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sign EIP-712 typed data with a specific address
|
|
55
|
+
* @param address - The address of the signer to use
|
|
56
|
+
* @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
|
|
57
|
+
* @returns signature for the specified address
|
|
58
|
+
* @throws Error if the address is not found in the keystore or signing fails
|
|
59
|
+
*/
|
|
60
|
+
public async signTypedDataWithAddress(address: EthAddress, typedData: TypedDataDefinition): Promise<Signature> {
|
|
61
|
+
if (!this.addresses.some(addr => addr.equals(address))) {
|
|
62
|
+
throw new Error(`Address ${address.toString()} not found in keystore`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return await this.makeJsonRpcSignTypedDataRequest(address, typedData);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Sign a message with all keystore addresses using EIP-191 prefix
|
|
70
|
+
*
|
|
71
|
+
* @param message - The message to sign
|
|
72
|
+
* @return signatures
|
|
73
|
+
*/
|
|
74
|
+
public signMessage(message: Buffer32): Promise<Signature[]> {
|
|
75
|
+
return Promise.all(this.addresses.map(address => this.makeJsonRpcSignRequest(address, message)));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sign a message with a specific address using EIP-191 prefix
|
|
80
|
+
* @param address - The address of the signer to use
|
|
81
|
+
* @param message - The message to sign
|
|
82
|
+
* @returns signature for the specified address
|
|
83
|
+
* @throws Error if the address is not found in the keystore or signing fails
|
|
84
|
+
*/
|
|
85
|
+
public async signMessageWithAddress(address: EthAddress, message: Buffer32): Promise<Signature> {
|
|
86
|
+
if (!this.addresses.some(addr => addr.equals(address))) {
|
|
87
|
+
throw new Error(`Address ${address.toString()} not found in keystore`);
|
|
88
|
+
}
|
|
89
|
+
return await this.makeJsonRpcSignRequest(address, message);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Make a JSON-RPC sign request to Web3Signer using eth_sign
|
|
94
|
+
* @param address - The Ethereum address to sign with
|
|
95
|
+
* @param data - The data to sign
|
|
96
|
+
* @returns The signature
|
|
97
|
+
*/
|
|
98
|
+
private async makeJsonRpcSignRequest(address: EthAddress, data: Buffer32): Promise<Signature> {
|
|
99
|
+
const url = this.baseUrl;
|
|
100
|
+
|
|
101
|
+
// Use JSON-RPC eth_sign method which automatically applies Ethereum message prefixing
|
|
102
|
+
const body = {
|
|
103
|
+
jsonrpc: '2.0',
|
|
104
|
+
method: 'eth_sign',
|
|
105
|
+
params: [
|
|
106
|
+
address.toString(), // Ethereum address as identifier
|
|
107
|
+
data.toString(), // Raw data to sign (eth_sign will apply Ethereum message prefix)
|
|
108
|
+
],
|
|
109
|
+
id: 1,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const response = await fetch(url, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify(body),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const errorText = await response.text();
|
|
122
|
+
throw new Error(`Web3Signer request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const result = await response.json();
|
|
126
|
+
|
|
127
|
+
// Handle JSON-RPC response format
|
|
128
|
+
if (result.error) {
|
|
129
|
+
throw new Error(`Web3Signer JSON-RPC error: ${result.error.code} - ${result.error.message}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!result.result) {
|
|
133
|
+
throw new Error('Invalid response from Web3Signer: no result found');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let signatureHex = result.result;
|
|
137
|
+
|
|
138
|
+
// Ensure the signature has the 0x prefix
|
|
139
|
+
if (!signatureHex.startsWith('0x')) {
|
|
140
|
+
signatureHex = '0x' + signatureHex;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Parse the signature from the hex string
|
|
144
|
+
return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private async makeJsonRpcSignTypedDataRequest(
|
|
148
|
+
address: EthAddress,
|
|
149
|
+
typedData: TypedDataDefinition,
|
|
150
|
+
): Promise<Signature> {
|
|
151
|
+
const url = this.baseUrl;
|
|
152
|
+
|
|
153
|
+
const body = {
|
|
154
|
+
jsonrpc: '2.0',
|
|
155
|
+
method: 'eth_signTypedData',
|
|
156
|
+
params: [address.toString(), JSON.stringify(typedData)],
|
|
157
|
+
id: 1,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const response = await fetch(url, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: {
|
|
163
|
+
'Content-Type': 'application/json',
|
|
164
|
+
},
|
|
165
|
+
body: JSON.stringify(body),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const errorText = await response.text();
|
|
170
|
+
throw new Error(`Web3Signer request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const result = await response.json();
|
|
174
|
+
|
|
175
|
+
if (result.error) {
|
|
176
|
+
throw new Error(`Web3Signer JSON-RPC error: ${result.error.code} - ${result.error.message}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!result.result) {
|
|
180
|
+
throw new Error('Invalid response from Web3Signer: no result found');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let signatureHex = result.result;
|
|
184
|
+
|
|
185
|
+
// Ensure the signature has the 0x prefix
|
|
186
|
+
if (!signatureHex.startsWith('0x')) {
|
|
187
|
+
signatureHex = '0x' + signatureHex;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return normalizeSignature(Signature.fromString(signatureHex as `0x${string}`));
|
|
191
|
+
}
|
|
192
|
+
}
|
package/src/metrics.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BlockProposal } from '@aztec/stdlib/p2p';
|
|
2
2
|
import {
|
|
3
3
|
Attributes,
|
|
4
|
-
type
|
|
4
|
+
type Histogram,
|
|
5
5
|
Metrics,
|
|
6
6
|
type TelemetryClient,
|
|
7
7
|
type UpDownCounter,
|
|
@@ -9,8 +9,14 @@ import {
|
|
|
9
9
|
} from '@aztec/telemetry-client';
|
|
10
10
|
|
|
11
11
|
export class ValidatorMetrics {
|
|
12
|
-
private reExecutionTime: Gauge;
|
|
13
12
|
private failedReexecutionCounter: UpDownCounter;
|
|
13
|
+
private successfulAttestationsCount: UpDownCounter;
|
|
14
|
+
private failedAttestationsBadProposalCount: UpDownCounter;
|
|
15
|
+
private failedAttestationsNodeIssueCount: UpDownCounter;
|
|
16
|
+
|
|
17
|
+
private reexMana: Histogram;
|
|
18
|
+
private reexTx: Histogram;
|
|
19
|
+
private reexDuration: Histogram;
|
|
14
20
|
|
|
15
21
|
constructor(telemetryClient: TelemetryClient) {
|
|
16
22
|
const meter = telemetryClient.getMeter('Validator');
|
|
@@ -21,30 +27,75 @@ export class ValidatorMetrics {
|
|
|
21
27
|
valueType: ValueType.INT,
|
|
22
28
|
});
|
|
23
29
|
|
|
24
|
-
this.
|
|
25
|
-
description: 'The
|
|
26
|
-
|
|
30
|
+
this.successfulAttestationsCount = meter.createUpDownCounter(Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT, {
|
|
31
|
+
description: 'The number of successful attestations',
|
|
32
|
+
valueType: ValueType.INT,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
this.failedAttestationsBadProposalCount = meter.createUpDownCounter(
|
|
36
|
+
Metrics.VALIDATOR_ATTESTATION_FAILED_BAD_PROPOSAL_COUNT,
|
|
37
|
+
{
|
|
38
|
+
description: 'The number of failed attestations due to invalid block proposals',
|
|
39
|
+
valueType: ValueType.INT,
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
this.failedAttestationsNodeIssueCount = meter.createUpDownCounter(
|
|
44
|
+
Metrics.VALIDATOR_ATTESTATION_FAILED_NODE_ISSUE_COUNT,
|
|
45
|
+
{
|
|
46
|
+
description: 'The number of failed attestations due to node issues (timeout, missing data, etc.)',
|
|
47
|
+
valueType: ValueType.INT,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA, {
|
|
52
|
+
description: 'The mana consumed by blocks',
|
|
27
53
|
valueType: ValueType.DOUBLE,
|
|
54
|
+
unit: 'Mmana',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.reexTx = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_TX_COUNT, {
|
|
58
|
+
description: 'The number of txs in a block proposal',
|
|
59
|
+
valueType: ValueType.INT,
|
|
60
|
+
unit: 'tx',
|
|
28
61
|
});
|
|
29
|
-
}
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
63
|
+
this.reexDuration = meter.createGauge(Metrics.VALIDATOR_RE_EXECUTION_TIME, {
|
|
64
|
+
description: 'The time taken to re-execute a transaction',
|
|
65
|
+
unit: 'ms',
|
|
66
|
+
valueType: ValueType.INT,
|
|
67
|
+
});
|
|
37
68
|
}
|
|
38
69
|
|
|
39
|
-
public
|
|
40
|
-
this.
|
|
70
|
+
public recordReex(time: number, txs: number, mManaTotal: number) {
|
|
71
|
+
this.reexDuration.record(Math.ceil(time));
|
|
72
|
+
this.reexTx.record(txs);
|
|
73
|
+
this.reexMana.record(mManaTotal);
|
|
41
74
|
}
|
|
42
75
|
|
|
43
|
-
public
|
|
76
|
+
public recordFailedReexecution(proposal: BlockProposal) {
|
|
77
|
+
const proposer = proposal.getSender();
|
|
44
78
|
this.failedReexecutionCounter.add(1, {
|
|
45
79
|
[Attributes.STATUS]: 'failed',
|
|
46
|
-
[Attributes.
|
|
47
|
-
|
|
80
|
+
[Attributes.BLOCK_PROPOSER]: proposer?.toString() ?? 'unknown',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public incSuccessfulAttestations(num: number) {
|
|
85
|
+
this.successfulAttestationsCount.add(num);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public incFailedAttestationsBadProposal(num: number, reason: string, inCommittee: boolean) {
|
|
89
|
+
this.failedAttestationsBadProposalCount.add(num, {
|
|
90
|
+
[Attributes.ERROR_TYPE]: reason,
|
|
91
|
+
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public incFailedAttestationsNodeIssue(num: number, reason: string, inCommittee: boolean) {
|
|
96
|
+
this.failedAttestationsNodeIssueCount.add(num, {
|
|
97
|
+
[Attributes.ERROR_TYPE]: reason,
|
|
98
|
+
[Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
|
|
48
99
|
});
|
|
49
100
|
}
|
|
50
101
|
}
|