-
Notifications
You must be signed in to change notification settings - Fork 322
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
451 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
l1-contracts/src/core/sequencer_selection/ConsensusValidator.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
pragma solidity ^0.8.13; | ||
|
||
import {SignatureLib} from "./SignatureLib.sol"; | ||
|
||
library ConsensusValidator { | ||
using SignatureLib for SignatureLib.Signature; | ||
|
||
struct ConsensusContent { | ||
SignatureLib.Signature[] signatures; | ||
address[] validators; | ||
} | ||
|
||
function validateConsensus(ConsensusContent memory _content, bytes32 _digest) | ||
internal | ||
pure | ||
returns (bool) | ||
{ | ||
uint256 validAttestations = 0; | ||
for (uint256 i = 0; i < _content.signatures.length; i++) { | ||
SignatureLib.Signature memory signature = _content.signatures[i]; | ||
if (signature.isEmpty) { | ||
continue; | ||
} | ||
|
||
require(signature.validate(_content.validators[i], _digest), "ECDSA: invalid signature"); | ||
validAttestations++; | ||
} | ||
uint256 needed = _content.validators.length * 2 / 3; | ||
|
||
require(validAttestations > needed, "Insufficient signatures"); | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright 2024 Aztec Labs. | ||
pragma solidity >=0.8.18; | ||
|
||
import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; | ||
import {ConsensusValidator} from "./ConsensusValidator.sol"; | ||
import {SignatureLib} from "./SignatureLib.sol"; | ||
|
||
// @todo @LHerskind, This is REALLY bad code, we just throw gas in the air like no-one cares! | ||
// It was planned to use validatorSetCommitments for a snapshot, but the way the full ts | ||
// is made it seems pain to do that, so instead we just go spend all the gas in the world | ||
|
||
contract Leonidas { | ||
using EnumerableSet for EnumerableSet.AddressSet; | ||
using ConsensusValidator for ConsensusValidator.ConsensusContent; | ||
|
||
// For gas reasons we want to just use a validatorSetCommitment | ||
// But the current sequencer setup seems to require the full set for easy integration | ||
// @todo Replace the `validatorSet` with a commitment to the set currently storing the entire set to very easily | ||
// have an accessible stable set that external actors can fetch WITHOUT keeping any storage on their own. | ||
// This is only temporary as we are experimenting because it required fewer changes to get going | ||
struct Epoch { | ||
address[] validatorSet; | ||
bool exists; | ||
} | ||
|
||
// The size/duration of a slot in seconds, multiple of 12 to align with Ethereum blocks | ||
uint256 public constant SLOT_SIZE = 12 * 5; | ||
// The size/duration of an epoch in slots | ||
uint256 public constant EPOCH_SIZE = 32; | ||
|
||
// The time that the contract was deployed | ||
uint256 public immutable GENESIS_TIME; | ||
|
||
// An enumerable set of validators that are up to date | ||
EnumerableSet.AddressSet private validatorSet; | ||
|
||
// A mapping to snapshots of the validator set | ||
mapping(uint256 epochNumber => Epoch epoch) public epochs; | ||
|
||
constructor() { | ||
GENESIS_TIME = block.timestamp; | ||
} | ||
|
||
/** | ||
* @notice Get the validator set for a given epoch | ||
* | ||
* @param _epoch The epoch number to get the validator set for | ||
* @return The validator set for the given epoch | ||
*/ | ||
function getEpochValidatorSet(uint256 _epoch) public view returns (address[] memory) { | ||
return epochs[_epoch].validatorSet; | ||
} | ||
|
||
/** | ||
* @notice Get the current epoch number | ||
* | ||
* @return The current epoch number | ||
*/ | ||
function getCurrentEpoch() public view returns (uint256) { | ||
return (block.timestamp - GENESIS_TIME) / (EPOCH_SIZE * SLOT_SIZE); | ||
} | ||
|
||
/** | ||
* @notice Get the current slot number | ||
* | ||
* @return The current slot number | ||
*/ | ||
function getCurrentSlot() public view returns (uint256) { | ||
return (block.timestamp - GENESIS_TIME) / SLOT_SIZE; | ||
} | ||
|
||
function getCurrentProposer() public view returns (address) { | ||
uint256 epochNumber = getCurrentEpoch(); | ||
Epoch storage epoch = epochs[epochNumber]; | ||
|
||
if (epoch.exists) { | ||
if (epoch.validatorSet.length == 0) { | ||
return address(0); | ||
} | ||
|
||
return epoch.validatorSet[getCurrentSlot() % epoch.validatorSet.length]; | ||
} | ||
|
||
// Allow anyone if there is no validator set | ||
if (validatorSet.length() == 0) { | ||
return address(0); | ||
} | ||
|
||
// If not setup, we "emulate" the setup | ||
address[] memory validators = sampleValidators(epochNumber); | ||
return validators[getCurrentSlot() % validators.length]; | ||
} | ||
|
||
/** | ||
* @dev Start crying because the gas here will melt the poles | ||
* https://i.giphy.com/U1aN4HTfJ2SmgB2BBK.webp | ||
*/ | ||
function setupEpoch() public { | ||
uint256 epochNumber = getCurrentEpoch(); | ||
Epoch storage epoch = epochs[epochNumber]; | ||
if (!epoch.exists) { | ||
epoch.exists = true; | ||
address[] memory validators = sampleValidators(epochNumber); | ||
/*for (uint256 i = 0; i < validators.length; i++) { | ||
epoch.validatorSet.push(validators[i]); | ||
}*/ | ||
epoch.validatorSet = validators; | ||
} | ||
} | ||
|
||
function addValidator(address _validator) public { | ||
setupEpoch(); | ||
validatorSet.add(_validator); | ||
} | ||
|
||
/** | ||
* @dev Remove a validator from the validator set AFTER setting up the epoch if not already | ||
* | ||
* @param _validator The validator to remove | ||
*/ | ||
function removeValidator(address _validator) public { | ||
setupEpoch(); | ||
validatorSet.remove(_validator); | ||
} | ||
|
||
// @todo @LHerskind, Short term, we just keep the validators stable across epochs | ||
function sampleValidators(uint256 _epoch) public view returns (address[] memory) { | ||
return validatorSet.values(); | ||
} | ||
|
||
// HACK: Return all the registered sequencers | ||
function getValidators() external view returns (address[] memory) { | ||
return validatorSet.values(); | ||
} | ||
|
||
function validateConsensus(SignatureLib.Signature[] memory _signatures, bytes32 _digest) | ||
public | ||
view | ||
returns (bool) | ||
{ | ||
Epoch storage epoch = epochs[getCurrentEpoch()]; | ||
require(epoch.exists, "Epoch not setup"); | ||
|
||
// Pass if no validators | ||
if (epoch.validatorSet.length == 0) { | ||
return true; | ||
} | ||
|
||
return ConsensusValidator.ConsensusContent({ | ||
signatures: _signatures, | ||
validators: epoch.validatorSet | ||
}).validateConsensus(_digest); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
l1-contracts/src/core/sequencer_selection/SignatureLib.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
pragma solidity ^0.8.13; | ||
|
||
library SignatureLib { | ||
struct Signature { | ||
bool isEmpty; | ||
uint8 v; | ||
bytes32 r; | ||
bytes32 s; | ||
} | ||
|
||
function validate(Signature memory _signature, address _signer, bytes32 _digest) | ||
internal | ||
pure | ||
returns (bool) | ||
{ | ||
require(!_signature.isEmpty, "Signature is empty"); | ||
require( | ||
_signer == ecrecover(_digest, _signature.v, _signature.r, _signature.s), | ||
"ECDSA: invalid signature" | ||
); | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.