Skip to content

Latest commit

 

History

History
510 lines (416 loc) · 14.4 KB

File metadata and controls

510 lines (416 loc) · 14.4 KB

Implementation Checklist & Next Steps

COMPLETED - Foundation (Production Ready)

Project Structure

  • Modular folder structure (src/node/, src/pool/, src/ui/, src/common/)
  • Cargo.toml with all required dependencies
  • Configuration system (TOML + environment variables)
  • Git ignore file
  • Comprehensive documentation

Core Architecture

  • Actor Pattern implementation
    • Node Actor (Bitcoin RPC client)
    • Pool Actor (SV2 protocol handler)
    • UI Actor (Terminal dashboard)
  • Message Passing via tokio::broadcast
  • No Shared Mutable State - compiler-verified

Error Handling

  • Zero-Unwrap Policy - no .unwrap() in production code
  • Custom JdcError type with thiserror
  • All functions return Result<T, JdcError>
  • Contextual error propagation

Node Actor (src/node/mod.rs)

  • Bitcoin Core RPC connection
  • getblocktemplate polling
  • Block template parsing
  • Transaction extraction
  • Fee calculation
  • Event broadcasting
  • Automatic reconnection logic

Pool Actor (src/pool/mod.rs)

  • TCP connection to pool
  • State machine (Disconnected → Connected → InitiatorSent → Complete)
  • Noise NX handshake - FULLY IMPLEMENTED
    • Initiator creation
    • Step 0: Generate & send ephemeral key
    • Step 1: Process responder key & derive transport keys
    • NoiseCodec creation
  • Encrypted channel establishment
  • Frame encryption/decryption infrastructure
  • Message routing from other actors
  • Reconnection on disconnect

UI Actor (src/ui/mod.rs)

  • ratatui terminal dashboard
  • Real-time statistics display
  • Connection status indicators
  • Event log with timestamps
  • User input handling ('q' to quit)
  • RAII cleanup (terminal state restoration)
  • Uptime tracking
  • Acceptance rate calculation

Common Module (src/common/)

  • AppMessage enum (12 variants)
  • AppStats structure
  • CoinbaseOutput type
  • Error types (JdcError with 12 variants)

Main Entry Point (src/main.rs)

  • Configuration loading
  • Logging initialization (tracing)
  • Tokio runtime setup
  • Actor spawning
  • Graceful shutdown handling

Documentation

  • README.md - User-facing overview
  • PROJECT_SUMMARY.md - Complete project details
  • ARCHITECTURE.md - System design explanation
  • NOISE_HANDSHAKE.md - Deep dive on cryptography
  • PRODUCTION_PATTERNS.md - Rust best practices
  • ARCHITECTURE_DIAGRAM.txt - Visual architecture
  • Quick start script (start.sh)
  • Example configuration (config.toml)

Build System

  • Compiles successfully (cargo check passes)
  • Release optimizations configured (LTO, opt-level 3)
  • All dependencies resolved

🚧 IN PROGRESS - SV2 Protocol Messages

Message Encoding (High Priority)

1. AllocateMiningJobToken (0x50)

Status: 🔴 Not Started
Location: src/pool/mod.rs
What to implement:

use job_declaration_sv2::AllocateMiningJobToken;

async fn request_job_token(&mut self) -> Result<Vec<u8>> {
    let msg = AllocateMiningJobToken {
        user_identifier: "miner_1".to_string(),
        request_id: self.next_request_id(),
    };
    
    let frame = StandardSv2Frame::from_message(
        msg,
        0x50, // message type
        0,    // extension type
        false // requires state?
    )?;
    
    let encoded = frame.serialize()?;
    self.send_encrypted(encoded).await?;
    Ok(())
}

References:


2. DeclareMiningJob (0x52)

Status: 🟡 Placeholder Exists
Location: src/pool/mod.rsbuild_job_declaration()
Current state: Returns empty vector
What to implement:

use job_declaration_sv2::DeclareMiningJob;
use binary_sv2::Seq0255;

