Skip to content

Commit 5ad8342

Browse files
authored
Block processing eip4844 (#3673)
* add eip4844 block processing * fix blob processing code * consensus logic fixes and cleanup * use safe arith
1 parent 29f2ec4 commit 5ad8342

File tree

9 files changed

+200
-2
lines changed

9 files changed

+200
-2
lines changed

consensus/state_processing/src/per_block_processing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub use self::verify_attester_slashing::{
1212
pub use self::verify_proposer_slashing::verify_proposer_slashing;
1313
pub use altair::sync_committee::process_sync_aggregate;
1414
pub use block_signature_verifier::{BlockSignatureVerifier, ParallelSignatureSets};
15+
pub use eip4844::eip4844::process_blob_kzg_commitments;
1516
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
1617
pub use process_operations::process_operations;
1718
pub use verify_attestation::{
@@ -24,6 +25,7 @@ pub use verify_exit::verify_exit;
2425

2526
pub mod altair;
2627
pub mod block_signature_verifier;
28+
pub mod eip4844;
2729
pub mod errors;
2830
mod is_valid_indexed_attestation;
2931
pub mod process_operations;
@@ -171,6 +173,8 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
171173
)?;
172174
}
173175

176+
process_blob_kzg_commitments(block.body())?;
177+
174178
Ok(())
175179
}
176180

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod eip4844;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use crate::BlockProcessingError;
2+
use eth2_hashing::hash_fixed;
3+
use itertools::{EitherOrBoth, Itertools};
4+
use safe_arith::SafeArith;
5+
use ssz::Decode;
6+
use ssz_types::VariableList;
7+
use types::consts::eip4844::{BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG};
8+
use types::{
9+
AbstractExecPayload, BeaconBlockBodyRef, EthSpec, ExecPayload, FullPayload, FullPayloadRef,
10+
KzgCommitment, Transaction, Transactions, VersionedHash,
11+
};
12+
13+
pub fn process_blob_kzg_commitments<T: EthSpec, Payload: AbstractExecPayload<T>>(
14+
block_body: BeaconBlockBodyRef<T, Payload>,
15+
) -> Result<(), BlockProcessingError> {
16+
if let (Ok(payload), Ok(kzg_commitments)) = (
17+
block_body.execution_payload(),
18+
block_body.blob_kzg_commitments(),
19+
) {
20+
if let Some(transactions) = payload.transactions() {
21+
if !verify_kzg_commitments_against_transactions::<T>(transactions, kzg_commitments)? {
22+
return Err(BlockProcessingError::BlobVersionHashMismatch);
23+
}
24+
}
25+
}
26+
27+
Ok(())
28+
}
29+
30+
pub fn verify_kzg_commitments_against_transactions<T: EthSpec>(
31+
transactions: &Transactions<T>,
32+
kzg_commitments: &VariableList<KzgCommitment, T::MaxBlobsPerBlock>,
33+
) -> Result<bool, BlockProcessingError> {
34+
let nested_iter = transactions
35+
.into_iter()
36+
.filter(|tx| {
37+
tx.get(0)
38+
.map(|tx_type| *tx_type == BLOB_TX_TYPE)
39+
.unwrap_or(false)
40+
})
41+
.map(|tx| tx_peek_blob_versioned_hashes::<T>(tx));
42+
43+
itertools::process_results(nested_iter, |iter| {
44+
let zipped_iter = iter
45+
.flatten()
46+
// Need to use `itertools::zip_longest` here because just zipping hides if one iter is shorter
47+
// and `itertools::zip_eq` panics.
48+
.zip_longest(kzg_commitments.into_iter())
49+
.enumerate()
50+
.map(|(index, next)| match next {
51+
EitherOrBoth::Both(hash, commitment) => Ok((hash?, commitment)),
52+
// The number of versioned hashes from the blob transactions exceeds the number of
53+
// commitments in the block.
54+
EitherOrBoth::Left(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch {
55+
commitments_processed_in_block: index,
56+
commitments_processed_in_transactions: index.safe_add(1)?,
57+
}),
58+
// The number of commitments in the block exceeds the number of versioned hashes
59+
// in the blob transactions.
60+
EitherOrBoth::Right(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch {
61+
commitments_processed_in_block: index.safe_add(1)?,
62+
commitments_processed_in_transactions: index,
63+
}),
64+
});
65+
66+
itertools::process_results(zipped_iter, |mut iter| {
67+
iter.all(|(tx_versioned_hash, commitment)| {
68+
tx_versioned_hash == kzg_commitment_to_versioned_hash(commitment)
69+
})
70+
})
71+
})?
72+
}
73+
74+
/// Only transactions of type `BLOB_TX_TYPE` should be passed into this function.
75+
fn tx_peek_blob_versioned_hashes<T: EthSpec>(
76+
opaque_tx: &Transaction<T::MaxBytesPerTransaction>,
77+
) -> Result<
78+
impl IntoIterator<Item = Result<VersionedHash, BlockProcessingError>> + '_,
79+
BlockProcessingError,
80+
> {
81+
let tx_len = opaque_tx.len();
82+
let message_offset = 1.safe_add(u32::from_ssz_bytes(opaque_tx.get(1..5).ok_or(
83+
BlockProcessingError::BlobVersionHashIndexOutOfBounds {
84+
length: tx_len,
85+
index: 5,
86+
},
87+
)?)?)?;
88+
89+
let message_offset_usize = message_offset as usize;
90+
91+
// field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188
92+
let blob_versioned_hashes_offset = message_offset.safe_add(u32::from_ssz_bytes(
93+
opaque_tx
94+
.get(message_offset_usize.safe_add(188)?..message_offset_usize.safe_add(192)?)
95+
.ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds {
96+
length: tx_len,
97+
index: message_offset_usize.safe_add(192)?,
98+
})?,
99+
)?)?;
100+
101+
let num_hashes = tx_len
102+
.safe_sub(blob_versioned_hashes_offset as usize)?
103+
.safe_div(32)?;
104+
105+
Ok((0..num_hashes).into_iter().map(move |i| {
106+
let next_version_hash_index =
107+
(blob_versioned_hashes_offset as usize).safe_add(i.safe_mul(32)?)?;
108+
let bytes = opaque_tx
109+
.get(next_version_hash_index..next_version_hash_index.safe_add(32)?)
110+
.ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds {
111+
length: tx_len,
112+
index: (next_version_hash_index as usize).safe_add(32)?,
113+
})?;
114+
Ok(VersionedHash::from_slice(bytes))
115+
}))
116+
}
117+
118+
fn kzg_commitment_to_versioned_hash(kzg_commitment: &KzgCommitment) -> VersionedHash {
119+
let mut hashed_commitment = hash_fixed(&kzg_commitment.0);
120+
hashed_commitment[0] = VERSIONED_HASH_VERSION_KZG;
121+
VersionedHash::from(hashed_commitment)
122+
}

