use std::sync::{
atomic::{AtomicU8, Ordering},
Arc,
};
use stratum_apps::{
key_utils::Secp256k1PublicKey,
stratum_core::{
binary_sv2::{Sv2DataType, U256},
bitcoin::{
block::{Header, Version},
hashes::Hash,
CompactTarget, Target, TxMerkleNode,
},
channels_sv2::{
merkle_root::merkle_root_from_path,
target::{bytes_to_hex, u256_to_block_hash},
},
sv1_api::{
client_to_server::{self, Submit},
json_rpc,
server_to_client::Notify,
utils::HexU32Be,
Message,
},
},
utils::types::{ChannelId, DownstreamId},
};
use tracing::{debug, warn};
use crate::error::TproxyErrorKind;
pub const AGGREGATED_CHANNEL_ID: ChannelId = u32::MAX;
pub fn validate_sv1_share(
share: &client_to_server::Submit<'static>,
target: Target,
extranonce1: Vec<u8>,
version_rolling_mask: Option<HexU32Be>,
job: Notify<'static>,
) -> Result<bool, TproxyErrorKind> {
let mut full_extranonce = vec![];
full_extranonce.extend_from_slice(extranonce1.as_slice());
full_extranonce.extend_from_slice(share.extra_nonce2.0.as_ref());
let share_version = share
.version_bits
.clone()
.map(|vb| vb.0)
.unwrap_or(job.version.0);
let mask = version_rolling_mask.unwrap_or(HexU32Be(0x1FFFE000_u32)).0;
let version = (job.version.0 & !mask) | (share_version & mask);
let prev_hash_vec: Vec<u8> = job.prev_hash.clone().into();
let prev_hash = U256::from_vec_(prev_hash_vec).map_err(TproxyErrorKind::BinarySv2)?;
let merkle_root: [u8; 32] = merkle_root_from_path(
job.coin_base1.as_ref(),
job.coin_base2.as_ref(),
full_extranonce.as_ref(),
job.merkle_branch.as_ref(),
)
.ok_or(TproxyErrorKind::InvalidMerkleRoot)?
.try_into()
.map_err(|_| TproxyErrorKind::InvalidMerkleRoot)?;
let header = Header {
version: Version::from_consensus(version as i32),
prev_blockhash: u256_to_block_hash(prev_hash),
merkle_root: TxMerkleNode::from_byte_array(merkle_root),
time: share.time.0,
bits: CompactTarget::from_consensus(job.bits.0),
nonce: share.nonce.0,
};
let hash = header.block_hash();
let raw_hash: [u8; 32] = *hash.to_raw_hash().as_ref();
let hash_as_target = Target::from_le_bytes(raw_hash);
let hash_bytes = hash_as_target.to_be_bytes();
let target_bytes = target.to_be_bytes();
debug!(
"share validation \nshare:\t\t{}\ndownstream target:\t{}\n",
bytes_to_hex(&hash_bytes),
bytes_to_hex(&target_bytes),
);
if hash_as_target < target {
return Ok(true);
}
Ok(false)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AggregatedState {
NoChannel = 0,
Pending = 1,
Connected = 2,
}
#[derive(Clone, Debug)]
pub struct AtomicAggregatedState {
inner: Arc<AtomicU8>,
}
impl AtomicAggregatedState {
pub fn new(state: AggregatedState) -> Self {
Self {
inner: Arc::new(AtomicU8::new(state as u8)),
}
}
pub fn get(&self) -> AggregatedState {
match self.inner.load(Ordering::SeqCst) {
0 => AggregatedState::NoChannel,
1 => AggregatedState::Pending,
2 => AggregatedState::Connected,
v => panic!("Invalid UpstreamState value: {v}"),
}
}
pub fn set(&self, state: AggregatedState) {
self.inner.store(state as u8, Ordering::SeqCst);
}
}
#[derive(Debug)]
pub struct UpstreamEntry {
pub host: String,
pub port: u16,
pub authority_pubkey: Secp256k1PublicKey,
pub tried_or_flagged: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TproxyMode {
Aggregated,
NonAggregated,
}
impl From<bool> for TproxyMode {
fn from(value: bool) -> Self {
if value {
return TproxyMode::Aggregated;
}
TproxyMode::NonAggregated
}
}
impl TproxyMode {
pub(crate) fn is_aggregated(self) -> bool {
TproxyMode::Aggregated == self
}
pub(crate) fn is_non_aggregated(self) -> bool {
TproxyMode::NonAggregated == self
}
}
#[derive(Debug)]
pub enum DownstreamMessages {
SubmitShares(SubmitShareWithChannelId),
OpenChannel(DownstreamId), }
#[derive(Debug, Clone)]
pub struct SubmitShareWithChannelId {
pub channel_id: ChannelId,
pub downstream_id: DownstreamId,
pub share: Submit<'static>,
pub extranonce: Vec<u8>,
pub extranonce2_len: usize,
pub version_rolling_mask: Option<HexU32Be>,
pub job_version: Option<u32>,
}
pub(crate) const KEEPALIVE_JOB_ID_DELIMITER: char = '#';
pub(crate) fn is_mining_authorize(msg: &Message) -> bool {
if let json_rpc::Message::StandardRequest(r) = &msg {
r.method == "mining.authorize"
} else {
false
}
}
pub(crate) fn tlv_compatible_username(s: &str) -> &str {
const MAX_USER_IDENTITY_BYTES: usize = 32;
let len = s.len();
if len <= MAX_USER_IDENTITY_BYTES {
return s;
}
let mut end = MAX_USER_IDENTITY_BYTES;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
let truncated = &s[..end];
warn!(
"Username '{}' exceeds {} bytes ({} bytes), truncating to '{}'. \
Consider using a shorter username for full visibility on the pool dashboard.",
s, MAX_USER_IDENTITY_BYTES, len, truncated
);
truncated
}