Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add end-of-block proposer payment #38

Merged
merged 6 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
ethers = "2.0.8"
futures-util = "0.3.28"
reth-interfaces = { git = "https://github.com/paradigmxyz/reth.git", package = "reth-interfaces", version = "0.1.0-alpha.6" }
reth-payload-builder = { git = "https://github.com/paradigmxyz/reth.git", package = "reth-payload-builder", version = "0.1.0-alpha.6" }
Expand All @@ -21,6 +22,5 @@ revm = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" }
revm-primitives = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" }

[dev-dependencies]
ethers = "2.0.8"
rand = "0.8.5"
reth-provider = { git = "https://github.com/paradigmxyz/reth.git", package = "reth-provider", version = "0.1.0-alpha.6", features = ["test-utils"] }
135 changes: 119 additions & 16 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use ethers::{
signers::{LocalWallet, Signer},
types::{
transaction::{
eip1559::Eip1559TransactionRequest, eip2718::TypedTransaction, eip2930::AccessList,
},
Bytes as EthersBytes, NameOrAddress,
},
};
use futures_util::{stream::Fuse, FutureExt, Stream, StreamExt};
use reth_interfaces::Error as RethError;
use reth_payload_builder::{
Expand All @@ -15,8 +24,8 @@ use reth_payload_builder::{
};
use reth_primitives::{
constants::{BEACON_NONCE, EMPTY_OMMER_ROOT},
proofs, Block, BlockNumber, ChainSpec, Header, IntoRecoveredTransaction, Receipt, SealedBlock,
SealedHeader, TransactionSigned, TransactionSignedEcRecovered, U256,
proofs, Block, BlockNumber, Bytes, ChainSpec, Header, IntoRecoveredTransaction, Receipt,
SealedBlock, SealedHeader, TransactionSigned, TransactionSignedEcRecovered, U256,
};
use reth_provider::{
BlockReaderIdExt, CanonStateNotification, PostState, StateProvider, StateProviderFactory,
Expand Down Expand Up @@ -106,12 +115,18 @@ struct Payload {
bundles: HashSet<BundleId>,
}

#[derive(Clone, Debug)]
struct PayloadAttributes {
inner: PayloadBuilderAttributes,
extra_data: u128,
wallet: LocalWallet,
}

#[derive(Clone, Debug)]
struct JobConfig {
attributes: PayloadBuilderAttributes,
attributes: PayloadAttributes,
parent: Arc<SealedHeader>,
chain: Arc<ChainSpec>,
extra_data: u128,
}

/// a build job scoped to `config`
Expand Down Expand Up @@ -330,11 +345,18 @@ where
}
}

#[derive(Clone, Debug)]
pub struct BuilderConfig {
pub extra_data: u128,
pub wallet: LocalWallet,
}

