Skip to content
This repository has been archived by the owner on May 7, 2024. It is now read-only.

Commit

Permalink
Some fix and optimization for rust-engine (#50)
Browse files Browse the repository at this point in the history
* chore: bug fix and optimization

* refine and optimize rust engine
  • Loading branch information
miralandlabs authored Jan 21, 2024
1 parent 8811cfe commit efc61e3
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 22 deletions.
12 changes: 11 additions & 1 deletion src/electrumx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,21 @@ pub trait Api: Config + Http {
loop {
for u in self.get_unspent_address(address.as_ref()).await? {
if u.atomicals.is_empty() && u.value >= satoshis {
tracing::info!(
"Detected Funding UTXO {txid}:{vout}) with value {value} for funding...",
txid = u.txid,
vout = u.vout,
value = u.value
);
return Ok(u);
}
}

tracing::info!("waiting for UTXO...");
tracing::info!(
"WAITING for UTXO... UNTIL {btc} BTC RECEIVED AT {addr}",
btc = satoshis as f64 / 100000000.,
addr = address.as_ref()
);

time::sleep(Duration::from_secs(5)).await;
}
Expand Down
6 changes: 3 additions & 3 deletions src/electrumx/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use super::*;

#[tokio::test]
async fn get_by_ticker_should_work() {
tracing_subscriber::fmt::init();
let _ = tracing_subscriber::fmt::try_init();

ElectrumXBuilder::testnet().build().unwrap().get_by_ticker("quark").await.unwrap();
}

#[tokio::test]
async fn get_ft_info_should_work() {
tracing_subscriber::fmt::init();
let _ = tracing_subscriber::fmt::try_init();

let e = ElectrumXBuilder::testnet().build().unwrap();

Expand All @@ -19,7 +19,7 @@ async fn get_ft_info_should_work() {

#[tokio::test]
async fn get_unspent_address_should_work() {
tracing_subscriber::fmt::init();
let _ = tracing_subscriber::fmt::try_init();

ElectrumXBuilder::testnet()
.build()
Expand Down
4 changes: 4 additions & 0 deletions src/electrumx/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@ pub struct Fields {
#[derive(Debug, Deserialize)]
pub struct Args {
pub bitworkc: String,
pub bitworkr: Option<String>,
pub max_mints: u64,
pub mint_amount: u64,
pub mint_bitworkc: String,
pub mint_bitworkr: Option<String>,
pub mint_height: u64,
// TODO: It's a `String` in mainnet but a `u64` in testnet.
// pub nonce: u64,
Expand All @@ -159,6 +161,8 @@ pub struct MintInfo {
pub bitwork: Bitwork,
#[serde(rename = "$mint_bitworkc")]
pub mint_bitworkc: String,
#[serde(rename = "$mint_bitworkr")]
pub mint_bitworkr: Option<String>,
#[serde(rename = "$request_ticker")]
pub request_ticker: String,
pub args: Args,
Expand Down
164 changes: 149 additions & 15 deletions src/engine/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
thread::{self, JoinHandle},
thread::{self, sleep, JoinHandle},
time::{Duration, SystemTime, UNIX_EPOCH},
};
// crates.io
use bitcoin::{
Expand Down Expand Up @@ -62,14 +63,27 @@ struct Miner {
}
impl Miner {
const BASE_BYTES: f64 = 10.5;
const BROADCAST_SLEEP_SECONDS: u32 = 15;
const INPUT_BYTES_BASE: f64 = 57.5;
const MAX_BROADCAST_NUM: u32 = 20;
const MAX_SEQUENCE: u32 = u32::MAX;
// OP_RETURN size
// 8-bytes value(roughly estimate), a one-byte script’s size
// actual value size depends precisely on final nonce
const OP_RETURN_BYTES: f64 = 21. + 8. + 1.;
const OUTPUT_BYTES_BASE: f64 = 43.;
const REVEAL_INPUT_BYTES_BASE: f64 = 66.;
const SEQ_RANGE_BUCKET: u32 = 100_000_000;

async fn mine(&self, wallet: &Wallet) -> Result<()> {
let concurrency: u32 = num_cpus::get() as u32;
let seq_range_per_revealer: u32 = Self::SEQ_RANGE_BUCKET / concurrency;

let d = self.prepare_data(wallet).await?;

tracing::info!("attempt to find a solution based on {d:#?}");
tracing::info!("\nStarting commit stage mining now...\n");
tracing::info!("Concurrency set to: {concurrency}");

let Data {
secp,
Expand Down Expand Up @@ -125,7 +139,7 @@ impl Miner {
let maybe_commit_tx = Arc::new(Mutex::new(None));

Self::sequence_ranges().into_iter().enumerate().for_each(|(i, r)| {
tracing::info!("spawning thread {i} for sequence range {r:?}");
tracing::info!("spawning commit worker thread {i} for sequence range {r:?}");

let secp = secp.clone();
let bitworkc = bitworkc.clone();
Expand Down Expand Up @@ -187,8 +201,8 @@ impl Miner {
let txid = tx.txid();

if txid.to_string().trim_start_matches("0x").starts_with(&bitworkc) {
tracing::info!("solution found");
tracing::info!("sequence {s}");
tracing::info!("solution found for commit step");
tracing::info!("commit sequence {s}");
tracing::info!("commit txid {txid}");
tracing::info!("commit tx {tx:#?}");

Expand All @@ -203,14 +217,42 @@ impl Miner {
}));
});

tracing::info!("\nStay calm and grab a drink! Commit workers have started mining...\n");
for t in ts {
t.join().unwrap()?;
}

// TODO: If no solution found.
let commit_tx = maybe_commit_tx.lock().unwrap().take().unwrap();

self.api.broadcast(encode::serialize_hex(&commit_tx)).await?;
let commit_txid = commit_tx.txid();
// tracing::info!("commit txid {}", commit_txid);
tracing::info!("Broadcasting commit tx...");
let raw_tx = encode::serialize_hex(&commit_tx);
tracing::info!("raw tx: {}", &raw_tx);

let mut attempts = 0;
while attempts < Self::MAX_BROADCAST_NUM {
if let Err(_) = self.api.broadcast(raw_tx.clone()).await {

Check warning on line 236 in src/engine/rust.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant pattern matching, consider using `is_err()`

warning: redundant pattern matching, consider using `is_err()` --> src/engine/rust.rs:236:11 | 236 | if let Err(_) = self.api.broadcast(raw_tx.clone()).await { | -------^^^^^^------------------------------------------- help: try: `if (self.api.broadcast(raw_tx.clone()).await).is_err()` | = note: this will change drop order of the result, as well as all temporaries = note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching = note: `#[warn(clippy::redundant_pattern_matching)]` on by default

Check warning on line 236 in src/engine/rust.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant pattern matching, consider using `is_err()`

warning: redundant pattern matching, consider using `is_err()` --> src/engine/rust.rs:236:11 | 236 | if let Err(_) = self.api.broadcast(raw_tx.clone()).await { | -------^^^^^^------------------------------------------- help: try: `if (self.api.broadcast(raw_tx.clone()).await).is_err()` | = note: this will change drop order of the result, as well as all temporaries = note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching = note: `#[warn(clippy::redundant_pattern_matching)]` on by default
tracing::info!(
"Network error, will retry to broadcast commit transaction in {} seconds...",
Self::BROADCAST_SLEEP_SECONDS
);
sleep(Duration::from_secs(15));
attempts += 1;
continue;
}
break;
}

if attempts < Self::MAX_BROADCAST_NUM {
tracing::info!("Successfully sent commit tx {commit_txid}");
} else {
tracing::info!("❌ Failed to send commit tx {commit_txid}");
return Ok(());
}

tracing::info!("\nCommit workers have completed their tasks for the commit transaction.\n");

let commit_txid = commit_tx.txid();
let commit_txid_ = self
Expand All @@ -228,6 +270,9 @@ impl Miner {
let reveal_hty = TapSighashType::SinglePlusAnyoneCanPay;
let reveal_lh = reveal_script.tapscript_leaf_hash();
let reveal_tx = if let Some(bitworkr) = bitworkr {
// exists bitworkr
tracing::info!("\nStarting reveal stage mining now...\n");
tracing::info!("Concurrency set to: {concurrency}");
let psbt = Psbt::from_unsigned_tx(Transaction {
version: Version::ONE,
lock_time: LockTime::ZERO,
Expand All @@ -241,10 +286,11 @@ impl Miner {
let mut ts = <Vec<JoinHandle<Result<()>>>>::new();
let solution_found = Arc::new(AtomicBool::new(false));
let must_tx = Arc::new(Mutex::new(None));
let solution_time = Arc::new(Mutex::<u64>::new(0));
let solution_nonce = Arc::new(Mutex::<u32>::new(0));

for i in 0..num_cpus::get() {
tracing::info!("spawning thread {i} for bitworkr worker");

for i in 0..concurrency {
tracing::info!("spawning reveal worker thread {i} for bitworkr");
let secp = secp.clone();
let bitworkr = bitworkr.clone();
let funding_kp = wallet.funding.pair;
Expand All @@ -254,18 +300,53 @@ impl Miner {
let psbt = psbt.clone();
let solution_found = solution_found.clone();
let must_tx = must_tx.clone();
let solution_time = solution_time.clone();
let solution_nonce = solution_nonce.clone();

ts.push(thread::spawn(move || {
let mut seq_start = i * seq_range_per_revealer;
let mut seq = seq_start;
let mut seq_end = seq_start + seq_range_per_revealer - 1;
if i == (concurrency - 1) {
seq_end = Self::SEQ_RANGE_BUCKET - 1;
}

let mut unixtime =
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let mut nonces_generated: u32 = 0;

loop {
if seq > seq_end {
if seq_end <= Self::MAX_SEQUENCE - Self::SEQ_RANGE_BUCKET {
seq_start += Self::SEQ_RANGE_BUCKET;
seq_end += Self::SEQ_RANGE_BUCKET;
seq = seq_start;
} else {
// reveal worker thread stop mining w/o soluton found
tracing::info!("reveal worker thread {i} traversed its range w/o solution found.");
}
}
if seq % 10000 == 0 {
tracing::trace!(
"started reveal mining for sequence: {seq} - {}",
(seq + 10000).min(seq_end)
);
}

if solution_found.load(Ordering::Relaxed) {
return Ok(());
}

if nonces_generated % 10000 == 0 {
unixtime =
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
}

let mut psbt = psbt.clone();

psbt.unsigned_tx.output.push(TxOut {
value: Amount::ZERO,
script_pubkey: util::time_nonce_script(),
script_pubkey: util::solution_tm_nonce_script(unixtime, seq),
});
psbt.outputs.push(Default::default());

Expand Down Expand Up @@ -314,14 +395,28 @@ impl Miner {
let txid = tx.txid();

if txid.to_string().trim_start_matches("0x").starts_with(&bitworkr) {
tracing::info!("solution found for reveal step");
tracing::info!("reveal sequence {seq}");
tracing::info!("solution at time: {unixtime}, solution nonce: {seq}");
solution_found.store(true, Ordering::Relaxed);
*must_tx.lock().unwrap() = Some(tx);
*solution_time.lock().unwrap() = unixtime;
*solution_nonce.lock().unwrap() = seq;

tracing::info!("\nReveal workers have completed their tasks for the reveal transaction.\n");

return Ok(());
}

seq += 1;
nonces_generated += 1;
}
}));
}

tracing::info!(
"\nDon't despair, it still takes some time! Reveal workers have started mining...\n"
);
for t in ts {
t.join().unwrap()?;
}
Expand All @@ -330,6 +425,7 @@ impl Miner {

tx
} else {
// No bitworkr
let mut psbt = Psbt::from_unsigned_tx(Transaction {
version: Version::ONE,
lock_time: LockTime::ZERO,
Expand Down Expand Up @@ -377,10 +473,33 @@ impl Miner {
psbt.extract_tx_unchecked_fee_rate()
};

tracing::info!("reveal txid {}", reveal_tx.txid());
let reveal_txid = reveal_tx.txid();
tracing::info!("reveal txid {}", reveal_txid);
tracing::info!("reveal tx {reveal_tx:#?}");

self.api.broadcast(encode::serialize_hex(&reveal_tx)).await?;
tracing::info!("Broadcasting reveal tx...");
let raw_tx = encode::serialize_hex(&reveal_tx);
tracing::info!("raw tx: {}", &raw_tx);
let mut attempts = 0;
while attempts < Self::MAX_BROADCAST_NUM {
if let Err(_) = self.api.broadcast(raw_tx.clone()).await {

Check warning on line 485 in src/engine/rust.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant pattern matching, consider using `is_err()`

warning: redundant pattern matching, consider using `is_err()` --> src/engine/rust.rs:485:11 | 485 | if let Err(_) = self.api.broadcast(raw_tx.clone()).await { | -------^^^^^^------------------------------------------- help: try: `if (self.api.broadcast(raw_tx.clone()).await).is_err()` | = note: this will change drop order of the result, as well as all temporaries = note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching

Check warning on line 485 in src/engine/rust.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant pattern matching, consider using `is_err()`

warning: redundant pattern matching, consider using `is_err()` --> src/engine/rust.rs:485:11 | 485 | if let Err(_) = self.api.broadcast(raw_tx.clone()).await { | -------^^^^^^------------------------------------------- help: try: `if (self.api.broadcast(raw_tx.clone()).await).is_err()` | = note: this will change drop order of the result, as well as all temporaries = note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
tracing::info!(
"Network error, will retry to broadcast reveal transaction in {} seconds...",
Self::BROADCAST_SLEEP_SECONDS
);
sleep(Duration::from_secs(15));
attempts += 1;
continue;
}
break;
}

if attempts < Self::MAX_BROADCAST_NUM {
tracing::info!("✅ Successfully sent reveal tx {reveal_txid}");
tracing::info!("✨Congratulations! Mission completed.✨");
} else {
tracing::info!("❌ Failed to send reveal tx {reveal_txid}");
}

Ok(())
}
Expand Down Expand Up @@ -409,7 +528,7 @@ impl Miner {

let secp = Secp256k1::new();
let satsbyte = if self.network == Network::Bitcoin {
util::query_fee().await?.min(self.max_fee) + 5
(util::query_fee().await? + 5).min(self.max_fee)
} else {
2
};
Expand All @@ -420,6 +539,7 @@ impl Miner {
let payload = PayloadWrapper {
args: {
let (time, nonce) = util::time_nonce();
tracing::info!("payload time: {time}, payload nonce: {nonce}");

Payload {
bitworkc: ft.mint_bitworkc.clone(),
Expand All @@ -437,7 +557,13 @@ impl Miner {
.add_leaf(0, reveal_script.clone())?
.finalize(&secp, wallet.funding.x_only_public_key)
.unwrap();
let fees = Self::fees_of(satsbyte, reveal_script.as_bytes().len(), &additional_outputs);
let perform_bitworkr = if ft.mint_bitworkr.is_some() { true } else { false };

Check warning on line 560 in src/engine/rust.rs

View workflow job for this annotation

GitHub Actions / clippy

this if-then-else expression returns a bool literal

warning: this if-then-else expression returns a bool literal --> src/engine/rust.rs:560:26 | 560 | let perform_bitworkr = if ft.mint_bitworkr.is_some() { true } else { false }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `ft.mint_bitworkr.is_some()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool = note: `#[warn(clippy::needless_bool)]` on by default

Check warning on line 560 in src/engine/rust.rs

View workflow job for this annotation

GitHub Actions / clippy

this if-then-else expression returns a bool literal

warning: this if-then-else expression returns a bool literal --> src/engine/rust.rs:560:26 | 560 | let perform_bitworkr = if ft.mint_bitworkr.is_some() { true } else { false }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can reduce it to: `ft.mint_bitworkr.is_some()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool = note: `#[warn(clippy::needless_bool)]` on by default
let fees = Self::fees_of(
satsbyte,
reveal_script.as_bytes().len(),
&additional_outputs,
perform_bitworkr,
);
let funding_utxo = self
.api
.wait_until_utxo(wallet.funding.address.to_string(), fees.commit_and_reveal_and_outputs)
Expand All @@ -456,12 +582,18 @@ impl Miner {
})
}

fn fees_of(satsbyte: u64, reveal_script_len: usize, additional_outputs: &[TxOut]) -> Fees {
fn fees_of(
satsbyte: u64,
reveal_script_len: usize,
additional_outputs: &[TxOut],
perform_bitworkr: bool,
) -> Fees {
let satsbyte = satsbyte as f64;
let commit = {
(satsbyte * (Self::BASE_BYTES + Self::INPUT_BYTES_BASE + Self::OUTPUT_BYTES_BASE))
.ceil() as u64
};
let op_return_size_bytes = if perform_bitworkr { Self::OP_RETURN_BYTES } else { 0. };
let reveal = {
let compact_input_bytes = if reveal_script_len <= 252 {
1.
Expand All @@ -478,6 +610,7 @@ impl Miner {
+ Self::REVEAL_INPUT_BYTES_BASE
+ (compact_input_bytes + reveal_script_len as f64) / 4.
// + utxos.len() as f64 * Self::INPUT_BYTES_BASE
+ op_return_size_bytes
+ additional_outputs.len() as f64 * Self::OUTPUT_BYTES_BASE))
.ceil() as u64
};
Expand All @@ -503,7 +636,8 @@ impl Miner {
}

fn sequence_ranges() -> Vec<Range<u32>> {
let step = (Sequence::MAX.0 as f64 / num_cpus::get() as f64).ceil() as u32;
let concurrency: u32 = num_cpus::get() as u32;
let step = (Sequence::MAX.0 as f64 / concurrency as f64).ceil() as u32;
let mut ranges = Vec::new();
let mut start = 0;

Expand Down
Loading

0 comments on commit efc61e3

Please sign in to comment.