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

compute coinbase payment for execution #35

Merged
merged 4 commits into from
Aug 9, 2023
Merged
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
185 changes: 156 additions & 29 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use reth_revm::{
into_reth_log,
revm::{
db::{CacheDB, DatabaseRef},
primitives::{result::InvalidTransaction, BlockEnv, CfgEnv, Env, ResultAndState},
primitives::{BlockEnv, CfgEnv, Env, ResultAndState, B160},
EVM,
},
};
Expand Down Expand Up @@ -486,7 +486,7 @@ fn build_on_state<S: StateProvider, I: Iterator<Item = (BundleId, BundleCompact)
let block_num = block_env.number.to::<u64>();
let block_gas_limit: u64 = block_env.gas_limit.try_into().unwrap_or(u64::MAX);

let mut total_fees = U256::ZERO;
let mut coinbase_payment = U256::ZERO;
let mut cumulative_gas_used = 0;
let mut txs = Vec::new();
let mut bundle_ids = HashSet::new();
Expand Down Expand Up @@ -516,7 +516,7 @@ fn build_on_state<S: StateProvider, I: Iterator<Item = (BundleId, BundleCompact)
);

if let Ok(execution) = execution {
total_fees += execution.total_fees;
coinbase_payment += execution.coinbase_payment;
cumulative_gas_used = execution.cumulative_gas_used;

txs.append(&mut bundle);
Expand Down Expand Up @@ -551,7 +551,7 @@ fn build_on_state<S: StateProvider, I: Iterator<Item = (BundleId, BundleCompact)
cumulative_gas_used,
)?;

let payload = BuiltPayload::new(config.attributes.id, block, total_fees);
let payload = BuiltPayload::new(config.attributes.id, block, coinbase_payment);
let payload = Payload {
inner: Arc::new(payload),
bundles: bundle_ids,
Expand All @@ -564,7 +564,7 @@ fn build_on_state<S: StateProvider, I: Iterator<Item = (BundleId, BundleCompact)
struct Execution {
post_state: PostState,
cumulative_gas_used: u64,
total_fees: U256,
coinbase_payment: U256,
}

fn execute<DB, I>(
Expand All @@ -579,10 +579,14 @@ where
<DB as DatabaseRef>::Error: std::fmt::Debug,
I: Iterator<Item = TransactionSignedEcRecovered>,
{
let base_fee = block_env.basefee.to::<u64>();
let block_num = block_env.number.to::<u64>();

let mut total_fees = U256::ZERO;
// determine the initial balance of the account at the coinbase address
let coinbase_acct = db
.basic(block_env.coinbase)
.map_err(|err| PayloadBuilderError::Internal(RethError::Custom(format!("{err:?}"))))?;
let initial_coinbase_balance = coinbase_acct.map_or(U256::ZERO, |acct| acct.balance);

let mut post_state = PostState::default();

for tx in txs {
Expand Down Expand Up @@ -618,18 +622,16 @@ where
logs: result.logs().into_iter().map(into_reth_log).collect(),
},
);

let miner_fee = tx
.effective_tip_per_gas(base_fee)
.ok_or(InvalidTransaction::GasPriceLessThanBasefee)
.map_err(|err| PayloadBuilderError::EvmExecutionError(err.into()))?;
total_fees += U256::from(miner_fee) * U256::from(result.gas_used());
}

// compute the coinbase payment
let coinbase_payment =
compute_coinbase_payment(&block_env.coinbase, initial_coinbase_balance, &post_state);

Ok(Execution {
post_state,
cumulative_gas_used,
total_fees,
coinbase_payment,
})
}

Expand Down Expand Up @@ -682,42 +684,68 @@ fn package_block<S: StateProvider>(
Ok(block.seal_slow())
}

/// Computes the payment to `coinbase` based on `initial_balance` and `post_state`.
///
/// NOTE: If the ending balance is less than `initial_balance`, then we define the payment as zero.
/// If the account has been deleted, then we define the payment as zero. If the account was not
/// modified, then the payment will be zero.
fn compute_coinbase_payment(
coinbase: &B160,
initial_balance: U256,
post_state: &PostState,
) -> U256 {
match post_state.account(coinbase) {
Some(Some(acct)) => acct.balance.saturating_sub(initial_balance),
Some(None) => U256::ZERO,
None => U256::ZERO,
}
}

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

use ethers::{
signers::{LocalWallet, Signer},
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest},
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, NameOrAddress},
utils::keccak256,
};
use reth_primitives::{Address, Bytes, TxType};
use reth_revm::revm::{
db::EmptyDB,
primitives::{specification::SpecId, state::AccountInfo, B256},
primitives::{
specification::SpecId, state::AccountInfo, Bytecode, BytecodeState, B256, KECCAK_EMPTY,
},
};

