-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(l1): getBlockHeaders eth capability test #989
Changes from 30 commits
a526c10
574299b
f23077e
122778d
7a4f9cf
5ae41fe
66b0bd8
6a5f580
a6ff7cb
0d64f5f
4b423a2
fd7db20
3930a3f
779093d
9967853
0d228c1
5efade9
f55c38f
7960aba
079d419
c2fa0d3
b4c450b
b52fde2
c04d319
ec4be28
8690db1
4031595
40c7d61
ed5560c
81df35a
d457d5f
a8c108c
f3933b8
71bcded
afa055e
47fa021
72716de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,19 +45,25 @@ impl RLPDecode for bool { | |
|
||
impl RLPDecode for u8 { | ||
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> { | ||
if rlp.is_empty() { | ||
return Err(RLPDecodeError::InvalidLength); | ||
} | ||
|
||
match rlp[0] { | ||
let first_byte = rlp.first().ok_or(RLPDecodeError::InvalidLength)?; | ||
match first_byte { | ||
// Single byte in the range [0x00, 0x7f] | ||
0..=0x7f => Ok((rlp[0], &rlp[1..])), | ||
0..=0x7f => { | ||
let rest = rlp.get(1..).ok_or(RLPDecodeError::MalformedData)?; | ||
Ok((*first_byte, rest)) | ||
} | ||
|
||
// RLP_NULL represents zero | ||
RLP_NULL => Ok((0, &rlp[1..])), | ||
&RLP_NULL => { | ||
let rest = rlp.get(1..).ok_or(RLPDecodeError::MalformedData)?; | ||
Ok((0, rest)) | ||
} | ||
|
||
// Two bytes, where the first byte is RLP_NULL + 1 | ||
x if rlp.len() >= 2 && x == RLP_NULL + 1 => Ok((rlp[1], &rlp[2..])), | ||
x if rlp.len() >= 2 && *x == RLP_NULL + 1 => { | ||
let rest = rlp.get(2..).ok_or(RLPDecodeError::MalformedData)?; | ||
Ok((rlp[1], rest)) | ||
} | ||
|
||
// Any other case is invalid for u8 | ||
_ => Err(RLPDecodeError::MalformedData), | ||
|
@@ -330,6 +336,34 @@ impl<T1: RLPDecode, T2: RLPDecode, T3: RLPDecode> RLPDecode for (T1, T2, T3) { | |
} | ||
} | ||
|
||
impl< | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: a small comment stating that this decodes a 4-element list as a tuple would be helpful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
T1: RLPDecode + std::fmt::Debug, | ||
T2: RLPDecode + std::fmt::Debug, | ||
T3: RLPDecode + std::fmt::Debug, | ||
T4: RLPDecode + std::fmt::Debug, | ||
> RLPDecode for (T1, T2, T3, T4) | ||
{ | ||
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> { | ||
if rlp.is_empty() { | ||
return Err(RLPDecodeError::InvalidLength); | ||
} | ||
let (is_list, payload, input_rest) = decode_rlp_item(rlp)?; | ||
if !is_list { | ||
return Err(RLPDecodeError::MalformedData); | ||
} | ||
let (first, first_rest) = T1::decode_unfinished(payload)?; | ||
let (second, second_rest) = T2::decode_unfinished(first_rest)?; | ||
let (third, third_rest) = T3::decode_unfinished(second_rest)?; | ||
let (fourth, fourth_rest) = T4::decode_unfinished(third_rest)?; | ||
// check that there is no more data to decode after the fourth element. | ||
if !fourth_rest.is_empty() { | ||
return Err(RLPDecodeError::MalformedData); | ||
} | ||
|
||
Ok(((first, second, third, fourth), input_rest)) | ||
} | ||
} | ||
|
||
/// Decodes an RLP item from a slice of bytes. | ||
/// It returns a 3-element tuple with the following elements: | ||
/// - A boolean indicating if the item is a list or not. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,14 @@ use ethereum_rust_rlp::{ | |
error::{RLPDecodeError, RLPEncodeError}, | ||
structs::{Decoder, Encoder}, | ||
}; | ||
use ethereum_rust_storage::Store; | ||
use snap::raw::Decoder as SnappyDecoder; | ||
|
||
use crate::rlpx::{message::RLPxMessage, utils::snappy_encode}; | ||
|
||
pub const HASH_FIRST_BYTE_DECODER: u8 = 160; | ||
|
||
#[derive(Debug, PartialEq, Eq)] | ||
#[derive(Debug, PartialEq, Eq, Clone)] | ||
pub enum HashOrNumber { | ||
Hash(BlockHash), | ||
Number(BlockNumber), | ||
|
@@ -55,13 +56,16 @@ impl RLPDecode for HashOrNumber { | |
pub(crate) struct GetBlockHeaders { | ||
// id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response | ||
// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages | ||
id: u64, | ||
startblock: HashOrNumber, | ||
limit: u64, | ||
skip: u64, | ||
reverse: bool, | ||
pub id: u64, | ||
pub startblock: HashOrNumber, | ||
pub limit: u64, | ||
pub skip: u64, | ||
pub reverse: bool, | ||
} | ||
|
||
// Limit taken from here: https://github.com/ethereum/go-ethereum/blob/20bf543a64d7c2a590b18a1e1d907cae65707013/eth/protocols/eth/handler.go#L40 | ||
pub const BLOCK_HEADER_LIMIT: u64 = 1024; | ||
|
||
impl GetBlockHeaders { | ||
pub fn new(id: u64, startblock: HashOrNumber, limit: u64, skip: u64, reverse: bool) -> Self { | ||
Self { | ||
|
@@ -72,19 +76,56 @@ impl GetBlockHeaders { | |
reverse, | ||
} | ||
} | ||
pub fn fetch_headers(&self, storage: &Store) -> Vec<BlockHeader> { | ||
let start_block = match self.startblock { | ||
// Check we have the given block hash and fetch its number | ||
HashOrNumber::Hash(block_hash) => storage.get_block_number(block_hash).ok().flatten(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the other PR here, we should return an empty response + log if error. |
||
// Don't check if the block number is available | ||
// because if it it's not, the loop below will | ||
// break early and return an empty vec. | ||
HashOrNumber::Number(block_num) => Some(block_num), | ||
}; | ||
|
||
let mut headers = vec![]; | ||
|
||
if let Some(start_block) = start_block { | ||
let mut current_block = start_block as i64; | ||
let block_skip = if self.reverse { | ||
-((self.skip + 1) as i64) | ||
} else { | ||
(self.skip + 1) as i64 | ||
}; | ||
let limit = if self.limit > BLOCK_HEADER_LIMIT { | ||
BLOCK_HEADER_LIMIT | ||
} else { | ||
self.limit | ||
}; | ||
for _ in 0..limit { | ||
let Some(block_header) = storage | ||
.get_block_header(current_block as u64) | ||
.ok() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
.flatten() | ||
else { | ||
break; | ||
}; | ||
headers.push(block_header); | ||
current_block += block_skip | ||
} | ||
} | ||
headers | ||
} | ||
} | ||
|
||
impl RLPxMessage for GetBlockHeaders { | ||
fn encode(&self, buf: &mut dyn BufMut) -> Result<(), RLPEncodeError> { | ||
let mut encoded_data = vec![]; | ||
let limit = self.limit; | ||
let skip = self.skip; | ||
let reverse = self.reverse as u8; | ||
Encoder::new(&mut encoded_data) | ||
.encode_field(&self.id) | ||
.encode_field(&self.startblock) | ||
.encode_field(&self.limit) | ||
.encode_field(&self.skip) | ||
.encode_field(&self.reverse) | ||
.encode_field(&(self.startblock.clone(), limit, skip, reverse)) | ||
.finish(); | ||
|
||
let msg_data = snappy_encode(encoded_data)?; | ||
buf.put_slice(&msg_data); | ||
Ok(()) | ||
|
@@ -97,21 +138,19 @@ impl RLPxMessage for GetBlockHeaders { | |
.map_err(|e| RLPDecodeError::Custom(e.to_string()))?; | ||
let decoder = Decoder::new(&decompressed_data)?; | ||
let (id, decoder): (u64, _) = decoder.decode_field("request-id")?; | ||
let (startblock, decoder): (HashOrNumber, _) = decoder.decode_field("startblock")?; | ||
let (limit, decoder): (u64, _) = decoder.decode_field("limit")?; | ||
let (skip, decoder): (u64, _) = decoder.decode_field("skip")?; | ||
let (reverse, _): (bool, _) = decoder.decode_field("reverse")?; | ||
|
||
Ok(Self::new(id, startblock, limit, skip, reverse)) | ||
let ((start_block, limit, skip, reverse), _): ((HashOrNumber, u64, u64, bool), _) = | ||
decoder.decode_field("get headers request params")?; | ||
Ok(Self::new(id, start_block, limit, skip, reverse)) | ||
} | ||
} | ||
|
||
// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#blockheaders-0x04 | ||
#[derive(Debug)] | ||
pub(crate) struct BlockHeaders { | ||
// id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response | ||
// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages | ||
id: u64, | ||
block_headers: Vec<BlockHeader>, | ||
pub id: u64, | ||
pub block_headers: Vec<BlockHeader>, | ||
} | ||
|
||
impl BlockHeaders { | ||
|
@@ -123,11 +162,13 @@ impl BlockHeaders { | |
impl RLPxMessage for BlockHeaders { | ||
fn encode(&self, buf: &mut dyn BufMut) -> Result<(), RLPEncodeError> { | ||
let mut encoded_data = vec![]; | ||
// Each message is encoded with its own | ||
// message identifier (code). | ||
// Go ethereum reference: https://github.com/ethereum/go-ethereum/blob/20bf543a64d7c2a590b18a1e1d907cae65707013/p2p/transport.go#L94 | ||
Encoder::new(&mut encoded_data) | ||
.encode_field(&self.id) | ||
.encode_field(&self.block_headers) | ||
.finish(); | ||
|
||
let msg_data = snappy_encode(encoded_data)?; | ||
buf.put_slice(&msg_data); | ||
Ok(()) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already fixed by
apply_fork_choice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How so? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apply_fork_choice
in line 157 sets the last block imported as the head, which makes all its ancestor blocks canonical. I'd check removing this as Fede suggests and checking everything still works.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed!