Skip to content
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

feat: implement the Bohr upgrade of BSC #86

Merged
merged 29 commits into from
Aug 5, 2024
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
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -554,10 +554,10 @@ test-fuzz = "5"
iai-callgrind = "0.11"

[patch.crates-io]
revm = { git = "https://github.com/bnb-chain/revm", rev = "0fceb6332b50ece91aabe3f90df06ce5aa44111f" }
revm-interpreter = { git = "https://github.com/bnb-chain/revm", rev = "0fceb6332b50ece91aabe3f90df06ce5aa44111f" }
revm-precompile = { git = "https://github.com/bnb-chain/revm", rev = "0fceb6332b50ece91aabe3f90df06ce5aa44111f" }
revm-primitives = { git = "https://github.com/bnb-chain/revm", rev = "0fceb6332b50ece91aabe3f90df06ce5aa44111f" }
revm = { git = "https://github.com/yutianwu/revm", rev = "dc6c26d9b03541191c1124c203a0cc88b17170a9" }
revm-interpreter = { git = "https://github.com/yutianwu/revm", rev = "dc6c26d9b03541191c1124c203a0cc88b17170a9" }
revm-precompile = { git = "https://github.com/yutianwu/revm", rev = "dc6c26d9b03541191c1124c203a0cc88b17170a9" }
revm-primitives = { git = "https://github.com/yutianwu/revm", rev = "dc6c26d9b03541191c1124c203a0cc88b17170a9" }
alloy-chains = { git = "https://github.com/bnb-chain/alloy-chains-rs.git", rev = "b7c5379cf47345181f8dce350acafb958f47152a" }
alloy-rpc-types-eth = { git = "https://github.com/bnb-chain/alloy", rev = "18f098dd78be661433bae682ad161a41f8a9c301" }
alloy-consensus = { git = "https://github.com/bnb-chain/alloy", rev = "18f098dd78be661433bae682ad161a41f8a9c301" }
Expand Down
26 changes: 26 additions & 0 deletions crates/bsc/consensus/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,19 @@ lazy_static! {
],
"stateMutability": "view"
},
{
"inputs": [],
"name": "getTurnLength",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"type": "function",
"name": "getValidators",
Expand Down Expand Up @@ -5975,6 +5988,19 @@ impl Parlia {

output[0].as_uint().unwrap().0
}

pub fn get_turn_length(&self) -> (Address, Bytes) {
let function = self.validator_abi.function("getTurnLength").unwrap().first().unwrap();

(VALIDATOR_CONTRACT.parse().unwrap(), Bytes::from(function.abi_encode_input(&[]).unwrap()))
}

pub fn unpack_data_into_turn_length(&self, data: &[u8]) -> U256 {
let function = self.validator_abi.function("getTurnLength").unwrap().first().unwrap();
let output = function.abi_decode_output(data, true).unwrap();

output[0].as_uint().unwrap().0
}
}

#[cfg(test)]
Expand Down
5 changes: 5 additions & 0 deletions crates/bsc/consensus/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub const EXTRA_VANITY_LEN: usize = 32;
pub const EXTRA_VANITY_LEN_WITH_VALIDATOR_NUM: usize = 33;
/// Fixed number of extra-data suffix bytes reserved for signer seal
pub const EXTRA_SEAL_LEN: usize = 65;
/// Fixed number of extra-data suffix bytes reserved for turnLength
pub const TURN_LEN: usize = 1;
/// Address length of signer
pub const ADDRESS_LENGTH: usize = 20;
/// BLS public key bytes length
Expand All @@ -29,3 +31,6 @@ pub const COLLECT_ADDITIONAL_VOTES_REWARD_RATIO: usize = 100;
pub(crate) const BACKOFF_TIME_OF_INITIAL: u64 = 1_u64;
/// Random additional delay (per signer) to allow concurrent signers, second
pub(crate) const BACKOFF_TIME_OF_WIGGLE: u64 = 1_u64;

