Skip to content

Commit

Permalink
Report reorg depth in block status (#2839)
Browse files Browse the repository at this point in the history
* Calculate reorg depth in BlockStatus::Reorg enum member

* rustfmt

* Fix reorg height calculation and implement reorg test

* rustfmt

* Report reorg depth in webhook payload

* Add optional depth field to the block webhook JSON reply
  • Loading branch information
eupn authored and hashmap committed May 24, 2019
1 parent 572be5d commit f9c5505
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 13 deletions.
5 changes: 4 additions & 1 deletion chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,15 +254,18 @@ impl Chain {
let is_more_work = head.is_some();

let mut is_next_block = false;
let mut reorg_depth = None;
if let Some(head) = head {
if head.prev_block_h == prev_head.last_block_h {
is_next_block = true;
} else {
reorg_depth = Some(prev_head.height.saturating_sub(head.height) + 1);
}
}

match (is_more_work, is_next_block) {
(true, true) => BlockStatus::Next,
(true, false) => BlockStatus::Reorg,
(true, false) => BlockStatus::Reorg(reorg_depth.unwrap_or(0)),
(false, _) => BlockStatus::Fork,
}
}
Expand Down
2 changes: 1 addition & 1 deletion chain/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,5 @@ pub enum BlockStatus {
Fork,
/// Block updates the chain head via a (potentially disruptive) "reorg".
/// Previous block was not our previous chain head.
Reorg,
Reorg(u64),
}
109 changes: 109 additions & 0 deletions chain/tests/mine_simple_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ use self::keychain::{ExtKeychain, ExtKeychainPath, Keychain};
use self::util::{RwLock, StopState};
use chrono::Duration;
use grin_chain as chain;
use grin_chain::{BlockStatus, ChainAdapter, Options};
use grin_core as core;
use grin_keychain as keychain;
use grin_util as util;
use std::cell::RefCell;
use std::fs;
use std::sync::Arc;

Expand All @@ -51,6 +53,41 @@ fn setup(dir_name: &str, genesis: Block) -> Chain {
.unwrap()
}

/// Adapter to retrieve last status
pub struct StatusAdapter {
pub last_status: RwLock<Option<BlockStatus>>,
}

impl StatusAdapter {
pub fn new(last_status: RwLock<Option<BlockStatus>>) -> Self {
StatusAdapter { last_status }
}
}

impl ChainAdapter for StatusAdapter {
fn block_accepted(&self, _b: &Block, status: BlockStatus, _opts: Options) {
*self.last_status.write() = Some(status);
}
}

/// Creates a `Chain` instance with `StatusAdapter` attached to it.
fn setup_with_status_adapter(dir_name: &str, genesis: Block, adapter: Arc<StatusAdapter>) -> Chain {
util::init_test_logger();
clean_output_dir(dir_name);
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
let chain = chain::Chain::init(
dir_name.to_string(),
adapter,
genesis,
pow::verify_size,
verifier_cache,
false,
)
.unwrap();

chain
}

#[test]
fn mine_empty_chain() {
global::set_mining_mode(ChainTypes::AutomatedTesting);
Expand Down Expand Up @@ -158,6 +195,78 @@ where
}
}

