mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-22 22:34:45 +00:00
Separate execution payloads in the DB (#3157)
## Proposed Changes Reduce post-merge disk usage by not storing finalized execution payloads in Lighthouse's database. ⚠️ **This is achieved in a backwards-incompatible way for networks that have already merged** ⚠️. Kiln users and shadow fork enjoyers will be unable to downgrade after running the code from this PR. The upgrade migration may take several minutes to run, and can't be aborted after it begins. The main changes are: - New column in the database called `ExecPayload`, keyed by beacon block root. - The `BeaconBlock` column now stores blinded blocks only. - Lots of places that previously used full blocks now use blinded blocks, e.g. analytics APIs, block replay in the DB, etc. - On finalization: - `prune_abanonded_forks` deletes non-canonical payloads whilst deleting non-canonical blocks. - `migrate_db` deletes finalized canonical payloads whilst deleting finalized states. - Conversions between blinded and full blocks are implemented in a compositional way, duplicating some work from Sean's PR #3134. - The execution layer has a new `get_payload_by_block_hash` method that reconstructs a payload using the EE's `eth_getBlockByHash` call. - I've tested manually that it works on Kiln, using Geth and Nethermind. - This isn't necessarily the most efficient method, and new engine APIs are being discussed to improve this: https://github.com/ethereum/execution-apis/pull/146. - We're depending on the `ethers` master branch, due to lots of recent changes. We're also using a workaround for https://github.com/gakonst/ethers-rs/issues/1134. - Payload reconstruction is used in the HTTP API via `BeaconChain::get_block`, which is now `async`. Due to the `async` fn, the `blocking_json` wrapper has been removed. - Payload reconstruction is used in network RPC to serve blocks-by-{root,range} responses. Here the `async` adjustment is messier, although I think I've managed to come up with a reasonable compromise: the handlers take the `SendOnDrop` by value so that they can drop it on _task completion_ (after the `fn` returns). Still, this is introducing disk reads onto core executor threads, which may have a negative performance impact (thoughts appreciated). ## Additional Info - [x] For performance it would be great to remove the cloning of full blocks when converting them to blinded blocks to write to disk. I'm going to experiment with a `put_block` API that takes the block by value, breaks it into a blinded block and a payload, stores the blinded block, and then re-assembles the full block for the caller. - [x] We should measure the latency of blocks-by-root and blocks-by-range responses. - [x] We should add integration tests that stress the payload reconstruction (basic tests done, issue for more extensive tests: https://github.com/sigp/lighthouse/issues/3159) - [x] We should (manually) test the schema v9 migration from several prior versions, particularly as blocks have changed on disk and some migrations rely on being able to load blocks. Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
@@ -37,7 +37,9 @@ use tree_hash_derive::TreeHash;
|
||||
ref_attributes(
|
||||
derive(Debug, PartialEq, TreeHash),
|
||||
tree_hash(enum_behaviour = "transparent")
|
||||
)
|
||||
),
|
||||
map_ref_into(BeaconBlockBodyRef),
|
||||
map_ref_mut_into(BeaconBlockBodyRefMut)
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)]
|
||||
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
|
||||
@@ -199,20 +201,17 @@ impl<'a, T: EthSpec, Payload: ExecPayload<T>> BeaconBlockRef<'a, T, Payload> {
|
||||
|
||||
/// Convenience accessor for the `body` as a `BeaconBlockBodyRef`.
|
||||
pub fn body(&self) -> BeaconBlockBodyRef<'a, T, Payload> {
|
||||
match self {
|
||||
BeaconBlockRef::Base(block) => BeaconBlockBodyRef::Base(&block.body),
|
||||
BeaconBlockRef::Altair(block) => BeaconBlockBodyRef::Altair(&block.body),
|
||||
BeaconBlockRef::Merge(block) => BeaconBlockBodyRef::Merge(&block.body),
|
||||
}
|
||||
map_beacon_block_ref_into_beacon_block_body_ref!(&'a _, *self, |block, cons| cons(
|
||||
&block.body
|
||||
))
|
||||
}
|
||||
|
||||
/// Return the tree hash root of the block's body.
|
||||
pub fn body_root(&self) -> Hash256 {
|
||||
match self {
|
||||
BeaconBlockRef::Base(block) => block.body.tree_hash_root(),
|
||||
BeaconBlockRef::Altair(block) => block.body.tree_hash_root(),
|
||||
BeaconBlockRef::Merge(block) => block.body.tree_hash_root(),
|
||||
}
|
||||
map_beacon_block_ref!(&'a _, *self, |block, cons| {
|
||||
let _: Self = cons(block);
|
||||
block.body.tree_hash_root()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the epoch corresponding to `self.slot()`.
|
||||
@@ -249,11 +248,9 @@ impl<'a, T: EthSpec, Payload: ExecPayload<T>> BeaconBlockRef<'a, T, Payload> {
|
||||
impl<'a, T: EthSpec, Payload: ExecPayload<T>> BeaconBlockRefMut<'a, T, Payload> {
|
||||
/// Convert a mutable reference to a beacon block to a mutable ref to its body.
|
||||
pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T, Payload> {
|
||||
match self {
|
||||
BeaconBlockRefMut::Base(block) => BeaconBlockBodyRefMut::Base(&mut block.body),
|
||||
BeaconBlockRefMut::Altair(block) => BeaconBlockBodyRefMut::Altair(&mut block.body),
|
||||
BeaconBlockRefMut::Merge(block) => BeaconBlockBodyRefMut::Merge(&mut block.body),
|
||||
}
|
||||
map_beacon_block_ref_mut_into_beacon_block_body_ref_mut!(&'a _, self, |block, cons| cons(
|
||||
&mut block.body
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,6 +462,99 @@ impl<T: EthSpec, Payload: ExecPayload<T>> BeaconBlockMerge<T, Payload> {
|
||||
}
|
||||
}
|
||||
|
||||
// We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads.
|
||||
impl<E: EthSpec> From<BeaconBlockBase<E, BlindedPayload<E>>>
|
||||
for BeaconBlockBase<E, FullPayload<E>>
|
||||
{
|
||||
fn from(block: BeaconBlockBase<E, BlindedPayload<E>>) -> Self {
|
||||
let BeaconBlockBase {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body,
|
||||
} = block;
|
||||
|
||||
BeaconBlockBase {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body: body.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlockAltair<E, BlindedPayload<E>>>
|
||||
for BeaconBlockAltair<E, FullPayload<E>>
|
||||
{
|
||||
fn from(block: BeaconBlockAltair<E, BlindedPayload<E>>) -> Self {
|
||||
let BeaconBlockAltair {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body,
|
||||
} = block;
|
||||
|
||||
BeaconBlockAltair {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body: body.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can convert blocks with payloads to blocks without payloads, and an optional payload.
|
||||
macro_rules! impl_from {
|
||||
($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>, $body_expr:expr) => {
|
||||
impl<E: EthSpec> From<$ty_name<$($from_params),*>>
|
||||
for ($ty_name<$($to_params),*>, Option<ExecutionPayload<E>>)
|
||||
{
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
fn from(block: $ty_name<$($from_params),*>) -> Self {
|
||||
let $ty_name {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body,
|
||||
} = block;
|
||||
|
||||
let (body, payload) = ($body_expr)(body);
|
||||
|
||||
($ty_name {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body,
|
||||
}, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from!(BeaconBlockBase, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyBase<_, _>| body.into());
|
||||
impl_from!(BeaconBlockAltair, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyAltair<_, _>| body.into());
|
||||
impl_from!(BeaconBlockMerge, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyMerge<_, _>| body.into());
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlock<E, FullPayload<E>>>
|
||||
for (
|
||||
BeaconBlock<E, BlindedPayload<E>>,
|
||||
Option<ExecutionPayload<E>>,
|
||||
)
|
||||
{
|
||||
fn from(block: BeaconBlock<E, FullPayload<E>>) -> Self {
|
||||
map_beacon_block!(block, |inner, cons| {
|
||||
let (block, payload) = inner.into();
|
||||
(cons(block), payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -73,6 +73,198 @@ impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// We can convert pre-Bellatrix block bodies without payloads into block bodies "with" payloads.
|
||||
impl<E: EthSpec> From<BeaconBlockBodyBase<E, BlindedPayload<E>>>
|
||||
for BeaconBlockBodyBase<E, FullPayload<E>>
|
||||
{
|
||||
fn from(body: BeaconBlockBodyBase<E, BlindedPayload<E>>) -> Self {
|
||||
let BeaconBlockBodyBase {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
_phantom,
|
||||
} = body;
|
||||
|
||||
BeaconBlockBodyBase {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlockBodyAltair<E, BlindedPayload<E>>>
|
||||
for BeaconBlockBodyAltair<E, FullPayload<E>>
|
||||
{
|
||||
fn from(body: BeaconBlockBodyAltair<E, BlindedPayload<E>>) -> Self {
|
||||
let BeaconBlockBodyAltair {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
_phantom,
|
||||
} = body;
|
||||
|
||||
BeaconBlockBodyAltair {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Likewise bodies with payloads can be transformed into bodies without.
|
||||
impl<E: EthSpec> From<BeaconBlockBodyBase<E, FullPayload<E>>>
|
||||
for (
|
||||
BeaconBlockBodyBase<E, BlindedPayload<E>>,
|
||||
Option<ExecutionPayload<E>>,
|
||||
)
|
||||
{
|
||||
fn from(body: BeaconBlockBodyBase<E, FullPayload<E>>) -> Self {
|
||||
let BeaconBlockBodyBase {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
_phantom,
|
||||
} = body;
|
||||
|
||||
(
|
||||
BeaconBlockBodyBase {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlockBodyAltair<E, FullPayload<E>>>
|
||||
for (
|
||||
BeaconBlockBodyAltair<E, BlindedPayload<E>>,
|
||||
Option<ExecutionPayload<E>>,
|
||||
)
|
||||
{
|
||||
fn from(body: BeaconBlockBodyAltair<E, FullPayload<E>>) -> Self {
|
||||
let BeaconBlockBodyAltair {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
_phantom,
|
||||
} = body;
|
||||
|
||||
(
|
||||
BeaconBlockBodyAltair {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlockBodyMerge<E, FullPayload<E>>>
|
||||
for (
|
||||
BeaconBlockBodyMerge<E, BlindedPayload<E>>,
|
||||
Option<ExecutionPayload<E>>,
|
||||
)
|
||||
{
|
||||
fn from(body: BeaconBlockBodyMerge<E, FullPayload<E>>) -> Self {
|
||||
let BeaconBlockBodyMerge {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
execution_payload: FullPayload { execution_payload },
|
||||
} = body;
|
||||
|
||||
(
|
||||
BeaconBlockBodyMerge {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
execution_payload: BlindedPayload {
|
||||
execution_payload_header: From::from(&execution_payload),
|
||||
},
|
||||
},
|
||||
Some(execution_payload),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlockBody<E, FullPayload<E>>>
|
||||
for (
|
||||
BeaconBlockBody<E, BlindedPayload<E>>,
|
||||
Option<ExecutionPayload<E>>,
|
||||
)
|
||||
{
|
||||
fn from(body: BeaconBlockBody<E, FullPayload<E>>) -> Self {
|
||||
map_beacon_block_body!(body, |inner, cons| {
|
||||
let (block, payload) = inner.into();
|
||||
(cons(block), payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod base {
|
||||
|
||||
@@ -139,7 +139,7 @@ pub use crate::shuffling_id::AttestationShufflingId;
|
||||
pub use crate::signed_aggregate_and_proof::SignedAggregateAndProof;
|
||||
pub use crate::signed_beacon_block::{
|
||||
SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockHash,
|
||||
SignedBeaconBlockMerge,
|
||||
SignedBeaconBlockMerge, SignedBlindedBeaconBlock,
|
||||
};
|
||||
pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader;
|
||||
pub use crate::signed_contribution_and_proof::SignedContributionAndProof;
|
||||
|
||||
@@ -15,7 +15,9 @@ pub enum BlockType {
|
||||
}
|
||||
|
||||
pub trait ExecPayload<T: EthSpec>:
|
||||
Encode
|
||||
Debug
|
||||
+ Clone
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ TestRandom
|
||||
+ TreeHash
|
||||
@@ -37,6 +39,7 @@ pub trait ExecPayload<T: EthSpec>:
|
||||
// More fields can be added here if you wish.
|
||||
fn parent_hash(&self) -> ExecutionBlockHash;
|
||||
fn prev_randao(&self) -> Hash256;
|
||||
fn block_number(&self) -> u64;
|
||||
fn timestamp(&self) -> u64;
|
||||
fn block_hash(&self) -> ExecutionBlockHash;
|
||||
}
|
||||
@@ -58,6 +61,10 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
|
||||
self.execution_payload.prev_randao
|
||||
}
|
||||
|
||||
fn block_number(&self) -> u64 {
|
||||
self.execution_payload.block_number
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> u64 {
|
||||
self.execution_payload.timestamp
|
||||
}
|
||||
@@ -84,6 +91,10 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
|
||||
self.execution_payload_header.prev_randao
|
||||
}
|
||||
|
||||
fn block_number(&self) -> u64 {
|
||||
self.execution_payload_header.block_number
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> u64 {
|
||||
self.execution_payload_header.timestamp
|
||||
}
|
||||
@@ -93,13 +104,28 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, TestRandom, Serialize, Deserialize, Derivative)]
|
||||
#[derive(Debug, Clone, TestRandom, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct BlindedPayload<T: EthSpec> {
|
||||
pub execution_payload_header: ExecutionPayloadHeader<T>,
|
||||
}
|
||||
|
||||
// NOTE: the `Default` implementation for `BlindedPayload` needs to be different from the `Default`
|
||||
// implementation for `ExecutionPayloadHeader` because payloads are checked for equality against the
|
||||
// default payload in `is_merge_transition_block` to determine whether the merge has occurred.
|
||||
//
|
||||
// The default `BlindedPayload` is therefore the payload header that results from blinding the
|
||||
// default `ExecutionPayload`, which differs from the default `ExecutionPayloadHeader` in that
|
||||
// its `transactions_root` is the hash of the empty list rather than 0x0.
|
||||
impl<T: EthSpec> Default for BlindedPayload<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
execution_payload_header: ExecutionPayloadHeader::from(&ExecutionPayload::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<ExecutionPayloadHeader<T>> for BlindedPayload<T> {
|
||||
fn from(execution_payload_header: ExecutionPayloadHeader<T>) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -53,7 +53,10 @@ impl From<SignedBeaconBlockHash> for Hash256 {
|
||||
derivative(PartialEq, Hash(bound = "E: EthSpec")),
|
||||
cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)),
|
||||
serde(bound = "E: EthSpec, Payload: ExecPayload<E>"),
|
||||
)
|
||||
),
|
||||
map_into(BeaconBlock),
|
||||
map_ref_into(BeaconBlockRef),
|
||||
map_ref_mut_into(BeaconBlockRefMut)
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)]
|
||||
#[derivative(PartialEq, Hash(bound = "E: EthSpec"))]
|
||||
@@ -72,6 +75,8 @@ pub struct SignedBeaconBlock<E: EthSpec, Payload: ExecPayload<E> = FullPayload<E
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
pub type SignedBlindedBeaconBlock<E> = SignedBeaconBlock<E, BlindedPayload<E>>;
|
||||
|
||||
impl<E: EthSpec, Payload: ExecPayload<E>> SignedBeaconBlock<E, Payload> {
|
||||
/// Returns the name of the fork pertaining to `self`.
|
||||
///
|
||||
@@ -132,31 +137,27 @@ impl<E: EthSpec, Payload: ExecPayload<E>> SignedBeaconBlock<E, Payload> {
|
||||
/// This is necessary to get a `&BeaconBlock` from a `SignedBeaconBlock` because
|
||||
/// `SignedBeaconBlock` only contains a `BeaconBlock` _variant_.
|
||||
pub fn deconstruct(self) -> (BeaconBlock<E, Payload>, Signature) {
|
||||
match self {
|
||||
SignedBeaconBlock::Base(block) => (BeaconBlock::Base(block.message), block.signature),
|
||||
SignedBeaconBlock::Altair(block) => {
|
||||
(BeaconBlock::Altair(block.message), block.signature)
|
||||
}
|
||||
SignedBeaconBlock::Merge(block) => (BeaconBlock::Merge(block.message), block.signature),
|
||||
}
|
||||
map_signed_beacon_block_into_beacon_block!(self, |block, beacon_block_cons| {
|
||||
(beacon_block_cons(block.message), block.signature)
|
||||
})
|
||||
}
|
||||
|
||||
/// Accessor for the block's `message` field as a ref.
|
||||
pub fn message(&self) -> BeaconBlockRef<'_, E, Payload> {
|
||||
match self {
|
||||
SignedBeaconBlock::Base(inner) => BeaconBlockRef::Base(&inner.message),
|
||||
SignedBeaconBlock::Altair(inner) => BeaconBlockRef::Altair(&inner.message),
|
||||
SignedBeaconBlock::Merge(inner) => BeaconBlockRef::Merge(&inner.message),
|
||||
}
|
||||
pub fn message<'a>(&'a self) -> BeaconBlockRef<'a, E, Payload> {
|
||||
map_signed_beacon_block_ref_into_beacon_block_ref!(
|
||||
&'a _,
|
||||
self.to_ref(),
|
||||
|inner, cons| cons(&inner.message)
|
||||
)
|
||||
}
|
||||
|
||||
/// Accessor for the block's `message` as a mutable reference (for testing only).
|
||||
pub fn message_mut(&mut self) -> BeaconBlockRefMut<'_, E, Payload> {
|
||||
match self {
|
||||
SignedBeaconBlock::Base(inner) => BeaconBlockRefMut::Base(&mut inner.message),
|
||||
SignedBeaconBlock::Altair(inner) => BeaconBlockRefMut::Altair(&mut inner.message),
|
||||
SignedBeaconBlock::Merge(inner) => BeaconBlockRefMut::Merge(&mut inner.message),
|
||||
}
|
||||
pub fn message_mut<'a>(&'a mut self) -> BeaconBlockRefMut<'a, E, Payload> {
|
||||
map_signed_beacon_block_ref_mut_into_beacon_block_ref_mut!(
|
||||
&'a _,
|
||||
self.to_mut(),
|
||||
|inner, cons| cons(&mut inner.message)
|
||||
)
|
||||
}
|
||||
|
||||
/// Verify `self.signature`.
|
||||
@@ -225,3 +226,165 @@ impl<E: EthSpec, Payload: ExecPayload<E>> SignedBeaconBlock<E, Payload> {
|
||||
self.message().tree_hash_root()
|
||||
}
|
||||
}
|
||||
|
||||
// We can convert pre-Bellatrix blocks without payloads into blocks with payloads.
|
||||
impl<E: EthSpec> From<SignedBeaconBlockBase<E, BlindedPayload<E>>>
|
||||
for SignedBeaconBlockBase<E, FullPayload<E>>
|
||||
{
|
||||
fn from(signed_block: SignedBeaconBlockBase<E, BlindedPayload<E>>) -> Self {
|
||||
let SignedBeaconBlockBase { message, signature } = signed_block;
|
||||
SignedBeaconBlockBase {
|
||||
message: message.into(),
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<SignedBeaconBlockAltair<E, BlindedPayload<E>>>
|
||||
for SignedBeaconBlockAltair<E, FullPayload<E>>
|
||||
{
|
||||
fn from(signed_block: SignedBeaconBlockAltair<E, BlindedPayload<E>>) -> Self {
|
||||
let SignedBeaconBlockAltair { message, signature } = signed_block;
|
||||
SignedBeaconBlockAltair {
|
||||
message: message.into(),
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-Bellatrix blocks can be "unblinded" by adding the full payload.
|
||||
// NOTE: It might be nice to come up with a `superstruct` pattern to abstract over this before
|
||||
// the first fork after Bellatrix.
|
||||
impl<E: EthSpec> SignedBeaconBlockMerge<E, BlindedPayload<E>> {
|
||||
pub fn into_full_block(
|
||||
self,
|
||||
execution_payload: ExecutionPayload<E>,
|
||||
) -> SignedBeaconBlockMerge<E, FullPayload<E>> {
|
||||
let SignedBeaconBlockMerge {
|
||||
message:
|
||||
BeaconBlockMerge {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body:
|
||||
BeaconBlockBodyMerge {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
execution_payload: BlindedPayload { .. },
|
||||
},
|
||||
},
|
||||
signature,
|
||||
} = self;
|
||||
SignedBeaconBlockMerge {
|
||||
message: BeaconBlockMerge {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body: BeaconBlockBodyMerge {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
execution_payload: FullPayload { execution_payload },
|
||||
},
|
||||
},
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> SignedBeaconBlock<E, BlindedPayload<E>> {
|
||||
pub fn try_into_full_block(
|
||||
self,
|
||||
execution_payload: Option<ExecutionPayload<E>>,
|
||||
) -> Option<SignedBeaconBlock<E, FullPayload<E>>> {
|
||||
let full_block = match self {
|
||||
SignedBeaconBlock::Base(block) => SignedBeaconBlock::Base(block.into()),
|
||||
SignedBeaconBlock::Altair(block) => SignedBeaconBlock::Altair(block.into()),
|
||||
SignedBeaconBlock::Merge(block) => {
|
||||
SignedBeaconBlock::Merge(block.into_full_block(execution_payload?))
|
||||
}
|
||||
};
|
||||
Some(full_block)
|
||||
}
|
||||
}
|
||||
|
||||
// We can blind blocks with payloads by converting the payload into a header.
|
||||
//
|
||||
// We can optionally keep the header, or discard it.
|
||||
impl<E: EthSpec> From<SignedBeaconBlock<E>>
|
||||
for (SignedBlindedBeaconBlock<E>, Option<ExecutionPayload<E>>)
|
||||
{
|
||||
fn from(signed_block: SignedBeaconBlock<E>) -> Self {
|
||||
let (block, signature) = signed_block.deconstruct();
|
||||
let (blinded_block, payload) = block.into();
|
||||
(
|
||||
SignedBeaconBlock::from_block(blinded_block, signature),
|
||||
payload,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<SignedBeaconBlock<E>> for SignedBlindedBeaconBlock<E> {
|
||||
fn from(signed_block: SignedBeaconBlock<E>) -> Self {
|
||||
let (blinded_block, _) = signed_block.into();
|
||||
blinded_block
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_remove_payload_roundtrip() {
|
||||
type E = MainnetEthSpec;
|
||||
|
||||
let spec = &E::default_spec();
|
||||
let sig = Signature::empty();
|
||||
let blocks = vec![
|
||||
SignedBeaconBlock::<E>::from_block(
|
||||
BeaconBlock::Base(BeaconBlockBase::empty(spec)),
|
||||
sig.clone(),
|
||||
),
|
||||
SignedBeaconBlock::from_block(
|
||||
BeaconBlock::Altair(BeaconBlockAltair::empty(spec)),
|
||||
sig.clone(),
|
||||
),
|
||||
SignedBeaconBlock::from_block(BeaconBlock::Merge(BeaconBlockMerge::empty(spec)), sig),
|
||||
];
|
||||
|
||||
for block in blocks {
|
||||
let (blinded_block, payload): (SignedBlindedBeaconBlock<E>, _) = block.clone().into();
|
||||
assert_eq!(blinded_block.tree_hash_root(), block.tree_hash_root());
|
||||
|
||||
if let Some(payload) = &payload {
|
||||
assert_eq!(
|
||||
payload.tree_hash_root(),
|
||||
block
|
||||
.message()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.tree_hash_root()
|
||||
);
|
||||
}
|
||||
|
||||
let reconstructed = blinded_block.try_into_full_block(payload).unwrap();
|
||||
assert_eq!(reconstructed, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user