Skip to content

Commit

Permalink
Merge #2967: [base-node] Create new orphan chain tip on chain reorg
Browse files Browse the repository at this point in the history
[base-node] Create new orphan chain tip on chain reorg
  • Loading branch information
stringhandler committed May 25, 2021
2 parents 83ca022 + 482f672 commit ef5df7f
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 15 deletions.
16 changes: 8 additions & 8 deletions base_layer/core/src/chain_storage/blockchain_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,25 @@ use croaring::Bitmap;
use tari_common_types::chain_metadata::ChainMetadata;
use tari_mmr::Hash;

/// Identify behaviour for Blockchain database back ends. Implementations must support `Send` and `Sync` so that
/// Identify behaviour for Blockchain database backends. Implementations must support `Send` and `Sync` so that
/// `BlockchainDatabase` can be thread-safe. The backend *must* also execute transactions atomically; i.e., every
/// operation within it must succeed, or they all fail. Failure to support this contract could lead to
/// synchronisation issues in your database backend.
///
/// Data is passed to and from the backend via the [DbKey], [DbValue], and [DbValueKey] enums. This strategy allows
/// us to keep the reading and writing API extremely simple. Extending the types of data that the back ends can handle
/// will entail adding to those enums, and the back ends, while this trait can remain unchanged.
/// us to keep the reading and writing API extremely simple. Extending the types of data that the backends can handle
/// will entail adding to those enums, and the backends, while this trait can remain unchanged.
#[allow(clippy::ptr_arg)]
pub trait BlockchainBackend: Send + Sync {
/// Commit the transaction given to the backend. If there is an error, the transaction must be rolled back, and
/// the error condition returned. On success, every operation in the transaction will have been committed, and
/// the function will return `Ok(())`.
fn write(&mut self, tx: DbTransaction) -> Result<(), ChainStorageError>;
/// Fetch a value from the back end corresponding to the given key. If the value is not found, `get` must return
/// `Ok(None)`. It should only error if there is an access or integrity issue with the underlying back end.
/// Fetch a value from the backend corresponding to the given key. If the value is not found, `get` must return
/// `Ok(None)`. It should only error if there is an access or integrity issue with the underlying backend.
fn fetch(&self, key: &DbKey) -> Result<Option<DbValue>, ChainStorageError>;
/// Checks to see whether the given key exists in the back end. This function should only fail if there is an
/// access or integrity issue with the back end.
/// Checks to see whether the given key exists in the backend. This function should only fail if there is an
/// access or integrity issue with the backend.
fn contains(&self, key: &DbKey) -> Result<bool, ChainStorageError>;

/// Fetches data that is calculated and accumulated for blocks that have been
Expand Down Expand Up @@ -129,7 +129,7 @@ pub trait BlockchainBackend: Send + Sync {
/// Returns the kernel count
fn kernel_count(&self) -> Result<usize, ChainStorageError>;

/// Fetches an current tip orphan by hash or returns None if the prohan is not found or is not a tip of any
/// Fetches an current tip orphan by hash or returns None if the orphan is not found or is not a tip of any
/// alternate chain
fn fetch_orphan_chain_tip_by_hash(&self, hash: &HashOutput) -> Result<Option<ChainHeader>, ChainStorageError>;
/// Fetch all orphans that have `hash` as a previous hash
Expand Down
17 changes: 14 additions & 3 deletions base_layer/core/src/chain_storage/blockchain_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,8 @@ fn check_for_valid_height<T: BlockchainBackend>(db: &T, height: u64) -> Result<(
Ok((tip_height, height < pruned_height))
}

/// Removes blocks from the db from current tip to specified height.
/// Returns the blocks removed, ordered from tip to height.
fn rewind_to_height<T: BlockchainBackend>(
db: &mut T,
mut height: u64,
Expand Down Expand Up @@ -1512,7 +1514,6 @@ fn handle_possible_reorg<T: BlockchainBackend>(

let num_added_blocks = reorg_chain.len();
let removed_blocks = reorganize_chain(db, block_validator, fork_height, &reorg_chain)?;

let num_removed_blocks = removed_blocks.len();

// reorg is required when any blocks are removed or more than one are added
Expand Down Expand Up @@ -1548,7 +1549,8 @@ fn handle_possible_reorg<T: BlockchainBackend>(
}
}

// Reorganize the main chain with the provided fork chain, starting at the specified height.
/// Reorganize the main chain with the provided fork chain, starting at the specified height.
/// Returns the blocks that were removed (if any), ordered from tip to fork (ie. height desc).
fn reorganize_chain<T: BlockchainBackend>(
backend: &mut T,
block_validator: &dyn PostOrphanBodyValidation<T>,
Expand Down Expand Up @@ -1603,6 +1605,15 @@ fn reorganize_chain<T: BlockchainBackend>(
}
}

if let Some(block) = removed_blocks.first() {
// insert the new orphan chain tip
let mut txn = DbTransaction::new();
let hash = block.hash().clone();
debug!(target: LOG_TARGET, "Inserting new orphan chain tip: {}", hash.to_hex());
txn.insert_orphan_chain_tip(hash);
backend.write(txn)?;
}

Ok(removed_blocks)
}

Expand Down Expand Up @@ -1632,7 +1643,7 @@ fn restore_reorged_chain<T: BlockchainBackend>(
Ok(())
}

// Insert the provided block into the orphan pool and returns any new tips that were created
/// Insert the provided block into the orphan pool and returns any new tips that were created.
fn insert_orphan_and_find_new_tips<T: BlockchainBackend>(
db: &mut T,
block: Arc<Block>,
Expand Down
8 changes: 4 additions & 4 deletions base_layer/core/src/mempool/sync_protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@
//! +-------+ +-----+
//! | |
//! | Txn Inventory |
//! |------------------------------->|
//! |------------------------------->|
//! | |
//! | TransactionItem(tx_b1) |
//! |<-------------------------------|
//! |<-------------------------------|
//! | ...streaming... |
//! | TransactionItem(empty) |
//! |<-------------------------------|
//! | Inventory missing txn indexes |
//! |<-------------------------------|
//! | |
//! | TransactionItem(tx_a1) |
//! |------------------------------->|
//! |------------------------------->|
//! | ...streaming... |
//! | TransactionItem(empty) |
//! |------------------------------->|
Expand Down Expand Up @@ -147,7 +147,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static
if (*status_watch.borrow()).bootstrapped {
break;
}
debug!(
trace!(
target: LOG_TARGET,
"Mempool sync still on hold, waiting for bootstrap to finish",
);
Expand Down
249 changes: 249 additions & 0 deletions base_layer/core/tests/chain_storage_tests/chain_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,255 @@ fn handle_reorg() {
assert!(store.fetch_orphan(blocks[4].hash().clone()).is_ok()); // B4
}

#[test]
fn reorgs_should_update_orphan_tips() {
// Create a main chain GB -> A1 -> A2
// Create an orphan chain GB -> B1
// Add a block B2 that forces a reorg to B2
// Check that A2 is in the orphan chain tips db
// Add a block A3 that forces a reorg to A3
// Check that B2 is in the orphan chain tips db
// Add 2 blocks B3 and B4 that force a reorg to B4
// Check that A3 is in the orphan chain tips db
// Add 2 blocks A4 and A5 that force a reorg to A5
// Check that B4 is in the orphan chain tips db

let network = Network::LocalNet;
let (store, blocks, outputs, consensus_manager) = create_new_blockchain(network);

// Create "A" Chain
let mut a_store = create_store_with_consensus(consensus_manager.clone());
let mut a_blocks = vec![blocks[0].clone()];
let mut a_outputs = vec![outputs[0].clone()];

// Block A1
let txs = vec![txn_schema!(from: vec![a_outputs[0][0].clone()], to: vec![50 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut a_store,
&mut a_blocks,
&mut a_outputs,
txs,
Difficulty::from(1),
&consensus_manager
)
.is_ok());

store.add_block(a_blocks[1].to_arc_block()).unwrap().assert_added();

// Block A2
let txs = vec![txn_schema!(from: vec![a_outputs[1][1].clone()], to: vec![30 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut a_store,
&mut a_blocks,
&mut a_outputs,
txs,
Difficulty::from(3),
&consensus_manager
)
.is_ok());

store.add_block(a_blocks[2].to_arc_block()).unwrap().assert_added();
let a2_hash = a_blocks[2].hash().clone();

// Create "B" Chain
let mut b_store = create_store_with_consensus(consensus_manager.clone());
let mut b_blocks = vec![blocks[0].clone()];
let mut b_outputs = vec![outputs[0].clone()];

// Block B1
let txs = vec![txn_schema!(from: vec![b_outputs[0][0].clone()], to: vec![50 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut b_store,
&mut b_blocks,
&mut b_outputs,
txs,
Difficulty::from(2),
&consensus_manager
)
.is_ok());

store.add_block(b_blocks[1].to_arc_block()).unwrap().assert_orphaned();
let b1_hash = b_blocks[1].hash().clone();

// check that B1 is in orphan tips
let orphan_tip_b1 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&b1_hash)
.unwrap();
assert!(orphan_tip_b1.is_some());
assert_eq!(orphan_tip_b1.unwrap().hash(), &b1_hash);

// Block B2
let txs = vec![txn_schema!(from: vec![b_outputs[1][0].clone()], to: vec![40 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut b_store,
&mut b_blocks,
&mut b_outputs,
txs,
Difficulty::from(4),
&consensus_manager
)
.is_ok());

store.add_block(b_blocks[2].to_arc_block()).unwrap().assert_reorg(2, 2);
let b2_hash = b_blocks[2].hash().clone();

// check that A2 is now in the orphan chain tip db
let orphan_tip_a2 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&a2_hash)
.unwrap();
assert!(orphan_tip_a2.is_some());
assert_eq!(orphan_tip_a2.unwrap().hash(), &a2_hash);

// check that B1 was removed from orphan chain tips
let orphan_tip_b1 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&b1_hash)
.unwrap();
assert!(orphan_tip_b1.is_none());

// Block A3
let txs = vec![txn_schema!(from: vec![a_outputs[2][0].clone()], to: vec![25 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut a_store,
&mut a_blocks,
&mut a_outputs,
txs,
Difficulty::from(5), // A chain accumulated difficulty 9
&consensus_manager,
)
.is_ok());

store.add_block(a_blocks[3].to_arc_block()).unwrap().assert_reorg(3, 2);
let a3_hash = a_blocks[3].hash().clone();

// check that B2 is now in the orphan chain tip db
let orphan_tip_b2 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&b2_hash)
.unwrap();
assert!(orphan_tip_b2.is_some());
assert_eq!(orphan_tip_b2.unwrap().hash(), &b2_hash);

// check that A2 was removed from orphan chain tips
let orphan_tip_a2 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&a2_hash)
.unwrap();
assert!(orphan_tip_a2.is_none());

// Block B3
let txs = vec![txn_schema!(from: vec![b_outputs[2][0].clone()], to: vec![30 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut b_store,
&mut b_blocks,
&mut b_outputs,
txs,
Difficulty::from(1), // B chain accumulated difficulty 7
&consensus_manager
)
.is_ok());

store.add_block(b_blocks[3].to_arc_block()).unwrap().assert_orphaned();
let b3_hash = b_blocks[3].hash().clone();

// Block B4
let txs = vec![txn_schema!(from: vec![b_outputs[3][0].clone()], to: vec![20 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut b_store,
&mut b_blocks,
&mut b_outputs,
txs,
Difficulty::from(5), // B chain accumulated difficulty 12
&consensus_manager
)
.is_ok());

store.add_block(b_blocks[4].to_arc_block()).unwrap().assert_reorg(4, 3);
let b4_hash = b_blocks[4].hash().clone();

// check that A3 is now in the orphan chain tip db
let orphan_tip_a3 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&a3_hash)
.unwrap();
assert!(orphan_tip_a3.is_some());
assert_eq!(orphan_tip_a3.unwrap().hash(), &a3_hash);

// check that B3 was removed from orphan chain tips
let orphan_tip_b3 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&b3_hash)
.unwrap();
assert!(orphan_tip_b3.is_none());

// Block A4
let txs = vec![txn_schema!(from: vec![a_outputs[3][0].clone()], to: vec![20 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut a_store,
&mut a_blocks,
&mut a_outputs,
txs,
Difficulty::from(2), // A chain accumulated difficulty 11
&consensus_manager
)
.is_ok());

store.add_block(a_blocks[4].to_arc_block()).unwrap().assert_orphaned();

// Block A5
let txs = vec![txn_schema!(from: vec![a_outputs[4][0].clone()], to: vec![10 * T])];
assert!(generate_new_block_with_achieved_difficulty(
&mut a_store,
&mut a_blocks,
&mut a_outputs,
txs,
Difficulty::from(4), // A chain accumulated difficulty 15
&consensus_manager
)
.is_ok());

store.add_block(a_blocks[5].to_arc_block()).unwrap().assert_reorg(5, 4);

// check that B4 is now in the orphan chain tip db
let orphan_tip_b4 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&b4_hash)
.unwrap();
assert!(orphan_tip_b4.is_some());
assert_eq!(orphan_tip_b4.unwrap().hash(), &b4_hash);

// check that A3 was removed from orphan chain tips
let orphan_tip_a3 = store
.db_read_access()
.unwrap()
.fetch_orphan_chain_tip_by_hash(&a3_hash)
.unwrap();
assert!(orphan_tip_a3.is_none());

// Check that B1 - B4 are orphans
assert!(store.fetch_orphan(b_blocks[1].hash().clone()).is_ok()); // B1
assert!(store.fetch_orphan(b_blocks[2].hash().clone()).is_ok()); // B2
assert!(store.fetch_orphan(b_blocks[3].hash().clone()).is_ok()); // B3
assert!(store.fetch_orphan(b_blocks[4].hash().clone()).is_ok()); // B4

// And blocks A1 - A5 are not
assert!(store.fetch_orphan(a_blocks[1].hash().clone()).is_err()); // A1
assert!(store.fetch_orphan(a_blocks[2].hash().clone()).is_err()); // A2
assert!(store.fetch_orphan(a_blocks[3].hash().clone()).is_err()); // A3
assert!(store.fetch_orphan(a_blocks[4].hash().clone()).is_err()); // A4
assert!(store.fetch_orphan(a_blocks[5].hash().clone()).is_err()); // A5
}

#[test]
fn handle_reorg_with_no_removed_blocks() {
// GB --> A1
Expand Down

0 comments on commit ef5df7f

Please sign in to comment.