Skip to content

Commit e404f07

Browse files
committed
Fix: always mine valid blocks on reorgs
1 parent 830a941 commit e404f07

File tree

7 files changed

+175
-38
lines changed

7 files changed

+175
-38
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ anyhow = "1.0.72"
1717
bincode = "1.3.3"
1818
bitcoin = "0.32.5"
1919
clap = { version = "4.5.4" }
20+
fallible-iterator = "0.3.0"
2021
futures = { version = "0.3.30", default-features = false }
2122
http = "1.2.0"
2223
jsonrpsee = { version = "0.25.1", features = ["tracing"] }

app/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ bitcoin = { workspace = true, features = ["serde"] }
1313
clap = { workspace = true, features = ["derive"] }
1414
dirs = "5.0.1"
1515
eframe = "0.30.0"
16+
fallible-iterator = { workspace = true }
1617
futures = { workspace = true }
1718
http = { workspace = true }
1819
human-size = "0.4.3"

app/app.rs

Lines changed: 144 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{collections::HashMap, sync::Arc};
22

3+
use fallible_iterator::FallibleIterator as _;
34
use futures::{StreamExt, TryFutureExt};
45
use parking_lot::RwLock;
56
use rustreexo::accumulator::proof::Proof;
@@ -36,6 +37,8 @@ pub enum Error {
3637
Node(#[source] Box<node::Error>),
3738
#[error("No CUSF mainchain wallet client")]
3839
NoCusfMainchainWalletClient,
40+
#[error("Failed to request mainchain ancestor info for {block_hash}")]
41+
RequestMainchainAncestorInfos { block_hash: bitcoin::BlockHash },
3942
#[error("Utreexo error: {0}")]
4043
Utreexo(String),
4144
#[error("Unable to verify existence of CUSF mainchain service(s) at {url}")]
@@ -319,49 +322,156 @@ impl App {
319322
let Some(miner) = self.miner.as_ref() else {
320323
return Err(Error::NoCusfMainchainWalletClient);
321324
};
322-
const NUM_TRANSACTIONS: usize = 1000;
323-
let (txs, tx_fees) = self.node.get_transactions(NUM_TRANSACTIONS)?;
324-
let coinbase = match tx_fees {
325-
bitcoin::Amount::ZERO => vec![],
326-
_ => vec![types::Output {
327-
address: self.wallet.get_new_address()?,
328-
content: types::OutputContent::Value(tx_fees),
329-
}],
330-
};
331-
let body = types::Body::new(txs, coinbase);
332-
let prev_side_hash = self.node.try_get_best_hash()?;
333325
let prev_main_hash = {
334326
let mut miner_write = miner.write().await;
335327
let prev_main_hash =
336328
miner_write.cusf_mainchain.get_chain_tip().await?.block_hash;
337329
drop(miner_write);
338330
prev_main_hash
339331
};
340-
let roots = {
341-
let mut accumulator = self.node.get_tip_accumulator()?;
342-
body.modify_memforest(&mut accumulator.0)
343-
.map_err(Error::Utreexo)?;
344-
accumulator
345-
.0
346-
.get_roots()
347-
.iter()
348-
.map(|root| root.get_data())
349-
.collect()
350-
};
351-
let header = types::Header {
352-
merkle_root: body.compute_merkle_root(),
353-
roots,
354-
prev_side_hash,
355-
prev_main_hash,
356-
};
357-
let bribe = fee.unwrap_or_else(|| {
358-
if tx_fees > bitcoin::Amount::ZERO {
359-
tx_fees
332+
let tip_hash = self.node.try_get_best_hash()?;
333+
// If `prev_side_hash` is not the best tip to mine on, then mine an
334+
// empty block.
335+
// This is a temporary fix, ideally we always choose the best tip to
336+
// mine on
337+
let prev_side_hash = if let Some(tip_hash) = tip_hash {
338+
let tip_header = self.node.get_header(tip_hash)?;
339+
let archive = self.node.archive();
340+
let prev_main_hash_header_in_archive = {
341+
let rotxn =
342+
self.node.env().read_txn().map_err(node::Error::from)?;
343+
archive
344+
.try_get_main_header_info(&rotxn, &prev_main_hash)
345+
.map_err(node::Error::from)?
346+
.is_some()
347+
};
348+
if !prev_main_hash_header_in_archive {
349+
// Request mainchain header info
350+
if !self
351+
.node
352+
.request_mainchain_ancestor_infos(prev_main_hash)
353+
.await?
354+
{
355+
return Err(Error::RequestMainchainAncestorInfos {
356+
block_hash: prev_main_hash,
357+
});
358+
}
359+
}
360+
let rotxn =
361+
self.node.env().read_txn().map_err(node::Error::from)?;
362+
let last_common_main_ancestor = archive
363+
.last_common_main_ancestor(
364+
&rotxn,
365+
prev_main_hash,
366+
tip_header.prev_main_hash,
367+
)
368+
.map_err(node::Error::from)?;
369+
if last_common_main_ancestor == tip_header.prev_main_hash {
370+
Some(tip_hash)
360371
} else {
361-
Self::EMPTY_BLOCK_BMM_BRIBE
372+
// Find a tip to mine on
373+
archive
374+
.ancestor_headers(&rotxn, tip_hash)
375+
.find_map(|(block_hash, header)| {
376+
if header.prev_main_hash == last_common_main_ancestor {
377+
Ok(None)
378+
} else if archive.is_main_descendant(
379+
&rotxn,
380+
header.prev_main_hash,
381+
last_common_main_ancestor,
382+
)? {
383+
Ok(Some(block_hash))
384+
} else {
385+
Ok(None)
386+
}
387+
})
388+
.map_err(node::Error::from)?
362389
}
363-
});
364-
390+
} else {
391+
None
392+
};
393+
let (bribe, header, body) = if prev_side_hash == tip_hash {
394+
const NUM_TRANSACTIONS: usize = 1000;
395+
let (txs, tx_fees) =
396+
self.node.get_transactions(NUM_TRANSACTIONS)?;
397+
let coinbase = match tx_fees {
398+
bitcoin::Amount::ZERO => Vec::new(),
399+
_ => vec![types::Output {
400+
address: self.wallet.get_new_address()?,
401+
content: types::OutputContent::Value(tx_fees),
402+
}],
403+
};
404+
let body = types::Body::new(txs, coinbase);
405+
let roots = {
406+
let mut accumulator = if let Some(tip_hash) = tip_hash {
407+
let rotxn = self
408+
.node
409+
.env()
410+
.read_txn()
411+
.map_err(node::Error::from)?;
412+
self.node
413+
.archive()
414+
.get_accumulator(&rotxn, tip_hash)
415+
.map_err(node::Error::from)?
416+
} else {
417+
types::Accumulator::default()
418+
};
419+
body.modify_memforest(&mut accumulator.0)
420+
.map_err(Error::Utreexo)?;
421+
accumulator
422+
.0
423+
.get_roots()
424+
.iter()
425+
.map(|root| root.get_data())
426+
.collect()
427+
};
428+
let header = types::Header {
429+
merkle_root: body.compute_merkle_root(),
430+
roots,
431+
prev_side_hash,
432+
prev_main_hash,
433+
};
434+
let bribe = fee.unwrap_or_else(|| {
435+
if tx_fees > bitcoin::Amount::ZERO {
436+
tx_fees
437+
} else {
438+
Self::EMPTY_BLOCK_BMM_BRIBE
439+
}
440+
});
441+
(bribe, header, body)
442+
} else {
443+
let coinbase = Vec::new();
444+
let body = types::Body::new(Vec::new(), coinbase);
445+
let roots = {
446+
let accumulator = if let Some(prev_side_hash) = prev_side_hash {
447+
let rotxn = self
448+
.node
449+
.env()
450+
.read_txn()
451+
.map_err(node::Error::from)?;
452+
self.node
453+
.archive()
454+
.get_accumulator(&rotxn, prev_side_hash)
455+
.map_err(node::Error::from)?
456+
} else {
457+
types::Accumulator::default()
458+
};
459+
accumulator
460+
.0
461+
.get_roots()
462+
.iter()
463+
.map(|root| root.get_data())
464+
.collect()
465+
};
466+
let header = types::Header {
467+
merkle_root: body.compute_merkle_root(),
468+
roots,
469+
prev_side_hash,
470+
prev_main_hash,
471+
};
472+
let bribe = Self::EMPTY_BLOCK_BMM_BRIBE;
473+
(bribe, header, body)
474+
};
365475
let mut miner_write = miner.write().await;
366476
let bmm_txid = miner_write
367477
.attempt_bmm(bribe.to_sat(), 0, header, body)

lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ byteorder = "1.4.3"
2323
bytes = "1.4.0"
2424
ed25519-dalek = { version = "2.1.1", features = ["batch", "serde"] }
2525
ed25519-dalek-bip32 = "0.3.0"
26-
fallible-iterator = "0.3.0"
26+
fallible-iterator = { workspace = true }
2727
fatality = "0.1.1"
2828
futures = "0.3.30"
2929
hashlink = { version = "0.10.0", features = ["serde_impl"] }

lib/archive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ impl Archive {
612612
}
613613

614614
/// Returns true if the second specified block is a descendant of the first
615-
/// specified block
615+
/// specified block.
616616
/// Returns an error if either of the specified block headers do not exist
617617
/// in the archive.
618618
pub fn is_descendant(

lib/node/mod.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
use bitcoin::amount::CheckedSum;
1010
use fallible_iterator::FallibleIterator;
1111
use futures::{Stream, future::BoxFuture};
12-
use sneed::{Env, EnvError, RwTxnError, db::error::Error as DbError};
12+
use sneed::{DbError, Env, EnvError, RwTxnError, env};
1313
use tokio::sync::Mutex;
1414
use tonic::transport::Channel;
1515

@@ -35,7 +35,8 @@ use mainchain_task::MainchainTaskHandle;
3535

3636
use self::net_task::NetTaskHandle;
3737

38-
#[derive(Debug, thiserror::Error)]
38+
#[derive(Debug, thiserror::Error, transitive::Transitive)]
39+
#[transitive(from(env::error::ReadTxn, EnvError))]
3940
pub enum Error {
4041
#[error("address parse error")]
4142
AddrParse(#[from] std::net::AddrParseError),
@@ -185,6 +186,14 @@ where
185186
})
186187
}
187188

189+
pub fn env(&self) -> &Env {
190+
&self.env
191+
}
192+
193+
pub fn archive(&self) -> &Archive {
194+
&self.archive
195+
}
196+
188197
/// Borrow the CUSF mainchain client, and execute the provided future.
189198
/// The CUSF mainchain client will be locked while the future is running.
190199
pub async fn with_cusf_mainchain<F, Output>(&self, f: F) -> Output
@@ -484,6 +493,21 @@ where
484493
self.net.get_active_peers()
485494
}
486495

496+
pub async fn request_mainchain_ancestor_infos(
497+
&self,
498+
block_hash: bitcoin::BlockHash,
499+
) -> Result<bool, Error> {
500+
let mainchain_task::Response::AncestorInfos(_, res): mainchain_task::Response = self
501+
.mainchain_task
502+
.request_oneshot(mainchain_task::Request::AncestorInfos(
503+
block_hash,
504+
))
505+
.map_err(|_| Error::SendMainchainTaskRequest)?
506+
.await
507+
.map_err(|_| Error::ReceiveMainchainTaskResponse)?;
508+
res.map_err(Error::MainchainAncestors)
509+
}
510+
487511
/// Attempt to submit a block.
488512
/// Returns `Ok(true)` if the block was accepted successfully as the new tip.
489513
/// Returns `Ok(false)` if the block could not be submitted for some reason,

0 commit comments

Comments
 (0)