fn build_job_declaration(
    &self,
    template_id: u64,
    coinbase_outputs: Vec<CoinbaseOutput>,
    transactions: Vec<Vec<u8>>,
) -> Result<Vec<u8>> {
    // 1. Calculate transaction short IDs
    let tx_short_ids: Vec<u64> = transactions
        .iter()
        .map(|tx| calculate_siphash_short_id(tx, self.pool_k0, self.pool_k1))
        .collect();
    
    // 2. Build coinbase output sequence
    let outputs = Seq0255::new(
        coinbase_outputs.iter()
            .map(|o| OutputScript {
                value: o.value,
                script: o.script_pubkey.clone().into(),
            })
            .collect()
    )?;
    
    // 3. Create DeclareMiningJob message
    let msg = DeclareMiningJob {
        request_id: template_id as u32,
        mining_job_token: self.current_mining_job_token.clone().into(),
        version: self.block_version,
        coinbase_prefix: self.build_coinbase_prefix()?.into(),
        coinbase_suffix: self.build_coinbase_suffix()?.into(),
        tx_short_id_list: tx_short_ids.into(),
        tx_short_id_mapping: self.build_short_id_mapping(&transactions)?.into(),
        tx_hash_list_hash: calculate_tx_hash_list_hash(&transactions),
        excess_data: Vec::new().into(),
    };
    
    // 4. Serialize to frame
    let frame = StandardSv2Frame::from_message(msg, 0x52, 0, false)?;
    Ok(frame.serialize()?)
}

Sub-tasks:

  • Implement SipHash-2-4 for transaction short IDs
  • Build coinbase prefix/suffix
  • Calculate tx_hash_list_hash
  • Create short ID mapping
  • Test against pool acceptance criteria

References:


3. Transaction Short ID Calculation

Status: 🔴 Not Started
Create new file: src/pool/short_id.rs
What to implement:

use siphasher::sip::SipHasher24;
use std::hash::Hasher;

pub fn calculate_short_id(
    tx_data: &[u8],
    k0: u64,
    k1: u64,
) -> u64 {
    let mut hasher = SipHasher24::new_with_keys(k0, k1);
    hasher.write(tx_data);
    hasher.finish()
}

// During handshake, pool sends its keys:
pub struct PoolKeys {
    pub k0: u64,
    pub k1: u64,
}

// Store in PoolActor
struct PoolActor {
    // ...existing fields...
    pool_keys: Option<PoolKeys>,
}

Dependencies to add:

siphasher = "1.0"

4. Message Parsing (Responses)

Status: 🟡 Partial
Location: src/pool/mod.rsprocess_pool_message()
Current state: Logs message types but doesn't parse
What to implement:

async fn process_pool_message(&mut self, frame: StandardSv2Frame<Vec<u8>>) -> Result<()> {
    match frame.msg_type {
        0x51 => {
            // AllocateMiningJobTokenSuccess
            let msg: AllocateMiningJobTokenSuccess = 
                codec_sv2::decode_message(&frame.payload)?;
            
            self.mining_job_token = Some(msg.mining_job_token.to_vec());
            info!("Received mining job token: {:?}", msg);
            
            let _ = self.tx.send(AppMessage::JobTokenReceived {
                request_id: msg.request_id,
            });
        }
        0x53 => {
            // DeclareMiningJobSuccess
            let msg: DeclareMiningJobSuccess = 
                codec_sv2::decode_message(&frame.payload)?;
            
            self.active_jobs.insert(msg.request_id, msg.new_mining_job_token.clone());
            
            let _ = self.tx.send(AppMessage::JobAccepted {
                template_id: msg.request_id as u64,
                new_mining_job_token: msg.new_mining_job_token.to_vec(),
            });
        }
        0x54 => {
            // DeclareMiningJobError
            let msg: DeclareMiningJobError = 
                codec_sv2::decode_message(&frame.payload)?;
            
            error!("Job declaration error: {:?}", msg);
            
            let _ = self.tx.send(AppMessage::JobRejected {
                template_id: msg.request_id as u64,
                reason: msg.error_code.clone(),
            });
        }
        0x55 => {
            // IdentifyTransactions
            self.handle_identify_transactions(frame).await?;
        }
        0x56 => {
            // ProvideMissingTransactions - sent by us, shouldn't receive
            warn!("Received unexpected ProvideMissingTransactions");
        }
        _ => {
            debug!("Unhandled message type: 0x{:02x}", frame.msg_type);
        }
    }
    Ok(())
}

5. IdentifyTransactions Handler (0x55)

Status: 🔴 Not Started
What to implement:

use job_declaration_sv2::{IdentifyTransactions, ProvideMissingTransactions};

async fn handle_identify_transactions(
    &mut self,
    frame: StandardSv2Frame<Vec<u8>>,
) -> Result<()> {
    let msg: IdentifyTransactions = codec_sv2::decode_message(&frame.payload)?;
    
    // Pool is asking for full transactions it doesn't have
    let unknown_tx_hashes: Vec<[u8; 32]> = msg.transaction_list.to_vec();
    
    // Find transactions in our template
    let mut missing_txs = Vec::new();
    for tx_hash in unknown_tx_hashes {
        if let Some(tx) = self.find_transaction_by_hash(&tx_hash) {
            missing_txs.push(tx);
        } else {
            warn!("Pool requested unknown transaction: {:?}", tx_hash);
        }
    }
    
    // Send ProvideMissingTransactions response
    let response = ProvideMissingTransactions {
        request_id: msg.request_id,
        transaction_list: Seq064K::new(missing_txs)?,
    };
    
    let frame = StandardSv2Frame::from_message(response, 0x56, 0, false)?;
    self.send_encrypted(frame.serialize()?).await?;
    
    Ok(())
}

