use faster_hex::hex_encode;
use sha3::digest::generic_array::typenum::U32;
use sha3::digest::generic_array::GenericArray;
use sha3::{Digest, Sha3_256};
use crate::block::BlockHeader;
#[cfg(any(feature = "cuda", feature = "opencl"))]
use crate::gpu::{gpu_miner_mine, gpu_miner_update};
use crate::transaction::TransactionID;
#[derive(Clone, Default, Debug)]
pub struct BlockHeaderHasher {
pub previous_hash_list_root: TransactionID,
pub previous_time: u64,
pub previous_nonce: u64,
pub previous_transaction_count: u32,
pub hash_list_root_offset: usize,
pub time_offset: usize,
pub nonce_offset: usize,
pub transaction_count_offset: usize,
pub time_len: usize,
pub nonce_len: usize,
pub tx_count_len: usize,
pub initialized: bool,
pub buf_len: usize,
pub buffer: Vec<u8>,
pub hasher: Sha3_256,
pub result: GenericArray<u8, U32>,
pub hashes_per_attempt: u64,
}
pub const HDR_PREVIOUS: &[u8] = br#"{"previous":""#;
pub const HDR_HASH_LIST_ROOT: &[u8] = br#"","hash_list_root":""#;
pub const HDR_TIME: &[u8] = br#"","time":"#;
pub const HDR_TARGET: &[u8] = br#","target":""#;
pub const HDR_CHAIN_WORK: &[u8] = br#"","chain_work":""#;
pub const HDR_NONCE: &[u8] = br#"","nonce":"#;
pub const HDR_HEIGHT: &[u8] = br#","height":"#;
pub const HDR_TRANSACTION_COUNT: &[u8] = br#","transaction_count":"#;
pub const HDR_END: &[u8] = br#"}"#;
const BUF_LEN: usize = HDR_PREVIOUS.len()
+ 64 + HDR_HASH_LIST_ROOT.len()
+ 64 + HDR_TIME.len()
+ 19 + HDR_TARGET.len()
+ 64 + HDR_CHAIN_WORK.len()
+ 64 + HDR_NONCE.len()
+ 19 + HDR_HEIGHT.len()
+ 19 + HDR_TRANSACTION_COUNT.len()
+ 10 + HDR_END.len();
impl BlockHeaderHasher {
pub fn new() -> Self {
Self {
buffer: vec![0; BUF_LEN],
hashes_per_attempt: 1,
..Default::default()
}
}
pub fn init_buffer(&mut self, header: &mut BlockHeader) {
self.buffer[..HDR_PREVIOUS.len()].copy_from_slice(HDR_PREVIOUS);
let mut buf_len = HDR_PREVIOUS.len();
let _ = hex_encode(
&header.previous,
&mut self.buffer[buf_len..][..header.previous.len() * 2],
);
buf_len += header.previous.len() * 2;
self.previous_hash_list_root = header.hash_list_root;
self.buffer[buf_len..][..HDR_HASH_LIST_ROOT.len()].copy_from_slice(HDR_HASH_LIST_ROOT);
buf_len += HDR_HASH_LIST_ROOT.len();
self.hash_list_root_offset = buf_len;
let _ = hex_encode(
&header.hash_list_root,
&mut self.buffer[buf_len..][..header.hash_list_root.len() * 2],
);
buf_len += header.hash_list_root.len() * 2;
self.previous_time = header.time;
self.buffer[buf_len..][..HDR_TIME.len()].copy_from_slice(HDR_TIME);
buf_len += HDR_TIME.len();
self.time_offset = buf_len;
let mut int_buf = itoa::Buffer::new();
let time_str = int_buf.format(header.time);
self.buffer[buf_len..buf_len + time_str.len()].copy_from_slice(time_str.as_bytes());
self.time_len = time_str.len();
buf_len += time_str.len();
self.buffer[buf_len..][..HDR_TARGET.len()].copy_from_slice(HDR_TARGET);
buf_len += HDR_TARGET.len();
let _ = hex_encode(
&header.target,
&mut self.buffer[buf_len..][..header.target.len() * 2],
);
buf_len += header.target.len() * 2;
self.buffer[buf_len..][..HDR_CHAIN_WORK.len()].copy_from_slice(HDR_CHAIN_WORK);
buf_len += HDR_CHAIN_WORK.len();
let _ = hex_encode(
&header.chain_work,
&mut self.buffer[buf_len..buf_len + header.chain_work.len() * 2],
);
buf_len += header.chain_work.len() * 2;
self.previous_nonce = header.nonce;
self.buffer[buf_len..][..HDR_NONCE.len()].copy_from_slice(HDR_NONCE);
buf_len += HDR_NONCE.len();
self.nonce_offset = buf_len;
let nonce_str = int_buf.format(header.nonce);
self.buffer[buf_len..][..nonce_str.len()].copy_from_slice(nonce_str.as_bytes());
self.nonce_len = nonce_str.len();
buf_len += nonce_str.len();
self.buffer[buf_len..][..HDR_HEIGHT.len()].copy_from_slice(HDR_HEIGHT);
buf_len += HDR_HEIGHT.len();
let height_str = int_buf.format(header.height);
self.buffer[buf_len..][..height_str.len()].copy_from_slice(height_str.as_bytes());
buf_len += height_str.len();
self.previous_transaction_count = header.transaction_count;
self.buffer[buf_len..][..HDR_TRANSACTION_COUNT.len()]
.copy_from_slice(HDR_TRANSACTION_COUNT);
buf_len += HDR_TRANSACTION_COUNT.len();
self.transaction_count_offset = buf_len;
let transaction_count_str = int_buf.format(header.transaction_count);
self.buffer[buf_len..][..transaction_count_str.len()]
.copy_from_slice(transaction_count_str.as_bytes());
self.tx_count_len = transaction_count_str.len();
buf_len += transaction_count_str.len();
self.buffer[buf_len..][..HDR_END.len()].copy_from_slice(HDR_END);
buf_len += HDR_END.len();
self.buf_len = buf_len;
self.initialized = true;
}
pub fn update(_miner_num: usize, header: &mut BlockHeader, hasher: &mut BlockHeaderHasher) {
let device_mining = cfg!(feature = "cuda") || cfg!(feature = "opencl");
let mut _buffer_changed = false;
if !hasher.initialized {
hasher.init_buffer(header);
_buffer_changed = true;
} else {
if hasher.previous_hash_list_root != header.hash_list_root {
_buffer_changed = true;
hasher.previous_hash_list_root = header.hash_list_root;
let _ = hex_encode(
&header.hash_list_root,
&mut hasher.buffer[hasher.hash_list_root_offset..]
[..header.hash_list_root.len() * 2],
);
}
let mut offset = 0;
let mut int_buf = itoa::Buffer::new();
if hasher.previous_time != header.time {
_buffer_changed = true;
hasher.previous_time = header.time;
let mut buf_len = hasher.time_offset;
let time_str = int_buf.format(header.time);
let time_len = time_str.len();
hasher.buffer[buf_len..buf_len + time_len].copy_from_slice(time_str.as_bytes());
buf_len += time_len;
offset = time_len as isize - hasher.time_len as isize;
hasher.time_len = time_len;
if offset != 0 {
hasher.buffer[buf_len..buf_len + HDR_TARGET.len()].copy_from_slice(HDR_TARGET);
buf_len += HDR_TARGET.len();
let _ = hex_encode(
&header.target,
&mut hasher.buffer[buf_len..buf_len + header.target.len() * 2],
);
buf_len += header.target.len() * 2;
hasher.buffer[buf_len..buf_len + HDR_CHAIN_WORK.len()]
.copy_from_slice(HDR_CHAIN_WORK);
buf_len += HDR_CHAIN_WORK.len();
let _ = hex_encode(
&header.chain_work,
&mut hasher.buffer[buf_len..buf_len + header.chain_work.len() * 2],
);
buf_len += header.chain_work.len() * 2;
hasher.buffer[buf_len..buf_len + HDR_NONCE.len()].copy_from_slice(HDR_NONCE);
}
}
if offset != 0 || (!device_mining && hasher.previous_nonce != header.nonce) {
_buffer_changed = true;
hasher.previous_nonce = header.nonce;
hasher.nonce_offset = (hasher.nonce_offset as isize + offset) as usize;
let mut buf_len = hasher.nonce_offset;
let nonce_str = int_buf.format(header.nonce);
let nonce_len = nonce_str.len();
hasher.buffer[buf_len..buf_len + nonce_len].copy_from_slice(nonce_str.as_bytes());
buf_len += nonce_len;
offset += nonce_len as isize - hasher.nonce_len as isize;
hasher.nonce_len = nonce_len;
if offset != 0 {
hasher.buffer[buf_len..buf_len + HDR_HEIGHT.len()].copy_from_slice(HDR_HEIGHT);
buf_len += HDR_HEIGHT.len();
let height_str = int_buf.format(header.height);
let height_len = height_str.len();
hasher.buffer[buf_len..buf_len + height_len]
.copy_from_slice(height_str.as_bytes());
buf_len += height_len;
hasher.buffer[buf_len..buf_len + HDR_TRANSACTION_COUNT.len()]
.copy_from_slice(HDR_TRANSACTION_COUNT);
}
}
if offset != 0 || hasher.previous_transaction_count != header.transaction_count {
_buffer_changed = true;
hasher.previous_transaction_count = header.transaction_count;
hasher.transaction_count_offset =
(hasher.transaction_count_offset as isize + offset) as usize;
let mut buf_len = hasher.transaction_count_offset;
let transaction_count_str = int_buf.format(header.transaction_count);
let tx_count_len = transaction_count_str.len();
hasher.buffer[buf_len..buf_len + tx_count_len]
.copy_from_slice(transaction_count_str.as_bytes());
buf_len += tx_count_len;
offset += tx_count_len as isize - hasher.tx_count_len as isize;
hasher.tx_count_len = tx_count_len;
if offset != 0 {
hasher.buffer[buf_len..buf_len + HDR_END.len()].copy_from_slice(HDR_END);
}
}
hasher.buf_len = (hasher.buf_len as isize + offset) as usize;
}
#[cfg(any(feature = "cuda", feature = "opencl"))]
{
let nonce = hasher.update_device(_miner_num, header, _buffer_changed);
if nonce == 0x7fffffff_ffffffff {
hasher.result.fill(0xff);
return;
} else {
log::info!(
"GPU miner {_miner_num} found a possible solution: {nonce}, double-checking it..."
);
header.nonce = nonce;
hasher.init_buffer(header);
}
}
hasher.hasher.update(&hasher.buffer[..hasher.buf_len]);
hasher.hasher.finalize_into_reset(&mut hasher.result);
}
#[cfg(any(feature = "cuda", feature = "opencl"))]
pub fn update_device(
&mut self,
miner_num: usize,
header: &BlockHeader,
buffer_changed: bool,
) -> u64 {
if buffer_changed {
let last_offset = self.nonce_offset + self.nonce_len;
self.hashes_per_attempt = gpu_miner_update(
miner_num,
&self.buffer,
self.buf_len,
self.nonce_offset,
last_offset,
&header.target,
);
}
gpu_miner_mine(miner_num, header.nonce)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::block::test_utils::make_test_block;
use crate::block::{Block, BlockID};
#[test]
fn test_block_header_hasher() {
let mut block = make_test_block(10);
assert!(compare_ids(&mut block), "ID mismatch 1");
block.header.time = 1234;
assert!(compare_ids(&mut block), "ID mismatch 2");
block.header.nonce = 1234;
assert!(compare_ids(&mut block), "ID mismatch 3");
block.header.nonce = 1235;
assert!(compare_ids(&mut block), "ID mismatch 4");
block.header.nonce = 1236;
block.header.time = 1234;
assert!(compare_ids(&mut block), "ID mismatch 5");
block.header.time = 123498;
block.header.nonce = 12370910;
let tx = &block.transactions[1];
let tx_id = tx.id().unwrap();
block.add_transaction(tx_id, tx.clone()).unwrap();
assert!(compare_ids(&mut block), "ID mismatch 6");
block.header.time = 987654321;
assert!(compare_ids(&mut block), "ID mismatch 7");
}
#[test]
fn test_block_header_hasher_reuses_buffer_across_length_changes() {
let mut block = make_test_block(10);
let mut hasher = BlockHeaderHasher::new();
assert!(
compare_ids_with_hasher(&mut block, &mut hasher),
"ID mismatch 1"
);
block.header.time = 9;
assert!(
compare_ids_with_hasher(&mut block, &mut hasher),
"ID mismatch 2"
);
block.header.time = 10;
assert!(
compare_ids_with_hasher(&mut block, &mut hasher),
"ID mismatch 3"
);
block.header.nonce = 99;
assert!(
compare_ids_with_hasher(&mut block, &mut hasher),
"ID mismatch 4"
);
block.header.nonce = 100;
assert!(
compare_ids_with_hasher(&mut block, &mut hasher),
"ID mismatch 5"
);
block.header.transaction_count = 99;
assert!(
compare_ids_with_hasher(&mut block, &mut hasher),
"ID mismatch 6"
);
block.header.transaction_count = 100;
assert!(
compare_ids_with_hasher(&mut block, &mut hasher),
"ID mismatch 7"
);
}
#[test]
fn test_device_header_bytes_ignore_cached_nonce_width() {
let mut block = make_test_block(10);
block.header.nonce = 9;
let mut hasher = BlockHeaderHasher::new();
hasher.init_buffer(&mut block.header);
assert_eq!(
device_header_bytes(&block, &hasher),
serde_json::to_vec(&block.header).unwrap()
);
block.header.nonce = 10;
assert_eq!(
device_header_bytes(&block, &hasher),
serde_json::to_vec(&block.header).unwrap()
);
block.header.nonce = 100;
assert_eq!(
device_header_bytes(&block, &hasher),
serde_json::to_vec(&block.header).unwrap()
);
}
fn compare_ids(block: &mut Block) -> bool {
let id = block.id().unwrap();
let mut hasher = BlockHeaderHasher::new();
block.header.id_fast(0, &mut hasher);
let id2 = BlockID::from(&hasher.result[..]);
id == id2
}
fn compare_ids_with_hasher(block: &mut Block, hasher: &mut BlockHeaderHasher) -> bool {
let id = block.id().unwrap();
block.header.id_fast(0, hasher);
let id2 = BlockID::from(&hasher.result[..]);
id == id2
}
fn device_header_bytes(block: &Block, hasher: &BlockHeaderHasher) -> Vec<u8> {
let mut nonce_buf = itoa::Buffer::new();
let nonce_str = nonce_buf.format(block.header.nonce);
let last_offset = hasher.nonce_offset + hasher.nonce_len;
let mut bytes = Vec::with_capacity(
hasher.nonce_offset + nonce_str.len() + hasher.buf_len - last_offset,
);
bytes.extend_from_slice(&hasher.buffer[..hasher.nonce_offset]);
bytes.extend_from_slice(nonce_str.as_bytes());
bytes.extend_from_slice(&hasher.buffer[last_offset..hasher.buf_len]);
bytes
}
}