Skip to content

Commit 9ab62a2

Browse files
committed
feat: implement part of ssz-related logics
1 parent 04d7ffc commit 9ab62a2

10 files changed

Lines changed: 2320 additions & 29 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ base64 = { version = "0.22.1" }
4646
sha3 = { version = "0.10.8" }
4747
k256 = { version = "0.13.4", features = ["ecdsa", "sha256"] }
4848
criterion = "0.7.0"
49+
ethereum_ssz = "0.10.0"
50+
ethereum_ssz_derive = "0.10.0"
4951

5052
# Crates in the workspace
5153
charon-crypto = { path = "crates/charon-crypto" }

crates/charon-cluster/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ charon-p2p.workspace = true
2323
charon-eth2.workspace = true
2424
charon-k1util.workspace = true
2525
k256.workspace = true
26+
ethereum_ssz.workspace = true
27+
ethereum_ssz_derive.workspace = true
2628

2729
[build-dependencies]
2830
prost-build.workspace = true

crates/charon-cluster/src/definition.rs

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::HashSet;
22

33
use crate::{
44
helpers::{EthHex, from_0x_hex_str},
5-
operator::{Operator, OperatorV1X1, OperatorV1X2OrLater},
5+
operator::{Operator, OperatorSSZ, OperatorV1X1, OperatorV1X2OrLater},
66
version::{CURRENT_VERSION, DKG_ALGO, versions::*},
77
};
88
use charon_eth2::enr::{Record, RecordError};
@@ -15,6 +15,7 @@ use serde_with::{
1515
base64::{Base64, Standard},
1616
serde_as,
1717
};
18+
use ssz_derive::{Decode, Encode};
1819
use uuid::Uuid;
1920

2021
/// Length of the fork version in bytes.
@@ -463,6 +464,129 @@ impl Definition {
463464
}
464465
}
465466

467+
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
468+
pub(crate) struct DefinitionSSZ {
469+
/// Human-readable random unique identifier. Max 64 chars.
470+
pub uuid: Vec<u8>,
471+
/// Human-readable cosmetic identifier. Max 256 chars.
472+
pub name: Vec<u8>,
473+
/// Schema version of this definition. Max 16 chars.
474+
pub version: Vec<u8>,
475+
/// Human-readable timestamp of this definition. Max 32
476+
/// chars. Note that this was added in v1.1.0, so may be empty for older
477+
/// versions.
478+
pub timestamp: Vec<u8>,
479+
/// Number of DVs to be created in the cluster lock
480+
/// file.
481+
pub num_validators: u64,
482+
/// Threshold required for signature reconstruction. Defaults to safe value
483+
/// for number of nodes/peers.
484+
pub threshold: u64,
485+
/// DKG algorithm to use for key generation. Max 32 chars.
486+
pub dkg_algorithm: Vec<u8>,
487+
/// Cluster's 4 byte beacon chain fork version
488+
/// (network/chain identifier).
489+
pub fork_version: [u8; 4],
490+
/// Charon nodes in the cluster and their operators.
491+
/// Max 256 operators.
492+
pub operators: Vec<OperatorSSZ>,
493+
/// Creator identifies the creator of a cluster definition. They may also be
494+
/// an operator.
495+
pub creator: CreatorSSZ,
496+
/// Addresses of each validator.
497+
pub validator_addresses: Vec<ValidatorAddressesSSZ>,
498+
/// Partial deposit amounts that sum up to at least
499+
/// 32ETH.
500+
/// todo: check type
501+
pub deposit_amounts: Vec<u64>,
502+
/// Consensus protocol name preferred by the
503+
/// cluster, e.g. "abft".
504+
pub consensus_protocol: Vec<u8>,
505+
/// Target block gas limit for the cluster.
506+
pub target_gas_limit: u64,
507+
/// Compounding flag enables compounding rewards for validators by using
508+
/// 0x02 withdrawal credentials.
509+
pub compounding: bool,
510+
/// Config hash uniquely identifies a cluster definition excluding operator
511+
/// ENRs and signatures.
512+
pub config_hash: [u8; 32],
513+
/// Definition hash uniquely identifies a cluster definition including
514+
/// operator ENRs and signatures.
515+
pub definition_hash: [u8; 32],
516+
}
517+
518+
impl From<Definition> for DefinitionSSZ {
519+
fn from(definition: Definition) -> Self {
520+
Self {
521+
uuid: definition.uuid.to_string().as_bytes().to_vec(),
522+
name: definition.name.as_bytes().to_vec(),
523+
version: definition.version.as_bytes().to_vec(),
524+
timestamp: definition.timestamp.timestamp().to_be_bytes().to_vec(),
525+
num_validators: definition.num_validators,
526+
threshold: definition.threshold,
527+
dkg_algorithm: definition.dkg_algorithm.as_bytes().to_vec(),
528+
fork_version: definition.fork_version.try_into().unwrap(),
529+
operators: definition
530+
.operators
531+
.into_iter()
532+
.map(OperatorSSZ::from)
533+
.collect(),
534+
creator: CreatorSSZ::from(definition.creator),
535+
validator_addresses: definition
536+
.validator_addresses
537+
.into_iter()
538+
.map(ValidatorAddressesSSZ::from)
539+
.collect(),
540+
deposit_amounts: definition.deposit_amounts,
541+
consensus_protocol: definition.consensus_protocol.as_bytes().to_vec(),
542+
target_gas_limit: definition.target_gas_limit,
543+
compounding: definition.compounding,
544+
config_hash: definition.config_hash.try_into().unwrap(),
545+
definition_hash: definition.definition_hash.try_into().unwrap(),
546+
}
547+
}
548+
}
549+
550+
impl TryFrom<DefinitionSSZ> for Definition {
551+
type Error = DefinitionError;
552+
553+
fn try_from(definition: DefinitionSSZ) -> Result<Self, Self::Error> {
554+
Ok(Self {
555+
uuid: Uuid::from_slice(&definition.uuid).unwrap(),
556+
name: String::from_utf8(definition.name).unwrap(),
557+
version: String::from_utf8(definition.version).unwrap(),
558+
timestamp: DateTime::from_timestamp(
559+
i64::from_be_bytes(definition.timestamp.try_into().unwrap()),
560+
0,
561+
)
562+
.unwrap(),
563+
num_validators: definition.num_validators,
564+
threshold: definition.threshold,
565+
dkg_algorithm: String::from_utf8(definition.dkg_algorithm).unwrap(),
566+
fork_version: definition.fork_version.to_vec(),
567+
operators: definition
568+
.operators
569+
.into_iter()
570+
.map(Operator::try_from)
571+
.collect::<Result<Vec<Operator>, DefinitionError>>()
572+
.unwrap(),
573+
creator: Creator::try_from(definition.creator).unwrap(),
574+
validator_addresses: definition
575+
.validator_addresses
576+
.into_iter()
577+
.map(ValidatorAddresses::try_from)
578+
.collect::<Result<Vec<ValidatorAddresses>, DefinitionError>>()
579+
.unwrap(),
580+
deposit_amounts: definition.deposit_amounts,
581+
consensus_protocol: String::from_utf8(definition.consensus_protocol).unwrap(),
582+
target_gas_limit: definition.target_gas_limit,
583+
compounding: definition.compounding,
584+
config_hash: definition.config_hash.to_vec(),
585+
definition_hash: definition.definition_hash.to_vec(),
586+
})
587+
}
588+
}
589+
466590
/// Creator identifies the creator of a cluster definition. They may also be an
467591
/// operator.
468592
#[serde_as]
@@ -475,6 +599,34 @@ pub struct Creator {
475599
pub config_signature: Vec<u8>,
476600
}
477601

