Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions contracts/near/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contracts/near/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ tree_hash = "0.8"
tree_hash_derive = "0.8"
ethereum_ssz = "0.7"
ethereum_ssz_derive = "0.7"
ethereum_serde_utils = "0.7"
ethereum_serde_utils = "0.8"
ethereum_hashing = "0.7.0"
derive_more = "^0.99.2"
hex = "0.4.2"
Expand Down
213 changes: 182 additions & 31 deletions contracts/near/eth-types/src/eth2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,74 @@ arr_wrapper_impl_tree_hash_and_borsh!(PublicKeyBytes, PUBLIC_KEY_BYTES_LEN);
arr_wrapper_impl_tree_hash_and_borsh!(SignatureBytes, SIGNATURE_BYTES_LEN);
arr_wrapper_impl_tree_hash_and_borsh!(SyncCommitteeBits, SYNC_COMMITTEE_BITS_SIZE_IN_BYTES);

#[derive(Debug, Clone, BorshSchema)]
pub struct ExtraData(pub Vec<u8>);

impl tree_hash::TreeHash for ExtraData {
fn tree_hash_type() -> tree_hash::TreeHashType {
tree_hash::TreeHashType::List
}

fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding {
unreachable!("List should never be packed.")
}

fn tree_hash_packing_factor() -> usize {
unreachable!("List should never be packed.")
}

fn tree_hash_root(&self) -> tree_hash::Hash256 {
let mut hasher =
tree_hash::MerkleHasher::with_leaves(self.0.len().div_ceil(tree_hash::BYTES_PER_CHUNK));

for item in &self.0 {
hasher.write(&item.tree_hash_packed_encoding()).unwrap();
}

let root = hasher.finish().unwrap();
tree_hash::mix_in_length(&root, self.0.len())
}
}

// Add Borsh implementations
impl borsh::BorshSerialize for ExtraData {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
BorshSerialize::serialize(&self.0, writer)
}
}

impl borsh::BorshDeserialize for ExtraData {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
Ok(ExtraData(Vec::<u8>::deserialize_reader(reader)?))
}
}

#[cfg(not(target_arch = "wasm32"))]
impl serde::Serialize for ExtraData {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
// Always serialize as hex string
let hex_string = format!("0x{}", hex::encode(&self.0));
serializer.serialize_str(&hex_string)
}
}

#[cfg(not(target_arch = "wasm32"))]
impl<'de> serde::Deserialize<'de> for ExtraData {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let hex_string = <std::string::String as Deserialize>::deserialize(deserializer)?;
let hex_string = hex_string.strip_prefix("0x").unwrap_or(&hex_string);
let bytes = hex::decode(hex_string)
.map_err(|e| serde::de::Error::custom(format!("Invalid hex: {}", e)))?;
Ok(ExtraData(bytes))
}
}

#[derive(
Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize, tree_hash_derive::TreeHash,
)]
Expand All @@ -43,18 +111,62 @@ pub struct BeaconBlockHeader {
pub body_root: H256,
}

// New execution header structure for Electra
#[derive(
Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize, tree_hash_derive::TreeHash,
)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct ExecutionHeader {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this copied copied instead of using the BlockHeader https://github.com/Near-One/rainbow-bridge/blob/691af191297eb6d67fd637dedfe79f3315b8a8e9/contracts/near/eth-types/src/lib.rs#L150?

We should somehow avoid reimplementing the same block header, we already have 3-4 different implementations

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a different set of fields in the Beacon's Execution Header:

Concept ExecutionHeader BlockHeader Notes
Prev Randao prev_randao: H256 ❌ Not present ExecutionHeader only
Block Hash block_hash: H256 ❌ Not present ExecutionHeader only
Uncles Hash ❌ Not present uncles_hash: H256 BlockHeader only
Difficulty ❌ Not present difficulty: U256 BlockHeader only
Mix Hash ❌ Not present mix_hash: H256 BlockHeader only
Nonce ❌ Not present nonce: H64 BlockHeader only
Parent Beacon Block Root ❌ Not present parent_beacon_block_root: Option BlockHeader only
Requests Hash ❌ Not present requests_hash: Option BlockHeader only
Hash ❌ Not present hash: Option BlockHeader only
Partial Hash ❌ Not present partial_hash: Option BlockHeader only

pub parent_hash: H256,
pub fee_recipient: H160,
pub state_root: H256,
pub receipts_root: H256,
pub logs_bloom: Bloom,
pub prev_randao: H256,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub block_number: u64,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub gas_limit: u64,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub gas_used: u64,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub timestamp: u64,
pub extra_data: ExtraData,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub base_fee_per_gas: u64,
pub block_hash: H256,
pub transactions_root: H256,
pub withdrawals_root: H256,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub blob_gas_used: u64,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub excess_blob_gas: u64,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will we handle optional fields that may be added after the new network upgrade? or, for example, if we want to run the tests for blocks without excess_blob_gas

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's why the specs have this weird implementation for get_lc_execution_root

}

