synor/crates/synor-mining/src/template.rs
Gulshan Yadav 5c643af64c fix: resolve all clippy warnings for CI
Fix all Rust clippy warnings that were causing CI failures when built
with RUSTFLAGS=-Dwarnings. Changes include:

- Replace derivable_impls with derive macros for BlockBody, Network, etc.
- Use div_ceil() instead of manual implementation
- Fix should_implement_trait by renaming from_str to parse
- Add type aliases for type_complexity warnings
- Use or_default(), is_some_and(), is_multiple_of() where appropriate
- Remove needless borrows and redundant closures
- Fix manual_strip with strip_prefix()
- Add allow attributes for intentional patterns (too_many_arguments,
  needless_range_loop in cryptographic code, assertions_on_constants)
- Remove unused imports, mut bindings, and dead code in tests
2026-01-08 05:58:22 +05:30

515 lines
14 KiB
Rust

//! Block template creation for mining.
//!
//! Templates provide miners with all the information needed to construct
//! and mine a valid block.
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use synor_types::{Address, Hash256};
use crate::{MiningError, Target};
/// A block template for mining.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BlockTemplate {
/// Template identifier (for tracking).
pub id: u64,
/// Version of the template format.
pub version: u32,
/// Parent block hashes (DAG structure).
pub parent_hashes: Vec<Hash256>,
/// Selected parent (for GHOSTDAG).
pub selected_parent: Hash256,
/// GHOSTDAG blue score.
pub blue_score: u64,
/// Bits representation of target.
pub bits: u32,
/// Target for mining.
pub target: [u8; 32],
/// Block timestamp.
pub timestamp: u64,
/// Coinbase transaction data.
pub coinbase_data: CoinbaseData,
/// Transactions to include.
pub transactions: Vec<TemplateTransaction>,
/// Total fees from transactions.
pub total_fees: u64,
/// Block reward (subsidy).
pub block_reward: u64,
/// Merkle root of transactions.
pub tx_merkle_root: Hash256,
/// Accepted ID merkle root.
pub accepted_id_merkle_root: Hash256,
/// UTXO commitment.
pub utxo_commitment: Hash256,
/// Header bytes for mining (without nonce).
pub header_data: Vec<u8>,
/// Extra nonce space for pool mining.
pub extra_nonce_range: (u64, u64),
}
impl BlockTemplate {
/// Creates the header bytes for hashing.
pub fn header_for_mining(&self) -> Vec<u8> {
self.header_data.clone()
}
/// Creates header with a specific extra nonce.
pub fn header_with_extra_nonce(&self, extra_nonce: u64) -> Vec<u8> {
let mut header = self.header_data.clone();
// Insert extra nonce at designated position (after merkle root)
if header.len() >= 40 {
let nonce_bytes = extra_nonce.to_le_bytes();
header[32..40].copy_from_slice(&nonce_bytes);
}
header
}
/// Gets the target.
pub fn get_target(&self) -> Target {
Target::from_bytes(self.target)
}
/// Validates the template is well-formed.
pub fn validate(&self) -> Result<(), MiningError> {
if self.parent_hashes.is_empty() {
return Err(MiningError::InvalidTemplate("No parent hashes".into()));
}
if !self.parent_hashes.contains(&self.selected_parent) {
return Err(MiningError::InvalidTemplate(
"Selected parent not in parent set".into(),
));
}
if self.header_data.is_empty() {
return Err(MiningError::InvalidTemplate("Empty header data".into()));
}
Ok(())
}
/// Returns expected total payout (reward + fees).
pub fn total_payout(&self) -> u64 {
self.block_reward + self.total_fees
}
}
/// Coinbase transaction data.
#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct CoinbaseData {
/// Miner address to receive rewards.
pub miner_address: Address,
/// Block height for coinbase maturity.
pub block_height: u64,
/// Extra data (pool name, etc.).
pub extra_data: Vec<u8>,
/// Script for payout.
pub script: Vec<u8>,
}
impl CoinbaseData {
/// Creates new coinbase data.
pub fn new(miner_address: Address, block_height: u64) -> Self {
CoinbaseData {
miner_address,
block_height,
extra_data: Vec::new(),
script: Vec::new(),
}
}
/// Adds extra data (e.g., pool name).
pub fn with_extra_data(mut self, data: Vec<u8>) -> Self {
self.extra_data = data;
self
}
}
/// Transaction in a block template.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TemplateTransaction {
/// Transaction ID.
pub txid: Hash256,
/// Serialized transaction data.
pub data: Vec<u8>,
/// Fee paid by this transaction.
pub fee: u64,
/// Transaction mass (weight).
pub mass: u64,
}
/// Builder for block templates.
pub struct BlockTemplateBuilder {
/// Template version.
version: u32,
/// Parent block hashes.
parents: Vec<Hash256>,
/// Selected parent.
selected_parent: Option<Hash256>,
/// Blue score.
blue_score: u64,
/// Difficulty bits.
bits: u32,
/// Timestamp.
timestamp: u64,
/// Coinbase data.
coinbase: Option<CoinbaseData>,
/// Transactions.
transactions: Vec<TemplateTransaction>,
/// Block reward.
reward: u64,
}
impl BlockTemplateBuilder {
/// Creates a new template builder.
pub fn new() -> Self {
BlockTemplateBuilder {
version: 1,
parents: Vec::new(),
selected_parent: None,
blue_score: 0,
bits: 0x1d00ffff,
timestamp: 0,
coinbase: None,
transactions: Vec::new(),
reward: 0,
}
}
/// Sets the template version.
pub fn version(mut self, version: u32) -> Self {
self.version = version;
self
}
/// Adds a parent hash.
pub fn add_parent(mut self, hash: Hash256) -> Self {
self.parents.push(hash);
self
}
/// Sets parent hashes.
pub fn parents(mut self, parents: Vec<Hash256>) -> Self {
self.parents = parents;
self
}
/// Sets the selected parent.
pub fn selected_parent(mut self, parent: Hash256) -> Self {
self.selected_parent = Some(parent);
self
}
/// Sets the blue score.
pub fn blue_score(mut self, score: u64) -> Self {
self.blue_score = score;
self
}
/// Sets difficulty bits.
pub fn bits(mut self, bits: u32) -> Self {
self.bits = bits;
self
}
/// Sets timestamp.
pub fn timestamp(mut self, ts: u64) -> Self {
self.timestamp = ts;
self
}
/// Sets coinbase data.
pub fn coinbase(mut self, coinbase: CoinbaseData) -> Self {
self.coinbase = Some(coinbase);
self
}
/// Adds a transaction.
pub fn add_transaction(mut self, tx: TemplateTransaction) -> Self {
self.transactions.push(tx);
self
}
/// Sets the block reward.
pub fn reward(mut self, reward: u64) -> Self {
self.reward = reward;
self
}
/// Builds the block template.
pub fn build(mut self, template_id: u64) -> Result<BlockTemplate, MiningError> {
let selected = self
.selected_parent
.or_else(|| self.parents.first().copied())
.ok_or_else(|| MiningError::InvalidTemplate("No parents".into()))?;
// Build header data first before taking coinbase
let header_data = self.build_header(&selected)?;
let coinbase = self
.coinbase
.take()
.ok_or_else(|| MiningError::InvalidTemplate("No coinbase".into()))?;
// Calculate fees
let total_fees: u64 = self.transactions.iter().map(|tx| tx.fee).sum();
// Calculate merkle roots (simplified)
let tx_merkle_root = self.calculate_tx_merkle_root();
let accepted_id_merkle_root = Hash256::default();
let utxo_commitment = Hash256::default();
Ok(BlockTemplate {
id: template_id,
version: self.version,
parent_hashes: self.parents,
selected_parent: selected,
blue_score: self.blue_score,
bits: self.bits,
target: Target::from_bits(self.bits).0,
timestamp: self.timestamp,
coinbase_data: coinbase,
transactions: self.transactions,
total_fees,
block_reward: self.reward,
tx_merkle_root,
accepted_id_merkle_root,
utxo_commitment,
header_data,
extra_nonce_range: (0, u64::MAX),
})
}
fn build_header(&self, selected_parent: &Hash256) -> Result<Vec<u8>, MiningError> {
// Block header format:
// - Version: 4 bytes
// - Parents hash (merkle of parents): 32 bytes
// - Extra nonce space: 8 bytes
// - TX merkle root: 32 bytes
// - Timestamp: 8 bytes
// - Bits: 4 bytes
// - Blue score: 8 bytes
// Total: 96 bytes (nonce added during mining)
let mut header = Vec::with_capacity(96);
// Version
header.extend_from_slice(&self.version.to_le_bytes());
// Parents hash (simplified: hash of selected parent)
header.extend_from_slice(selected_parent.as_bytes());
// Extra nonce space (8 bytes, filled during mining)
header.extend_from_slice(&[0u8; 8]);
// TX merkle root
let merkle = self.calculate_tx_merkle_root();
header.extend_from_slice(merkle.as_bytes());
// Timestamp
header.extend_from_slice(&self.timestamp.to_le_bytes());
// Bits
header.extend_from_slice(&self.bits.to_le_bytes());
// Blue score
header.extend_from_slice(&self.blue_score.to_le_bytes());
Ok(header)
}
fn calculate_tx_merkle_root(&self) -> Hash256 {
if self.transactions.is_empty() {
return Hash256::default();
}
// Collect all txids
let txids: Vec<&[u8; 32]> = self
.transactions
.iter()
.map(|tx| tx.txid.as_bytes())
.collect();
// Simple merkle tree
merkle_root(&txids)
}
}
impl Default for BlockTemplateBuilder {
fn default() -> Self {
Self::new()
}
}
/// Builds a coinbase transaction.
pub struct CoinbaseBuilder {
miner_address: Address,
block_height: u64,
extra_data: Vec<u8>,
block_reward: u64,
fees: u64,
}
impl CoinbaseBuilder {
/// Creates a new coinbase builder.
pub fn new(miner_address: Address, block_height: u64) -> Self {
CoinbaseBuilder {
miner_address,
block_height,
extra_data: Vec::new(),
block_reward: 0,
fees: 0,
}
}
/// Sets extra data (pool name, etc.).
pub fn extra_data(mut self, data: Vec<u8>) -> Self {
self.extra_data = data;
self
}
/// Sets the block reward.
pub fn reward(mut self, reward: u64) -> Self {
self.block_reward = reward;
self
}
/// Sets the total fees.
pub fn fees(mut self, fees: u64) -> Self {
self.fees = fees;
self
}
/// Builds the coinbase data.
pub fn build(self) -> CoinbaseData {
let script = self.build_script();
CoinbaseData {
miner_address: self.miner_address,
block_height: self.block_height,
extra_data: self.extra_data,
script,
}
}
fn build_script(&self) -> Vec<u8> {
// Simplified coinbase script
// In production, this would be a proper output script
let mut script = Vec::new();
// Height push (BIP34)
let height_bytes = self.block_height.to_le_bytes();
let len = height_bytes.iter().rposition(|&b| b != 0).unwrap_or(0) + 1;
script.push(len as u8);
script.extend_from_slice(&height_bytes[..len]);
// Extra data
if !self.extra_data.is_empty() {
script.extend_from_slice(&self.extra_data);
}
script
}
/// Total payout (reward + fees).
pub fn total_payout(&self) -> u64 {
self.block_reward + self.fees
}
}
/// Computes merkle root from a list of hashes.
fn merkle_root(hashes: &[&[u8; 32]]) -> Hash256 {
if hashes.is_empty() {
return Hash256::default();
}
if hashes.len() == 1 {
return Hash256::from_bytes(*hashes[0]);
}
let mut current: Vec<[u8; 32]> = hashes.iter().map(|h| **h).collect();
while current.len() > 1 {
let mut next = Vec::with_capacity(current.len().div_ceil(2));
for chunk in current.chunks(2) {
let mut data = [0u8; 64];
data[..32].copy_from_slice(&chunk[0]);
if chunk.len() > 1 {
data[32..].copy_from_slice(&chunk[1]);
} else {
data[32..].copy_from_slice(&chunk[0]); // Duplicate odd element
}
let hash: [u8; 32] = blake3::hash(&data).into();
next.push(hash);
}
current = next;
}
Hash256::from_bytes(current[0])
}
#[cfg(test)]
mod tests {
use super::*;
use synor_types::Network;
fn test_address() -> Address {
Address::from_ed25519_pubkey(Network::Mainnet, &[0x42; 32])
}
fn test_hash(n: u8) -> Hash256 {
let mut bytes = [0u8; 32];
bytes[0] = n;
Hash256::from_bytes(bytes)
}
#[test]
fn test_coinbase_builder() {
let coinbase = CoinbaseBuilder::new(test_address(), 1000)
.extra_data(b"Synor Pool".to_vec())
.reward(500_00000000)
.fees(1_00000000)
.build();
assert_eq!(coinbase.block_height, 1000);
assert!(!coinbase.script.is_empty());
}
#[test]
fn test_block_template_builder() {
let coinbase = CoinbaseBuilder::new(test_address(), 1)
.reward(500_00000000)
.build();
let template = BlockTemplateBuilder::new()
.version(1)
.add_parent(test_hash(1))
.timestamp(1234567890)
.bits(0x1d00ffff)
.blue_score(100)
.coinbase(coinbase)
.reward(500_00000000)
.build(1)
.unwrap();
assert_eq!(template.id, 1);
assert!(!template.header_data.is_empty());
assert!(template.validate().is_ok());
}
#[test]
fn test_merkle_root() {
let h1 = [1u8; 32];
let h2 = [2u8; 32];
let root1 = merkle_root(&[&h1]);
assert_eq!(*root1.as_bytes(), h1);
let root2 = merkle_root(&[&h1, &h2]);
assert_ne!(*root2.as_bytes(), h1);
assert_ne!(*root2.as_bytes(), h2);
}
}