583 lines
18 KiB
Rust
583 lines
18 KiB
Rust
//! Core DAG data structure for block relationships.
|
|
//!
|
|
//! Maintains parent-child relationships between blocks and provides
|
|
//! efficient queries for traversal.
|
|
|
|
use crate::{BlockId, BlueScore, DaaScore, MAX_BLOCK_PARENTS};
|
|
use hashbrown::{HashMap, HashSet};
|
|
use parking_lot::RwLock;
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
/// Relations for a single block in the DAG.
|
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
|
pub struct BlockRelations {
|
|
/// Parent block IDs (blocks this block references).
|
|
pub parents: Vec<BlockId>,
|
|
/// Child block IDs (blocks that reference this block).
|
|
pub children: Vec<BlockId>,
|
|
}
|
|
|
|
impl BlockRelations {
|
|
/// Creates new relations with the given parents.
|
|
pub fn new(parents: Vec<BlockId>) -> Self {
|
|
BlockRelations {
|
|
parents,
|
|
children: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Returns true if this block has no parents (genesis).
|
|
pub fn is_genesis(&self) -> bool {
|
|
self.parents.is_empty()
|
|
}
|
|
|
|
/// Returns the number of parents.
|
|
pub fn parent_count(&self) -> usize {
|
|
self.parents.len()
|
|
}
|
|
|
|
/// Returns the number of children.
|
|
pub fn child_count(&self) -> usize {
|
|
self.children.len()
|
|
}
|
|
}
|
|
|
|
/// Metadata stored for each block in the DAG.
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct BlockMeta {
|
|
/// Block relations (parents/children).
|
|
pub relations: BlockRelations,
|
|
/// Blue score (cumulative blue ancestor count).
|
|
pub blue_score: BlueScore,
|
|
/// DAA score (difficulty adjustment score).
|
|
pub daa_score: DaaScore,
|
|
/// Timestamp when block was received.
|
|
pub timestamp: u64,
|
|
/// Whether this block is in the blue set.
|
|
pub is_blue: bool,
|
|
/// The selected parent (main chain continuation).
|
|
pub selected_parent: Option<BlockId>,
|
|
/// Merge set blues (blue blocks merged at this block).
|
|
pub merge_set_blues: Vec<BlockId>,
|
|
/// Merge set reds (red blocks merged at this block).
|
|
pub merge_set_reds: Vec<BlockId>,
|
|
}
|
|
|
|
impl BlockMeta {
|
|
/// Creates metadata for a new block.
|
|
pub fn new(parents: Vec<BlockId>, timestamp: u64) -> Self {
|
|
BlockMeta {
|
|
relations: BlockRelations::new(parents),
|
|
blue_score: 0,
|
|
daa_score: 0,
|
|
timestamp,
|
|
is_blue: false,
|
|
selected_parent: None,
|
|
merge_set_blues: Vec::new(),
|
|
merge_set_reds: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Creates metadata for the genesis block.
|
|
pub fn genesis(timestamp: u64) -> Self {
|
|
BlockMeta {
|
|
relations: BlockRelations::default(),
|
|
blue_score: 0,
|
|
daa_score: 0,
|
|
timestamp,
|
|
is_blue: true, // Genesis is always blue
|
|
selected_parent: None,
|
|
merge_set_blues: Vec::new(),
|
|
merge_set_reds: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The main DAG data structure.
|
|
pub struct BlockDag {
|
|
/// All blocks in the DAG.
|
|
blocks: RwLock<HashMap<BlockId, BlockMeta>>,
|
|
/// Tips of the DAG (blocks with no children).
|
|
tips: RwLock<HashSet<BlockId>>,
|
|
/// The genesis block ID.
|
|
genesis: BlockId,
|
|
/// Virtual genesis (for pruning).
|
|
virtual_genesis: RwLock<BlockId>,
|
|
}
|
|
|
|
impl BlockDag {
|
|
/// Creates a new DAG with the given genesis block.
|
|
pub fn new(genesis_id: BlockId, genesis_timestamp: u64) -> Self {
|
|
let mut blocks = HashMap::new();
|
|
blocks.insert(genesis_id, BlockMeta::genesis(genesis_timestamp));
|
|
|
|
let mut tips = HashSet::new();
|
|
tips.insert(genesis_id);
|
|
|
|
BlockDag {
|
|
blocks: RwLock::new(blocks),
|
|
tips: RwLock::new(tips),
|
|
genesis: genesis_id,
|
|
virtual_genesis: RwLock::new(genesis_id),
|
|
}
|
|
}
|
|
|
|
/// Returns the genesis block ID.
|
|
pub fn genesis(&self) -> BlockId {
|
|
self.genesis
|
|
}
|
|
|
|
/// Returns the current virtual genesis (for pruned DAGs).
|
|
pub fn virtual_genesis(&self) -> BlockId {
|
|
*self.virtual_genesis.read()
|
|
}
|
|
|
|
/// Returns the current tips (blocks with no children).
|
|
pub fn tips(&self) -> Vec<BlockId> {
|
|
self.tips.read().iter().copied().collect()
|
|
}
|
|
|
|
/// Returns the number of blocks in the DAG.
|
|
pub fn len(&self) -> usize {
|
|
self.blocks.read().len()
|
|
}
|
|
|
|
/// Returns true if the DAG is empty (only genesis).
|
|
pub fn is_empty(&self) -> bool {
|
|
self.len() <= 1
|
|
}
|
|
|
|
/// Checks if a block exists in the DAG.
|
|
pub fn contains(&self, block_id: &BlockId) -> bool {
|
|
self.blocks.read().contains_key(block_id)
|
|
}
|
|
|
|
/// Gets metadata for a block.
|
|
pub fn get_block(&self, block_id: &BlockId) -> Option<BlockMeta> {
|
|
self.blocks.read().get(block_id).cloned()
|
|
}
|
|
|
|
/// Gets the parents of a block.
|
|
pub fn get_parents(&self, block_id: &BlockId) -> Option<Vec<BlockId>> {
|
|
self.blocks
|
|
.read()
|
|
.get(block_id)
|
|
.map(|meta| meta.relations.parents.clone())
|
|
}
|
|
|
|
/// Gets the children of a block.
|
|
pub fn get_children(&self, block_id: &BlockId) -> Option<Vec<BlockId>> {
|
|
self.blocks
|
|
.read()
|
|
.get(block_id)
|
|
.map(|meta| meta.relations.children.clone())
|
|
}
|
|
|
|
/// Gets the blue score of a block.
|
|
pub fn get_blue_score(&self, block_id: &BlockId) -> Option<BlueScore> {
|
|
self.blocks.read().get(block_id).map(|meta| meta.blue_score)
|
|
}
|
|
|
|
/// Gets the selected parent of a block.
|
|
pub fn get_selected_parent(&self, block_id: &BlockId) -> Option<BlockId> {
|
|
self.blocks
|
|
.read()
|
|
.get(block_id)
|
|
.and_then(|meta| meta.selected_parent)
|
|
}
|
|
|
|
/// Inserts a new block into the DAG.
|
|
///
|
|
/// Returns an error if:
|
|
/// - Block already exists
|
|
/// - Any parent doesn't exist
|
|
/// - Too many parents
|
|
pub fn insert_block(
|
|
&self,
|
|
block_id: BlockId,
|
|
parents: Vec<BlockId>,
|
|
timestamp: u64,
|
|
) -> Result<(), DagError> {
|
|
// Validate parent count
|
|
if parents.len() > MAX_BLOCK_PARENTS {
|
|
return Err(DagError::TooManyParents {
|
|
count: parents.len(),
|
|
max: MAX_BLOCK_PARENTS,
|
|
});
|
|
}
|
|
|
|
// Validate parents exist (except for genesis check)
|
|
if parents.is_empty() && block_id != self.genesis {
|
|
return Err(DagError::NoParents);
|
|
}
|
|
|
|
let mut blocks = self.blocks.write();
|
|
let mut tips = self.tips.write();
|
|
|
|
// Check block doesn't already exist
|
|
if blocks.contains_key(&block_id) {
|
|
return Err(DagError::BlockExists(block_id));
|
|
}
|
|
|
|
// Verify all parents exist
|
|
for parent in &parents {
|
|
if !blocks.contains_key(parent) {
|
|
return Err(DagError::ParentNotFound(*parent));
|
|
}
|
|
}
|
|
|
|
// Create block metadata
|
|
let meta = BlockMeta::new(parents.clone(), timestamp);
|
|
blocks.insert(block_id, meta);
|
|
|
|
// Update parent-child relationships
|
|
for parent in &parents {
|
|
if let Some(parent_meta) = blocks.get_mut(parent) {
|
|
parent_meta.relations.children.push(block_id);
|
|
}
|
|
// Parent is no longer a tip
|
|
tips.remove(parent);
|
|
}
|
|
|
|
// New block is a tip
|
|
tips.insert(block_id);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Updates GHOSTDAG data for a block.
|
|
pub fn update_ghostdag_data(
|
|
&self,
|
|
block_id: &BlockId,
|
|
blue_score: BlueScore,
|
|
selected_parent: Option<BlockId>,
|
|
is_blue: bool,
|
|
merge_set_blues: Vec<BlockId>,
|
|
merge_set_reds: Vec<BlockId>,
|
|
) -> Result<(), DagError> {
|
|
let mut blocks = self.blocks.write();
|
|
let meta = blocks
|
|
.get_mut(block_id)
|
|
.ok_or(DagError::BlockNotFound(*block_id))?;
|
|
|
|
meta.blue_score = blue_score;
|
|
meta.selected_parent = selected_parent;
|
|
meta.is_blue = is_blue;
|
|
meta.merge_set_blues = merge_set_blues;
|
|
meta.merge_set_reds = merge_set_reds;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns all ancestors of a block up to a given depth.
|
|
pub fn get_ancestors(&self, block_id: &BlockId, max_depth: usize) -> HashSet<BlockId> {
|
|
let blocks = self.blocks.read();
|
|
let mut ancestors = HashSet::new();
|
|
let mut frontier = vec![*block_id];
|
|
let mut depth = 0;
|
|
|
|
while depth < max_depth && !frontier.is_empty() {
|
|
let mut next_frontier = Vec::new();
|
|
for current in frontier {
|
|
if let Some(meta) = blocks.get(¤t) {
|
|
for parent in &meta.relations.parents {
|
|
if ancestors.insert(*parent) {
|
|
next_frontier.push(*parent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
frontier = next_frontier;
|
|
depth += 1;
|
|
}
|
|
|
|
ancestors
|
|
}
|
|
|
|
/// Returns all descendants of a block.
|
|
pub fn get_descendants(&self, block_id: &BlockId) -> HashSet<BlockId> {
|
|
let blocks = self.blocks.read();
|
|
let mut descendants = HashSet::new();
|
|
let mut frontier = vec![*block_id];
|
|
|
|
while !frontier.is_empty() {
|
|
let mut next_frontier = Vec::new();
|
|
for current in frontier {
|
|
if let Some(meta) = blocks.get(¤t) {
|
|
for child in &meta.relations.children {
|
|
if descendants.insert(*child) {
|
|
next_frontier.push(*child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
frontier = next_frontier;
|
|
}
|
|
|
|
descendants
|
|
}
|
|
|
|
/// Finds the anticone of a block (blocks neither ancestor nor descendant).
|
|
pub fn get_anticone(&self, block_id: &BlockId, depth_limit: usize) -> HashSet<BlockId> {
|
|
let blocks = self.blocks.read();
|
|
let ancestors = self.get_ancestors(block_id, depth_limit);
|
|
let descendants = self.get_descendants(block_id);
|
|
|
|
let mut anticone = HashSet::new();
|
|
for (id, _) in blocks.iter() {
|
|
if *id != *block_id && !ancestors.contains(id) && !descendants.contains(id) {
|
|
anticone.insert(*id);
|
|
}
|
|
}
|
|
|
|
anticone
|
|
}
|
|
|
|
/// Gets the selected chain from a block back to genesis.
|
|
pub fn get_selected_chain(&self, from: &BlockId) -> Vec<BlockId> {
|
|
let blocks = self.blocks.read();
|
|
let mut chain = vec![*from];
|
|
let mut current = *from;
|
|
|
|
while let Some(meta) = blocks.get(¤t) {
|
|
if let Some(selected_parent) = meta.selected_parent {
|
|
chain.push(selected_parent);
|
|
current = selected_parent;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
chain
|
|
}
|
|
|
|
/// Iterates over all blocks in topological order (parents before children).
|
|
pub fn iter_topological(&self) -> Vec<BlockId> {
|
|
let blocks = self.blocks.read();
|
|
let mut result = Vec::with_capacity(blocks.len());
|
|
let mut in_degree: HashMap<BlockId, usize> = HashMap::new();
|
|
let mut queue = Vec::new();
|
|
|
|
// Calculate in-degrees
|
|
for (id, meta) in blocks.iter() {
|
|
let degree = meta.relations.parents.len();
|
|
in_degree.insert(*id, degree);
|
|
if degree == 0 {
|
|
queue.push(*id);
|
|
}
|
|
}
|
|
|
|
// Process in topological order
|
|
while let Some(current) = queue.pop() {
|
|
result.push(current);
|
|
if let Some(meta) = blocks.get(¤t) {
|
|
for child in &meta.relations.children {
|
|
if let Some(degree) = in_degree.get_mut(child) {
|
|
*degree -= 1;
|
|
if *degree == 0 {
|
|
queue.push(*child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for BlockDag {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("BlockDag")
|
|
.field("block_count", &self.len())
|
|
.field("tip_count", &self.tips.read().len())
|
|
.field("genesis", &self.genesis)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
/// Errors that can occur with the DAG.
|
|
#[derive(Debug, Error)]
|
|
pub enum DagError {
|
|
#[error("Block already exists: {0}")]
|
|
BlockExists(BlockId),
|
|
|
|
#[error("Block not found: {0}")]
|
|
BlockNotFound(BlockId),
|
|
|
|
#[error("Parent not found: {0}")]
|
|
ParentNotFound(BlockId),
|
|
|
|
#[error("No parents specified (non-genesis block)")]
|
|
NoParents,
|
|
|
|
#[error("Too many parents: {count} (max {max})")]
|
|
TooManyParents { count: usize, max: usize },
|
|
|
|
#[error("Cycle detected in DAG")]
|
|
CycleDetected,
|
|
|
|
#[error("Invalid block structure: {0}")]
|
|
InvalidStructure(String),
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use synor_types::Hash256;
|
|
|
|
fn make_block_id(n: u8) -> BlockId {
|
|
let mut bytes = [0u8; 32];
|
|
bytes[0] = n;
|
|
Hash256::from_bytes(bytes)
|
|
}
|
|
|
|
#[test]
|
|
fn test_genesis_creation() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
assert_eq!(dag.genesis(), genesis_id);
|
|
assert_eq!(dag.len(), 1);
|
|
assert!(dag.contains(&genesis_id));
|
|
}
|
|
|
|
#[test]
|
|
fn test_insert_block() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
let block1 = make_block_id(1);
|
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
|
|
|
assert_eq!(dag.len(), 2);
|
|
assert!(dag.contains(&block1));
|
|
|
|
let parents = dag.get_parents(&block1).unwrap();
|
|
assert_eq!(parents.len(), 1);
|
|
assert_eq!(parents[0], genesis_id);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tips_tracking() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
// Initially, genesis is the only tip
|
|
assert_eq!(dag.tips().len(), 1);
|
|
assert!(dag.tips().contains(&genesis_id));
|
|
|
|
// Add a child block
|
|
let block1 = make_block_id(1);
|
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
|
|
|
// Now only block1 is a tip
|
|
assert_eq!(dag.tips().len(), 1);
|
|
assert!(dag.tips().contains(&block1));
|
|
assert!(!dag.tips().contains(&genesis_id));
|
|
|
|
// Add another block with genesis as parent (parallel mining)
|
|
let block2 = make_block_id(2);
|
|
dag.insert_block(block2, vec![genesis_id], 100).unwrap();
|
|
|
|
// Now we have two tips
|
|
assert_eq!(dag.tips().len(), 2);
|
|
assert!(dag.tips().contains(&block1));
|
|
assert!(dag.tips().contains(&block2));
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_parents() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
let block1 = make_block_id(1);
|
|
let block2 = make_block_id(2);
|
|
|
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
|
dag.insert_block(block2, vec![genesis_id], 100).unwrap();
|
|
|
|
// Block with multiple parents
|
|
let block3 = make_block_id(3);
|
|
dag.insert_block(block3, vec![block1, block2], 200).unwrap();
|
|
|
|
let parents = dag.get_parents(&block3).unwrap();
|
|
assert_eq!(parents.len(), 2);
|
|
assert!(parents.contains(&block1));
|
|
assert!(parents.contains(&block2));
|
|
}
|
|
|
|
#[test]
|
|
fn test_ancestors() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
let block1 = make_block_id(1);
|
|
let block2 = make_block_id(2);
|
|
let block3 = make_block_id(3);
|
|
|
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
|
dag.insert_block(block2, vec![block1], 200).unwrap();
|
|
dag.insert_block(block3, vec![block2], 300).unwrap();
|
|
|
|
let ancestors = dag.get_ancestors(&block3, 10);
|
|
assert!(ancestors.contains(&block2));
|
|
assert!(ancestors.contains(&block1));
|
|
assert!(ancestors.contains(&genesis_id));
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_duplicate_block() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
let block1 = make_block_id(1);
|
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
|
|
|
// Try to insert same block again
|
|
let result = dag.insert_block(block1, vec![genesis_id], 100);
|
|
assert!(matches!(result, Err(DagError::BlockExists(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_error_missing_parent() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
let missing_parent = make_block_id(99);
|
|
let block1 = make_block_id(1);
|
|
|
|
let result = dag.insert_block(block1, vec![missing_parent], 100);
|
|
assert!(matches!(result, Err(DagError::ParentNotFound(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_topological_order() {
|
|
let genesis_id = make_block_id(0);
|
|
let dag = BlockDag::new(genesis_id, 0);
|
|
|
|
let block1 = make_block_id(1);
|
|
let block2 = make_block_id(2);
|
|
let block3 = make_block_id(3);
|
|
|
|
dag.insert_block(block1, vec![genesis_id], 100).unwrap();
|
|
dag.insert_block(block2, vec![block1], 200).unwrap();
|
|
dag.insert_block(block3, vec![block2], 300).unwrap();
|
|
|
|
let order = dag.iter_topological();
|
|
|
|
// Genesis should come first
|
|
assert_eq!(order[0], genesis_id);
|
|
|
|
// Check that parents come before children
|
|
let idx_genesis = order.iter().position(|&x| x == genesis_id).unwrap();
|
|
let idx_block1 = order.iter().position(|&x| x == block1).unwrap();
|
|
let idx_block2 = order.iter().position(|&x| x == block2).unwrap();
|
|
let idx_block3 = order.iter().position(|&x| x == block3).unwrap();
|
|
|
|
assert!(idx_genesis < idx_block1);
|
|
assert!(idx_block1 < idx_block2);
|
|
assert!(idx_block2 < idx_block3);
|
|
}
|
|
}
|