@aztec/validator-client 0.0.0-test.1 → 0.0.1-commit.1142ef1

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 (74) hide show
  1. package/README.md +256 -0
  2. package/dest/block_proposal_handler.d.ts +63 -0
  3. package/dest/block_proposal_handler.d.ts.map +1 -0
  4. package/dest/block_proposal_handler.js +551 -0
  5. package/dest/checkpoint_builder.d.ts +70 -0
  6. package/dest/checkpoint_builder.d.ts.map +1 -0
  7. package/dest/checkpoint_builder.js +155 -0
  8. package/dest/config.d.ts +3 -14
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +46 -7
  11. package/dest/duties/validation_service.d.ts +36 -12
  12. package/dest/duties/validation_service.d.ts.map +1 -1
  13. package/dest/duties/validation_service.js +69 -16
  14. package/dest/factory.d.ts +27 -5
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +13 -6
  17. package/dest/index.d.ts +6 -2
  18. package/dest/index.d.ts.map +1 -1
  19. package/dest/index.js +5 -1
  20. package/dest/key_store/index.d.ts +3 -1
  21. package/dest/key_store/index.d.ts.map +1 -1
  22. package/dest/key_store/index.js +2 -0
  23. package/dest/key_store/interface.d.ts +55 -6
  24. package/dest/key_store/interface.d.ts.map +1 -1
  25. package/dest/key_store/interface.js +3 -3
  26. package/dest/key_store/local_key_store.d.ts +41 -11
  27. package/dest/key_store/local_key_store.d.ts.map +1 -1
  28. package/dest/key_store/local_key_store.js +64 -17
  29. package/dest/key_store/node_keystore_adapter.d.ts +138 -0
  30. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -0
  31. package/dest/key_store/node_keystore_adapter.js +316 -0
  32. package/dest/key_store/web3signer_key_store.d.ts +61 -0
  33. package/dest/key_store/web3signer_key_store.d.ts.map +1 -0
  34. package/dest/key_store/web3signer_key_store.js +152 -0
  35. package/dest/metrics.d.ts +12 -5
  36. package/dest/metrics.d.ts.map +1 -1
  37. package/dest/metrics.js +36 -24
  38. package/dest/tx_validator/index.d.ts +3 -0
  39. package/dest/tx_validator/index.d.ts.map +1 -0
  40. package/dest/tx_validator/index.js +2 -0
  41. package/dest/tx_validator/nullifier_cache.d.ts +14 -0
  42. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
  43. package/dest/tx_validator/nullifier_cache.js +24 -0
  44. package/dest/tx_validator/tx_validator_factory.d.ts +18 -0
  45. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -0
  46. package/dest/tx_validator/tx_validator_factory.js +53 -0
  47. package/dest/validator.d.ts +73 -58
  48. package/dest/validator.d.ts.map +1 -1
  49. package/dest/validator.js +557 -166
  50. package/package.json +33 -21
  51. package/src/block_proposal_handler.ts +556 -0
  52. package/src/checkpoint_builder.ts +267 -0
  53. package/src/config.ts +58 -22
  54. package/src/duties/validation_service.ts +124 -18
  55. package/src/factory.ts +64 -11
  56. package/src/index.ts +5 -1
  57. package/src/key_store/index.ts +2 -0
  58. package/src/key_store/interface.ts +61 -5
  59. package/src/key_store/local_key_store.ts +68 -18
  60. package/src/key_store/node_keystore_adapter.ts +375 -0
  61. package/src/key_store/web3signer_key_store.ts +192 -0
  62. package/src/metrics.ts +48 -24
  63. package/src/tx_validator/index.ts +2 -0
  64. package/src/tx_validator/nullifier_cache.ts +30 -0
  65. package/src/tx_validator/tx_validator_factory.ts +133 -0
  66. package/src/validator.ts +749 -218
  67. package/dest/errors/index.d.ts +0 -2
  68. package/dest/errors/index.d.ts.map +0 -1
  69. package/dest/errors/index.js +0 -1
  70. package/dest/errors/validator.error.d.ts +0 -29
  71. package/dest/errors/validator.error.d.ts.map +0 -1
  72. package/dest/errors/validator.error.js +0 -45
  73. package/src/errors/index.ts +0 -1
  74. 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/secp256k1-signer';
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
@@ -2,49 +2,73 @@ import type { BlockProposal } from '@aztec/stdlib/p2p';
2
2
  import {
3
3
  Attributes,
4
4
  type Gauge,
5
+ type Histogram,
5
6
  Metrics,
6
7
  type TelemetryClient,
7
8
  type UpDownCounter,
8
- ValueType,
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: Gauge;
14
20
 
15
21
  constructor(telemetryClient: TelemetryClient) {
16
22
  const meter = telemetryClient.getMeter('Validator');
17
23
 
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,
22
- });
24
+ this.failedReexecutionCounter = meter.createUpDownCounter(Metrics.VALIDATOR_FAILED_REEXECUTION_COUNT);
23
25
 
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
- }
26
+ this.successfulAttestationsCount = meter.createUpDownCounter(Metrics.VALIDATOR_ATTESTATION_SUCCESS_COUNT);
27
+
28
+ this.failedAttestationsBadProposalCount = meter.createUpDownCounter(
29
+ Metrics.VALIDATOR_ATTESTATION_FAILED_BAD_PROPOSAL_COUNT,
30
+ );
31
+
32
+ this.failedAttestationsNodeIssueCount = meter.createUpDownCounter(
33
+ Metrics.VALIDATOR_ATTESTATION_FAILED_NODE_ISSUE_COUNT,
34
+ );
35
+
36
+ this.reexMana = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_MANA);
37
+
38
+ this.reexTx = meter.createHistogram(Metrics.VALIDATOR_RE_EXECUTION_TX_COUNT);
30
39
 
