Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 48150f2

Browse files
arkparbkchrandresilva
authored
Sync: validate block responses for required data (#5052)
* Less verbose state-db logging * Validate block responses for block bodies * Update client/network/src/protocol.rs Co-Authored-By: Bastian Köcher <[email protected]> * Added validation test * Disconnect on missing header as well * Typo Co-Authored-By: André Silva <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: André Silva <[email protected]>
1 parent 7c75157 commit 48150f2

File tree

6 files changed

+111
-24
lines changed

6 files changed

+111
-24
lines changed

client/network/src/protocol.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ mod rep {
131131
pub const BAD_PROTOCOL: Rep = Rep::new_fatal("Unsupported protocol");
132132
/// Peer role does not match (e.g. light peer connecting to another light peer).
133133
pub const BAD_ROLE: Rep = Rep::new_fatal("Unsupported role");
134+
/// Peer response data does not have requested bits.
135+
pub const BAD_RESPONSE: Rep = Rep::new(-(1 << 12), "Incomplete response");
134136
}
135137

136138
// Lock must always be taken in order declared here.
@@ -701,12 +703,14 @@ impl<B: BlockT, H: ExHashT> Protocol<B, H> {
701703
peer: PeerId,
702704
request: message::BlockRequest<B>
703705
) {
704-
trace!(target: "sync", "BlockRequest {} from {}: from {:?} to {:?} max {:?}",
706+
trace!(target: "sync", "BlockRequest {} from {}: from {:?} to {:?} max {:?} for {:?}",
705707
request.id,
706708
peer,
707709
request.from,
708710
request.to,
709-
request.max);
711+
request.max,
712+
request.fields,
713+
);
710714

711715
// sending block requests to the node that is unable to serve it is considered a bad behavior
712716
if !self.config.roles.is_full() {
@@ -754,6 +758,11 @@ impl<B: BlockT, H: ExHashT> Protocol<B, H> {
754758
message_queue: None,
755759
justification,
756760
};
761+
// Stop if we don't have requested block body
762+
if get_body && block_data.body.is_none() {
763+
trace!(target: "sync", "Missing data for block request.");
764+
break;
765+
}
757766
blocks.push(block_data);
758767
match request.direction {
759768
message::Direction::Ascending => id = BlockId::Number(number + One::one()),
@@ -784,7 +793,7 @@ impl<B: BlockT, H: ExHashT> Protocol<B, H> {
784793
request: message::BlockRequest<B>,
785794
response: message::BlockResponse<B>,
786795
) -> CustomMessageOutcome<B> {
787-
let blocks_range = match (
796+
let blocks_range = || match (
788797
response.blocks.first().and_then(|b| b.header.as_ref().map(|h| h.number())),
789798
response.blocks.last().and_then(|b| b.header.as_ref().map(|h| h.number())),
790799
) {
@@ -796,7 +805,7 @@ impl<B: BlockT, H: ExHashT> Protocol<B, H> {
796805
response.id,
797806
peer,
798807
response.blocks.len(),
799-
blocks_range
808+
blocks_range(),
800809
);
801810

802811
if request.fields == message::BlockAttributes::JUSTIFICATION {
@@ -811,6 +820,20 @@ impl<B: BlockT, H: ExHashT> Protocol<B, H> {
811820
}
812821
}
813822
} else {
823+
// Validate fields against the request.
824+
if request.fields.contains(message::BlockAttributes::HEADER) && response.blocks.iter().any(|b| b.header.is_none()) {
825+
self.behaviour.disconnect_peer(&peer);
826+
self.peerset_handle.report_peer(peer, rep::BAD_RESPONSE);
827+
trace!(target: "sync", "Missing header for a block");
828+
return CustomMessageOutcome::None
829+
}
830+
if request.fields.contains(message::BlockAttributes::BODY) && response.blocks.iter().any(|b| b.body.is_none()) {
831+
self.behaviour.disconnect_peer(&peer);
832+
self.peerset_handle.report_peer(peer, rep::BAD_RESPONSE);
833+
trace!(target: "sync", "Missing body for a block");
834+
return CustomMessageOutcome::None
835+
}
836+
814837
match self.sync.on_block_data(peer, Some(request), response) {
815838
Ok(sync::OnBlockData::Import(origin, blocks)) =>
816839
CustomMessageOutcome::BlockImport(origin, blocks),

client/network/src/protocol/sync.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ impl<B: BlockT> ChainSync<B> {
751751
| PeerSyncState::DownloadingFinalityProof(..) => Vec::new()
752752
}
753753
} else {
754-
// When request.is_none() just accept blocks
754+
// When request.is_none() this is a block announcement. Just accept blocks.
755755
blocks.into_iter().map(|b| {
756756
IncomingBlock {
757757
hash: b.hash,

client/network/test/src/lib.rs

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ impl<D> Peer<D> {
237237
where F: FnMut(BlockBuilder<Block, PeersFullClient, substrate_test_runtime_client::Backend>) -> Block
238238
{
239239
let best_hash = self.client.info().best_hash;
240-
self.generate_blocks_at(BlockId::Hash(best_hash), count, origin, edit_block)
240+
self.generate_blocks_at(BlockId::Hash(best_hash), count, origin, edit_block, false)
241241
}
242242

243243
/// Add blocks to the peer -- edit the block before adding. The chain will
@@ -247,7 +247,8 @@ impl<D> Peer<D> {
247247
at: BlockId<Block>,
248248
count: usize,
249249
origin: BlockOrigin,
250-
mut edit_block: F
250+
mut edit_block: F,
251+
headers_only: bool,
251252
) -> H256 where F: FnMut(BlockBuilder<Block, PeersFullClient, substrate_test_runtime_client::Backend>) -> Block {
252253
let full_client = self.client.as_full()
253254
.expect("blocks could only be generated by full clients");
@@ -272,7 +273,7 @@ impl<D> Peer<D> {
272273
origin,
273274
header.clone(),
274275
None,
275-
Some(block.extrinsics)
276+
if headers_only { None } else { Some(block.extrinsics) },
276277
).unwrap();
277278
let cache = if let Some(cache) = cache {
278279
cache.into_iter().collect()
@@ -294,28 +295,46 @@ impl<D> Peer<D> {
294295
self.push_blocks_at(BlockId::Hash(best_hash), count, with_tx)
295296
}
296297

298+
/// Push blocks to the peer (simplified: with or without a TX)
299+
pub fn push_headers(&mut self, count: usize) -> H256 {
300+
let best_hash = self.client.info().best_hash;
301+
self.generate_tx_blocks_at(BlockId::Hash(best_hash), count, false, true)
302+
}
303+
297304
/// Push blocks to the peer (simplified: with or without a TX) starting from
298305
/// given hash.
299306
pub fn push_blocks_at(&mut self, at: BlockId<Block>, count: usize, with_tx: bool) -> H256 {
307+
self.generate_tx_blocks_at(at, count, with_tx, false)
308+
}
309+
310+
/// Push blocks/headers to the peer (simplified: with or without a TX) starting from
311+
/// given hash.
312+
fn generate_tx_blocks_at(&mut self, at: BlockId<Block>, count: usize, with_tx: bool, headers_only:bool) -> H256 {
300313
let mut nonce = 0;
301314
if with_tx {
302-
self.generate_blocks_at(at, count, BlockOrigin::File, |mut builder| {
303-
let transfer = Transfer {
304-
from: AccountKeyring::Alice.into(),
305-
to: AccountKeyring::Alice.into(),
306-
amount: 1,
307-
nonce,
308-
};
309-
builder.push(transfer.into_signed_tx()).unwrap();
310-
nonce = nonce + 1;
311-
builder.build().unwrap().block
312-
})
315+
self.generate_blocks_at(
316+
at,
317+
count,
318+
BlockOrigin::File, |mut builder| {
319+
let transfer = Transfer {
320+
from: AccountKeyring::Alice.into(),
321+
to: AccountKeyring::Alice.into(),
322+
amount: 1,
323+
nonce,
324+
};
325+
builder.push(transfer.into_signed_tx()).unwrap();
326+
nonce = nonce + 1;
327+
builder.build().unwrap().block
328+
},
329+
headers_only
330+
)
313331
} else {
314332
self.generate_blocks_at(
315333
at,
316334
count,
317335
BlockOrigin::File,
318336
|builder| builder.build().unwrap().block,
337+
headers_only,
319338
)
320339
}
321340
}
@@ -748,13 +767,37 @@ pub trait TestNetFactory: Sized {
748767
Async::Ready(())
749768
}
750769

770+
/// Polls the testnet until theres' no activiy of any kind.
771+
///
772+
/// Must be executed in a task context.
773+
fn poll_until_idle(&mut self) -> Async<()> {
774+
self.poll();
775+
776+
for peer in self.peers().iter() {
777+
if peer.is_major_syncing() || peer.network.num_queued_blocks() != 0 {
778+
return Async::NotReady
779+
}
780+
if peer.network.num_sync_requests() != 0 {
781+
return Async::NotReady
782+
}
783+
}
784+
Async::Ready(())
785+
}
786+
751787
/// Blocks the current thread until we are sync'ed.
752788
///
753789
/// Calls `poll_until_sync` repeatedly with the runtime passed as parameter.
754790
fn block_until_sync(&mut self, runtime: &mut tokio::runtime::current_thread::Runtime) {
755791
runtime.block_on(futures::future::poll_fn::<(), (), _>(|| Ok(self.poll_until_sync()))).unwrap();
756792
}
757793

794+
/// Blocks the current thread until there are no pending packets.
795+
///
796+
/// Calls `poll_until_idle` repeatedly with the runtime passed as parameter.
797+
fn block_until_idle(&mut self, runtime: &mut tokio::runtime::current_thread::Runtime) {
798+
runtime.block_on(futures::future::poll_fn::<(), (), _>(|| Ok(self.poll_until_idle()))).unwrap();
799+
}
800+
758801
/// Polls the testnet. Processes all the pending actions and returns `NotReady`.
759802
fn poll(&mut self) {
760803
self.mut_peers(|peers| {

client/network/test/src/sync.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,3 +660,24 @@ fn does_not_sync_announced_old_best_block() {
660660
})).unwrap();
661661
assert!(!net.peer(1).is_major_syncing());
662662
}
663+
664+
#[test]
665+
fn full_sync_requires_block_body() {
666+
// Check that we don't sync headers-only in full mode.
667+
let _ = ::env_logger::try_init();
668+
let mut runtime = current_thread::Runtime::new().unwrap();
669+
let mut net = TestNet::new(2);
670+
671+
net.peer(0).push_headers(1);
672+
// Wait for nodes to connect
673+
runtime.block_on(futures::future::poll_fn::<(), (), _>(|| -> Result<_, ()> {
674+
net.poll();
675+
if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 {
676+
Ok(Async::NotReady)
677+
} else {
678+
Ok(Async::Ready(()))
679+
}
680+
})).unwrap();
681+
net.block_until_idle(&mut runtime);
682+
assert_eq!(net.peer(1).client.info().best_number, 0);
683+
}

client/state-db/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ impl<BlockHash: Hash, Key: Hash> StateDbSync<BlockHash, Key> {
340340
{
341341
let refs = self.pinned.entry(hash.clone()).or_default();
342342
if *refs == 0 {
343-
trace!(target: "state-db", "Pinned block: {:?}", hash);
343+
trace!(target: "state-db-pin", "Pinned block: {:?}", hash);
344344
self.non_canonical.pin(hash);
345345
}
346346
*refs += 1;
@@ -357,11 +357,11 @@ impl<BlockHash: Hash, Key: Hash> StateDbSync<BlockHash, Key> {
357357
Entry::Occupied(mut entry) => {
358358
*entry.get_mut() -= 1;
359359
if *entry.get() == 0 {
360-
trace!(target: "state-db", "Unpinned block: {:?}", hash);
360+
trace!(target: "state-db-pin", "Unpinned block: {:?}", hash);
361361
entry.remove();
362362
self.non_canonical.unpin(hash);
363363
} else {
364-
trace!(target: "state-db", "Releasing reference for {:?}", hash);
364+
trace!(target: "state-db-pin", "Releasing reference for {:?}", hash);
365365
}
366366
},
367367
Entry::Vacant(_) => {},

client/state-db/src/noncanonical.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
436436
while let Some(hash) = parent {
437437
let refs = self.pinned.entry(hash.clone()).or_default();
438438
if *refs == 0 {
439-
trace!(target: "state-db", "Pinned non-canon block: {:?}", hash);
439+
trace!(target: "state-db-pin", "Pinned non-canon block: {:?}", hash);
440440
}
441441
*refs += 1;
442442
parent = self.parents.get(hash);
@@ -455,7 +455,7 @@ impl<BlockHash: Hash, Key: Hash> NonCanonicalOverlay<BlockHash, Key> {
455455
if *entry.get() == 0 {
456456
entry.remove();
457457
if let Some(inserted) = self.pinned_insertions.remove(&hash) {
458-
trace!(target: "state-db", "Discarding unpinned non-canon block: {:?}", hash);
458+
trace!(target: "state-db-pin", "Discarding unpinned non-canon block: {:?}", hash);
459459
discard_values(&mut self.values, inserted);
460460
self.parents.remove(&hash);
461461
}

0 commit comments

Comments
 (0)