Skip to content

passes sim result to the submit tasks #100

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

Draft
wants to merge 9 commits into
base: prestwich/simrevert
Choose a base branch
from
Draft
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
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ integration = []
[dependencies]
init4-bin-base = { version = "0.4.2", features = ["perms"] }

signet-constants = { git = "https://github.com/init4tech/signet-sdk" }
signet-sim = { git = "https://github.com/init4tech/signet-sdk" }
signet-tx-cache = { git = "https://github.com/init4tech/signet-sdk" }
signet-types = { git = "https://github.com/init4tech/signet-sdk" }
signet-zenith = { git = "https://github.com/init4tech/signet-sdk" }
signet-constants = { git = "https://github.com/init4tech/signet-sdk", branch = "dylan/logging" }
signet-sim = { git = "https://github.com/init4tech/signet-sdk", branch = "dylan/logging" }
signet-tx-cache = { git = "https://github.com/init4tech/signet-sdk", branch = "dylan/logging" }
signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "dylan/logging" }
signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "dylan/logging" }

trevm = { version = "0.23.4", features = ["concurrent-db", "test-utils"] }

Expand Down
20 changes: 18 additions & 2 deletions bin/submit_transaction.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! A simple transaction submitter that sends a transaction to a recipient address
//! on a regular interval for the purposes of roughly testing rollup mining.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
primitives::{Address, U256},
Expand Down Expand Up @@ -67,18 +69,29 @@ async fn main() {
}
}

/// Sends a transaction to the specified recipient address
async fn send_transaction(provider: &HostProvider, recipient_address: Address) {
// construct simple transaction to send ETH to a recipient
let nonce = match provider.get_transaction_count(provider.default_signer_address()).await {
Ok(count) => count,
Err(e) => {
error!(error = ?e, "failed to get transaction count");
return;
}
};

let tx = TransactionRequest::default()
.with_from(provider.default_signer_address())
.with_to(recipient_address)
.with_value(U256::from(1))
.with_nonce(nonce)
.with_gas_limit(30_000);

// start timer to measure how long it takes to mine the transaction
let dispatch_start_time: Instant = Instant::now();

// dispatch the transaction
debug!(?tx.nonce, "sending transaction with nonce");
let result = provider.send_transaction(tx).await.unwrap();

// wait for the transaction to mine
Expand All @@ -95,10 +108,13 @@ async fn send_transaction(provider: &HostProvider, recipient_address: Address) {
}
};

let hash = receipt.transaction_hash.to_string();
record_metrics(dispatch_start_time, receipt);
}

// record metrics for how long it took to mine the transaction
/// Record metrics for how long it took to mine the transaction
fn record_metrics(dispatch_start_time: Instant, receipt: alloy::rpc::types::TransactionReceipt) {
let mine_time = dispatch_start_time.elapsed().as_secs();
let hash = receipt.transaction_hash.to_string();
debug!(success = receipt.status(), mine_time, hash, "transaction mined");
histogram!("txn_submitter.tx_mine_time").record(mine_time as f64);
}
19 changes: 14 additions & 5 deletions src/tasks/block/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ pub struct Simulator {
pub block_env: watch::Receiver<Option<BlockEnv>>,
}

/// SimResult bundles a BuiltBlock to the BlockEnv it was simulated against.
#[derive(Debug, Clone)]
pub struct SimResult {
/// The block built with the successfully simulated transactions
pub block: BuiltBlock,
/// The block environment the transactions were simulated against.
pub env: BlockEnv,
}

