@aztec/validator-client 2.0.3 → 2.1.0-rc.10
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 +47 -0
- package/dest/block_proposal_handler.d.ts.map +1 -0
- package/dest/block_proposal_handler.js +257 -0
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -0
- package/dest/duties/validation_service.d.ts +3 -0
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +8 -0
- package/dest/factory.d.ts +13 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +8 -0
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/key_store/interface.d.ts +1 -1
- package/dest/key_store/interface.d.ts.map +1 -1
- package/dest/key_store/local_key_store.d.ts +1 -1
- package/dest/key_store/local_key_store.d.ts.map +1 -1
- package/dest/key_store/local_key_store.js +1 -1
- package/dest/key_store/node_keystore_adapter.d.ts +1 -1
- package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
- package/dest/key_store/node_keystore_adapter.js +2 -2
- package/dest/key_store/web3signer_key_store.d.ts +1 -1
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/metrics.d.ts +1 -1
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +3 -2
- package/dest/validator.d.ts +16 -20
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +66 -179
- package/package.json +14 -14
- package/src/block_proposal_handler.ts +314 -0
- package/src/config.ts +6 -0
- package/src/duties/validation_service.ts +16 -1
- package/src/factory.ts +32 -4
- package/src/index.ts +1 -0
- package/src/key_store/interface.ts +1 -1
- package/src/key_store/local_key_store.ts +1 -1
- package/src/key_store/node_keystore_adapter.ts +3 -3
- package/src/key_store/web3signer_key_store.ts +1 -1
- package/src/metrics.ts +2 -1
- package/src/validator.ts +109 -228
package/dest/validator.d.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
2
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
+
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
3
4
|
import { Fr } from '@aztec/foundation/fields';
|
|
4
5
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
5
6
|
import type { KeystoreManager } from '@aztec/node-keystore';
|
|
6
|
-
import type { P2P, PeerId } from '@aztec/p2p';
|
|
7
|
-
import { TxProvider } from '@aztec/p2p';
|
|
7
|
+
import type { P2P, PeerId, TxProvider } from '@aztec/p2p';
|
|
8
8
|
import { type SlasherConfig, type Watcher, type WatcherEmitter } from '@aztec/slasher';
|
|
9
9
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
10
|
-
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
10
|
+
import type { CommitteeAttestationsAndSigners, L2BlockSource } from '@aztec/stdlib/block';
|
|
11
11
|
import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server';
|
|
12
12
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
13
13
|
import type { BlockAttestation, BlockProposal, BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
14
|
-
import {
|
|
14
|
+
import type { ProposedBlockHeader, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
15
15
|
import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
|
|
16
|
-
import type { TypedDataDefinition } from 'viem';
|
|
16
|
+
import type { TypedDataDefinition } from '@spalladino/viem';
|
|
17
|
+
import { BlockProposalHandler } from './block_proposal_handler.js';
|
|
17
18
|
import type { ValidatorClientConfig } from './config.js';
|
|
18
19
|
import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
|
|
19
20
|
declare const ValidatorClient_base: new () => WatcherEmitter;
|
|
@@ -21,50 +22,45 @@ declare const ValidatorClient_base: new () => WatcherEmitter;
|
|
|
21
22
|
* Validator Client
|
|
22
23
|
*/
|
|
23
24
|
export declare class ValidatorClient extends ValidatorClient_base implements Validator, Watcher {
|
|
24
|
-
private blockBuilder;
|
|
25
25
|
private keyStore;
|
|
26
26
|
private epochCache;
|
|
27
27
|
private p2pClient;
|
|
28
|
-
private
|
|
29
|
-
private l1ToL2MessageSource;
|
|
30
|
-
private txProvider;
|
|
28
|
+
private blockProposalHandler;
|
|
31
29
|
private config;
|
|
32
30
|
private dateProvider;
|
|
33
31
|
private log;
|
|
34
32
|
readonly tracer: Tracer;
|
|
35
33
|
private validationService;
|
|
36
34
|
private metrics;
|
|
35
|
+
private hasRegisteredHandlers;
|
|
37
36
|
private previousProposal?;
|
|
38
37
|
private lastEpochForCommitteeUpdateLoop;
|
|
39
38
|
private epochCacheUpdateLoop;
|
|
40
|
-
private blockProposalValidator;
|
|
41
39
|
private proposersOfInvalidBlocks;
|
|
42
|
-
protected constructor(
|
|
40
|
+
protected constructor(keyStore: NodeKeystoreAdapter, epochCache: EpochCache, p2pClient: P2P, blockProposalHandler: BlockProposalHandler, config: ValidatorClientFullConfig, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
|
|
43
41
|
static validateKeyStoreConfiguration(keyStoreManager: KeystoreManager): void;
|
|
44
42
|
private handleEpochCommitteeUpdate;
|
|
45
43
|
static new(config: ValidatorClientConfig & Pick<SlasherConfig, 'slashBroadcastedInvalidBlockPenalty'>, blockBuilder: IFullNodeBlockBuilder, epochCache: EpochCache, p2pClient: P2P, blockSource: L2BlockSource, l1ToL2MessageSource: L1ToL2MessageSource, txProvider: TxProvider, keyStoreManager: KeystoreManager, dateProvider?: DateProvider, telemetry?: TelemetryClient): ValidatorClient;
|
|
46
44
|
getValidatorAddresses(): EthAddress[];
|
|
47
|
-
|
|
45
|
+
getBlockProposalHandler(): BlockProposalHandler;
|
|
46
|
+
reExecuteTransactions(proposal: BlockProposal, txs: any[], l1ToL2Messages: Fr[]): Promise<any>;
|
|
47
|
+
signWithAddress(addr: EthAddress, msg: TypedDataDefinition): Promise<Signature>;
|
|
48
48
|
getCoinbaseForAttestor(attestor: EthAddress): EthAddress;
|
|
49
49
|
getFeeRecipientForAttestor(attestor: EthAddress): AztecAddress;
|
|
50
50
|
getConfig(): ValidatorClientFullConfig;
|
|
51
51
|
updateConfig(config: Partial<ValidatorClientFullConfig>): void;
|
|
52
52
|
start(): Promise<void>;
|
|
53
53
|
stop(): Promise<void>;
|
|
54
|
-
|
|
54
|
+
/** Register handlers on the p2p client */
|
|
55
|
+
registerHandlers(): Promise<void>;
|
|
55
56
|
attestToProposal(proposal: BlockProposal, proposalSender: PeerId): Promise<BlockAttestation[] | undefined>;
|
|
56
|
-
private getReexecutionDeadline;
|
|
57
|
-
/**
|
|
58
|
-
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
59
|
-
* @param proposal - The proposal to re-execute
|
|
60
|
-
*/
|
|
61
|
-
reExecuteTransactions(proposal: BlockProposal, txs: Tx[], l1ToL2Messages: Fr[]): Promise<void>;
|
|
62
57
|
private slashInvalidBlock;
|
|
63
58
|
createBlockProposal(blockNumber: number, header: ProposedBlockHeader, archive: Fr, stateReference: StateReference, txs: Tx[], proposerAddress: EthAddress | undefined, options: BlockProposalOptions): Promise<BlockProposal | undefined>;
|
|
64
59
|
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
60
|
+
signAttestationsAndSigners(attestationsAndSigners: CommitteeAttestationsAndSigners, proposer: EthAddress | undefined): Promise<Signature>;
|
|
65
61
|
collectOwnAttestations(proposal: BlockProposal): Promise<BlockAttestation[]>;
|
|
66
62
|
collectAttestations(proposal: BlockProposal, required: number, deadline: Date): Promise<BlockAttestation[]>;
|
|
67
|
-
private
|
|
63
|
+
private createBlockAttestationsFromProposal;
|
|
68
64
|
private handleAuthRequest;
|
|
69
65
|
}
|
|
70
66
|
export {};
|
package/dest/validator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/dest/validator.js
CHANGED
|
@@ -1,53 +1,49 @@
|
|
|
1
|
-
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
2
|
-
import { Fr } from '@aztec/foundation/fields';
|
|
3
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
-
import { retryUntil } from '@aztec/foundation/retry';
|
|
5
2
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
6
3
|
import { sleep } from '@aztec/foundation/sleep';
|
|
7
|
-
import { DateProvider
|
|
8
|
-
import { AuthRequest, AuthResponse, ReqRespSubProtocol } from '@aztec/p2p';
|
|
9
|
-
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
10
|
-
import { computeInHashFromL1ToL2Messages } from '@aztec/prover-client/helpers';
|
|
4
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
5
|
+
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
11
6
|
import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
12
|
-
import {
|
|
13
|
-
import { GlobalVariables } from '@aztec/stdlib/tx';
|
|
14
|
-
import { AttestationTimeoutError, ReExFailedTxsError, ReExStateMismatchError, ReExTimeoutError, TransactionsNotAvailableError } from '@aztec/stdlib/validators';
|
|
7
|
+
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
15
8
|
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
16
9
|
import { EventEmitter } from 'events';
|
|
10
|
+
import { BlockProposalHandler } from './block_proposal_handler.js';
|
|
17
11
|
import { ValidationService } from './duties/validation_service.js';
|
|
18
12
|
import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js';
|
|
19
13
|
import { ValidatorMetrics } from './metrics.js';
|
|
20
14
|
// We maintain a set of proposers who have proposed invalid blocks.
|
|
21
15
|
// Just cap the set to avoid unbounded growth.
|
|
22
16
|
const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
17
|
+
// What errors from the block proposal handler result in slashing
|
|
18
|
+
const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
19
|
+
'state_mismatch',
|
|
20
|
+
'failed_txs'
|
|
21
|
+
];
|
|
23
22
|
/**
|
|
24
23
|
* Validator Client
|
|
25
24
|
*/ export class ValidatorClient extends EventEmitter {
|
|
26
|
-
blockBuilder;
|
|
27
25
|
keyStore;
|
|
28
26
|
epochCache;
|
|
29
27
|
p2pClient;
|
|
30
|
-
|
|
31
|
-
l1ToL2MessageSource;
|
|
32
|
-
txProvider;
|
|
28
|
+
blockProposalHandler;
|
|
33
29
|
config;
|
|
34
30
|
dateProvider;
|
|
35
31
|
log;
|
|
36
32
|
tracer;
|
|
37
33
|
validationService;
|
|
38
34
|
metrics;
|
|
35
|
+
// Whether it has already registered handlers on the p2p client
|
|
36
|
+
hasRegisteredHandlers;
|
|
39
37
|
// Used to check if we are sending the same proposal twice
|
|
40
38
|
previousProposal;
|
|
41
39
|
lastEpochForCommitteeUpdateLoop;
|
|
42
40
|
epochCacheUpdateLoop;
|
|
43
|
-
blockProposalValidator;
|
|
44
41
|
proposersOfInvalidBlocks;
|
|
45
|
-
constructor(
|
|
46
|
-
super(), this.
|
|
42
|
+
constructor(keyStore, epochCache, p2pClient, blockProposalHandler, config, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
43
|
+
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.config = config, this.dateProvider = dateProvider, this.log = log, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
|
|
47
44
|
this.tracer = telemetry.getTracer('Validator');
|
|
48
45
|
this.metrics = new ValidatorMetrics(telemetry);
|
|
49
46
|
this.validationService = new ValidationService(keyStore);
|
|
50
|
-
this.blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
51
47
|
// Refresh epoch cache every second to trigger alert if participation in committee changes
|
|
52
48
|
this.epochCacheUpdateLoop = new RunningPromise(this.handleEpochCommitteeUpdate.bind(this), log, 1000);
|
|
53
49
|
const myAddresses = this.getValidatorAddresses();
|
|
@@ -94,14 +90,22 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
92
|
static new(config, blockBuilder, epochCache, p2pClient, blockSource, l1ToL2MessageSource, txProvider, keyStoreManager, dateProvider = new DateProvider(), telemetry = getTelemetryClient()) {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
const metrics = new ValidatorMetrics(telemetry);
|
|
94
|
+
const blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
95
|
+
const blockProposalHandler = new BlockProposalHandler(blockBuilder, blockSource, l1ToL2MessageSource, txProvider, blockProposalValidator, config, metrics, dateProvider, telemetry);
|
|
96
|
+
const validator = new ValidatorClient(NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager), epochCache, p2pClient, blockProposalHandler, config, dateProvider, telemetry);
|
|
100
97
|
return validator;
|
|
101
98
|
}
|
|
102
99
|
getValidatorAddresses() {
|
|
103
100
|
return this.keyStore.getAddresses().filter((addr)=>!this.config.disabledValidators.some((disabled)=>disabled.equals(addr)));
|
|
104
101
|
}
|
|
102
|
+
getBlockProposalHandler() {
|
|
103
|
+
return this.blockProposalHandler;
|
|
104
|
+
}
|
|
105
|
+
// Proxy method for backwards compatibility with tests
|
|
106
|
+
reExecuteTransactions(proposal, txs, l1ToL2Messages) {
|
|
107
|
+
return this.blockProposalHandler.reexecuteTransactions(proposal, txs, l1ToL2Messages);
|
|
108
|
+
}
|
|
105
109
|
signWithAddress(addr, msg) {
|
|
106
110
|
return this.keyStore.signTypedDataWithAddress(addr, msg);
|
|
107
111
|
}
|
|
@@ -121,8 +125,11 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
121
125
|
};
|
|
122
126
|
}
|
|
123
127
|
async start() {
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
if (this.epochCacheUpdateLoop.isRunning()) {
|
|
129
|
+
this.log.warn(`Validator client already started`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
await this.registerHandlers();
|
|
126
133
|
const myAddresses = this.getValidatorAddresses();
|
|
127
134
|
const inCommittee = await this.epochCache.filterInCommittee('now', myAddresses);
|
|
128
135
|
if (inCommittee.length > 0) {
|
|
@@ -131,185 +138,62 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
131
138
|
this.log.info(`Started validator with addresses: ${myAddresses.map((a)=>a.toString()).join(', ')}`);
|
|
132
139
|
}
|
|
133
140
|
this.epochCacheUpdateLoop.start();
|
|
134
|
-
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
135
|
-
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
136
141
|
return Promise.resolve();
|
|
137
142
|
}
|
|
138
143
|
async stop() {
|
|
139
144
|
await this.epochCacheUpdateLoop.stop();
|
|
140
145
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
/** Register handlers on the p2p client */ async registerHandlers() {
|
|
147
|
+
if (!this.hasRegisteredHandlers) {
|
|
148
|
+
this.hasRegisteredHandlers = true;
|
|
149
|
+
this.log.debug(`Registering validator handlers for p2p client`);
|
|
150
|
+
const handler = (block, proposalSender)=>this.attestToProposal(block, proposalSender);
|
|
151
|
+
this.p2pClient.registerBlockProposalHandler(handler);
|
|
152
|
+
const myAddresses = this.getValidatorAddresses();
|
|
153
|
+
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
154
|
+
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
155
|
+
}
|
|
144
156
|
}
|
|
145
157
|
async attestToProposal(proposal, proposalSender) {
|
|
146
158
|
const slotNumber = proposal.slotNumber.toBigInt();
|
|
147
|
-
const blockNumber = proposal.blockNumber;
|
|
148
159
|
const proposer = proposal.getSender();
|
|
149
160
|
// Check that I have any address in current committee before attesting
|
|
150
161
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
151
162
|
const partOfCommittee = inCommittee.length > 0;
|
|
163
|
+
const incFailedAttestation = (reason)=>this.metrics.incFailedAttestations(1, reason, partOfCommittee);
|
|
152
164
|
const proposalInfo = {
|
|
153
165
|
...proposal.toBlockInfo(),
|
|
154
166
|
proposer: proposer.toString()
|
|
155
167
|
};
|
|
156
|
-
this.log.info(`Received proposal for slot ${slotNumber}`, {
|
|
168
|
+
this.log.info(`Received proposal for block ${proposal.blockNumber} at slot ${slotNumber}`, {
|
|
157
169
|
...proposalInfo,
|
|
158
|
-
txHashes: proposal.txHashes.map((
|
|
159
|
-
});
|
|
160
|
-
// Collect txs from the proposal. Note that we do this before checking if we have an address in the
|
|
161
|
-
// current committee, since we want to collect txs anyway to facilitate propagation.
|
|
162
|
-
const { txs, missingTxs } = await this.txProvider.getTxsForBlockProposal(proposal, {
|
|
163
|
-
pinnedPeer: proposalSender,
|
|
164
|
-
deadline: this.getReexecutionDeadline(proposal, this.blockBuilder.getConfig())
|
|
170
|
+
txHashes: proposal.txHashes.map((t)=>t.toString())
|
|
165
171
|
});
|
|
166
|
-
//
|
|
167
|
-
if
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (partOfCommittee) {
|
|
177
|
-
this.metrics.incFailedAttestations(1, 'invalid_proposal');
|
|
178
|
-
}
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
// Check that the parent proposal is a block we know, otherwise reexecution would fail.
|
|
182
|
-
// Q: Should we move this to the block proposal validator? If there, then p2p would check it
|
|
183
|
-
// before re-broadcasting it. This means that proposals built on top of an L1-reorg'ed-out block
|
|
184
|
-
// would not be rebroadcasted. But it also means that nodes that have not fully synced would
|
|
185
|
-
// not rebroadcast the proposal.
|
|
186
|
-
if (blockNumber > INITIAL_L2_BLOCK_NUM) {
|
|
187
|
-
const config = this.blockBuilder.getConfig();
|
|
188
|
-
const deadline = this.getReexecutionDeadline(proposal, config);
|
|
189
|
-
const currentTime = this.dateProvider.now();
|
|
190
|
-
const timeoutDurationMs = deadline.getTime() - currentTime;
|
|
191
|
-
const parentBlock = timeoutDurationMs <= 0 ? undefined : await retryUntil(async ()=>{
|
|
192
|
-
const block = await this.blockSource.getBlock(blockNumber - 1);
|
|
193
|
-
if (block) {
|
|
194
|
-
return block;
|
|
195
|
-
}
|
|
196
|
-
await this.blockSource.syncImmediate();
|
|
197
|
-
return await this.blockSource.getBlock(blockNumber - 1);
|
|
198
|
-
}, 'Force Archiver Sync', timeoutDurationMs / 1000, 0.5);
|
|
199
|
-
if (parentBlock === undefined) {
|
|
200
|
-
this.log.warn(`Parent block for ${blockNumber} not found, skipping attestation`, proposalInfo);
|
|
201
|
-
if (partOfCommittee) {
|
|
202
|
-
this.metrics.incFailedAttestations(1, 'parent_block_not_found');
|
|
203
|
-
}
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
if (!proposal.payload.header.lastArchiveRoot.equals(parentBlock.archive.root)) {
|
|
207
|
-
this.log.warn(`Parent block archive root for proposal does not match, skipping attestation`, {
|
|
208
|
-
proposalLastArchiveRoot: proposal.payload.header.lastArchiveRoot.toString(),
|
|
209
|
-
parentBlockArchiveRoot: parentBlock.archive.root.toString(),
|
|
210
|
-
...proposalInfo
|
|
211
|
-
});
|
|
212
|
-
if (partOfCommittee) {
|
|
213
|
-
this.metrics.incFailedAttestations(1, 'parent_block_does_not_match');
|
|
214
|
-
}
|
|
215
|
-
return undefined;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Check that I have the same set of l1ToL2Messages as the proposal
|
|
219
|
-
// Q: Same as above, should this be part of p2p validation?
|
|
220
|
-
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
221
|
-
const computedInHash = await computeInHashFromL1ToL2Messages(l1ToL2Messages);
|
|
222
|
-
const proposalInHash = proposal.payload.header.contentCommitment.inHash;
|
|
223
|
-
if (!computedInHash.equals(proposalInHash)) {
|
|
224
|
-
this.log.warn(`L1 to L2 messages in hash mismatch, skipping attestation`, {
|
|
225
|
-
proposalInHash: proposalInHash.toString(),
|
|
226
|
-
computedInHash: computedInHash.toString(),
|
|
227
|
-
...proposalInfo
|
|
228
|
-
});
|
|
229
|
-
if (partOfCommittee) {
|
|
230
|
-
this.metrics.incFailedAttestations(1, 'in_hash_mismatch');
|
|
231
|
-
}
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
|
-
// Check that all of the transactions in the proposal are available in the tx pool before attesting
|
|
235
|
-
if (missingTxs.length > 0) {
|
|
236
|
-
this.log.warn(`Missing ${missingTxs.length} txs to attest to proposal`, {
|
|
237
|
-
...proposalInfo,
|
|
238
|
-
missingTxs
|
|
239
|
-
});
|
|
240
|
-
if (partOfCommittee) {
|
|
241
|
-
this.metrics.incFailedAttestations(1, 'TransactionsNotAvailableError');
|
|
242
|
-
}
|
|
243
|
-
return undefined;
|
|
244
|
-
}
|
|
245
|
-
// Try re-executing the transactions in the proposal
|
|
246
|
-
try {
|
|
247
|
-
this.log.verbose(`Processing attestation for slot ${slotNumber}`, proposalInfo);
|
|
248
|
-
if (this.config.validatorReexecute) {
|
|
249
|
-
this.log.verbose(`Re-executing transactions in the proposal before attesting`);
|
|
250
|
-
await this.reExecuteTransactions(proposal, txs, l1ToL2Messages);
|
|
251
|
-
}
|
|
252
|
-
} catch (error) {
|
|
253
|
-
this.metrics.incFailedAttestations(1, error instanceof Error ? error.name : 'unknown');
|
|
254
|
-
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
255
|
-
if (error instanceof ReExStateMismatchError && this.config.slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
172
|
+
// Reexecute txs if we are part of the committee so we can attest, or if slashing is enabled so we can slash
|
|
173
|
+
// invalid proposals even when not in the committee, or if we are configured to always reexecute for monitoring purposes.
|
|
174
|
+
const { validatorReexecute, slashBroadcastedInvalidBlockPenalty, alwaysReexecuteBlockProposals } = this.config;
|
|
175
|
+
const shouldReexecute = slashBroadcastedInvalidBlockPenalty > 0n && validatorReexecute || partOfCommittee && validatorReexecute || alwaysReexecuteBlockProposals;
|
|
176
|
+
const validationResult = await this.blockProposalHandler.handleBlockProposal(proposal, proposalSender, !!shouldReexecute);
|
|
177
|
+
if (!validationResult.isValid) {
|
|
178
|
+
this.log.warn(`Proposal validation failed: ${validationResult.reason}`, proposalInfo);
|
|
179
|
+
incFailedAttestation(validationResult.reason || 'unknown');
|
|
180
|
+
// Slash invalid block proposals
|
|
181
|
+
if (validationResult.reason && SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT.includes(validationResult.reason) && slashBroadcastedInvalidBlockPenalty > 0n) {
|
|
256
182
|
this.log.warn(`Slashing proposer for invalid block proposal`, proposalInfo);
|
|
257
183
|
this.slashInvalidBlock(proposal);
|
|
258
184
|
}
|
|
259
185
|
return undefined;
|
|
260
186
|
}
|
|
187
|
+
// Check that I have any address in current committee before attesting
|
|
188
|
+
if (!partOfCommittee) {
|
|
189
|
+
this.log.verbose(`No validator in the current committee, skipping attestation`, proposalInfo);
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
261
192
|
// Provided all of the above checks pass, we can attest to the proposal
|
|
262
|
-
this.log.info(`Attesting to proposal for slot ${slotNumber}`, proposalInfo);
|
|
193
|
+
this.log.info(`Attesting to proposal for block ${proposal.blockNumber} at slot ${slotNumber}`, proposalInfo);
|
|
263
194
|
this.metrics.incAttestations(inCommittee.length);
|
|
264
195
|
// If the above function does not throw an error, then we can attest to the proposal
|
|
265
|
-
return this.
|
|
266
|
-
}
|
|
267
|
-
getReexecutionDeadline(proposal, config) {
|
|
268
|
-
const nextSlotTimestampSeconds = Number(getTimestampForSlot(proposal.slotNumber.toBigInt() + 1n, config));
|
|
269
|
-
const msNeededForPropagationAndPublishing = this.config.validatorReexecuteDeadlineMs;
|
|
270
|
-
return new Date(nextSlotTimestampSeconds * 1000 - msNeededForPropagationAndPublishing);
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Re-execute the transactions in the proposal and check that the state updates match the header state
|
|
274
|
-
* @param proposal - The proposal to re-execute
|
|
275
|
-
*/ async reExecuteTransactions(proposal, txs, l1ToL2Messages) {
|
|
276
|
-
const { header } = proposal.payload;
|
|
277
|
-
const { txHashes } = proposal;
|
|
278
|
-
// If we do not have all of the transactions, then we should fail
|
|
279
|
-
if (txs.length !== txHashes.length) {
|
|
280
|
-
const foundTxHashes = txs.map((tx)=>tx.getTxHash());
|
|
281
|
-
const missingTxHashes = txHashes.filter((txHash)=>!foundTxHashes.includes(txHash));
|
|
282
|
-
throw new TransactionsNotAvailableError(missingTxHashes);
|
|
283
|
-
}
|
|
284
|
-
// Use the sequencer's block building logic to re-execute the transactions
|
|
285
|
-
const timer = new Timer();
|
|
286
|
-
const config = this.blockBuilder.getConfig();
|
|
287
|
-
const globalVariables = GlobalVariables.from({
|
|
288
|
-
...proposal.payload.header,
|
|
289
|
-
blockNumber: proposal.blockNumber,
|
|
290
|
-
timestamp: header.timestamp,
|
|
291
|
-
chainId: new Fr(config.l1ChainId),
|
|
292
|
-
version: new Fr(config.rollupVersion)
|
|
293
|
-
});
|
|
294
|
-
const { block, failedTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, globalVariables, {
|
|
295
|
-
deadline: this.getReexecutionDeadline(proposal, config)
|
|
296
|
-
});
|
|
297
|
-
this.log.verbose(`Transaction re-execution complete`);
|
|
298
|
-
const numFailedTxs = failedTxs.length;
|
|
299
|
-
if (numFailedTxs > 0) {
|
|
300
|
-
this.metrics.recordFailedReexecution(proposal);
|
|
301
|
-
throw new ReExFailedTxsError(numFailedTxs);
|
|
302
|
-
}
|
|
303
|
-
if (block.body.txEffects.length !== txHashes.length) {
|
|
304
|
-
this.metrics.recordFailedReexecution(proposal);
|
|
305
|
-
throw new ReExTimeoutError();
|
|
306
|
-
}
|
|
307
|
-
// This function will throw an error if state updates do not match
|
|
308
|
-
if (!block.archive.root.equals(proposal.archive)) {
|
|
309
|
-
this.metrics.recordFailedReexecution(proposal);
|
|
310
|
-
throw new ReExStateMismatchError(proposal.archive, block.archive.root, proposal.payload.stateReference, block.header.state);
|
|
311
|
-
}
|
|
312
|
-
this.metrics.recordReex(timer.ms(), txs.length, block.header.totalManaUsed.toNumber() / 1e6);
|
|
196
|
+
return this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
313
197
|
}
|
|
314
198
|
slashInvalidBlock(proposal) {
|
|
315
199
|
const proposer = proposal.getSender();
|
|
@@ -340,13 +224,16 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
340
224
|
async broadcastBlockProposal(proposal) {
|
|
341
225
|
await this.p2pClient.broadcastProposal(proposal);
|
|
342
226
|
}
|
|
227
|
+
async signAttestationsAndSigners(attestationsAndSigners, proposer) {
|
|
228
|
+
return await this.validationService.signAttestationsAndSigners(attestationsAndSigners, proposer);
|
|
229
|
+
}
|
|
343
230
|
async collectOwnAttestations(proposal) {
|
|
344
231
|
const slot = proposal.payload.header.slotNumber.toBigInt();
|
|
345
232
|
const inCommittee = await this.epochCache.filterInCommittee(slot, this.getValidatorAddresses());
|
|
346
233
|
this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, {
|
|
347
234
|
inCommittee
|
|
348
235
|
});
|
|
349
|
-
return this.
|
|
236
|
+
return this.createBlockAttestationsFromProposal(proposal, inCommittee);
|
|
350
237
|
}
|
|
351
238
|
async collectAttestations(proposal, required, deadline) {
|
|
352
239
|
// Wait and poll the p2pClient's attestation pool for this block until we have enough attestations
|
|
@@ -382,7 +269,7 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
382
269
|
await sleep(this.config.attestationPollingIntervalMs);
|
|
383
270
|
}
|
|
384
271
|
}
|
|
385
|
-
async
|
|
272
|
+
async createBlockAttestationsFromProposal(proposal, attestors = []) {
|
|
386
273
|
const attestations = await this.validationService.attestToProposal(proposal, attestors);
|
|
387
274
|
await this.p2pClient.addAttestations(attestations);
|
|
388
275
|
return attestations;
|
|
@@ -405,4 +292,4 @@ const MAX_PROPOSERS_OF_INVALID_BLOCKS = 1000;
|
|
|
405
292
|
const authResponse = new AuthResponse(statusMessage, signature);
|
|
406
293
|
return authResponse.toBuffer();
|
|
407
294
|
}
|
|
408
|
-
}
|
|
295
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.1.0-rc.10",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -64,20 +64,20 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@aztec/constants": "2.0.
|
|
68
|
-
"@aztec/epoch-cache": "2.0.
|
|
69
|
-
"@aztec/ethereum": "2.0.
|
|
70
|
-
"@aztec/foundation": "2.0.
|
|
71
|
-
"@aztec/node-keystore": "2.0.
|
|
72
|
-
"@aztec/p2p": "2.0.
|
|
73
|
-
"@aztec/prover-client": "2.0.
|
|
74
|
-
"@aztec/slasher": "2.0.
|
|
75
|
-
"@aztec/stdlib": "2.0.
|
|
76
|
-
"@aztec/telemetry-client": "2.0.
|
|
67
|
+
"@aztec/constants": "2.1.0-rc.10",
|
|
68
|
+
"@aztec/epoch-cache": "2.1.0-rc.10",
|
|
69
|
+
"@aztec/ethereum": "2.1.0-rc.10",
|
|
70
|
+
"@aztec/foundation": "2.1.0-rc.10",
|
|
71
|
+
"@aztec/node-keystore": "2.1.0-rc.10",
|
|
72
|
+
"@aztec/p2p": "2.1.0-rc.10",
|
|
73
|
+
"@aztec/prover-client": "2.1.0-rc.10",
|
|
74
|
+
"@aztec/slasher": "2.1.0-rc.10",
|
|
75
|
+
"@aztec/stdlib": "2.1.0-rc.10",
|
|
76
|
+
"@aztec/telemetry-client": "2.1.0-rc.10",
|
|
77
|
+
"@spalladino/viem": "2.38.2-eip7594.0",
|
|
77
78
|
"koa": "^2.16.1",
|
|
78
|
-
"koa-router": "^
|
|
79
|
-
"tslib": "^2.4.0"
|
|
80
|
-
"viem": "2.23.7"
|
|
79
|
+
"koa-router": "^13.1.1",
|
|
80
|
+
"tslib": "^2.4.0"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@jest/globals": "^30.0.0",
|