Block processing eip4844 (#3673)
* add eip4844 block processing * fix blob processing code * consensus logic fixes and cleanup * use safe arith
This commit is contained in:
parent
29f2ec46d3
commit
5ad834280b
@ -12,6 +12,7 @@ pub use self::verify_attester_slashing::{
|
||||
pub use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||
pub use altair::sync_committee::process_sync_aggregate;
|
||||
pub use block_signature_verifier::{BlockSignatureVerifier, ParallelSignatureSets};
|
||||
pub use eip4844::eip4844::process_blob_kzg_commitments;
|
||||
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
|
||||
pub use process_operations::process_operations;
|
||||
pub use verify_attestation::{
|
||||
@ -24,6 +25,7 @@ pub use verify_exit::verify_exit;
|
||||
|
||||
pub mod altair;
|
||||
pub mod block_signature_verifier;
|
||||
pub mod eip4844;
|
||||
pub mod errors;
|
||||
mod is_valid_indexed_attestation;
|
||||
pub mod process_operations;
|
||||
@ -171,6 +173,8 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
)?;
|
||||
}
|
||||
|
||||
process_blob_kzg_commitments(block.body())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
pub mod eip4844;
|
@ -0,0 +1,122 @@
|
||||
use crate::BlockProcessingError;
|
||||
use eth2_hashing::hash_fixed;
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use safe_arith::SafeArith;
|
||||
use ssz::Decode;
|
||||
use ssz_types::VariableList;
|
||||
use types::consts::eip4844::{BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG};
|
||||
use types::{
|
||||
AbstractExecPayload, BeaconBlockBodyRef, EthSpec, ExecPayload, FullPayload, FullPayloadRef,
|
||||
KzgCommitment, Transaction, Transactions, VersionedHash,
|
||||
};
|
||||
|
||||
pub fn process_blob_kzg_commitments<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
block_body: BeaconBlockBodyRef<T, Payload>,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
if let (Ok(payload), Ok(kzg_commitments)) = (
|
||||
block_body.execution_payload(),
|
||||
block_body.blob_kzg_commitments(),
|
||||
) {
|
||||
if let Some(transactions) = payload.transactions() {
|
||||
if !verify_kzg_commitments_against_transactions::<T>(transactions, kzg_commitments)? {
|
||||
return Err(BlockProcessingError::BlobVersionHashMismatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_kzg_commitments_against_transactions<T: EthSpec>(
|
||||
transactions: &Transactions<T>,
|
||||
kzg_commitments: &VariableList<KzgCommitment, T::MaxBlobsPerBlock>,
|
||||
) -> Result<bool, BlockProcessingError> {
|
||||
let nested_iter = transactions
|
||||
.into_iter()
|
||||
.filter(|tx| {
|
||||
tx.get(0)
|
||||
.map(|tx_type| *tx_type == BLOB_TX_TYPE)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|tx| tx_peek_blob_versioned_hashes::<T>(tx));
|
||||
|
||||
itertools::process_results(nested_iter, |iter| {
|
||||
let zipped_iter = iter
|
||||
.flatten()
|
||||
// Need to use `itertools::zip_longest` here because just zipping hides if one iter is shorter
|
||||
// and `itertools::zip_eq` panics.
|
||||
.zip_longest(kzg_commitments.into_iter())
|
||||
.enumerate()
|
||||
.map(|(index, next)| match next {
|
||||
EitherOrBoth::Both(hash, commitment) => Ok((hash?, commitment)),
|
||||
// The number of versioned hashes from the blob transactions exceeds the number of
|
||||
// commitments in the block.
|
||||
EitherOrBoth::Left(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch {
|
||||
commitments_processed_in_block: index,
|
||||
commitments_processed_in_transactions: index.safe_add(1)?,
|
||||
}),
|
||||
// The number of commitments in the block exceeds the number of versioned hashes
|
||||
// in the blob transactions.
|
||||
EitherOrBoth::Right(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch {
|
||||
commitments_processed_in_block: index.safe_add(1)?,
|
||||
commitments_processed_in_transactions: index,
|
||||
}),
|
||||
});
|
||||
|
||||
itertools::process_results(zipped_iter, |mut iter| {
|
||||
iter.all(|(tx_versioned_hash, commitment)| {
|
||||
tx_versioned_hash == kzg_commitment_to_versioned_hash(commitment)
|
||||
})
|
||||
})
|
||||
})?
|
||||
}
|
||||
|
||||
/// Only transactions of type `BLOB_TX_TYPE` should be passed into this function.
|
||||
fn tx_peek_blob_versioned_hashes<T: EthSpec>(
|
||||
opaque_tx: &Transaction<T::MaxBytesPerTransaction>,
|
||||
) -> Result<
|
||||
impl IntoIterator<Item = Result<VersionedHash, BlockProcessingError>> + '_,
|
||||
BlockProcessingError,
|
||||
> {
|
||||
let tx_len = opaque_tx.len();
|
||||
let message_offset = 1.safe_add(u32::from_ssz_bytes(opaque_tx.get(1..5).ok_or(
|
||||
BlockProcessingError::BlobVersionHashIndexOutOfBounds {
|
||||
length: tx_len,
|
||||
index: 5,
|
||||
},
|
||||
)?)?)?;
|
||||
|
||||
let message_offset_usize = message_offset as usize;
|
||||
|
||||
// field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188
|
||||
let blob_versioned_hashes_offset = message_offset.safe_add(u32::from_ssz_bytes(
|
||||
opaque_tx
|
||||
.get(message_offset_usize.safe_add(188)?..message_offset_usize.safe_add(192)?)
|
||||
.ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds {
|
||||
length: tx_len,
|
||||
index: message_offset_usize.safe_add(192)?,
|
||||
})?,
|
||||
)?)?;
|
||||
|
||||
let num_hashes = tx_len
|
||||
.safe_sub(blob_versioned_hashes_offset as usize)?
|
||||
.safe_div(32)?;
|
||||
|
||||
Ok((0..num_hashes).into_iter().map(move |i| {
|
||||
let next_version_hash_index =
|
||||
(blob_versioned_hashes_offset as usize).safe_add(i.safe_mul(32)?)?;
|
||||
let bytes = opaque_tx
|
||||
.get(next_version_hash_index..next_version_hash_index.safe_add(32)?)
|
||||
.ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds {
|
||||
length: tx_len,
|
||||
index: (next_version_hash_index as usize).safe_add(32)?,
|
||||
})?;
|
||||
Ok(VersionedHash::from_slice(bytes))
|
||||
}))
|
||||
}
|
||||
|
||||
fn kzg_commitment_to_versioned_hash(kzg_commitment: &KzgCommitment) -> VersionedHash {
|
||||
let mut hashed_commitment = hash_fixed(&kzg_commitment.0);
|
||||
hashed_commitment[0] = VERSIONED_HASH_VERSION_KZG;
|
||||
VersionedHash::from(hashed_commitment)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use super::signature_sets::Error as SignatureSetError;
|
||||
use merkle_proof::MerkleTreeError;
|
||||
use safe_arith::ArithError;
|
||||
use ssz::DecodeError;
|
||||
use types::*;
|
||||
|
||||
/// The error returned from the `per_block_processing` function. Indicates that a block is either
|
||||
@ -53,6 +54,7 @@ pub enum BlockProcessingError {
|
||||
BeaconStateError(BeaconStateError),
|
||||
SignatureSetError(SignatureSetError),
|
||||
SszTypesError(ssz_types::Error),
|
||||
SszDecodeError(DecodeError),
|
||||
MerkleTreeError(MerkleTreeError),
|
||||
ArithError(ArithError),
|
||||
InconsistentBlockFork(InconsistentFork),
|
||||
@ -70,6 +72,18 @@ pub enum BlockProcessingError {
|
||||
found: u64,
|
||||
},
|
||||
ExecutionInvalid,
|
||||
BlobVersionHashMismatch,
|
||||
/// The number of commitments in blob transactions in the payload does not match the number
|
||||
/// of commitments in the block.
|
||||
BlobNumCommitmentsMismatch {
|
||||
commitments_processed_in_block: usize,
|
||||
/// This number depic
|
||||
commitments_processed_in_transactions: usize,
|
||||
},
|
||||
BlobVersionHashIndexOutOfBounds {
|
||||
index: usize,
|
||||
length: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for BlockProcessingError {
|
||||
@ -90,6 +104,12 @@ impl From<ssz_types::Error> for BlockProcessingError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for BlockProcessingError {
|
||||
fn from(error: DecodeError) -> Self {
|
||||
BlockProcessingError::SszDecodeError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArithError> for BlockProcessingError {
|
||||
fn from(e: ArithError) -> Self {
|
||||
BlockProcessingError::ArithError(e)
|
||||
|
@ -34,4 +34,5 @@ pub mod eip4844 {
|
||||
.expect("should initialize BLS_MODULUS");
|
||||
}
|
||||
pub const BLOB_TX_TYPE: u8 = 5;
|
||||
pub const VERSIONED_HASH_VERSION_KZG: u8 = 1;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use tree_hash::{PackedEncoding, TreeHash};
|
||||
|
||||
#[derive(Derivative, Debug, Clone, Serialize, Deserialize)]
|
||||
#[derivative(PartialEq, Eq, Hash)]
|
||||
pub struct KzgCommitment(#[serde(with = "BigArray")] [u8; 48]);
|
||||
pub struct KzgCommitment(#[serde(with = "BigArray")] pub [u8; 48]);
|
||||
|
||||
impl Display for KzgCommitment {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::test_utils::{RngCore, TestRandom};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_big_array::BigArray;
|
||||
use ssz::{Decode, DecodeError, Encode};
|
||||
use std::fmt;
|
||||
use tree_hash::{PackedEncoding, TreeHash};
|
||||
use serde_big_array::BigArray;
|
||||
|
||||
const KZG_PROOF_BYTES_LEN: usize = 48;
|
||||
|
||||
|
@ -193,6 +193,7 @@ pub type Address = H160;
|
||||
pub type ForkVersion = [u8; 4];
|
||||
pub type BLSFieldElement = Uint256;
|
||||
pub type Blob<T> = FixedVector<BLSFieldElement, <T as EthSpec>::FieldElementsPerBlob>;
|
||||
pub type VersionedHash = Hash256;
|
||||
|
||||
pub use bls::{
|
||||
AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey,
|
||||
|
@ -35,6 +35,9 @@ pub trait ExecPayload<T: EthSpec>: Debug + Clone + PartialEq + Hash + TreeHash +
|
||||
fn fee_recipient(&self) -> Address;
|
||||
fn gas_limit(&self) -> u64;
|
||||
|
||||
/// This will return `None` on blinded blocks or pre-merge blocks.
|
||||
fn transactions(&self) -> Option<&Transactions<T>>;
|
||||
|
||||
// Is this a default payload? (pre-merge)
|
||||
fn is_default(&self) -> bool;
|
||||
}
|
||||
@ -191,6 +194,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayloadMerge<T> {
|
||||
self.execution_payload.gas_limit
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
Some(&self.execution_payload.transactions)
|
||||
}
|
||||
|
||||
// TODO: can this function be optimized?
|
||||
fn is_default(&self) -> bool {
|
||||
self.execution_payload == ExecutionPayloadMerge::default()
|
||||
@ -235,6 +242,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayloadCapella<T> {
|
||||
self.execution_payload.gas_limit
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
Some(&self.execution_payload.transactions)
|
||||
}
|
||||
|
||||
// TODO: can this function be optimized?
|
||||
fn is_default(&self) -> bool {
|
||||
self.execution_payload == ExecutionPayloadCapella::default()
|
||||
@ -279,6 +290,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayloadEip4844<T> {
|
||||
self.execution_payload.gas_limit
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
Some(&self.execution_payload.transactions)
|
||||
}
|
||||
|
||||
// TODO: can this function be optimized?
|
||||
fn is_default(&self) -> bool {
|
||||
self.execution_payload == ExecutionPayloadEip4844::default()
|
||||
@ -347,6 +362,13 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
|
||||
})
|
||||
}
|
||||
|
||||
fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
Some(&payload.execution_payload.transactions)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_default(&self) -> bool {
|
||||
match self {
|
||||
Self::Merge(payload) => payload.is_default(),
|
||||
@ -428,6 +450,13 @@ impl<'b, T: EthSpec> ExecPayload<T> for FullPayloadRef<'b, T> {
|
||||
})
|
||||
}
|
||||
|
||||
fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
Some(&payload.execution_payload.transactions)
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: can this function be optimized?
|
||||
fn is_default<'a>(&'a self) -> bool {
|
||||
match self {
|
||||
@ -687,6 +716,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
// TODO: can this function be optimized?
|
||||
fn is_default(&self) -> bool {
|
||||
match self {
|
||||
@ -773,6 +806,10 @@ impl<'b, T: EthSpec> ExecPayload<T> for BlindedPayloadRef<'b, T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
// TODO: can this function be optimized?
|
||||
fn is_default<'a>(&'a self) -> bool {
|
||||
match self {
|
||||
@ -828,6 +865,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayloadMerge<T> {
|
||||
self.execution_payload_header.gas_limit
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_default(&self) -> bool {
|
||||
self.execution_payload_header == ExecutionPayloadHeaderMerge::default()
|
||||
}
|
||||
@ -871,6 +912,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayloadCapella<T> {
|
||||
self.execution_payload_header.gas_limit
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_default(&self) -> bool {
|
||||
self.execution_payload_header == ExecutionPayloadHeaderCapella::default()
|
||||
}
|
||||
@ -914,6 +959,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayloadEip4844<T> {
|
||||
self.execution_payload_header.gas_limit
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_default(&self) -> bool {
|
||||
self.execution_payload_header == ExecutionPayloadHeaderEip4844::default()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user