From fe0bfdf115ebfddeef81255f5b642103894d3236 Mon Sep 17 00:00:00 2001 From: nkatha23 Date: Tue, 15 Sep 2026 15:06:25 +0300 Subject: [PATCH 1/5] feat(pool): bridge validated shares from SV2 pool to Braidpool node --- pool-apps/pool/src/lib/braidpool/mod.rs | 115 ++++++++++++++++++ .../channel_manager/mining_message_handler.rs | 30 +++++ pool-apps/pool/src/lib/channel_manager/mod.rs | 6 + pool-apps/pool/src/lib/mod.rs | 2 + pool-apps/pool/src/lib/pool_runtime.rs | 15 +++ 5 files changed, 168 insertions(+) create mode 100644 pool-apps/pool/src/lib/braidpool/mod.rs diff --git a/pool-apps/pool/src/lib/braidpool/mod.rs b/pool-apps/pool/src/lib/braidpool/mod.rs new file mode 100644 index 000000000..a579c5e09 --- /dev/null +++ b/pool-apps/pool/src/lib/braidpool/mod.rs @@ -0,0 +1,115 @@ +//! Braidpool-specific types bridging the SV2 pool to the braidpool node. +//! +//! This module is the boundary layer between the generic SV2 pool and +//! Braidpool's bead-based consensus. It defines the types that flow from +//! the pool (where shares are validated) to the node (where beads are built). +//! +//! # Data flow +//! ```text +//! SubmitSharesExtended +//! → validate_share (ExtendedChannel) +//! → ValidatedShare ──[ShareBridgeSender]──► node::propagate_valid_bead (PR 4) +//! ``` + +use tokio::sync::mpsc; + +/// A share that passed pool-level PoW validation, ready for bead construction. +/// +/// `extranonce1` is `Vec` rather than `u64` because in audit mode the +/// pool-assigned prefix is `N + 7` bytes (upstream extranonce1 + 2-byte miner +/// prefix + 5-byte commitment), which can exceed 8 bytes for common 8-byte +/// upstream configurations. +#[derive(Debug, Clone)] +pub struct ValidatedShare { + /// Template the share was mined against. `None` for custom-job shares. + pub template_id: Option, + /// Pool-assigned extranonce prefix (extranonce1 in Stratum v1 terms). + pub extranonce1: Vec, + /// Miner-rolled extranonce suffix (extranonce2). + pub extranonce2: Vec, + /// Block version field (may include BIP-320 rolled bits). + pub version: u32, + /// Block header nTime. + pub ntime: u32, + /// Block header nonce. + pub nonce: u32, +} + +/// Template context attached to a validated share for bead metadata construction. +/// +/// Carries the information needed to build `CommittedMetadata` in the node. +/// Populated when a `ValidatedShare` is matched to a template. +#[derive(Debug)] +pub struct BeadContext { + /// Template that was being mined when the share arrived. + pub template_id: u64, + /// Wall-clock time the template was received from the template provider. + /// Used to measure share propagation latency for bead timing. + pub received_at: std::time::Instant, + /// Committed transactions from the template (for CommittedMetadata). + pub transactions: Vec>, +} + +/// Extranonce allocation strategy for the pool↔tproxy Extended Channel. +/// +/// Determines the split between pool-assigned prefix (extranonce1) and +/// miner-rolled space (extranonce2) based on the upstream pool configuration. +/// +/// # Normal mode (4+8) +/// ```text +/// upstream negotiates: ext1=4, ext2=8 +/// pool sends to tproxy: prefix=4 bytes (random), extranonce_size=12 +/// ``` +/// +/// # Audit mode +/// Braidpool embeds commitment bytes inside the extranonce space it sends to +/// downstream miners, then reconstructs the upstream-agreed format before +/// submitting shares back upstream. +/// +/// ```text +/// upstream negotiates: ext1=N, ext2=8 (common: N=4 or N=8) +/// pool prefix sent to tproxy: N + 7 bytes (N upstream + 2 miner prefix + 5 commitment) +/// miner rolling space: total - (N+7) bytes +/// on share submission upstream: reconstruct ext1=N, ext2=8 +/// ``` +#[derive(Debug, Clone)] +pub enum ExtraNonceConfig { + /// Standard mode: 4-byte pool prefix, up to 12-byte miner rolling space. + Normal, + /// Audit mode: prefix embeds upstream extranonce1 + commitment bytes. + Audit { + /// Size (bytes) of extranonce1 as negotiated with the upstream pool. + upstream_ext1_size: usize, + }, +} + +impl ExtraNonceConfig { + /// Pool-assigned prefix size (extranonce1) for this configuration. + pub fn extranonce1_size(&self) -> usize { + match self { + Self::Normal => 4, + // N bytes upstream + 2 miner prefix + 5 commitment + Self::Audit { upstream_ext1_size } => upstream_ext1_size + 7, + } + } + + /// Miner-controlled rolling space size (extranonce2) for this configuration, + /// given `total_extranonce_bytes` agreed with the upstream pool. + pub fn extranonce2_size(&self, total_extranonce_bytes: usize) -> usize { + total_extranonce_bytes.saturating_sub(self.extranonce1_size()) + } +} + +/// Sender half of the share bridge — held by the pool's `ChannelManager`. +pub type ShareBridgeSender = mpsc::UnboundedSender; + +/// Receiver half of the share bridge — consumed by the node IPC task (PR 4). +pub type ShareBridgeReceiver = mpsc::UnboundedReceiver; + +/// Create the share bridge channel connecting pool validation to node bead construction. +/// +/// The sender is stored on `ChannelManager`; the receiver is kept alive in +/// `PoolRuntime` until PR 4 wires it to `propagate_valid_bead` over IPC. +pub fn create_share_bridge() -> (ShareBridgeSender, ShareBridgeReceiver) { + mpsc::unbounded_channel() +} diff --git a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs index 40eccda2d..5cadda9c0 100644 --- a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs +++ b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs @@ -36,6 +36,7 @@ use tracing::{error, info, warn}; use jd_server_sv2::job_declarator::SetCustomMiningJobResponse; use crate::{ + braidpool::ValidatedShare, channel_manager::{CLIENT_SEARCH_SPACE_BYTES, ChannelManager, RouteMessageTo}, error::{self, PoolError, PoolErrorKind}, utils::{PayoutMode, PayoutModeError, create_close_channel_msg}, @@ -943,6 +944,9 @@ impl HandleMiningMessagesFromClientOwnedAsync for ChannelManager { let channel_id = msg.channel_id; let vardiff_key = (downstream_id, channel_id).into(); + // Clone before the immutable borrow below so the sender can be moved + // into the closure without holding a reference to self. + let share_bridge = self.share_bridge_sender.clone(); let messages = self.with_registered_downstream(downstream_id, |downstream| { let messages = if !downstream.extended_channels.contains_key(&channel_id) { let error = SubmitSharesErrorOwned { @@ -1001,6 +1005,19 @@ impl HandleMiningMessagesFromClientOwnedAsync for ChannelManager { downstream_id, channel_id, msg.sequence_number, share_hash, share_work ); } + if let Some(sender) = &share_bridge { + let validated = ValidatedShare { + template_id: None, + extranonce1: extended_channel.get_extranonce_prefix().to_vec(), + extranonce2: msg.extranonce.as_bytes().to_vec(), + version: msg.version, + ntime: msg.ntime, + nonce: msg.nonce, + }; + if sender.send(validated).is_err() { + warn!("share bridge closed — validated share dropped"); + } + } } Ok(ShareValidationResult::BlockFound( share_hash, @@ -1008,6 +1025,19 @@ impl HandleMiningMessagesFromClientOwnedAsync for ChannelManager { coinbase, )) => { info!("SubmitSharesExtended: 💰 Block Found!!! 💰{share_hash}"); + if let Some(sender) = &share_bridge { + let validated = ValidatedShare { + template_id, + extranonce1: extended_channel.get_extranonce_prefix().to_vec(), + extranonce2: msg.extranonce.as_bytes().to_vec(), + version: msg.version, + ntime: msg.ntime, + nonce: msg.nonce, + }; + if sender.send(validated).is_err() { + warn!("share bridge closed — block-found share dropped"); + } + } if let Some(template_id) = template_id { info!("SubmitSharesExtended: Propagating solution to the Template Provider."); let solution = SubmitSolutionOwned { diff --git a/pool-apps/pool/src/lib/channel_manager/mod.rs b/pool-apps/pool/src/lib/channel_manager/mod.rs index 5e9ad4cff..bb162bbfb 100644 --- a/pool-apps/pool/src/lib/channel_manager/mod.rs +++ b/pool-apps/pool/src/lib/channel_manager/mod.rs @@ -118,6 +118,10 @@ pub struct ChannelManager { required_extensions: Vec, /// Embedded Job Declaration engine (present when `[jds]` config is set). job_declarator: Option, + /// Sender half of the Braidpool share bridge. Present when the pool runs in + /// Braidpool mode; `None` in standalone SRI mode. Every validated share is + /// forwarded here for bead construction in the node (wired by PR 4). + share_bridge_sender: Option, } #[cfg_attr(not(test), hotpath::measure_all)] @@ -166,6 +170,7 @@ impl ChannelManager { downstream_receiver: Receiver<(DownstreamId, MiningOwned, Option>)>, coinbase_outputs: Vec, job_declarator: Option, + share_bridge_sender: Option, ) -> PoolResult { // Simulating a scenario where there are multiple mining servers, // `server_id` is used as `local_prefix_bytes` so each pool instance @@ -199,6 +204,7 @@ impl ChannelManager { supported_extensions: config.supported_extensions().to_vec(), required_extensions: config.required_extensions().to_vec(), job_declarator, + share_bridge_sender, }; Ok(channel_manager) diff --git a/pool-apps/pool/src/lib/mod.rs b/pool-apps/pool/src/lib/mod.rs index 0eb06bbd5..60b94053a 100644 --- a/pool-apps/pool/src/lib/mod.rs +++ b/pool-apps/pool/src/lib/mod.rs @@ -1,5 +1,7 @@ use error::PoolErrorKind; use pool_runtime::{Init, PoolRuntime}; + +pub mod braidpool; use std::sync::{ Arc, atomic::{AtomicBool, Ordering}, diff --git a/pool-apps/pool/src/lib/pool_runtime.rs b/pool-apps/pool/src/lib/pool_runtime.rs index 84a4f9b66..eeae487d0 100644 --- a/pool-apps/pool/src/lib/pool_runtime.rs +++ b/pool-apps/pool/src/lib/pool_runtime.rs @@ -27,6 +27,7 @@ use jd_server_sv2::job_declarator::{ use super::PoolSv2; use crate::{ + braidpool::{ShareBridgeReceiver, create_share_bridge}, channel_manager::ChannelManager, error::PoolErrorKind, template_receiver::{ @@ -105,6 +106,10 @@ pub(super) struct PoolRuntime { bitcoin_core_sv2: Option, encoded_outputs: Vec, coinbase_outputs: Vec, + /// Receiver half of the Braidpool share bridge. Created in + /// `bootstrap_channel_manager` and kept alive here until PR 4 wires it + /// to the node IPC task via `propagate_valid_bead`. + share_bridge_receiver: Option, } impl PoolRuntime { @@ -117,6 +122,7 @@ impl PoolRuntime { bitcoin_core_sv2: self.bitcoin_core_sv2, encoded_outputs: self.encoded_outputs, coinbase_outputs: self.coinbase_outputs, + share_bridge_receiver: self.share_bridge_receiver, } } @@ -191,6 +197,7 @@ impl PoolRuntime { bitcoin_core_sv2: None, coinbase_outputs, encoded_outputs, + share_bridge_receiver: None, }) } @@ -219,6 +226,7 @@ impl PoolRuntime { coinbase_outputs: self.coinbase_outputs, encoded_outputs: self.encoded_outputs, state: IoReady { io }, + share_bridge_receiver: self.share_bridge_receiver, } } @@ -357,6 +365,7 @@ impl PoolRuntime { coinbase_outputs: self.coinbase_outputs, encoded_outputs: self.encoded_outputs, state: new_state, + share_bridge_receiver: self.share_bridge_receiver, }) } } @@ -467,6 +476,7 @@ impl PoolRuntime { coinbase_outputs: self.coinbase_outputs, encoded_outputs: self.encoded_outputs, state: new_state, + share_bridge_receiver: self.share_bridge_receiver, }) } } @@ -476,6 +486,8 @@ impl PoolRuntime { self, ) -> Result, (PoolErrorKind, PoolRuntime)> { + let (share_bridge_sender, share_bridge_receiver) = create_share_bridge(); + let channel_manager = match ChannelManager::new( self.pool.config.clone(), self.state.io.channel_manager_to_tp_sender.clone(), @@ -483,6 +495,7 @@ impl PoolRuntime { self.state.io.downstream_to_channel_manager_receiver.clone(), self.encoded_outputs.clone(), self.jd.clone(), + Some(share_bridge_sender), ) .await { @@ -505,6 +518,7 @@ impl PoolRuntime { coinbase_outputs: self.coinbase_outputs, encoded_outputs: self.encoded_outputs, state: new_state, + share_bridge_receiver: Some(share_bridge_receiver), }) } } @@ -612,6 +626,7 @@ impl PoolRuntime { coinbase_outputs: self.coinbase_outputs, encoded_outputs: self.encoded_outputs, state: Running, + share_bridge_receiver: self.share_bridge_receiver, }) } } From 0ba82cd9d14d8900b118899704f36cc17be447ed Mon Sep 17 00:00:00 2001 From: nkatha23 Date: Tue, 22 Sep 2026 12:08:27 +0300 Subject: [PATCH 2/5] fix(pool): populate template_id from active job in Valid share arm --- .../src/lib/channel_manager/mining_message_handler.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs index 5cadda9c0..b9bba14f5 100644 --- a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs +++ b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs @@ -19,6 +19,7 @@ use stratum_apps::stratum_core::{ server::{ error::{ExtendedChannelError, StandardChannelError}, extended::ExtendedChannel, + jobs::JobOrigin, share_accounting::{ShareValidationError, ShareValidationResult}, standard::StandardChannel, }, @@ -1006,8 +1007,14 @@ impl HandleMiningMessagesFromClientOwnedAsync for ChannelManager { ); } if let Some(sender) = &share_bridge { + let template_id = extended_channel + .get_active_job() + .and_then(|job| match job.get_origin() { + JobOrigin::NewTemplate(t) => Some(t.template_id), + _ => None, + }); let validated = ValidatedShare { - template_id: None, + template_id, extranonce1: extended_channel.get_extranonce_prefix().to_vec(), extranonce2: msg.extranonce.as_bytes().to_vec(), version: msg.version, From e302ff22a94912dca8c8c07e6c10e5d708cd1029 Mon Sep 17 00:00:00 2001 From: nkatha23 Date: Tue, 22 Sep 2026 12:14:28 +0300 Subject: [PATCH 3/5] feat(pool): add channel_id to ValidatedShare --- pool-apps/pool/src/lib/braidpool/mod.rs | 2 ++ .../pool/src/lib/channel_manager/mining_message_handler.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pool-apps/pool/src/lib/braidpool/mod.rs b/pool-apps/pool/src/lib/braidpool/mod.rs index a579c5e09..280546f13 100644 --- a/pool-apps/pool/src/lib/braidpool/mod.rs +++ b/pool-apps/pool/src/lib/braidpool/mod.rs @@ -23,6 +23,8 @@ use tokio::sync::mpsc; pub struct ValidatedShare { /// Template the share was mined against. `None` for custom-job shares. pub template_id: Option, + /// SV2 extended channel ID the share arrived on. + pub channel_id: u32, /// Pool-assigned extranonce prefix (extranonce1 in Stratum v1 terms). pub extranonce1: Vec, /// Miner-rolled extranonce suffix (extranonce2). diff --git a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs index b9bba14f5..d875c6614 100644 --- a/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs +++ b/pool-apps/pool/src/lib/channel_manager/mining_message_handler.rs @@ -1015,6 +1015,7 @@ impl HandleMiningMessagesFromClientOwnedAsync for ChannelManager { }); let validated = ValidatedShare { template_id, + channel_id, extranonce1: extended_channel.get_extranonce_prefix().to_vec(), extranonce2: msg.extranonce.as_bytes().to_vec(), version: msg.version, @@ -1035,6 +1036,7 @@ impl HandleMiningMessagesFromClientOwnedAsync for ChannelManager { if let Some(sender) = &share_bridge { let validated = ValidatedShare { template_id, + channel_id, extranonce1: extended_channel.get_extranonce_prefix().to_vec(), extranonce2: msg.extranonce.as_bytes().to_vec(), version: msg.version, From 3ccbaa587a8c51a0ae20ae7fc282295e747fc66d Mon Sep 17 00:00:00 2001 From: nkatha23 Date: Tue, 13 Oct 2026 22:05:29 +0300 Subject: [PATCH 4/5] feat(pool): wire BraidpoolIpc template provider and expose ShareBridgeReceiver - Add BraidpoolIpc variant to TemplateProviderType (no config fields, channel wired at runtime via PoolSv2::with_braidpool_template_rx) - Add PoolSv2::with_braidpool_template_rx() builder and take_braidpool_template_rx() - Change PoolSv2::start() to accept Option> so callers receive validated shares without blocking on pool shutdown - Handle BraidpoolIpc in bootstrap_template_provider via sv2_template_consumer - BraidpoolIpc errors on JDS (requires BitcoinCoreIpc, same as Sv2Tp) - Add braidpool-common and braidpool-template-provider deps to pool_sv2 --- pool-apps/pool/Cargo.toml | 2 + pool-apps/pool/src/lib/mod.rs | 54 +++++++++++++++++++-- pool-apps/pool/src/lib/pool_runtime.rs | 66 ++++++++++++++++++++++++-- pool-apps/pool/src/main.rs | 2 +- stratum-apps/src/tp_type.rs | 6 +++ 5 files changed, 121 insertions(+), 9 deletions(-) diff --git a/pool-apps/pool/Cargo.toml b/pool-apps/pool/Cargo.toml index b0cf7ab18..9258fadbe 100644 --- a/pool-apps/pool/Cargo.toml +++ b/pool-apps/pool/Cargo.toml @@ -19,6 +19,8 @@ path = "src/lib/mod.rs" [dependencies] stratum-apps = { version = "0.8.0", path = "../../stratum-apps", features = ["pool"] } +braidpool-common = { path = "../braidpool-common" } +braidpool-template-provider = { path = "../braidpool-template-provider" } async-channel = "1.5.1" serde = { version = "1.0.89", features = ["derive", "alloc"], default-features = false } tokio = { version = "1.44.1", features = ["full"] } diff --git a/pool-apps/pool/src/lib/mod.rs b/pool-apps/pool/src/lib/mod.rs index 60b94053a..4c7d16d76 100644 --- a/pool-apps/pool/src/lib/mod.rs +++ b/pool-apps/pool/src/lib/mod.rs @@ -10,7 +10,9 @@ use stratum_apps::bitcoin_core_sv2::CancellationToken; use tokio::sync::Notify; use tracing::info; -use crate::config::PoolConfig; +use braidpool_common::template::BraidpoolTemplate; + +use crate::{braidpool::ShareBridgeReceiver, config::PoolConfig}; pub mod channel_manager; pub mod config; @@ -29,6 +31,10 @@ pub struct PoolSv2 { cancellation_token: CancellationToken, shutdown_notify: Arc, is_alive: Arc, + /// In-process template channel for `BraidpoolIpc` mode. + /// Wrapped in `Arc>>` so `PoolSv2` remains `Clone`. + braidpool_template_rx: + Arc>>>, } #[cfg_attr(not(test), hotpath::measure_all)] @@ -39,9 +45,35 @@ impl PoolSv2 { cancellation_token: CancellationToken::new(), shutdown_notify: Arc::new(Notify::new()), is_alive: Arc::new(AtomicBool::new(true)), + braidpool_template_rx: Arc::new(std::sync::Mutex::new(None)), } } + /// Wires the in-process template channel for `BraidpoolIpc` mode. + /// + /// Must be called before [`start`] when `template_provider_type = BraidpoolIpc`. + /// The receiver is consumed once during bootstrap; calling `start` a second + /// time after the channel has been taken will fail with a configuration error. + pub fn with_braidpool_template_rx( + self, + rx: tokio::sync::mpsc::Receiver, + ) -> Self { + self.braidpool_template_rx + .lock() + .expect("braidpool_template_rx mutex poisoned") + .replace(rx); + self + } + + pub(crate) fn take_braidpool_template_rx( + &self, + ) -> Option> { + self.braidpool_template_rx + .lock() + .expect("braidpool_template_rx mutex poisoned") + .take() + } + /// Starts the Pool server and blocks asynchronously on the `PoolRuntime`. /// /// The startup and execution sequence follows: @@ -55,7 +87,15 @@ impl PoolSv2 { /// /// If any error occurs during bootstrapping, `start` receives the partially initialized /// runtime, gracefully shuts it down, and then returns the error. - pub async fn start(&self) -> Result<(), PoolErrorKind> { + /// + /// When `share_rx_notify` is `Some`, the [`ShareBridgeReceiver`] is sent over it once + /// the channel manager is bootstrapped and the pool is ready to process shares. The + /// caller can then forward validated shares to `propagate_valid_bead` without waiting + /// for the pool to shut down. + pub async fn start( + &self, + share_rx_notify: Option>, + ) -> Result<(), PoolErrorKind> { let runtime = match PoolRuntime::::new(self.clone()) { Ok(runtime) => runtime, Err(err) => { @@ -64,7 +104,7 @@ impl PoolSv2 { } }; - let runtime = match runtime.bootstrap().await { + let mut runtime = match runtime.bootstrap().await { Ok(runtime) => runtime, Err(err) => { let (err, runtime) = err.into_parts(); @@ -73,6 +113,14 @@ impl PoolSv2 { } }; + // Deliver the share bridge receiver to the caller before blocking on shutdown, + // so they can start consuming shares without waiting for the pool to exit. + if let Some(tx) = share_rx_notify { + if let Some(rx) = runtime.take_share_bridge_receiver() { + let _ = tx.send(rx); + } + } + runtime.wait_for_shutdown().await; runtime.shutdown().await; diff --git a/pool-apps/pool/src/lib/pool_runtime.rs b/pool-apps/pool/src/lib/pool_runtime.rs index eeae487d0..7028864e2 100644 --- a/pool-apps/pool/src/lib/pool_runtime.rs +++ b/pool-apps/pool/src/lib/pool_runtime.rs @@ -26,6 +26,8 @@ use jd_server_sv2::job_declarator::{ }; use super::PoolSv2; +use braidpool_common::template::BraidpoolTemplate; + use crate::{ braidpool::{ShareBridgeReceiver, create_share_bridge}, channel_manager::ChannelManager, @@ -107,9 +109,12 @@ pub(super) struct PoolRuntime { encoded_outputs: Vec, coinbase_outputs: Vec, /// Receiver half of the Braidpool share bridge. Created in - /// `bootstrap_channel_manager` and kept alive here until PR 4 wires it - /// to the node IPC task via `propagate_valid_bead`. + /// `bootstrap_channel_manager` and delivered to the node via + /// `PoolSv2::start`'s `share_rx_notify` oneshot. share_bridge_receiver: Option, + /// In-process template channel for `BraidpoolIpc` mode. Taken from + /// `PoolSv2` during `new()` and consumed in `bootstrap_template_provider`. + braidpool_template_rx: Option>, } impl PoolRuntime { @@ -123,6 +128,7 @@ impl PoolRuntime { encoded_outputs: self.encoded_outputs, coinbase_outputs: self.coinbase_outputs, share_bridge_receiver: self.share_bridge_receiver, + braidpool_template_rx: self.braidpool_template_rx, } } @@ -189,6 +195,8 @@ impl PoolRuntime { .consensus_encode(&mut encoded_outputs) .map_err(|err| PoolErrorKind::Io(err.into()))?; + let braidpool_template_rx = pool.take_braidpool_template_rx(); + Ok(PoolRuntime { pool, task_manager: Arc::new(TaskManager::new()), @@ -198,6 +206,7 @@ impl PoolRuntime { coinbase_outputs, encoded_outputs, share_bridge_receiver: None, + braidpool_template_rx, }) } @@ -227,6 +236,7 @@ impl PoolRuntime { encoded_outputs: self.encoded_outputs, state: IoReady { io }, share_bridge_receiver: self.share_bridge_receiver, + braidpool_template_rx: self.braidpool_template_rx, } } @@ -284,11 +294,12 @@ impl PoolRuntime { Err(err) => return Err((PoolErrorKind::Jds(err), self)), } } - TemplateProviderType::Sv2Tp { .. } => { + TemplateProviderType::Sv2Tp { .. } + | TemplateProviderType::BraidpoolIpc => { return Err(( PoolErrorKind::Configuration( "[jds] requires template_provider_type = BitcoinCoreIpc \ - (JDS needs direct IPC access to Bitcoin Core)" + (JDS needs direct IPC access to Bitcoin Core)" .to_string(), ), self, @@ -366,18 +377,54 @@ impl PoolRuntime { encoded_outputs: self.encoded_outputs, state: new_state, share_bridge_receiver: self.share_bridge_receiver, + braidpool_template_rx: self.braidpool_template_rx, }) } } impl PoolRuntime { async fn bootstrap_template_provider( - self, + mut self, ) -> Result, (PoolErrorKind, PoolRuntime)> { let cancellation_token = self.pool.cancellation_token.clone(); let mut bitcoin_core_sv2: Option = None; match self.pool.config.template_provider_type().clone() { + TemplateProviderType::BraidpoolIpc => { + let template_rx = match self.braidpool_template_rx.take() { + Some(rx) => rx, + None => { + return Err(( + PoolErrorKind::Configuration( + "BraidpoolIpc requires a template receiver; \ + call with_braidpool_template_rx() before start()" + .to_string(), + ), + self, + )); + } + }; + + let sv2_outgoing_tx = self.state.io.tp_to_channel_manager_sender.clone(); + let sv2_incoming_rx = self.state.io.channel_manager_to_tp_receiver.clone(); + let ct = cancellation_token.clone(); + let tm = self.task_manager.clone(); + + tm.spawn(async move { + if let Err(e) = braidpool_template_provider::sv2_template_consumer( + template_rx, + sv2_outgoing_tx, + sv2_incoming_rx, + ct, + ) + .await + { + error!(error = %e, "braidpool sv2_template_consumer exited with error"); + } + }); + + info!("Braidpool IPC template provider started"); + } TemplateProviderType::Sv2Tp { address, public_key, @@ -477,6 +524,7 @@ impl PoolRuntime { encoded_outputs: self.encoded_outputs, state: new_state, share_bridge_receiver: self.share_bridge_receiver, + braidpool_template_rx: None, // consumed above; not needed after this stage }) } } @@ -519,6 +567,7 @@ impl PoolRuntime { encoded_outputs: self.encoded_outputs, state: new_state, share_bridge_receiver: Some(share_bridge_receiver), + braidpool_template_rx: None, }) } } @@ -627,6 +676,7 @@ impl PoolRuntime { encoded_outputs: self.encoded_outputs, state: Running, share_bridge_receiver: self.share_bridge_receiver, + braidpool_template_rx: None, }) } } @@ -635,4 +685,10 @@ impl PoolRuntime { pub(super) async fn wait_for_shutdown(&self) { self.pool.cancellation_token.cancelled().await; } + + /// Takes the share bridge receiver out of the runtime so the caller can + /// forward validated shares to `propagate_valid_bead`. + pub(super) fn take_share_bridge_receiver(&mut self) -> Option { + self.share_bridge_receiver.take() + } } diff --git a/pool-apps/pool/src/main.rs b/pool-apps/pool/src/main.rs index 6813f04d2..41c714347 100644 --- a/pool-apps/pool/src/main.rs +++ b/pool-apps/pool/src/main.rs @@ -36,7 +36,7 @@ async fn inner_main() { } }); - if let Err(e) = pool.start().await { + if let Err(e) = pool.start(None).await { tracing::error!("Pool Error'ed out: {e}"); std::process::exit(1); }; diff --git a/stratum-apps/src/tp_type.rs b/stratum-apps/src/tp_type.rs index b0330fcfe..77b82994b 100644 --- a/stratum-apps/src/tp_type.rs +++ b/stratum-apps/src/tp_type.rs @@ -96,6 +96,12 @@ pub enum TemplateProviderType { fee_threshold: u64, min_interval: u8, }, + /// Braidpool node injects templates in-process via a tokio channel. + /// + /// No config fields — the channel receiver is wired at runtime via + /// [`PoolSv2::with_braidpool_template_rx`]. Configure in TOML as: + /// `template_provider_type = "BraidpoolIpc"`. + BraidpoolIpc, } #[cfg(test)] From 32dd883517bcc0da35e61fecb16620e95aed4221 Mon Sep 17 00:00:00 2001 From: nkatha23 Date: Wed, 14 Oct 2026 12:27:21 +0300 Subject: [PATCH 5/5] feat(pool): re-export key types and add PoolConfig::for_braidpool factory Re-exports Secp256k1PublicKey, Secp256k1SecretKey, CoinbaseRewardScript, and TemplateProviderType from pool_sv2::config so callers don't need a direct stratum-apps dependency. Adds PoolConfig::for_braidpool() factory that wires BraidpoolIpc with sensible defaults (1h cert, 20 shares/min, batch 100, no JDS). --- pool-apps/pool/src/lib/config.rs | 37 +++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/pool-apps/pool/src/lib/config.rs b/pool-apps/pool/src/lib/config.rs index 4322075c9..00afbd493 100644 --- a/pool-apps/pool/src/lib/config.rs +++ b/pool-apps/pool/src/lib/config.rs @@ -14,11 +14,14 @@ use std::{ }; pub use jd_server_sv2::config::{JDSConfig, JDSPartialConfig}; -use stratum_apps::{ - config_helpers::{CoinbaseRewardScript, opt_path_from_toml}, +pub use stratum_apps::{ + config_helpers::CoinbaseRewardScript, key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}, - stratum_core::bitcoin::{Amount, TxOut}, tp_type::TemplateProviderType, +}; +use stratum_apps::{ + config_helpers::opt_path_from_toml, + stratum_core::bitcoin::{Amount, TxOut}, utils::types::{SharesBatchSize, SharesPerMinute}, }; @@ -93,6 +96,34 @@ impl PoolConfig { } } + /// Builds a [`PoolConfig`] for Braidpool's in-process pool mode. + /// + /// Uses `BraidpoolIpc` as the template provider (no external TDP address needed — + /// the template channel is wired at runtime via [`PoolSv2::with_braidpool_template_rx`]). + /// Sensible defaults are applied: 1-hour cert validity, 20 shares/min target, + /// batch size 100, no JDS, no monitoring. + pub fn for_braidpool( + listen_address: SocketAddr, + authority_public_key: Secp256k1PublicKey, + authority_secret_key: Secp256k1SecretKey, + coinbase_reward_script: CoinbaseRewardScript, + ) -> Self { + Self::new( + ConnectionConfig::new(listen_address, 3600, "braidpool".to_string()), + TemplateProviderType::BraidpoolIpc, + AuthorityConfig::new(authority_public_key, authority_secret_key), + coinbase_reward_script, + 20.0, + 100, + 0, + vec![], + vec![], + None, + None, + None, + ) + } + /// Returns the coinbase output. pub fn coinbase_reward_script(&self) -> &CoinbaseRewardScript { &self.coinbase_reward_script