bittensor_core/pow/
mod.rs1mod opencl;
5
6use sha2::{Digest, Sha256};
7use sp_core::{hashing::keccak_256, U256};
8
9pub use opencl::{gpu_devices, GpuDevice, GpuMiner};
10
11pub const PREFIX_LEN: usize = 123;
12pub const MAX_BATCH: u32 = 1_048_576;
13pub type Solution = (u64, [u8; 32]);
14
15pub fn seal(prefix: &[u8], nonce: u64) -> Result<[u8; 32], String> {
16 validate_prefix(prefix)?;
17 let mut hasher = Sha256::new();
18 hasher.update(prefix);
19 hasher.update(nonce.to_le_bytes());
20 Ok(keccak_256(&hasher.finalize()))
21}
22
23fn validate_prefix(prefix: &[u8]) -> Result<(), String> {
24 if prefix.len() != PREFIX_LEN || !prefix.starts_with(b"subtensor-pow-register-v1") {
25 return Err("expected the 123-byte subnet registration challenge prefix".into());
26 }
27 if prefix.get(25..27) == Some(&[0, 0]) {
28 return Err("PoW registration is unavailable on root".into());
29 }
30 Ok(())
31}
32
33fn target(difficulty: u64) -> Result<U256, String> {
34 if difficulty == 0 {
35 return Err("PoW difficulty must be positive".into());
36 }
37 U256::MAX
38 .checked_div(U256::from(difficulty))
39 .ok_or_else(|| "PoW difficulty must be positive".into())
40}
41
42fn validate_batch(prefix: &[u8], difficulty: u64, attempts: u32) -> Result<U256, String> {
43 validate_prefix(prefix)?;
44 if attempts == 0 || attempts > MAX_BATCH {
45 return Err(format!("PoW batch must contain 1..{MAX_BATCH} attempts"));
46 }
47 target(difficulty)
48}
49
50pub fn mine_cpu(
52 prefix: &[u8],
53 difficulty: u64,
54 start: u64,
55 attempts: u32,
56) -> Result<Option<Solution>, String> {
57 let limit = validate_batch(prefix, difficulty, attempts)?;
58 for offset in 0..attempts {
59 let nonce = start.wrapping_add(u64::from(offset));
60 let work = seal(prefix, nonce)?;
61 if U256::from_little_endian(&work) <= limit {
62 return Ok(Some((nonce, work)));
63 }
64 }
65 Ok(None)
66}
67
68#[cfg(test)]
69mod tests {
70 #![allow(
71 clippy::unwrap_used,
72 clippy::expect_used,
73 clippy::indexing_slicing,
74 clippy::arithmetic_side_effects
75 )]
76 use super::*;
77
78 pub(super) fn prefix() -> Vec<u8> {
79 let mut bytes = b"subtensor-pow-register-v1".to_vec();
80 bytes.extend_from_slice(&1u16.to_le_bytes());
81 bytes.extend_from_slice(&[1u8; 32]);
82 bytes.extend_from_slice(&[2u8; 32]);
83 bytes.extend_from_slice(&[3u8; 32]);
84 bytes
85 }
86
87 #[test]
88 fn seal_matches_independent_python_vectors() {
89 let prefix = prefix();
90 for (nonce, expected) in [
91 (
92 0,
93 "7cdaced691a70795a7656ad9b76ef2c16b961686a14a790bcb22eed6226ef103",
94 ),
95 (
96 1,
97 "c2291b632879447b6fe0785b0f25b5151c85a7e07df23a7860cf029e8a03f466",
98 ),
99 (
100 u64::MAX,
101 "69f1aef50783893195365b4fb62a4745085c31fee525a0984bc20301ab377aaf",
102 ),
103 ] {
104 assert_eq!(hex::encode(seal(&prefix, nonce).unwrap()), expected);
105 }
106 }
107
108 #[test]
109 fn cpu_mining_preserves_nonce_and_little_endian_target() {
110 let prefix = prefix();
111 let solution = mine_cpu(&prefix, 1, u64::MAX, 2).unwrap().unwrap();
112 assert_eq!(solution.0, u64::MAX);
113 assert_eq!(solution.1, seal(&prefix, u64::MAX).unwrap());
114 assert_eq!(target(2).unwrap(), U256::MAX / U256::from(2));
115 assert_eq!(target(u64::MAX).unwrap(), U256::MAX / U256::from(u64::MAX));
116 }
117
118 #[test]
119 #[ignore = "requires physical OpenCL GPUs; run explicitly on the GPU test host"]
120 fn gpu_mines_and_revalidates_chain_compatible_work() {
121 let mut miner = GpuMiner::new(None).expect("hardware test requires GPUs");
122 assert!(!miner.devices().is_empty());
123 let prefix = prefix();
124 for start in [0, u64::MAX - 100] {
125 let (nonce, work) = miner.mine(&prefix, 10, start, 4096).unwrap().unwrap();
126 assert_eq!(work, seal(&prefix, nonce).unwrap());
127 assert!(U256::from_little_endian(&work) <= target(10).unwrap());
128 }
129 }
130
131 #[test]
132 fn invalid_challenges_and_unbounded_batches_are_rejected() {
133 let prefix = prefix();
134 assert!(mine_cpu(&prefix, 0, 0, 1).is_err());
135 assert!(mine_cpu(&prefix, 1, 0, 0).is_err());
136 assert!(mine_cpu(&prefix, 1, 0, MAX_BATCH + 1).is_err());
137 assert!(mine_cpu(&prefix[..122], 1, 0, 1).is_err());
138 let mut root = prefix.clone();
139 root[25..27].fill(0);
140 assert!(seal(&root, 1).is_err());
141 }
142}