Skip to content

Commit 6aae6d9

Browse files
feat: skip tx_list compression during recovery (#914)
* feat: skip tx_list compression during recovery * bump version * Update shasta/src/node/proposal_manager/proposal_builder.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 8944290 commit 6aae6d9

4 files changed

Lines changed: 161 additions & 10 deletions

File tree

Cargo.lock

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ resolver = "2"
1313
default-members = ["node"]
1414

1515
[workspace.package]
16-
version = "1.34.8"
16+
version = "1.34.9"
1717
edition = "2024"
1818
repository = "https://github.com/NethermindEth/Catalyst"
1919
license = "MIT"

shasta/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ tracing-subscriber = { workspace = true }
2727

2828
[dev-dependencies]
2929
mockito = { workspace = true }
30+
rand = { workspace = true, features = ["std", "thread_rng"] }
3031
tokio = { workspace = true, features = ["full", "test-util"] }
3132

3233
[lints]

shasta/src/node/proposal_manager/proposal_builder.rs

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,7 @@ impl ProposalBuilder {
239239
proposal.anchor_state_root = anchor_info.state_root();
240240
}
241241

242-
let bytes_length =
243-
crate::shared::l2_tx_lists::encode_and_compress(&tx_list)?.len() as u64;
242+
let bytes_length = crate::shared::l2_tx_lists::rlp_encode(&tx_list).len() as u64;
244243

245244
let l2_draft_block = L2BlockV2Draft {
246245
prebuilt_tx_list: PreBuiltTxList {
@@ -251,6 +250,7 @@ impl ProposalBuilder {
251250
timestamp_sec: l2_block_timestamp_sec,
252251
gas_limit_without_anchor: gas_limit,
253252
};
253+
254254
if !self.can_consume_l2_block(&l2_draft_block) {
255255
return Err(anyhow::anyhow!(
256256
"recover_from: block does not fit in proposal {} (adding {} bytes would exceed blob size limit). Reorg needed.",
@@ -504,6 +504,7 @@ mod tests {
504504
use alloy::primitives::{B256, Uint};
505505
use common::l1::slot_clock::SlotClock;
506506
use common::metrics::Metrics;
507+
use rand::Rng;
507508

508509
const COINBASE: Address = Address::ZERO;
509510

@@ -535,6 +536,38 @@ mod tests {
535536
.expect("valid test tx json")
536537
}
537538

539+
fn make_tx_with_size(size: usize) -> alloy::rpc::types::Transaction {
540+
let mut bytes = vec![0_u8; size];
541+
rand::rng().fill(bytes.as_mut_slice());
542+
let input = format!("0x{}", hex::encode(bytes));
543+
serde_json::from_str(&format!(
544+
r#"{{
545+
"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
546+
"blockNumber":"0x1",
547+
"from":"0x0000000000000000000000000000000000000001",
548+
"gas":"0x5208",
549+
"gasPrice":"0x1",
550+
"hash":"0x0000000000000000000000000000000000000000000000000000000000000001",
551+
"input":"{}",
552+
"nonce":"0x0",
553+
"to":"0x0000000000000000000000000000000000000002",
554+
"transactionIndex":"0x0",
555+
"value":"0x0",
556+
"type":"0x2",
557+
"accessList":[],
558+
"chainId":"0x1",
559+
"maxFeePerGas":"0x1",
560+
"maxPriorityFeePerGas":"0x0",
561+
"v":"0x0",
562+
"r":"0x0000000000000000000000000000000000000000000000000000000000000000",
563+
"s":"0x0000000000000000000000000000000000000000000000000000000000000000",
564+
"yParity":"0x0"
565+
}}"#,
566+
input
567+
))
568+
.expect("valid test tx json")
569+
}
570+
538571
fn make_config() -> BatchBuilderConfig {
539572
BatchBuilderConfig {
540573
max_bytes_size_of_batch: 10_000,
@@ -559,6 +592,29 @@ mod tests {
559592
ProposalBuilder::new(config, slot_clock, metrics)
560593
}
561594

595+
fn make_recovery_stress_config() -> BatchBuilderConfig {
596+
BatchBuilderConfig {
597+
max_bytes_size_of_batch: 10_000,
598+
max_blocks_per_batch: 100,
599+
l1_slot_duration_sec: 12,
600+
max_time_shift_between_blocks_sec: 255,
601+
max_anchor_height_offset: 64,
602+
default_coinbase: COINBASE,
603+
preconf_min_txs: 3,
604+
preconf_max_skipped_l2_slots: 5,
605+
proposal_max_time_sec: 120,
606+
}
607+
}
608+
609+
fn build_recovery_txs_list(
610+
tx_per_block: usize,
611+
tx_size: usize,
612+
) -> Vec<alloy::rpc::types::Transaction> {
613+
(0..tx_per_block)
614+
.map(|_| make_tx_with_size(tx_size))
615+
.collect()
616+
}
617+
562618
fn make_anchor(id: u64, timestamp_sec: u64) -> AnchorBlockInfo {
563619
AnchorBlockInfo::new(id, timestamp_sec, B256::ZERO, B256::ZERO)
564620
}
@@ -1003,6 +1059,99 @@ mod tests {
10031059

10041060
// --- Recovery ---
10051061

1062+
#[tokio::test]
1063+
async fn test_overfill_proposal() {
1064+
const RECOVERABLE_BLOCKS_PER_PROPOSAL: u64 = 47;
1065+
const RECOVERY_TXS_PER_BLOCK: usize = 2;
1066+
const RECOVERY_TX_INPUT_BYTES: usize = 100;
1067+
1068+
let mut builder = make_builder_with_config(make_recovery_stress_config());
1069+
1070+
for block_id in 1..=RECOVERABLE_BLOCKS_PER_PROPOSAL {
1071+
let anchor = make_anchor(100, 1000);
1072+
builder
1073+
.recover_from(
1074+
1,
1075+
anchor,
1076+
COINBASE,
1077+
build_recovery_txs_list(RECOVERY_TXS_PER_BLOCK, RECOVERY_TX_INPUT_BYTES),
1078+
1000 + block_id,
1079+
1_000_000,
1080+
false,
1081+
)
1082+
.await
1083+
.expect("recovering a fitting block should succeed");
1084+
}
1085+
1086+
let anchor = make_anchor(100, 1000);
1087+
let res = builder
1088+
.recover_from(
1089+
1,
1090+
anchor,
1091+
COINBASE,
1092+
build_recovery_txs_list(RECOVERY_TXS_PER_BLOCK, RECOVERY_TX_INPUT_BYTES),
1093+
1000 + RECOVERABLE_BLOCKS_PER_PROPOSAL + 1,
1094+
1_000_000,
1095+
false,
1096+
)
1097+
.await;
1098+
assert!(
1099+
res.is_err(),
1100+
"should not be able to recover when proposal is full"
1101+
);
1102+
assert_eq!(builder.get_current_proposal_id(), Some(1));
1103+
assert_eq!(
1104+
builder
1105+
.current_proposal
1106+
.as_ref()
1107+
.expect("proposal should still exist")
1108+
.l2_blocks
1109+
.len(),
1110+
RECOVERABLE_BLOCKS_PER_PROPOSAL as usize
1111+
);
1112+
}
1113+
1114+
#[tokio::test]
1115+
async fn test_recover_three_full_proposals() {
1116+
const PROPOSALS: u64 = 3;
1117+
const RECOVERABLE_BLOCKS_PER_PROPOSAL: u64 = 47;
1118+
const RECOVERY_TXS_PER_BLOCK: usize = 2;
1119+
const RECOVERY_TX_INPUT_BYTES: usize = 100;
1120+
1121+
let mut builder = make_builder_with_config(make_recovery_stress_config());
1122+
1123+
for proposal_id in 1..=PROPOSALS {
1124+
let timestamp_base = 1000 * proposal_id;
1125+
for block_id in 1..=RECOVERABLE_BLOCKS_PER_PROPOSAL {
1126+
let anchor = make_anchor(100 * proposal_id, timestamp_base);
1127+
builder
1128+
.recover_from(
1129+
proposal_id,
1130+
anchor,
1131+
COINBASE,
1132+
build_recovery_txs_list(RECOVERY_TXS_PER_BLOCK, RECOVERY_TX_INPUT_BYTES),
1133+
timestamp_base + block_id,
1134+
1_000_000,
1135+
false,
1136+
)
1137+
.await
1138+
.expect("recovering a fitting block should succeed");
1139+
}
1140+
}
1141+
1142+
builder.finalize_current_proposal();
1143+
assert_eq!(builder.get_current_proposal_id(), None);
1144+
assert_eq!(builder.queue.len(), PROPOSALS);
1145+
builder.queue.take_all().iter_mut().for_each(|proposal| {
1146+
proposal.compress();
1147+
assert_eq!(
1148+
proposal.l2_blocks.len(),
1149+
RECOVERABLE_BLOCKS_PER_PROPOSAL as usize
1150+
);
1151+
assert!(proposal.total_bytes <= builder.config.max_bytes_size_of_batch as u64);
1152+
});
1153+
}
1154+
10061155
#[tokio::test]
10071156
async fn test_recover_from_creates_new_proposal() {
10081157
let mut builder = make_builder();

0 commit comments

Comments
 (0)