Merge pull request #3941 from realbigsean/blob-decoding

Blob decoding
This commit is contained in:
realbigsean 2023-02-07 08:38:20 -05:00 committed by GitHub
commit 21e5b7fecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 202 additions and 35 deletions

View File

@ -1026,8 +1026,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
//FIXME(sean) avoid the clone by comparing refs to headers (`as_execution_payload_header` method ?) //FIXME(sean) avoid the clone by comparing refs to headers (`as_execution_payload_header` method ?)
let full_payload: FullPayload<T::EthSpec> = execution_payload.clone().into(); let full_payload: FullPayload<T::EthSpec> = execution_payload.clone().into();
//FIXME(sean) we're not decoding blobs txs correctly yet
if !matches!(execution_payload, ExecutionPayload::Eip4844(_)) {
// Verify payload integrity. // Verify payload integrity.
let header_from_payload = full_payload.to_execution_payload_header(); let header_from_payload = full_payload.to_execution_payload_header();
if header_from_payload != execution_payload_header { if header_from_payload != execution_payload_header {
@ -1048,7 +1046,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
reconstructed_transactions_root: header_from_payload.transactions_root(), reconstructed_transactions_root: header_from_payload.transactions_root(),
}); });
} }
}
// Add the payload to the block to form a full block. // Add the payload to the block to form a full block.
blinded_block blinded_block

View File

@ -1,4 +1,5 @@
use crate::engines::ForkchoiceState; use crate::engines::ForkchoiceState;
use crate::BlobTxConversionError;
pub use ethers_core::types::Transaction; pub use ethers_core::types::Transaction;
use ethers_core::utils::rlp::{self, Decodable, Rlp}; use ethers_core::utils::rlp::{self, Decodable, Rlp};
use http::deposit_methods::RpcError; use http::deposit_methods::RpcError;
@ -40,8 +41,7 @@ pub enum Error {
PayloadIdUnavailable, PayloadIdUnavailable,
TransitionConfigurationMismatch, TransitionConfigurationMismatch,
PayloadConversionLogicFlaw, PayloadConversionLogicFlaw,
DeserializeTransaction(ssz_types::Error), SszError(ssz_types::Error),
DeserializeTransactions(ssz_types::Error),
DeserializeWithdrawals(ssz_types::Error), DeserializeWithdrawals(ssz_types::Error),
BuilderApi(builder_client::Error), BuilderApi(builder_client::Error),
IncorrectStateVariant, IncorrectStateVariant,
@ -49,6 +49,7 @@ pub enum Error {
UnsupportedForkVariant(String), UnsupportedForkVariant(String),
BadConversion(String), BadConversion(String),
RlpDecoderError(rlp::DecoderError), RlpDecoderError(rlp::DecoderError),
BlobTxConversionError(BlobTxConversionError),
} }
impl From<reqwest::Error> for Error { impl From<reqwest::Error> for Error {
@ -88,6 +89,18 @@ impl From<rlp::DecoderError> for Error {
} }
} }
impl From<ssz_types::Error> for Error {
fn from(e: ssz_types::Error) -> Self {
Error::SszError(e)
}
}
impl From<BlobTxConversionError> for Error {
fn from(e: BlobTxConversionError) -> Self {
Error::BlobTxConversionError(e)
}
}
#[derive(Clone, Copy, Debug, PartialEq, IntoStaticStr)] #[derive(Clone, Copy, Debug, PartialEq, IntoStaticStr)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum PayloadStatusV1Status { pub enum PayloadStatusV1Status {

View File

