use crate::dig_l2_definition as definitions;
use crate::{body::L2BlockBody, emission::Emission, header::L2BlockHeader};
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub struct BuildL2BlockArgs<'ba> {
pub version: u32,
pub network_id: [u8; 32],
pub epoch: u64,
pub prev_block_root: [u8; 32],
pub proposer_pubkey: [u8; 48],
pub data: Vec<u8>,
pub extra_emissions: Vec<Emission>,
pub attester_pubkeys: &'ba [[u8; 48]],
pub cfg: &'ba crate::emission_config::ConsensusEmissionConfig,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DigL2Block {
pub header: L2BlockHeader,
pub body: L2BlockBody,
}
impl DigL2Block {
pub fn calculate_root(&self) -> definitions::Hash32 {
let header_root = self.header.calculate_root();
let body_root = self.body.calculate_root();
definitions::COMPUTE_BLOCK_ROOT(&header_root, &body_root)
}
pub fn new(
header: L2BlockHeader,
body: L2BlockBody,
expected_version: Option<u32>,
) -> Result<Self, BlockError> {
if let Some(v) = expected_version {
header.validate_version(v)?;
}
let calc_body_root = body.calculate_root();
if header.body_root != calc_body_root {
return Err(BlockError::BodyRootMismatch {
header_body_root: header.body_root,
calculated: calc_body_root,
});
}
header.validate_counts(body.data.len(), body.emissions.len())?;
Ok(DigL2Block { header, body })
}
pub fn build(args: &BuildL2BlockArgs<'_>) -> Result<Self, BlockError> {
args.cfg
.validate_for_attesters(args.attester_pubkeys.len())?;
let tuples = definitions::BUILD_CONSENSUS_EMISSIONS(
args.proposer_pubkey,
args.attester_pubkeys,
args.cfg.proposer_reward_share,
args.cfg.attester_reward_share,
)?;
let mut emissions: Vec<Emission> = tuples
.into_iter()
.map(|(pk, w)| Emission {
pubkey: pk,
weight: w,
})
.collect();
emissions.extend(args.extra_emissions.clone());
let body = L2BlockBody {
data: args.data.clone(),
emissions,
};
let body_root = body.calculate_root();
let header = L2BlockHeader {
version: args.version,
network_id: args.network_id,
epoch: args.epoch,
prev_block_root: args.prev_block_root,
body_root,
data_count: body.data.len() as u32,
emissions_count: body.emissions.len() as u32,
proposer_pubkey: args.proposer_pubkey,
};
Ok(DigL2Block { header, body })
}
}
#[derive(Debug, Error)]
pub enum BlockError {
#[error(transparent)]
Header(#[from] crate::header::HeaderError),
#[error(transparent)]
Body(#[from] crate::body::BodyError),
#[error("body_root mismatch: header {header_body_root:?} != calculated {calculated:?}")]
BodyRootMismatch {
header_body_root: [u8; 32],
calculated: [u8; 32],
},
#[error(transparent)]
Definitions(#[from] crate::dig_l2_definition::DefinitionError),
#[error(transparent)]
Config(#[from] crate::emission_config::EmissionConfigError),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emission::Emission;
fn make_body() -> L2BlockBody {
L2BlockBody {
data: vec![1, 2, 3],
emissions: vec![Emission {
pubkey: [5u8; 48],
weight: 10,
}],
}
}
fn make_header_for_body(body: &L2BlockBody) -> L2BlockHeader {
let body_root = body.calculate_root();
L2BlockHeader {
version: 1,
network_id: [0xabu8; 32],
epoch: 7,
prev_block_root: [0u8; 32],
body_root,
data_count: body.data.len() as u32,
emissions_count: body.emissions.len() as u32,
proposer_pubkey: [9u8; 48],
}
}
#[test]
fn block_root_composition_matches_definitions() {
let body = make_body();
let header = make_header_for_body(&body);
let block = DigL2Block::new(header, body, Some(1)).unwrap();
let h_root = block.header.calculate_root();
let b_root = block.body.calculate_root();
let expect = definitions::COMPUTE_BLOCK_ROOT(&h_root, &b_root);
assert_eq!(block.calculate_root(), expect);
}
#[test]
fn new_rejects_mismatched_counts() {
let body = make_body();
let mut header = make_header_for_body(&body);
header.data_count += 1; let err = DigL2Block::new(header, body, Some(1)).unwrap_err();
match err {
BlockError::Header(crate::header::HeaderError::CountMismatch { .. }) => {}
_ => panic!("unexpected error type"),
}
}
#[test]
fn new_rejects_body_root_mismatch() {
let mut body = make_body();
let header = make_header_for_body(&body);
body.data.push(4);
let err = DigL2Block::new(header, body, Some(1)).unwrap_err();
match err {
BlockError::BodyRootMismatch { .. } => {}
_ => panic!("unexpected error type"),
}
}
#[test]
fn build_block_with_attesters_and_extras() {
let data = vec![1u8, 2, 3, 4];
let extra = vec![Emission {
pubkey: [0x33u8; 48],
weight: 7,
}];
let attesters = vec![[0x11u8; 48], [0x22u8; 48], [0x44u8; 48]];
let cfg = crate::emission_config::ConsensusEmissionConfig::new(12, 90);
let build_block_args = BuildL2BlockArgs {
version: 1,
network_id: [0xabu8; 32],
epoch: 7,
prev_block_root: [0u8; 32],
proposer_pubkey: [9u8; 48],
data,
extra_emissions: extra.clone(),
attester_pubkeys: &attesters,
cfg: &cfg,
};
let block = DigL2Block::build(&build_block_args).unwrap();
assert_eq!(block.header.data_count as usize, block.body.data.len());
assert_eq!(
block.header.emissions_count as usize,
block.body.emissions.len()
);
let expect_body_root = block.body.calculate_root();
assert_eq!(block.header.body_root, expect_body_root);
let s = serde_json::to_string(&block).unwrap();
let back: DigL2Block = serde_json::from_str(&s).unwrap();
assert_eq!(block, back);
}
#[test]
fn build_block_zero_attesters_policy() {
let cfg = crate::emission_config::ConsensusEmissionConfig::new(12, 0);
let bb_args = BuildL2BlockArgs {
version: 1,
network_id: [0xabu8; 32],
epoch: 7,
prev_block_root: [0u8; 32],
proposer_pubkey: [9u8; 48],
data: vec![],
extra_emissions: vec![],
attester_pubkeys: &[],
cfg: &cfg,
};
let b = DigL2Block::build(&bb_args).unwrap();
assert_eq!(b.body.emissions.len(), 1);
let cfg_bad = crate::emission_config::ConsensusEmissionConfig::new(12, 1);
let bb_e_args = BuildL2BlockArgs {
version: 1,
network_id: [0u8; 32],
epoch: 7,
prev_block_root: [0u8; 32],
proposer_pubkey: [1u8; 48],
data: vec![],
extra_emissions: vec![],
attester_pubkeys: &[],
cfg: &cfg_bad,
};
let err = DigL2Block::build(&bb_e_args).unwrap_err();
match err {
BlockError::Config(
crate::emission_config::EmissionConfigError::NonZeroAttesterShareWithNoAttesters,
) => {}
other => panic!("unexpected error: {other:?}"),
}
}
}