consensus/state_processing/src/per_block_processing/errors.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::signature_sets::Error as SignatureSetError;
22
use merkle_proof::MerkleTreeError;
33
use safe_arith::ArithError;
4+
use ssz::DecodeError;
45
use types::*;
56

67
/// The error returned from the `per_block_processing` function. Indicates that a block is either
@@ -53,6 +54,7 @@ pub enum BlockProcessingError {
5354
BeaconStateError(BeaconStateError),
5455
SignatureSetError(SignatureSetError),
5556
SszTypesError(ssz_types::Error),
57+
SszDecodeError(DecodeError),
5658
MerkleTreeError(MerkleTreeError),
5759
ArithError(ArithError),
5860
InconsistentBlockFork(InconsistentFork),
@@ -70,6 +72,18 @@ pub enum BlockProcessingError {
7072
found: u64,
7173
},
7274
ExecutionInvalid,
75+
BlobVersionHashMismatch,
76+
/// The number of commitments in blob transactions in the payload does not match the number
77+
/// of commitments in the block.
78+
BlobNumCommitmentsMismatch {
79+
commitments_processed_in_block: usize,
80+
/// This number depic
81+
commitments_processed_in_transactions: usize,
82+
},
83+
BlobVersionHashIndexOutOfBounds {
84+
index: usize,
85+
length: usize,
86+
},
7387
}
7488

7589
impl From<BeaconStateError> for BlockProcessingError {
@@ -90,6 +104,12 @@ impl From<ssz_types::Error> for BlockProcessingError {
90104
}
91105
}
92106