/// Default turn length
pub const DEFAULT_TURN_LENGTH: u8 = 1;
4 changes: 4 additions & 0 deletions crates/bsc/consensus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,8 @@ pub enum ParliaConsensusError {
/// Error when encountering a recover ecdsa inner error
#[error("recover ecdsa inner error")]
RecoverECDSAInnerError,

/// Error when header extra turn is invalid
#[error("invalid turnLength")]
ExtraInvalidTurnLength,
}
91 changes: 67 additions & 24 deletions crates/bsc/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use secp256k1::{
use sha3::{Digest, Keccak256};
use std::{
clone::Clone,
collections::HashMap,
fmt::{Debug, Formatter},
num::NonZeroUsize,
sync::Arc,
Expand Down Expand Up @@ -182,6 +181,33 @@ impl Parlia {
}
}

pub fn get_turn_length_from_header(
&self,
header: &Header,
) -> Result<Option<u8>, ParliaConsensusError> {
if header.number % self.epoch != 0 ||
!self.chain_spec.is_bohr_active_at_timestamp(header.timestamp)
{
return Ok(None);
}

if header.extra_data.len() <= EXTRA_VANITY_LEN + EXTRA_SEAL_LEN {
return Err(ParliaConsensusError::InvalidHeaderExtraLen {
header_extra_len: header.extra_data.len() as u64,
});
}

let num = header.extra_data[EXTRA_VANITY_LEN] as usize;
let pos = EXTRA_VANITY_LEN + 1 + num * EXTRA_VALIDATOR_LEN;

if header.extra_data.len() <= pos {
return Err(ParliaConsensusError::ExtraInvalidTurnLength);
}

let turn_length = header.extra_data[pos];
Ok(Some(turn_length))
}

pub fn get_vote_attestation_from_header(
&self,
header: &Header,
Expand All @@ -201,8 +227,16 @@ impl Parlia {
} else {
let validator_count =
header.extra_data[EXTRA_VANITY_LEN_WITH_VALIDATOR_NUM - 1] as usize;
let start = EXTRA_VANITY_LEN_WITH_VALIDATOR_NUM + validator_count * EXTRA_VALIDATOR_LEN;
let mut start =
EXTRA_VANITY_LEN_WITH_VALIDATOR_NUM + validator_count * EXTRA_VALIDATOR_LEN;
let is_bohr_active = self.chain_spec.is_bohr_active_at_timestamp(header.timestamp);
if is_bohr_active {
start += TURN_LEN;
}
let end = extra_len - EXTRA_SEAL_LEN;
if end <= start {
return Ok(None)
}
&header.extra_data[start..end]
};
if raw_attestation_data.is_empty() {
Expand Down Expand Up @@ -240,15 +274,17 @@ impl Parlia {
}

let count = header.extra_data[EXTRA_VANITY_LEN] as usize;
if count == 0 ||
extra_len <= EXTRA_VANITY_LEN + EXTRA_SEAL_LEN + count * EXTRA_VALIDATOR_LEN
{
return None;
}

let start = EXTRA_VANITY_LEN_WITH_VALIDATOR_NUM;
let end = start + count * EXTRA_VALIDATOR_LEN;

let mut extra_min_len = end + EXTRA_SEAL_LEN;
let is_bohr_active = self.chain_spec.is_bohr_active_at_timestamp(header.timestamp);
if is_bohr_active {
extra_min_len += TURN_LEN;
}
if count == 0 || extra_len < extra_min_len {
return None
}
Some(header.extra_data[start..end].to_vec())
}
}
Expand All @@ -263,36 +299,35 @@ impl Parlia {
let mut validators = snap.validators.clone();

if self.chain_spec.is_planck_active_at_block(header.number) {
let validator_count = validators.len() as u64;

let mut recents = HashMap::with_capacity(snap.recent_proposers.len());
let bound = header.number.saturating_sub(validator_count / 2 + 1);
for (&seen, &proposer) in &snap.recent_proposers {
if seen <= bound {
continue
};
recents.insert(proposer, seen);
}

if recents.contains_key(&validator) {
let counts = snap.count_recent_proposers();
if snap.sign_recently_by_counts(validator, &counts) {
// The backOffTime does not matter when a validator has signed recently.
return 0;
}

let inturn_addr = snap.inturn_validator();
if recents.contains_key(&inturn_addr) {
if snap.sign_recently_by_counts(inturn_addr, &counts) {
trace!(
"in turn validator({:?}) has recently signed, skip initialBackOffTime",
inturn_addr
);
delay = 0
}

// Exclude the recently signed validators
validators.retain(|addr| !recents.contains_key(addr));
// Exclude the recently signed validators and inturn validator
validators.retain(|addr| {
!(snap.sign_recently_by_counts(*addr, &counts) ||
self.chain_spec.is_bohr_active_at_timestamp(header.timestamp) &&
*addr == inturn_addr)
});
}

let mut rng = RngSource::new(snap.block_number as i64);
let mut rng = if self.chain_spec.is_bohr_active_at_timestamp(header.timestamp) {
RngSource::new(header.number as i64 / snap.turn_length as i64)
} else {
RngSource::new(snap.block_number as i64)
};

let mut back_off_steps: Vec<u64> = (0..validators.len() as u64).collect();
back_off_steps.shuffle(&mut rng);

Expand Down Expand Up @@ -480,6 +515,14 @@ impl Parlia {
return Err(ConsensusError::BlobGasUsedUnexpected)
} else if header.excess_blob_gas.is_some() {
return Err(ConsensusError::ExcessBlobGasUnexpected)
}

if self.chain_spec.is_bohr_active_at_timestamp(header.timestamp) {
if header.parent_beacon_block_root.is_none() ||
header.parent_beacon_block_root.unwrap() != B256::default()
{
return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
}
} else if header.parent_beacon_block_root.is_some() {
return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
}
Expand Down
31 changes: 28 additions & 3 deletions crates/bsc/consensus/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ use reth_primitives::{
keccak256, system_contracts::SYSTEM_CONTRACTS_SET, Address, BufMut, BytesMut, Header,
TransactionSigned, B256, B64, U256,
};
use std::env;

const SECONDS_PER_DAY: u64 = 86400; // 24 * 60 * 60

pub const fn is_same_day_in_utc(first: u64, second: u64) -> bool {
first / SECONDS_PER_DAY == second / SECONDS_PER_DAY
pub fn is_same_day_in_utc(first: u64, second: u64) -> bool {
let interval = env::var("BREATHE_BLOCK_INTERVAL")
.ok()
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(SECONDS_PER_DAY);

first / interval == second / interval
}

pub const fn is_breathe_block(last_block_time: u64, block_time: u64) -> bool {
pub fn is_breathe_block(last_block_time: u64, block_time: u64) -> bool {
last_block_time != 0 && !is_same_day_in_utc(last_block_time, block_time)
}

Expand Down Expand Up @@ -57,6 +63,16 @@ pub fn encode_header_with_chain_id(header: &Header, out: &mut dyn BufMut, chain_
Encodable::encode(&header.extra_data[..header.extra_data.len() - EXTRA_SEAL_LEN], out); // will panic if extra_data is less than EXTRA_SEAL_LEN
Encodable::encode(&header.mix_hash, out);
Encodable::encode(&B64::new(header.nonce.to_be_bytes()), out);

if header.parent_beacon_block_root.is_some() &&
header.parent_beacon_block_root.unwrap() == B256::default()
{
Encodable::encode(&U256::from(header.base_fee_per_gas.unwrap()), out);
Encodable::encode(&header.withdrawals_root.unwrap(), out);
Encodable::encode(&header.blob_gas_used.unwrap(), out);
Encodable::encode(&header.excess_blob_gas.unwrap(), out);
Encodable::encode(&header.parent_beacon_block_root.unwrap(), out);
}
}

fn rlp_header(header: &Header, chain_id: u64) -> alloy_rlp::Header {
Expand All @@ -81,6 +97,15 @@ fn rlp_header(header: &Header, chain_id: u64) -> alloy_rlp::Header {
rlp_head.payload_length += header.mix_hash.length(); // mix_hash
rlp_head.payload_length += &B64::new(header.nonce.to_be_bytes()).length(); // nonce

if header.parent_beacon_block_root.is_some() &&
header.parent_beacon_block_root.unwrap() == B256::default()
{
rlp_head.payload_length += U256::from(header.base_fee_per_gas.unwrap()).length();
rlp_head.payload_length += header.withdrawals_root.unwrap().length();
rlp_head.payload_length += header.blob_gas_used.unwrap().length();
rlp_head.payload_length += header.excess_blob_gas.unwrap().length();
rlp_head.payload_length += header.parent_beacon_block_root.unwrap().length();
}
rlp_head
}

Expand Down
8 changes: 6 additions & 2 deletions crates/bsc/evm/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ pub fn revm_spec_by_timestamp_after_shanghai(
chain_spec: &ChainSpec,
timestamp: u64,
) -> revm_primitives::SpecId {
if chain_spec.fork(BscHardfork::HaberFix).active_at_timestamp(timestamp) {
if chain_spec.fork(BscHardfork::Bohr).active_at_timestamp(timestamp) {
revm_primitives::BOHR
} else if chain_spec.fork(BscHardfork::HaberFix).active_at_timestamp(timestamp) {
revm_primitives::HABER_FIX
} else if chain_spec.fork(BscHardfork::Haber).active_at_timestamp(timestamp) {
revm_primitives::HABER
Expand All @@ -26,7 +28,9 @@ pub fn revm_spec_by_timestamp_after_shanghai(

/// return `revm_spec` from spec configuration.
pub fn revm_spec(chain_spec: &ChainSpec, block: &Head) -> revm_primitives::SpecId {
if chain_spec.fork(BscHardfork::HaberFix).active_at_head(block) {
if chain_spec.fork(BscHardfork::Bohr).active_at_head(block) {
revm_primitives::BOHR
} else if chain_spec.fork(BscHardfork::HaberFix).active_at_head(block) {
revm_primitives::HABER_FIX
} else if chain_spec.fork(BscHardfork::Haber).active_at_head(block) {
revm_primitives::HABER
Expand Down
4 changes: 4 additions & 0 deletions crates/bsc/evm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ pub enum BscBlockExecutionError {
/// Error when failed to execute system contract upgrade
#[error("system contract upgrade error")]
SystemContractUpgradeError,

/// Error when the turn length is different from the calculated turn length
#[error("mismatching turn length on epoch block")]
MismatchingEpochTurnLengthError,
}

impl From<BscBlockExecutionError> for BlockExecutionError {
Expand Down
Loading
Loading