@ -13,6 +13,7 @@ pub use engine_api::{http, http::deposit_methods, http::HttpJsonRpc};
use engines::{Engine, EngineError}; use engines::{Engine, EngineError};
pub use engines::{EngineState, ForkchoiceState}; pub use engines::{EngineState, ForkchoiceState};
use eth2::types::{builder_bid::SignedBuilderBid, ForkVersionedResponse}; use eth2::types::{builder_bid::SignedBuilderBid, ForkVersionedResponse};
use ethers_core::types::Transaction as EthersTransaction;
use fork_choice::ForkchoiceUpdateParameters; use fork_choice::ForkchoiceUpdateParameters;
use lru::LruCache; use lru::LruCache;
use payload_status::process_payload_status; use payload_status::process_payload_status;
@ -21,6 +22,7 @@ use sensitive_url::SensitiveUrl;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slog::{crit, debug, error, info, trace, warn, Logger}; use slog::{crit, debug, error, info, trace, warn, Logger};
use slot_clock::SlotClock; use slot_clock::SlotClock;
use ssz::Encode;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
@ -35,10 +37,13 @@ use tokio::{
time::sleep, time::sleep,
}; };
use tokio_stream::wrappers::WatchStream; use tokio_stream::wrappers::WatchStream;
use types::consts::eip4844::BLOB_TX_TYPE;
use types::transaction::{AccessTuple, BlobTransaction};
use types::{AbstractExecPayload, BeaconStateError, Blob, ExecPayload, KzgCommitment}; use types::{AbstractExecPayload, BeaconStateError, Blob, ExecPayload, KzgCommitment};
use types::{ use types::{
BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ForkName, BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ForkName,
ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Uint256, ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Transaction,
Uint256,
}; };
use types::{ use types::{
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge,
@ -1612,15 +1617,13 @@ impl<T: EthSpec> ExecutionLayer<T> {
return Ok(None); return Ok(None);
}; };
let transactions = VariableList::new( let transactions = VariableList::from(
block block
.transactions() .transactions()
.iter() .into_iter()
.map(|transaction| VariableList::new(transaction.rlp().to_vec())) .map(ethers_tx_to_bytes::<T>)
.collect::<Result<_, _>>() .collect::<Result<Vec<_>, BlobTxConversionError>>()?,
.map_err(ApiError::DeserializeTransaction)?, );
)
.map_err(ApiError::DeserializeTransactions)?;
let payload = match block { let payload = match block {
ExecutionBlockWithTransactions::Merge(merge_block) => { ExecutionBlockWithTransactions::Merge(merge_block) => {
@ -1928,6 +1931,132 @@ async fn timed_future<F: Future<Output = T>, T>(metric: &str, future: F) -> (T,
(result, duration) (result, duration)
} }
#[derive(Debug)]
pub enum BlobTxConversionError {
/// The transaction type was not set.
NoTransactionType,
/// The transaction chain ID was not set.
NoChainId,
/// The transaction nonce was too large to fit in a `u64`.
NonceTooLarge,
/// The transaction gas was too large to fit in a `u64`.
GasTooHigh,
/// Missing the `max_fee_per_gas` field.
MaxFeePerGasMissing,
/// Missing the `max_priority_fee_per_gas` field.
MaxPriorityFeePerGasMissing,
/// Missing the `access_list` field.
AccessListMissing,
/// Missing the `max_fee_per_data_gas` field.
MaxFeePerDataGasMissing,
/// Missing the `blob_versioned_hashes` field.
BlobVersionedHashesMissing,
/// There was an error converting the transaction to SSZ.
SszError(ssz_types::Error),
/// There was an error converting the transaction from JSON.
SerdeJson(serde_json::Error),
}
impl From<ssz_types::Error> for BlobTxConversionError {
fn from(value: ssz_types::Error) -> Self {
Self::SszError(value)
}
}
impl From<serde_json::Error> for BlobTxConversionError {
fn from(value: serde_json::Error) -> Self {
Self::SerdeJson(value)
}
}
/// A utility function to convert a `ethers-rs` `Transaction` into the correct bytes encoding based
/// on transaction type. That means RLP encoding if this is a transaction other than a
/// `BLOB_TX_TYPE` transaction in which case, SSZ encoding will be used.
fn ethers_tx_to_bytes<T: EthSpec>(
transaction: &EthersTransaction,
) -> Result<Transaction<T::MaxBytesPerTransaction>, BlobTxConversionError> {
let tx_type = transaction
.transaction_type
.ok_or(BlobTxConversionError::NoTransactionType)?
.as_u64();
let tx = if BLOB_TX_TYPE as u64 == tx_type {
let chain_id = transaction
.chain_id
.ok_or(BlobTxConversionError::NoChainId)?;
let nonce = if transaction.nonce > Uint256::from(u64::MAX) {
return Err(BlobTxConversionError::NonceTooLarge);
} else {
transaction.nonce.as_u64()
};
let max_priority_fee_per_gas = transaction
.max_priority_fee_per_gas
.ok_or(BlobTxConversionError::MaxPriorityFeePerGasMissing)?;
let max_fee_per_gas = transaction
.max_fee_per_gas
.ok_or(BlobTxConversionError::MaxFeePerGasMissing)?;
let gas = if transaction.gas > Uint256::from(u64::MAX) {
return Err(BlobTxConversionError::GasTooHigh);
} else {
transaction.gas.as_u64()
};
let to = transaction.to;
let value = transaction.value;
let data = VariableList::new(transaction.input.to_vec())?;
let access_list = VariableList::new(
transaction
.access_list
.as_ref()
.ok_or(BlobTxConversionError::AccessListMissing)?
.0
.iter()
.map(|access_tuple| {
Ok(AccessTuple {
address: access_tuple.address,
storage_keys: VariableList::new(access_tuple.storage_keys.clone())?,
})
})
.collect::<Result<Vec<AccessTuple>, BlobTxConversionError>>()?,
)?;
let max_fee_per_data_gas = transaction
.other
.get("maxFeePerDataGas")
.ok_or(BlobTxConversionError::MaxFeePerDataGasMissing)?
.as_str()
.ok_or(BlobTxConversionError::MaxFeePerDataGasMissing)?
.parse()
.map_err(|_| BlobTxConversionError::MaxFeePerDataGasMissing)?;
let blob_versioned_hashes = serde_json::from_str(
transaction
.other
.get("blobVersionedHashes")
.ok_or(BlobTxConversionError::BlobVersionedHashesMissing)?
.as_str()
.ok_or(BlobTxConversionError::BlobVersionedHashesMissing)?,
)?;
BlobTransaction {
chain_id,
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas,
to,
value,
data,
access_list,
max_fee_per_data_gas,
blob_versioned_hashes,
}
.as_ssz_bytes()
} else {
transaction.rlp().to_vec()
};
VariableList::new(tx).map_err(Into::into)
}
fn noop<T: EthSpec>(_: &ExecutionLayer<T>, _: &ExecutionPayload<T>) -> Option<ExecutionPayload<T>> {
None
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -2075,10 +2204,6 @@ mod test {
} }
} }
fn noop<T: EthSpec>(_: &ExecutionLayer<T>, _: &ExecutionPayload<T>) -> Option<ExecutionPayload<T>> {
None
}
#[cfg(test)] #[cfg(test)]
/// Returns the duration since the unix epoch. /// Returns the duration since the unix epoch.
fn timestamp_now() -> u64 { fn timestamp_now() -> u64 {

View File

@ -101,6 +101,7 @@ pub mod sqlite;
pub mod blobs_sidecar; pub mod blobs_sidecar;
pub mod signed_block_and_blobs; pub mod signed_block_and_blobs;
pub mod transaction;
use ethereum_types::{H160, H256}; use ethereum_types::{H160, H256};

View File

@ -0,0 +1,31 @@
use crate::{Hash256, Uint256, VersionedHash};
use ethereum_types::Address;
use ssz_derive::{Decode, Encode};
use ssz_types::typenum::U16777216;
use ssz_types::VariableList;
pub type MaxCalldataSize = U16777216;
pub type MaxAccessListSize = U16777216;
pub type MaxVersionedHashesListSize = U16777216;
pub type MaxAccessListStorageKeys = U16777216;
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub struct BlobTransaction {
pub chain_id: Uint256,
pub nonce: u64,
pub max_priority_fee_per_gas: Uint256,
pub max_fee_per_gas: Uint256,
pub gas: u64,
pub to: Option<Address>,
pub value: Uint256,
pub data: VariableList<u8, MaxCalldataSize>,
pub access_list: VariableList<AccessTuple, MaxAccessListSize>,
pub max_fee_per_data_gas: Uint256,
pub blob_versioned_hashes: VariableList<VersionedHash, MaxVersionedHashesListSize>,
}
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub struct AccessTuple {
pub address: Address,
pub storage_keys: VariableList<Hash256, MaxAccessListStorageKeys>,
}