31
- public reExecutionTimer(): () => void {
32
- const start = performance.now();
33
- return () => {
34
- const end = performance.now();
35
- this.recordReExecutionTime(end - start);
36
- };
40
+ this.reexDuration = meter.createGauge(Metrics.VALIDATOR_RE_EXECUTION_TIME);
37
41
  }
38
42
 
39
- public recordReExecutionTime(time: number) {
40
- this.reExecutionTime.record(time);
43
+ public recordReex(time: number, txs: number, mManaTotal: number) {
44
+ this.reexDuration.record(Math.ceil(time));
45
+ this.reexTx.record(txs);
46
+ this.reexMana.record(mManaTotal);
41
47
  }
42
48
 
43
- public async recordFailedReexecution(proposal: BlockProposal) {
49
+ public recordFailedReexecution(proposal: BlockProposal) {
50
+ const proposer = proposal.getSender();
44
51
  this.failedReexecutionCounter.add(1, {
45
52
  [Attributes.STATUS]: 'failed',
46
- [Attributes.BLOCK_NUMBER]: proposal.payload.header.globalVariables.blockNumber.toString(),
47
- [Attributes.BLOCK_PROPOSER]: (await proposal.getSender())?.toString(),
53
+ [Attributes.BLOCK_PROPOSER]: proposer?.toString() ?? 'unknown',
54
+ });
55
+ }
56
+
57
+ public incSuccessfulAttestations(num: number) {
58
+ this.successfulAttestationsCount.add(num);
59
+ }
60
+
61
+ public incFailedAttestationsBadProposal(num: number, reason: string, inCommittee: boolean) {
62
+ this.failedAttestationsBadProposalCount.add(num, {
63
+ [Attributes.ERROR_TYPE]: reason,
64
+ [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
65
+ });
66
+ }
67
+
68
+ public incFailedAttestationsNodeIssue(num: number, reason: string, inCommittee: boolean) {
69
+ this.failedAttestationsNodeIssueCount.add(num, {
70
+ [Attributes.ERROR_TYPE]: reason,
71
+ [Attributes.IS_COMMITTEE_MEMBER]: inCommittee,
48
72
  });
49
73
  }
50
74
  }
@@ -0,0 +1,2 @@
1
+ export * from './nullifier_cache.js';
2
+ export * from './tx_validator_factory.js';
@@ -0,0 +1,30 @@
1
+ import type { NullifierSource } from '@aztec/p2p';
2
+ import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
3
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
4
+
5
+ /**
6
+ * Implements a nullifier source by checking a DB and an in-memory collection.
7
+ * Intended for validating transactions as they are added to a block.
8
+ */
9
+ export class NullifierCache implements NullifierSource {
10
+ nullifiers: Set<string>;
11
+
12
+ constructor(private db: MerkleTreeReadOperations) {
13
+ this.nullifiers = new Set();
14
+ }
15
+
16
+ public async nullifiersExist(nullifiers: Buffer[]): Promise<boolean[]> {
17
+ const cacheResults = nullifiers.map(n => this.nullifiers.has(n.toString()));
18
+ const toCheckDb = nullifiers.filter((_n, index) => !cacheResults[index]);
19
+ const dbHits = await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, toCheckDb);
20
+
21
+ let dbIndex = 0;
22
+ return nullifiers.map((_n, index) => cacheResults[index] || dbHits[dbIndex++] !== undefined);
23
+ }
24
+
25
+ public addNullifiers(nullifiers: Buffer[]) {
26
+ for (const nullifier of nullifiers) {
27
+ this.nullifiers.add(nullifier.toString());
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,133 @@
1
+ import { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import { Fr } from '@aztec/foundation/curves/bn254';
3
+ import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
4
+ import {
5
+ AggregateTxValidator,
6
+ ArchiveCache,
7
+ BlockHeaderTxValidator,
8
+ DataTxValidator,
9
+ DoubleSpendTxValidator,
10
+ GasTxValidator,
11
+ MetadataTxValidator,
12
+ PhasesTxValidator,
13
+ TimestampTxValidator,
14
+ TxPermittedValidator,
15
+ TxProofValidator,
16
+ } from '@aztec/p2p';
17
+ import { ProtocolContractAddress, protocolContractsHash } from '@aztec/protocol-contracts';
18
+ import type { ContractDataSource } from '@aztec/stdlib/contract';
19
+ import type { GasFees } from '@aztec/stdlib/gas';
20
+ import type {
21
+ AllowedElement,
22
+ ClientProtocolCircuitVerifier,
23
+ MerkleTreeReadOperations,
24
+ PublicProcessorValidator,
25
+ } from '@aztec/stdlib/interfaces/server';
26
+ import { DatabasePublicStateSource, type PublicStateSource } from '@aztec/stdlib/trees';
27
+ import { GlobalVariables, type Tx, type TxValidator } from '@aztec/stdlib/tx';
28
+ import type { UInt64 } from '@aztec/stdlib/types';
29
+
30
+ import { NullifierCache } from './nullifier_cache.js';
31
+
32
+ export function createValidatorForAcceptingTxs(
33
+ db: MerkleTreeReadOperations,
34
+ contractDataSource: ContractDataSource,
35
+ verifier: ClientProtocolCircuitVerifier | undefined,
36
+ {
37
+ l1ChainId,
38
+ rollupVersion,
39
+ setupAllowList,
40
+ gasFees,
41
+ skipFeeEnforcement,
42
+ timestamp,
43
+ blockNumber,
44
+ txsPermitted,
45
+ }: {
46
+ l1ChainId: number;
47
+ rollupVersion: number;
48
+ setupAllowList: AllowedElement[];
49
+ gasFees: GasFees;
50
+ skipFeeEnforcement?: boolean;
51
+ timestamp: UInt64;
52
+ blockNumber: BlockNumber;
53
+ txsPermitted: boolean;
54
+ },
55
+ ): TxValidator<Tx> {
56
+ const validators: TxValidator<Tx>[] = [
57
+ new TxPermittedValidator(txsPermitted),
58
+ new DataTxValidator(),
59
+ new MetadataTxValidator({
60
+ l1ChainId: new Fr(l1ChainId),
61
+ rollupVersion: new Fr(rollupVersion),
62
+ protocolContractsHash,
63
+ vkTreeRoot: getVKTreeRoot(),
64
+ }),
65
+ new TimestampTxValidator({
66
+ timestamp,
67
+ blockNumber,
68
+ }),
69
+ new DoubleSpendTxValidator(new NullifierCache(db)),
70
+ new PhasesTxValidator(contractDataSource, setupAllowList, timestamp),
71
+ new BlockHeaderTxValidator(new ArchiveCache(db)),
72
+ ];
73
+
74
+ if (!skipFeeEnforcement) {
75
+ validators.push(new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees));
76
+ }
77
+
78
+ if (verifier) {
79
+ validators.push(new TxProofValidator(verifier));
80
+ }
81
+
82
+ return new AggregateTxValidator(...validators);
83
+ }
84
+
85
+ export function createValidatorForBlockBuilding(
86
+ db: MerkleTreeReadOperations,
87
+ contractDataSource: ContractDataSource,
88
+ globalVariables: GlobalVariables,
89
+ setupAllowList: AllowedElement[],
90
+ ): PublicProcessorValidator {
91
+ const nullifierCache = new NullifierCache(db);
92
+ const archiveCache = new ArchiveCache(db);
93
+ const publicStateSource = new DatabasePublicStateSource(db);
94
+
95
+ return {
96
+ preprocessValidator: preprocessValidator(
97
+ nullifierCache,
98
+ archiveCache,
99
+ publicStateSource,
100
+ contractDataSource,
101
+ globalVariables,
102
+ setupAllowList,
103
+ ),
104
+ nullifierCache,
105
+ };
106
+ }
107
+
108
+ function preprocessValidator(
109
+ nullifierCache: NullifierCache,
110
+ archiveCache: ArchiveCache,
111
+ publicStateSource: PublicStateSource,
112
+ contractDataSource: ContractDataSource,
113
+ globalVariables: GlobalVariables,
114
+ setupAllowList: AllowedElement[],
115
+ ): TxValidator<Tx> {
116
+ // We don't include the TxProofValidator nor the DataTxValidator here because they are already checked by the time we get to block building.
117
+ return new AggregateTxValidator(
118
+ new MetadataTxValidator({
119
+ l1ChainId: globalVariables.chainId,
120
+ rollupVersion: globalVariables.version,
121
+ protocolContractsHash,
122
+ vkTreeRoot: getVKTreeRoot(),
123
+ }),
124
+ new TimestampTxValidator({
125
+ timestamp: globalVariables.timestamp,
126
+ blockNumber: globalVariables.blockNumber,
127
+ }),
128
+ new DoubleSpendTxValidator(nullifierCache),
129
+ new PhasesTxValidator(contractDataSource, setupAllowList, globalVariables.timestamp),
130
+ new GasTxValidator(publicStateSource, ProtocolContractAddress.FeeJuice, globalVariables.gasFees),
131
+ new BlockHeaderTxValidator(archiveCache),
132
+ );
133
+ }