Merge pull request #3951 from realbigsean/fix-blob-tx-ssz

Encode blob transactions as signed blob transactions
This commit is contained in:
realbigsean 2023-02-13 14:50:44 -05:00 committed by GitHub
commit e58d7e85bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 153 additions and 55 deletions

View File

@ -14,7 +14,9 @@ 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 ethers_core::abi::ethereum_types::FromStrRadixErr;
use ethers_core::types::transaction::eip2930::AccessListItem;
use ethers_core::types::{Transaction as EthersTransaction, U64};
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;
@ -39,8 +41,10 @@ use tokio::{
}; };
use tokio_stream::wrappers::WatchStream; use tokio_stream::wrappers::WatchStream;
use types::consts::eip4844::BLOB_TX_TYPE; use types::consts::eip4844::BLOB_TX_TYPE;
use types::transaction::{AccessTuple, BlobTransaction}; use types::transaction::{AccessTuple, BlobTransaction, EcdsaSignature, SignedBlobTransaction};
use types::{AbstractExecPayload, BeaconStateError, Blob, ExecPayload, KzgCommitment}; use types::{
AbstractExecPayload, BeaconStateError, Blob, ExecPayload, KzgCommitment, VersionedHash,
};
use types::{ use types::{
BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ForkName, BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ForkName,
ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Transaction, ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Transaction,
@ -1687,13 +1691,15 @@ impl<T: EthSpec> ExecutionLayer<T> {
return Ok(None); return Ok(None);
}; };
let transactions = VariableList::from( let convert_transactions = |transactions: Vec<EthersTransaction>| {
block VariableList::new(
.transactions() transactions
.into_iter() .into_iter()
.map(ethers_tx_to_bytes::<T>) .map(ethers_tx_to_bytes::<T>)
.collect::<Result<Vec<_>, BlobTxConversionError>>()?, .collect::<Result<Vec<_>, BlobTxConversionError>>()?,
); )
.map_err(BlobTxConversionError::SszError)
};
let payload = match block { let payload = match block {
ExecutionBlockWithTransactions::Merge(merge_block) => { ExecutionBlockWithTransactions::Merge(merge_block) => {
@ -1711,7 +1717,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
extra_data: merge_block.extra_data, extra_data: merge_block.extra_data,
base_fee_per_gas: merge_block.base_fee_per_gas, base_fee_per_gas: merge_block.base_fee_per_gas,
block_hash: merge_block.block_hash, block_hash: merge_block.block_hash,
transactions, transactions: convert_transactions(merge_block.transactions)?,
}) })
} }
ExecutionBlockWithTransactions::Capella(capella_block) => { ExecutionBlockWithTransactions::Capella(capella_block) => {
@ -1737,7 +1743,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
extra_data: capella_block.extra_data, extra_data: capella_block.extra_data,
base_fee_per_gas: capella_block.base_fee_per_gas, base_fee_per_gas: capella_block.base_fee_per_gas,
block_hash: capella_block.block_hash, block_hash: capella_block.block_hash,
transactions, transactions: convert_transactions(capella_block.transactions)?,
withdrawals, withdrawals,
}) })
} }
@ -1765,7 +1771,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
base_fee_per_gas: eip4844_block.base_fee_per_gas, base_fee_per_gas: eip4844_block.base_fee_per_gas,
excess_data_gas: eip4844_block.excess_data_gas, excess_data_gas: eip4844_block.excess_data_gas,
block_hash: eip4844_block.block_hash, block_hash: eip4844_block.block_hash,
transactions, transactions: convert_transactions(eip4844_block.transactions)?,
withdrawals, withdrawals,
}) })
} }
@ -2030,10 +2036,18 @@ pub enum BlobTxConversionError {
MaxFeePerDataGasMissing, MaxFeePerDataGasMissing,
/// Missing the `blob_versioned_hashes` field. /// Missing the `blob_versioned_hashes` field.
BlobVersionedHashesMissing, BlobVersionedHashesMissing,
/// `y_parity` field was greater than one.
InvalidYParity,
/// There was an error converting the transaction to SSZ. /// There was an error converting the transaction to SSZ.
SszError(ssz_types::Error), SszError(ssz_types::Error),
/// There was an error converting the transaction from JSON. /// There was an error converting the transaction from JSON.
SerdeJson(serde_json::Error), SerdeJson(serde_json::Error),
/// There was an error converting the transaction from hex.
FromHex(String),
/// There was an error converting the transaction from hex.
FromStrRadix(FromStrRadixErr),
/// A `versioned_hash` did not contain 32 bytes.
InvalidVersionedHashBytesLen,
} }
impl From<ssz_types::Error> for BlobTxConversionError { impl From<ssz_types::Error> for BlobTxConversionError {
@ -2052,67 +2066,123 @@ impl From<serde_json::Error> for BlobTxConversionError {
/// on transaction type. That means RLP encoding if this is a transaction other than a /// 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. /// `BLOB_TX_TYPE` transaction in which case, SSZ encoding will be used.
fn ethers_tx_to_bytes<T: EthSpec>( fn ethers_tx_to_bytes<T: EthSpec>(
transaction: &EthersTransaction, transaction: EthersTransaction,
) -> Result<Transaction<T::MaxBytesPerTransaction>, BlobTxConversionError> { ) -> Result<Transaction<T::MaxBytesPerTransaction>, BlobTxConversionError> {
let tx_type = transaction let tx_type = transaction
.transaction_type .transaction_type
.ok_or(BlobTxConversionError::NoTransactionType)? .ok_or(BlobTxConversionError::NoTransactionType)?
.as_u64(); .as_u64();
let tx = if BLOB_TX_TYPE as u64 == tx_type { let tx = if BLOB_TX_TYPE as u64 == tx_type {
let chain_id = transaction let EthersTransaction {
.chain_id hash: _,
.ok_or(BlobTxConversionError::NoChainId)?; nonce,
let nonce = if transaction.nonce > Uint256::from(u64::MAX) { block_hash: _,
block_number: _,
transaction_index: _,
from: _,
to,
value,
gas_price: _,
gas,
input,
v,
r,
s,
transaction_type: _,
access_list,
max_priority_fee_per_gas,
max_fee_per_gas,
chain_id,
other,
} = transaction;
// ******************** BlobTransaction fields ********************
// chainId
let chain_id = chain_id.ok_or(BlobTxConversionError::NoChainId)?;
// nonce
let nonce = if nonce > Uint256::from(u64::MAX) {
return Err(BlobTxConversionError::NonceTooLarge); return Err(BlobTxConversionError::NonceTooLarge);
} else { } else {
transaction.nonce.as_u64() nonce.as_u64()
}; };
let max_priority_fee_per_gas = transaction
.max_priority_fee_per_gas // maxPriorityFeePerGas
.ok_or(BlobTxConversionError::MaxPriorityFeePerGasMissing)?; let max_priority_fee_per_gas =
let max_fee_per_gas = transaction max_priority_fee_per_gas.ok_or(BlobTxConversionError::MaxPriorityFeePerGasMissing)?;
.max_fee_per_gas
.ok_or(BlobTxConversionError::MaxFeePerGasMissing)?; // maxFeePerGas
let gas = if transaction.gas > Uint256::from(u64::MAX) { let max_fee_per_gas = max_fee_per_gas.ok_or(BlobTxConversionError::MaxFeePerGasMissing)?;
// gas
let gas = if gas > Uint256::from(u64::MAX) {
return Err(BlobTxConversionError::GasTooHigh); return Err(BlobTxConversionError::GasTooHigh);
} else { } else {
transaction.gas.as_u64() gas.as_u64()
}; };
let to = transaction.to;
let value = transaction.value; // data (a.k.a input)
let data = VariableList::new(transaction.input.to_vec())?; let data = VariableList::new(input.to_vec())?;
// accessList
let access_list = VariableList::new( let access_list = VariableList::new(
transaction access_list
.access_list
.as_ref()
.ok_or(BlobTxConversionError::AccessListMissing)? .ok_or(BlobTxConversionError::AccessListMissing)?
.0 .0
.iter() .into_iter()
.map(|access_tuple| { .map(|access_tuple| {
let AccessListItem {
address,
storage_keys,
} = access_tuple;
Ok(AccessTuple { Ok(AccessTuple {
address: access_tuple.address, address,
storage_keys: VariableList::new(access_tuple.storage_keys.clone())?, storage_keys: VariableList::new(storage_keys)?,
}) })
}) })
.collect::<Result<Vec<AccessTuple>, BlobTxConversionError>>()?, .collect::<Result<Vec<AccessTuple>, BlobTxConversionError>>()?,
)?; )?;
let max_fee_per_data_gas = transaction
.other // ******************** BlobTransaction `other` fields ********************
//
// Here we use the `other` field in the `ethers-rs` `Transaction` type because
// `ethers-rs` does not yet support SSZ and therefore the blobs transaction type.
// maxFeePerDataGas
let max_fee_per_data_gas = Uint256::from_str_radix(
other
.get("maxFeePerDataGas") .get("maxFeePerDataGas")
.ok_or(BlobTxConversionError::MaxFeePerDataGasMissing)? .ok_or(BlobTxConversionError::MaxFeePerDataGasMissing)?
.as_str() .as_str()
.ok_or(BlobTxConversionError::MaxFeePerDataGasMissing)? .ok_or(BlobTxConversionError::MaxFeePerDataGasMissing)?,
.parse() 16,
.map_err(|_| BlobTxConversionError::MaxFeePerDataGasMissing)?; )
let blob_versioned_hashes = serde_json::from_str( .map_err(BlobTxConversionError::FromStrRadix)?;
transaction
.other // blobVersionedHashes
let blob_versioned_hashes = other
.get("blobVersionedHashes") .get("blobVersionedHashes")
.ok_or(BlobTxConversionError::BlobVersionedHashesMissing)? .ok_or(BlobTxConversionError::BlobVersionedHashesMissing)?
.as_array()
.ok_or(BlobTxConversionError::BlobVersionedHashesMissing)?
.into_iter()
.map(|versioned_hash| {
let hash_bytes = eth2_serde_utils::hex::decode(
versioned_hash
.as_str() .as_str()
.ok_or(BlobTxConversionError::BlobVersionedHashesMissing)?, .ok_or(BlobTxConversionError::BlobVersionedHashesMissing)?,
)?; )
BlobTransaction { .map_err(BlobTxConversionError::FromHex)?;
if hash_bytes.len() != Hash256::ssz_fixed_len() {
Err(BlobTxConversionError::InvalidVersionedHashBytesLen)
} else {
Ok(Hash256::from_slice(&hash_bytes))
}
})
.collect::<Result<Vec<VersionedHash>, BlobTxConversionError>>()?;
let message = BlobTransaction {
chain_id, chain_id,
nonce, nonce,
max_priority_fee_per_gas, max_priority_fee_per_gas,
@ -2123,9 +2193,24 @@ fn ethers_tx_to_bytes<T: EthSpec>(
data, data,
access_list, access_list,
max_fee_per_data_gas, max_fee_per_data_gas,
blob_versioned_hashes, blob_versioned_hashes: VariableList::new(blob_versioned_hashes)?,
} };
.as_ssz_bytes()
// ******************** EcdsaSignature fields ********************
let y_parity = if v == U64::zero() {
false
} else if v == U64::one() {
true
} else {
return Err(BlobTxConversionError::InvalidYParity);
};
let signature = EcdsaSignature { y_parity, r, s };
// The `BLOB_TX_TYPE` should prepend the SSZ encoded `SignedBlobTransaction`.
let mut signed_tx = SignedBlobTransaction { message, signature }.as_ssz_bytes();
signed_tx.insert(0, BLOB_TX_TYPE);
signed_tx
} else { } else {
transaction.rlp().to_vec() transaction.rlp().to_vec()
}; };

View File

@ -9,6 +9,12 @@ pub type MaxAccessListSize = U16777216;
pub type MaxVersionedHashesListSize = U16777216; pub type MaxVersionedHashesListSize = U16777216;
pub type MaxAccessListStorageKeys = U16777216; pub type MaxAccessListStorageKeys = U16777216;
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub struct SignedBlobTransaction {
pub message: BlobTransaction,
pub signature: EcdsaSignature,
}
#[derive(Debug, Clone, PartialEq, Encode, Decode)] #[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub struct BlobTransaction { pub struct BlobTransaction {
pub chain_id: Uint256, pub chain_id: Uint256,
@ -29,3 +35,10 @@ pub struct AccessTuple {
pub address: Address, pub address: Address,
pub storage_keys: VariableList<Hash256, MaxAccessListStorageKeys>, pub storage_keys: VariableList<Hash256, MaxAccessListStorageKeys>,
} }
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub struct EcdsaSignature {
pub y_parity: bool,
pub r: Uint256,
pub s: Uint256,
}