Skip to content
Open
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
17 changes: 16 additions & 1 deletion examples/custom-bundle-type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ use {
primitives::{Address, B256, Keccak256, TxHash, U256},
signers::local::PrivateKeySigner,
},
alloy_origin::eips::Encodable2718,
rblib::{alloy, prelude::*, reth, test_utils::*},
reth::{
ethereum::primitives::SignedTransaction,
optimism::primitives::OpTransactionSigned,
primitives::Recovered,
revm::db::BundleState,
},
reth_ethereum::primitives::WithEncoded,
serde::{Deserialize, Serialize},
std::sync::Arc,
std::{iter::Map, slice::Iter, sync::Arc},
};

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
Expand Down Expand Up @@ -153,6 +155,12 @@ impl CustomBundleType {

impl Bundle<CustomPlatform> for CustomBundleType {
type PostExecutionError = MinimumProfitNotMet;
type TransactionsEncoded<'a> = Map<
Iter<'a, Recovered<types::Transaction<CustomPlatform>>>,
fn(
&'a Recovered<types::Transaction<CustomPlatform>>,
) -> WithEncoded<&'a Recovered<types::Transaction<CustomPlatform>>>,
>;

fn transactions(&self) -> &[Recovered<types::Transaction<Optimism>>] {
&self.txs
Expand Down Expand Up @@ -252,6 +260,13 @@ impl Bundle<CustomPlatform> for CustomBundleType {

hasher.finalize()
}

fn transactions_encoded(&self) -> Self::TransactionsEncoded<'_> {
self
.transactions()
.iter()
.map(|tx| WithEncoded::new(tx.encoded_2718().into(), tx))
}
}

fn transfer_tx(
Expand Down
16 changes: 8 additions & 8 deletions src/payload/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,14 @@ impl<P: Platform> Executable<P> {
let mut discarded = Vec::new();
let mut results = Vec::with_capacity(bundle.transactions().len());

for transaction in bundle.transactions() {
let tx_hash = transaction.tx_hash();
let optional = bundle.is_optional(tx_hash);
let allowed_to_fail = bundle.is_allowed_to_fail(tx_hash);
for transaction in bundle.transactions_encoded() {
let tx_hash = *transaction.tx_hash();
let optional = bundle.is_optional(&tx_hash);
let allowed_to_fail = bundle.is_allowed_to_fail(&tx_hash);

let result = evm_config
.evm_with_env(&mut db, evm_env.clone())
.transact(transaction);
.transact(&transaction);

match result {
// Valid transaction or allowed to fail: include it in the bundle
Expand All @@ -224,16 +224,16 @@ impl<P: Platform> Executable<P> {
// Optional failing transaction, not allowed to fail
// or optional invalid transaction: discard it
Ok(_) | Err(_) if optional => {
discarded.push(*tx_hash);
discarded.push(tx_hash);
}
// Non-Optional failing transaction, not allowed to fail: invalidate the
// bundle
Ok(_) => {
return Err(ExecutionError::BundleTransactionReverted(*tx_hash));
return Err(ExecutionError::BundleTransactionReverted(tx_hash));
}
// Non-Optional invalid transaction: invalidate the bundle
Err(err) => {
return Err(ExecutionError::InvalidBundleTransaction(*tx_hash, err));
return Err(ExecutionError::InvalidBundleTransaction(tx_hash, err));
}
}
}
Expand Down
59 changes: 59 additions & 0 deletions src/platform/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use {
network::eip2718::Eip2718Error,
primitives::{B256, Keccak256, TxHash},
},
alloy_origin::primitives::Bytes,
core::{
convert::Infallible,
fmt::Debug,
Expand All @@ -21,6 +22,7 @@ use {
revm::db::BundleState,
rpc::types::mev::EthSendBundle,
},
reth_ethereum::primitives::WithEncoded,
serde::{
Deserialize,
Deserializer,
Expand All @@ -42,6 +44,9 @@ pub trait Bundle<P: Platform>:
{
type PostExecutionError: core::error::Error + Send + Sync + 'static;

type TransactionsEncoded<'a>: Iterator<Item = WithEncoded<&'a Recovered<types::Transaction<P>>>>
+ 'a;

/// Access to the transactions that are part of this bundle.
fn transactions(&self) -> &[Recovered<types::Transaction<P>>];

Expand Down Expand Up @@ -104,6 +109,29 @@ pub trait Bundle<P: Platform>:
/// metadata attached to it, so two bundles with the same transactions but
/// different metadata should have different hashes.
fn hash(&self) -> B256;

/// Returns an iterator that yields the bundle's transactions in a format
/// ready for execution.
///
/// By default, this wraps the plain `Recovered` transactions. Implementors
/// that store pre-encoded bytes can override this to provide the more
/// efficient `WithEncoded` wrapper.
fn executables<'a>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this could be slightly confusing with the concept of Executable in the payload API, we could rename this function to something like fn transactions_encoded instead

&'a self,
) -> Box<
dyn Iterator<Item = WithEncoded<&'a Recovered<types::Transaction<P>>>> + 'a,
> {
Comment on lines +119 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you can even use impl Iterator here

but unsure if this trait should be object safe

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think impl Iterator is good.
To keep Bundle object safe, we can consider moving it in a BundleExt in payload::ext

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I tried the impl Iterator and BundleExt suggestions, but hit a compiler error due to different iterator types between the optimised and fallback paths.

I switched to using a GAT on the Bundle trait instead. Let me know what you think of this approach.

Box::new(
self
.transactions()
.iter()
.map(|tx| WithEncoded::new(tx.encoded_2718().into(), tx)),
)
}

/// Returns an iterator over the bundle's transactions in a format ready for
/// execution.
fn transactions_encoded(&self) -> Self::TransactionsEncoded<'_>;
}

/// The eligibility of a bundle for inclusion in a block.
Expand Down Expand Up @@ -261,6 +289,15 @@ impl<P: Platform> FlashbotsBundle<P> {

impl<P: Platform> Bundle<P> for FlashbotsBundle<P> {
type PostExecutionError = Infallible;
type TransactionsEncoded<'a> = std::iter::Map<
std::iter::Zip<
std::slice::Iter<'a, Recovered<types::Transaction<P>>>,
std::slice::Iter<'a, Bytes>,
>,
fn(
(&'a Recovered<types::Transaction<P>>, &'a Bytes),
) -> WithEncoded<&'a Recovered<types::Transaction<P>>>,
>;

fn transactions(&self) -> &[Recovered<types::Transaction<P>>] {
&self.recovered
Expand Down Expand Up @@ -341,6 +378,28 @@ impl<P: Platform> Bundle<P> for FlashbotsBundle<P> {
// extra fields not taken into account in the hash
hasher.finalize()
}

fn executables<'a>(
&'a self,
) -> Box<
dyn Iterator<Item = WithEncoded<&'a Recovered<types::Transaction<P>>>> + 'a,
> {
Box::new(
self
.recovered
.iter()
.zip(self.inner.txs.iter())
.map(|(tx, bytes)| WithEncoded::new(bytes.clone(), tx)),
)
}

fn transactions_encoded(&self) -> Self::TransactionsEncoded<'_> {
self
.recovered
.iter()
.zip(self.inner.txs.iter())
.map(|(tx, bytes)| WithEncoded::new(bytes.clone(), tx))
}
}

impl<P: Platform> FlashbotsBundle<P> {
Expand Down