Skip to content

Commit

Permalink
Common properties doc additions
Browse files Browse the repository at this point in the history
  • Loading branch information
rrybarczyk committed Dec 21, 2024
1 parent 8d16924 commit da2b372
Showing 1 changed file with 168 additions and 42 deletions.
210 changes: 168 additions & 42 deletions protocols/v2/roles-logic-sv2/src/common_properties.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
//! This module defines traits for properties that every SRI-based application should implement
//! # Common Properties for Stratum V2 (Sv2) Roles
//!
//! Defines common properties, traits, and utilities for implementing upstream and downstream
//! roles. It provides abstractions for features like connection pairing, mining job distribution,
//! and channel management. These definitions form the foundation for consistent communication and
//! behavior across Sv2 roles/applications.
use crate::selectors::{
DownstreamMiningSelector, DownstreamSelector, NullDownstreamMiningSelector,
Expand All @@ -8,32 +13,82 @@ use mining_sv2::{Extranonce, Target};
use nohash_hasher::BuildNoHashHasher;
use std::{collections::HashMap, fmt::Debug as D};

/// Defines a mining downstream node at the most basic level
/// Defines a mining downstream role at the most basic level.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct CommonDownstreamData {
/// Header-only mining flag.
///
/// Enables the processing of block headers only, leaving transaction management and template
/// construction to the upstream role. It reduces the amount of data processed and transmitted
/// by the downstream role, making it useful when bandwidth is limited and transmitting full
/// block templates is costly.
///
/// - `true`: The downstream role only processes block headers, relying on the upstream for
/// transaction management.
/// - `false`: The downstream role handles full blocks.
pub header_only: bool,

/// Work selection flag.
///
/// Enables the selection of which transactions or templates the role will work on. It allows
/// for more autonomy for downstream roles to define specific version bits to roll, adjust the
/// difficulty target, apply `timestamp` range constraints, etc.
///
/// - `true`: The downstream role can modify or customize the mining job, such as choosing
/// specific transactions to include in a block template.
/// - `false`: The downstream role strictly follows the work provided by the upstream, such as
/// pre-constructed templates from a pool.
pub work_selection: bool,

/// Version rolling flag.
///
/// Enables rolling the block header version bits which allows for more unique hash generation
/// on the same block template by expanding the nonce-space. Used when other fields (e.g.
/// `nonce` or `extranonce`) are fully exhausted.
///
/// - `true`: The downstream supports version rolling and can modify specific bits in the
/// version field. This is useful for increasing hash rate efficiency by exploring a larger
/// solution space.
/// - `false`: The downstream does not support version rolling and relies on the upstream to
/// provide jobs with adjusted version fields.
pub version_rolling: bool,
}

/// SetupConnection sugared
/// Encapsulates settings for pairing upstream and downstream roles.
///
/// Simplifies the [`SetupConnection`] configuration process by bundling the protocol, version
/// range, and flag settings.
#[derive(Debug, Copy, Clone)]
pub struct PairSettings {
/// Role the settings apply to.
pub protocol: Protocol,

/// Minimum protocol version the role supports.
pub min_v: u16,

/// Minimum protocol version the role supports.
pub max_v: u16,

/// Flags indicating optional protocol features the role supports (e.g. header-only mining,
/// Noise protocol support, job configuration parameters, etc.). Each protocol field as its own
/// flags.
pub flags: u32,
}

/// General properties that every Sv2 compatible upstream node must implement.
/// Properties defining behaviors common to all Sv2 upstream roles.
pub trait IsUpstream<Down: IsDownstream, Sel: DownstreamSelector<Down> + ?Sized> {
/// Used to bitcoin protocol version for the channel.
/// Returns the protocol version used by the upstream node.
fn get_version(&self) -> u16;
/// Used to get flags for the defined sv2 message protocol

/// Returns the flags indicating the upstream node's protocol capabilities.
fn get_flags(&self) -> u32;
/// Used to check if the upstream supports the protocol that the downstream wants to use

/// Lists the protocols supported by the upstream node.
///
/// Used to check if the upstream supports the protocol that the downstream wants to use.
fn get_supported_protocols(&self) -> Vec<Protocol>;
/// Checking if the upstream supports the protocol that the downstream wants to use.

/// Verifies if the upstream can pair with the given downstream settings.
fn is_pairable(&self, pair_settings: &PairSettings) -> bool {
let protocol = pair_settings.protocol;
let min_v = pair_settings.min_v;
Expand All @@ -44,71 +99,117 @@ pub trait IsUpstream<Down: IsDownstream, Sel: DownstreamSelector<Down> + ?Sized>
let check_flags = SetupConnection::check_flags(protocol, self.get_flags(), flags);
check_version && check_flags
}
/// Should return the channel id

/// Returns the channel ID managed by the upstream node.
fn get_id(&self) -> u32;
/// Should return a request id mapper for viewing and handling request ids.

/// Provides a request ID mapper for viewing and managing upstream-downstream communication.
fn get_mapper(&mut self) -> Option<&mut RequestIdMapper>;
/// Should return the selector of the Downstream node. See [`crate::selectors`].

/// Returns the selector ([`crate::selectors`] for managing downstream nodes.
fn get_remote_selector(&mut self) -> &mut Sel;
}

