feat: add EIP-4788 parent_beacon_block_root to Header (#4299)

This commit is contained in:
Dan Cline
2023-08-29 10:55:13 -07:00
committed by GitHub
parent 4a2c1691fc
commit 0c7a93717a
16 changed files with 189 additions and 94 deletions

View File

@@ -277,6 +277,7 @@ impl StorageInner {
blob_gas_used: None,
excess_blob_gas: None,
extra_data: Default::default(),
parent_beacon_block_root: None,
};
header.transactions_root = if transactions.is_empty() {

View File

@@ -5,6 +5,7 @@ use crate::{
BeaconForkChoiceUpdateError, BeaconOnNewPayloadError,
};
use futures::TryFutureExt;
use reth_primitives::H256;
use reth_rpc_types::engine::{
ExecutionPayload, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadStatus,
};
@@ -34,9 +35,14 @@ impl BeaconConsensusEngineHandle {
pub async fn new_payload(
&self,
payload: ExecutionPayload,
parent_beacon_block_root: Option<H256>,
) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
let (tx, rx) = oneshot::channel();
let _ = self.to_engine.send(BeaconEngineMessage::NewPayload { payload, tx });
let _ = self.to_engine.send(BeaconEngineMessage::NewPayload {
payload,
parent_beacon_block_root,
tx,
});
rx.await.map_err(|_| BeaconOnNewPayloadError::EngineUnavailable)?
}

View File

@@ -5,6 +5,7 @@ use crate::{
use futures::{future::Either, FutureExt};
use reth_interfaces::consensus::ForkchoiceState;
use reth_payload_builder::error::PayloadBuilderError;
use reth_primitives::H256;
use reth_rpc_types::engine::{
ExecutionPayload, ForkChoiceUpdateResult, ForkchoiceUpdateError, ForkchoiceUpdated,
PayloadAttributes, PayloadId, PayloadStatus, PayloadStatusEnum,
@@ -146,6 +147,8 @@ pub enum BeaconEngineMessage {
NewPayload {
/// The execution payload received by Engine API.
payload: ExecutionPayload,
/// The parent beacon block root, if any.
parent_beacon_block_root: Option<H256>,
/// The sender for returning payload status result.
tx: oneshot::Sender<Result<PayloadStatus, BeaconOnNewPayloadError>>,
},

View File

@@ -1049,12 +1049,13 @@ where
///
/// This returns a [`PayloadStatus`] that represents the outcome of a processed new payload and
/// returns an error if an internal error occurred.
#[instrument(level = "trace", skip(self, payload), fields(block_hash= ?payload.block_hash, block_number = %payload.block_number.as_u64(), is_pipeline_idle = %self.sync.is_pipeline_idle()), target = "consensus::engine")]
#[instrument(level = "trace", skip(self, payload, parent_beacon_block_root), fields(block_hash= ?payload.block_hash, block_number = %payload.block_number.as_u64(), is_pipeline_idle = %self.sync.is_pipeline_idle()), target = "consensus::engine")]
fn on_new_payload(
&mut self,
payload: ExecutionPayload,
parent_beacon_block_root: Option<H256>,
) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
let block = match self.ensure_well_formed_payload(payload) {
let block = match self.ensure_well_formed_payload(payload, parent_beacon_block_root) {
Ok(block) => block,
Err(status) => return Ok(status),
};
@@ -1118,9 +1119,10 @@ where
fn ensure_well_formed_payload(
&self,
payload: ExecutionPayload,
parent_beacon_block_root: Option<H256>,
) -> Result<SealedBlock, PayloadStatus> {
let parent_hash = payload.parent_hash;
let block = match SealedBlock::try_from(payload) {
let block = match payload.try_into_sealed_block(parent_beacon_block_root) {
Ok(block) => block,
Err(error) => {
error!(target: "consensus::engine", ?error, "Invalid payload");
@@ -1725,9 +1727,9 @@ where
}
}
}
BeaconEngineMessage::NewPayload { payload, tx } => {
BeaconEngineMessage::NewPayload { payload, parent_beacon_block_root, tx } => {
this.metrics.new_payload_messages.increment(1);
let res = this.on_new_payload(payload);
let res = this.on_new_payload(payload, parent_beacon_block_root);
let _ = tx.send(res);
}
BeaconEngineMessage::TransitionConfigurationExchanged => {
@@ -1865,7 +1867,7 @@ mod tests {
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
// consensus engine is still idle because no FCUs were received
let _ = env.send_new_payload(SealedBlock::default().into()).await;
let _ = env.send_new_payload(SealedBlock::default().into(), None).await;
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
// consensus engine is still idle because pruning is running
@@ -2279,14 +2281,16 @@ mod tests {
let mut engine_rx = spawn_consensus_engine(consensus_engine);
// Send new payload
let res =
env.send_new_payload(random_block(&mut rng, 0, None, None, Some(0)).into()).await;
let res = env
.send_new_payload(random_block(&mut rng, 0, None, None, Some(0)).into(), None)
.await;
// Invalid, because this is a genesis block
assert_matches!(res, Ok(result) => assert_matches!(result.status, PayloadStatusEnum::Invalid { .. }));
// Send new payload
let res =
env.send_new_payload(random_block(&mut rng, 1, None, None, Some(0)).into()).await;
let res = env
.send_new_payload(random_block(&mut rng, 1, None, None, Some(0)).into(), None)
.await;
let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing);
assert_matches!(res, Ok(result) => assert_eq!(result, expected_result));
@@ -2336,7 +2340,7 @@ mod tests {
// Send new payload
let result =
env.send_new_payload_retry_on_syncing(block2.clone().into()).await.unwrap();
env.send_new_payload_retry_on_syncing(block2.clone().into(), None).await.unwrap();
let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Valid)
.with_latest_valid_hash(block2.hash);
assert_eq!(result, expected_result);
@@ -2434,7 +2438,7 @@ mod tests {
// Send new payload
let block = random_block(&mut rng, 2, Some(H256::random()), None, Some(0));
let res = env.send_new_payload(block.into()).await;
let res = env.send_new_payload(block.into(), None).await;
let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing);
assert_matches!(res, Ok(result) => assert_eq!(result, expected_result));
@@ -2497,7 +2501,7 @@ mod tests {
// Send new payload
let result =
env.send_new_payload_retry_on_syncing(block2.clone().into()).await.unwrap();
env.send_new_payload_retry_on_syncing(block2.clone().into(), None).await.unwrap();
let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Invalid {
validation_error: BlockValidationError::BlockPreMerge { hash: block2.hash }

View File

@@ -69,8 +69,9 @@ impl<DB> TestEnv<DB> {
pub async fn send_new_payload(
&self,
payload: ExecutionPayload,
parent_beacon_block_root: Option<H256>,
) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
self.engine_handle.new_payload(payload).await
self.engine_handle.new_payload(payload, parent_beacon_block_root).await
}
/// Sends the `ExecutionPayload` message to the consensus engine and retries if the engine
@@ -78,9 +79,10 @@ impl<DB> TestEnv<DB> {
pub async fn send_new_payload_retry_on_syncing(
&self,
payload: ExecutionPayload,
parent_beacon_block_root: Option<H256>,
) -> Result<PayloadStatus, BeaconOnNewPayloadError> {
loop {
let result = self.send_new_payload(payload.clone()).await?;
let result = self.send_new_payload(payload.clone(), parent_beacon_block_root).await?;
if !result.is_syncing() {
return Ok(result)
}

View File

@@ -50,6 +50,8 @@ pub fn validate_header_standalone(
return Err(ConsensusError::BlobGasUsedUnexpected)
} else if header.excess_blob_gas.is_some() {
return Err(ConsensusError::ExcessBlobGasUnexpected)
} else if header.parent_beacon_block_root.is_some() {
return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
}
Ok(())
@@ -451,6 +453,7 @@ pub fn validate_4844_header_with_parent(
///
/// * `blob_gas_used` exists as a header field
/// * `excess_blob_gas` exists as a header field
/// * `parent_beacon_block_root` exists as a header field
/// * `blob_gas_used` is less than or equal to `MAX_DATA_GAS_PER_BLOCK`
/// * `blob_gas_used` is a multiple of `DATA_GAS_PER_BLOB`
pub fn validate_4844_header_standalone(header: &SealedHeader) -> Result<(), ConsensusError> {
@@ -460,6 +463,10 @@ pub fn validate_4844_header_standalone(header: &SealedHeader) -> Result<(), Cons
return Err(ConsensusError::ExcessBlobGasMissing)
}
if header.parent_beacon_block_root.is_none() {
return Err(ConsensusError::ParentBeaconBlockRootMissing)
}
if blob_gas_used > MAX_DATA_GAS_PER_BLOCK {
return Err(ConsensusError::BlobGasUsedExceedsMaxBlobGasPerBlock {
blob_gas_used,
@@ -633,6 +640,7 @@ mod tests {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
};
// size: 0x9b5

View File

@@ -143,6 +143,10 @@ pub enum ConsensusError {
ExcessBlobGasMissing,
#[error("Unexpected excess blob gas")]
ExcessBlobGasUnexpected,
#[error("Missing parent beacon block root")]
ParentBeaconBlockRootMissing,
#[error("Unexpected parent beacon block root")]
ParentBeaconBlockRootUnexpected,
#[error("Blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
BlobGasUsedExceedsMaxBlobGasPerBlock { blob_gas_used: u64, max_blob_gas_per_block: u64 },
#[error(

View File

@@ -260,6 +260,7 @@ mod test {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
]),
}.encode(&mut data);
@@ -293,6 +294,7 @@ mod test {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
]),
};
@@ -407,6 +409,7 @@ mod test {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
],
withdrawals: None,
@@ -493,6 +496,7 @@ mod test {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
],
withdrawals: None,

View File

@@ -785,6 +785,7 @@ where
extra_data: extra_data.into(),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
};
// seal the block
@@ -856,6 +857,7 @@ where
blob_gas_used: None,
excess_blob_gas: None,
extra_data: extra_data.into(),
parent_beacon_block_root: None,
};
let block = Block { header, body: vec![], ommers: vec![], withdrawals };

View File

@@ -100,6 +100,9 @@ pub struct Header {
/// with above-target blob gas consumption increase this value, blocks with below-target blob
/// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
pub excess_blob_gas: Option<u64>,
/// TODO: Docs
/// This was added in EIP-4788.
pub parent_beacon_block_root: Option<H256>,
/// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
/// fewer; formally Hx.
pub extra_data: Bytes,
@@ -127,6 +130,7 @@ impl Default for Header {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
}
}
}
@@ -227,6 +231,7 @@ impl Header {
mem::size_of::<Option<u64>>() + // base fee per gas
mem::size_of::<Option<u64>>() + // blob gas used
mem::size_of::<Option<u64>>() + // excess blob gas
mem::size_of::<Option<H256>>() + // parent beacon block root
self.extra_data.len() // extra data
}
@@ -252,32 +257,42 @@ impl Header {
length += U256::from(base_fee).length();
} else if self.withdrawals_root.is_some() ||
self.blob_gas_used.is_some() ||
self.excess_blob_gas.is_some()
self.excess_blob_gas.is_some() ||
self.parent_beacon_block_root.is_some()
{
length += 1; // EMPTY STRING CODE
length += 1; // EMPTY LIST CODE
}
if let Some(root) = self.withdrawals_root {
length += root.length();
} else if self.blob_gas_used.is_some() || self.excess_blob_gas.is_some() {
} else if self.blob_gas_used.is_some() ||
self.excess_blob_gas.is_some() ||
self.parent_beacon_block_root.is_some()
{
length += 1; // EMPTY STRING CODE
}
if let Some(blob_gas_used) = self.blob_gas_used {
length += U256::from(blob_gas_used).length();
} else if self.excess_blob_gas.is_some() {
length += 1; // EMPTY STRING CODE
} else if self.excess_blob_gas.is_some() || self.parent_beacon_block_root.is_some() {
length += 1; // EMPTY LIST CODE
}
// Encode excess blob gas length. If new fields are added, the above pattern will need to
// be repeated and placeholder length added. Otherwise, it's impossible to tell _which_
// fields are missing. This is mainly relevant for contrived cases where a header is
// created at random, for example:
if let Some(excess_blob_gas) = self.excess_blob_gas {
length += U256::from(excess_blob_gas).length();
} else if self.parent_beacon_block_root.is_some() {
length += 1; // EMPTY LIST CODE
}
// Encode parent beacon block root length. If new fields are added, the above pattern will
// need to be repeated and placeholder length added. Otherwise, it's impossible to
// tell _which_ fields are missing. This is mainly relevant for contrived cases
// where a header is created at random, for example:
// * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
// post-London, so this is technically not valid. However, a tool like proptest would
// generate a block like this.
if let Some(excess_blob_gas) = self.excess_blob_gas {
length += U256::from(excess_blob_gas).length();
if let Some(parent_beacon_block_root) = self.parent_beacon_block_root {
length += parent_beacon_block_root.length();
}
length
@@ -305,42 +320,54 @@ impl Encodable for Header {
self.mix_hash.encode(out);
H64::from_low_u64_be(self.nonce).encode(out);
// Encode base fee. Put empty string if base fee is missing,
// Encode base fee. Put empty list if base fee is missing,
// but withdrawals root is present.
if let Some(ref base_fee) = self.base_fee_per_gas {
U256::from(*base_fee).encode(out);
} else if self.withdrawals_root.is_some() ||
self.blob_gas_used.is_some() ||
self.excess_blob_gas.is_some()
self.excess_blob_gas.is_some() ||
self.parent_beacon_block_root.is_some()
{
out.put_u8(EMPTY_STRING_CODE);
out.put_u8(EMPTY_LIST_CODE);
}
// Encode withdrawals root. Put empty string if withdrawals root is missing,
// but blob gas used is present.
if let Some(ref root) = self.withdrawals_root {
root.encode(out);
} else if self.blob_gas_used.is_some() || self.excess_blob_gas.is_some() {
} else if self.blob_gas_used.is_some() ||
self.excess_blob_gas.is_some() ||
self.parent_beacon_block_root.is_some()
{
out.put_u8(EMPTY_STRING_CODE);
}
// Encode blob gas used. Put empty string if blob gas used is missing,
// Encode blob gas used. Put empty list if blob gas used is missing,
// but excess blob gas is present.
if let Some(ref blob_gas_used) = self.blob_gas_used {
U256::from(*blob_gas_used).encode(out);
} else if self.excess_blob_gas.is_some() {
} else if self.excess_blob_gas.is_some() || self.parent_beacon_block_root.is_some() {
out.put_u8(EMPTY_LIST_CODE);
}
// Encode excess blob gas. If new fields are added, the above pattern will need to be
// repeated and placeholders added. Otherwise, it's impossible to tell _which_ fields
// are missing. This is mainly relevant for contrived cases where a header is created
// at random, for example:
// Encode excess blob gas. Put empty list if excess blob gas is missing,
// but parent beacon block root is present.
if let Some(ref excess_blob_gas) = self.excess_blob_gas {
U256::from(*excess_blob_gas).encode(out);
} else if self.parent_beacon_block_root.is_some() {
out.put_u8(EMPTY_LIST_CODE);
}
// Encode parent beacon block root. If new fields are added, the above pattern will need to
// be repeated and placeholders added. Otherwise, it's impossible to tell _which_
// fields are missing. This is mainly relevant for contrived cases where a header is
// created at random, for example:
// * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
// post-London, so this is technically not valid. However, a tool like proptest would
// generate a block like this.
if let Some(ref excess_blob_gas) = self.excess_blob_gas {
U256::from(*excess_blob_gas).encode(out);
if let Some(ref parent_beacon_block_root) = self.parent_beacon_block_root {
parent_beacon_block_root.encode(out);
}
}
@@ -379,10 +406,11 @@ impl Decodable for Header {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
};
if started_len - buf.len() < rlp_head.payload_length {
if buf.first().map(|b| *b == EMPTY_STRING_CODE).unwrap_or_default() {
if buf.first().map(|b| *b == EMPTY_LIST_CODE).unwrap_or_default() {
buf.advance(1)
} else {
this.base_fee_per_gas = Some(U256::decode(buf)?.to::<u64>());
@@ -407,15 +435,23 @@ impl Decodable for Header {
}
}
// Decode excess blob gas. If new fields are added, the above pattern will need to be
// repeated and placeholders decoded. Otherwise, it's impossible to tell _which_ fields are
// missing. This is mainly relevant for contrived cases where a header is created at
// random, for example:
if started_len - buf.len() < rlp_head.payload_length {
if buf.first().map(|b| *b == EMPTY_LIST_CODE).unwrap_or_default() {
buf.advance(1)
} else {
this.excess_blob_gas = Some(U256::decode(buf)?.to::<u64>());
}
}
// Decode parent beacon block root. If new fields are added, the above pattern will need to
// be repeated and placeholders decoded. Otherwise, it's impossible to tell _which_
// fields are missing. This is mainly relevant for contrived cases where a header is
// created at random, for example:
// * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
// post-London, so this is technically not valid. However, a tool like proptest would
// generate a block like this.
if started_len - buf.len() < rlp_head.payload_length {
this.excess_blob_gas = Some(U256::decode(buf)?.to::<u64>());
this.parent_beacon_block_root = Some(H256::decode(buf)?);
}
let consumed = started_len - buf.len();
@@ -642,6 +678,7 @@ mod ethers_compat {
logs_bloom: block.logs_bloom.unwrap_or_default().0.into(),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
}
}
}
@@ -713,6 +750,7 @@ mod tests {
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
};
assert_eq!(header.hash_slow(), expected_hash);
}
@@ -836,6 +874,7 @@ mod tests {
),
blob_gas_used: Some(0x020000),
excess_blob_gas: Some(0),
parent_beacon_block_root: None,
};
let header = Header::decode(&mut data.as_slice()).unwrap();
@@ -892,6 +931,7 @@ mod tests {
H256::from_str("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
.unwrap(),
),
parent_beacon_block_root: None,
blob_gas_used: Some(0),
excess_blob_gas: Some(0x1600000),
};

View File

@@ -75,7 +75,7 @@ where
EngineApiMessageVersion::V1,
PayloadOrAttributes::from_execution_payload(&payload, None),
)?;
Ok(self.inner.beacon_consensus.new_payload(payload).await?)
Ok(self.inner.beacon_consensus.new_payload(payload, None).await?)
}
/// See also <https://github.com/ethereum/execution-apis/blob/3d627c95a4d3510a8187dd02e0250ecb4331d27e/src/engine/shanghai.md#engine_newpayloadv2>
@@ -87,7 +87,7 @@ where
EngineApiMessageVersion::V2,
PayloadOrAttributes::from_execution_payload(&payload, None),
)?;
Ok(self.inner.beacon_consensus.new_payload(payload).await?)
Ok(self.inner.beacon_consensus.new_payload(payload, None).await?)
}
/// See also <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#engine_newpayloadv3>
@@ -103,7 +103,7 @@ where
)?;
// TODO: validate versioned hashes and figure out what to do with parent_beacon_block_root
Ok(self.inner.beacon_consensus.new_payload(payload).await?)
Ok(self.inner.beacon_consensus.new_payload(payload, Some(parent_beacon_block_root)).await?)
}
/// Sends a message to the beacon consensus engine to update the fork choice _without_
@@ -443,6 +443,8 @@ where
Ok(EngineApi::new_payload_v2(self, payload).await?)
}
/// Handler for `engine_newPayloadV3`
/// See also <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#engine_newpayloadv3>
async fn new_payload_v3(
&self,
_payload: ExecutionPayload,
@@ -517,6 +519,15 @@ where
Ok(EngineApi::get_payload_v2(self, payload_id).await?)
}
/// Handler for `engine_getPayloadV3`
///
/// Returns the most recent version of the payload that is available in the corresponding
/// payload build process at the time of receiving this call.
///
/// See also <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#engine_getpayloadv3>
///
/// Note:
/// > Provider software MAY stop the corresponding build process after serving this call.
async fn get_payload_v3(&self, _payload_id: PayloadId) -> RpcResult<ExecutionPayloadEnvelope> {
Err(jsonrpsee_types::error::ErrorCode::MethodNotFound.into())
}

View File

@@ -57,7 +57,7 @@ fn payload_validation() {
b.header.extra_data = BytesMut::zeroed(32).freeze().into();
b
});
assert_matches!(TryInto::<SealedBlock>::try_into(block_with_valid_extra_data), Ok(_));
assert_matches!(block_with_valid_extra_data.try_into_sealed_block(None), Ok(_));
// Invalid extra data
let block_with_invalid_extra_data: Bytes = BytesMut::zeroed(33).freeze();
@@ -66,7 +66,7 @@ fn payload_validation() {
b
});
assert_matches!(
TryInto::<SealedBlock>::try_into(invalid_extra_data_block),
invalid_extra_data_block.try_into_sealed_block(None),
Err(PayloadError::ExtraData(data)) if data == block_with_invalid_extra_data
);
@@ -76,7 +76,7 @@ fn payload_validation() {
b
});
assert_matches!(
TryInto::<SealedBlock>::try_into(block_with_zero_base_fee),
block_with_zero_base_fee.try_into_sealed_block(None),
Err(PayloadError::BaseFee(val)) if val == U256::ZERO
);
@@ -86,7 +86,7 @@ fn payload_validation() {
*tx = Bytes::new().into();
});
assert_matches!(
TryInto::<SealedBlock>::try_into(payload_with_invalid_txs),
payload_with_invalid_txs.try_into_sealed_block(None),
Err(PayloadError::Decode(DecodeError::InputTooShort))
);
@@ -96,7 +96,7 @@ fn payload_validation() {
b
});
assert_matches!(
TryInto::<SealedBlock>::try_into(block_with_ommers.clone()),
block_with_ommers.clone().try_into_sealed_block(None),
Err(PayloadError::BlockHash { consensus, .. })
if consensus == block_with_ommers.block_hash
);
@@ -107,7 +107,7 @@ fn payload_validation() {
b
});
assert_matches!(
TryInto::<SealedBlock>::try_into(block_with_difficulty.clone()),
block_with_difficulty.clone().try_into_sealed_block(None),
Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_difficulty.block_hash
);
@@ -117,7 +117,7 @@ fn payload_validation() {
b
});
assert_matches!(
TryInto::<SealedBlock>::try_into(block_with_nonce.clone()),
block_with_nonce.clone().try_into_sealed_block(None),
Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_nonce.block_hash
);

