-
Notifications
You must be signed in to change notification settings - Fork 252
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
fix(alloy-eips): SimpleCoder::decode_one()
should return Ok(None)
#1818
fix(alloy-eips): SimpleCoder::decode_one()
should return Ok(None)
#1818
Conversation
c89713b
to
7baf239
Compare
@@ -434,6 +436,17 @@ mod tests { | |||
assert_eq!(decoded, data); | |||
} | |||
|
|||
#[test] | |||
fn big_ingestion_strategy() { | |||
let data = vec![1u8; 126_945]; |
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.
Blob has size of 128 * 1024
bytes. First 32 bytes are used as length, thus leaving 4095 FEs / 4096 FEs. This constant was calculated as 128 * 1024 - 32 - 4095
(4095
zeros in each FE).
crates/eips/src/eip4844/builder.rs
Outdated
if blobs | ||
.iter() | ||
.flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES).map(WholeFe::new)) | ||
.any(|fe| fe.is_none()) | ||
{ | ||
return None; | ||
} | ||
|
||
let mut fes = blobs | ||
.iter() | ||
.flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES).map(WholeFe::new_unchecked)); |
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.
Previously it didn't work correctly because .map(WholeFe::new)).map(Option::unwrap)
was executed lazily (when decode_one()
reached the wrong WholeFe
, it would panic).
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.
I decided to check this in a non-lazy way to do not pass Option<Option<Option<Option<WholeFe>>
to decode_one()
.
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.
tysm for looking into this,
all of this looks fine to me, although not an expert when it comes to the internals, but these changes match with what I discovered when I looked into the issue earlier
some nits
crates/eips/src/eip4844/utils.rs
Outdated
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.
no functional changes here, right?
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.
I could leave it u64
, but then I'd have to make as usize
in all usages, which isn't very convenient. Maybe it makes sense to introduce FIELD_ELEMENT_BYTES_USIZE: usize = FIELD_ELEMENT_BYTES as usize
? Yes, there are no other functional changes.
let last_unused_blob_index = self.fe / FIELD_ELEMENTS_PER_BLOB as usize; | ||
self.blobs.get_mut(last_unused_blob_index).expect("never empty") |
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 makes sense
let Some(first) = fes.next() else { | ||
return Ok(None); | ||
}; |
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 also makes sense, because before this would always return an error for iters len > 1, right?
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 returned an error when there was no more data left in the iterator. but that's a little different from what we want from decode_all().
crates/eips/src/eip4844/builder.rs
Outdated
if !matches!(blobs.len(), 1..=MAX_BLOBS_PER_BLOCK) { | ||
return None; | ||
} |
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 restriction will be problematic soon, because this constant should no longer be used, so I'd rather remove this check entirely
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.
what's the problem? if the number of blobs is increased, we just update the constants and everything will work.
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.
the max number of blobs will be decoupled from this constant ethereum/EIPs#9129
@klkvr should we use the eip7840::BlobParams::cancun()
for now or remove this entirely, I guess the proper way of doing this would be to pass around the max_block setting but this seems a bit much...
ref https://github.com/alloy-rs/alloy/pull/1828/files
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.
yeah don't think we need this check
custom implementations can store the constant in coder if needed
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.
Ok, I'll remove the use of MAX_BLOBS_PER_BLOCK
in this place and here for now:
alloy/crates/eips/src/eip4844/builder.rs
Lines 206 to 209 in 8ab278c
// if there are too many bytes | |
if num_bytes > BYTES_PER_BLOB * MAX_BLOBS_PER_BLOCK { | |
return Err(()); | |
} |
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.
We could then do a check that blobs: &[Blob]
is not empty and that num_bytes
does not exceed 2 MiB (it's limit of 16 blobs against the current 6 blobs and is quite normal practice to defend against attacks of allocating too much memory).
if blobs | ||
.iter() | ||
.flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES_USIZE).map(WholeFe::new)) | ||
.any(|fe| fe.is_none()) | ||
{ | ||
return None; | ||
} | ||
|
||
let mut fes = blobs | ||
.iter() | ||
.flat_map(|blob| blob.chunks(FIELD_ELEMENT_BYTES_USIZE).map(WholeFe::new_unchecked)); |
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.
yeah as pointed out, this overhead should be fine
Motivation
Resolves #1388
For example you want to decode 4096 FEs. In this case the first time
decode_one()
will return data, but the second time (because ofloop
) it will returnErr(())
and it will fail the wholedecode_all()
.alloy/crates/eips/src/eip4844/builder.rs
Lines 246 to 259 in 24ab11e
Solution
Returns
Ok(None)
if there is no data (empty iterator, length prefix is 0).PR Checklist