/// Channel to be opened with the upstream nodes.
/// The types of channels that can be opened with upstream roles.
#[derive(Debug, Clone, Copy)]
pub enum UpstreamChannel {
/// nominal hash rate
/// A standard channel with a nominal hash rate.
///
/// Typically used for mining devices with a direct connection to an upstream role (e.g. a pool
/// or proxy). The hashrate is specified as a `f32` value, representing the expected
/// computational capacity of the miner.
Standard(f32),

/// A grouped channel for aggregated mining.
///
/// Aggregates mining work for multiple devices or clients under a single channel, enabling the
/// upstream to manage work distribution and result aggregation for an entire group of miners.
///
/// Typically used by a mining proxy managing multiple downstream miners.
Group,

/// An extended channel for additional features.
///
/// Provides additional features or capabilities beyond standard and group channels,
/// accommodating features like custom job templates or experimental protocol extensions.
Extended,
}

/// Properties of a standard mining channel.
///
/// Standard channels are intended to be used by end mining devices with a nominal hashrate, where
/// each device operates on an independent connection to its upstream.
#[derive(Debug, Clone)]
/// Standard channels are intended to be used by end mining devices.
pub struct StandardChannel {
/// Newly assigned identifier of the channel, stable for the whole lifetime of the connection.
/// e.g. it is used for broadcasting new jobs by `NewExtendedMiningJob`
/// Identifies a specific channel, unique to each mining connection.
///
/// Dynamically assigned when a mining connection is established (as part of the negotiation
/// process) to avoid conflicts with other connections (e.g. other mining devices) managed by
/// the same upstream role. The identifier remains stable for the whole lifetime of the
/// connection.
///
/// Used for broadcasting new jobs by [`mining_sv2::NewExtendedMiningJob`].
pub channel_id: u32,
/// Identifier of the group where the standard channel belongs

/// Identifies a specific group in which the standard channel belongs.
pub group_id: u32,
/// Initial target for the mining channel

/// Initial difficulty target assigned to the mining.
pub target: Target,
/// Extranonce bytes which need to be added to the coinbase to form a fully valid submission:
/// (full coinbase = coinbase_tx_prefix + extranonce_prefix + extranonce + coinbase_tx_suffix).

/// Additional nonce value used to differentiate shares within the same channel.
///
/// Helps to avoid nonce collisions when multiple mining devices are working on the same job.
///
/// The extranonce bytes are added to the coinbase to form a fully valid submission:
/// `full coinbase = coinbase_tx_prefix + extranonce_prefix + extranonce + coinbase_tx_suffix`
pub extranonce: Extranonce,
}

/// General properties that every Sv2 compatible mining upstream node must implement.
/// Properties of a Sv2-compatible mining upstream role.
///
/// This trait extends [`IsUpstream`] with additional functionality specific to mining, such as
/// hashrate management and channel updates.
pub trait IsMiningUpstream<Down: IsMiningDownstream, Sel: DownstreamMiningSelector<Down> + ?Sized>:
IsUpstream<Down, Sel>
{
/// should return total hash rate local to the node
/// Returns the total hashrate managed by the upstream role.
fn total_hash_rate(&self) -> u64;
/// add hashrate to the node

/// Adds hash rate to the upstream role.
fn add_hash_rate(&mut self, to_add: u64);
/// get open channels on the node

/// Returns the list of open channels on the upstream role.
fn get_opened_channels(&mut self) -> &mut Vec<UpstreamChannel>;
/// update channels

/// Updates the list of channels managed by the upstream role.
fn update_channels(&mut self, c: UpstreamChannel);
/// check if node is limited to hom

/// Checks if the upstream role supports header-only mining.
fn is_header_only(&self) -> bool {
has_requires_std_job(self.get_flags())
}
}

/// General properties that every Sv2 compatible downstream node must implement.
/// Properties defining behaviors common to all Sv2 downstream roles.
pub trait IsDownstream {
/// get downstream mining data
/// Returns the common properties of the downstream role (e.g. support for header-only mining,
/// work selection, and version rolling).
fn get_downstream_mining_data(&self) -> CommonDownstreamData;
}

