Skip to content
Merged
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: 9 additions & 1 deletion src/consensus/parlia/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,21 @@ where
(Vec::new(), None, None)
};

// Parse attestation from header for vote tracking
let attestation = super::attestation::parse_vote_attestation_from_header(
header,
working_snapshot.epoch_num,
self.chain_spec.is_luban_active_at_block(header.number),
self.chain_spec.is_bohr_active_at_timestamp(header.timestamp)
);

// Apply header to snapshot (now determines hardfork activation internally)
working_snapshot = match working_snapshot.apply(
header.beneficiary,
header,
new_validators,
vote_addrs,
None, // TODO: Parse attestation from header like reth-bsc-trail for vote tracking
attestation,
turn_length,
&*self.chain_spec,
) {
Expand Down
74 changes: 47 additions & 27 deletions src/consensus/parlia/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ pub const LORENTZ_TURN_LENGTH: u8 = 8;
pub const MAXWELL_EPOCH_LENGTH: u64 = 1000;
pub const MAXWELL_TURN_LENGTH: u8 = 16;

// Approximate block intervals converted to seconds (BSC headers store
// timestamps in seconds precision).
pub const DEFAULT_BLOCK_INTERVAL_SECS: u64 = 3; // 3000 ms
pub const LORENTZ_BLOCK_INTERVAL_SECS: u64 = 2; // 1500 ms (ceil)
pub const MAXWELL_BLOCK_INTERVAL_SECS: u64 = 1; // 750 ms (ceil)
pub const DEFAULT_BLOCK_INTERVAL: u64 = 3000; // 3000 ms
pub const LORENTZ_BLOCK_INTERVAL: u64 = 1500; // 1500 ms
pub const MAXWELL_BLOCK_INTERVAL: u64 = 750; // 750 ms

/// `ValidatorInfo` holds metadata for a validator at a given epoch.
#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -110,7 +108,7 @@ impl Snapshot {
recent_proposers: Default::default(),
vote_data: Default::default(),
turn_length: Some(DEFAULT_TURN_LENGTH),
block_interval: DEFAULT_BLOCK_INTERVAL_SECS,
block_interval: DEFAULT_BLOCK_INTERVAL,
}
}

