From 2fd2825e245a43c4e9704f669e7d5b74e7eedc1f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 6 Apr 2023 20:48:31 +0200 Subject: [PATCH] feat: add Payload build abstraction (#2143) --- Cargo.lock | 12 +++ Cargo.toml | 1 + crates/miner/Cargo.toml | 19 +++++ crates/miner/src/error.rs | 6 ++ crates/miner/src/lib.rs | 74 +++++++++++++++++ crates/miner/src/payload.rs | 79 +++++++++++++++++++ crates/primitives/src/withdrawal.rs | 2 +- .../rpc-types/src/eth/engine/forkchoice.rs | 7 +- .../rpc/rpc-types/src/eth/engine/payload.rs | 15 +++- 9 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 crates/miner/Cargo.toml create mode 100644 crates/miner/src/error.rs create mode 100644 crates/miner/src/lib.rs create mode 100644 crates/miner/src/payload.rs diff --git a/Cargo.lock b/Cargo.lock index f05e91f71..85b2ac2bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4843,6 +4843,18 @@ dependencies = [ "trybuild", ] +[[package]] +name = "reth-miner" +version = "0.1.0" +dependencies = [ + "parking_lot 0.12.1", + "reth-primitives", + "reth-rlp", + "reth-rpc-types", + "sha2 0.10.6", + "thiserror", +] + [[package]] name = "reth-net-common" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 937b85a69..3805c7e1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/consensus/common", "crates/executor", "crates/interfaces", + "crates/miner", "crates/metrics/metrics-derive", "crates/metrics/common", "crates/net/common", diff --git a/crates/miner/Cargo.toml b/crates/miner/Cargo.toml new file mode 100644 index 000000000..d2dd9593e --- /dev/null +++ b/crates/miner/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "reth-miner" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/paradigmxyz/reth" +readme = "README.md" +description = "Block production" + +[dependencies] +## reth +reth-primitives = { path = "../primitives" } +reth-rpc-types = { path = "../rpc/rpc-types" } +reth-rlp = { path = "../rlp" } + +## misc +thiserror = "1.0" +sha2 = { version = "0.10.6", default-features = false } +parking_lot = "0.12.1" diff --git a/crates/miner/src/error.rs b/crates/miner/src/error.rs new file mode 100644 index 000000000..c0bc06e9c --- /dev/null +++ b/crates/miner/src/error.rs @@ -0,0 +1,6 @@ +//! Error types emitted by types or implementations of this crate. + +/// Possible error variants during payload building. +#[derive(Debug, thiserror::Error)] +#[error("Payload error")] +pub struct PayloadError; diff --git a/crates/miner/src/lib.rs b/crates/miner/src/lib.rs new file mode 100644 index 000000000..bc85aa1d9 --- /dev/null +++ b/crates/miner/src/lib.rs @@ -0,0 +1,74 @@ +#![warn(missing_docs)] +#![deny( + unused_must_use, + rust_2018_idioms, + rustdoc::broken_intra_doc_links, + unused_crate_dependencies +)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] + +//! reth miner implementation + +mod payload; + +use crate::error::PayloadError; +use parking_lot::Mutex; +pub use payload::{BuiltPayload, PayloadBuilderAttributes}; +use reth_primitives::H256; +use reth_rpc_types::engine::{ExecutionPayload, PayloadAttributes, PayloadId}; +use std::{collections::HashMap, sync::Arc}; + +pub mod error; + +/// A type that has access to all locally built payloads and can create new ones. +/// This type is intended to by used by the engine API. +pub trait PayloadStore: Send + Sync { + /// Returns true if the payload store contains the given payload. + fn contains(&self, payload_id: PayloadId) -> bool; + + /// Returns the current [ExecutionPayload] associated with the [PayloadId]. + /// + /// Returns `None` if the payload is not yet built, See [PayloadStore::new_payload]. + fn get_execution_payload(&self, payload_id: PayloadId) -> Option; + + /// Builds and stores a new payload using the given attributes. + /// + /// Returns an error if the payload could not be built. + // TODO: does this require async? + fn new_payload( + &self, + parent: H256, + attributes: PayloadAttributes, + ) -> Result; +} + +/// A simple in-memory payload store. +#[derive(Debug, Default)] +pub struct TestPayloadStore { + payloads: Arc>>, +} + +impl PayloadStore for TestPayloadStore { + fn contains(&self, payload_id: PayloadId) -> bool { + self.payloads.lock().contains_key(&payload_id) + } + + fn get_execution_payload(&self, _payload_id: PayloadId) -> Option { + // TODO requires conversion + None + } + + fn new_payload( + &self, + parent: H256, + attributes: PayloadAttributes, + ) -> Result { + let attr = PayloadBuilderAttributes::new(parent, attributes); + let payload_id = attr.payload_id(); + self.payloads.lock().insert(payload_id, BuiltPayload::new(payload_id, Default::default())); + Ok(payload_id) + } +} diff --git a/crates/miner/src/payload.rs b/crates/miner/src/payload.rs new file mode 100644 index 000000000..d23ab68ff --- /dev/null +++ b/crates/miner/src/payload.rs @@ -0,0 +1,79 @@ +//! Contains types required for building a payload. + +use reth_primitives::{Address, Block, SealedBlock, Withdrawal, H256}; +use reth_rlp::Encodable; +use reth_rpc_types::engine::{PayloadAttributes, PayloadId}; + +/// Contains the built payload. +/// +/// According to the [engine API specification](https://github.com/ethereum/execution-apis/blob/main/src/engine/README.md) the execution layer should build the initial version of the payload with an empty transaction set and then keep update it in order to maximize the revenue. +/// Therefore, the empty-block here is always available and full-block will be set/updated +/// afterwards. +#[derive(Debug)] +pub struct BuiltPayload { + /// Identifier of the payload + pub(crate) id: PayloadId, + /// The initially empty block. + _initial_empty_block: SealedBlock, +} + +// === impl BuiltPayload === + +impl BuiltPayload { + /// Initializes the payload with the given initial block. + pub(crate) fn new(id: PayloadId, initial: Block) -> Self { + Self { id, _initial_empty_block: initial.seal_slow() } + } + + /// Returns the identifier of the payload. + pub fn id(&self) -> PayloadId { + self.id + } +} + +/// Container type for all components required to build a payload. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PayloadBuilderAttributes { + /// Parent block to build the payload on top + pub(crate) parent: H256, + /// Timestamp for the generated payload + pub(crate) timestamp: u64, + /// Address of the recipient for collecting transaction fee + pub(crate) suggested_fee_recipient: Address, + /// Randomness value for the generated payload + pub(crate) prev_randao: H256, + /// Withdrawals for the generated payload + pub(crate) withdrawals: Vec, +} + +// === impl PayloadBuilderAttributes === + +impl PayloadBuilderAttributes { + /// Creates a new payload builder for the given parent block and the attributes + pub fn new(parent: H256, attributes: PayloadAttributes) -> Self { + Self { + parent, + timestamp: attributes.timestamp.as_u64(), + suggested_fee_recipient: attributes.suggested_fee_recipient, + prev_randao: attributes.prev_randao, + withdrawals: attributes.withdrawals.unwrap_or_default(), + } + } + + /// Generates the payload id for the configured payload + /// + /// Returns an 8-byte identifier by hashing the payload components. + pub fn payload_id(&self) -> PayloadId { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(self.parent.as_bytes()); + hasher.update(&self.timestamp.to_be_bytes()[..]); + hasher.update(self.prev_randao.as_bytes()); + hasher.update(self.suggested_fee_recipient.as_bytes()); + let mut buf = Vec::new(); + self.withdrawals.encode(&mut buf); + hasher.update(buf); + let out = hasher.finalize(); + PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length")) + } +} diff --git a/crates/primitives/src/withdrawal.rs b/crates/primitives/src/withdrawal.rs index f94175f8f..d8d0145b4 100644 --- a/crates/primitives/src/withdrawal.rs +++ b/crates/primitives/src/withdrawal.rs @@ -4,7 +4,7 @@ use reth_rlp::{RlpDecodable, RlpEncodable}; /// Withdrawal represents a validator withdrawal from the consensus layer. #[main_codec] -#[derive(Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodable, RlpDecodable)] pub struct Withdrawal { /// Monotonically increasing identifier issued by consensus layer. #[serde(with = "u64_hex")] diff --git a/crates/rpc/rpc-types/src/eth/engine/forkchoice.rs b/crates/rpc/rpc-types/src/eth/engine/forkchoice.rs index ef251f671..249899075 100644 --- a/crates/rpc/rpc-types/src/eth/engine/forkchoice.rs +++ b/crates/rpc/rpc-types/src/eth/engine/forkchoice.rs @@ -1,5 +1,6 @@ use super::{PayloadStatus, PayloadStatusEnum}; -use reth_primitives::{H256, H64}; +use crate::engine::PayloadId; +use reth_primitives::H256; use serde::{Deserialize, Serialize}; /// This structure encapsulates the fork choice state @@ -15,7 +16,7 @@ pub struct ForkchoiceState { #[serde(rename_all = "camelCase")] pub struct ForkchoiceUpdated { pub payload_status: PayloadStatus, - pub payload_id: Option, + pub payload_id: Option, } impl ForkchoiceUpdated { @@ -32,7 +33,7 @@ impl ForkchoiceUpdated { self } - pub fn with_payload_id(mut self, id: H64) -> Self { + pub fn with_payload_id(mut self, id: PayloadId) -> Self { self.payload_id = Some(id); self } diff --git a/crates/rpc/rpc-types/src/eth/engine/payload.rs b/crates/rpc/rpc-types/src/eth/engine/payload.rs index 6f9fe95fd..158adfc72 100644 --- a/crates/rpc/rpc-types/src/eth/engine/payload.rs +++ b/crates/rpc/rpc-types/src/eth/engine/payload.rs @@ -2,11 +2,24 @@ use reth_primitives::{ constants::MIN_PROTOCOL_BASE_FEE_U256, proofs::{self, EMPTY_LIST_HASH}, Address, Block, Bloom, Bytes, Header, SealedBlock, TransactionSigned, UintTryTo, Withdrawal, - H256, U256, U64, + H256, H64, U256, U64, }; use reth_rlp::{Decodable, Encodable}; use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; +/// And 8-byte identifier for an execution payload. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct PayloadId(H64); + +// === impl PayloadId === + +impl PayloadId { + /// Creates a new payload id from the given identifier. + pub fn new(id: [u8; 8]) -> Self { + Self(H64::from(id)) + } +} + /// This structure maps on the ExecutionPayload structure of the beacon chain spec. /// /// See also: