mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 02:49:55 +00:00
127 lines
4.0 KiB
Rust
127 lines
4.0 KiB
Rust
use alloy_primitives::{B256, BlockNumber};
|
|
use reth_provider::{BlockNumReader, ProviderError};
|
|
use std::cmp::Ordering;
|
|
|
|
/// Errors that can occur in Hl consensus
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum HlConsensusErr {
|
|
/// Error from the provider
|
|
#[error(transparent)]
|
|
Provider(#[from] ProviderError),
|
|
/// Head block hash not found
|
|
#[error("Head block hash not found")]
|
|
HeadHashNotFound,
|
|
}
|
|
|
|
/// Hl consensus implementation
|
|
pub struct HlConsensus<P> {
|
|
/// The provider for reading block information
|
|
pub provider: P,
|
|
}
|
|
|
|
impl<P> HlConsensus<P>
|
|
where
|
|
P: BlockNumReader + Clone,
|
|
{
|
|
/// Determines the head block hash according to Hl consensus rules:
|
|
/// 1. Follow the highest block number
|
|
/// 2. For same height blocks, pick the one with lower hash
|
|
pub(crate) fn canonical_head(
|
|
&self,
|
|
hash: B256,
|
|
number: BlockNumber,
|
|
) -> Result<(B256, B256), HlConsensusErr> {
|
|
let current_head = self.provider.best_block_number()?;
|
|
let current_hash =
|
|
self.provider.block_hash(current_head)?.ok_or(HlConsensusErr::HeadHashNotFound)?;
|
|
|
|
match number.cmp(¤t_head) {
|
|
Ordering::Greater => Ok((hash, current_hash)),
|
|
Ordering::Equal => Ok((hash.min(current_hash), current_hash)),
|
|
Ordering::Less => Ok((current_hash, current_hash)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use alloy_primitives::hex;
|
|
use reth_chainspec::ChainInfo;
|
|
use reth_provider::BlockHashReader;
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Clone)]
|
|
struct MockProvider {
|
|
blocks: HashMap<BlockNumber, B256>,
|
|
head_number: BlockNumber,
|
|
head_hash: B256,
|
|
}
|
|
|
|
impl MockProvider {
|
|
fn new(head_number: BlockNumber, head_hash: B256) -> Self {
|
|
let mut blocks = HashMap::new();
|
|
blocks.insert(head_number, head_hash);
|
|
Self { blocks, head_number, head_hash }
|
|
}
|
|
}
|
|
|
|
impl BlockHashReader for MockProvider {
|
|
fn block_hash(&self, number: BlockNumber) -> Result<Option<B256>, ProviderError> {
|
|
Ok(self.blocks.get(&number).copied())
|
|
}
|
|
|
|
fn canonical_hashes_range(
|
|
&self,
|
|
_start: BlockNumber,
|
|
_end: BlockNumber,
|
|
) -> Result<Vec<B256>, ProviderError> {
|
|
Ok(vec![])
|
|
}
|
|
}
|
|
|
|
impl BlockNumReader for MockProvider {
|
|
fn chain_info(&self) -> Result<ChainInfo, ProviderError> {
|
|
Ok(ChainInfo { best_hash: self.head_hash, best_number: self.head_number })
|
|
}
|
|
|
|
fn best_block_number(&self) -> Result<BlockNumber, ProviderError> {
|
|
Ok(self.head_number)
|
|
}
|
|
|
|
fn last_block_number(&self) -> Result<BlockNumber, ProviderError> {
|
|
Ok(self.head_number)
|
|
}
|
|
|
|
fn block_number(&self, hash: B256) -> Result<Option<BlockNumber>, ProviderError> {
|
|
Ok(self.blocks.iter().find_map(|(num, h)| (*h == hash).then_some(*num)))
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_canonical_head() {
|
|
let hash1 = B256::from_slice(&hex!(
|
|
"1111111111111111111111111111111111111111111111111111111111111111"
|
|
));
|
|
let hash2 = B256::from_slice(&hex!(
|
|
"2222222222222222222222222222222222222222222222222222222222222222"
|
|
));
|
|
|
|
let test_cases = [
|
|
((hash1, 2, 1, hash2), hash1), // Higher block wins
|
|
((hash1, 1, 2, hash2), hash2), // Lower block stays
|
|
((hash1, 1, 1, hash2), hash1), // Same height, lower hash wins
|
|
((hash2, 1, 1, hash1), hash1), // Same height, lower hash stays
|
|
];
|
|
|
|
for ((curr_hash, curr_num, head_num, head_hash), expected) in test_cases {
|
|
let provider = MockProvider::new(head_num, head_hash);
|
|
let consensus = HlConsensus { provider };
|
|
let (head_block_hash, current_hash) =
|
|
consensus.canonical_head(curr_hash, curr_num).unwrap();
|
|
assert_eq!(head_block_hash, expected);
|
|
assert_eq!(current_hash, head_hash);
|
|
}
|
|
}
|
|
}
|