#[test]
fn execute_transfer() {
let mut db = CacheDB::new(EmptyDB());

// builder will be the coinbase (i.e. beneficiary)
let builder_wallet = LocalWallet::new(&mut rand::thread_rng());

fn env(coinbase: Address, basefee: U256) -> (CfgEnv, BlockEnv) {
let cfg_env = CfgEnv {
chain_id: U256::from(1),
spec_id: SpecId::CANCUN,
..Default::default()
};
let block_env = BlockEnv {
number: U256::ZERO,
coinbase: builder_wallet.address().into(),
coinbase,
timestamp: U256::ZERO,
difficulty: U256::ZERO,
prevrandao: Some(B256::random()),
basefee: U256::ZERO,
basefee,
gas_limit: U256::from(15000000),
};

(cfg_env, block_env)
}

#[test]
fn execute_transfer() {
let mut db = CacheDB::new(EmptyDB());

// builder will be the coinbase (i.e. beneficiary)
let builder_wallet = LocalWallet::new(&mut rand::thread_rng());

let (cfg_env, block_env) = env(builder_wallet.address().into(), U256::ZERO);

let sender_wallet = LocalWallet::new(&mut rand::thread_rng());
let initial_sender_balance = 10000000;
let sender_nonce = 0;
Expand Down Expand Up @@ -765,7 +793,7 @@ mod tests {
let Execution {
post_state,
cumulative_gas_used,
total_fees,
coinbase_payment,
} = execution;

// expected gas usage is the transfer transaction's gas limit
Expand Down Expand Up @@ -799,8 +827,107 @@ mod tests {
// check gas usage
assert_eq!(cumulative_gas_used, expected_cumulative_gas_used);

// check fees
let expected_total_fees = tx_gas_limit * max_priority_fee;
assert_eq!(total_fees, U256::from(expected_total_fees));
// check coinbase payment
let expected_coinbase_payment = cumulative_gas_used * max_priority_fee;
let builder_account = post_state
.account(&Address::from(builder_wallet.address()))
.expect("builder account touched")
.expect("builder account not destroyed");
assert_eq!(
builder_account.balance,
U256::from(expected_coinbase_payment)
);
assert_eq!(coinbase_payment, U256::from(expected_coinbase_payment));
}

#[test]
fn execute_coinbase_transfer() {
let mut db = CacheDB::new(EmptyDB());

// builder will be the coinbase (i.e. beneficiary)
let builder_wallet = LocalWallet::new(&mut rand::thread_rng());

let (cfg_env, block_env) = env(builder_wallet.address().into(), U256::ZERO);

// populate coinbase transfer smart contract in the DB
//
// h/t lightclient: https://github.com/lightclient/sendall
let bytecode = vec![0x5f, 0x5f, 0x5f, 0x5f, 0x47, 0x41, 0x5a, 0xf1, 0x00];
let code_hash = B256::from(keccak256(bytecode.clone()));
let bytecode = Bytecode {
bytecode: Bytes::from(bytecode).into(),
hash: code_hash,
state: BytecodeState::Raw,
};
let contract_acct = AccountInfo::new(U256::ZERO, 0, bytecode);
let contract_addr = Address::random();
db.insert_account_info(contract_addr, contract_acct);

// contract is the only account in the DB
let contract_addr = *db.accounts.keys().next().unwrap();

let sender_wallet = LocalWallet::new(&mut rand::thread_rng());
let initial_sender_balance = 10000000;
let sender_nonce = 0;

// populate sender account in the DB
db.insert_account_info(
sender_wallet.address().into(),
AccountInfo {
balance: U256::from(initial_sender_balance),
nonce: sender_nonce,
code_hash: KECCAK_EMPTY,
code: None,
},
);

let tx_value = 100;
let tx_gas_limit = 84000;
let max_priority_fee = 100;
let max_fee = block_env.basefee.to::<u64>() + max_priority_fee;

// construct the transfer transaction for execution
let tx = Eip1559TransactionRequest::new()
.from(sender_wallet.address())
.to(NameOrAddress::Address(ethers::types::H160::from(
contract_addr,
)))
.gas(tx_gas_limit)
.max_fee_per_gas(max_fee)
.max_priority_fee_per_gas(max_priority_fee)
.value(tx_value)
.data(ethers::types::Bytes::default())
.access_list(ethers::types::transaction::eip2930::AccessList::default())
.nonce(sender_nonce)
.chain_id(cfg_env.chain_id.to::<u64>());
let tx = TypedTransaction::Eip1559(tx);
let signature = sender_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");
let tx = tx.into_ecrecovered().expect("can recover tx signer");

let execution = execute(&mut db, &cfg_env, &block_env, 0, vec![tx].into_iter())
.expect("execution doesn't fail");
let Execution {
post_state,
coinbase_payment,
cumulative_gas_used,
..
} = execution;

// check coinbase payment
let expected_coinbase_payment = tx_value + (cumulative_gas_used * max_priority_fee);
let builder_account = post_state
.account(&Address::from(builder_wallet.address()))
.expect("builder account touched")
.expect("builder account not destroyed");
assert_eq!(
builder_account.balance,
U256::from(expected_coinbase_payment)
);
assert_eq!(coinbase_payment, U256::from(expected_coinbase_payment));
}
}