@aztec/validator-client 0.0.1-commit.6d63667d → 0.0.1-commit.858058eac
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/README.md +21 -18
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +4 -0
- package/dest/validator.d.ts +23 -4
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +112 -11
- package/package.json +19 -19
- package/src/config.ts +4 -0
- package/src/validator.ts +157 -20
package/README.md
CHANGED
|
@@ -77,6 +77,8 @@ These rules must always hold:
|
|
|
77
77
|
2. **Global variables match within checkpoint**: All blocks within the same checkpoint must have identical global variables (except `blockNumber`), which includes the slot number
|
|
78
78
|
3. **inHash is constant**: All blocks in a checkpoint share the same L1-to-L2 messages hash
|
|
79
79
|
4. **Sequential indexWithinCheckpoint**: Block N must have `indexWithinCheckpoint = parent.indexWithinCheckpoint + 1`
|
|
80
|
+
5. **One proposer per slot**: Each slot has exactly one designated proposer. Sending multiple proposals for the same position (slot, indexWithinCheckpoint) with different content is equivocation and slashable
|
|
81
|
+
6. **One attestation per slot**: Validators should only attest to one checkpoint per slot. Attesting to different proposals (different archives) for the same slot is equivocation and slashable
|
|
80
82
|
|
|
81
83
|
## Validation Flow
|
|
82
84
|
|
|
@@ -87,15 +89,14 @@ When a `BlockProposal` is received via P2P, the `BlockProposalHandler` performs:
|
|
|
87
89
|
```
|
|
88
90
|
1. Verify proposer signature
|
|
89
91
|
2. Check proposal is from current/next slot proposer (via BlockProposalValidator)
|
|
90
|
-
3.
|
|
91
|
-
4.
|
|
92
|
-
5.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
9. Compare re-execution result with proposal
|
|
92
|
+
3. Detect duplicate proposals (same slot + indexWithinCheckpoint, different archive) slashing proposer on equivocation
|
|
93
|
+
4. Find parent block by archive root (wait/retry if not synced)
|
|
94
|
+
5. Compute checkpoint number from parent
|
|
95
|
+
6. If indexWithinCheckpoint > 0, then validate global variables match parent (chainId, version, slotNumber, timestamp, coinbase, feeRecipient, gasFees)
|
|
96
|
+
7. Verify inHash matches computed from L1-to-L2 messages
|
|
97
|
+
8. Collect transactions from pool/network/proposal
|
|
98
|
+
9. Re-execute transactions (if enabled)
|
|
99
|
+
10. Compare re-execution result with proposal
|
|
99
100
|
```
|
|
100
101
|
|
|
101
102
|
### Checkpoint Proposal Validation
|
|
@@ -155,15 +156,17 @@ Time | Proposer | Validator
|
|
|
155
156
|
|
|
156
157
|
## Configuration
|
|
157
158
|
|
|
158
|
-
| Flag | Purpose
|
|
159
|
-
| ------------------------------------- |
|
|
160
|
-
| `validatorReexecute` | Re-execute transactions to verify proposals
|
|
161
|
-
| `fishermanMode` | Validate proposals but don't broadcast attestations (monitoring only)
|
|
162
|
-
| `alwaysReexecuteBlockProposals` | Force re-execution even when not in committee
|
|
163
|
-
| `slashBroadcastedInvalidBlockPenalty` | Penalty amount for invalid proposals (0 = disabled)
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
159
|
+
| Flag | Purpose |
|
|
160
|
+
| ------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
161
|
+
| `validatorReexecute` | Re-execute transactions to verify proposals |
|
|
162
|
+
| `fishermanMode` | Validate proposals but don't broadcast attestations (monitoring only) |
|
|
163
|
+
| `alwaysReexecuteBlockProposals` | Force re-execution even when not in committee |
|
|
164
|
+
| `slashBroadcastedInvalidBlockPenalty` | Penalty amount for invalid proposals (0 = disabled) |
|
|
165
|
+
| `slashDuplicateProposalPenalty` | Penalty amount for duplicate proposals (0 = disabled) |
|
|
166
|
+
| `slashDuplicateAttestationPenalty` | Penalty amount for duplicate attestations (0 = disabled) |
|
|
167
|
+
| `validatorReexecuteDeadlineMs` | Time reserved at end of slot for propagation/publishing |
|
|
168
|
+
| `attestationPollingIntervalMs` | How often to poll for attestations when collecting |
|
|
169
|
+
| `disabledValidators` | Validator addresses to exclude from duties |
|
|
167
170
|
|
|
168
171
|
### High Availability (HA) Keystore
|
|
169
172
|
|
package/dest/config.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export declare const validatorClientConfigMappings: ConfigMappingsType<Validator
|
|
|
8
8
|
* @returns The validator configuration.
|
|
9
9
|
*/
|
|
10
10
|
export declare function getProverEnvVars(): ValidatorClientConfig;
|
|
11
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCxLQUFLLGtCQUFrQixFQUt4QixNQUFNLDBCQUEwQixDQUFDO0FBRWxDLE9BQU8sS0FBSyxFQUFFLHFCQUFxQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFHN0UsWUFBWSxFQUFFLHFCQUFxQixFQUFFLENBQUM7QUFFdEMsZUFBTyxNQUFNLDZCQUE2QixFQUFFLGtCQUFrQixDQUFDLHFCQUFxQixDQW1FbkYsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCx3QkFBZ0IsZ0JBQWdCLElBQUkscUJBQXFCLENBRXhEIn0=
|
package/dest/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAG7E,YAAY,EAAE,qBAAqB,EAAE,CAAC;AAEtC,eAAO,MAAM,6BAA6B,EAAE,kBAAkB,CAAC,qBAAqB,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAG7E,YAAY,EAAE,qBAAqB,EAAE,CAAC;AAEtC,eAAO,MAAM,6BAA6B,EAAE,kBAAkB,CAAC,qBAAqB,CAmEnF,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,qBAAqB,CAExD"}
|
package/dest/config.js
CHANGED
|
@@ -53,6 +53,10 @@ export const validatorClientConfigMappings = {
|
|
|
53
53
|
description: 'Skip pushing re-executed blocks to archiver (default: false)',
|
|
54
54
|
defaultValue: false
|
|
55
55
|
},
|
|
56
|
+
attestToEquivocatedProposals: {
|
|
57
|
+
description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
|
|
58
|
+
...booleanConfigHelper(false)
|
|
59
|
+
},
|
|
56
60
|
...validatorHASignerConfigMappings
|
|
57
61
|
};
|
|
58
62
|
/**
|
package/dest/validator.d.ts
CHANGED
|
@@ -13,8 +13,7 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
|
13
13
|
import type { CommitteeAttestationsAndSigners, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
14
14
|
import type { CreateCheckpointProposalLastBlockData, ITxProvider, Validator, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
15
15
|
import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
16
|
-
import type
|
|
17
|
-
import { CheckpointProposal } from '@aztec/stdlib/p2p';
|
|
16
|
+
import { type BlockProposal, type BlockProposalOptions, type CheckpointAttestation, CheckpointProposal, type CheckpointProposalCore, type CheckpointProposalOptions } from '@aztec/stdlib/p2p';
|
|
18
17
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
19
18
|
import type { BlockHeader, Tx } from '@aztec/stdlib/tx';
|
|
20
19
|
import { type TelemetryClient, type Tracer } from '@aztec/telemetry-client';
|
|
@@ -44,10 +43,15 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
44
43
|
private metrics;
|
|
45
44
|
private log;
|
|
46
45
|
private hasRegisteredHandlers;
|
|
47
|
-
|
|
46
|
+
/** Tracks the last block proposal we created, to detect duplicate proposal attempts. */
|
|
47
|
+
private lastProposedBlock?;
|
|
48
|
+
/** Tracks the last checkpoint proposal we created. */
|
|
49
|
+
private lastProposedCheckpoint?;
|
|
48
50
|
private lastEpochForCommitteeUpdateLoop;
|
|
49
51
|
private epochCacheUpdateLoop;
|
|
50
52
|
private proposersOfInvalidBlocks;
|
|
53
|
+
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */
|
|
54
|
+
private lastAttestedProposal?;
|
|
51
55
|
protected constructor(keyStore: ExtendedValidatorKeyStore, epochCache: EpochCache, p2pClient: P2P, blockProposalHandler: BlockProposalHandler, blockSource: L2BlockSource, checkpointsBuilder: FullNodeCheckpointsBuilder, worldState: WorldStateSynchronizer, l1ToL2MessageSource: L1ToL2MessageSource, config: ValidatorClientFullConfig, blobClient: BlobClientInterface, dateProvider?: DateProvider, telemetry?: TelemetryClient, log?: Logger);
|
|
52
56
|
static validateKeyStoreConfiguration(keyStoreManager: KeystoreManager, logger?: Logger): void;
|
|
53
57
|
private handleEpochCommitteeUpdate;
|
|
@@ -76,6 +80,11 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
76
80
|
* @returns Checkpoint attestations if valid, undefined otherwise
|
|
77
81
|
*/
|
|
78
82
|
attestToCheckpointProposal(proposal: CheckpointProposalCore, _proposalSender: PeerId): Promise<CheckpointAttestation[] | undefined>;
|
|
83
|
+
/**
|
|
84
|
+
* Checks if we should attest to a slot based on equivocation prevention rules.
|
|
85
|
+
* @returns true if we should attest, false if we should skip
|
|
86
|
+
*/
|
|
87
|
+
private shouldAttestToSlot;
|
|
79
88
|
private createCheckpointAttestationsFromProposal;
|
|
80
89
|
private validateCheckpointProposal;
|
|
81
90
|
/**
|
|
@@ -84,6 +93,16 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
84
93
|
private extractCheckpointConstants;
|
|
85
94
|
private uploadBlobsForCheckpoint;
|
|
86
95
|
private slashInvalidBlock;
|
|
96
|
+
/**
|
|
97
|
+
* Handle detection of a duplicate proposal (equivocation).
|
|
98
|
+
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
99
|
+
*/
|
|
100
|
+
private handleDuplicateProposal;
|
|
101
|
+
/**
|
|
102
|
+
* Handle detection of a duplicate attestation (equivocation).
|
|
103
|
+
* Emits a slash event when an attester signs attestations for different proposals at the same slot.
|
|
104
|
+
*/
|
|
105
|
+
private handleDuplicateAttestation;
|
|
87
106
|
createBlockProposal(blockHeader: BlockHeader, indexWithinCheckpoint: IndexWithinCheckpoint, inHash: Fr, archive: Fr, txs: Tx[], proposerAddress: EthAddress | undefined, options?: BlockProposalOptions): Promise<BlockProposal>;
|
|
88
107
|
createCheckpointProposal(checkpointHeader: CheckpointHeader, archive: Fr, lastBlockInfo: CreateCheckpointProposalLastBlockData | undefined, proposerAddress: EthAddress | undefined, options?: CheckpointProposalOptions): Promise<CheckpointProposal>;
|
|
89
108
|
broadcastBlockProposal(proposal: BlockProposal): Promise<void>;
|
|
@@ -93,4 +112,4 @@ export declare class ValidatorClient extends ValidatorClient_base implements Val
|
|
|
93
112
|
private handleAuthRequest;
|
|
94
113
|
}
|
|
95
114
|
export {};
|
|
96
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
115
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFckUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUNMLFdBQVcsRUFDWCxnQkFBZ0IsRUFFaEIscUJBQXFCLEVBQ3JCLFVBQVUsRUFDWCxNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUVwRCxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUNoRSxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNqRSxPQUFPLEVBQWdCLEtBQUssTUFBTSxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBSWhGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssRUFBRSxlQUFlLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUM1RCxPQUFPLEtBQUssRUFBbUQsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQztBQUUvRixPQUFPLEVBQW9DLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDckcsT0FBTyxLQUFLLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsK0JBQStCLEVBQVcsV0FBVyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRWhILE9BQU8sS0FBSyxFQUNWLHFDQUFxQyxFQUNyQyxXQUFXLEVBQ1gsU0FBUyxFQUNULHlCQUF5QixFQUN6QixzQkFBc0IsRUFDdkIsTUFBTSxpQ0FBaUMsQ0FB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
|
package/dest/validator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,WAAW,EACX,gBAAgB,EAEhB,qBAAqB,EACrB,UAAU,EACX,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAgB,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAIhF,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
package/dest/validator.js
CHANGED
|
@@ -48,11 +48,12 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
48
48
|
log;
|
|
49
49
|
// Whether it has already registered handlers on the p2p client
|
|
50
50
|
hasRegisteredHandlers;
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
/** Tracks the last block proposal we created, to detect duplicate proposal attempts. */ lastProposedBlock;
|
|
52
|
+
/** Tracks the last checkpoint proposal we created. */ lastProposedCheckpoint;
|
|
53
53
|
lastEpochForCommitteeUpdateLoop;
|
|
54
54
|
epochCacheUpdateLoop;
|
|
55
55
|
proposersOfInvalidBlocks;
|
|
56
|
+
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */ lastAttestedProposal;
|
|
56
57
|
constructor(keyStore, epochCache, p2pClient, blockProposalHandler, blockSource, checkpointsBuilder, worldState, l1ToL2MessageSource, config, blobClient, dateProvider = new DateProvider(), telemetry = getTelemetryClient(), log = createLogger('validator')){
|
|
57
58
|
super(), this.keyStore = keyStore, this.epochCache = epochCache, this.p2pClient = p2pClient, this.blockProposalHandler = blockProposalHandler, this.blockSource = blockSource, this.checkpointsBuilder = checkpointsBuilder, this.worldState = worldState, this.l1ToL2MessageSource = l1ToL2MessageSource, this.config = config, this.blobClient = blobClient, this.dateProvider = dateProvider, this.hasRegisteredHandlers = false, this.proposersOfInvalidBlocks = new Set();
|
|
58
59
|
// Create child logger with fisherman prefix if in fisherman mode
|
|
@@ -183,6 +184,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
183
184
|
// and processed separately via the block handler above.
|
|
184
185
|
const checkpointHandler = (checkpoint, proposalSender)=>this.attestToCheckpointProposal(checkpoint, proposalSender);
|
|
185
186
|
this.p2pClient.registerCheckpointProposalHandler(checkpointHandler);
|
|
187
|
+
// Duplicate proposal handler - triggers slashing for equivocation
|
|
188
|
+
this.p2pClient.registerDuplicateProposalCallback((info)=>{
|
|
189
|
+
this.handleDuplicateProposal(info);
|
|
190
|
+
});
|
|
191
|
+
// Duplicate attestation handler - triggers slashing for attestation equivocation
|
|
192
|
+
this.p2pClient.registerDuplicateAttestationCallback((info)=>{
|
|
193
|
+
this.handleDuplicateAttestation(info);
|
|
194
|
+
});
|
|
186
195
|
const myAddresses = this.getValidatorAddresses();
|
|
187
196
|
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
188
197
|
await this.p2pClient.addReqRespSubProtocol(ReqRespSubProtocol.AUTH, this.handleAuthRequest.bind(this));
|
|
@@ -203,6 +212,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
203
212
|
this.log.warn(`Received block proposal with invalid signature for slot ${slotNumber}`);
|
|
204
213
|
return false;
|
|
205
214
|
}
|
|
215
|
+
// Ignore proposals from ourselves (may happen in HA setups)
|
|
216
|
+
if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
|
|
217
|
+
this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
|
|
218
|
+
proposer: proposer.toString(),
|
|
219
|
+
slotNumber
|
|
220
|
+
});
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
206
223
|
// Check if we're in the committee (for metrics purposes)
|
|
207
224
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
208
225
|
const partOfCommittee = inCommittee.length > 0;
|
|
@@ -274,6 +291,14 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
274
291
|
this.log.warn(`Received checkpoint proposal with invalid signature for slot ${slotNumber}`);
|
|
275
292
|
return undefined;
|
|
276
293
|
}
|
|
294
|
+
// Ignore proposals from ourselves (may happen in HA setups)
|
|
295
|
+
if (this.getValidatorAddresses().some((addr)=>addr.equals(proposer))) {
|
|
296
|
+
this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
|
|
297
|
+
proposer: proposer.toString(),
|
|
298
|
+
slotNumber
|
|
299
|
+
});
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
277
302
|
// Check that I have any address in current committee before attesting
|
|
278
303
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
279
304
|
const partOfCommittee = inCommittee.length > 0;
|
|
@@ -337,11 +362,32 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
337
362
|
});
|
|
338
363
|
return undefined;
|
|
339
364
|
}
|
|
340
|
-
return this.createCheckpointAttestationsFromProposal(proposal, attestors);
|
|
365
|
+
return await this.createCheckpointAttestationsFromProposal(proposal, attestors);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Checks if we should attest to a slot based on equivocation prevention rules.
|
|
369
|
+
* @returns true if we should attest, false if we should skip
|
|
370
|
+
*/ shouldAttestToSlot(slotNumber) {
|
|
371
|
+
// If attestToEquivocatedProposals is true, always allow
|
|
372
|
+
if (this.config.attestToEquivocatedProposals) {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
// Check if incoming slot is strictly greater than last attested
|
|
376
|
+
if (this.lastAttestedProposal && slotNumber <= this.lastAttestedProposal.slotNumber) {
|
|
377
|
+
this.log.warn(`Refusing to process a proposal for slot ${slotNumber} given we already attested to a proposal for slot ${this.lastAttestedProposal.slotNumber}`);
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
341
381
|
}
|
|
342
382
|
async createCheckpointAttestationsFromProposal(proposal, attestors = []) {
|
|
383
|
+
// Equivocation check: must happen right before signing to minimize the race window
|
|
384
|
+
if (!this.shouldAttestToSlot(proposal.slotNumber)) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
343
387
|
const attestations = await this.validationService.attestToCheckpointProposal(proposal, attestors);
|
|
344
|
-
|
|
388
|
+
// Track the proposal we attested to (to prevent equivocation)
|
|
389
|
+
this.lastAttestedProposal = proposal;
|
|
390
|
+
await this.p2pClient.addOwnCheckpointAttestations(attestations);
|
|
345
391
|
return attestations;
|
|
346
392
|
}
|
|
347
393
|
/**
|
|
@@ -524,23 +570,75 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
524
570
|
}
|
|
525
571
|
]);
|
|
526
572
|
}
|
|
573
|
+
/**
|
|
574
|
+
* Handle detection of a duplicate proposal (equivocation).
|
|
575
|
+
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
576
|
+
*/ handleDuplicateProposal(info) {
|
|
577
|
+
const { slot, proposer, type } = info;
|
|
578
|
+
this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
|
|
579
|
+
proposer: proposer.toString(),
|
|
580
|
+
slot,
|
|
581
|
+
type
|
|
582
|
+
});
|
|
583
|
+
// Emit slash event
|
|
584
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
585
|
+
{
|
|
586
|
+
validator: proposer,
|
|
587
|
+
amount: this.config.slashDuplicateProposalPenalty,
|
|
588
|
+
offenseType: OffenseType.DUPLICATE_PROPOSAL,
|
|
589
|
+
epochOrSlot: BigInt(slot)
|
|
590
|
+
}
|
|
591
|
+
]);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Handle detection of a duplicate attestation (equivocation).
|
|
595
|
+
* Emits a slash event when an attester signs attestations for different proposals at the same slot.
|
|
596
|
+
*/ handleDuplicateAttestation(info) {
|
|
597
|
+
const { slot, attester } = info;
|
|
598
|
+
this.log.warn(`Triggering slash event for duplicate attestation from ${attester.toString()} at slot ${slot}`, {
|
|
599
|
+
attester: attester.toString(),
|
|
600
|
+
slot
|
|
601
|
+
});
|
|
602
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
603
|
+
{
|
|
604
|
+
validator: attester,
|
|
605
|
+
amount: this.config.slashDuplicateAttestationPenalty,
|
|
606
|
+
offenseType: OffenseType.DUPLICATE_ATTESTATION,
|
|
607
|
+
epochOrSlot: BigInt(slot)
|
|
608
|
+
}
|
|
609
|
+
]);
|
|
610
|
+
}
|
|
527
611
|
async createBlockProposal(blockHeader, indexWithinCheckpoint, inHash, archive, txs, proposerAddress, options = {}) {
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
612
|
+
// Validate that we're not creating a proposal for an older or equal position
|
|
613
|
+
if (this.lastProposedBlock) {
|
|
614
|
+
const lastSlot = this.lastProposedBlock.slotNumber;
|
|
615
|
+
const lastIndex = this.lastProposedBlock.indexWithinCheckpoint;
|
|
616
|
+
const newSlot = blockHeader.globalVariables.slotNumber;
|
|
617
|
+
if (newSlot < lastSlot || newSlot === lastSlot && indexWithinCheckpoint <= lastIndex) {
|
|
618
|
+
throw new Error(`Cannot create block proposal for slot ${newSlot} index ${indexWithinCheckpoint}: ` + `already proposed block for slot ${lastSlot} index ${lastIndex}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
533
621
|
this.log.info(`Assembling block proposal for block ${blockHeader.globalVariables.blockNumber} slot ${blockHeader.globalVariables.slotNumber}`);
|
|
534
622
|
const newProposal = await this.validationService.createBlockProposal(blockHeader, indexWithinCheckpoint, inHash, archive, txs, proposerAddress, {
|
|
535
623
|
...options,
|
|
536
624
|
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal
|
|
537
625
|
});
|
|
538
|
-
this.
|
|
626
|
+
this.lastProposedBlock = newProposal;
|
|
539
627
|
return newProposal;
|
|
540
628
|
}
|
|
541
629
|
async createCheckpointProposal(checkpointHeader, archive, lastBlockInfo, proposerAddress, options = {}) {
|
|
630
|
+
// Validate that we're not creating a proposal for an older or equal slot
|
|
631
|
+
if (this.lastProposedCheckpoint) {
|
|
632
|
+
const lastSlot = this.lastProposedCheckpoint.slotNumber;
|
|
633
|
+
const newSlot = checkpointHeader.slotNumber;
|
|
634
|
+
if (newSlot <= lastSlot) {
|
|
635
|
+
throw new Error(`Cannot create checkpoint proposal for slot ${newSlot}: ` + `already proposed checkpoint for slot ${lastSlot}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
542
638
|
this.log.info(`Assembling checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
|
|
543
|
-
|
|
639
|
+
const newProposal = await this.validationService.createCheckpointProposal(checkpointHeader, archive, lastBlockInfo, proposerAddress, options);
|
|
640
|
+
this.lastProposedCheckpoint = newProposal;
|
|
641
|
+
return newProposal;
|
|
544
642
|
}
|
|
545
643
|
async broadcastBlockProposal(proposal) {
|
|
546
644
|
await this.p2pClient.broadcastProposal(proposal);
|
|
@@ -555,6 +653,9 @@ const SLASHABLE_BLOCK_PROPOSAL_VALIDATION_RESULT = [
|
|
|
555
653
|
inCommittee
|
|
556
654
|
});
|
|
557
655
|
const attestations = await this.createCheckpointAttestationsFromProposal(proposal, inCommittee);
|
|
656
|
+
if (!attestations) {
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
558
659
|
// We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
|
|
559
660
|
// other nodes can see that our validators did attest to this block proposal, and do not slash us
|
|
560
661
|
// due to inactivity for missed attestations.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-client",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.858058eac",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -64,30 +64,30 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@aztec/blob-client": "0.0.1-commit.
|
|
68
|
-
"@aztec/blob-lib": "0.0.1-commit.
|
|
69
|
-
"@aztec/constants": "0.0.1-commit.
|
|
70
|
-
"@aztec/epoch-cache": "0.0.1-commit.
|
|
71
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
72
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
73
|
-
"@aztec/node-keystore": "0.0.1-commit.
|
|
74
|
-
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.
|
|
75
|
-
"@aztec/p2p": "0.0.1-commit.
|
|
76
|
-
"@aztec/protocol-contracts": "0.0.1-commit.
|
|
77
|
-
"@aztec/prover-client": "0.0.1-commit.
|
|
78
|
-
"@aztec/simulator": "0.0.1-commit.
|
|
79
|
-
"@aztec/slasher": "0.0.1-commit.
|
|
80
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
81
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
82
|
-
"@aztec/validator-ha-signer": "0.0.1-commit.
|
|
67
|
+
"@aztec/blob-client": "0.0.1-commit.858058eac",
|
|
68
|
+
"@aztec/blob-lib": "0.0.1-commit.858058eac",
|
|
69
|
+
"@aztec/constants": "0.0.1-commit.858058eac",
|
|
70
|
+
"@aztec/epoch-cache": "0.0.1-commit.858058eac",
|
|
71
|
+
"@aztec/ethereum": "0.0.1-commit.858058eac",
|
|
72
|
+
"@aztec/foundation": "0.0.1-commit.858058eac",
|
|
73
|
+
"@aztec/node-keystore": "0.0.1-commit.858058eac",
|
|
74
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.858058eac",
|
|
75
|
+
"@aztec/p2p": "0.0.1-commit.858058eac",
|
|
76
|
+
"@aztec/protocol-contracts": "0.0.1-commit.858058eac",
|
|
77
|
+
"@aztec/prover-client": "0.0.1-commit.858058eac",
|
|
78
|
+
"@aztec/simulator": "0.0.1-commit.858058eac",
|
|
79
|
+
"@aztec/slasher": "0.0.1-commit.858058eac",
|
|
80
|
+
"@aztec/stdlib": "0.0.1-commit.858058eac",
|
|
81
|
+
"@aztec/telemetry-client": "0.0.1-commit.858058eac",
|
|
82
|
+
"@aztec/validator-ha-signer": "0.0.1-commit.858058eac",
|
|
83
83
|
"koa": "^2.16.1",
|
|
84
84
|
"koa-router": "^13.1.1",
|
|
85
85
|
"tslib": "^2.4.0",
|
|
86
86
|
"viem": "npm:@aztec/viem@2.38.2"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@aztec/archiver": "0.0.1-commit.
|
|
90
|
-
"@aztec/world-state": "0.0.1-commit.
|
|
89
|
+
"@aztec/archiver": "0.0.1-commit.858058eac",
|
|
90
|
+
"@aztec/world-state": "0.0.1-commit.858058eac",
|
|
91
91
|
"@electric-sql/pglite": "^0.3.14",
|
|
92
92
|
"@jest/globals": "^30.0.0",
|
|
93
93
|
"@types/jest": "^30.0.0",
|
package/src/config.ts
CHANGED
|
@@ -73,6 +73,10 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
73
73
|
description: 'Skip pushing re-executed blocks to archiver (default: false)',
|
|
74
74
|
defaultValue: false,
|
|
75
75
|
},
|
|
76
|
+
attestToEquivocatedProposals: {
|
|
77
|
+
description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
|
|
78
|
+
...booleanConfigHelper(false),
|
|
79
|
+
},
|
|
76
80
|
...validatorHASignerConfigMappings,
|
|
77
81
|
};
|
|
78
82
|
|
package/src/validator.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
|
18
18
|
import { sleep } from '@aztec/foundation/sleep';
|
|
19
19
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
20
20
|
import type { KeystoreManager } from '@aztec/node-keystore';
|
|
21
|
-
import type { P2P, PeerId } from '@aztec/p2p';
|
|
21
|
+
import type { DuplicateAttestationInfo, DuplicateProposalInfo, P2P, PeerId } from '@aztec/p2p';
|
|
22
22
|
import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p';
|
|
23
23
|
import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher';
|
|
24
24
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
@@ -32,14 +32,14 @@ import type {
|
|
|
32
32
|
WorldStateSynchronizer,
|
|
33
33
|
} from '@aztec/stdlib/interfaces/server';
|
|
34
34
|
import { type L1ToL2MessageSource, accumulateCheckpointOutHashes } from '@aztec/stdlib/messaging';
|
|
35
|
-
import
|
|
36
|
-
BlockProposal,
|
|
37
|
-
BlockProposalOptions,
|
|
38
|
-
CheckpointAttestation,
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
import {
|
|
36
|
+
type BlockProposal,
|
|
37
|
+
type BlockProposalOptions,
|
|
38
|
+
type CheckpointAttestation,
|
|
39
|
+
CheckpointProposal,
|
|
40
|
+
type CheckpointProposalCore,
|
|
41
|
+
type CheckpointProposalOptions,
|
|
41
42
|
} from '@aztec/stdlib/p2p';
|
|
42
|
-
import { CheckpointProposal } from '@aztec/stdlib/p2p';
|
|
43
43
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
44
44
|
import type { BlockHeader, CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
45
45
|
import { AttestationTimeoutError } from '@aztec/stdlib/validators';
|
|
@@ -80,14 +80,20 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
80
80
|
// Whether it has already registered handlers on the p2p client
|
|
81
81
|
private hasRegisteredHandlers = false;
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
private
|
|
83
|
+
/** Tracks the last block proposal we created, to detect duplicate proposal attempts. */
|
|
84
|
+
private lastProposedBlock?: BlockProposal;
|
|
85
|
+
|
|
86
|
+
/** Tracks the last checkpoint proposal we created. */
|
|
87
|
+
private lastProposedCheckpoint?: CheckpointProposal;
|
|
85
88
|
|
|
86
89
|
private lastEpochForCommitteeUpdateLoop: EpochNumber | undefined;
|
|
87
90
|
private epochCacheUpdateLoop: RunningPromise;
|
|
88
91
|
|
|
89
92
|
private proposersOfInvalidBlocks: Set<string> = new Set();
|
|
90
93
|
|
|
94
|
+
/** Tracks the last checkpoint proposal we attested to, to prevent equivocation. */
|
|
95
|
+
private lastAttestedProposal?: CheckpointProposalCore;
|
|
96
|
+
|
|
91
97
|
protected constructor(
|
|
92
98
|
private keyStore: ExtendedValidatorKeyStore,
|
|
93
99
|
private epochCache: EpochCache,
|
|
@@ -309,6 +315,16 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
309
315
|
): Promise<CheckpointAttestation[] | undefined> => this.attestToCheckpointProposal(checkpoint, proposalSender);
|
|
310
316
|
this.p2pClient.registerCheckpointProposalHandler(checkpointHandler);
|
|
311
317
|
|
|
318
|
+
// Duplicate proposal handler - triggers slashing for equivocation
|
|
319
|
+
this.p2pClient.registerDuplicateProposalCallback((info: DuplicateProposalInfo) => {
|
|
320
|
+
this.handleDuplicateProposal(info);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Duplicate attestation handler - triggers slashing for attestation equivocation
|
|
324
|
+
this.p2pClient.registerDuplicateAttestationCallback((info: DuplicateAttestationInfo) => {
|
|
325
|
+
this.handleDuplicateAttestation(info);
|
|
326
|
+
});
|
|
327
|
+
|
|
312
328
|
const myAddresses = this.getValidatorAddresses();
|
|
313
329
|
this.p2pClient.registerThisValidatorAddresses(myAddresses);
|
|
314
330
|
|
|
@@ -336,6 +352,15 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
336
352
|
return false;
|
|
337
353
|
}
|
|
338
354
|
|
|
355
|
+
// Ignore proposals from ourselves (may happen in HA setups)
|
|
356
|
+
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
357
|
+
this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
|
|
358
|
+
proposer: proposer.toString(),
|
|
359
|
+
slotNumber,
|
|
360
|
+
});
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
339
364
|
// Check if we're in the committee (for metrics purposes)
|
|
340
365
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
341
366
|
const partOfCommittee = inCommittee.length > 0;
|
|
@@ -437,6 +462,15 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
437
462
|
return undefined;
|
|
438
463
|
}
|
|
439
464
|
|
|
465
|
+
// Ignore proposals from ourselves (may happen in HA setups)
|
|
466
|
+
if (this.getValidatorAddresses().some(addr => addr.equals(proposer))) {
|
|
467
|
+
this.log.warn(`Ignoring block proposal from self for slot ${slotNumber}`, {
|
|
468
|
+
proposer: proposer.toString(),
|
|
469
|
+
slotNumber,
|
|
470
|
+
});
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
|
|
440
474
|
// Check that I have any address in current committee before attesting
|
|
441
475
|
const inCommittee = await this.epochCache.filterInCommittee(slotNumber, this.getValidatorAddresses());
|
|
442
476
|
const partOfCommittee = inCommittee.length > 0;
|
|
@@ -510,15 +544,45 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
510
544
|
return undefined;
|
|
511
545
|
}
|
|
512
546
|
|
|
513
|
-
return this.createCheckpointAttestationsFromProposal(proposal, attestors);
|
|
547
|
+
return await this.createCheckpointAttestationsFromProposal(proposal, attestors);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Checks if we should attest to a slot based on equivocation prevention rules.
|
|
552
|
+
* @returns true if we should attest, false if we should skip
|
|
553
|
+
*/
|
|
554
|
+
private shouldAttestToSlot(slotNumber: SlotNumber): boolean {
|
|
555
|
+
// If attestToEquivocatedProposals is true, always allow
|
|
556
|
+
if (this.config.attestToEquivocatedProposals) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Check if incoming slot is strictly greater than last attested
|
|
561
|
+
if (this.lastAttestedProposal && slotNumber <= this.lastAttestedProposal.slotNumber) {
|
|
562
|
+
this.log.warn(
|
|
563
|
+
`Refusing to process a proposal for slot ${slotNumber} given we already attested to a proposal for slot ${this.lastAttestedProposal.slotNumber}`,
|
|
564
|
+
);
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return true;
|
|
514
569
|
}
|
|
515
570
|
|
|
516
571
|
private async createCheckpointAttestationsFromProposal(
|
|
517
572
|
proposal: CheckpointProposalCore,
|
|
518
573
|
attestors: EthAddress[] = [],
|
|
519
|
-
): Promise<CheckpointAttestation[]> {
|
|
574
|
+
): Promise<CheckpointAttestation[] | undefined> {
|
|
575
|
+
// Equivocation check: must happen right before signing to minimize the race window
|
|
576
|
+
if (!this.shouldAttestToSlot(proposal.slotNumber)) {
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
|
|
520
580
|
const attestations = await this.validationService.attestToCheckpointProposal(proposal, attestors);
|
|
521
|
-
|
|
581
|
+
|
|
582
|
+
// Track the proposal we attested to (to prevent equivocation)
|
|
583
|
+
this.lastAttestedProposal = proposal;
|
|
584
|
+
|
|
585
|
+
await this.p2pClient.addOwnCheckpointAttestations(attestations);
|
|
522
586
|
return attestations;
|
|
523
587
|
}
|
|
524
588
|
|
|
@@ -721,6 +785,52 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
721
785
|
]);
|
|
722
786
|
}
|
|
723
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Handle detection of a duplicate proposal (equivocation).
|
|
790
|
+
* Emits a slash event when a proposer sends multiple proposals for the same position.
|
|
791
|
+
*/
|
|
792
|
+
private handleDuplicateProposal(info: DuplicateProposalInfo): void {
|
|
793
|
+
const { slot, proposer, type } = info;
|
|
794
|
+
|
|
795
|
+
this.log.warn(`Triggering slash event for duplicate ${type} proposal from ${proposer.toString()} at slot ${slot}`, {
|
|
796
|
+
proposer: proposer.toString(),
|
|
797
|
+
slot,
|
|
798
|
+
type,
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Emit slash event
|
|
802
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
803
|
+
{
|
|
804
|
+
validator: proposer,
|
|
805
|
+
amount: this.config.slashDuplicateProposalPenalty,
|
|
806
|
+
offenseType: OffenseType.DUPLICATE_PROPOSAL,
|
|
807
|
+
epochOrSlot: BigInt(slot),
|
|
808
|
+
},
|
|
809
|
+
]);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Handle detection of a duplicate attestation (equivocation).
|
|
814
|
+
* Emits a slash event when an attester signs attestations for different proposals at the same slot.
|
|
815
|
+
*/
|
|
816
|
+
private handleDuplicateAttestation(info: DuplicateAttestationInfo): void {
|
|
817
|
+
const { slot, attester } = info;
|
|
818
|
+
|
|
819
|
+
this.log.warn(`Triggering slash event for duplicate attestation from ${attester.toString()} at slot ${slot}`, {
|
|
820
|
+
attester: attester.toString(),
|
|
821
|
+
slot,
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
this.emit(WANT_TO_SLASH_EVENT, [
|
|
825
|
+
{
|
|
826
|
+
validator: attester,
|
|
827
|
+
amount: this.config.slashDuplicateAttestationPenalty,
|
|
828
|
+
offenseType: OffenseType.DUPLICATE_ATTESTATION,
|
|
829
|
+
epochOrSlot: BigInt(slot),
|
|
830
|
+
},
|
|
831
|
+
]);
|
|
832
|
+
}
|
|
833
|
+
|
|
724
834
|
async createBlockProposal(
|
|
725
835
|
blockHeader: BlockHeader,
|
|
726
836
|
indexWithinCheckpoint: IndexWithinCheckpoint,
|
|
@@ -730,11 +840,19 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
730
840
|
proposerAddress: EthAddress | undefined,
|
|
731
841
|
options: BlockProposalOptions = {},
|
|
732
842
|
): Promise<BlockProposal> {
|
|
733
|
-
//
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
843
|
+
// Validate that we're not creating a proposal for an older or equal position
|
|
844
|
+
if (this.lastProposedBlock) {
|
|
845
|
+
const lastSlot = this.lastProposedBlock.slotNumber;
|
|
846
|
+
const lastIndex = this.lastProposedBlock.indexWithinCheckpoint;
|
|
847
|
+
const newSlot = blockHeader.globalVariables.slotNumber;
|
|
848
|
+
|
|
849
|
+
if (newSlot < lastSlot || (newSlot === lastSlot && indexWithinCheckpoint <= lastIndex)) {
|
|
850
|
+
throw new Error(
|
|
851
|
+
`Cannot create block proposal for slot ${newSlot} index ${indexWithinCheckpoint}: ` +
|
|
852
|
+
`already proposed block for slot ${lastSlot} index ${lastIndex}`,
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
738
856
|
|
|
739
857
|
this.log.info(
|
|
740
858
|
`Assembling block proposal for block ${blockHeader.globalVariables.blockNumber} slot ${blockHeader.globalVariables.slotNumber}`,
|
|
@@ -751,7 +869,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
751
869
|
broadcastInvalidBlockProposal: this.config.broadcastInvalidBlockProposal,
|
|
752
870
|
},
|
|
753
871
|
);
|
|
754
|
-
this.
|
|
872
|
+
this.lastProposedBlock = newProposal;
|
|
755
873
|
return newProposal;
|
|
756
874
|
}
|
|
757
875
|
|
|
@@ -762,14 +880,29 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
762
880
|
proposerAddress: EthAddress | undefined,
|
|
763
881
|
options: CheckpointProposalOptions = {},
|
|
764
882
|
): Promise<CheckpointProposal> {
|
|
883
|
+
// Validate that we're not creating a proposal for an older or equal slot
|
|
884
|
+
if (this.lastProposedCheckpoint) {
|
|
885
|
+
const lastSlot = this.lastProposedCheckpoint.slotNumber;
|
|
886
|
+
const newSlot = checkpointHeader.slotNumber;
|
|
887
|
+
|
|
888
|
+
if (newSlot <= lastSlot) {
|
|
889
|
+
throw new Error(
|
|
890
|
+
`Cannot create checkpoint proposal for slot ${newSlot}: ` +
|
|
891
|
+
`already proposed checkpoint for slot ${lastSlot}`,
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
765
896
|
this.log.info(`Assembling checkpoint proposal for slot ${checkpointHeader.slotNumber}`);
|
|
766
|
-
|
|
897
|
+
const newProposal = await this.validationService.createCheckpointProposal(
|
|
767
898
|
checkpointHeader,
|
|
768
899
|
archive,
|
|
769
900
|
lastBlockInfo,
|
|
770
901
|
proposerAddress,
|
|
771
902
|
options,
|
|
772
903
|
);
|
|
904
|
+
this.lastProposedCheckpoint = newProposal;
|
|
905
|
+
return newProposal;
|
|
773
906
|
}
|
|
774
907
|
|
|
775
908
|
async broadcastBlockProposal(proposal: BlockProposal): Promise<void> {
|
|
@@ -791,6 +924,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
|
|
|
791
924
|
this.log.debug(`Collecting ${inCommittee.length} self-attestations for slot ${slot}`, { inCommittee });
|
|
792
925
|
const attestations = await this.createCheckpointAttestationsFromProposal(proposal, inCommittee);
|
|
793
926
|
|
|
927
|
+
if (!attestations) {
|
|
928
|
+
return [];
|
|
929
|
+
}
|
|
930
|
+
|
|
794
931
|
// We broadcast our own attestations to our peers so, in case our block does not get mined on L1,
|
|
795
932
|
// other nodes can see that our validators did attest to this block proposal, and do not slash us
|
|
796
933
|
// due to inactivity for missed attestations.
|