fn find_transaction_by_hash(&self, hash: &[u8; 32]) -> Option<Vec<u8>> {
    // Look up in stored templates
    self.current_template
        .as_ref()
        .and_then(|t| t.transactions.get(hash))
        .cloned()
}

State Management Enhancements

Add Job Tracking

Status: 🔴 Not Started
What to implement:

// In PoolActor
struct JobState {
    template_id: u64,
    request_id: u32,
    submitted_at: SystemTime,
    status: JobStatus,
}

enum JobStatus {
    Pending,
    Accepted { mining_job_token: Vec<u8> },
    Rejected { error_code: String },
}

struct PoolActor {
    // ...existing...
    active_jobs: HashMap<u32, JobState>,
    mining_job_token: Option<Vec<u8>>,
    pool_keys: Option<PoolKeys>,
}

📋 TODO - Testing & Validation

Unit Tests

  • Test Noise handshake state transitions
  • Test message encoding/decoding
  • Test short ID calculation
  • Test error handling paths

Integration Tests

  • Test against SV2 reference implementation pool
  • Test reconnection logic
  • Test multi-template scenarios
  • Test edge cases (rejected jobs, network errors)

Example:

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_short_id_calculation() {
        let tx = hex::decode("...").unwrap();
        let k0 = 0x1234567890ABCDEF;
        let k1 = 0xFEDCBA0987654321;
        
        let short_id = calculate_short_id(&tx, k0, k1);
        assert_eq!(short_id, 0x...); // Known value
    }
    
    #[tokio::test]
    async fn test_job_declaration_encoding() {
        let job = build_test_job();
        let encoded = build_job_declaration(job).unwrap();
        
        // Decode and verify
        let decoded: DeclareMiningJob = 
            codec_sv2::decode_message(&encoded).unwrap();
        assert_eq!(decoded.request_id, job.template_id);
    }
}

TODO - Enhanced Features

Transaction Selection Strategy

  • Implement fee-based selection
  • Add mempool filtering
  • Support custom transaction policies
  • Optimize for maximum fees

Metrics & Monitoring

  • Export Prometheus metrics
  • Add latency tracking
  • Monitor job acceptance rate
  • Track network statistics

Advanced UI

  • Add job details view
  • Show transaction selection criteria
  • Display pool statistics
  • Add keyboard shortcuts for navigation

Recommended Implementation Order

Phase 1: Basic SV2 Communication (1-2 days)

  1. Implement AllocateMiningJobToken request
  2. Parse AllocateMiningJobTokenSuccess response
  3. Store mining job token
  4. Test token allocation flow

Phase 2: Job Declaration (2-3 days)

  1. Implement transaction short ID calculation
  2. Build coinbase prefix/suffix
  3. Implement DeclareMiningJob encoding
  4. Test against pool

Phase 3: Transaction Handling (1-2 days)

  1. Implement IdentifyTransactions handler
  2. Implement ProvideMissingTransactions response
  3. Add transaction storage/lookup

Phase 4: Testing & Refinement (2-3 days)

  1. Write unit tests
  2. Integration testing with real pool
  3. Performance optimization
  4. Documentation updates

Total estimated time: 1-2 weeks


Resources for Implementation

Official Documentation

Crate Documentation

Testing Pools

  • SRI test pool (local setup)
  • Public SV2 testnet pools

Definition of Done

The project is complete when:

  1. Foundation - Actor architecture working (COMPLETE)
  2. Handshake - Noise NX fully functional (COMPLETE)
  3. Messages - All SV2 job declaration messages implemented
  4. Integration - Successfully declares jobs to real pool
  5. Testing - Comprehensive test coverage
  6. Documentation - All code documented
  7. Performance - Optimized for production use

Current Progress: 40% Complete (Foundation & Handshake Done)


Quick Win Next Steps

To get the first successful job declaration:

  1. Add SipHash dependency to Cargo.toml
  2. Implement calculate_short_id() function
  3. Request mining job token on handshake complete
  4. Build basic DeclareMiningJob message
  5. Send to pool and observe response

Estimated time to first job: 4-6 hours


This checklist provides a clear roadmap for completing the Stratum V2 JDC implementation!