Deep EVM #16: Bundling y Resolución de Conflictos — Empaquetando Transacciones Rentables
Engineering Team
El problema del bundling
Un searcher de MEV puede encontrar docenas de oportunidades rentables en un solo bloque. El desafío es empaquetarlas en un bundle coherente:
- Las transacciones deben ejecutarse en orden específico
- Algunas transacciones conflictan (acceden al mismo estado)
- El bundle debe pagar suficiente al builder para ser incluido
- El coste total de gas debe dejar margen de beneficio
Detección de conflictos
Dos transacciones conflictan cuando una lee o escribe estado que la otra escribe. Para detectar conflictos, rastreamos los conjuntos de lectura/escritura de cada transacción:
struct AccessSet {
reads: HashSet<(Address, U256)>, // (contrato, slot)
writes: HashSet<(Address, U256)>,
}
fn conflicts(a: &AccessSet, b: &AccessSet) -> bool {
// Conflicto si A escribe algo que B lee o escribe,
// o si B escribe algo que A lee
!a.writes.is_disjoint(&b.reads)
|| !a.writes.is_disjoint(&b.writes)
|| !b.writes.is_disjoint(&a.reads)
}
revm puede capturar los access sets durante la simulación usando un inspector personalizado.
Grafo de conflictos
Con N oportunidades, construimos un grafo de conflictos:
struct ConflictGraph {
opportunities: Vec<Opportunity>,
conflicts: Vec<Vec<bool>>, // matriz de adyacencia
}
impl ConflictGraph {
fn build(opps: Vec<Opportunity>, db: &CacheDB) -> Self {
let n = opps.len();
let mut conflicts = vec![vec![false; n]; n];
for i in 0..n {
for j in (i+1)..n {
if access_sets_conflict(&opps[i], &opps[j]) {
conflicts[i][j] = true;
conflicts[j][i] = true;
}
}
}
Self { opportunities: opps, conflicts }
}
}
Algoritmo de empaquetado
El empaquetado óptimo es NP-hard (es una variante del problema de conjunto independiente máximo con pesos). Usamos una heurística greedy:
fn greedy_pack(
graph: &ConflictGraph,
) -> Vec<usize> {
// Ordenar por beneficio descendente
let mut indices: Vec<usize> = (0..graph.opportunities.len()).collect();
indices.sort_by(|&a, &b| {
graph.opportunities[b].profit
.cmp(&graph.opportunities[a].profit)
});
let mut selected = Vec::new();
let mut excluded = HashSet::new();
for &idx in &indices {
if excluded.contains(&idx) {
continue;
}
selected.push(idx);
// Excluir todas las oportunidades que conflictan
for (other, &has_conflict) in graph.conflicts[idx].iter().enumerate() {
if has_conflict {
excluded.insert(other);
}
}
}
selected
}
Ordenamiento topológico
Una vez seleccionadas las oportunidades sin conflictos, debemos ordenarlas correctamente. Algunas pueden tener dependencias (e.g., un swap que genera tokens necesarios para otro):
fn topological_sort(
selected: &[usize],
dependencies: &HashMap<usize, Vec<usize>>,
) -> Vec<usize> {
let mut result = Vec::new();
let mut visited = HashSet::new();
fn dfs(
node: usize,
deps: &HashMap<usize, Vec<usize>>,
visited: &mut HashSet<usize>,
result: &mut Vec<usize>,
) {
if visited.contains(&node) { return; }
visited.insert(node);
if let Some(parents) = deps.get(&node) {
for &parent in parents {
dfs(parent, deps, visited, result);
}
}
result.push(node);
}
for &idx in selected {
dfs(idx, dependencies, &mut visited, &mut result);
}
result
}
Pago al builder
El bundle debe incluir un pago al builder para ser competitivo. La estrategia de pago depende del beneficio total y la competencia:
fn calculate_builder_payment(
total_profit: U256,
gas_cost: U256,
competition_level: f64, // 0.0 a 1.0
) -> U256 {
let net_profit = total_profit - gas_cost;
// Porcentaje del beneficio neto que va al builder
// Alta competencia = mayor porcentaje
let builder_share = match competition_level {
x if x > 0.8 => 0.95, // 95% al builder en alta competencia
x if x > 0.5 => 0.80,
x if x > 0.2 => 0.60,
_ => 0.40,
};
// Implementar como transferencia de ETH al coinbase
net_profit * U256::from((builder_share * 100.0) as u64) / U256::from(100)
}
Envío multi-builder
Enviar el bundle a múltiples builders maximiza la probabilidad de inclusión:
async fn submit_to_builders(
bundle: &Bundle,
builders: &[BuilderEndpoint],
) -> Vec<Result<SubmitResponse, Error>> {
let futures: Vec<_> = builders.iter().map(|builder| {
submit_bundle(bundle, builder)
}).collect();
futures::future::join_all(futures).await
}
Builders principales:
- Flashbots Builder (relay.flashbots.net)
- BloXroute (mev.api.blxrbdn.com)
- Titan Builder
- Beaver Build
Conclusión
El bundling efectivo convierte oportunidades individuales de MEV en bloques rentables. La detección de conflictos basada en access sets, el empaquetado greedy, el ordenamiento topológico, y la estrategia de pago al builder son las piezas clave. El envío multi-builder maximiza la probabilidad de inclusión, y el monitoreo continuo permite ajustar la estrategia competitiva.