trustless fetching of execution payload
This commit is contained in:
parent
37749df08a
commit
32603f56c3
|
@ -29,10 +29,128 @@ impl ConsensusClient {
|
||||||
let res = reqwest::get(req).await?.json::<FinalityUpdateResponse>().await?;
|
let res = reqwest::get(req).await?.json::<FinalityUpdateResponse>().await?;
|
||||||
Ok(res.data)
|
Ok(res.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_block(&self, slot: u64) -> Result<BeaconBlock>{
|
||||||
|
let req = format!("{}/eth/v2/beacon/blocks/{}", self.rpc, slot);
|
||||||
|
let res = reqwest::get(req).await?.json::<BeaconBlockResponse>().await?;
|
||||||
|
Ok(res.data.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BLSPubKey = Vector<u8, 48>;
|
pub type BLSPubKey = Vector<u8, 48>;
|
||||||
|
pub type SignatureBytes = Vector<u8, 96>;
|
||||||
pub type Bytes32 = Vector<u8, 32>;
|
pub type Bytes32 = Vector<u8, 32>;
|
||||||
|
pub type Address = Vector<u8, 20>;
|
||||||
|
pub type LogsBloom = Vector<u8, 256>;
|
||||||
|
pub type Transaction = List<u8, 1073741824>;
|
||||||
|
|
||||||
|
#[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<Dummy, 16>,
|
||||||
|
// TODO: handle
|
||||||
|
attester_slashings: List<Dummy, 2>,
|
||||||
|
attestations: List<Attestation, 128>,
|
||||||
|
// TODO: handle
|
||||||
|
deposits: List<Dummy, 16>,
|
||||||
|
// TODO: handle
|
||||||
|
voluntary_exits: List<Dummy, 16>,
|
||||||
|
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<u8, 256>,
|
||||||
|
#[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<u8, 32>,
|
||||||
|
#[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<Transaction, 1048576>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
pub struct Bootstrap {
|
pub struct Bootstrap {
|
||||||
|
@ -89,10 +207,21 @@ pub struct SyncCommittee {
|
||||||
pub aggregate_pubkey: BLSPubKey,
|
pub aggregate_pubkey: BLSPubKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug, Clone)]
|
#[derive(serde::Deserialize, Debug, Clone, Default, SimpleSerialize)]
|
||||||
pub struct SyncAggregate {
|
pub struct SyncAggregate {
|
||||||
pub sync_committee_bits: String,
|
pub sync_committee_bits: Bitvector<512>,
|
||||||
pub sync_committee_signature: String,
|
#[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)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
@ -129,6 +258,12 @@ fn pubkeys_deserialize<'de, D>(deserializer: D) -> Result<Vector<BLSPubKey, 512>
|
||||||
}).collect::<Result<Vector<BLSPubKey, 512>>>().map_err(D::Error::custom)?)
|
}).collect::<Result<Vector<BLSPubKey, 512>>>().map_err(D::Error::custom)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn signature_deserialize<'de, D>(deserializer: D) -> Result<SignatureBytes, D::Error> 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<Vec<Bytes32>, D::Error> where D: serde::Deserializer<'de> {
|
fn branch_deserialize<'de, D>(deserializer: D) -> Result<Vec<Bytes32>, D::Error> where D: serde::Deserializer<'de> {
|
||||||
let branch: Vec<String> = serde::Deserialize::deserialize(deserializer)?;
|
let branch: Vec<String> = serde::Deserialize::deserialize(deserializer)?;
|
||||||
Ok(branch.iter().map(|elem| {
|
Ok(branch.iter().map(|elem| {
|
||||||
|
@ -142,8 +277,43 @@ fn u64_deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error> where D: se
|
||||||
Ok(val.parse().unwrap())
|
Ok(val.parse().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn u256_deserialize<'de, D>(deserializer: D) -> Result<U256, D::Error> where D: serde::Deserializer<'de> {
|
||||||
|
let val: String = serde::Deserialize::deserialize(deserializer)?;
|
||||||
|
// TODO: support larger values
|
||||||
|
let i = val.parse::<u64>().map_err(D::Error::custom)?;
|
||||||
|
Ok(U256::from(i))
|
||||||
|
}
|
||||||
|
|
||||||
fn bytes32_deserialize<'de, D>(deserializer: D) -> Result<Bytes32, D::Error> where D: serde::Deserializer<'de> {
|
fn bytes32_deserialize<'de, D>(deserializer: D) -> Result<Bytes32, D::Error> where D: serde::Deserializer<'de> {
|
||||||
let bytes: String = serde::Deserialize::deserialize(deserializer)?;
|
let bytes: String = serde::Deserialize::deserialize(deserializer)?;
|
||||||
let bytes = hex::decode(bytes.strip_prefix("0x").unwrap()).unwrap();
|
let bytes = hex::decode(bytes.strip_prefix("0x").unwrap()).unwrap();
|
||||||
Ok(bytes.to_vec().try_into().unwrap())
|
Ok(bytes.to_vec().try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn logs_bloom_deserialize<'de, D>(deserializer: D) -> Result<LogsBloom, 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 address_deserialize<'de, D>(deserializer: D) -> Result<Address, 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 extra_data_deserialize<'de, D>(deserializer: D) -> Result<List<u8, 32>, 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<List<Transaction, 1048576>, D::Error> where D: serde::Deserializer<'de> {
|
||||||
|
let transactions: Vec<String> = 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::<List<Transaction,1048576>>();
|
||||||
|
Ok(transactions)
|
||||||
|
}
|
||||||
|
|
44
src/main.rs
44
src/main.rs
|
@ -17,6 +17,9 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
client.sync().await?;
|
client.sync().await?;
|
||||||
|
|
||||||
|
let payload = client.get_execution_payload().await?;
|
||||||
|
println!("verified execution block hash: {}", hex::encode(payload.block_hash));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +64,19 @@ impl LightClient {
|
||||||
Ok(LightClient { consensus_client, store })
|
Ok(LightClient { consensus_client, store })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_execution_payload(&mut self) -> Result<ExecutionPayload> {
|
||||||
|
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<()> {
|
async fn sync(&mut self) -> Result<()> {
|
||||||
|
|
||||||
let current_period = calc_sync_period(self.store.header.slot);
|
let current_period = calc_sync_period(self.store.header.slot);
|
||||||
|
@ -86,6 +102,8 @@ impl LightClient {
|
||||||
self.apply_update(&finality_update_generic);
|
self.apply_update(&finality_update_generic);
|
||||||
|
|
||||||
println!("synced up to slot: {}", self.store.header.slot);
|
println!("synced up to slot: {}", self.store.header.slot);
|
||||||
|
|
||||||
|
self.consensus_client.get_block(self.store.header.slot).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -136,15 +154,15 @@ impl LightClient {
|
||||||
let pks = get_participating_keys(sync_committee, &update.sync_aggregate.sync_committee_bits)?;
|
let pks = get_participating_keys(sync_committee, &update.sync_aggregate.sync_committee_bits)?;
|
||||||
let pks: Vec<&PublicKey> = pks.iter().map(|pk| pk).collect();
|
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 {
|
if !committee_quorum {
|
||||||
return Err(eyre::eyre!("Invalid Update"));
|
return Err(eyre::eyre!("Invalid Update"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let header_root = bytes_to_bytes32(update.attested_header.hash_tree_root()?.as_bytes());
|
let header_root = bytes_to_bytes32(update.attested_header.hash_tree_root()?.as_bytes());
|
||||||
let signing_root = compute_committee_sign_root(header_root)?;
|
let signing_root = compute_committee_sign_root(header_root)?;
|
||||||
let sig_bytes = hex_str_to_bytes(&update.sync_aggregate.sync_committee_signature)?;
|
let sig = &update.sync_aggregate.sync_committee_signature;
|
||||||
let is_valid_sig = is_aggregate_valid(sig_bytes, signing_root.as_bytes(), &pks);
|
let is_valid_sig = is_aggregate_valid(sig, signing_root.as_bytes(), &pks);
|
||||||
|
|
||||||
if !is_valid_sig {
|
if !is_valid_sig {
|
||||||
return Err(eyre::eyre!("Invalid Update"));
|
return Err(eyre::eyre!("Invalid Update"));
|
||||||
|
@ -169,24 +187,20 @@ impl LightClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_participating_keys(committee: &SyncCommittee, bitfield: &str) -> Result<Vec<PublicKey>> {
|
fn get_participating_keys(committee: &SyncCommittee, bitfield: &Bitvector<512>) -> Result<Vec<PublicKey>> {
|
||||||
let bytes = hex_str_to_bytes(bitfield)?;
|
|
||||||
let mut pks: Vec<PublicKey> = Vec::new();
|
let mut pks: Vec<PublicKey> = Vec::new();
|
||||||
bytes.iter().enumerate().for_each(|(i, byte)| {
|
bitfield.iter().enumerate().for_each(|(i, bit)| {
|
||||||
format!("{:08b}", byte).chars().rev().enumerate().for_each(|(j, bit)| {
|
if bit == true {
|
||||||
if bit == '1' {
|
let pk = &committee.pubkeys[i];
|
||||||
let index = 8 * i + j;
|
let pk = PublicKey::from_bytes(&pk).unwrap();
|
||||||
let pk = &committee.pubkeys[index];
|
pks.push(pk);
|
||||||
let pk = PublicKey::from_bytes(&pk).unwrap();
|
}
|
||||||
pks.push(pk);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(pks)
|
Ok(pks)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_aggregate_valid(sig_bytes: Vec<u8>, 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 dst: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
|
||||||
let sig_res = Signature::from_bytes(&sig_bytes);
|
let sig_res = Signature::from_bytes(&sig_bytes);
|
||||||
match sig_res {
|
match sig_res {
|
||||||
|
|
Loading…
Reference in New Issue