Skip to content

Commit

Permalink
check_known now takes total_difficulty into consideration
Browse files Browse the repository at this point in the history
  • Loading branch information
antiochp committed May 4, 2020
1 parent 8a22fb5 commit edf4691
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 18 deletions.
49 changes: 31 additions & 18 deletions chain/src/pipe.rs
Expand Up @@ -45,12 +45,17 @@ pub struct BlockContext<'a> {
pub verifier_cache: Arc<RwLock<dyn VerifierCache>>,
}

// Check if we already know about this block for various reasons
// from cheapest to most expensive (delay hitting the db until last).
fn check_known(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Result<(), Error> {
check_known_head(header, ctx)?;
check_known_store(header, ctx)?;
Ok(())
// If this block has greater total difficulty than treat as unknown in current context.
// If it matches current chain head (latest or previous hash) then we know about it.
// If it exists in the local db then we know about it.
fn check_known(header: &BlockHeader, head: &Tip, ctx: &BlockContext<'_>) -> Result<(), Error> {
if header.total_difficulty() > head.total_difficulty {
Ok(())
} else {
check_known_head(header, head)?;
check_known_store(header, head, ctx)?;
Ok(())
}
}

// Validate only the proof of work in a block header.
Expand Down Expand Up @@ -86,15 +91,18 @@ pub fn process_block(b: &Block, ctx: &mut BlockContext<'_>) -> Result<Option<Tip
b.kernels().len(),
);

// Read current chain head from db via the batch.
// We use this for various operations later.
let head = ctx.batch.head()?;

// Check if we have already processed this block previously.
check_known(&b.header, ctx)?;
check_known(&b.header, &head, ctx)?;

// Quick pow validation. No point proceeding if this is invalid.
// We want to do this before we add the block to the orphan pool so we
// want to do this now and not later during header validation.
validate_pow_only(&b.header, ctx)?;

let head = ctx.batch.head()?;
let prev = prev_header_store(&b.header, &mut ctx.batch)?;

// Block is an orphan if we do not know about the previous full block.
Expand Down Expand Up @@ -231,9 +239,13 @@ pub fn process_block_header(header: &BlockHeader, ctx: &mut BlockContext<'_>) ->
// Check this header is not an orphan, we must know about the previous header to continue.
let prev_header = ctx.batch.get_previous_header(&header)?;

// Check if we know about the full block for this header.
if check_known(header, ctx).is_err() {
return Ok(());
// If we have already processed the full block for this header then done.
// Note: "already known" in this context is success so subsequent processing can continue.
{
let head = ctx.batch.head()?;
if check_known(header, &head, ctx).is_err() {
return Ok(());
}
}

// If we have not yet seen the full block then check if we have seen this header.
Expand Down Expand Up @@ -267,11 +279,9 @@ pub fn process_block_header(header: &BlockHeader, ctx: &mut BlockContext<'_>) ->
Ok(())
}

/// Quick in-memory check to fast-reject any block handled recently.
/// Keeps duplicates from the network in check.
/// Checks against the last_block_h and prev_block_h of the chain head.
fn check_known_head(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Result<(), Error> {
let head = ctx.batch.head()?;
/// Quick check to reject recently handled blocks.
/// Checks against last_block_h and prev_block_h of the chain head.
fn check_known_head(header: &BlockHeader, head: &Tip) -> Result<(), Error> {
let bh = header.hash();
if bh == head.last_block_h || bh == head.prev_block_h {
return Err(ErrorKind::Unfit("already known in head".to_string()).into());
Expand All @@ -280,10 +290,13 @@ fn check_known_head(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Result<
}

// Check if this block is in the store already.
fn check_known_store(header: &BlockHeader, ctx: &mut BlockContext<'_>) -> Result<(), Error> {
fn check_known_store(
header: &BlockHeader,
head: &Tip,
ctx: &BlockContext<'_>,
) -> Result<(), Error> {
match ctx.batch.block_exists(&header.hash()) {
Ok(true) => {
let head = ctx.batch.head()?;
if header.height < head.height.saturating_sub(50) {
// TODO - we flag this as an "abusive peer" but only in the case
// where we have the full block in our store.
Expand Down
81 changes: 81 additions & 0 deletions chain/tests/test_block_known.rs
@@ -0,0 +1,81 @@
// Copyright 2020 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod chain_test_helper;
use self::chain_test_helper::{clean_output_dir, init_chain, mine_chain};
use chain::ErrorKind;
use chain::Tip;
use grin_chain as chain;
use grin_core::core::hash::Hashed;
use grin_util as util;

#[test]
fn check_known() {
let chain_dir = ".grin.check_known";
util::init_test_logger();
clean_output_dir(chain_dir);

// mine some blocks
let (latest, genesis) = {
let chain = mine_chain(chain_dir, 3);
let genesis = chain
.get_block(&chain.get_header_by_height(0).unwrap().hash())
.unwrap();
let head = chain.head().unwrap();
let latest = chain.get_block(&head.last_block_h).unwrap();
(latest, genesis)
};

// attempt to reprocess latest block
{
let chain = init_chain(chain_dir, genesis.clone());
let res = chain.process_block(latest.clone(), chain::Options::NONE);
assert_eq!(
res.unwrap_err().kind(),
ErrorKind::Unfit("already known in head".to_string()).into()
);
}

// attempt to reprocess genesis block
{
let chain = init_chain(chain_dir, genesis.clone());
let res = chain.process_block(genesis.clone(), chain::Options::NONE);
assert_eq!(
res.unwrap_err().kind(),
ErrorKind::Unfit("already known in store".to_string()).into()
);
}

// reset chain head to earlier state
{
let chain = init_chain(chain_dir, genesis.clone());
let store = chain.store();
let batch = store.batch().unwrap();
let head_header = chain.head_header().unwrap();
let prev = batch.get_previous_header(&head_header).unwrap();
batch.save_body_head(&Tip::from_header(&prev)).unwrap();
batch.commit().unwrap();
}

// reprocess latest block and check the updated head
{
let chain = init_chain(chain_dir, genesis.clone());
let head = chain
.process_block(latest.clone(), chain::Options::NONE)
.unwrap();
assert_eq!(head, Some(Tip::from_header(&latest.header)));
}

clean_output_dir(chain_dir);
}

0 comments on commit edf4691

Please sign in to comment.