Skip to content

Commit 3d8b3e3

Browse files
Merge pull request #8 from Davidson-Souza/handle-reorgs
Handle reorgs
2 parents e75f56b + df44696 commit 3d8b3e3

10 files changed

Lines changed: 265 additions & 69 deletions

File tree

config.toml.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ rpc_password = "SomePassword"
77
rpc_host = "https://127.0.0.1:38334"
88

99
[misc]
10-
external_sync = ""
10+
batch_sync = ""

src/blockchain/chain_state.rs

Lines changed: 187 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,165 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
9191
}
9292
Ok(true)
9393
}
94+
#[inline]
95+
/// Whether a node is the genesis block for this net
96+
fn is_genesis(&self, header: &BlockHeader) -> bool {
97+
header.block_hash() == self.chain_params().genesis.block_hash()
98+
}
99+
#[inline]
100+
/// Returns the ancestor of a given block
101+
fn get_ancestor(&self, header: &BlockHeader) -> Result<DiskBlockHeader, BlockchainError> {
102+
self.get_disk_block_header(&header.prev_blockhash)
103+
}
104+
/// Returns the cumulative work in this branch
105+
fn get_branch_work(&self, header: &BlockHeader) -> Result<Uint256, BlockchainError> {
106+
let mut header = header.clone();
107+
let mut work = Uint256::from_u64(0).unwrap();
108+
while !self.is_genesis(&header) {
109+
work = work + header.work();
110+
header = *self.get_ancestor(&header)?;
111+
}
112+
113+
Ok(work)
114+
}
115+
fn check_branch(&self, branch_tip: &BlockHeader) -> Result<(), BlockchainError> {
116+
let mut header = self.get_disk_block_header(&branch_tip.block_hash())?;
117+
118+
while !self.is_genesis(&header) {
119+
header = self.get_ancestor(&header)?;
120+
match header {
121+
DiskBlockHeader::Orphan(block) => {
122+
return Err(BlockchainError::InvalidTip(format!(
123+
"Block {} doesn't have a known ancestor (i.e an orphan block)",
124+
block.block_hash()
125+
)))
126+
}
127+
_ => { /* do nothing */ }
128+
}
129+
}
130+
131+
Ok(())
132+
}
133+
fn get_chain_depth(&self, branch_tip: &BlockHeader) -> Result<u32, BlockchainError> {
134+
let mut header = self.get_disk_block_header(&branch_tip.block_hash())?;
135+
136+
let mut counter = 0;
137+
while !self.is_genesis(&header) {
138+
header = self.get_ancestor(&header)?;
139+
counter += 1;
140+
}
141+
142+
Ok(counter)
143+
}
144+
fn mark_chain_as_active(&self, new_tip: &BlockHeader) -> Result<(), BlockchainError> {
145+
let mut header = self.get_disk_block_header(&new_tip.block_hash())?;
146+
let height = self.get_chain_depth(new_tip)?;
147+
let inner = read_lock!(self);
148+
while !self.is_genesis(&header) {
149+
header = self.get_ancestor(&header)?;
150+
inner
151+
.chainstore
152+
.update_block_index(height, header.block_hash())?;
153+
let new_header = DiskBlockHeader::HeadersOnly(*header, height);
154+
inner.chainstore.save_header(&new_header)?;
155+
}
156+
157+
Ok(())
158+
}
159+
/// Mark the current index as inactive, either because we found an invalid ancestor,
160+
/// or we are in the middle of reorg
161+
fn mark_chain_as_inactive(&self, new_tip: &BlockHeader) -> Result<(), BlockchainError> {
162+
let mut header = self.get_disk_block_header(&new_tip.block_hash())?;
163+
let inner = read_lock!(self);
164+
while !self.is_genesis(&header) {
165+
header = self.get_ancestor(&header)?;
166+
let new_header = DiskBlockHeader::InFork(*header);
167+
inner.chainstore.save_header(&new_header)?;
168+
}
169+
170+
Ok(())
171+
}
172+
// This method should only be called after we validate the new branch
173+
fn reorg(&self, new_tip: BlockHeader) -> Result<(), BlockchainError> {
174+
let current_best_block = self.get_best_block().unwrap().1;
175+
let current_best_block = self.get_block_header(&current_best_block)?;
176+
self.mark_chain_as_active(&new_tip)?;
177+
self.mark_chain_as_inactive(&current_best_block)?;
178+
179+
let mut inner = self.inner.write().unwrap();
180+
inner.best_block.best_block = new_tip.block_hash();
181+
inner.best_block.validation_index = self.get_last_valid_block(&new_tip)?;
182+
Ok(())
183+
}
184+
185+
/// Grabs the last block we validated in this branch. We don't validate a fork, unless it
186+
/// becomes the best chain. This function technically finds out what is the last common block
187+
/// between two branches.
188+
fn get_last_valid_block(&self, header: &BlockHeader) -> Result<BlockHash, BlockchainError> {
189+
let mut header = self.get_disk_block_header(&header.block_hash())?;
190+
191+
while !self.is_genesis(&header) {
192+
match header {
193+
DiskBlockHeader::FullyValid(_, _) => return Ok(header.block_hash()),
194+
DiskBlockHeader::Orphan(_) => {
195+
return Err(BlockchainError::InvalidTip(format!(
196+
"Block {} doesn't have a known ancestor (i.e an orphan block)",
197+
header.block_hash()
198+
)))
199+
}
200+
DiskBlockHeader::HeadersOnly(_, _) | DiskBlockHeader::InFork(_) => {}
201+
}
202+
header = self.get_ancestor(&header)?;
203+
}
204+
205+
unreachable!()
206+
}
207+
/// If we get a header that doesn't build on top of our best chain, it may cause a reorganization.
208+
/// We check this here.
209+
pub fn maybe_reorg(&self, branch_tip: BlockHeader) -> Result<(), BlockchainError> {
210+
let current_tip = self.get_block_header(&self.get_best_block().unwrap().1)?;
211+
self.check_branch(&branch_tip)?;
212+
213+
let current_work = self.get_branch_work(&current_tip)?;
214+
let new_work = self.get_branch_work(&branch_tip)?;
215+
216+
if new_work > current_work {
217+
self.reorg(branch_tip)?;
218+
return Ok(());
219+
}
220+
self.push_alt_tip(&branch_tip)?;
221+
222+
read_lock!(self)
223+
.chainstore
224+
.save_header(&super::chainstore::DiskBlockHeader::InFork(branch_tip))?;
225+
Ok(())
226+
}
227+
/// Stores a new tip for a branch that is not the best one
228+
fn push_alt_tip(&self, branch_tip: &BlockHeader) -> Result<(), BlockchainError> {
229+
let ancestor = self.get_ancestor(branch_tip);
230+
let ancestor = match ancestor {
231+
Ok(ancestor) => Some(ancestor),
232+
Err(BlockchainError::BlockNotPresent) => None,
233+
Err(e) => return Err(e),
234+
};
235+
let mut inner = write_lock!(self);
236+
if ancestor.is_some() {
237+
let ancestor_hash = ancestor.unwrap().block_hash();
238+
if let Some(idx) = inner
239+
.best_block
240+
.alternative_tips
241+
.iter()
242+
.position(|hash| ancestor_hash == *hash)
243+
{
244+
inner.best_block.alternative_tips.remove(idx);
245+
}
246+
}
247+
inner
248+
.best_block
249+
.alternative_tips
250+
.push(branch_tip.block_hash());
251+
Ok(())
252+
}
94253
fn calc_next_work_required(
95254
last_block: &BlockHeader,
96255
first_block: &BlockHeader,
@@ -239,11 +398,15 @@ impl<PersistedState: ChainStore> ChainState<PersistedState> {
239398
) -> ChainState<KvChainStore> {
240399
let genesis = genesis_block(network);
241400
chainstore
242-
.save_header(
243-
&super::chainstore::DiskBlockHeader::FullyValid(genesis.header, 0),
401+
.save_header(&super::chainstore::DiskBlockHeader::FullyValid(
402+
genesis.header,
244403
0,
245-
)
404+
))
246405
.expect("Error while saving genesis");
406+
chainstore
407+
.update_block_index(0, genesis.block_hash())
408+
.expect("Error updating index");
409+
247410
let assume_valid_hash = Self::get_assume_valid_value(network, assume_valid);
248411
ChainState {
249412
inner: RwLock::new(ChainStateInner {
@@ -462,6 +625,7 @@ impl<PersistedState: ChainStore> BlockchainProviderInterface for ChainState<Pers
462625
return Ok(());
463626
}
464627
DiskBlockHeader::Orphan(_) => return Ok(()),
628+
DiskBlockHeader::InFork(_) => return Ok(()),
465629
DiskBlockHeader::HeadersOnly(_, height) => height,
466630
};
467631
let inner = self.inner.read().unwrap();
@@ -501,10 +665,15 @@ impl<PersistedState: ChainStore> BlockchainProviderInterface for ChainState<Pers
501665
// Notify others we have a new block
502666
async_std::task::block_on(self.notify(Notification::NewBlock((block.to_owned(), height))));
503667

504-
inner.chainstore.save_header(
505-
&super::chainstore::DiskBlockHeader::FullyValid(block.header, height),
506-
height,
507-
)?;
668+
inner
669+
.chainstore
670+
.save_header(&super::chainstore::DiskBlockHeader::FullyValid(
671+
block.header,
672+
height,
673+
))?;
674+
inner
675+
.chainstore
676+
.update_block_index(height, block.block_hash())?;
508677
inner.chainstore.save_height(&inner.best_block)?;
509678
// Drop this lock because we need a write lock to inner, if we hold this lock this will
510679
// cause a deadlock.
@@ -518,10 +687,6 @@ impl<PersistedState: ChainStore> BlockchainProviderInterface for ChainState<Pers
518687
Ok(())
519688
}
520689

521-
fn handle_reorg(&self) -> super::Result<()> {
522-
todo!()
523-
}
524-
525690
fn handle_transaction(&self) -> super::Result<()> {
526691
unimplemented!("This chain_state has no mempool")
527692
}
@@ -553,21 +718,23 @@ impl<PersistedState: ChainStore> BlockchainProviderInterface for ChainState<Pers
553718
let prev_block = self.get_block_header(&best_block.best_block)?;
554719
// Check pow
555720
let target = self.get_next_required_work(&prev_block, height);
556-
let _ = header.validate_pow(&target).map_err(|_| {
721+
let block_hash = header.validate_pow(&target).map_err(|_| {
557722
BlockchainError::BlockValidationError(BlockValidationErrors::NotEnoughPow)
558723
})?;
559-
inner.chainstore.save_header(
560-
&super::chainstore::DiskBlockHeader::HeadersOnly(header, height),
561-
height,
562-
)?;
724+
inner
725+
.chainstore
726+
.save_header(&super::chainstore::DiskBlockHeader::HeadersOnly(
727+
header, height,
728+
))?;
729+
inner.chainstore.update_block_index(height, block_hash)?;
563730
drop(inner);
564731
let mut inner = self.inner.write().unwrap();
565-
inner.best_block.new_block(header.block_hash(), height);
732+
inner.best_block.new_block(block_hash, height);
566733
if header.block_hash() == inner.assume_valid.0 {
567734
inner.assume_valid.1 = height;
568735
}
569736
} else {
570-
unimplemented!();
737+
self.maybe_reorg(header)?;
571738
}
572739
Ok(())
573740
}
@@ -579,7 +746,7 @@ impl<PersistedState: ChainStore> BlockchainProviderInterface for ChainState<Pers
579746
match header {
580747
DiskBlockHeader::HeadersOnly(_, height) => Ok(height),
581748
DiskBlockHeader::FullyValid(_, height) => Ok(height),
582-
DiskBlockHeader::Orphan(_) => unreachable!(),
749+
_ => unreachable!(),
583750
}
584751
}
585752
}
@@ -597,6 +764,7 @@ macro_rules! write_lock {
597764
$obj.inner.write().expect("get_block_hash: Poisoned lock")
598765
};
599766
}
767+
600768
#[derive(Clone)]
601769
/// Internal representation of the chain we are in
602770
pub struct BestChain {

src/blockchain/chainparams.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,30 @@ pub struct ChainParams {
2323
/// it's more, we decrease difficulty, if it's less we increase difficulty
2424
pub pow_target_timespan: u64,
2525
}
26-
impl ChainParams {}
26+
impl ChainParams {
27+
fn max_target(net: Network) -> Uint256 {
28+
match net {
29+
Network::Bitcoin => max_target(net),
30+
Network::Testnet => max_target(net),
31+
Network::Signet => Uint256([
32+
0x00000377ae000000,
33+
0x0000000000000000,
34+
0x0000000000000000,
35+
0x0000000000000000,
36+
]),
37+
Network::Regtest => Uint256([
38+
0x7fffffffffffffff,
39+
0xffffffffffffffff,
40+
0xffffffffffffffff,
41+
0xffffffffffffffff,
42+
]),
43+
}
44+
}
45+
}
2746
impl From<Network> for ChainParams {
2847
fn from(net: Network) -> Self {
2948
let genesis = genesis_block(net);
30-
let max_target = max_target(net);
49+
let max_target = ChainParams::max_target(net);
3150
match net {
3251
Network::Bitcoin => ChainParams {
3352
genesis,
@@ -59,7 +78,7 @@ impl From<Network> for ChainParams {
5978
Network::Regtest => ChainParams {
6079
genesis,
6180
max_target,
62-
pow_allow_min_diff: true,
81+
pow_allow_min_diff: false,
6382
pow_allow_no_retarget: true,
6483
pow_target_spacing: 10 * 60, // One block every 600 seconds (10 minutes)
6584
pow_target_timespan: 14 * 24 * 60 * 60, // two weeks

0 commit comments

Comments
 (0)