107+
impl From<DecodeError> for BlockProcessingError {
108+
fn from(error: DecodeError) -> Self {
109+
BlockProcessingError::SszDecodeError(error)
110+
}
111+
}
112+
93113
impl From<ArithError> for BlockProcessingError {
94114
fn from(e: ArithError) -> Self {
95115
BlockProcessingError::ArithError(e)

consensus/types/src/consts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ pub mod eip4844 {
3434
.expect("should initialize BLS_MODULUS");
3535
}
3636
pub const BLOB_TX_TYPE: u8 = 5;
37+
pub const VERSIONED_HASH_VERSION_KZG: u8 = 1;
3738
}

consensus/types/src/kzg_commitment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use tree_hash::{PackedEncoding, TreeHash};
99

1010
#[derive(Derivative, Debug, Clone, Serialize, Deserialize)]
1111
#[derivative(PartialEq, Eq, Hash)]
12-
pub struct KzgCommitment(#[serde(with = "BigArray")] [u8; 48]);
12+
pub struct KzgCommitment(#[serde(with = "BigArray")] pub [u8; 48]);
1313

1414
impl Display for KzgCommitment {
1515
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {

consensus/types/src/kzg_proof.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::test_utils::{RngCore, TestRandom};
22
use serde::{Deserialize, Serialize};
3+
use serde_big_array::BigArray;
34
use ssz::{Decode, DecodeError, Encode};
45
use std::fmt;
56
use tree_hash::{PackedEncoding, TreeHash};
6-
use serde_big_array::BigArray;
77

88
const KZG_PROOF_BYTES_LEN: usize = 48;
99

consensus/types/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ pub type Address = H160;
193193
pub type ForkVersion = [u8; 4];
194194
pub type BLSFieldElement = Uint256;
195195
pub type Blob<T> = FixedVector<BLSFieldElement, <T as EthSpec>::FieldElementsPerBlob>;
196+
pub type VersionedHash = Hash256;
196197

197198
pub use bls::{
198199
AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey,

consensus/types/src/payload.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub trait ExecPayload<T: EthSpec>: Debug + Clone + PartialEq + Hash + TreeHash +
3535
fn fee_recipient(&self) -> Address;
3636
fn gas_limit(&self) -> u64;
3737

38+
/// This will return `None` on blinded blocks or pre-merge blocks.
39+
fn transactions(&self) -> Option<&Transactions<T>>;
40+
3841
// Is this a default payload? (pre-merge)
3942
fn is_default(&self) -> bool;
4043
}
@@ -191,6 +194,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayloadMerge<T> {
191194
self.execution_payload.gas_limit
192195
}
193196

197+
fn transactions(&self) -> Option<&Transactions<T>> {
198+
Some(&self.execution_payload.transactions)
199+
}
200+
194201
// TODO: can this function be optimized?
195202
fn is_default(&self) -> bool {
196203
self.execution_payload == ExecutionPayloadMerge::default()
@@ -235,6 +242,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayloadCapella<T> {
235242
self.execution_payload.gas_limit
236243
}
237244

245+
fn transactions(&self) -> Option<&Transactions<T>> {
246+
Some(&self.execution_payload.transactions)
247+
}
248+
238249
// TODO: can this function be optimized?
239250
fn is_default(&self) -> bool {
240251
self.execution_payload == ExecutionPayloadCapella::default()
@@ -279,6 +290,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayloadEip4844<T> {
279290
self.execution_payload.gas_limit
280291
}
281292

293+
fn transactions(&self) -> Option<&Transactions<T>> {
294+
Some(&self.execution_payload.transactions)
295+
}
296+
282297
// TODO: can this function be optimized?
283298
fn is_default(&self) -> bool {
284299
self.execution_payload == ExecutionPayloadEip4844::default()
@@ -347,6 +362,13 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
347362
})
348363
}
349364

365+
fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
366+
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
367+
cons(payload);
368+
Some(&payload.execution_payload.transactions)
369+
})
370+
}
371+
350372
fn is_default(&self) -> bool {
351373
match self {
352374
Self::Merge(payload) => payload.is_default(),
@@ -428,6 +450,13 @@ impl<'b, T: EthSpec> ExecPayload<T> for FullPayloadRef<'b, T> {
428450
})
429451
}
430452

453+
fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
454+
map_full_payload_ref!(&'a _, self, move |payload, cons| {
455+
cons(payload);
456+
Some(&payload.execution_payload.transactions)
457+
})
458+
}
459+
431460
// TODO: can this function be optimized?
432461
fn is_default<'a>(&'a self) -> bool {
433462
match self {
@@ -687,6 +716,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
687716
}
688717
}
689718

719+
fn transactions(&self) -> Option<&Transactions<T>> {
720+
None
721+
}
722+
690723
// TODO: can this function be optimized?
691724
fn is_default(&self) -> bool {
692725
match self {
@@ -773,6 +806,10 @@ impl<'b, T: EthSpec> ExecPayload<T> for BlindedPayloadRef<'b, T> {
773806
}
774807
}
775808

809+
fn transactions(&self) -> Option<&Transactions<T>> {
810+
None
811+
}
812+
776813
// TODO: can this function be optimized?
777814
fn is_default<'a>(&'a self) -> bool {
778815
match self {
@@ -828,6 +865,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayloadMerge<T> {
828865
self.execution_payload_header.gas_limit
829866
}
830867

868+
fn transactions(&self) -> Option<&Transactions<T>> {
869+
None
870+
}
871+
831872
fn is_default(&self) -> bool {
832873
self.execution_payload_header == ExecutionPayloadHeaderMerge::default()
833874
}
@@ -871,6 +912,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayloadCapella<T> {
871912
self.execution_payload_header.gas_limit
872913
}
873914

915+
fn transactions(&self) -> Option<&Transactions<T>> {
916+
None
917+
}
918+
874919
fn is_default(&self) -> bool {
875920
self.execution_payload_header == ExecutionPayloadHeaderCapella::default()
876921
}
@@ -914,6 +959,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayloadEip4844<T> {
914959
self.execution_payload_header.gas_limit
915960
}
916961

962+
fn transactions(&self) -> Option<&Transactions<T>> {
963+
None
964+
}
965+
917966
fn is_default(&self) -> bool {
918967
self.execution_payload_header == ExecutionPayloadHeaderEip4844::default()
919968
}

0 commit comments

Comments
 (0)