- 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
- 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
- Zero-Unwrap Policy - no
.unwrap()in production code - Custom
JdcErrortype withthiserror - All functions return
Result<T, JdcError> - Contextual error propagation
- Bitcoin Core RPC connection
-
getblocktemplatepolling - Block template parsing
- Transaction extraction
- Fee calculation
- Event broadcasting
- Automatic reconnection logic
- 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
-
ratatuiterminal 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
-
AppMessageenum (12 variants) -
AppStatsstructure -
CoinbaseOutputtype - Error types (
JdcErrorwith 12 variants)
- Configuration loading
- Logging initialization (
tracing) - Tokio runtime setup
- Actor spawning
- Graceful shutdown handling
- 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)
- Compiles successfully (
cargo checkpasses) - Release optimizations configured (LTO, opt-level 3)
- All dependencies resolved
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:
- SV2 Spec: AllocateMiningJobToken
job_declaration_sv2crate documentation
Status: 🟡 Placeholder Exists
Location: src/pool/mod.rs → build_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:
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"Status: 🟡 Partial
Location: src/pool/mod.rs → process_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(())
}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()
}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>,
}- Test Noise handshake state transitions
- Test message encoding/decoding
- Test short ID calculation
- Test error handling paths
- Test against SV2 reference implementation pool
- Test reconnection logic
- Test multi-template scenarios
- Test edge cases (rejected jobs, network errors)
#[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);
}
}- Implement fee-based selection
- Add mempool filtering
- Support custom transaction policies
- Optimize for maximum fees
- Export Prometheus metrics
- Add latency tracking
- Monitor job acceptance rate
- Track network statistics
- Add job details view
- Show transaction selection criteria
- Display pool statistics
- Add keyboard shortcuts for navigation
- Implement
AllocateMiningJobTokenrequest - Parse
AllocateMiningJobTokenSuccessresponse - Store mining job token
- Test token allocation flow
- Implement transaction short ID calculation
- Build coinbase prefix/suffix
- Implement
DeclareMiningJobencoding - Test against pool
- Implement
IdentifyTransactionshandler - Implement
ProvideMissingTransactionsresponse - Add transaction storage/lookup
- Write unit tests
- Integration testing with real pool
- Performance optimization
- Documentation updates
Total estimated time: 1-2 weeks
- SRI test pool (local setup)
- Public SV2 testnet pools
The project is complete when:
- Foundation - Actor architecture working (COMPLETE)
- Handshake - Noise NX fully functional (COMPLETE)
- Messages - All SV2 job declaration messages implemented
- Integration - Successfully declares jobs to real pool
- Testing - Comprehensive test coverage
- Documentation - All code documented
- Performance - Optimized for production use
Current Progress: 40% Complete (Foundation & Handshake Done)
To get the first successful job declaration:
- Add SipHash dependency to
Cargo.toml - Implement
calculate_short_id()function - Request mining job token on handshake complete
- Build basic
DeclareMiningJobmessage - 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!