diff --git a/src/consensus_client.rs b/src/consensus_client.rs index ee7f9ba..8ab00b7 100644 --- a/src/consensus_client.rs +++ b/src/consensus_client.rs @@ -29,10 +29,128 @@ impl ConsensusClient { let res = reqwest::get(req).await?.json::().await?; Ok(res.data) } + + pub async fn get_block(&self, slot: u64) -> Result{ + let req = format!("{}/eth/v2/beacon/blocks/{}", self.rpc, slot); + let res = reqwest::get(req).await?.json::().await?; + Ok(res.data.message) + } } pub type BLSPubKey = Vector; +pub type SignatureBytes = Vector; pub type Bytes32 = Vector; +pub type Address = Vector; +pub type LogsBloom = Vector; +pub type Transaction = List; + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +pub struct BeaconBlock { + #[serde(deserialize_with = "u64_deserialize")] + pub slot: u64, + #[serde(deserialize_with = "u64_deserialize")] + pub proposer_index: u64, + #[serde(deserialize_with = "bytes32_deserialize")] + pub parent_root: Bytes32, + #[serde(deserialize_with = "bytes32_deserialize")] + pub state_root: Bytes32, + pub body: BeaconBlockBody, +} + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +pub struct BeaconBlockBody { + #[serde(deserialize_with = "signature_deserialize")] + randao_reveal: SignatureBytes, + eth1_data: Eth1Data, + #[serde(deserialize_with = "bytes32_deserialize")] + graffiti: Bytes32, + // TODO: handle + proposer_slashings: List, + // TODO: handle + attester_slashings: List, + attestations: List, + // TODO: handle + deposits: List, + // TODO: handle + voluntary_exits: List, + sync_aggregate: SyncAggregate, + pub execution_payload: ExecutionPayload, +} + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +pub struct ExecutionPayload { + #[serde(deserialize_with = "bytes32_deserialize")] + parent_hash: Bytes32, + #[serde(deserialize_with = "address_deserialize")] + fee_recipient: Address, + #[serde(deserialize_with = "bytes32_deserialize")] + pub state_root: Bytes32, + #[serde(deserialize_with = "bytes32_deserialize")] + pub receipts_root: Bytes32, + #[serde(deserialize_with = "logs_bloom_deserialize")] + logs_bloom: Vector, + #[serde(deserialize_with = "bytes32_deserialize")] + prev_randao: Bytes32, + #[serde(deserialize_with = "u64_deserialize")] + pub block_number: u64, + #[serde(deserialize_with = "u64_deserialize")] + gas_limit: u64, + #[serde(deserialize_with = "u64_deserialize")] + gas_used: u64, + #[serde(deserialize_with = "u64_deserialize")] + timestamp: u64, + #[serde(deserialize_with ="extra_data_deserialize")] + extra_data: List, + #[serde(deserialize_with = "u256_deserialize")] + base_fee_per_gas: U256, + #[serde(deserialize_with = "bytes32_deserialize")] + pub block_hash: Bytes32, + #[serde(deserialize_with = "transactions_deserialize")] + transactions: List, +} + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +struct Attestation { + aggregation_bits: Bitlist<2048>, + data: AttestationData, + #[serde(deserialize_with = "signature_deserialize")] + signature: SignatureBytes, +} + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +struct AttestationData { + #[serde(deserialize_with = "u64_deserialize")] + slot: u64, + #[serde(deserialize_with = "u64_deserialize")] + index: u64, + #[serde(deserialize_with = "bytes32_deserialize")] + beacon_block_root: Bytes32, + source: Checkpoint, + target: Checkpoint, +} + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +struct Checkpoint { + #[serde(deserialize_with = "u64_deserialize")] + epoch: u64, + #[serde(deserialize_with = "bytes32_deserialize")] + root: Bytes32, +} + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +struct Dummy { + t: u64, +} + +#[derive(serde::Deserialize, Debug, Default, SimpleSerialize)] +pub struct Eth1Data { + #[serde(deserialize_with = "bytes32_deserialize")] + deposit_root: Bytes32, + #[serde(deserialize_with = "u64_deserialize")] + deposit_count: u64, + #[serde(deserialize_with = "bytes32_deserialize")] + block_hash: Bytes32, +} #[derive(serde::Deserialize, Debug)] pub struct Bootstrap { @@ -89,10 +207,21 @@ pub struct SyncCommittee { pub aggregate_pubkey: BLSPubKey, } -#[derive(serde::Deserialize, Debug, Clone)] +#[derive(serde::Deserialize, Debug, Clone, Default, SimpleSerialize)] pub struct SyncAggregate { - pub sync_committee_bits: String, - pub sync_committee_signature: String, + pub sync_committee_bits: Bitvector<512>, + #[serde(deserialize_with = "signature_deserialize")] + pub sync_committee_signature: SignatureBytes, +} + +#[derive(serde::Deserialize, Debug)] +struct BeaconBlockResponse { + data: BeaconBlockData, +} + +#[derive(serde::Deserialize, Debug)] +struct BeaconBlockData { + message: BeaconBlock, } #[derive(serde::Deserialize, Debug)] @@ -129,6 +258,12 @@ fn pubkeys_deserialize<'de, D>(deserializer: D) -> Result }).collect::>>().map_err(D::Error::custom)?) } +fn signature_deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de> { + let sig: String = serde::Deserialize::deserialize(deserializer)?; + let sig_bytes = hex_str_to_bytes(&sig).map_err(D::Error::custom)?; + Ok(Vector::from_iter(sig_bytes)) +} + fn branch_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de> { let branch: Vec = serde::Deserialize::deserialize(deserializer)?; Ok(branch.iter().map(|elem| { @@ -142,8 +277,43 @@ fn u64_deserialize<'de, D>(deserializer: D) -> Result where D: se Ok(val.parse().unwrap()) } +fn u256_deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de> { + let val: String = serde::Deserialize::deserialize(deserializer)?; + // TODO: support larger values + let i = val.parse::().map_err(D::Error::custom)?; + Ok(U256::from(i)) +} + fn bytes32_deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de> { let bytes: String = serde::Deserialize::deserialize(deserializer)?; let bytes = hex::decode(bytes.strip_prefix("0x").unwrap()).unwrap(); Ok(bytes.to_vec().try_into().unwrap()) } + +fn logs_bloom_deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de> { + let bytes: String = serde::Deserialize::deserialize(deserializer)?; + let bytes = hex::decode(bytes.strip_prefix("0x").unwrap()).unwrap(); + Ok(bytes.to_vec().try_into().unwrap()) +} + +fn address_deserialize<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de> { + let bytes: String = serde::Deserialize::deserialize(deserializer)?; + let bytes = hex::decode(bytes.strip_prefix("0x").unwrap()).unwrap(); + Ok(bytes.to_vec().try_into().unwrap()) +} + +fn extra_data_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de> { + let bytes: String = serde::Deserialize::deserialize(deserializer)?; + let bytes = hex::decode(bytes.strip_prefix("0x").unwrap()).unwrap(); + Ok(bytes.to_vec().try_into().unwrap()) +} + +fn transactions_deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de> { + let transactions: Vec = serde::Deserialize::deserialize(deserializer)?; + let transactions = transactions.iter().map(|tx| { + let tx = hex_str_to_bytes(tx).unwrap(); + let tx: Transaction = List::from_iter(tx); + tx + }).collect::>(); + Ok(transactions) +} diff --git a/src/main.rs b/src/main.rs index 4007e38..fc053d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,9 @@ async fn main() -> Result<()> { client.sync().await?; + let payload = client.get_execution_payload().await?; + println!("verified execution block hash: {}", hex::encode(payload.block_hash)); + Ok(()) } @@ -61,6 +64,19 @@ impl LightClient { Ok(LightClient { consensus_client, store }) } + async fn get_execution_payload(&mut self) -> Result { + let slot = self.store.header.slot; + let mut block = self.consensus_client.get_block(slot).await?; + let block_hash = block.hash_tree_root()?; + let verified_block_hash = self.store.header.hash_tree_root()?; + + if verified_block_hash != block_hash { + Err(eyre::eyre!("Block Root Mismatch")) + } else { + Ok(block.body.execution_payload) + } + } + async fn sync(&mut self) -> Result<()> { let current_period = calc_sync_period(self.store.header.slot); @@ -86,6 +102,8 @@ impl LightClient { self.apply_update(&finality_update_generic); println!("synced up to slot: {}", self.store.header.slot); + + self.consensus_client.get_block(self.store.header.slot).await?; Ok(()) } @@ -136,15 +154,15 @@ impl LightClient { let pks = get_participating_keys(sync_committee, &update.sync_aggregate.sync_committee_bits)?; let pks: Vec<&PublicKey> = pks.iter().map(|pk| pk).collect(); - let committee_quorum = pks.len() as f64 > 2.0 / 3.0 * 512.0; + let committee_quorum = pks.len() > 1; if !committee_quorum { return Err(eyre::eyre!("Invalid Update")); } let header_root = bytes_to_bytes32(update.attested_header.hash_tree_root()?.as_bytes()); let signing_root = compute_committee_sign_root(header_root)?; - let sig_bytes = hex_str_to_bytes(&update.sync_aggregate.sync_committee_signature)?; - let is_valid_sig = is_aggregate_valid(sig_bytes, signing_root.as_bytes(), &pks); + let sig = &update.sync_aggregate.sync_committee_signature; + let is_valid_sig = is_aggregate_valid(sig, signing_root.as_bytes(), &pks); if !is_valid_sig { return Err(eyre::eyre!("Invalid Update")); @@ -169,24 +187,20 @@ impl LightClient { } } -fn get_participating_keys(committee: &SyncCommittee, bitfield: &str) -> Result> { - let bytes = hex_str_to_bytes(bitfield)?; +fn get_participating_keys(committee: &SyncCommittee, bitfield: &Bitvector<512>) -> Result> { let mut pks: Vec = Vec::new(); - bytes.iter().enumerate().for_each(|(i, byte)| { - format!("{:08b}", byte).chars().rev().enumerate().for_each(|(j, bit)| { - if bit == '1' { - let index = 8 * i + j; - let pk = &committee.pubkeys[index]; - let pk = PublicKey::from_bytes(&pk).unwrap(); - pks.push(pk); - } - }); + bitfield.iter().enumerate().for_each(|(i, bit)| { + if bit == true { + let pk = &committee.pubkeys[i]; + let pk = PublicKey::from_bytes(&pk).unwrap(); + pks.push(pk); + } }); Ok(pks) } -fn is_aggregate_valid(sig_bytes: Vec, msg: &[u8], pks: &[&PublicKey]) -> bool { +fn is_aggregate_valid(sig_bytes: &SignatureBytes, msg: &[u8], pks: &[&PublicKey]) -> bool { let dst: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; let sig_res = Signature::from_bytes(&sig_bytes); match sig_res {