-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'unstable' into lore/feat/holesky-launch
- Loading branch information
Showing
11 changed files
with
316 additions
and
14 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
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,192 @@ | ||
use std::str::FromStr; | ||
|
||
use alloy::{ | ||
contract::Error as ContractError, | ||
primitives::{Address, Bytes}, | ||
providers::{ProviderBuilder, RootProvider}, | ||
sol, | ||
sol_types::SolInterface, | ||
transports::{http::Http, TransportError}, | ||
}; | ||
use ethereum_consensus::primitives::BlsPublicKey; | ||
use eyre::bail; | ||
use reqwest::{Client, Url}; | ||
use serde::Serialize; | ||
|
||
use BoltManagerContract::{BoltManagerContractErrors, BoltManagerContractInstance, ProposerStatus}; | ||
|
||
use crate::config::chain::Chain; | ||
|
||
use super::utils::{self, CompressedHash}; | ||
|
||
/// A wrapper over a BoltManagerContract that exposes various utility methods. | ||
#[derive(Debug, Clone)] | ||
pub struct BoltManager(BoltManagerContractInstance<Http<Client>, RootProvider<Http<Client>>>); | ||
|
||
impl BoltManager { | ||
/// Creates a new BoltRegistry instance. Returns `None` if a canonical BoltManager contract is | ||
/// not deployed on such chain. | ||
/// | ||
/// TODO: change after https://github.com/chainbound/bolt/issues/343 is completed | ||
pub fn from_chain<U: Into<Url>>(execution_client_url: U, chain: Chain) -> Option<Self> { | ||
let address = chain.manager_address()?; | ||
Some(Self::from_address(execution_client_url, address)) | ||
} | ||
|
||
/// Creates a new BoltRegistry instance. | ||
pub fn from_address<U: Into<Url>>(execution_client_url: U, manager_address: Address) -> Self { | ||
let provider = ProviderBuilder::new().on_http(execution_client_url.into()); | ||
let registry = BoltManagerContract::new(manager_address, provider); | ||
|
||
Self(registry) | ||
} | ||
|
||
/// Verify the provided validator public keys are registered in Bolt and are active | ||
/// and their authorized operator is the given commitment signer public key. | ||
/// | ||
/// NOTE: it also checks the operator associated to the `commitment_signer_pubkey` exists. | ||
pub async fn verify_validator_pubkeys( | ||
&self, | ||
keys: &[BlsPublicKey], | ||
commitment_signer_pubkey: Address, | ||
) -> eyre::Result<Vec<ProposerStatus>> { | ||
let hashes = utils::pubkey_hashes(keys); | ||
|
||
let returndata = self.0.getProposerStatuses(hashes).call().await; | ||
|
||
// TODO: clean this after https://github.com/alloy-rs/alloy/issues/787 is merged | ||
let error = match returndata.map(|data| data.statuses) { | ||
Ok(statuses) => { | ||
for status in &statuses { | ||
if !status.active { | ||
bail!( | ||
"validator with public key hash {:?} is not active in Bolt", | ||
status.pubkeyHash | ||
); | ||
} else if status.operator != commitment_signer_pubkey { | ||
bail!(generate_operator_keys_mismatch_error( | ||
status.pubkeyHash, | ||
commitment_signer_pubkey, | ||
status.operator | ||
)); | ||
} | ||
} | ||
|
||
return Ok(statuses); | ||
} | ||
Err(error) => match error { | ||
ContractError::TransportError(TransportError::ErrorResp(err)) => { | ||
let data = err.data.unwrap_or_default(); | ||
let data = data.get().trim_matches('"'); | ||
let data = Bytes::from_str(data)?; | ||
|
||
BoltManagerContractErrors::abi_decode(&data, true)? | ||
} | ||
e => return Err(e)?, | ||
}, | ||
}; | ||
|
||
match error { | ||
BoltManagerContractErrors::ValidatorDoesNotExist(pubkey_hash) => { | ||
bail!("validator with public key hash {:?} is not registered in Bolt", pubkey_hash); | ||
} | ||
BoltManagerContractErrors::InvalidQuery(_) => { | ||
bail!("invalid zero public key hash"); | ||
} | ||
BoltManagerContractErrors::KeyNotFound(_) => { | ||
bail!("operator associated with commitment signer public key {:?} is not registered in Bolt", commitment_signer_pubkey); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn generate_operator_keys_mismatch_error( | ||
pubkey_hash: CompressedHash, | ||
commitment_signer_pubkey: Address, | ||
operator: Address, | ||
) -> String { | ||
format!( | ||
"mismatch between commitment signer public key and authorized operator address for validator with public key hash {:?} in Bolt.\n - commitment signer public key: {:?}\n - authorized operator address: {:?}", | ||
pubkey_hash, | ||
commitment_signer_pubkey, | ||
operator | ||
) | ||
} | ||
|
||
sol! { | ||
#[allow(missing_docs)] | ||
#[sol(rpc)] | ||
interface BoltManagerContract { | ||
#[derive(Debug, Default, Serialize)] | ||
struct ProposerStatus { | ||
bytes20 pubkeyHash; | ||
bool active; | ||
address operator; | ||
string operatorRPC; | ||
address[] collaterals; | ||
uint256[] amounts; | ||
} | ||
|
||
function getProposerStatuses(bytes20[] calldata pubkeyHashes) public view returns (ProposerStatus[] memory statuses); | ||
|
||
function isOperator(address operator) external view returns (bool isOperator); | ||
|
||
error KeyNotFound(); | ||
error InvalidQuery(); | ||
#[derive(Debug)] | ||
error ValidatorDoesNotExist(bytes20 pubkeyHash); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use ::hex::FromHex; | ||
use alloy::{hex, primitives::Address}; | ||
use ethereum_consensus::primitives::BlsPublicKey; | ||
use reqwest::Url; | ||
|
||
use crate::{ | ||
chain_io::{manager::generate_operator_keys_mismatch_error, utils::pubkey_hash}, | ||
config::chain::Chain, | ||
}; | ||
|
||
use super::BoltManager; | ||
|
||
#[tokio::test] | ||
#[ignore = "requires Chainbound tailnet"] | ||
async fn test_verify_validator_pubkeys() { | ||
let url = Url::parse("http://remotebeast:48545").expect("valid url"); | ||
let manager = | ||
BoltManager::from_chain(url, Chain::Holesky).expect("manager deployed on Holesky"); | ||
|
||
let operator = | ||
Address::from_hex("725028b0b7c3db8b8242d35cd3a5779838b217b1").expect("valid address"); | ||
|
||
let keys = vec![BlsPublicKey::try_from([0; 48].as_ref()).expect("valid bls public key")]; | ||
let commitment_signer_pubkey = Address::ZERO; | ||
|
||
let res = manager.verify_validator_pubkeys(&keys, commitment_signer_pubkey).await; | ||
assert!(res.unwrap_err().to_string().contains("ValidatorDoesNotExist")); | ||
|
||
let keys = vec![ | ||
BlsPublicKey::try_from( | ||
hex!("87cbbfe6f08a0fd424507726cfcf5b9df2b2fd6b78a65a3d7bb6db946dca3102eb8abae32847d5a9a27e414888414c26") | ||
.as_ref()).expect("valid bls public key")]; | ||
let res = manager.verify_validator_pubkeys(&keys, commitment_signer_pubkey).await; | ||
assert!( | ||
res.unwrap_err().to_string() | ||
== generate_operator_keys_mismatch_error( | ||
pubkey_hash(&keys[0]), | ||
commitment_signer_pubkey, | ||
operator | ||
) | ||
); | ||
|
||
let commitment_signer_pubkey = operator; | ||
let res = manager | ||
.verify_validator_pubkeys(&keys, commitment_signer_pubkey) | ||
.await | ||
.expect("active validator and correct operator"); | ||
assert!(res[0].active); | ||
} | ||
} |
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,5 @@ | ||
/// Wrapper over the BoltManager contract | ||
pub mod manager; | ||
|
||
/// Utilities and functions used in the Bolt contracts | ||
pub mod utils; |
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,52 @@ | ||
use alloy::primitives::{FixedBytes, B512}; | ||
use ethereum_consensus::primitives::BlsPublicKey; | ||
use reth_primitives::keccak256; | ||
|
||
/// A 20-byte compressed hash of a BLS public key. | ||
/// | ||
/// Reference: https://github.com/chainbound/bolt/blob/bec46baae6d7c16dddd81e5e72710ca8e3064f82/bolt-contracts/script/holesky/validators/RegisterValidators.s.sol#L65-L69 | ||
pub(crate) type CompressedHash = FixedBytes<20>; | ||
|
||
/// Hash the public keys of the proposers. This follows the same | ||
/// implementation done on-chain in the BoltValidators contract. | ||
pub fn pubkey_hashes(keys: &[BlsPublicKey]) -> Vec<CompressedHash> { | ||
keys.iter().map(pubkey_hash).collect() | ||
} | ||
|
||
/// Hash the public key of the proposer. This follows the same | ||
/// implementation done on-chain in the BoltValidators contract. | ||
/// | ||
/// Reference: https://github.com/chainbound/bolt/blob/bec46baae6d7c16dddd81e5e72710ca8e3064f82/bolt-contracts/script/holesky/validators/RegisterValidators.s.sol#L65-L69 | ||
pub fn pubkey_hash(key: &BlsPublicKey) -> CompressedHash { | ||
let digest = pubkey_hash_digest(key); | ||
let hash = keccak256(digest); | ||
CompressedHash::from_slice(hash.get(0..20).expect("hash is longer than 20 bytes")) | ||
} | ||
|
||
fn pubkey_hash_digest(key: &BlsPublicKey) -> B512 { | ||
let mut onchain_pubkey_repr = B512::ZERO; | ||
|
||
// copy the pubkey bytes into the rightmost 48 bytes of the 512-bit buffer. | ||
// the result should look like this: | ||
// | ||
// 0x00000000000000000000000000000000b427fd179b35ef085409e4a98fb3ab84ee29c689df5c64020eab0b20a4f91170f610177db172dc091682df627c9f4021 | ||
// |<---------- 16 bytes ---------->||<----------------------------------------- 48 bytes ----------------------------------------->| | ||
onchain_pubkey_repr[16..].copy_from_slice(key); | ||
onchain_pubkey_repr | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use alloy::hex; | ||
use ethereum_consensus::primitives::BlsPublicKey; | ||
|
||
use super::pubkey_hash; | ||
|
||
#[test] | ||
fn test_public_key_hash() { | ||
let bytes = hex!("87cbbfe6f08a0fd424507726cfcf5b9df2b2fd6b78a65a3d7bb6db946dca3102eb8abae32847d5a9a27e414888414c26").as_ref(); | ||
let bls_public_key = BlsPublicKey::try_from(bytes).expect("valid bls public key"); | ||
let hash = pubkey_hash(&bls_public_key); | ||
assert_eq!(hex::encode(hash.as_slice()), "cf44d8bca49d695164be6796108cf788d8d056e1"); | ||
} | ||
} |
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
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
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
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
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.