// New combined header structure
#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct AttestedHeader {
pub beacon: BeaconBlockHeader,
pub execution: ExecutionHeader,
pub execution_branch: Vec<H256>,
}

#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct FinalizedHeader {
pub beacon: BeaconBlockHeader,
pub execution: ExecutionHeader,
pub execution_branch: Vec<H256>,
}

#[derive(Debug, Clone, PartialEq, tree_hash_derive::TreeHash)]
pub struct ForkData {
pub current_version: ForkVersion,
pub genesis_validators_root: H256,
}

#[derive(Debug, PartialEq, Clone, tree_hash_derive::TreeHash)]
pub struct SigningData {
pub object_root: H256,
pub domain: H256,
}

/// This is used specifically for backwards-compatibility, storing state in the contract
#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct ExtendedBeaconBlockHeader {
Expand All @@ -63,17 +175,23 @@ pub struct ExtendedBeaconBlockHeader {
pub execution_block_hash: H256,
}

impl From<HeaderUpdate> for ExtendedBeaconBlockHeader {
fn from(item: HeaderUpdate) -> Self {
let root = item.beacon_header.tree_hash_root();
ExtendedBeaconBlockHeader {
header: item.beacon_header,
beacon_block_root: H256(root.0.into()),
execution_block_hash: item.execution_block_hash,
impl From<FinalizedHeader> for ExtendedBeaconBlockHeader {
fn from(finalized_header: FinalizedHeader) -> Self {
let beacon = finalized_header.beacon;
Self {
header: beacon.clone(),
beacon_block_root: beacon.tree_hash_root().0.into(),
execution_block_hash: finalized_header.execution.block_hash,
}
}
}

#[derive(Debug, PartialEq, Clone, tree_hash_derive::TreeHash)]
pub struct SigningData {
pub object_root: H256,
pub domain: H256,
}

#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct SyncCommitteePublicKeys(pub Vec<PublicKeyBytes>);
Expand All @@ -95,37 +213,70 @@ pub struct SyncAggregate {
pub sync_committee_signature: SignatureBytes,
}

// Updated light client update structure for Electra
#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct SyncCommitteeUpdate {
pub next_sync_committee: SyncCommittee,
pub next_sync_committee_branch: Vec<H256>,
pub struct LightClientUpdate {
pub attested_header: AttestedHeader,
pub next_sync_committee: Option<SyncCommittee>,
pub next_sync_committee_branch: Option<Vec<H256>>,
pub finalized_header: FinalizedHeader,
pub finality_branch: Vec<H256>,
pub sync_aggregate: SyncAggregate,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub signature_slot: Slot,
}

#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
// Version enum for different Ethereum fork versions
#[derive(Debug, Clone, Copy, PartialEq, Eq, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct HeaderUpdate {
pub beacon_header: BeaconBlockHeader,
pub execution_block_hash: H256,
pub execution_hash_branch: Vec<H256>,
#[cfg_attr(not(target_arch = "wasm32"), serde(rename_all = "lowercase"))]
pub enum LightClientVersion {
Altair,
Bellatrix,
Capella,
Deneb,
Electra,
}

impl LightClientVersion {
pub fn as_str(&self) -> &'static str {
match self {
LightClientVersion::Altair => "altair",
LightClientVersion::Bellatrix => "bellatrix",
LightClientVersion::Capella => "capella",
LightClientVersion::Deneb => "deneb",
LightClientVersion::Electra => "electra",
}
}
}

impl std::fmt::Display for LightClientVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

// Top-level wrapper with version (optional, for when you need versioning)
#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct FinalizedHeaderUpdate {
pub header_update: HeaderUpdate,
pub finality_branch: Vec<H256>,
pub struct VersionedLightClientUpdate {
pub version: LightClientVersion,
pub data: LightClientUpdate,
}

// For arrays of light client updates
#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub struct LightClientUpdate {
pub attested_beacon_header: BeaconBlockHeader,
pub sync_aggregate: SyncAggregate,
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "serde_utils::quoted_u64"))]
pub signature_slot: Slot,
pub finality_update: FinalizedHeaderUpdate,
pub sync_committee_update: Option<SyncCommitteeUpdate>,
pub struct LightClientUpdates(pub Vec<LightClientUpdate>);

// Alternative: Version-specific data if structures differ significantly between versions
#[derive(Debug, Clone, BorshDeserialize, BorshSchema, BorshSerialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Serialize, Deserialize))]
pub enum LightClientUpdateVariant {
Electra(LightClientUpdate),
// Future versions can be added here with different data structures
// Deneb(DenebLightClientUpdate),
}

#[derive(Clone, BorshDeserialize, BorshSchema, BorshSerialize, Debug)]
Expand Down
Loading
Loading