Adds Snowbridge to Rococo runtime (#2522)

# Description

Adds Snowbridge to the Rococo bridge hub runtime. Includes config
changes required in Rococo asset hub.

---------

Co-authored-by: Alistair Singh <alistair.singh7@gmail.com>
Co-authored-by: ron <yrong1997@gmail.com>
Co-authored-by: Vincent Geddes <vincent.geddes@hey.com>
Co-authored-by: claravanstaden <Cats 4 life!>
This commit is contained in:
Clara van Staden
2023-12-21 18:06:36 +02:00
committed by GitHub
parent 9f5221cc2f
commit 18d53dbf91
151 changed files with 19379 additions and 149 deletions
@@ -0,0 +1,52 @@
[package]
name = "snowbridge-beacon-primitives"
description = "Snowbridge Beacon Primitives"
version = "0.0.1"
authors = ["Snowfork <contact@snowfork.com>"]
edition = "2021"
license = "Apache-2.0"
[dependencies]
serde = { version = "1.0.188", optional = true, features = ["derive"] }
hex = { version = "0.4", default-features = false }
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
rlp = { version = "0.5", default-features = false }
frame-support = { path = "../../../../../substrate/frame/support", default-features = false }
frame-system = { path = "../../../../../substrate/frame/system", default-features = false }
sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false }
sp-core = { path = "../../../../../substrate/primitives/core", default-features = false }
sp-std = { path = "../../../../../substrate/primitives/std", default-features = false }
sp-io = { path = "../../../../../substrate/primitives/io", default-features = false }
ssz_rs = { version = "0.9.0", default-features = false }
ssz_rs_derive = { version = "0.9.0", default-features = false }
byte-slice-cast = { version = "1.2.1", default-features = false }
snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false }
static_assertions = { version = "1.1.0" }
milagro_bls = { git = "https://github.com/snowfork/milagro_bls", default-features = false, rev = "a6d66e4eb89015e352fb1c9f7b661ecdbb5b2176" }
[dev-dependencies]
hex-literal = { version = "0.4.1" }
[features]
default = ["std"]
std = [
"byte-slice-cast/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"hex/std",
"milagro_bls/std",
"rlp/std",
"scale-info/std",
"serde",
"snowbridge-ethereum/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"ssz_rs/std",
]
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use sp_std::{convert::TryInto, prelude::*};
use ssz_rs::{Bitvector, Deserialize};
pub fn decompress_sync_committee_bits<
const SYNC_COMMITTEE_SIZE: usize,
const SYNC_COMMITTEE_BITS_SIZE: usize,
>(
input: [u8; SYNC_COMMITTEE_BITS_SIZE],
) -> [u8; SYNC_COMMITTEE_SIZE] {
Bitvector::<{ SYNC_COMMITTEE_SIZE }>::deserialize(&input)
.expect("checked statically; qed")
.iter()
.map(|bit| u8::from(bit == true))
.collect::<Vec<u8>>()
.try_into()
.expect("checked statically; qed")
}
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::{PublicKey, Signature};
use codec::{Decode, Encode};
use frame_support::{ensure, PalletError};
pub use milagro_bls::{
AggregatePublicKey, AggregateSignature, PublicKey as PublicKeyPrepared,
Signature as SignaturePrepared,
};
use scale_info::TypeInfo;
use sp_core::H256;
use sp_runtime::RuntimeDebug;
use sp_std::prelude::*;
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, TypeInfo, RuntimeDebug, PalletError)]
pub enum BlsError {
InvalidSignature,
InvalidPublicKey,
InvalidAggregatePublicKeys,
SignatureVerificationFailed,
}
/// fast_aggregate_verify optimized with aggregate key subtracting absent ones.
pub fn fast_aggregate_verify(
aggregate_pubkey: &PublicKeyPrepared,
absent_pubkeys: &Vec<PublicKeyPrepared>,
message: H256,
signature: &Signature,
) -> Result<(), BlsError> {
let agg_sig = prepare_aggregate_signature(signature)?;
let agg_key = prepare_aggregate_pubkey_from_absent(aggregate_pubkey, absent_pubkeys)?;
fast_aggregate_verify_pre_aggregated(agg_sig, agg_key, message)
}
/// Decompress one public key into a point in G1.
pub fn prepare_milagro_pubkey(pubkey: &PublicKey) -> Result<PublicKeyPrepared, BlsError> {
PublicKeyPrepared::from_bytes_unchecked(&pubkey.0).map_err(|_| BlsError::InvalidPublicKey)
}
/// Prepare for G1 public keys.
pub fn prepare_g1_pubkeys(pubkeys: &[PublicKey]) -> Result<Vec<PublicKeyPrepared>, BlsError> {
pubkeys
.iter()
// Deserialize one public key from compressed bytes
.map(prepare_milagro_pubkey)
.collect::<Result<Vec<PublicKeyPrepared>, BlsError>>()
}
/// Prepare for G1 AggregatePublicKey.
pub fn prepare_aggregate_pubkey(
pubkeys: &[PublicKeyPrepared],
) -> Result<AggregatePublicKey, BlsError> {
AggregatePublicKey::into_aggregate(pubkeys).map_err(|_| BlsError::InvalidPublicKey)
}
/// Prepare for G1 AggregatePublicKey.
pub fn prepare_aggregate_pubkey_from_absent(
aggregate_key: &PublicKeyPrepared,
absent_pubkeys: &Vec<PublicKeyPrepared>,
) -> Result<AggregatePublicKey, BlsError> {
let mut aggregate_pubkey = AggregatePublicKey::from_public_key(aggregate_key);
if !absent_pubkeys.is_empty() {
let absent_aggregate_key = prepare_aggregate_pubkey(absent_pubkeys)?;
aggregate_pubkey.point.sub(&absent_aggregate_key.point);
}
Ok(AggregatePublicKey { point: aggregate_pubkey.point })
}
/// Prepare for G2 AggregateSignature, normally more expensive than G1 operation.
pub fn prepare_aggregate_signature(signature: &Signature) -> Result<AggregateSignature, BlsError> {
Ok(AggregateSignature::from_signature(
&SignaturePrepared::from_bytes(&signature.0).map_err(|_| BlsError::InvalidSignature)?,
))
}
/// fast_aggregate_verify_pre_aggregated which is the most expensive call in beacon light client.
pub fn fast_aggregate_verify_pre_aggregated(
agg_sig: AggregateSignature,
aggregate_key: AggregatePublicKey,
message: H256,
) -> Result<(), BlsError> {
ensure!(
agg_sig.fast_aggregate_verify_pre_aggregated(&message[..], &aggregate_key),
BlsError::SignatureVerificationFailed
);
Ok(())
}
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pub const MAX_PROOF_SIZE: u32 = 20;
pub const FEE_RECIPIENT_SIZE: usize = 20;
pub const EXTRA_DATA_SIZE: usize = 32;
pub const LOGS_BLOOM_SIZE: usize = 256;
pub const PUBKEY_SIZE: usize = 48;
pub const SIGNATURE_SIZE: usize = 96;
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
#![cfg_attr(not(feature = "std"), no_std)]
pub mod bits;
pub mod bls;
pub mod config;
pub mod merkle_proof;
pub mod receipt;
pub mod ssz;
pub mod types;
pub mod updates;
#[cfg(feature = "std")]
mod serde_utils;
pub use types::{
BeaconHeader, CompactBeaconState, CompactExecutionHeader, ExecutionHeaderState,
ExecutionPayloadHeader, FinalizedHeaderState, Fork, ForkData, ForkVersion, ForkVersions, Mode,
PublicKey, Signature, SigningData, SyncAggregate, SyncCommittee, SyncCommitteePrepared,
};
pub use updates::{CheckpointUpdate, ExecutionHeaderUpdate, NextSyncCommitteeUpdate, Update};
pub use bits::decompress_sync_committee_bits;
pub use bls::{
fast_aggregate_verify, prepare_aggregate_pubkey, prepare_aggregate_pubkey_from_absent,
prepare_aggregate_signature, prepare_g1_pubkeys, AggregatePublicKey, AggregateSignature,
BlsError, PublicKeyPrepared, SignaturePrepared,
};
pub use merkle_proof::verify_merkle_branch;
pub use receipt::verify_receipt_proof;
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use sp_core::H256;
use sp_io::hashing::sha2_256;
/// Specified by <https://github.com/ethereum/consensus-specs/blob/fe9c1a8cbf0c2da8a4f349efdcd77dd7ac8445c4/specs/phase0/beacon-chain.md?plain=1#L742>
/// with improvements from <https://github.com/ethereum/consensus-specs/blob/dev/ssz/merkle-proofs.md>
pub fn verify_merkle_branch(
leaf: H256,
branch: &[H256],
index: usize,
depth: usize,
root: H256,
) -> bool {
// verify the proof length
if branch.len() != depth {
return false
}
// verify the computed merkle root
root == compute_merkle_root(leaf, branch, index)
}
fn compute_merkle_root(leaf: H256, proof: &[H256], index: usize) -> H256 {
let mut value: [u8; 32] = leaf.into();
for (i, node) in proof.iter().enumerate() {
let mut data = [0u8; 64];
if generalized_index_bit(index, i) {
// right node
data[0..32].copy_from_slice(node.as_bytes());
data[32..64].copy_from_slice(&value);
value = sha2_256(&data);
} else {
// left node
data[0..32].copy_from_slice(&value);
data[32..64].copy_from_slice(node.as_bytes());
value = sha2_256(&data);
}
}
value.into()
}
/// Spec: <https://github.com/ethereum/consensus-specs/blob/fe9c1a8cbf0c2da8a4f349efdcd77dd7ac8445c4/ssz/merkle-proofs.md#get_generalized_index_bit>
fn generalized_index_bit(index: usize, position: usize) -> bool {
index & (1 << position) > 0
}
/// Spec: <https://github.com/ethereum/consensus-specs/blob/fe9c1a8cbf0c2da8a4f349efdcd77dd7ac8445c4/specs/altair/light-client/sync-protocol.md#get_subtree_index>
pub const fn subtree_index(generalized_index: usize) -> usize {
generalized_index % (1 << generalized_index_length(generalized_index))
}
/// Spec: <https://github.com/ethereum/consensus-specs/blob/fe9c1a8cbf0c2da8a4f349efdcd77dd7ac8445c4/ssz/merkle-proofs.md#get_generalized_index_length>
pub const fn generalized_index_length(generalized_index: usize) -> usize {
match generalized_index.checked_ilog2() {
Some(v) => v as usize,
None => panic!("checked statically; qed"),
}
}
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use sp_core::H256;
use sp_io::hashing::keccak_256;
use sp_std::prelude::*;
use snowbridge_ethereum::{mpt, Receipt};
pub fn verify_receipt_proof(
receipts_root: H256,
proof: &[Vec<u8>],
) -> Option<Result<Receipt, rlp::DecoderError>> {
match apply_merkle_proof(proof) {
Some((root, data)) if root == receipts_root => Some(rlp::decode(&data)),
Some((_, _)) => None,
None => None,
}
}
fn apply_merkle_proof(proof: &[Vec<u8>]) -> Option<(H256, Vec<u8>)> {
let mut iter = proof.iter().rev();
let first_bytes = match iter.next() {
Some(b) => b,
None => return None,
};
let item_to_prove: mpt::ShortNode = rlp::decode(first_bytes).ok()?;
let final_hash: Option<[u8; 32]> = iter.try_fold(keccak_256(first_bytes), |acc, x| {
let node: Box<dyn mpt::Node> = x.as_slice().try_into().ok()?;
if (*node).contains_hash(acc.into()) {
return Some(keccak_256(x))
}
None
});
final_hash.map(|hash| (hash.into(), item_to_prove.value))
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
#[test]
fn test_verify_receipt_proof() {
let root: H256 =
hex!("fd5e397a84884641f53c496804f24b5276cbb8c5c9cfc2342246be8e3ce5ad02").into();
// Valid proof
let proof_receipt5 = vec!(
hex!("f90131a0b5ba404eb5a6a88e56579f4d37ef9813b5ad7f86f0823ff3b407ac5a6bb465eca0398ead2655e78e03c127ce22c5830e90f18b1601ec055f938336c084feb915a9a026d322c26e46c50942c1aabde50e36df5cde572aed650ce73ea3182c6e90a02ca00600a356135f4db1db0d9842264cdff2652676f881669e91e316c0b6dd783011a0837f1deb4075336da320388c1edfffc56c448a43f4a5ba031300d32a7b509fc5a01c3ac82fd65b4aba7f9afaf604d9c82ec7e2deb573a091ae235751bc5c0c288da05d454159d9071b0f68b6e0503d290f23ac7602c1db0c569dee4605d8f5298f09a00bbed10350ec954448df795f6fd46e3faefc800ede061b3840eedc6e2b07a74da0acb02d26a3650f2064c14a435fdf1f668d8655daf455ebdf671713a7c089b3898080808080808080").to_vec(),
hex!("f901f180a00046a08d4f0bdbdc6b31903086ce323182bce6725e7d9415f7ff91ee8f4820bda0e7cd26ad5f3d2771e4b5ab788e268a14a10209f94ee918eb6c829d21d3d11c1da00d4a56d9e9a6751874fd86c7e3cb1c6ad5a848da62751325f478978a00ea966ea064b81920c8f04a8a1e21f53a8280e739fbb7b00b2ab92493ca3f610b70e8ac85a0b1040ed4c55a73178b76abb16f946ce5bebd6b93ab873c83327df54047d12c27a0de6485e9ac58dc6e2b04b4bb38f562684f0b1a2ee586cc11079e7d9a9dc40b32a0d394f4d3532c3124a65fa36e69147e04fd20453a72ee9c50660f17e13ce9df48a066501003fc3e3478efd2803cd0eded6bbe9243ca01ba754d6327071ddbcbc649a0b2684e518f325fee39fc8ea81b68f3f5c785be00d087f3bed8857ae2ee8da26ea071060a5c52042e8d7ce21092f8ecf06053beb9a0b773a6f91a30c4220aa276b2a0fc22436632574ccf6043d0986dede27ea94c9ca9a3bb5ec03ce776a4ddef24a9a05a8a1d6698c4e7d8cc3a2506cb9b12ea9a079c9c7099bc919dc804033cc556e4a0170c468b0716fd36d161f0bf05875f15756a2976de92f9efe7716320509d79c9a0182f909a90cab169f3efb62387f9cccdd61440acc4deec42f68a4f7ca58075c7a055cf0e9202ac75689b76318f1171f3a44465eddc06aae0713bfb6b34fdd27b7980").to_vec(),
hex!("f904de20b904daf904d701830652f0b9010004200000000000000000000080020000000000010000000000010000000000000000000000000000000000000000000002000000080000000000000000200000000000000000000000000008000000220000000000400010000000000000000000000000000000000000000000000000000000000000040000000010000100000000000800000000004000000000000000000000000000080000004000000000020000000000020000000000000000000000000000000000000000000004000000000002000000000100000000000000000000000000001000000002000020000010200000000000010000000000000000000000000000000000000010000000f903ccf89b9421130f34829b4c343142047a28ce96ec07814b15f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a000000000000000000000000000000000000000000000000000000005d09b7380f89b9421130f34829b4c343142047a28ce96ec07814b15f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0ffffffffffffffffffffffffffffffffffffffffffffffffffffffcc840c6920f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078ef87994e9c1281aae66801fa35ec404d5f2aea393ff6988e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000000001f1420ad1d40000000000000000000000000000000000000000000000014ad400879d159a38f8fc94e9c1281aae66801fa35ec404d5f2aea393ff6988f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488db88000000000000000000000000000000000000000000000000000000005d415f3320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e973b5a5d1078ef87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078e").to_vec(),
);
assert!(verify_receipt_proof(root, &proof_receipt5).is_some());
// Various invalid proofs
let proof_empty: Vec<Vec<u8>> = vec![];
let proof_missing_full_node = vec![proof_receipt5[0].clone(), proof_receipt5[2].clone()];
let proof_missing_short_node1 = vec![proof_receipt5[0].clone(), proof_receipt5[1].clone()];
let proof_missing_short_node2 = vec![proof_receipt5[0].clone()];
let proof_invalid_encoding = vec![proof_receipt5[2][2..].to_vec()];
let proof_no_full_node = vec![proof_receipt5[2].clone(), proof_receipt5[2].clone()];
assert!(verify_receipt_proof(root, &proof_empty).is_none());
assert!(verify_receipt_proof(root, &proof_missing_full_node).is_none());
assert_eq!(
verify_receipt_proof(root, &proof_missing_short_node1),
Some(Err(rlp::DecoderError::Custom("Unsupported receipt type")))
);
assert_eq!(
verify_receipt_proof(root, &proof_missing_short_node2),
Some(Err(rlp::DecoderError::Custom("Unsupported receipt type")))
);
assert!(verify_receipt_proof(root, &proof_invalid_encoding).is_none());
assert!(verify_receipt_proof(root, &proof_no_full_node).is_none());
}
#[test]
fn test_verify_receipt_proof_with_intermediate_short_node() {
let root: H256 =
hex!("d128e3a57142d2bf15bc0cbcac7ad54f40750d571b5c3097e425882c10c9ba66").into();
let proof_receipt263 = vec![
hex!("f90131a00d3cb8d3f57ac1c0e12918a2ebe0cafed8c273577b9dd73e7ed1079b403ef494a0678b9835b834f8a287c0dd33a8fca9146e456ca688555ed4ec1361a2180b778da0fe42da181a46677a043b3d9d4b8bb05a6a17b7b5c010c17e7c1d31cfb7c4f911a0c89f0e2c53241cdb578e1f2b4caf6ba36e00500bdc57fecd66b84a6a58394c19a086c3c1fae5a0575940b5d38e111c469d07883106c26856f3ef608469a2081f13a06c5992ff00aab6226a70a032fd2f571ba22f797321f45e2daa73020d638d21b0a050861e9503ef68728f6c90a44f7fe1bceb2a9bdab6957bbe7136166bd849561ea006aa6eaca8a07e57176e9aa41e6a09edfb7678d1a112404e0ec779d7e567e82ea0bb0b430d303ba21b0af11c487b8a218bd75db54c98940b3f11bad8ff47cad3ef8080808080808080").to_vec(),
hex!("f871a0246de222036ee6a03329b0105da0a6b3f916fc95a9ed5a403a581a0c4d74242ca0ac108a49a88b57a05ac34a108b39f1e45f6f167f2b9fbc8d52fb58e2e5a6af1ea0fcfe07ac2ccd3c28b6eab68d1bce112f6f6dbd9023e4ec3c05b96615aa803d798080808080808080808080808080").to_vec(),
hex!("e4820001a04fff54398cad4d05ea6abfd8b0f3b4fe14c04d7ff5f5211c5b927d9cf72ac1d8").to_vec(),
hex!("f851a096d010643ca2d47412ca66898286b5f2412963b9ec051b33e570d575914c9c5ca028cd24c652989542fe89479ec6388eac4592432242af5ba97563b3ac7c71c019808080808080808080808080808080").to_vec(),
hex!("f90211a0bb35a84c5b1dcb78ec9d32614912c696e62df77bebf9ab326ee55b5d3acdde46a01084b30dac8df0accfcd0fd6330b7f6fc72a4651246d0694be9162151686a620a03eed50afdce7909d784c6157c445a444c806b5f23d31f3b63786f600c84a95b2a0af5232f1df6c6d41879804d081abe867002abe26ba3e5f8e0254a83a54769831a0607915fb13dd5da594256389a45007a67a7f7a86e95d38d8462792b6c98a722ea00e1260fda1730f2738c650ce2bfba83857bc10f8fb119ebc4fb39acba24e6fbaa0d11de17e417327457812675ca3b84ae8e1b64827abfe01420953697c8313d5b1a05fcaf2f7a88f76336a0c32ffc78acb87ae2005454bd25d658035331be3173b46a03f94f4952ab9e650f83cfd0e7f367b1bcc493aacf39a06f16c4a2e1b5605da48a0bdb4ec79785ca8ae22d60f1bbd42d707b4d7ec4aff231a3ebab755e315b35053a043a67c3f2bcef37c8f47a673adcb7061007a553696d1092408601c11b2e6846aa0c519d5af48cae87c7f4538845417c9735813bee892a6fe2dda79f5c414e8576aa0f7058256e09589501d7c231d739e61c84a850e139690989d24fda6058b432e98a081a52faab520978cb19ce14400dba0cd5bcdc4e5a3c0740678aa8f97ee0e5c56a0bcecc61cadeae52518e3b68a48af4b11603dfd9d99d99d7985efa6d2de44f904a02cba4accfc6f39bc5adb6d4440eb6358b4a5103ef93298e4e694f1f940f8b48280").to_vec(),
hex!("f901ae20b901aaf901a70183bb444eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000000000000000000100000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000000000000002000000000000000000081000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000f89df89b94dac17f958d2ee523a2206206994597c13d831ec7f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e514404ff6823f1b46a8318a709251db414e5e1a000000000000000000000000055021c55847c00d764357a352e5803237d328954a0000000000000000000000000000000000000000000000000000000000201c370").to_vec(),
];
assert!(verify_receipt_proof(root, &proof_receipt263).is_some());
}
}
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use sp_core::U256;
use core::fmt::Formatter;
use serde::{Deserialize, Deserializer};
// helper to deserialize arbitrary arrays like [T; N]
pub mod arrays {
use std::{convert::TryInto, marker::PhantomData};
use serde::{
de::{SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Deserializer, Serialize, Serializer,
};
pub fn serialize<S: Serializer, T: Serialize, const N: usize>(
data: &[T; N],
ser: S,
) -> Result<S::Ok, S::Error> {
let mut s = ser.serialize_tuple(N)?;
for item in data {
s.serialize_element(item)?;
}
s.end()
}
struct ArrayVisitor<T, const N: usize>(PhantomData<T>);
impl<'de, T, const N: usize> Visitor<'de> for ArrayVisitor<T, N>
where
T: Deserialize<'de>,
{
type Value = [T; N];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(&format!("an array of length {}", N))
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
// can be optimized using MaybeUninit
let mut data = Vec::with_capacity(N);
for _ in 0..N {
match (seq.next_element())? {
Some(val) => data.push(val),
None => return Err(serde::de::Error::invalid_length(N, &self)),
}
}
match data.try_into() {
Ok(arr) => Ok(arr),
Err(_) => unreachable!(),
}
}
}
pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result<[T; N], D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
deserializer.deserialize_tuple(N, ArrayVisitor::<T, N>(PhantomData))
}
}
pub(crate) fn from_hex_to_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let str_without_0x = match s.strip_prefix("0x") {
Some(val) => val,
None => &s,
};
let hex_bytes = match hex::decode(str_without_0x) {
Ok(bytes) => bytes,
Err(e) => return Err(serde::de::Error::custom(e.to_string())),
};
Ok(hex_bytes)
}
pub(crate) fn from_int_to_u256<'de, D>(deserializer: D) -> Result<U256, D::Error>
where
D: Deserializer<'de>,
{
let number = u128::deserialize(deserializer)?;
Ok(U256::from(number))
}
pub struct HexVisitor<const LENGTH: usize>();
impl<'de, const LENGTH: usize> serde::de::Visitor<'de> for HexVisitor<LENGTH> {
type Value = [u8; LENGTH];
fn expecting(&self, formatter: &mut Formatter) -> sp_std::fmt::Result {
formatter.write_str("a hex string with an '0x' prefix")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let stripped = match v.strip_prefix("0x") {
Some(stripped) => stripped,
None => v,
};
let decoded = match hex::decode(stripped) {
Ok(decoded) => decoded,
Err(e) => return Err(serde::de::Error::custom(e.to_string())),
};
if decoded.len() != LENGTH {
return Err(serde::de::Error::custom("publickey expected to be 48 characters"))
}
let data: Self::Value = decoded
.try_into()
.map_err(|_e| serde::de::Error::custom("hex data has unexpected length"))?;
Ok(data)
}
}
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::{
config::{EXTRA_DATA_SIZE, FEE_RECIPIENT_SIZE, LOGS_BLOOM_SIZE, PUBKEY_SIZE, SIGNATURE_SIZE},
types::{
BeaconHeader, ExecutionPayloadHeader, ForkData, SigningData, SyncAggregate, SyncCommittee,
},
};
use byte_slice_cast::AsByteSlice;
use sp_core::H256;
use sp_std::{vec, vec::Vec};
use ssz_rs::{
prelude::{List, Vector},
Bitvector, Deserialize, DeserializeError, SimpleSerialize, SimpleSerializeError, Sized, U256,
};
use ssz_rs_derive::SimpleSerialize as SimpleSerializeDerive;
#[derive(Default, SimpleSerializeDerive, Clone, Debug)]
pub struct SSZBeaconBlockHeader {
pub slot: u64,
pub proposer_index: u64,
pub parent_root: [u8; 32],
pub state_root: [u8; 32],
pub body_root: [u8; 32],
}
impl From<BeaconHeader> for SSZBeaconBlockHeader {
fn from(beacon_header: BeaconHeader) -> Self {
SSZBeaconBlockHeader {
slot: beacon_header.slot,
proposer_index: beacon_header.proposer_index,
parent_root: beacon_header.parent_root.to_fixed_bytes(),
state_root: beacon_header.state_root.to_fixed_bytes(),
body_root: beacon_header.body_root.to_fixed_bytes(),
}
}
}
#[derive(Default, SimpleSerializeDerive, Clone)]
pub struct SSZSyncCommittee<const COMMITTEE_SIZE: usize> {
pub pubkeys: Vector<Vector<u8, PUBKEY_SIZE>, COMMITTEE_SIZE>,
pub aggregate_pubkey: Vector<u8, PUBKEY_SIZE>,
}
impl<const COMMITTEE_SIZE: usize> From<SyncCommittee<COMMITTEE_SIZE>>
for SSZSyncCommittee<COMMITTEE_SIZE>
{
fn from(sync_committee: SyncCommittee<COMMITTEE_SIZE>) -> Self {
let mut pubkeys_vec = Vec::new();
for pubkey in sync_committee.pubkeys.iter() {
// The only thing that can go wrong in the conversion from vec to Vector (ssz type) is
// that the Vector size is 0, or that the given data to create the Vector from does not
// match the expected size N. Because these sizes are statically checked (i.e.
// PublicKey's size is 48, and const PUBKEY_SIZE is 48, it is impossible for "try_from"
// to return an error condition.
let conv_pubkey = Vector::<u8, PUBKEY_SIZE>::try_from(pubkey.0.to_vec())
.expect("checked statically; qed");
pubkeys_vec.push(conv_pubkey);
}
let pubkeys = Vector::<Vector<u8, PUBKEY_SIZE>, { COMMITTEE_SIZE }>::try_from(pubkeys_vec)
.expect("checked statically; qed");
let aggregate_pubkey =
Vector::<u8, PUBKEY_SIZE>::try_from(sync_committee.aggregate_pubkey.0.to_vec())
.expect("checked statically; qed");
SSZSyncCommittee { pubkeys, aggregate_pubkey }
}
}
#[derive(Default, Debug, SimpleSerializeDerive, Clone)]
pub struct SSZSyncAggregate<const COMMITTEE_SIZE: usize> {
pub sync_committee_bits: Bitvector<COMMITTEE_SIZE>,
pub sync_committee_signature: Vector<u8, SIGNATURE_SIZE>,
}
impl<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize>
From<SyncAggregate<COMMITTEE_SIZE, COMMITTEE_BITS_SIZE>> for SSZSyncAggregate<COMMITTEE_SIZE>
{
fn from(sync_aggregate: SyncAggregate<COMMITTEE_SIZE, COMMITTEE_BITS_SIZE>) -> Self {
SSZSyncAggregate {
sync_committee_bits: Bitvector::<COMMITTEE_SIZE>::deserialize(
&sync_aggregate.sync_committee_bits,
)
.expect("checked statically; qed"),
sync_committee_signature: Vector::<u8, SIGNATURE_SIZE>::try_from(
sync_aggregate.sync_committee_signature.0.to_vec(),
)
.expect("checked statically; qed"),
}
}
}
#[derive(Default, SimpleSerializeDerive, Clone)]
pub struct SSZForkData {
pub current_version: [u8; 4],
pub genesis_validators_root: [u8; 32],
}
impl From<ForkData> for SSZForkData {
fn from(fork_data: ForkData) -> Self {
SSZForkData {
current_version: fork_data.current_version,
genesis_validators_root: fork_data.genesis_validators_root,
}
}
}
#[derive(Default, SimpleSerializeDerive, Clone)]
pub struct SSZSigningData {
pub object_root: [u8; 32],
pub domain: [u8; 32],
}
impl From<SigningData> for SSZSigningData {
fn from(signing_data: SigningData) -> Self {
SSZSigningData {
object_root: signing_data.object_root.into(),
domain: signing_data.domain.into(),
}
}
}
#[derive(Default, SimpleSerializeDerive, Clone, Debug)]
pub struct SSZExecutionPayloadHeader {
pub parent_hash: [u8; 32],
pub fee_recipient: Vector<u8, FEE_RECIPIENT_SIZE>,
pub state_root: [u8; 32],
pub receipts_root: [u8; 32],
pub logs_bloom: Vector<u8, LOGS_BLOOM_SIZE>,
pub prev_randao: [u8; 32],
pub block_number: u64,
pub gas_limit: u64,
pub gas_used: u64,
pub timestamp: u64,
pub extra_data: List<u8, EXTRA_DATA_SIZE>,
pub base_fee_per_gas: U256,
pub block_hash: [u8; 32],
pub transactions_root: [u8; 32],
pub withdrawals_root: [u8; 32],
}
impl TryFrom<ExecutionPayloadHeader> for SSZExecutionPayloadHeader {
type Error = SimpleSerializeError;
fn try_from(payload: ExecutionPayloadHeader) -> Result<Self, Self::Error> {
Ok(SSZExecutionPayloadHeader {
parent_hash: payload.parent_hash.to_fixed_bytes(),
fee_recipient: Vector::<u8, FEE_RECIPIENT_SIZE>::try_from(
payload.fee_recipient.to_fixed_bytes().to_vec(),
)
.expect("checked statically; qed"),
state_root: payload.state_root.to_fixed_bytes(),
receipts_root: payload.receipts_root.to_fixed_bytes(),
// Logs bloom bytes size is not constrained, so here we do need to check the try_from
// error
logs_bloom: Vector::<u8, LOGS_BLOOM_SIZE>::try_from(payload.logs_bloom)
.map_err(|(_, err)| err)?,
prev_randao: payload.prev_randao.to_fixed_bytes(),
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
// Extra data bytes size is not constrained, so here we do need to check the try_from
// error
extra_data: List::<u8, EXTRA_DATA_SIZE>::try_from(payload.extra_data)
.map_err(|(_, err)| err)?,
base_fee_per_gas: U256::from_bytes_le(
payload
.base_fee_per_gas
.as_byte_slice()
.try_into()
.expect("checked in prep; qed"),
),
block_hash: payload.block_hash.to_fixed_bytes(),
transactions_root: payload.transactions_root.to_fixed_bytes(),
withdrawals_root: payload.withdrawals_root.to_fixed_bytes(),
})
}
}
pub fn hash_tree_root<T: SimpleSerialize>(mut object: T) -> Result<H256, SimpleSerializeError> {
match object.hash_tree_root() {
Ok(node) => {
let fixed_bytes: [u8; 32] =
node.as_ref().try_into().expect("Node is a newtype over [u8; 32]; qed");
Ok(fixed_bytes.into())
},
Err(err) => Err(err.into()),
}
}
@@ -0,0 +1,512 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_core::{H160, H256, U256};
use sp_runtime::RuntimeDebug;
use sp_std::{boxed::Box, prelude::*};
use crate::config::{PUBKEY_SIZE, SIGNATURE_SIZE};
#[cfg(feature = "std")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "std")]
use crate::serde_utils::HexVisitor;
use crate::ssz::{
hash_tree_root, SSZBeaconBlockHeader, SSZExecutionPayloadHeader, SSZForkData, SSZSigningData,
SSZSyncAggregate, SSZSyncCommittee,
};
use ssz_rs::SimpleSerializeError;
pub use crate::bits::decompress_sync_committee_bits;
use crate::bls::{prepare_g1_pubkeys, prepare_milagro_pubkey, BlsError};
use milagro_bls::PublicKey as PublicKeyPrepared;
pub type ValidatorIndex = u64;
pub type ForkVersion = [u8; 4];
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct ForkVersions {
pub genesis: Fork,
pub altair: Fork,
pub bellatrix: Fork,
pub capella: Fork,
}
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct Fork {
pub version: [u8; 4],
pub epoch: u64,
}
#[derive(Copy, Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct PublicKey(pub [u8; PUBKEY_SIZE]);
impl Default for PublicKey {
fn default() -> Self {
PublicKey([0u8; PUBKEY_SIZE])
}
}
impl From<[u8; PUBKEY_SIZE]> for PublicKey {
fn from(v: [u8; PUBKEY_SIZE]) -> Self {
Self(v)
}
}
impl MaxEncodedLen for PublicKey {
fn max_encoded_len() -> usize {
PUBKEY_SIZE
}
}
#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for PublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(HexVisitor::<PUBKEY_SIZE>()).map(|v| v.into())
}
}
#[cfg(feature = "std")]
impl Serialize for PublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.0)
}
}
#[derive(Copy, Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct Signature(pub [u8; SIGNATURE_SIZE]);
impl Default for Signature {
fn default() -> Self {
Signature([0u8; SIGNATURE_SIZE])
}
}
impl From<[u8; SIGNATURE_SIZE]> for Signature {
fn from(v: [u8; SIGNATURE_SIZE]) -> Self {
Self(v)
}
}
#[cfg(feature = "std")]
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(HexVisitor::<SIGNATURE_SIZE>()).map(|v| v.into())
}
}
#[derive(Copy, Clone, Default, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct ExecutionHeaderState {
pub beacon_block_root: H256,
pub beacon_slot: u64,
pub block_hash: H256,
pub block_number: u64,
}
#[derive(Copy, Clone, Default, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct FinalizedHeaderState {
pub beacon_block_root: H256,
pub beacon_slot: u64,
}
#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug)]
pub struct ForkData {
// 1 or 0 bit, indicates whether a sync committee participated in a vote
pub current_version: [u8; 4],
pub genesis_validators_root: [u8; 32],
}
impl ForkData {
pub fn hash_tree_root(&self) -> Result<H256, SimpleSerializeError> {
hash_tree_root::<SSZForkData>(self.clone().into())
}
}
#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug)]
pub struct SigningData {
pub object_root: H256,
pub domain: H256,
}
impl SigningData {
pub fn hash_tree_root(&self) -> Result<H256, SimpleSerializeError> {
hash_tree_root::<SSZSigningData>(self.clone().into())
}
}
/// Sync committee as it is stored in the runtime storage.
#[derive(
Encode, Decode, PartialEqNoBound, CloneNoBound, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,
)]
#[cfg_attr(
feature = "std",
derive(Serialize, Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
)]
#[codec(mel_bound())]
pub struct SyncCommittee<const COMMITTEE_SIZE: usize> {
#[cfg_attr(feature = "std", serde(with = "crate::serde_utils::arrays"))]
pub pubkeys: [PublicKey; COMMITTEE_SIZE],
pub aggregate_pubkey: PublicKey,
}
impl<const COMMITTEE_SIZE: usize> Default for SyncCommittee<COMMITTEE_SIZE> {
fn default() -> Self {
SyncCommittee {
pubkeys: [Default::default(); COMMITTEE_SIZE],
aggregate_pubkey: Default::default(),
}
}
}
impl<const COMMITTEE_SIZE: usize> SyncCommittee<COMMITTEE_SIZE> {
pub fn hash_tree_root(&self) -> Result<H256, SimpleSerializeError> {
hash_tree_root::<SSZSyncCommittee<COMMITTEE_SIZE>>(self.clone().into())
}
}
/// Prepared G1 public key of sync committee as it is stored in the runtime storage.
#[derive(Clone, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct SyncCommitteePrepared<const COMMITTEE_SIZE: usize> {
pub root: H256,
pub pubkeys: Box<[PublicKeyPrepared; COMMITTEE_SIZE]>,
pub aggregate_pubkey: PublicKeyPrepared,
}
impl<const COMMITTEE_SIZE: usize> Default for SyncCommitteePrepared<COMMITTEE_SIZE> {
fn default() -> Self {
SyncCommitteePrepared {
root: H256::default(),
pubkeys: Box::new([PublicKeyPrepared::default(); COMMITTEE_SIZE]),
aggregate_pubkey: PublicKeyPrepared::default(),
}
}
}
impl<const COMMITTEE_SIZE: usize> TryFrom<&SyncCommittee<COMMITTEE_SIZE>>
for SyncCommitteePrepared<COMMITTEE_SIZE>
{
type Error = BlsError;
fn try_from(sync_committee: &SyncCommittee<COMMITTEE_SIZE>) -> Result<Self, Self::Error> {
let g1_pubkeys = prepare_g1_pubkeys(&sync_committee.pubkeys)?;
let sync_committee_root = sync_committee.hash_tree_root().expect("checked statically; qed");
Ok(SyncCommitteePrepared::<COMMITTEE_SIZE> {
pubkeys: g1_pubkeys.try_into().expect("checked statically; qed"),
aggregate_pubkey: prepare_milagro_pubkey(&sync_committee.aggregate_pubkey)?,
root: sync_committee_root,
})
}
}
/// Beacon block header as it is stored in the runtime storage. The block root is the
/// Merkleization of a BeaconHeader.
#[derive(
Copy, Clone, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct BeaconHeader {
// The slot for which this block is created. Must be greater than the slot of the block defined
// by parent root.
pub slot: u64,
// The index of the validator that proposed the block.
pub proposer_index: ValidatorIndex,
// The block root of the parent block, forming a block chain.
pub parent_root: H256,
// The hash root of the post state of running the state transition through this block.
pub state_root: H256,
// The hash root of the beacon block body
pub body_root: H256,
}
impl BeaconHeader {
pub fn hash_tree_root(&self) -> Result<H256, SimpleSerializeError> {
hash_tree_root::<SSZBeaconBlockHeader>((*self).into())
}
}
#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)]
#[cfg_attr(
feature = "std",
derive(Deserialize),
serde(
try_from = "IntermediateSyncAggregate",
deny_unknown_fields,
bound(serialize = ""),
bound(deserialize = "")
)
)]
#[codec(mel_bound())]
pub struct SyncAggregate<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize> {
pub sync_committee_bits: [u8; COMMITTEE_BITS_SIZE],
pub sync_committee_signature: Signature,
}
impl<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize> Default
for SyncAggregate<COMMITTEE_SIZE, COMMITTEE_BITS_SIZE>
{
fn default() -> Self {
SyncAggregate {
sync_committee_bits: [0; COMMITTEE_BITS_SIZE],
sync_committee_signature: Default::default(),
}
}
}
impl<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize>
SyncAggregate<COMMITTEE_SIZE, COMMITTEE_BITS_SIZE>
{
pub fn hash_tree_root(&self) -> Result<H256, SimpleSerializeError> {
hash_tree_root::<SSZSyncAggregate<COMMITTEE_SIZE>>(self.clone().into())
}
}
/// Serde deserialization helper for SyncAggregate
#[cfg(feature = "std")]
#[derive(Deserialize)]
struct IntermediateSyncAggregate {
#[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_hex_to_bytes"))]
pub sync_committee_bits: Vec<u8>,
pub sync_committee_signature: Signature,
}
#[cfg(feature = "std")]
impl<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize>
TryFrom<IntermediateSyncAggregate> for SyncAggregate<COMMITTEE_SIZE, COMMITTEE_BITS_SIZE>
{
type Error = String;
fn try_from(other: IntermediateSyncAggregate) -> Result<Self, Self::Error> {
Ok(Self {
sync_committee_bits: other
.sync_committee_bits
.try_into()
.map_err(|_| "unexpected length".to_owned())?,
sync_committee_signature: other.sync_committee_signature,
})
}
}
/// ExecutionPayloadHeader
/// <https://github.com/ethereum/annotated-spec/blob/master/capella/beacon-chain.md#executionpayloadheader>
#[derive(
Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo,
)]
#[cfg_attr(
feature = "std",
derive(Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
)]
#[codec(mel_bound())]
pub struct ExecutionPayloadHeader {
pub parent_hash: H256,
pub fee_recipient: H160,
pub state_root: H256,
pub receipts_root: H256,
#[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_hex_to_bytes"))]
pub logs_bloom: Vec<u8>,
pub prev_randao: H256,
pub block_number: u64,
pub gas_limit: u64,
pub gas_used: u64,
pub timestamp: u64,
#[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_hex_to_bytes"))]
pub extra_data: Vec<u8>,
#[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_int_to_u256"))]
pub base_fee_per_gas: U256,
pub block_hash: H256,
pub transactions_root: H256,
pub withdrawals_root: H256,
}
impl ExecutionPayloadHeader {
pub fn hash_tree_root(&self) -> Result<H256, SimpleSerializeError> {
hash_tree_root::<SSZExecutionPayloadHeader>(self.clone().try_into()?)
}
}
#[derive(
Default,
Encode,
Decode,
CloneNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
MaxEncodedLen,
)]
pub struct CompactExecutionHeader {
pub parent_hash: H256,
#[codec(compact)]
pub block_number: u64,
pub state_root: H256,
pub receipts_root: H256,
}
impl From<ExecutionPayloadHeader> for CompactExecutionHeader {
fn from(execution_payload: ExecutionPayloadHeader) -> Self {
Self {
parent_hash: execution_payload.parent_hash,
block_number: execution_payload.block_number,
state_root: execution_payload.state_root,
receipts_root: execution_payload.receipts_root,
}
}
}
#[derive(
Default,
Encode,
Decode,
Copy,
Clone,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
MaxEncodedLen,
)]
pub struct CompactBeaconState {
#[codec(compact)]
pub slot: u64,
pub block_roots_root: H256,
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
#[test]
pub fn test_hash_beacon_header1() {
let hash_root = BeaconHeader {
slot: 3,
proposer_index: 2,
parent_root: hex!("796ea53efb534eab7777809cc5ee2d84e7f25024b9d0c4d7e5bcaab657e4bdbd")
.into(),
state_root: hex!("ba3ff080912be5c9c158b2e962c1b39a91bc0615762ba6fa2ecacafa94e9ae0a")
.into(),
body_root: hex!("a18d7fcefbb74a177c959160e0ee89c23546482154e6831237710414465dcae5")
.into(),
}
.hash_tree_root();
assert!(hash_root.is_ok());
assert_eq!(
hash_root.unwrap(),
hex!("7d42595818709e805dd2fa710a2d2c1f62576ef1ab7273941ac9130fb94b91f7").into()
);
}
#[test]
pub fn test_hash_beacon_header2() {
let hash_root = BeaconHeader {
slot: 3476424,
proposer_index: 314905,
parent_root: hex!("c069d7b49cffd2b815b0fb8007eb9ca91202ea548df6f3db60000f29b2489f28")
.into(),
state_root: hex!("444d293e4533501ee508ad608783a7d677c3c566f001313e8a02ce08adf590a3")
.into(),
body_root: hex!("6508a0241047f21ba88f05d05b15534156ab6a6f8e029a9a5423da429834e04a")
.into(),
}
.hash_tree_root();
assert!(hash_root.is_ok());
assert_eq!(
hash_root.unwrap(),
hex!("0aa41166ff01e58e111ac8c42309a738ab453cf8d7285ed8477b1c484acb123e").into()
);
}
#[test]
pub fn test_hash_fork_data() {
let hash_root = ForkData {
current_version: hex!("83f38a34"),
genesis_validators_root: hex!(
"22370bbbb358800f5711a10ea9845284272d8493bed0348cab87b8ab1e127930"
),
}
.hash_tree_root();
assert!(hash_root.is_ok());
assert_eq!(
hash_root.unwrap(),
hex!("57c12c4246bc7152b174b51920506bf943eff9c7ffa50b9533708e9cc1f680fc").into()
);
}
#[test]
pub fn test_hash_signing_data() {
let hash_root = SigningData {
object_root: hex!("63654cbe64fc07853f1198c165dd3d49c54fc53bc417989bbcc66da15f850c54")
.into(),
domain: hex!("037da907d1c3a03c0091b2254e1480d9b1783476e228ab29adaaa8f133e08f7a").into(),
}
.hash_tree_root();
assert!(hash_root.is_ok());
assert_eq!(
hash_root.unwrap(),
hex!("b9eb2caf2d691b183c2d57f322afe505c078cd08101324f61c3641714789a54e").into()
);
}
#[test]
pub fn test_hash_sync_aggregate() {
let hash_root = SyncAggregate::<512, 64>{
sync_committee_bits: hex!("cefffffefffffff767fffbedffffeffffeeffdffffdebffffff7f7dbdf7fffdffffbffcfffdff79dfffbbfefff2ffffff7ddeff7ffffc98ff7fbfffffffffff7"),
sync_committee_signature: hex!("8af1a8577bba419fe054ee49b16ed28e081dda6d3ba41651634685e890992a0b675e20f8d9f2ec137fe9eb50e838aa6117f9f5410e2e1024c4b4f0e098e55144843ce90b7acde52fe7b94f2a1037342c951dc59f501c92acf7ed944cb6d2b5f7").into(),
}.hash_tree_root();
assert!(hash_root.is_ok());
assert_eq!(
hash_root.unwrap(),
hex!("e6dcad4f60ce9ff8a587b110facbaf94721f06cd810b6d8bf6cffa641272808d").into()
);
}
#[test]
pub fn test_hash_execution_payload() {
let hash_root =
ExecutionPayloadHeader{
parent_hash: hex!("eadee5ab098dde64e9fd02ae5858064bad67064070679625b09f8d82dec183f7").into(),
fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
state_root: hex!("564fa064c2a324c2b5978d7fdfc5d4224d4f421a45388af1ed405a399c845dff").into(),
receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(),
logs_bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec(),
prev_randao: hex!("6bf538bdfbdf1c96ff528726a40658a91d0bda0f1351448c4c4f3604db2a0ccf").into(),
block_number: 477434,
gas_limit: 8154925,
gas_used: 0,
timestamp: 1652816940,
extra_data: vec![],
base_fee_per_gas: U256::from(7_i16),
block_hash: hex!("cd8df91b4503adb8f2f1c7a4f60e07a1f1a2cbdfa2a95bceba581f3ff65c1968").into(),
transactions_root: hex!("7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1").into(),
withdrawals_root: hex!("28ba1834a3a7b657460ce79fa3a1d909ab8828fd557659d4d0554a9bdbc0ec30").into(),
}.hash_tree_root();
assert!(hash_root.is_ok());
}
}
/// Operating modes for beacon client
#[derive(Encode, Decode, Copy, Clone, PartialEq, RuntimeDebug, TypeInfo)]
pub enum Mode {
Active,
Blocked,
}
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use codec::{Decode, Encode};
use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound};
use scale_info::TypeInfo;
use sp_core::H256;
use sp_std::prelude::*;
use crate::types::{BeaconHeader, ExecutionPayloadHeader, SyncAggregate, SyncCommittee};
#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)]
#[cfg_attr(
feature = "std",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
)]
pub struct CheckpointUpdate<const COMMITTEE_SIZE: usize> {
pub header: BeaconHeader,
pub current_sync_committee: SyncCommittee<COMMITTEE_SIZE>,
pub current_sync_committee_branch: Vec<H256>,
pub validators_root: H256,
pub block_roots_root: H256,
pub block_roots_branch: Vec<H256>,
}
impl<const COMMITTEE_SIZE: usize> Default for CheckpointUpdate<COMMITTEE_SIZE> {
fn default() -> Self {
CheckpointUpdate {
header: Default::default(),
current_sync_committee: Default::default(),
current_sync_committee_branch: Default::default(),
validators_root: Default::default(),
block_roots_root: Default::default(),
block_roots_branch: Default::default(),
}
}
}
#[derive(
Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo,
)]
#[cfg_attr(
feature = "std",
derive(serde::Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
)]
pub struct Update<const COMMITTEE_SIZE: usize, const COMMITTEE_BITS_SIZE: usize> {
/// A recent header attesting to the finalized header, using its `state_root`.
pub attested_header: BeaconHeader,
/// The signing data that the sync committee produced for this attested header, including
/// who participated in the vote and the resulting signature.
pub sync_aggregate: SyncAggregate<COMMITTEE_SIZE, COMMITTEE_BITS_SIZE>,
/// The slot at which the sync aggregate can be found, typically attested_header.slot + 1, if
/// the next slot block was not missed.
pub signature_slot: u64,
/// The next sync committee for the next sync committee period, if present.
pub next_sync_committee_update: Option<NextSyncCommitteeUpdate<COMMITTEE_SIZE>>,
/// The latest finalized header.
pub finalized_header: BeaconHeader,
/// The merkle proof testifying to the finalized header, using the `attested_header.state_root`
/// as tree root.
pub finality_branch: Vec<H256>,
/// The finalized_header's `block_roots` root in the beacon state, used for ancestry proofs.
pub block_roots_root: H256,
/// The merkle path to prove the `block_roots_root` value.
pub block_roots_branch: Vec<H256>,
}
#[derive(
Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo,
)]
#[cfg_attr(
feature = "std",
derive(serde::Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
)]
pub struct NextSyncCommitteeUpdate<const COMMITTEE_SIZE: usize> {
pub next_sync_committee: SyncCommittee<COMMITTEE_SIZE>,
pub next_sync_committee_branch: Vec<H256>,
}
#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)]
#[cfg_attr(
feature = "std",
derive(serde::Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
)]
pub struct ExecutionHeaderUpdate {
/// Header for the beacon block containing the execution payload
pub header: BeaconHeader,
/// Proof that `header` is an ancestor of a finalized header
pub ancestry_proof: Option<AncestryProof>,
/// Execution header to be imported
pub execution_header: ExecutionPayloadHeader,
/// Merkle proof that execution payload is contained within `header`
pub execution_branch: Vec<H256>,
}
#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)]
#[cfg_attr(
feature = "std",
derive(serde::Deserialize),
serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))
)]
pub struct AncestryProof {
/// Merkle proof that `header` is an ancestor of `finalized_header`
pub header_branch: Vec<H256>,
/// Root of a finalized block that has already been imported into the light client
pub finalized_block_root: H256,
}