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

Some fix and optimization for rust engine #50

Merged
merged 2 commits into from
Jan 21, 2024
Merged
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
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.;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about how you obtained these values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Aurevior, my understanding is:
<8 bytes value/amount> + <1 byte len of following script> + <1 byte OP_RETURN(0x6a)> + <1 byte len indicating the rest> + <10 byte unix_timestamp> + <1 byte for colon separator(:)> + <8 bytes nonce, roughly estimation>
so: 8 + 1 + 1 + 1 + 10 + 1 + 8 = 30 bytes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the comments maybe a little confusing: 8-bytes nonce rather than 8-bytes value and value size should be nonce size @AurevoirXavier

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 {
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 {
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 };
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
Loading