View File

@@ -131,6 +131,9 @@ pub struct Header {
/// Excess blob gas
#[serde(rename = "excessBlobGas", skip_serializing_if = "Option::is_none")]
pub excess_blob_gas: Option<U64>,
/// Parent beacon block root
#[serde(rename = "parentBeaconBlockRoot", skip_serializing_if = "Option::is_none")]
pub parent_beacon_block_root: Option<H256>,
}
// === impl Header ===
@@ -162,6 +165,7 @@ impl Header {
withdrawals_root,
blob_gas_used,
excess_blob_gas,
parent_beacon_block_root,
},
hash,
} = primitive_header;
@@ -187,6 +191,7 @@ impl Header {
base_fee_per_gas: base_fee_per_gas.map(U256::from),
blob_gas_used: blob_gas_used.map(U64::from),
excess_blob_gas: excess_blob_gas.map(U64::from),
parent_beacon_block_root,
}
}
}
@@ -318,6 +323,7 @@ mod tests {
base_fee_per_gas: Some(U256::from(20)),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
total_difficulty: Some(U256::from(100000)),
uncles: vec![H256::from_low_u64_be(17)],
@@ -358,6 +364,7 @@ mod tests {
base_fee_per_gas: Some(U256::from(20)),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
},
total_difficulty: Some(U256::from(100000)),
uncles: vec![H256::from_low_u64_be(17)],

View File

@@ -135,27 +135,28 @@ impl From<SealedBlock> for ExecutionPayload {
}
}
/// Try to construct a block from given payload. Perform addition validation of `extra_data` and
/// `base_fee_per_gas` fields.
///
/// NOTE: The log bloom is assumed to be validated during serialization.
/// NOTE: Empty ommers, nonce and difficulty values are validated upon computing block hash and
/// comparing the value with `payload.block_hash`.
///
/// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
impl TryFrom<ExecutionPayload> for SealedBlock {
type Error = PayloadError;
fn try_from(payload: ExecutionPayload) -> Result<Self, Self::Error> {
if payload.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
return Err(PayloadError::ExtraData(payload.extra_data))
impl ExecutionPayload {
/// Tries to create a new block from the given payload and optional parent beacon block root.
/// Perform additional validation of `extra_data` and `base_fee_per_gas` fields.
///
/// NOTE: The log bloom is assumed to be validated during serialization.
/// NOTE: Empty ommers, nonce and difficulty values are validated upon computing block hash and
/// comparing the value with `payload.block_hash`.
///
/// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
pub fn try_into_sealed_block(
self,
parent_beacon_block_root: Option<H256>,
) -> Result<SealedBlock, PayloadError> {
if self.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
return Err(PayloadError::ExtraData(self.extra_data))
}
if payload.base_fee_per_gas < MIN_PROTOCOL_BASE_FEE_U256 {
return Err(PayloadError::BaseFee(payload.base_fee_per_gas))
if self.base_fee_per_gas < MIN_PROTOCOL_BASE_FEE_U256 {
return Err(PayloadError::BaseFee(self.base_fee_per_gas))
}
let transactions = payload
let transactions = self
.transactions
.iter()
.map(|tx| TransactionSigned::decode(&mut tx.as_ref()))
@@ -163,32 +164,30 @@ impl TryFrom<ExecutionPayload> for SealedBlock {
let transactions_root = proofs::calculate_transaction_root(&transactions);
let withdrawals_root =
payload.withdrawals.as_ref().map(|w| proofs::calculate_withdrawals_root(w));
self.withdrawals.as_ref().map(|w| proofs::calculate_withdrawals_root(w));
let header = Header {
parent_hash: payload.parent_hash,
beneficiary: payload.fee_recipient,
state_root: payload.state_root,
parent_hash: self.parent_hash,
beneficiary: self.fee_recipient,
state_root: self.state_root,
transactions_root,
receipts_root: payload.receipts_root,
receipts_root: self.receipts_root,
withdrawals_root,
logs_bloom: payload.logs_bloom,
number: payload.block_number.as_u64(),
gas_limit: payload.gas_limit.as_u64(),
gas_used: payload.gas_used.as_u64(),
timestamp: payload.timestamp.as_u64(),
mix_hash: payload.prev_randao,
parent_beacon_block_root,
logs_bloom: self.logs_bloom,
number: self.block_number.as_u64(),
gas_limit: self.gas_limit.as_u64(),
gas_used: self.gas_used.as_u64(),
timestamp: self.timestamp.as_u64(),
mix_hash: self.prev_randao,
base_fee_per_gas: Some(
payload
.base_fee_per_gas
self.base_fee_per_gas
.uint_try_to()
.map_err(|_| PayloadError::BaseFee(payload.base_fee_per_gas))?,
.map_err(|_| PayloadError::BaseFee(self.base_fee_per_gas))?,
),
blob_gas_used: payload.blob_gas_used.map(|blob_gas_used| blob_gas_used.as_u64()),
excess_blob_gas: payload
.excess_blob_gas
.map(|excess_blob_gas| excess_blob_gas.as_u64()),
extra_data: payload.extra_data,
blob_gas_used: self.blob_gas_used.map(|blob_gas_used| blob_gas_used.as_u64()),
excess_blob_gas: self.excess_blob_gas.map(|excess_blob_gas| excess_blob_gas.as_u64()),
extra_data: self.extra_data,
// Defaults
ommers_hash: EMPTY_LIST_HASH,
difficulty: Default::default(),
@@ -196,17 +195,17 @@ impl TryFrom<ExecutionPayload> for SealedBlock {
}
.seal_slow();
if payload.block_hash != header.hash() {
if self.block_hash != header.hash() {
return Err(PayloadError::BlockHash {
execution: header.hash(),
consensus: payload.block_hash,
consensus: self.block_hash,
})
}
Ok(SealedBlock {
header,
body: transactions,
withdrawals: payload.withdrawals,
withdrawals: self.withdrawals,
ommers: Default::default(),
})
}

View File

@@ -145,6 +145,7 @@ impl PendingBlockEnv {
blob_gas_used: None,
excess_blob_gas: None,
extra_data: Default::default(),
parent_beacon_block_root: None,
};
// seal the block

View File

@@ -82,6 +82,8 @@ pub struct Header {
pub blob_gas_used: Option<JsonU256>,
/// Excess blob gas.
pub excess_blob_gas: Option<JsonU256>,
/// Parent beacon block root.
pub parent_beacon_block_root: Option<H256>,
}
impl From<Header> for SealedHeader {
@@ -106,6 +108,7 @@ impl From<Header> for SealedHeader {
withdrawals_root: value.withdrawals_root,
blob_gas_used: value.blob_gas_used.map(|v| v.0.to::<u64>()),
excess_blob_gas: value.excess_blob_gas.map(|v| v.0.to::<u64>()),
parent_beacon_block_root: value.parent_beacon_block_root,
};
header.seal(value.hash)
}