//! Block ordering and linearization for GHOSTDAG. //! //! Even though blocks form a DAG, transactions need a linear ordering for //! execution. This module provides that ordering based on GHOSTDAG rules: //! //! 1. Blocks are ordered by their position in the selected chain //! 2. Merge set blocks are ordered after the selected parent //! 3. Within the merge set, blues come before reds //! 4. Within blues/reds, ordering is by blue score then hash use crate::{ ghostdag::GhostdagManager, BlockId, BlueScore, }; use std::cmp::Ordering; use thiserror::Error; /// An ordered block with its position and metadata. #[derive(Clone, Debug)] pub struct OrderedBlock { /// The block ID. pub id: BlockId, /// Position in the linear ordering. pub position: u64, /// The block's blue score. pub blue_score: BlueScore, /// Whether this block is blue (in the main chain). pub is_blue: bool, /// Whether this block is in a merge set (not on selected chain). pub is_merge_block: bool, } impl OrderedBlock { /// Creates a new ordered block. pub fn new( id: BlockId, position: u64, blue_score: BlueScore, is_blue: bool, is_merge_block: bool, ) -> Self { OrderedBlock { id, position, blue_score, is_blue, is_merge_block, } } } /// Block ordering manager. pub struct BlockOrdering { /// Reference to the GHOSTDAG manager. ghostdag: std::sync::Arc, } impl BlockOrdering { /// Creates a new block ordering manager. pub fn new(ghostdag: std::sync::Arc) -> Self { BlockOrdering { ghostdag } } /// Gets the linear ordering of blocks from a given block back to genesis. /// /// This is the canonical ordering used for transaction execution. pub fn get_ordering(&self, from: &BlockId) -> Result, OrderingError> { let mut ordering = Vec::new(); let mut position = 0u64; // Get the selected chain first let selected_chain = self.ghostdag.get_selected_chain(from)?; // Process in reverse (genesis to tip) for chain_block in selected_chain.into_iter().rev() { let data = self.ghostdag.get_data(&chain_block)?; // First add the selected chain block ordering.push(OrderedBlock::new( chain_block, position, data.blue_score, true, false, // Not a merge block )); position += 1; // Then add merge set (blues first, then reds) let mut merge_blocks = Vec::new(); // Add blues for blue in &data.merge_set_blues { let blue_score = self.ghostdag.get_blue_score(blue).unwrap_or(0); merge_blocks.push((*blue, blue_score, true)); } // Add reds for red in &data.merge_set_reds { let blue_score = self.ghostdag.get_blue_score(red).unwrap_or(0); merge_blocks.push((*red, blue_score, false)); } // Sort: blues before reds, then by blue score (desc), then by hash merge_blocks.sort_by(|a, b| { // Blues come first match (a.2, b.2) { (true, false) => Ordering::Less, (false, true) => Ordering::Greater, _ => { // Same color: sort by blue score descending match b.1.cmp(&a.1) { Ordering::Equal => { // Same score: sort by hash for determinism a.0.cmp(&b.0) } other => other, } } } }); // Add sorted merge blocks for (block_id, blue_score, is_blue) in merge_blocks { ordering.push(OrderedBlock::new( block_id, position, blue_score, is_blue, true, // Is a merge block )); position += 1; } } Ok(ordering) } /// Gets only the blocks that need to be processed between two points. /// /// Returns blocks that are in `to`'s past but not in `from`'s past. pub fn get_diff(&self, from: &BlockId, to: &BlockId) -> Result, OrderingError> { let from_ordering = self.get_ordering(from)?; let to_ordering = self.get_ordering(to)?; // Get all blocks in from's past let from_blocks: std::collections::HashSet<_> = from_ordering.iter().map(|b| b.id).collect(); // Return blocks in to's past that aren't in from's past let diff: Vec<_> = to_ordering .into_iter() .filter(|b| !from_blocks.contains(&b.id)) .collect(); Ok(diff) } /// Checks if block A comes before block B in the canonical ordering. pub fn is_before(&self, a: &BlockId, b: &BlockId) -> Result { let ordering = self.get_ordering(b)?; let pos_a = ordering.iter().position(|block| &block.id == a); let pos_b = ordering.iter().position(|block| &block.id == b); match (pos_a, pos_b) { (Some(pa), Some(pb)) => Ok(pa < pb), (Some(_), None) => Ok(true), // A is in past of B (None, Some(_)) => Ok(false), // A is not in past of B (None, None) => Err(OrderingError::BlocksNotRelated(*a, *b)), } } /// Gets the merge set at a specific block in canonical order. pub fn get_merge_set_ordered(&self, block_id: &BlockId) -> Result, OrderingError> { let data = self.ghostdag.get_data(block_id)?; let mut merge_blocks = Vec::new(); let mut position = 0u64; // Add blues for blue in &data.merge_set_blues { let blue_score = self.ghostdag.get_blue_score(blue).unwrap_or(0); merge_blocks.push(OrderedBlock::new(*blue, position, blue_score, true, true)); position += 1; } // Add reds for red in &data.merge_set_reds { let blue_score = self.ghostdag.get_blue_score(red).unwrap_or(0); merge_blocks.push(OrderedBlock::new(*red, position, blue_score, false, true)); position += 1; } // Sort by blue score and hash merge_blocks.sort_by(|a, b| { match (a.is_blue, b.is_blue) { (true, false) => Ordering::Less, (false, true) => Ordering::Greater, _ => match b.blue_score.cmp(&a.blue_score) { Ordering::Equal => a.id.cmp(&b.id), other => other, }, } }); // Update positions after sorting for (i, block) in merge_blocks.iter_mut().enumerate() { block.position = i as u64; } Ok(merge_blocks) } } impl std::fmt::Debug for BlockOrdering { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BlockOrdering").finish() } } /// Errors that can occur during block ordering. #[derive(Debug, Error)] pub enum OrderingError { #[error("GHOSTDAG error: {0}")] GhostdagError(#[from] crate::ghostdag::GhostdagError), #[error("Blocks not related: {0} and {1}")] BlocksNotRelated(BlockId, BlockId), #[error("Block not found: {0}")] BlockNotFound(BlockId), } /// Iterator that yields blocks in canonical order. pub struct OrderingIterator<'a> { ordering: &'a BlockOrdering, current_chain: Vec, chain_index: usize, merge_set_queue: Vec, } impl<'a> OrderingIterator<'a> { /// Creates a new ordering iterator starting from the given block. pub fn new(ordering: &'a BlockOrdering, from: BlockId) -> Result { let selected_chain = ordering.ghostdag.get_selected_chain(&from)?; Ok(OrderingIterator { ordering, current_chain: selected_chain.into_iter().rev().collect(), // Reverse to go genesis -> tip chain_index: 0, merge_set_queue: Vec::new(), }) } } impl<'a> Iterator for OrderingIterator<'a> { type Item = Result; fn next(&mut self) -> Option { // First drain the merge set queue if let Some(block) = self.merge_set_queue.pop() { return Some(Ok(block)); } // Get next block from chain if self.chain_index >= self.current_chain.len() { return None; } let chain_block = self.current_chain[self.chain_index]; self.chain_index += 1; // Get GHOSTDAG data for this block let data = match self.ordering.ghostdag.get_data(&chain_block) { Ok(d) => d, Err(e) => return Some(Err(e.into())), }; // Queue up the merge set (in reverse order so we pop in correct order) match self.ordering.get_merge_set_ordered(&chain_block) { Ok(merge_set) => { self.merge_set_queue = merge_set.into_iter().rev().collect(); } Err(e) => return Some(Err(e)), } // Return the chain block Some(Ok(OrderedBlock::new( chain_block, (self.chain_index - 1) as u64, data.blue_score, true, false, ))) } } #[cfg(test)] mod tests { use super::*; use crate::{dag::BlockDag, reachability::ReachabilityStore}; use synor_types::Hash256; fn make_block_id(n: u8) -> BlockId { let mut bytes = [0u8; 32]; bytes[0] = n; Hash256::from_bytes(bytes) } fn setup_test() -> ( std::sync::Arc, std::sync::Arc, std::sync::Arc, BlockOrdering, ) { let genesis = make_block_id(0); let dag = std::sync::Arc::new(BlockDag::new(genesis, 0)); let reachability = std::sync::Arc::new(ReachabilityStore::new(genesis)); let ghostdag = std::sync::Arc::new(GhostdagManager::with_k( dag.clone(), reachability.clone(), 3, )); let ordering = BlockOrdering::new(ghostdag.clone()); (dag, reachability, ghostdag, ordering) } #[test] fn test_simple_chain_ordering() { let genesis = make_block_id(0); let (dag, reachability, ghostdag, ordering) = setup_test(); // Build a simple chain let block1 = make_block_id(1); let block2 = make_block_id(2); dag.insert_block(block1, vec![genesis], 100) .unwrap(); dag.insert_block(block2, vec![block1], 200) .unwrap(); reachability.add_block(block1, genesis, &[genesis]).unwrap(); reachability.add_block(block2, block1, &[block1]).unwrap(); ghostdag.add_block(block1, &[genesis]).unwrap(); ghostdag.add_block(block2, &[block1]).unwrap(); let order = ordering.get_ordering(&block2).unwrap(); // Should be: genesis, block1, block2 assert_eq!(order.len(), 3); assert_eq!(order[0].id, genesis); assert_eq!(order[1].id, block1); assert_eq!(order[2].id, block2); } #[test] fn test_is_before() { let genesis = make_block_id(0); let (dag, reachability, ghostdag, ordering) = setup_test(); let block1 = make_block_id(1); let block2 = make_block_id(2); dag.insert_block(block1, vec![genesis], 100) .unwrap(); dag.insert_block(block2, vec![block1], 200) .unwrap(); reachability.add_block(block1, genesis, &[genesis]).unwrap(); reachability.add_block(block2, block1, &[block1]).unwrap(); ghostdag.add_block(block1, &[genesis]).unwrap(); ghostdag.add_block(block2, &[block1]).unwrap(); assert!(ordering.is_before(&genesis, &block2).unwrap()); assert!(ordering.is_before(&block1, &block2).unwrap()); assert!(!ordering.is_before(&block2, &genesis).unwrap()); } #[test] fn test_ordering_positions() { let genesis = make_block_id(0); let (dag, reachability, ghostdag, ordering) = setup_test(); let block1 = make_block_id(1); dag.insert_block(block1, vec![genesis], 100) .unwrap(); reachability.add_block(block1, genesis, &[genesis]).unwrap(); ghostdag.add_block(block1, &[genesis]).unwrap(); let order = ordering.get_ordering(&block1).unwrap(); // Check positions are sequential for (i, block) in order.iter().enumerate() { assert_eq!(block.position, i as u64); } } }