impl Simulator {
/// Creates a new `Simulator` instance.
///
Expand Down Expand Up @@ -115,7 +124,7 @@ impl Simulator {
self,
constants: SignetSystemConstants,
cache: SimCache,
submit_sender: mpsc::UnboundedSender<BuiltBlock>,
submit_sender: mpsc::UnboundedSender<SimResult>,
) -> JoinHandle<()> {
debug!("starting simulator task");

Expand All @@ -140,7 +149,7 @@ impl Simulator {
mut self,
constants: SignetSystemConstants,
cache: SimCache,
submit_sender: mpsc::UnboundedSender<BuiltBlock>,
submit_sender: mpsc::UnboundedSender<SimResult>,
) {
loop {
let sim_cache = cache.clone();
Expand All @@ -156,10 +165,10 @@ impl Simulator {
let Some(block_env) = self.block_env.borrow_and_update().clone() else { return };
debug!(block_env = ?block_env, "building on block env");

match self.handle_build(constants, sim_cache, finish_by, block_env).await {
match self.handle_build(constants, sim_cache, finish_by, block_env.clone()).await {
Ok(block) => {
debug!(block = ?block, "built block");
let _ = submit_sender.send(block);
debug!(block = ?block.block_number(), tx_count = block.transactions().len(), "built block");
let _ = submit_sender.send(SimResult { block, env: block_env });
}
Err(e) => {
error!(err = %e, "failed to build block");
Expand Down
115 changes: 70 additions & 45 deletions src/tasks/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@ use signet_zenith::{
Zenith::{self, IncorrectHostBlock},
};
use std::time::{Instant, UNIX_EPOCH};
use tokio::{sync::mpsc, task::JoinHandle};
use tokio::{
sync::mpsc::{self},
task::JoinHandle,
};
use trevm::revm::context::BlockEnv;

/// Base maximum fee per gas to use as a starting point for retry bumps
pub const BASE_FEE_PER_GAS: u128 = 10 * GWEI_TO_WEI as u128;
/// Base max priority fee per gas to use as a starting point for retry bumps
pub const BASE_MAX_PRIORITY_FEE_PER_GAS: u128 = 2 * GWEI_TO_WEI as u128;
/// Base maximum fee per blob gas to use as a starting point for retry bumps
pub const BASE_MAX_FEE_PER_BLOB_GAS: u128 = GWEI_TO_WEI as u128;
use super::block::sim::SimResult;

macro_rules! spawn_provider_send {
($provider:expr, $tx:expr) => {
Expand Down Expand Up @@ -222,7 +221,7 @@ impl SubmitTask {
})
}

/// Builds blob transaction and encodes the sidecar for it from the provided header and signature values
/// Encodes the sidecar and then builds the 4844 blob transaction from the provided header and signature values.
fn build_blob_tx(
&self,
fills: Vec<FillPermit2>,
Expand All @@ -245,8 +244,9 @@ impl SubmitTask {
retry_count: usize,
resp: &SignResponse,
block: &BuiltBlock,
block_env: &BlockEnv,
) -> eyre::Result<ControlFlow> {
let tx = self.prepare_tx(retry_count, resp, block).await?;
let tx = self.prepare_tx(retry_count, resp, block, block_env).await?;

self.send_transaction(resp, tx).await
}
Expand All @@ -258,9 +258,11 @@ impl SubmitTask {
retry_count: usize,
resp: &SignResponse,
block: &BuiltBlock,
block_env: &BlockEnv,
) -> Result<TransactionRequest, eyre::Error> {
// Create the transaction request with the signature values
let tx: TransactionRequest = self.new_tx_request(retry_count, resp, block).await?;
let tx: TransactionRequest =
self.new_tx_request(retry_count, resp, block, block_env).await?;

// Simulate the transaction with a call to the host provider and report any errors
if let Err(err) = self.sim_with_call(&tx).await {
Expand Down Expand Up @@ -288,6 +290,7 @@ impl SubmitTask {
retry_count: usize,
resp: &SignResponse,
block: &BuiltBlock,
block_env: &BlockEnv,
) -> Result<TransactionRequest, eyre::Error> {
// manually retrieve nonce
let nonce =
Expand All @@ -297,14 +300,8 @@ impl SubmitTask {
// Extract the signature components from the response
let (v, r, s) = extract_signature_components(&resp.sig);

// Calculate gas limits based on retry attempts
let (max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas) =
calculate_gas_limits(
retry_count,
BASE_FEE_PER_GAS,
BASE_MAX_PRIORITY_FEE_PER_GAS,
BASE_MAX_FEE_PER_BLOB_GAS,
);
calculate_gas(retry_count, block_env);

// Build the block header
let header: BlockHeader = BlockHeader {
Expand Down Expand Up @@ -386,6 +383,7 @@ impl SubmitTask {
&self,
retry_count: usize,
block: &BuiltBlock,
block_env: &BlockEnv,
) -> eyre::Result<ControlFlow> {
info!(retry_count, txns = block.tx_count(), "handling inbound block");
let Ok(sig_request) = self.construct_sig_request(block).await.inspect_err(|e| {
Expand All @@ -402,13 +400,14 @@ impl SubmitTask {

let signed = self.quincey.get_signature(&sig_request).await?;

self.submit_transaction(retry_count, &signed, block).await
self.submit_transaction(retry_count, &signed, block, block_env).await
}

/// Handles the retry logic for the inbound block.
async fn retrying_handle_inbound(
&self,
block: &BuiltBlock,
block_env: &BlockEnv,
retry_limit: usize,
) -> eyre::Result<ControlFlow> {
let mut retries = 0;
Expand All @@ -418,11 +417,11 @@ impl SubmitTask {

// Retry loop
let result = loop {
// Log the retry attempt
let span = debug_span!("SubmitTask::retrying_handle_inbound", retries);

let inbound_result =
match self.handle_inbound(retries, block).instrument(span.clone()).await {
match self.handle_inbound(retries, block, block_env).instrument(span.clone()).await
{
Ok(control_flow) => control_flow,
Err(err) => {
// Delay until next slot if we get a 403 error
Expand All @@ -431,6 +430,7 @@ impl SubmitTask {
debug!(slot_number, "403 detected - skipping slot");
return Ok(ControlFlow::Skip);
} else {
// Otherwise, log error and retry
error!(error = %err, "error handling inbound block");
}

Expand Down Expand Up @@ -500,66 +500,91 @@ impl SubmitTask {
/// Task future for the submit task
/// NB: This task assumes that the simulator will only send it blocks for
/// slots that it's assigned.
async fn task_future(self, mut inbound: mpsc::UnboundedReceiver<BuiltBlock>) {
async fn task_future(self, mut inbound: mpsc::UnboundedReceiver<SimResult>) {
// Holds a reference to the last block we attempted to submit
let mut last_block_attempted: u64 = 0;

loop {
// Wait to receive a new block
let Some(block) = inbound.recv().await else {
let Some(result) = inbound.recv().await else {
debug!("upstream task gone");
break;
};
debug!(block_number = block.block_number(), ?block, "submit channel received block");

debug!(block_number = result.block.block_number(), "submit channel received block");

// Only attempt each block number once
if block.block_number() == last_block_attempted {
debug!("block number is unchanged from last attempt - skipping");
if result.block.block_number() == last_block_attempted {
debug!(block_number = result.block.block_number(), "block number is unchanged from last attempt - skipping");
continue;
}

// This means we have encountered a new block, so reset the last block attempted
last_block_attempted = block.block_number();
last_block_attempted = result.block.block_number();
debug!(last_block_attempted, "resetting last block attempted");

if self.retrying_handle_inbound(&block, 3).await.is_err() {
if self.retrying_handle_inbound(&result.block, &result.env, 3).await.is_err() {
debug!("error handling inbound block");
continue;
};
}
}

/// Spawns the in progress block building task
pub fn spawn(self) -> (mpsc::UnboundedSender<BuiltBlock>, JoinHandle<()>) {
let (sender, inbound) = mpsc::unbounded_channel();
pub fn spawn(self) -> (mpsc::UnboundedSender<SimResult>, JoinHandle<()>) {
let (sender, inbound) = mpsc::unbounded_channel::<SimResult>();
let handle = tokio::spawn(self.task_future(inbound));

(sender, handle)
}
}

// Returns gas parameters based on retry counts.
fn calculate_gas_limits(
/// Calculates gas parameters based on the block environment and retry count.
fn calculate_gas(retry_count: usize, block_env: &BlockEnv) -> (u128, u128, u128) {
let fallback_blob_basefee = 500;

match block_env.blob_excess_gas_and_price {
Some(excess) => {
if excess.blob_gasprice == 0 {
warn!("blob excess gas price is zero, using default blob base fee");
return bump_gas_from_retries(
retry_count,
block_env.basefee,
fallback_blob_basefee,
);
}

bump_gas_from_retries(retry_count, block_env.basefee, excess.blob_gasprice)
}
None => {
warn!("no blob excess gas and price in block env, using defaults");
bump_gas_from_retries(retry_count, block_env.basefee, fallback_blob_basefee)
}
}
}

/// Bumps the gas parameters based on the retry count, base fee, and blob base fee.
pub fn bump_gas_from_retries(
retry_count: usize,
base_max_fee_per_gas: u128,
base_max_priority_fee_per_gas: u128,
base_max_fee_per_blob_gas: u128,
basefee: u64,
blob_basefee: u128,
) -> (u128, u128, u128) {
let bump_multiplier = 1150u128.pow(retry_count as u32); // 15% bump
let blob_bump_multiplier = 2000u128.pow(retry_count as u32); // 100% bump (double each time) for blob gas
let bump_divisor = 1000u128.pow(retry_count as u32);
const PRIORITY_FEE_BASE: u64 = 2 * GWEI_TO_WEI;
const BASE_MULTIPLIER: u128 = 2;
const BLOB_MULTIPLIER: u128 = 2;

// Increase priority fee by 20% per retry
let priority_fee =
PRIORITY_FEE_BASE * (12u64.pow(retry_count as u32) / 10u64.pow(retry_count as u32));

let max_fee_per_gas = base_max_fee_per_gas * bump_multiplier / bump_divisor;
let max_priority_fee_per_gas = base_max_priority_fee_per_gas * bump_multiplier / bump_divisor;
let max_fee_per_blob_gas = base_max_fee_per_blob_gas * blob_bump_multiplier / bump_divisor;
// Max fee includes basefee + priority + headroom (double basefee, etc.)
let max_fee_per_gas = (basefee as u128) * BASE_MULTIPLIER + (priority_fee as u128);
let max_fee_per_blob_gas = blob_basefee * BLOB_MULTIPLIER * (retry_count as u128 + 1);

debug!(
retry_count,
max_fee_per_gas,
max_priority_fee_per_gas,
max_fee_per_blob_gas,
"calculated bumped gas parameters"
max_fee_per_gas, priority_fee, max_fee_per_blob_gas, "calculated bumped gas parameters"
);

(max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas)
(max_fee_per_gas as u128, priority_fee as u128, max_fee_per_blob_gas)
}
2 changes: 1 addition & 1 deletion tests/block_builder_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,5 @@ async fn test_spawn() {
// Assert on the block
let block = result.unwrap();
assert!(block.is_some(), "Block channel closed without receiving a block");
assert!(block.unwrap().tx_count() == 2); // TODO: Why is this failing? I'm seeing EVM errors but haven't tracked them down yet.
assert!(block.unwrap().block.tx_count() == 2); // TODO: Why is this failing? I'm seeing EVM errors but haven't tracked them down yet.
}
Loading