Expand Down Expand Up @@ -150,43 +148,57 @@ impl Snapshot {
if !snap.validators.contains(&validator) {
return None;
}
if snap.sign_recently(validator) {
return None;
}
snap.recent_proposers.insert(block_number, validator);

// -------------------------------------------------------------------
// Epoch / turn-length upgrades at Lorentz & Maxwell (following bsc-erigon approach)
// -------------------------------------------------------------------

// Determine hardfork activation using chain spec (like bsc-erigon)
let header_timestamp = next_header.timestamp();
let is_bohr = chain_spec.is_bohr_active_at_timestamp(header_timestamp);
let is_lorentz_active = chain_spec.is_lorentz_active_at_timestamp(header_timestamp);
if is_bohr {
if snap.sign_recently(validator) {
tracing::warn!("🔍 snap-debug [BSC] after bohr, validator over-proposed, validator: {:?}, block_number: {:?}", validator, block_number);
return None;
}
} else {
for (_, &v) in &snap.recent_proposers {
if v == validator {
tracing::warn!("🔍 snap-debug [BSC] before bohr, validator over-proposed, validator: {:?}, block_number: {:?}", validator, block_number);
return None;
}
}
}
snap.recent_proposers.insert(block_number, validator);

let is_maxwell_active = chain_spec.is_maxwell_active_at_timestamp(header_timestamp);
if is_maxwell_active {
let latest_finalized_block_number = snap.get_finalized_number();
// BEP-524: Clear entries up to the latest finalized block
let blocks_to_remove: Vec<u64> = snap.recent_proposers.keys()
.filter(|&&block_number| block_number <= latest_finalized_block_number)
.copied()
.collect();
for block_number in blocks_to_remove {
snap.recent_proposers.remove(&block_number);
}
}

// Update block interval based on hardforks (like bsc-erigon)
let is_lorentz_active = chain_spec.is_lorentz_active_at_timestamp(header_timestamp);
if is_maxwell_active {
snap.block_interval = MAXWELL_BLOCK_INTERVAL_SECS;
snap.block_interval = MAXWELL_BLOCK_INTERVAL;
} else if is_lorentz_active {
snap.block_interval = LORENTZ_BLOCK_INTERVAL_SECS;
snap.block_interval = LORENTZ_BLOCK_INTERVAL;
}

// Update epoch_num and turn_length based on active hardforks
let epoch_length = snap.epoch_num;
let next_block_number = block_number + 1;
if snap.epoch_num == DEFAULT_EPOCH_LENGTH && is_lorentz_active && next_block_number % LORENTZ_EPOCH_LENGTH == 0 {
// Like bsc-erigon: prevent incorrect block usage for validator parsing after Lorentz
snap.epoch_num = LORENTZ_EPOCH_LENGTH;
snap.turn_length = Some(LORENTZ_TURN_LENGTH);
}
if snap.epoch_num == LORENTZ_EPOCH_LENGTH && is_maxwell_active && next_block_number % MAXWELL_EPOCH_LENGTH == 0 {
snap.epoch_num = MAXWELL_EPOCH_LENGTH;
snap.turn_length = Some(MAXWELL_TURN_LENGTH);
}

// Epoch change driven by new validator set / checkpoint header.
let epoch_key = u64::MAX - block_number / snap.epoch_num;
// change validator set
let epoch_key = u64::MAX - block_number / epoch_length;
if !new_validators.is_empty() && (!is_bohr || !snap.recent_proposers.contains_key(&epoch_key)) {
// Epoch change driven by new validator set / checkpoint header.
new_validators.sort();
if let Some(tl) = turn_length { snap.turn_length = Some(tl) }

Expand Down Expand Up @@ -293,6 +305,14 @@ impl Snapshot {
}
false
}

pub fn get_finalized_number(&self) -> BlockNumber {
if self.vote_data.source_number > 0 {
self.vote_data.source_number
} else {
0
}
}
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -429,8 +449,8 @@ mod tests {
};

// Create a mock chain spec for testing
use crate::chainspec::BscChainSpec;
let chain_spec = BscChainSpec::bsc_testnet();
use crate::chainspec::{bsc_testnet, BscChainSpec};
let chain_spec = BscChainSpec::from(bsc_testnet());

// This should not panic due to division by zero
let result = snapshot.apply(
Expand Down
3 changes: 2 additions & 1 deletion src/consensus/parlia/transaction_splitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ mod tests {
use super::*;
use alloy_primitives::{address, U256};
use alloy_consensus::TxLegacy;
use reth_primitives::{Transaction, Signature};
use reth_primitives::Transaction;
use alloy_primitives::Signature;
use crate::system_contracts::SLASH_CONTRACT;

/// Helper to create a test transaction
Expand Down
8 changes: 7 additions & 1 deletion src/consensus/parlia/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ where
rlp_head.payload_length += extra_without_seal.length();
rlp_head.payload_length += header.mix_hash().unwrap_or_default().length();
rlp_head.payload_length += header.nonce().unwrap_or_default().length();

// Add conditional field lengths for post-4844 blocks (exactly like reth-bsc-trail)
if header.parent_beacon_block_root().is_some() &&
header.parent_beacon_block_root().unwrap() == alloy_primitives::B256::ZERO
Expand All @@ -239,6 +239,9 @@ where
rlp_head.payload_length += header.blob_gas_used().unwrap_or_default().length();
rlp_head.payload_length += header.excess_blob_gas().unwrap_or_default().length();
rlp_head.payload_length += header.parent_beacon_block_root().unwrap().length();
if header.requests_hash().is_some() {
rlp_head.payload_length += header.requests_hash().unwrap().length();
}
}

// Encode the RLP list header first
Expand Down Expand Up @@ -271,6 +274,9 @@ where
Encodable::encode(&header.blob_gas_used().unwrap_or_default(), &mut out);
Encodable::encode(&header.excess_blob_gas().unwrap_or_default(), &mut out);
Encodable::encode(&header.parent_beacon_block_root().unwrap(), &mut out);
if header.requests_hash().is_some() {
Encodable::encode(&header.requests_hash().unwrap(), &mut out);
}
}

let encoded = out.to_vec();
Expand Down
19 changes: 17 additions & 2 deletions src/node/evm/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,13 +789,28 @@ where
(Vec::new(), None, None)
};

// Get current header and parse attestation
let current_header = provider.get_checkpoint_header(current_block_number);
let (apply_header, attestation) = if let Some(current_header) = current_header {
let attestation = crate::consensus::parlia::attestation::parse_vote_attestation_from_header(
&current_header,
parent_snapshot.epoch_num,
self.spec.is_luban_active_at_block(current_block_number),
self.spec.is_bohr_active_at_timestamp(current_header.timestamp)
);
(current_header, attestation)
} else {
// Fallback to the constructed header if we can't get the real one
(header, None)
};

// Apply current block to parent snapshot (like reth-bsc-trail does)
if let Some(current_snapshot) = parent_snapshot.apply(
current_block.beneficiary, // proposer
&header,
&apply_header,
new_validators, // parsed validators from checkpoint header
vote_addrs, // parsed vote addresses from checkpoint header
None, // TODO: parse attestation like reth-bsc-trail
attestation, // parsed attestation from header
turn_length, // parsed turn length from checkpoint header
&self.spec,
) {
Expand Down