mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
Report reorg depth in block status (#2839)
* 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
This commit is contained in:
parent
572be5d264
commit
f9c5505e9f
5 changed files with 144 additions and 13 deletions
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
@ -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 {}",
|
||||
|
|
Loading…
Reference in a new issue