1use std::{
20 io::{Error as IoError, Read, Result as IoResult, Write},
21 sync::Arc,
22};
23
24use log::info;
25use rand::rngs::OsRng;
26use smol::lock::RwLock;
27use structopt::StructOpt;
28use url::Url;
29
30use darkfi::{system::ExecutorPtr, Error, Result};
31use darkfi_sdk::crypto::{Keypair, PublicKey, SecretKey};
32use darkfi_serial::{
33 async_trait, AsyncDecodable, AsyncEncodable, AsyncRead, AsyncWrite, Decodable, Encodable,
34};
35
36use crate::{
37 bitcoin::{BitcoinBlockHash, BitcoinHashCache},
38 equix::{Challenge, EquiXBuilder, EquiXPow, Solution, SolverMemory, NONCE_LEN},
39};
40
41#[derive(Clone, Debug)]
42pub struct PowSettings {
43 pub equix_effort: u32,
45 pub btc_hash_count: usize,
47 pub btc_timeout: u64,
49 pub btc_electrum_nodes: Vec<Url>,
51}
52
53impl Default for PowSettings {
54 fn default() -> Self {
55 Self {
56 equix_effort: 10000,
57 btc_hash_count: 144,
58 btc_timeout: 15,
59 btc_electrum_nodes: vec![],
60 }
61 }
62}
63
64#[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
65#[structopt()]
66#[serde(rename = "pow")]
67pub struct PowSettingsOpt {
68 #[structopt(long)]
70 pub equix_effort: Option<u32>,
71
72 #[structopt(long)]
74 pub btc_hash_count: Option<usize>,
75
76 #[structopt(long)]
78 pub btc_timeout: Option<u64>,
79
80 #[structopt(long, use_delimiter = true)]
82 pub btc_electrum_nodes: Vec<Url>,
83}
84
85impl From<PowSettingsOpt> for PowSettings {
86 fn from(opt: PowSettingsOpt) -> Self {
87 let def = PowSettings::default();
88
89 Self {
90 equix_effort: opt.equix_effort.unwrap_or(def.equix_effort),
91 btc_hash_count: opt.btc_hash_count.unwrap_or(def.btc_hash_count),
92 btc_timeout: opt.btc_timeout.unwrap_or(def.btc_timeout),
93 btc_electrum_nodes: opt.btc_electrum_nodes,
94 }
95 }
96}
97
98pub struct FudPow {
100 pub settings: Arc<RwLock<PowSettings>>,
101 pub bitcoin_hash_cache: BitcoinHashCache,
102 equix_pow: EquiXPow,
103}
104impl FudPow {
105 pub fn new(settings: PowSettings, ex: ExecutorPtr) -> Self {
106 let pow_settings: Arc<RwLock<PowSettings>> = Arc::new(RwLock::new(settings));
107 let bitcoin_hash_cache = BitcoinHashCache::new(pow_settings.clone(), ex.clone());
108
109 Self {
110 settings: pow_settings,
111 bitcoin_hash_cache,
112 equix_pow: EquiXPow {
113 effort: 0, challenge: Challenge::new(&[], &[0u8; NONCE_LEN]),
115 equix: EquiXBuilder::default(),
116 mem: SolverMemory::default(),
117 },
118 }
119 }
120
121 pub async fn generate_node(&mut self) -> Result<(VerifiableNodeData, SecretKey)> {
123 info!(target: "fud::FudPow::generate_node()", "Generating a new node id...");
124
125 let keypair = Keypair::random(&mut OsRng);
127
128 let n = 3;
130 let btc_block_hash = {
131 let block_hashes = &self.bitcoin_hash_cache.block_hashes;
132 if block_hashes.is_empty() {
133 return Err(Error::Custom(
134 "Can't generate a node id without BTC block hashes".into(),
135 ));
136 }
137
138 let block_hash = if n > block_hashes.len() {
139 block_hashes.last()
140 } else {
141 block_hashes.get(block_hashes.len() - 1 - n)
142 };
143
144 if block_hash.is_none() {
145 return Err(Error::Custom("Could not find a recent BTC block hash".into()));
146 }
147 *block_hash.unwrap()
148 };
149
150 self.equix_pow.effort = self.settings.read().await.equix_effort;
152
153 self.equix_pow.challenge = Challenge::new(
155 &[keypair.public.to_bytes(), btc_block_hash].concat(),
156 &[0u8; NONCE_LEN],
157 );
158
159 info!(target: "fud::FudPow::generate_node()", "Equi-X Proof-of-Work starts...");
161 let solution =
162 self.equix_pow.run().map_err(|e| Error::Custom(format!("Equi-X error: {e}")))?;
163 info!(target: "fud::FudPow::generate_node()", "Equi-X Proof-of-Work is done");
164
165 Ok((
167 VerifiableNodeData {
168 public_key: keypair.public,
169 btc_block_hash,
170 nonce: self.equix_pow.challenge.nonce(),
171 solution,
172 },
173 keypair.secret,
174 ))
175 }
176
177 pub async fn verify_node(&mut self, node_data: &VerifiableNodeData) -> Result<()> {
179 self.equix_pow.effort = self.settings.read().await.equix_effort;
181
182 if !self.bitcoin_hash_cache.block_hashes.contains(&node_data.btc_block_hash) {
184 return Err(Error::Custom(
185 "Error verifying node data: the BTC block hash is unknown".into(),
186 ))
187 }
188
189 self.equix_pow
191 .verify(&node_data.challenge(), &node_data.solution)
192 .map_err(|e| Error::Custom(format!("Error verifying Equi-X solution: {e}")))
193 }
194}
195
196#[derive(Debug, Clone)]
198pub struct VerifiableNodeData {
199 pub public_key: PublicKey,
200 pub btc_block_hash: BitcoinBlockHash,
201 pub nonce: [u8; NONCE_LEN],
202 pub solution: Solution,
203}
204
205impl VerifiableNodeData {
206 pub fn id(&self) -> blake3::Hash {
208 blake3::hash(&[self.challenge().to_bytes(), self.solution.to_bytes().to_vec()].concat())
209 }
210
211 pub fn challenge(&self) -> Challenge {
213 Challenge::new(&[self.public_key.to_bytes(), self.btc_block_hash].concat(), &self.nonce)
214 }
215}
216
217impl Encodable for VerifiableNodeData {
218 fn encode<S: Write>(&self, s: &mut S) -> IoResult<usize> {
219 let mut len = 0;
220 len += self.public_key.encode(s)?;
221 len += self.btc_block_hash.encode(s)?;
222 len += self.nonce.encode(s)?;
223 len += self.solution.to_bytes().encode(s)?;
224 Ok(len)
225 }
226}
227
228#[async_trait]
229impl AsyncEncodable for VerifiableNodeData {
230 async fn encode_async<S: AsyncWrite + Unpin + Send>(&self, s: &mut S) -> IoResult<usize> {
231 let mut len = 0;
232 len += self.public_key.encode_async(s).await?;
233 len += self.btc_block_hash.encode_async(s).await?;
234 len += self.nonce.encode_async(s).await?;
235 len += self.solution.to_bytes().encode_async(s).await?;
236 Ok(len)
237 }
238}
239
240impl Decodable for VerifiableNodeData {
241 fn decode<D: Read>(d: &mut D) -> IoResult<Self> {
242 Ok(Self {
243 public_key: PublicKey::decode(d)?,
244 btc_block_hash: BitcoinBlockHash::decode(d)?,
245 nonce: <[u8; NONCE_LEN]>::decode(d)?,
246 solution: Solution::try_from_bytes(&<[u8; Solution::NUM_BYTES]>::decode(d)?)
247 .map_err(|e| IoError::other(format!("Error parsing Equi-X solution: {e}")))?,
248 })
249 }
250}
251
252#[async_trait]
253impl AsyncDecodable for VerifiableNodeData {
254 async fn decode_async<D: AsyncRead + Unpin + Send>(d: &mut D) -> IoResult<Self> {
255 Ok(Self {
256 public_key: PublicKey::decode_async(d).await?,
257 btc_block_hash: BitcoinBlockHash::decode_async(d).await?,
258 nonce: <[u8; NONCE_LEN]>::decode_async(d).await?,
259 solution: Solution::try_from_bytes(
260 &<[u8; Solution::NUM_BYTES]>::decode_async(d).await?,
261 )
262 .map_err(|e| IoError::other(format!("Error parsing Equi-X solution: {e}")))?,
263 })
264 }
265}