Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion integration-tests/lib/template_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
path::PathBuf,
process::{Child, Command, Stdio},
};
use stratum_apps::stratum_core::bitcoin::{Address, Amount, Txid};
use stratum_apps::stratum_core::bitcoin::{Address, Amount, Block, BlockHash, Txid};
use tracing::warn;

use crate::utils::{fs_utils, http, tarball};
Expand Down Expand Up @@ -300,6 +300,12 @@ impl BitcoinCore {
Ok(block_hash)
}

/// Fetch the full block (header + txdata) for the given hash.
pub fn get_block(&self, hash: BlockHash) -> Result<Block, corepc_node::Error> {
let block = self.bitcoind.client.get_block(hash)?;
Ok(block)
}

/// Return the IPC socket path for connecting to this node.
pub fn ipc_socket_path(&self) -> PathBuf {
let network_dir = if self.is_signet { "signet" } else { "regtest" };
Expand Down Expand Up @@ -441,6 +447,11 @@ impl TemplateProvider {
self.bitcoin_core.get_best_block_hash()
}

/// Fetch the full block (header + txdata) for the given hash.
pub fn get_block(&self, hash: BlockHash) -> Result<Block, corepc_node::Error> {
self.bitcoin_core.get_block(hash)
}

/// Return the sv2 port that sv2-tp is listening on.
pub fn sv2_port(&self) -> u16 {
self.sv2_port
Expand Down
91 changes: 90 additions & 1 deletion integration-tests/tests/pool_solo_mining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use integration_tests_sv2::{
POOL_COINBASE_REWARD_ADDRESS, *,
};
use stratum_apps::stratum_core::{
bitcoin::{consensus::deserialize, params::TESTNET4, Address, Transaction},
bitcoin::{consensus::deserialize, params::TESTNET4, Address, BlockHash, Transaction},
common_messages_sv2::*,
mining_sv2::*,
parsers_sv2::{self, AnyMessage, Mining},
Expand Down Expand Up @@ -1294,3 +1294,92 @@ async fn pool_solo_mining_partial_donation() {

shutdown_all!(pool);
}

#[tokio::test]
async fn pool_solo_mining_partial_donation_end_to_end() {
start_tracing();
let (tp, tp_addr) = start_template_provider(Some(5), DifficultyLevel::Low);
tp.fund_wallet().unwrap();
let (pool, pool_addr, _) = start_pool(sv2_tp_config(tp_addr), vec![], vec![], false).await;

let (_address, mempool_txid) = tp.create_mempool_transaction().unwrap();
let start_hash: BlockHash = tp
.get_best_block_hash()
.unwrap()
.parse()
.expect("invalid block hash");

let donate_identity = format!("sri/donate/5/{}/worker.1", MINER_COINBASE_REWARD_ADDR);
let (translator, tproxy_addr, _) = start_sv2_translator_with_user_identity(
&[pool_addr],
false,
vec![],
vec![],
None,
donate_identity,
false,
false,
)
.await;
let (_minerd_process, _minerd_addr) = start_minerd(tproxy_addr, None, None, false).await;

// Mine until a block includes the mempool transaction, proving the round-trip
// worked against a template carrying real txdata (non-empty merkle path). Each
// poll walks the chain back to the starting tip so no mined block is missed.
let timeout = tokio::time::Duration::from_secs(60);
let poll_interval = tokio::time::Duration::from_secs(2);
let start_time = tokio::time::Instant::now();
let block = 'outer: loop {
if start_time.elapsed() > timeout {
panic!("no block including the mempool transaction was mined within 60 seconds");
}
tokio::time::sleep(poll_interval).await;
let mut cursor: BlockHash = tp
.get_best_block_hash()
.unwrap()
.parse()
.expect("invalid block hash");
while cursor != start_hash {
let block = tp.get_block(cursor).expect("failed to fetch mined block");
if block
.txdata
.iter()
.any(|tx| tx.compute_txid() == mempool_txid)
{
break 'outer block;
}
cursor = block.header.prev_blockhash;
}
};

let coinbase = &block.txdata[0];
assert_eq!(
coinbase.output.len(),
3,
"coinbase should have 3 outputs (pool 5%, miner 95%, witness commitment)"
);

let payout = extract_payout_info(coinbase);
assert_eq!(
payout.addresses.len(),
2,
"coinbase should pay exactly two addresses"
);
assert_eq!(
payout.addresses[0], POOL_COINBASE_REWARD_ADDRESS,
"pool payout should be the first coinbase output"
);
assert_eq!(
payout.addresses[1], MINER_COINBASE_REWARD_ADDR,
"miner payout should be the second coinbase output"
);
assert_payout_percentage(
&payout,
&[
(POOL_COINBASE_REWARD_ADDRESS.to_string(), 5.0),
(MINER_COINBASE_REWARD_ADDR.to_string(), 95.0),
],
);

shutdown_all!(pool, translator);
}
Loading