use core::convert::Infallible;
use core::num::NonZeroU8;
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature = "std")]
use super::AsyncPowAdapter;
use super::PowAdapter;
#[cfg(feature = "std")]
use super::blocking_wrapper::BlockingPowFuture;
#[cfg(feature = "std")]
use crate::util::BoxedFuture;
use crate::{Tag, UnsignedEvent};
#[derive(Debug)]
pub struct SingleThreadPow;
impl PowAdapter for SingleThreadPow {
type Error = Infallible;
#[inline]
fn compute(
&self,
unsigned: UnsignedEvent,
target_difficulty: NonZeroU8,
) -> Result<UnsignedEvent, Self::Error> {
Ok(mine(unsigned, target_difficulty.get(), None).unwrap())
}
}
#[cfg(feature = "std")]
impl AsyncPowAdapter for SingleThreadPow {
type Error = Infallible;
fn compute_async(
&self,
unsigned: UnsignedEvent,
target_difficulty: NonZeroU8,
) -> BoxedFuture<'_, Result<UnsignedEvent, Self::Error>> {
let diff: u8 = target_difficulty.get();
Box::pin(async move {
Ok(
BlockingPowFuture::new(Box::new(move |cancel| mine(unsigned, diff, Some(&cancel))))
.await
.unwrap(),
)
})
}
}
pub(super) fn mine(
mut unsigned: UnsignedEvent,
difficulty: u8,
cancel: Option<&AtomicBool>,
) -> Option<UnsignedEvent> {
let mut nonce: u128 = 0;
unsigned.tags.push(Tag::pow(0, difficulty));
let pow_tag_index = unsigned.tags.len() - 1;
loop {
if let Some(cancel) = cancel {
if nonce % 1024 == 0 && cancel.load(Ordering::SeqCst) {
return None;
}
}
nonce += 1;
unsigned.tags[pow_tag_index] = Tag::pow(nonce, difficulty);
let event_id = unsigned.compute_id();
if event_id.check_pow(difficulty) {
unsigned.id = Some(event_id);
return Some(unsigned);
}
}
}
#[cfg(test)]
#[cfg(feature = "std")]
pub mod tests {
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::thread;
use std::time::Duration;
use super::*;
use crate::prelude::*;
#[test]
fn single_adapter() {
let unsigned = EventBuilder::text_note(
"Proof of Work: The only workout my CPU gets since I stopped gaming",
)
.finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap());
let unsigned = unsigned
.mine(&SingleThreadPow, NonZeroU8::new(2).unwrap())
.unwrap();
let Some(nonce_tag) = unsigned.tags.iter().find(|t| t.kind() == "nonce") else {
panic!("nonce tag should be exist")
};
assert!(unsigned.id.is_some());
assert_eq!(nonce_tag.as_slice()[2], "2");
assert!(get_leading_zero_bits(unsigned.id.unwrap()) >= 2)
}
#[test]
fn single_thread_mining_can_be_cancelled() {
let cancel: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
let unsigned = EventBuilder::text_note("single thread cancellation test")
.finalize_unsigned(PublicKey::from_slice(&[0; 32]).unwrap());
let worker_cancel: Arc<AtomicBool> = cancel.clone();
let handle = thread::spawn(move || mine(unsigned, u8::MAX, Some(worker_cancel.as_ref())));
thread::sleep(Duration::from_millis(10));
cancel.store(true, Ordering::SeqCst);
let result = handle.join().expect("single thread miner should not panic");
assert!(
result.is_none(),
"single thread miner should stop on cancel"
);
}
}