Skip to content

Commit 19ec00e

Browse files
committed
feat: implement some of the cluster config function, ENR functionality, k1util, RLP, p2p.name etc.
1 parent d0d603f commit 19ec00e

20 files changed

Lines changed: 6437 additions & 461 deletions

File tree

Cargo.lock

Lines changed: 4183 additions & 328 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,20 @@ prost = "0.14"
3838
prost-types = "0.14"
3939
prost-build = "0.14"
4040
rand = {version = "0.8", features = ["std_rng"]}
41-
uuid = {version = "1.16", features = ["serde"] }
41+
uuid = {version = "1.16", features = ["serde", "v4"] }
4242
serde_with = { version = "3", features = ["hex", "base64"] }
43+
libp2p = { version = "0.56", features = ["full", "secp256k1"] }
44+
base64 = { version = "0.22.1" }
45+
sha3 = { version = "0.10.8" }
46+
k256 = { version = "0.13.4", features = ["ecdsa", "sha256"] }
47+
criterion = "0.7.0"
48+
49+
# Crates in the workspace
50+
charon = { path = "crates/charon" }
4351
charon-crypto = { path = "crates/charon-crypto" }
52+
charon-eth2 = { path = "crates/charon-eth2" }
53+
charon-p2p = { path = "crates/charon-p2p" }
54+
charon-testutil = { path = "crates/charon-testutil" }
4455

4556
[workspace.lints.rust]
4657
missing_docs = "deny"

crates/charon-cluster/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ hex.workspace = true
1717
serde_with.workspace = true
1818
charon-crypto.workspace = true
1919
thiserror.workspace = true
20+
rand_core.workspace = true
21+
libp2p.workspace = true
22+
charon-p2p.workspace = true
23+
charon-eth2.workspace = true
2024

2125
[build-dependencies]
2226
prost-build.workspace = true

crates/charon-cluster/src/definition.rs

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
use std::collections::HashSet;
2+
13
use crate::{
2-
helpers::EthHex,
4+
helpers::{EthHex, from_0x_hex_str},
35
operator::{Operator, OperatorV1X1, OperatorV1X2OrLater},
4-
version::versions::*,
6+
version::{CURRENT_VERSION, DKG_ALGO, versions::*},
57
};
8+
use charon_eth2::enr::{Record, RecordError};
9+
use charon_p2p::peer::{Peer, PeerError};
610
use chrono::{DateTime, Utc};
11+
use libp2p::PeerId;
712
use serde::{Deserialize, Deserializer, Serialize, Serializer};
813
use serde_with::{
914
DisplayFromStr, PickFirst,
@@ -12,6 +17,22 @@ use serde_with::{
1217
};
1318
use uuid::Uuid;
1419

20+
/// Length of the fork version in bytes.
21+
pub const FORK_VERSION_LEN: usize = 4;
22+
23+
/// Length of the address in bytes.
24+
pub const ADDRESS_LEN: usize = 20;
25+
26+
/// NodeIdx represents the index of a node/peer/share in the cluster as operator
27+
/// order in cluster definition.
28+
#[derive(Debug, Clone, PartialEq, Eq)]
29+
pub struct NodeIdx {
30+
/// Index of a peer in the peer list (it's 0-indexed).
31+
pub peer_idx: usize,
32+
/// tbls share identifier (it is 1-indexed).
33+
pub share_idx: usize,
34+
}
35+
1536
/// Definition defines an intended charon cluster configuration excluding
1637
/// validators.
1738
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -159,9 +180,198 @@ pub enum DefinitionError {
159180
/// are found.
160181
#[error("Multiple withdrawal or fee recipient addresses found")]
161182
InvalidValidatorAddresses,
183+
184+
/// Insufficient fee-recipient addresses
185+
#[error("Insufficient fee-recipient addresses")]
186+
InsufficientFeeRecipientAddresses,
187+
188+
/// Insufficient withdrawal addresses
189+
#[error("Insufficient withdrawal addresses")]
190+
InsufficientWithdrawalAddresses,
191+
192+
/// Failed to convert length
193+
#[error("Failed to convert length")]
194+
FailedToConvertLength,
195+
196+
/// Failed to convert hex string
197+
#[error("Failed to convert hex string")]
198+
FailedToConvertHexString(#[from] hex::FromHexError),
199+
200+
/// Invalid target gas limit
201+
#[error("Invalid target gas limit: {0}")]
202+
InvalidTargetGasLimit(&'static str),
203+
204+
/// Invalid deposit amounts
205+
#[error("Invalid deposit amounts: {0}")]
206+
InvalidDepositAmounts(&'static str),
207+
208+
/// Invalid compounding
209+
#[error("Invalid deposit amounts: {0}")]
210+
InvalidCompounding(&'static str),
211+
212+
/// Invalid gas limit
213+
#[error("Invalid gas limit: {0}")]
214+
InvalidGasLimit(&'static str),
215+
216+
/// Peer not found
217+
#[error("Peer not in definition: {0}")]
218+
PeerNotFound(String),
219+
220+
/// Duplicate peer ENRs
221+
#[error("Duplicate peer ENRs: {0}")]
222+
DuplicatePeerENRs(String),
223+
224+
/// Failed to parse ENR
225+
#[error("Failed to parse ENR: {0}")]
226+
FailedToParseENR(#[from] RecordError),
227+
228+
/// Failed to create peer
229+
#[error("Failed to create peer: {0}")]
230+
FailedToCreatePeer(#[from] PeerError),
162231
}
163232

164233
impl Definition {
234+
/// Create a new cluster definition.
235+
#[allow(clippy::too_many_arguments)]
236+
pub fn new(
237+
name: String,
238+
num_validators: u64,
239+
threshold: u64,
240+
fee_recipient_addresses: Vec<String>,
241+
withdrawal_addresses: Vec<String>,
242+
fork_version_hex: String,
243+
creator: Creator,
244+
operators: Vec<Operator>,
245+
deposit_amounts: Vec<u64>,
246+
consensus_protocol: String,
247+
target_gas_limit: u64,
248+
compounding: bool,
249+
opts: Vec<fn(&mut Self) -> Self>,
250+
) -> Result<Self, DefinitionError> {
251+
if u64::try_from(fee_recipient_addresses.len())
252+
.map_err(|_| DefinitionError::FailedToConvertLength)?
253+
!= num_validators
254+
{
255+
return Err(DefinitionError::InsufficientFeeRecipientAddresses);
256+
}
257+
258+
if u64::try_from(withdrawal_addresses.len())
259+
.map_err(|_| DefinitionError::FailedToConvertLength)?
260+
!= num_validators
261+
{
262+
return Err(DefinitionError::InsufficientWithdrawalAddresses);
263+
}
264+
265+
let uuid = Uuid::new_v4();
266+
267+
let mut def = Definition {
268+
uuid,
269+
name,
270+
version: CURRENT_VERSION.to_string(),
271+
timestamp: Utc::now(),
272+
num_validators,
273+
threshold,
274+
dkg_algorithm: DKG_ALGO.to_string(),
275+
fork_version: Default::default(),
276+
operators,
277+
creator,
278+
validator_addresses: Vec::new(),
279+
deposit_amounts,
280+
consensus_protocol,
281+
target_gas_limit,
282+
compounding,
283+
config_hash: Default::default(),
284+
definition_hash: Default::default(),
285+
};
286+
287+
def.validator_addresses = fee_recipient_addresses
288+
.into_iter()
289+
.enumerate()
290+
.map(|(i, address)| ValidatorAddresses {
291+
fee_recipient_address: address,
292+
withdrawal_address: withdrawal_addresses[i].clone(),
293+
})
294+
.collect();
295+
296+
def.fork_version = from_0x_hex_str(&fork_version_hex, FORK_VERSION_LEN)?;
297+
298+
for opt in opts {
299+
opt(&mut def);
300+
}
301+
302+
if def.deposit_amounts.len() > 1 && !Self::support_partial_deposits(&def.version) {
303+
return Err(DefinitionError::InvalidDepositAmounts(
304+
"the version does not support partial deposits",
305+
));
306+
}
307+
308+
if def.target_gas_limit != 0 && !Self::support_target_gas_limit(&def.version) {
309+
return Err(DefinitionError::InvalidTargetGasLimit(
310+
"the version does not support custom target gas limit",
311+
));
312+
}
313+
314+
if def.compounding && !Self::support_compounding(&def.version) {
315+
return Err(DefinitionError::InvalidCompounding(
316+
"the version does not support compounding",
317+
));
318+
}
319+
320+
if def.target_gas_limit == 0 && Self::support_target_gas_limit(&def.version) {
321+
return Err(DefinitionError::InvalidTargetGasLimit(
322+
"target gas limit should be set",
323+
));
324+
}
325+
326+
// TODO: Construct and return a Definition. Placeholder for now.
327+
def.set_definition_hashes()
328+
}
329+
330+
/// Sets the definition hashes.
331+
pub fn set_definition_hashes(self) -> Result<Self, DefinitionError> {
332+
// todo
333+
Ok(self)
334+
}
335+
336+
/// Returns the node index for a given peer ID.
337+
pub fn node_idx(&self, pid: &PeerId) -> Result<NodeIdx, DefinitionError> {
338+
let peers = self.peers()?;
339+
340+
for (i, peer) in peers.iter().enumerate() {
341+
if peer.id == *pid {
342+
return Ok(NodeIdx {
343+
peer_idx: i,
344+
share_idx: peer.share_idx(),
345+
});
346+
}
347+
}
348+
349+
Err(DefinitionError::PeerNotFound(pid.to_string()))
350+
}
351+
352+
/// Returns the peers in the cluster.
353+
pub fn peers(&self) -> Result<Vec<Peer>, DefinitionError> {
354+
let mut peers = Vec::new();
355+
356+
let mut dedup: HashSet<String> = HashSet::new();
357+
358+
for (i, operator) in self.operators.iter().enumerate() {
359+
if dedup.contains(&operator.enr) {
360+
return Err(DefinitionError::DuplicatePeerENRs(operator.enr.clone()));
361+
}
362+
363+
dedup.insert(operator.enr.clone());
364+
365+
let enr = Record::try_from(operator.enr.as_str())?;
366+
367+
let peer = Peer::from_enr(&enr, i)?;
368+
369+
peers.push(peer);
370+
}
371+
372+
Ok(peers)
373+
}
374+
165375
/// Legacy single withdrawal and single
166376
/// fee recipient addresses or an error if multiple addresses are found.
167377
pub fn legacy_validator_addresses(&self) -> Result<ValidatorAddresses, DefinitionError> {
@@ -177,6 +387,39 @@ impl Definition {
177387

178388
Ok(result_validator_addresses)
179389
}
390+
391+
/// Returns true if the provided definition version supports EIP712
392+
/// signatures. Note that Definition versions prior to v1.3.0 don't
393+
/// support EIP712 signatures.
394+
pub fn support_eip712_sigs(version: &str) -> bool {
395+
!matches!(version, V1_0 | V1_1 | V1_2)
396+
}
397+
398+
/// Returns true if the provided definition version supports partial
399+
/// deposits.
400+
pub fn support_partial_deposits(version: &str) -> bool {
401+
!matches!(
402+
version,
403+
V1_0 | V1_1 | V1_2 | V1_3 | V1_4 | V1_5 | V1_6 | V1_7
404+
)
405+
}
406+
407+
/// Returns true if the provided definition version supports custom target
408+
/// gas limit.
409+
pub fn support_target_gas_limit(version: &str) -> bool {
410+
!matches!(
411+
version,
412+
V1_0 | V1_1 | V1_2 | V1_3 | V1_4 | V1_5 | V1_6 | V1_7 | V1_8 | V1_9
413+
)
414+
}
415+
416+
/// Returns true if the provided definition version supports compounding.
417+
pub fn support_compounding(version: &str) -> bool {
418+
!matches!(
419+
version,
420+
V1_0 | V1_1 | V1_2 | V1_3 | V1_4 | V1_5 | V1_6 | V1_7 | V1_8 | V1_9
421+
)
422+
}
180423
}
181424

182425
/// Creator identifies the creator of a cluster definition. They may also be an

crates/charon-cluster/src/helpers.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ impl<'de> DeserializeAs<'de, DateTime<Utc>> for TimestampSeconds {
114114
}
115115
}
116116

117+
/// Converts a 0x prefixed hex string to a byte slice.
118+
pub fn from_0x_hex_str(s: &str, len: usize) -> Result<Vec<u8>, hex::FromHexError> {
119+
let s = s.strip_prefix("0x").unwrap_or(s);
120+
let bytes = hex::decode(s)?;
121+
if bytes.len() != len {
122+
return Err(hex::FromHexError::InvalidStringLength);
123+
}
124+
Ok(bytes)
125+
}
126+
117127
#[cfg(test)]
118128
mod tests {
119129
use super::*;

crates/charon-eth2/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ license.workspace = true
77
publish.workspace = true
88

99
[dependencies]
10+
k256.workspace = true
11+
thiserror.workspace = true
12+
base64.workspace = true
13+
sha3.workspace = true
14+
charon.workspace = true
15+
hex.workspace = true
16+
charon-testutil.workspace = true
1017

1118
[lints]
1219
workspace = true

0 commit comments

Comments
 (0)