/// General properties that every Sv2 compatible mining downstream node must implement.
/// Properties of a SV2-compatible mining downstream role.
///
/// This trait extends [`IsDownstream`] with additional functionality specific to mining, such as
/// header-only mining checks.
pub trait IsMiningDownstream: IsDownstream {
/// check if node is doing hom
/// Checks if the downstream role supports header-only mining.
fn is_header_only(&self) -> bool {
self.get_downstream_mining_data().header_only
}
}

// Implemented for the NullDownstreamMiningSelector
// Implemented for the `NullDownstreamMiningSelector`.
impl<Down: IsDownstream + D> IsUpstream<Down, NullDownstreamMiningSelector> for () {
fn get_version(&self) -> u16 {
unreachable!("Null upstream do not have a version");
Expand All @@ -134,7 +235,7 @@ impl<Down: IsDownstream + D> IsUpstream<Down, NullDownstreamMiningSelector> for
}
}

// Implemented for the NullDownstreamMiningSelector
// Implemented for the `NullDownstreamMiningSelector`.
impl IsDownstream for () {
fn get_downstream_mining_data(&self) -> CommonDownstreamData {
unreachable!("Null downstream do not have mining data");
Expand All @@ -160,35 +261,60 @@ impl<Down: IsMiningDownstream + D> IsMiningUpstream<Down, NullDownstreamMiningSe

impl IsMiningDownstream for () {}

/// Proxies likely need to change the request ids of the downstream's messages. They also need to
/// remember the original id to patch the upstream's response with it.
/// Maps request IDs between upstream and downstream nodes.
///
/// Most commonly used by proxies, this struct facilitates communication between upstream and
/// downstream nodes by mapping request IDs. This ensures responses are routed correctly back from
/// the upstream to the originating downstream requester, even when request IDs are modified in
/// transit.
///
/// ### Workflow
/// 1. **Request Mapping**: When a downstream node sends a request, `on_open_channel` assigns a new
/// unique upstream request ID and stores the mapping.
/// 2. **Response Mapping**: When the upstream responds, the proxy uses the map to translate the
/// upstream ID back to the original downstream ID.
/// 3. **Cleanup**: Once the responses are processed, the mapping is removed.
///
/// **Note**: Most upstream-originating requests are broadcast to all downstreams, eliminating the
/// need for request-response tracking. The primary use case for [`RequestIdMapper`] is tracking
/// downstream-to-upstream requests and responses, as these require precise mapping to ensure
/// proper routing.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct RequestIdMapper {
// Mapping of upstream id -> downstream ids
// A mapping between upstream-assigned request IDs and the original downstream IDs.
//
// In the hashmap, the key is the upstream request ID and the value is the corresponding
// downstream request ID. `BuildNoHashHasher` is an optimization to bypass the hashing step for
// integer keys.
request_ids_map: HashMap<u32, u32, BuildNoHashHasher<u32>>,

// A counter for assigning unique request IDs to upstream nodes, incrementing after every
// assignment.
next_id: u32,
}

impl RequestIdMapper {
/// Builds a new `RequestIdMapper` initialized with an empty hashmap and initializes `next_id`
/// to `0`.
/// Creates a new [`RequestIdMapper`] instance.
pub fn new() -> Self {
Self {
request_ids_map: HashMap::with_hasher(BuildNoHashHasher::default()),
next_id: 0,
}
}

/// Updates the `RequestIdMapper` with a new upstream/downstream mapping.
/// Assigns a new upstream request ID for a request sent by a downstream node.
///
/// Ensures every request forwarded to the upstream node has a unique ID while retaining
/// traceability to the original downstream request.
pub fn on_open_channel(&mut self, id: u32) -> u32 {
let new_id = self.next_id;
self.next_id += 1;
let new_id = self.next_id; // Assign new upstream ID

Check warning on line 310 in protocols/v2/roles-logic-sv2/src/common_properties.rs

View check run for this annotation

Codecov / codecov/patch

protocols/v2/roles-logic-sv2/src/common_properties.rs#L310

Added line #L310 was not covered by tests
self.next_id += 1; // Increment next_id for future requests

self.request_ids_map.insert(new_id, id);
self.request_ids_map.insert(new_id, id); // Map new upstream ID to downstream ID
new_id
}

/// Removes a upstream/downstream mapping from the `RequestIdMapper`.
/// Removes the mapping for a request ID once the response has been processed.
pub fn remove(&mut self, upstream_id: u32) -> Option<u32> {
self.request_ids_map.remove(&upstream_id)
}
Expand Down

0 comments on commit da2b372

Please sign in to comment.