#[test]
// This test creates a reorg at REORG_DEPTH by mining a block with difficulty that
// exceeds original chain total difficulty.
//
// Illustration of reorg with NUM_BLOCKS_MAIN = 6 and REORG_DEPTH = 5:
//
// difficulty: 1 2 3 4 5 6
//
// / [ 2 ] - [ 3 ] - [ 4 ] - [ 5 ] - [ 6 ] <- original chain
// [ Genesis ] -[ 1 ]- *
// ^ \ [ 2' ] - ................................ <- reorg chain with depth 5
// |
// difficulty: 1 | 24
// |
// \----< Fork point and chain reorg
fn mine_reorg() {
// Test configuration
const NUM_BLOCKS_MAIN: u64 = 6; // Number of blocks to mine in main chain
const REORG_DEPTH: u64 = 5; // Number of blocks to be discarded from main chain after reorg

const DIR_NAME: &str = ".grin_reorg";
clean_output_dir(DIR_NAME);

global::set_mining_mode(ChainTypes::AutomatedTesting);
let kc = ExtKeychain::from_random_seed(false).unwrap();

let genesis = pow::mine_genesis_block().unwrap();
{
// Create chain that reports last block status
let mut last_status = RwLock::new(None);
let adapter = Arc::new(StatusAdapter::new(last_status));
let chain = setup_with_status_adapter(DIR_NAME, genesis.clone(), adapter.clone());

// Add blocks to main chain with gradually increasing difficulty
let mut prev = chain.head_header().unwrap();
for n in 1..=NUM_BLOCKS_MAIN {
let b = prepare_block(&kc, &prev, &chain, n);
prev = b.header.clone();
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
}

let head = chain.head_header().unwrap();
assert_eq!(head.height, NUM_BLOCKS_MAIN);
assert_eq!(head.hash(), prev.hash());

// Reorg chain should exceed main chain's total difficulty to be considered
let reorg_difficulty = head.total_difficulty().to_num();

// Create one block for reorg chain forking off NUM_BLOCKS_MAIN - REORG_DEPTH height
let fork_head = chain
.get_header_by_height(NUM_BLOCKS_MAIN - REORG_DEPTH)
.unwrap();
let b = prepare_fork_block(&kc, &fork_head, &chain, reorg_difficulty);
let reorg_head = b.header.clone();
chain.process_block(b, chain::Options::SKIP_POW).unwrap();

// Check that reorg is correctly reported in block status
assert_eq!(
*adapter.last_status.read(),
Some(BlockStatus::Reorg(REORG_DEPTH))
);

// Chain should be switched to the reorganized chain
let head = chain.head_header().unwrap();
assert_eq!(head.height, NUM_BLOCKS_MAIN - REORG_DEPTH + 1);
assert_eq!(head.hash(), reorg_head.hash());
}

// Cleanup chain directory
clean_output_dir(DIR_NAME);
}

#[test]
fn mine_forks() {
global::set_mining_mode(ChainTypes::AutomatedTesting);
Expand Down
9 changes: 7 additions & 2 deletions servers/src/common/adapters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,12 @@ impl ChainAdapter for ChainToPoolAndNetAdapter {
// Reconcile the txpool against the new block *after* we have broadcast it too our peers.
// This may be slow and we do not want to delay block propagation.
// We only want to reconcile the txpool against the new block *if* total work has increased.
if status == BlockStatus::Next || status == BlockStatus::Reorg {
let is_reorg = if let BlockStatus::Reorg(_) = status {
true
} else {
false
};
if status == BlockStatus::Next || is_reorg {
let mut tx_pool = self.tx_pool.write();

let _ = tx_pool.reconcile_block(b);
Expand All @@ -732,7 +737,7 @@ impl ChainAdapter for ChainToPoolAndNetAdapter {
tx_pool.truncate_reorg_cache(cutoff);
}

if status == BlockStatus::Reorg {
if is_reorg {
let _ = self.tx_pool.write().reconcile_reorg_cache(&b.header);
}
}
Expand Down
32 changes: 23 additions & 9 deletions servers/src/common/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,12 @@ impl NetEvents for EventLogger {
impl ChainEvents for EventLogger {
fn on_block_accepted(&self, block: &core::Block, status: &BlockStatus) {
match status {
BlockStatus::Reorg => {
BlockStatus::Reorg(depth) => {
warn!(
"block_accepted (REORG!): {:?} at {} (diff: {})",
"block_accepted (REORG!): {:?} at {} (depth: {}, diff: {})",
block.hash(),
block.header.height,
depth,
block.header.total_difficulty(),
);
}
Expand Down Expand Up @@ -261,16 +262,29 @@ impl WebHook {

impl ChainEvents for WebHook {
fn on_block_accepted(&self, block: &core::Block, status: &BlockStatus) {
let status = match status {
BlockStatus::Reorg => "reorg",
let status_str = match status {
BlockStatus::Reorg(_) => "reorg",
BlockStatus::Fork => "fork",
BlockStatus::Next => "head",
};
let payload = json!({
"hash": block.header.hash().to_hex(),
"status": status,
"data": block
});

// Add additional `depth` field to the JSON in case of reorg
let payload = if let BlockStatus::Reorg(depth) = status {
json!({
"hash": block.header.hash().to_hex(),
"status": status_str,
"data": block,

"depth": depth
})
} else {
json!({
"hash": block.header.hash().to_hex(),
"status": status_str,
"data": block
})
};

if !self.make_request(&payload, &self.block_accepted_url) {
error!(
"Failed to serialize block {} at height {}",
Expand Down

0 comments on commit f9c5505

Please sign in to comment.