pub struct Builder<Client, Pool> {
chain: Arc<ChainSpec>,
wallet: LocalWallet,
extra_data: u128,
client: Arc<Client>,
pool: Arc<Pool>,
extra_data: u128,
bundle_pool: Arc<Mutex<BundlePool>>,
incoming: broadcast::Sender<(BundleId, BlockNumber, BundleCompact)>,
invalidated: broadcast::Sender<BundleId>,
Expand All @@ -345,7 +367,7 @@ where
Client: StateProviderFactory + Unpin,
Pool: TransactionPool + Unpin,
{
pub fn new(chain: ChainSpec, extra_data: u128, client: Client, pool: Pool) -> Self {
pub fn new(config: BuilderConfig, chain: ChainSpec, client: Client, pool: Pool) -> Self {
let chain = Arc::new(chain);
let client = Arc::new(client);
let pool = Arc::new(pool);
Expand All @@ -357,9 +379,10 @@ where

Self {
chain,
wallet: config.wallet,
extra_data: config.extra_data,
client,
pool,
extra_data,
bundle_pool,
incoming,
invalidated,
Expand Down Expand Up @@ -451,12 +474,17 @@ where
)));
}

let attributes = PayloadAttributes {
inner: attributes,
extra_data: self.extra_data,
wallet: self.wallet.clone(),
};

let parent = Arc::new(latest.header.seal_slow());
let config = JobConfig {
chain: Arc::clone(&self.chain),
parent,
attributes,
extra_data: self.extra_data,
};

// collect eligible bundles from the pool
Expand Down Expand Up @@ -514,13 +542,22 @@ where

let mut post_state = PostState::default();

let (cfg_env, block_env) = config
let (cfg_env, mut block_env) = config
.attributes
.inner
.cfg_and_block_env(&config.chain, &config.parent);

// mark the builder as the coinbase in the block env
block_env.coinbase = config.attributes.wallet.address().into();

let block_num = block_env.number.to::<u64>();
let base_fee = block_env.basefee.to::<u64>();
let block_gas_limit: u64 = block_env.gas_limit.try_into().unwrap_or(u64::MAX);

// reserve gas for end-of-block proposer payment
const PROPOSER_PAYMENT_GAS_ALLOWANCE: u64 = 21000;
let execution_gas_limit = block_gas_limit - PROPOSER_PAYMENT_GAS_ALLOWANCE;

let mut coinbase_payment = U256::ZERO;
let mut cumulative_gas_used = 0;
let mut txs = Vec::new();
Expand All @@ -530,7 +567,7 @@ where
for (id, bundle) in bundles {
// check gas for entire bundle
let bundle_gas_limit: u64 = bundle.0.iter().map(|tx| tx.gas_limit()).sum();
if cumulative_gas_used + bundle_gas_limit > block_gas_limit {
if cumulative_gas_used + bundle_gas_limit > execution_gas_limit {
continue;
}

Expand Down Expand Up @@ -572,7 +609,7 @@ where
let mut mempool_txs = pool.best_transactions_with_base_fee(base_fee);
while let Some(tx) = mempool_txs.next() {
// check gas
if cumulative_gas_used + tx.gas_limit() > block_gas_limit {
if cumulative_gas_used + tx.gas_limit() > execution_gas_limit {
continue;
}

Expand Down Expand Up @@ -605,27 +642,63 @@ where
}
}

// construct payment to proposer fee recipient.
//
// NOTE: we give the entire coinbase payment to the proposer, except for the gas that we need
// to execute the transaction. if the coinbase payment cannot cover the gas cost to pay the
// proposer, then we do not make any payment.
let payment_tx_gas_cost = block_env.basefee * U256::from(PROPOSER_PAYMENT_GAS_ALLOWANCE);
let proposer_payment = coinbase_payment.saturating_sub(payment_tx_gas_cost);
if proposer_payment > U256::ZERO {
let builder_acct = db
.basic(block_env.coinbase)?
.expect("builder account exists if coinbase payment non-zero");
let payment_tx = proposer_payment_tx(
&config.attributes.wallet,
builder_acct.nonce,
base_fee,
cfg_env.chain_id.to::<u64>(),
&config.attributes.inner.suggested_fee_recipient,
proposer_payment,
);

// execute payment to proposer fee recipient
//
// if the payment transaction fails, then the entire payload build fails
let execution = execute(
&mut db,
&cfg_env,
&block_env,
cumulative_gas_used,
Some(payment_tx.clone()).into_iter(),
)
.map_err(PayloadBuilderError::EvmExecutionError)?;
cumulative_gas_used = execution.cumulative_gas_used;
txs.push(payment_tx);
post_state.extend(execution.post_state);
}

// NOTE: here we assume post-shanghai
let balance_increments = post_block_withdrawals_balance_increments(
&config.chain,
config.attributes.timestamp,
&config.attributes.withdrawals,
config.attributes.inner.timestamp,
&config.attributes.inner.withdrawals,
);
for (address, increment) in balance_increments {
increment_account_balance(&mut db, &mut post_state, block_num, address, increment)?;
}

let block = package_block(
state.state(),
&config.attributes,
config.extra_data,
&config.attributes.inner,
config.attributes.extra_data,
&block_env,
txs.into_iter().map(|tx| tx.into_signed()).collect(),
post_state,
cumulative_gas_used,
)?;

let payload = BuiltPayload::new(config.attributes.id, block, coinbase_payment);
let payload = BuiltPayload::new(config.attributes.inner.id, block, proposer_payment);
let payload = Payload {
inner: Arc::new(payload),
bundles: bundle_ids,
Expand Down Expand Up @@ -769,6 +842,36 @@ fn compute_coinbase_payment(
}
}

/// Constructs a transfer transaction to pay `amount` to `proposer`.
fn proposer_payment_tx(
wallet: &LocalWallet,
nonce: u64,
base_fee: u64,
chain_id: u64,
proposer: &B160,
amount: U256,
) -> TransactionSignedEcRecovered {
let tx = Eip1559TransactionRequest::new()
.from(wallet.address())
.to(NameOrAddress::Address(ethers::types::H160::from_slice(
proposer.as_bytes(),
)))
.gas(21000)
.max_fee_per_gas(base_fee)
.max_priority_fee_per_gas(0)
.value(amount)
.data(EthersBytes::default())
.access_list(AccessList::default())
.nonce(nonce)
.chain_id(chain_id);
let tx = TypedTransaction::Eip1559(tx);
let signature = wallet.sign_transaction_sync(&tx).expect("can sign tx");
let tx_encoded = tx.rlp_signed(&signature);
let tx = TransactionSigned::decode_enveloped(Bytes::from(tx_encoded.as_ref()))
.expect("can decode tx");
tx.into_ecrecovered().expect("can recover tx signer")
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down