602+
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
603+
pub(crate) struct CreatorSSZ {
604+
/// The Ethereum address of the creator
605+
pub address: [u8; 20],
606+
/// The creator's signature over the config hash
607+
pub config_signature: [u8; 65],
608+
}
609+
610+
impl From<Creator> for CreatorSSZ {
611+
fn from(creator: Creator) -> Self {
612+
Self {
613+
address: creator.address.as_bytes().to_vec().try_into().unwrap(),
614+
config_signature: creator.config_signature.try_into().unwrap(),
615+
}
616+
}
617+
}
618+
619+
impl TryFrom<CreatorSSZ> for Creator {
620+
type Error = DefinitionError;
621+
622+
fn try_from(creator: CreatorSSZ) -> Result<Self, Self::Error> {
623+
Ok(Self {
624+
address: String::from_utf8(creator.address.to_vec()).unwrap(),
625+
config_signature: creator.config_signature.to_vec(),
626+
})
627+
}
628+
}
629+
478630
/// Addresses for a validator
479631
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
480632
pub struct ValidatorAddresses {
@@ -484,6 +636,48 @@ pub struct ValidatorAddresses {
484636
pub withdrawal_address: String,
485637
}
486638

639+
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
640+
pub(crate) struct ValidatorAddressesSSZ {
641+
/// The fee recipient address for the validator
642+
pub fee_recipient_address: [u8; 20],
643+
/// The withdrawal address for the validator
644+
pub withdrawal_address: [u8; 20],
645+
}
646+
647+
impl From<ValidatorAddresses> for ValidatorAddressesSSZ {
648+
fn from(validator_addresses: ValidatorAddresses) -> Self {
649+
Self {
650+
fee_recipient_address: validator_addresses
651+
.fee_recipient_address
652+
.as_bytes()
653+
.to_vec()
654+
.try_into()
655+
.unwrap(),
656+
withdrawal_address: validator_addresses
657+
.withdrawal_address
658+
.as_bytes()
659+
.to_vec()
660+
.try_into()
661+
.unwrap(),
662+
}
663+
}
664+
}
665+
666+
impl TryFrom<ValidatorAddressesSSZ> for ValidatorAddresses {
667+
type Error = DefinitionError;
668+
669+
fn try_from(validator_addresses: ValidatorAddressesSSZ) -> Result<Self, Self::Error> {
670+
Ok(Self {
671+
fee_recipient_address: String::from_utf8(
672+
validator_addresses.fee_recipient_address.to_vec(),
673+
)
674+
.unwrap(),
675+
withdrawal_address: String::from_utf8(validator_addresses.withdrawal_address.to_vec())
676+
.unwrap(),
677+
})
678+
}
679+
}
680+
487681
/// DefinitionV1x0or1 is a cluster definition for version 1.0.0 or 1.1.0
488682
#[serde_as]
489683
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

crates/charon-cluster/src/helpers.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
33
use serde_with::{DeserializeAs, SerializeAs};
44
use std::borrow::Cow;
55

6+
use crate::{definition::ADDRESS_LEN, ssz::SSZError, ssz_hasher::HashWalker};
7+
68
/// EthHex represents byte slices that are json formatted as 0x prefixed hex.
79
/// Can be used both as a standalone type and with serde_as.
810
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -124,6 +126,75 @@ pub fn from_0x_hex_str(s: &str, len: usize) -> Result<Vec<u8>, hex::FromHexError
124126
Ok(bytes)
125127
}
126128

