-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathIValidatorConfigV2.sol
More file actions
301 lines (256 loc) · 15.2 KB
/
IValidatorConfigV2.sol
File metadata and controls
301 lines (256 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.13 <0.9.0;
/// @title IValidatorConfigV2 - Validator Config V2 Precompile Interface
/// @notice Interface for managing consensus validators with append-only, delete-once semantics
/// @dev This precompile manages the set of validators that participate in consensus.
/// V2 uses an append-only design that eliminates the need for historical state access
/// during node recovery. Validators are immutable after creation and can only be deleted once.
///
/// Key differences from V1:
/// - `active` bool replaced by `addedAtHeight` and `deactivatedAtHeight`
/// - No `updateValidator` - validators are immutable after creation
/// - Requires Ed25519 signature on `addValidator` to prove key ownership
/// - Both address and public key must be unique across all validators (including deleted)
interface IValidatorConfigV2 {
// =========================================================================
// Events
// =========================================================================
event ValidatorAdded(
uint64 indexed index,
address indexed validatorAddress,
bytes32 publicKey,
string ingress,
string egress,
address feeRecipient
);
event ValidatorDeactivated(uint64 indexed index, address indexed validatorAddress);
event ValidatorRotated(
uint64 indexed index,
uint64 indexed deactivatedIndex,
address indexed validatorAddress,
bytes32 oldPublicKey,
bytes32 newPublicKey,
string ingress,
string egress,
address caller
);
event FeeRecipientUpdated(uint64 indexed index, address feeRecipient, address caller);
event IpAddressesUpdated(uint64 indexed index, string ingress, string egress, address caller);
event ValidatorOwnershipTransferred(
uint64 indexed index, address indexed oldAddress, address indexed newAddress, address caller
);
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
event ValidatorMigrated(uint64 indexed index, address indexed validatorAddress, bytes32 publicKey);
event NetworkIdentityRotationEpochSet(uint64 indexed previousEpoch, uint64 indexed nextEpoch);
event Initialized(uint64 height);
event SkippedValidatorMigration(uint64 indexed index, address indexed validatorAddress, bytes32 publicKey);
// =========================================================================
// Errors
// =========================================================================
/// @notice Thrown when caller lacks authorization to perform the requested action
error Unauthorized();
/// @notice Thrown when trying to add a validator with an address that already exists
error AddressAlreadyHasValidator();
/// @notice Thrown when trying to add a validator with a public key that already exists
error PublicKeyAlreadyExists();
/// @notice Thrown when validator is not found
error ValidatorNotFound();
/// @notice Thrown when trying to deactivate an already-deactivated validator
error ValidatorAlreadyDeactivated();
/// @notice Thrown when public key is invalid (zero)
error InvalidPublicKey();
/// @notice Thrown when validator address is invalid (zero)
error InvalidValidatorAddress();
/// @notice Thrown when the Ed25519 signature verification fails
error InvalidSignature();
/// @notice Thrown when V2 is not yet initialized (writes blocked before init)
error NotInitialized();
/// @notice Thrown when V2 is already initialized (migration blocked after init)
error AlreadyInitialized();
/// @notice Thrown when migration is not complete (not all V1 validators migrated)
error MigrationNotComplete();
/// @notice Thrown when V1 has no validators to migrate
error EmptyV1ValidatorSet();
/// @notice Thrown when migration index is out of order
error InvalidMigrationIndex();
/// @notice Thrown when new owner is address(0)
error InvalidOwner();
/// @notice Thrown when address is not in valid ip:port format
/// @param input The invalid input that was provided
/// @param backtrace Additional error context
error NotIpPort(string input, string backtrace);
/// @notice Thrown when address is not a valid IP (for egress field)
/// @param input The invalid input that was provided
/// @param backtrace Additional error context
error NotIp(string input, string backtrace);
/// @notice Thrown when ingress IP is already in use by another active validator
/// @param ingress The ingress address that is already in use
error IngressAlreadyExists(string ingress);
// =========================================================================
// Structs
// =========================================================================
/// @dev Internal storage struct with bookkeeping fields not exposed in the public Validator struct.
struct ValidatorStorage {
bytes32 publicKey;
address validatorAddress;
string ingress;
string egress;
address feeRecipient;
uint64 index;
uint64 activeIdx;
uint64 addedAtHeight;
uint64 deactivatedAtHeight;
}
/// @notice Validator information returned by view functions
/// @param publicKey Ed25519 communication public key (non-zero, unique across all validators)
/// @param validatorAddress Ethereum-style address of the validator (unique across active validators)
/// @param ingress Address where other validators can connect (format: `<ip>:<port>`)
/// @param egress IP address from which this validator will dial, e.g. for firewall whitelisting (format: `<ip>`)
/// @param index Position in validators array (stable across rotations for the same slot)
/// @param addedAtHeight Block height when validator was added
/// @param deactivatedAtHeight Block height when validator was deleted (0 = active)
/// @param feeRecipient The fee recipient the node will set when proposing blocks as a leader.
struct Validator {
bytes32 publicKey;
address validatorAddress;
string ingress;
string egress;
address feeRecipient;
uint64 index;
uint64 addedAtHeight;
uint64 deactivatedAtHeight;
}
// =========================================================================
// State-Changing Functions
// =========================================================================
/// @notice Add a new validator (owner only)
/// @dev The signature must be an Ed25519 signature over:
/// keccak256(abi.encodePacked("TEMPO", "_VALIDATOR_CONFIG_V2_ADD_VALIDATOR", chainId, contractAddress, validatorAddress, ingress, egress))
/// This proves the caller controls the private key corresponding to publicKey.
/// Reverts if isInitialized() returns false.
/// @param validatorAddress The address of the new validator
/// @param publicKey The validator's Ed25519 communication public key
/// @param ingress The validator's inbound address `<ip>:<port>` for incoming connections
/// @param egress The validator's outbound IP address `<ip>` for firewall whitelisting
/// @param feeRecipient The fee recipient the validator sets when proposing.
/// @param signature Ed25519 signature (64 bytes) proving ownership of the public key
function addValidator(
address validatorAddress,
bytes32 publicKey,
string calldata ingress,
string calldata egress,
address feeRecipient,
bytes calldata signature
) external returns (uint64 index);
/// @notice Deactivates a validator (owner or validator only)
/// @dev Marks the validator as deactivated by setting deactivatedAtHeight to the current block height.
/// The validator's entry remains in storage for historical queries.
/// The public key remains reserved and cannot be reused.
/// @param idx The global index into the validators array
function deactivateValidator(uint64 idx) external;
/// @notice Rotate a validator to a new identity (owner or validator only)
/// @dev Copies the current validator's data to the end of the array as a deactivated snapshot,
/// then overwrites the original slot with the new identity. The validator's global index
/// is preserved across rotations.
/// The same validation rules as addValidator apply:
/// - The new public key must not already exist
/// - Ingress parseable as <ip>:<port>. Must be different from rotated-out validator (changing port is enough).
/// - Egress must be parseable as <ip>
/// - The signature must prove ownership of the new public key
/// The signature must be an Ed25519 signature over:
/// keccak256(abi.encodePacked("TEMPO", "_VALIDATOR_CONFIG_V2_ROTATE_VALIDATOR", chainId, contractAddress, validatorAddress, ingress, egress))
/// @param idx The global index of the validator to rotate
/// @param publicKey The new validator's Ed25519 communication public key
/// @param ingress The new validator's inbound address `<ip>:<port>` for incoming connections
/// @param egress The new validator's outbound IP address `<ip>` for firewall whitelisting
/// @param signature Ed25519 signature (64 bytes) proving ownership of the new public key
function rotateValidator(
uint64 idx,
bytes32 publicKey,
string calldata ingress,
string calldata egress,
bytes calldata signature
) external;
/// @notice Update a validator's IP addresses (owner or validator only)
/// @dev Can be called by the contract owner or by the validator's own address.
/// This allows validators to update their network addresses without requiring
/// a full rotation.
/// @param idx The global index of the validator to update
/// @param ingress The new inbound address `<ip>:<port>` for incoming connections
/// @param egress The new outbound IP address `<ip>` for firewall whitelisting
function setIpAddresses(uint64 idx, string calldata ingress, string calldata egress) external;
/// @notice Update validator fee recipient (owner or validator only).
/// @dev Can be called by the contract owner or by the validator's own address.
/// @param idx Validator index.
/// @param feeRecipient New fee recipient.
function setFeeRecipient(uint64 idx, address feeRecipient) external;
/// @notice Transfer a validator entry to a new address (owner or validator only)
/// @dev Can be called by the contract owner or by the validator's own address.
/// Updates the validator's address in the lookup maps.
/// Reverts if the new address already exists in the validator set.
/// @param idx The global index of the validator to transfer
/// @param newAddress The new address to assign to the validator
function transferValidatorOwnership(uint64 idx, address newAddress) external;
/// @notice Transfer owner of the contract (owner only)
/// @dev Reverts with InvalidOwner if newOwner is address(0).
/// @param newOwner The new owner address
function transferOwnership(address newOwner) external;
/// @notice Set the epoch at which a rotation of the network identity will be triggered (owner only)
/// @param epoch The epoch at which a DKG ceremony will rotate the network identity.
/// If `epoch` is ahead of the current network epoch, epoch+1 will run with a new identity.
function setNetworkIdentityRotationEpoch(uint64 epoch) external;
// =========================================================================
// View Functions
// =========================================================================
/// @notice Get only active validators (where deactivatedAtHeight == 0).
/// @notice The order of validators is not stable and should not be relied upon.
/// @return validators Array of active validators
function getActiveValidators() external view returns (Validator[] memory validators);
/// @notice Get the owner of the precompile
/// @return The owner address
function owner() external view returns (address);
/// @notice Get total number of validators ever added (including deleted)
/// @return The count of validators
function validatorCount() external view returns (uint64);
/// @notice Get validator information by index in the validators array
/// @param index The index in the validators array
/// @return The validator struct at the given index
function validatorByIndex(uint64 index) external view returns (Validator memory);
/// @notice Get validator information by address
/// @param validatorAddress The validator address to look up
/// @return The validator struct for the given address
function validatorByAddress(address validatorAddress) external view returns (Validator memory);
/// @notice Get validator information by public key
/// @param publicKey The validator's public key to look up
/// @return The validator struct for the given public key
function validatorByPublicKey(bytes32 publicKey) external view returns (Validator memory);
/// @notice Get the epoch at which a network identity rotation will be triggered (via full DKG ceremony)
/// @return The epoch number
function getNextNetworkIdentityRotationEpoch() external view returns (uint64);
/// @notice Check if V2 has been initialized from V1
/// @return True if initialized, false otherwise
function isInitialized() external view returns (bool);
/// @notice Get the height at which the contract was initialized
/// @return The height at which the contract was initialized. Note that this
/// value only makes sense in conjunction with isInitialized()
function getInitializedAtHeight() external view returns (uint64);
// =========================================================================
// Migration Functions (V1 -> V2)
// =========================================================================
/// @notice Migrate a single validator from V1 to V2 (owner only)
/// @dev Can be called multiple times to migrate validators one at a time.
/// On first call, copies owner from V1 if V2 owner is address(0).
/// Active V1 validators get addedAtHeight=block.number and deactivatedAtHeight=0.
/// Inactive V1 validators get addedAtHeight=deactivatedAtHeight=block.number at migration time.
/// Reverts if already initialized or already migrated.
/// Reverts if idx != validatorsArray.length.
/// Reverts if `V2.isInitialized()` (no migrations after V2 is initialized).
/// @param idx Index of the validator in V1 validators array (must equal current validatorsArray.length)
function migrateValidator(uint64 idx) external;
/// @notice Initialize V2 and enable reads (owner only)
/// @dev Should only be called after all validators have been migrated via migrateValidator.
/// Sets initialized=true. After this call, CL reads from V2 instead of V1.
/// Copies nextDkgCeremony from V1.
/// Reverts if V2 validators count < V1 validators count (ensures all validators migrated).
function initializeIfMigrated() external;
}