Skip to content

Commit

Permalink
feat(gear.exe): validators commitment (#4373)
Browse files Browse the repository at this point in the history
  • Loading branch information
grishasobol authored Dec 12, 2024
1 parent 89ca9b7 commit d7a3924
Show file tree
Hide file tree
Showing 22 changed files with 1,075 additions and 520 deletions.
12 changes: 9 additions & 3 deletions ethexe/common/src/events/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ pub enum Event {
code_id: CodeId,
},
StorageSlotChanged,
ValidatorsChanged,
NextEraValidatorsCommitted {
next_era_start: u64,
},
}

impl Event {
Expand All @@ -67,7 +69,9 @@ impl Event {
RequestEvent::ProgramCreated { actor_id, code_id }
}
Self::StorageSlotChanged => RequestEvent::StorageSlotChanged,
Self::ValidatorsChanged => RequestEvent::ValidatorsChanged,
Self::NextEraValidatorsCommitted { next_era_start } => {
RequestEvent::NextEraValidatorsCommitted { next_era_start }
}
Self::BlockCommitted { .. } | Self::CodeGotValidated { .. } => return None,
})
}
Expand All @@ -90,5 +94,7 @@ pub enum RequestEvent {
code_id: CodeId,
},
StorageSlotChanged,
ValidatorsChanged,
NextEraValidatorsCommitted {
next_era_start: u64,
},
}
6 changes: 6 additions & 0 deletions ethexe/common/src/gear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ pub struct CodeCommitment {
pub valid: bool,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub struct ValidatorsCommitment {
pub validators: Vec<ActorId>,
pub era_index: u64,
}

#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)]
pub enum CodeState {
#[default]
Expand Down
10 changes: 9 additions & 1 deletion ethexe/contracts/script/Deployment.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ contract DeploymentScript is Script {
deployerAddress,
abi.encodeCall(
Router.initialize,
(deployerAddress, mirrorAddress, mirrorProxyAddress, address(wrappedVara), validatorsArray)
(
deployerAddress,
mirrorAddress,
mirrorProxyAddress,
address(wrappedVara),
1 days,
2 hours,
validatorsArray
)
)
)
);
Expand Down
16 changes: 12 additions & 4 deletions ethexe/contracts/src/IRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ interface IRouter {
/// @notice Computation parameters for programs processing.
/// @dev These parameters should be used for the operational logic of event and message handling on nodes. Any modifications will take effect in the next block.
Gear.ComputationSettings computeSettings;
/// @notice Protocol timelines.
/// @dev This contains information about the protocol's timelines.
Gear.Timelines timelines;
/// @notice Gear protocol data related to this router instance.
/// @dev This contains information about the available codes and programs.
Gear.ProtocolData protocolData;
Expand All @@ -45,6 +48,11 @@ interface IRouter {
/// @param blobTxHash The transaction hash that contains the WASM blob. Set to zero if applied to the current transaction.
event CodeValidationRequested(bytes32 codeId, bytes32 blobTxHash);

/// @notice Emitted when validators for the next era has been set.
/// @dev This is an *informational* and *request* event, signaling that validators has been set for the next era.
/// @param startTimestamp timestamp when the new era starts.
event NextEraValidatorsCommitted(uint256 startTimestamp);

/// @notice Emitted when the computation settings have been changed.
/// @dev This is both an *informational* and *requesting* event, signaling that an authority decided to change the computation settings. Users and program authors may want to adjust their practices, while validators need to apply the changes internally starting from the next block.
/// @param threshold The amount of Gear gas initially allocated for free to allow the program to decide if it wants to process the incoming message.
Expand All @@ -61,12 +69,9 @@ interface IRouter {
/// @dev This is both an *informational* and *requesting* event, signaling that an authority decided to wipe the router state, rendering all previously existing codes and programs ineligible. Validators need to wipe their databases immediately.
event StorageSlotChanged();

/// @notice Emitted when the election mechanism forces the validator set to be changed.
/// @dev This is an *informational* event, signaling that only new validators are now able to pass commitment signing verification.
event ValidatorsChanged();

// # Views.
function genesisBlockHash() external view returns (bytes32);
function genesisTimestamp() external view returns (uint48);
function latestCommittedBlockHash() external view returns (bytes32);

function mirrorImpl() external view returns (address);
Expand Down Expand Up @@ -107,4 +112,7 @@ interface IRouter {
function commitCodes(Gear.CodeCommitment[] calldata codeCommitments, bytes[] calldata signatures) external;
/// @dev BlockCommitted Emitted on success. Triggers multiple events for each corresponding mirror.
function commitBlocks(Gear.BlockCommitment[] calldata blockCommitments, bytes[] calldata signatures) external;
/// @dev NextEraValidatorsCommitted Emitted on success.
function commitValidators(Gear.ValidatorsCommitment calldata validatorsCommitment, bytes[] calldata signatures)
external;
}
1 change: 1 addition & 0 deletions ethexe/contracts/src/Middleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {MapWithTimeData} from "./libraries/MapWithTimeData.sol";
// TODO (asap): document all functions and variables
// TODO (asap): implement rewards distribution
// TODO (asap): add validators commission
// TODO: introduce common struct for address and balance/value
// TODO: implement forced operators removal
// TODO: implement forced vaults removal
// TODO: use hints for symbiotic calls
Expand Down
107 changes: 85 additions & 22 deletions ethexe/contracts/src/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";

// TODO (gsobol): append middleware for slashing support.
contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
// keccak256(abi.encode(uint256(keccak256("router.storage.Slot")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant SLOT_STORAGE = 0x5c09ca1b9b8127a4fd9f3c384aac59b661441e820e17733753ff5f2e86e1e000;
Expand All @@ -25,18 +26,28 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
address _mirror,
address _mirrorProxy,
address _wrappedVara,
uint256 _eraDuration,
uint256 _electionDuration,
address[] calldata _validators
) public initializer {
__Ownable_init(_owner);

// Because of validator storages impl we have to check, that current timestamp is greater than 0.
require(block.timestamp > 0, "current timestamp must be greater than 0");
require(_electionDuration > 0, "election duration must be greater than 0");
require(_eraDuration > _electionDuration, "era duration must be greater than election duration");

_setStorageSlot("router.storage.RouterV1");
Storage storage router = _router();

router.genesisBlock = Gear.newGenesis();
router.implAddresses = Gear.AddressBook(_mirror, _mirrorProxy, _wrappedVara);
router.validationSettings.signingThresholdPercentage = Gear.SIGNING_THRESHOLD_PERCENTAGE;
_setValidators(router, _validators);
router.computeSettings = Gear.defaultComputationSettings();
router.timelines = Gear.Timelines(_eraDuration, _electionDuration);

// Set validators for the era 0.
_resetValidators(router.validationSettings.validators0, _validators, block.timestamp);
}

function reinitialize() public onlyOwner reinitializer(2) {
Expand All @@ -45,21 +56,44 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
_setStorageSlot("router.storage.RouterV2");
Storage storage newRouter = _router();

// Set current block as genesis.
newRouter.genesisBlock = Gear.newGenesis();

// New router latestCommittedBlock is already zeroed.

// Copy impl addresses from the old router.
newRouter.implAddresses = oldRouter.implAddresses;

// Copy signing threshold percentage from the old router.
newRouter.validationSettings.signingThresholdPercentage =
oldRouter.validationSettings.signingThresholdPercentage;
_setValidators(newRouter, oldRouter.validationSettings.validators);

// Copy validators from the old router.
// TODO (gsobol): consider what to do. Maybe we should start reelection process.
// Skipping validators1 copying - means we forget election results
// if an election is already done for the next era.
_resetValidators(
newRouter.validationSettings.validators0, Gear.currentEraValidators(oldRouter).list, block.timestamp
);

// Copy computation settings from the old router.
newRouter.computeSettings = oldRouter.computeSettings;

// Copy timelines from the old router.
newRouter.timelines = oldRouter.timelines;

// All protocol data must be removed - so leave it zeroed in new router.
}

// # Views.
function genesisBlockHash() public view returns (bytes32) {
return _router().genesisBlock.hash;
}

function genesisTimestamp() public view returns (uint48) {
return _router().genesisBlock.timestamp;
}

function latestCommittedBlockHash() public view returns (bytes32) {
return _router().latestCommittedBlock.hash;
}
Expand All @@ -77,10 +111,10 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
}

function areValidators(address[] calldata _validators) public view returns (bool) {
Storage storage router = _router();
Gear.Validators storage _currentValidators = Gear.currentEraValidators(_router());

for (uint256 i = 0; i < _validators.length; i++) {
if (!router.validationSettings.validatorsKeyMap[_validators[i]]) {
if (!_currentValidators.map[_validators[i]]) {
return false;
}
}
Expand All @@ -89,23 +123,26 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
}

function isValidator(address _validator) public view returns (bool) {
return _router().validationSettings.validatorsKeyMap[_validator];
return Gear.currentEraValidators(_router()).map[_validator];
}

function signingThresholdPercentage() public view returns (uint16) {
return _router().validationSettings.signingThresholdPercentage;
}

function validators() public view returns (address[] memory) {
return _router().validationSettings.validators;
return Gear.currentEraValidators(_router()).list;
}

function validatorsCount() public view returns (uint256) {
return _router().validationSettings.validators.length;
return Gear.currentEraValidators(_router()).list.length;
}

function validatorsThreshold() public view returns (uint256) {
return Gear.validatorsThresholdOf(_router().validationSettings);
IRouter.Storage storage router = _router();
return Gear.validatorsThreshold(
Gear.currentEraValidators(router).list.length, router.validationSettings.signingThresholdPercentage
);
}

function computeSettings() public view returns (Gear.ComputationSettings memory) {
Expand Down Expand Up @@ -207,6 +244,33 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
}

// # Validators calls.

/// @dev Set validators for the next era.
function commitValidators(Gear.ValidatorsCommitment calldata commitment, bytes[] calldata signatures) external {
Storage storage router = _router();

uint256 currentEraIndex = (block.timestamp - router.genesisBlock.timestamp) / router.timelines.era;

require(commitment.eraIndex == currentEraIndex + 1, "commitment era index is not next era index");

uint256 nextEraStart = router.genesisBlock.timestamp + router.timelines.era * commitment.eraIndex;
require(block.timestamp >= nextEraStart - router.timelines.election, "election is not yet started");

// Maybe free slot for new validators:
Gear.Validators storage _validators = Gear.previousEraValidators(router);
require(_validators.useFromTimestamp < block.timestamp, "looks like validators for next era are already set");

bytes32 commitmentHash = Gear.validatorsCommitmentHash(commitment);
require(
Gear.validateSignatures(router, keccak256(abi.encodePacked(commitmentHash)), signatures),
"next era validators signatures verification failed"
);

_resetValidators(_validators, commitment.validators, nextEraStart);

emit NextEraValidatorsCommitted(nextEraStart);
}

function commitCodes(Gear.CodeCommitment[] calldata _codeCommitments, bytes[] calldata _signatures) external {
Storage storage router = _router();
require(router.genesisBlock.hash != bytes32(0), "router genesis is zero; call `lookupGenesisHash()` first");
Expand Down Expand Up @@ -343,22 +407,21 @@ contract Router is IRouter, OwnableUpgradeable, ReentrancyGuardTransient {
return keccak256(transitionsHashes);
}

function _setValidators(Storage storage router, address[] memory _validators) private {
require(router.validationSettings.validators.length == 0, "remove previous validators first");

for (uint256 i = 0; i < _validators.length; i++) {
router.validationSettings.validatorsKeyMap[_validators[i]] = true;
function _resetValidators(
Gear.Validators storage _validators,
address[] memory _newValidators,
uint256 _useFromTimestamp
) private {
for (uint256 i = 0; i < _validators.list.length; i++) {
address _validator = _validators.list[i];
_validators.map[_validator] = false;
}

router.validationSettings.validators = _validators;
}

function _removeValidators(Storage storage router) private {
for (uint256 i = 0; i < router.validationSettings.validators.length; i++) {
delete router.validationSettings.validatorsKeyMap[router.validationSettings.validators[i]];
for (uint256 i = 0; i < _newValidators.length; i++) {
address _validator = _newValidators[i];
_validators.map[_validator] = true;
}

delete router.validationSettings.validators;
_validators.list = _newValidators;
_validators.useFromTimestamp = _useFromTimestamp;
}

function _router() private view returns (Storage storage router) {
Expand Down
Loading

0 comments on commit d7a3924

Please sign in to comment.