129+
/// `put_byte_list` appends a ssz byte list.
130+
/// See reference:
131+
/// github.com/attestantio/go-eth2-client/spec/bellatrix/
132+
/// executionpayload_encoding.go:277-284.
133+
pub fn put_byte_list<H: HashWalker>(
134+
hh: &mut H,
135+
bytes: &[u8],
136+
limit: usize,
137+
field: &str,
138+
) -> Result<(), SSZError<H>> {
139+
let elem_indx = hh.index();
140+
141+
let byte_len = bytes.len();
142+
143+
if byte_len > limit {
144+
return Err(SSZError::<H>::IncorrectListSize {
145+
namespace: "put_byte_list",
146+
field: field.to_string(),
147+
actual: byte_len,
148+
expected: limit,
149+
});
150+
}
151+
152+
let mut bytes32 = [0; 32];
153+
bytes32[..].copy_from_slice(bytes);
154+
155+
hh.append_bytes32(&bytes32)
156+
.map_err(SSZError::<H>::HashWalkerError)?;
157+
158+
hh.merkleize_with_mixin(elem_indx, byte_len, (limit + 31) / 32)
159+
.map_err(SSZError::<H>::HashWalkerError)?;
160+
161+
Ok(())
162+
}
163+
164+
/// `put_bytes_n` appends b as a ssz fixed size byte array of length n.
165+
pub fn put_bytes_n<H: HashWalker>(hh: &mut H, bytes: &[u8], n: usize) -> Result<(), SSZError<H>> {
166+
if bytes.len() > n {
167+
return Err(SSZError::<H>::IncorrectListSize {
168+
namespace: "put_bytes_n",
169+
field: "".to_string(),
170+
actual: bytes.len(),
171+
expected: n,
172+
});
173+
}
174+
175+
hh.put_bytes(&left_pad(bytes, n))
176+
.map_err(SSZError::<H>::HashWalkerError)?;
177+
178+
Ok(())
179+
}
180+
181+
/// `put_hex_bytes_20` appends a 20 byte fixed size byte ssz array from the
182+
/// 0xhex address.
183+
pub fn put_hex_bytes_20<H: HashWalker>(hh: &mut H, address: &str) -> Result<(), SSZError<H>> {
184+
let bytes = from_0x_hex_str(address, ADDRESS_LEN)?;
185+
hh.put_bytes(&left_pad(&bytes, ADDRESS_LEN))
186+
.map_err(SSZError::<H>::HashWalkerError)?;
187+
Ok(())
188+
}
189+
190+
/// `left_pad` returns the byte slice left padded with zero to ensure a length
191+
/// of at least len.
192+
pub fn left_pad(bytes: &[u8], len: usize) -> Vec<u8> {
193+
let mut padded = vec![0; len];
194+
padded[len - bytes.len()..].copy_from_slice(&bytes);
195+
padded
196+
}
197+
127198
#[cfg(test)]
128199
mod tests {
129200
use super::*;

crates/charon-cluster/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub mod operator;
2626
pub mod registration;
2727
/// Cluster SSZ management and coordination.
2828
pub mod ssz;
29+
/// Cluster SSZ hashing management and coordination.
30+
pub mod ssz_hasher;
2931
/// Cluster test cluster management and coordination.
3032
pub mod test_cluster;
3133
/// Cluster version management and coordination.

0 commit comments

Comments
 (0)