Skip to content

Commit 6a5e79d

Browse files
bowenwang1996chefsale
authored andcommitted
fix(chain): ban peer properly when receiving a bad block (#3029)
Currently we ban the peer who sends us the block if the block is bad. However, when we receive a block, we only validate its header before rebroadcasting it, which means that an honest peer can relay a bad block and we should not ban them. Otherwise a malicious attack can use this as an opportunity to cause peers to ban each other, which may lead to a network split. Test plan --------- * `test_not_ban_peer_for_invalid_block` * `test_ban_peer_for_invalid_block_header` * `test_not_process_height_twice`
1 parent a92eab4 commit 6a5e79d

15 files changed

Lines changed: 516 additions & 145 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chain/chain/src/chain.rs

Lines changed: 74 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::time::{Duration as TimeDuration, Instant};
55
use borsh::BorshSerialize;
66
use chrono::Duration;
77
use chrono::Utc;
8-
use log::{debug, error, info};
8+
use log::{debug, error, info, warn};
99
use rand::rngs::StdRng;
1010
use rand::seq::SliceRandom;
1111
use rand::SeedableRng;
@@ -400,9 +400,9 @@ impl Chain {
400400
pub fn save_block(&mut self, block: &Block) -> Result<(), Error> {
401401
let mut chain_store_update = ChainStoreUpdate::new(&mut self.store);
402402

403-
if !block.check_validity() {
403+
if let Err(e) = block.check_validity() {
404404
byzantine_assert!(false);
405-
return Err(ErrorKind::Other("Invalid block".into()).into());
405+
return Err(e.into());
406406
}
407407

408408
chain_store_update.save_block(block.clone());
@@ -421,6 +421,14 @@ impl Chain {
421421
});
422422
}
423423

424+
fn save_block_height_processed(&mut self, block_height: BlockHeight) -> Result<(), Error> {
425+
let mut chain_store_update = ChainStoreUpdate::new(&mut self.store);
426+
if !chain_store_update.is_height_processed(block_height)? {
427+
chain_store_update.save_block_height_processed(block_height);
428+
}
429+
Ok(())
430+
}
431+
424432
// GC CONTRACT
425433
// ===
426434
//
@@ -605,6 +613,13 @@ impl Chain {
605613
Ok(())
606614
}
607615

616+
/// Do Basic validation of a block upon receiving it. Check that header is valid
617+
/// and block is well-formed (various roots match).
618+
pub fn validate_block(&mut self, block: &Block) -> Result<(), Error> {
619+
self.process_block_header(&block.header(), |_| {})?;
620+
block.check_validity().map_err(|e| e.into())
621+
}
622+
608623
/// Process a block header received during "header first" propagation.
609624
pub fn process_block_header<F>(
610625
&mut self,
@@ -1020,10 +1035,6 @@ impl Chain {
10201035
{
10211036
near_metrics::inc_counter(&metrics::BLOCK_PROCESSED_TOTAL);
10221037

1023-
if block.chunks().len() != self.runtime_adapter.num_shards() as usize {
1024-
return Err(ErrorKind::IncorrectNumberOfChunkHeaders.into());
1025-
}
1026-
10271038
let prev_head = self.store.head()?;
10281039
let mut chain_update = ChainUpdate::new(
10291040
&mut self.store,
@@ -1035,9 +1046,11 @@ impl Chain {
10351046
self.doomslug_threshold_mode,
10361047
);
10371048
let maybe_new_head = chain_update.process_block(me, &block, &provenance, on_challenge);
1049+
let block_height = block.header().height();
10381050

10391051
match maybe_new_head {
10401052
Ok((head, needs_to_start_fetching_state)) => {
1053+
chain_update.chain_store_update.save_block_height_processed(block_height);
10411054
chain_update.commit()?;
10421055

10431056
if needs_to_start_fetching_state {
@@ -1079,62 +1092,64 @@ impl Chain {
10791092

10801093
Ok(head)
10811094
}
1082-
Err(e) => match e.kind() {
1083-
ErrorKind::Orphan => {
1084-
let tail_height = self.store.tail()?;
1085-
// we only add blocks that couldn't have been gc'ed to the orphan pool.
1086-
if block.header().height() >= tail_height {
1095+
Err(e) => {
1096+
match e.kind() {
1097+
ErrorKind::Orphan => {
1098+
let tail_height = self.store.tail()?;
1099+
// we only add blocks that couldn't have been gc'ed to the orphan pool.
1100+
if block_height >= tail_height {
1101+
let block_hash = *block.hash();
1102+
let orphan = Orphan { block, provenance, added: Instant::now() };
1103+
1104+
self.orphans.add(orphan);
1105+
1106+
debug!(
1107+
target: "chain",
1108+
"Process block: orphan: {:?}, # orphans {}{}",
1109+
block_hash,
1110+
self.orphans.len(),
1111+
if self.orphans.len_evicted() > 0 {
1112+
format!(", # evicted {}", self.orphans.len_evicted())
1113+
} else {
1114+
String::new()
1115+
},
1116+
);
1117+
}
1118+
}
1119+
ErrorKind::ChunksMissing(missing_chunks) => {
10871120
let block_hash = *block.hash();
1121+
block_misses_chunks(missing_chunks.clone());
10881122
let orphan = Orphan { block, provenance, added: Instant::now() };
10891123

1090-
self.orphans.add(orphan);
1124+
self.blocks_with_missing_chunks.add(orphan);
10911125

10921126
debug!(
10931127
target: "chain",
1094-
"Process block: orphan: {:?}, # orphans {}{}",
1095-
block_hash,
1096-
self.orphans.len(),
1097-
if self.orphans.len_evicted() > 0 {
1098-
format!(", # evicted {}", self.orphans.len_evicted())
1099-
} else {
1100-
String::new()
1101-
},
1128+
"Process block: missing chunks. Block hash: {:?}. Missing chunks: {:?}",
1129+
block_hash, missing_chunks,
11021130
);
11031131
}
1104-
Err(e)
1105-
}
1106-
ErrorKind::ChunksMissing(missing_chunks) => {
1107-
let block_hash = *block.hash();
1108-
block_misses_chunks(missing_chunks.clone());
1109-
let orphan = Orphan { block, provenance, added: Instant::now() };
1110-
1111-
self.blocks_with_missing_chunks.add(orphan);
1112-
1113-
debug!(
1114-
target: "chain",
1115-
"Process block: missing chunks. Block hash: {:?}. Missing chunks: {:?}",
1116-
block_hash, missing_chunks,
1117-
);
1118-
Err(e)
1119-
}
1120-
ErrorKind::EpochOutOfBounds => {
1121-
// Possibly block arrived before we finished processing all of the blocks for epoch before last.
1122-
// Or someone is attacking with invalid chain.
1123-
debug!(target: "chain", "Received block {}/{} ignored, as epoch is unknown", block.header().height(), block.hash());
1124-
Err(e)
1132+
ErrorKind::EpochOutOfBounds => {
1133+
// Possibly block arrived before we finished processing all of the blocks for epoch before last.
1134+
// Or someone is attacking with invalid chain.
1135+
debug!(target: "chain", "Received block {}/{} ignored, as epoch is unknown", block_height, block.hash());
1136+
}
1137+
ErrorKind::Unfit(ref msg) => {
1138+
debug!(
1139+
target: "chain",
1140+
"Block {} at {} is unfit at this time: {}",
1141+
block.hash(),
1142+
block_height,
1143+
msg
1144+
);
1145+
}
1146+
_ => {}
11251147
}
1126-
ErrorKind::Unfit(ref msg) => {
1127-
debug!(
1128-
target: "chain",
1129-
"Block {} at {} is unfit at this time: {}",
1130-
block.hash(),
1131-
block.header().height(),
1132-
msg
1133-
);
1134-
Err(ErrorKind::Unfit(msg.clone()).into())
1148+
if let Err(e) = self.save_block_height_processed(block_height) {
1149+
warn!(target: "chain", "Failed to save processed height {}: {}", block_height, e);
11351150
}
1136-
_ => Err(e),
1137-
},
1151+
Err(e)
1152+
}
11381153
}
11391154
}
11401155

@@ -2823,6 +2838,10 @@ impl<'a> ChainUpdate<'a> {
28232838
{
28242839
debug!(target: "chain", "Process block {} at {}, approvals: {}, me: {:?}", block.hash(), block.header().height(), block.header().num_approvals(), me);
28252840

2841+
if block.chunks().len() != self.runtime_adapter.num_shards() as usize {
2842+
return Err(ErrorKind::IncorrectNumberOfChunkHeaders.into());
2843+
}
2844+
28262845
// Check if we have already processed this block previously.
28272846
self.check_known(block.header().hash())?;
28282847

@@ -2888,9 +2907,9 @@ impl<'a> ChainUpdate<'a> {
28882907
return Err(ErrorKind::InvalidRandomnessBeaconOutput.into());
28892908
}
28902909

2891-
if !block.check_validity() {
2910+
if let Err(e) = block.check_validity() {
28922911
byzantine_assert!(false);
2893-
return Err(ErrorKind::Other("Invalid block".into()).into());
2912+
return Err(e.into());
28942913
}
28952914

28962915
let protocol_version =
@@ -3140,8 +3159,6 @@ impl<'a> ChainUpdate<'a> {
31403159
return Err(ErrorKind::InvalidApprovals.into());
31413160
};
31423161

3143-
self.runtime_adapter.verify_block_signature(header)?;
3144-
31453162
let stakes = self
31463163
.runtime_adapter
31473164
.get_epoch_block_approvers_ordered(header.prev_hash())?

chain/chain/src/error.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use chrono::{DateTime, Utc};
55
use failure::{Backtrace, Context, Fail};
66
use log::error;
77

8+
use near_primitives::block::BlockValidityError;
89
use near_primitives::challenge::{ChunkProofs, ChunkState};
910
use near_primitives::errors::{EpochError, StorageError};
1011
use near_primitives::hash::CryptoHash;
@@ -46,9 +47,6 @@ pub enum ErrorKind {
4647
/// Invalid block proposed signature.
4748
#[fail(display = "Invalid Block Proposer Signature")]
4849
InvalidBlockProposer,
49-
/// Invalid block confirmation signature.
50-
#[fail(display = "Invalid Block Confirmation Signature")]
51-
InvalidBlockConfirmation,
5250
/// Invalid state root hash.
5351
#[fail(display = "Invalid State Root Hash")]
5452
InvalidStateRoot,
@@ -76,6 +74,9 @@ pub enum ErrorKind {
7674
/// Invalid transactions in the block.
7775
#[fail(display = "Invalid Transactions")]
7876
InvalidTransactions,
77+
/// Invalid Challenge Root (doesn't match actual challenge)
78+
#[fail(display = "Invalid Challenge Root")]
79+
InvalidChallengeRoot,
7980
/// Invalid challenge (wrong signature or format).
8081
#[fail(display = "Invalid Challenge")]
8182
InvalidChallenge,
@@ -241,7 +242,6 @@ impl Error {
241242
| ErrorKind::InvalidBlockFutureTime(_)
242243
| ErrorKind::InvalidBlockHeight
243244
| ErrorKind::InvalidBlockProposer
244-
| ErrorKind::InvalidBlockConfirmation
245245
| ErrorKind::InvalidChunk
246246
| ErrorKind::InvalidChunkProofs(_)
247247
| ErrorKind::InvalidChunkState(_)
@@ -273,7 +273,8 @@ impl Error {
273273
| ErrorKind::InvalidStateRequest(_)
274274
| ErrorKind::InvalidRandomnessBeaconOutput
275275
| ErrorKind::InvalidBlockMerkleRoot
276-
| ErrorKind::NotAValidator => true,
276+
| ErrorKind::NotAValidator
277+
| ErrorKind::InvalidChallengeRoot => true,
277278
}
278279
}
279280

@@ -315,3 +316,17 @@ impl From<EpochError> for Error {
315316
.into()
316317
}
317318
}
319+
320+
impl From<BlockValidityError> for Error {
321+
fn from(error: BlockValidityError) -> Self {
322+
match error {
323+
BlockValidityError::InvalidStateRoot => ErrorKind::InvalidStateRoot,
324+
BlockValidityError::InvalidReceiptRoot => ErrorKind::InvalidChunkReceiptsRoot,
325+
BlockValidityError::InvalidTransactionRoot => ErrorKind::InvalidTxRoot,
326+
BlockValidityError::InvalidChunkHeaderRoot => ErrorKind::InvalidChunkHeadersRoot,
327+
BlockValidityError::InvalidNumChunksIncluded => ErrorKind::InvalidChunkMask,
328+
BlockValidityError::InvalidChallengeRoot => ErrorKind::InvalidChallengeRoot,
329+
}
330+
.into()
331+
}
332+
}

0 commit comments

Comments
 (0)