Skip to content

Commit 69182a1

Browse files
committed
Rework sync checking and initial reorg tests
1 parent cdcdd2a commit 69182a1

File tree

5 files changed

+88
-9
lines changed

5 files changed

+88
-9
lines changed

node/src/node.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub trait BlockSource {
2626
fn get_block(&self, hash: &BlockHash) -> Result<Block, BitcoinRpcError>;
2727
fn get_median_time(&self) -> Result<u64, BitcoinRpcError>;
2828
fn get_block_count(&self) -> Result<u64, BitcoinRpcError>;
29+
fn get_best_chain(&self) -> Result<ChainAnchor, BitcoinRpcError>;
2930
}
3031

3132
#[derive(Debug, Clone)]

node/src/source.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,29 @@ impl BlockFetcher {
320320
self.job_id.fetch_add(1, Ordering::SeqCst);
321321
}
322322

323+
fn should_sync(
324+
source: &BitcoinBlockSource,
325+
start: ChainAnchor,
326+
) -> Result<Option<ChainAnchor>, BlockFetchError> {
327+
let tip = source.get_best_chain()?;
328+
if start.height > tip.height {
329+
return Err(BlockFetchError::BlockMismatch);
330+
}
331+
332+
// Ensure start block is still in the best chain
333+
let start_hash: BlockHash = source.get_block_hash(start.height)?;
334+
if start.hash != start_hash {
335+
return Err(BlockFetchError::BlockMismatch);
336+
}
337+
338+
// If the chain didn't advance and the hashes match, no rescan is needed
339+
if tip.height == start.height && tip.hash == start.hash {
340+
return Ok(None);
341+
}
342+
343+
Ok(Some(tip))
344+
}
345+
323346
pub fn start(&self, mut checkpoint: ChainAnchor) {
324347
self.stop();
325348

@@ -343,27 +366,28 @@ impl BlockFetcher {
343366
}
344367
last_check = Instant::now();
345368

346-
let tip: u32 = match task_src.get_block_count() {
347-
Ok(t) => t as _,
369+
let tip = match BlockFetcher::should_sync(&task_src, checkpoint) {
370+
Ok(t) => t,
348371
Err(e) => {
349-
_ = task_sender.send(BlockEvent::Error(BlockFetchError::RpcError(e)));
372+
_ = task_sender.send(BlockEvent::Error(e));
350373
return;
351374
}
352375
};
353376

354-
if tip > checkpoint.height {
377+
if let Some(tip) = tip {
355378
let res = Self::run_workers(
356379
job_id,
357380
current_task.clone(),
358381
task_src.clone(),
359382
task_sender.clone(),
360383
checkpoint,
361-
tip,
384+
tip.height,
362385
num_workers,
363386
);
364387

365388
match res {
366389
Ok(new_tip) => {
390+
info!("new tip set: {}", new_tip.hash);
367391
checkpoint = new_tip;
368392
}
369393
Err(e) => {
@@ -724,4 +748,22 @@ impl BlockSource for BitcoinBlockSource {
724748
.rpc
725749
.send_json_blocking(&self.client, &self.rpc.get_block_count())?)
726750
}
751+
752+
fn get_best_chain(&self) -> Result<ChainAnchor, BitcoinRpcError> {
753+
#[derive(Deserialize)]
754+
struct Info {
755+
#[serde(rename = "blocks")]
756+
height: u64,
757+
#[serde(rename = "bestblockhash")]
758+
hash: BlockHash,
759+
}
760+
let info: Info = self
761+
.rpc
762+
.send_json_blocking(&self.client, &self.rpc.get_blockchain_info())?;
763+
764+
Ok(ChainAnchor {
765+
hash: info.hash,
766+
height: info.height as _,
767+
})
768+
}
727769
}

node/tests/fetcher_tests.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@ fn test_block_fetching_from_bitcoin_rpc() -> Result<()> {
2626
&rig.bitcoind.rpc_url(),
2727
BitcoinRpcAuth::UserPass("user".to_string(), "password".to_string()),
2828
));
29-
let (fetcher, receiver) = BlockFetcher::new(
30-
fetcher_rpc.clone(),
31-
8
32-
);
29+
let (fetcher, receiver) = BlockFetcher::new(fetcher_rpc.clone(), 8);
3330
fetcher.start(ChainAnchor { hash, height: 0 });
3431

3532
let timeout = Duration::from_secs(5);

node/tests/reorg_tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use std::time::Duration;
2+
3+
use spaced::rpc::RpcClient;
4+
use testutil::TestRig;
5+
6+
#[tokio::test]
7+
async fn it_should_resync_after_reorg_at_same_height() -> anyhow::Result<()> {
8+
let rig = TestRig::new().await?;
9+
rig.mine_blocks(38, None).await?;
10+
rig.wait_until_synced().await?;
11+
12+
let info = rig.spaced.client.get_server_info().await?;
13+
assert_eq!(info.tip.height, 38);
14+
assert_eq!(info.tip.hash, rig.get_best_block_hash().await?);
15+
16+
let reorged = rig.reorg(1).await?;
17+
assert_eq!(reorged.len(), 1);
18+
19+
rig.wait_until_tip(reorged[0], Duration::from_secs(2))
20+
.await?;
21+
Ok(())
22+
}

testutil/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use ::spaced::{
1919
use anyhow::Result;
2020
use bitcoind::{
2121
anyhow,
22+
anyhow::anyhow,
2223
bitcoincore_rpc::{
2324
bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},
2425
RpcApi,
@@ -90,6 +91,22 @@ impl TestRig {
9091
}
9192
}
9293

94+
/// Waits until spaced tip == specified best_hash or specified time out
95+
pub async fn wait_until_tip(&self, tip: BlockHash, timeout: Duration) -> Result<()> {
96+
let start_time = tokio::time::Instant::now();
97+
98+
loop {
99+
let info = self.spaced.client.get_server_info().await?;
100+
if info.tip.hash == tip {
101+
return Ok(());
102+
}
103+
if start_time.elapsed() >= timeout {
104+
return Err(anyhow!("Rimed out waiting for tip {:?}", tip));
105+
}
106+
tokio::time::sleep(Duration::from_millis(100)).await;
107+
}
108+
}
109+
93110
/// Waits until named wallet tip == bitcoind tip
94111
pub async fn wait_until_wallet_synced(&self, wallet_name: &str) -> anyhow::Result<()> {
95112
loop {

0 commit comments

Comments
 (0)