diff --git a/Cargo.toml b/Cargo.toml index 21186c6..016fcf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ path = "src/main.rs" name = "snapshot-checker" path = "src/bin/snapshot_checker.rs" + + [dependencies] reth = { git = "https://github.com/clydemeng/reth.git", branch = "parlia-6487f0b" } reth-cli = { git = "https://github.com/clydemeng/reth.git", branch = "parlia-6487f0b" } diff --git a/scripts/start_testnet.sh b/scripts/start_testnet.sh index 7036733..7bf1c69 100755 --- a/scripts/start_testnet.sh +++ b/scripts/start_testnet.sh @@ -26,10 +26,17 @@ fi # tip_block=0xb230ec6bfd3348dff7ae9af62d8d2fb25a2ff3781c770b3fcf75a186e6ddc1bd # 25M # tip_block=0x1253e0b2342239c7e042d87d75974e7824c5503cd2ec34bfc7f5a8b25a1c36b1 # 35M +# tip_block=0xb74e00072b7aa7720f547c4ec3075b1f7310f98a2d8b3323289ad66927010dfa # 47M +tip_block=0xb74e00072b7aa7720f547c4ec3075b1f7310f98a2d8b3323289ad66927010dfa # 45M + + + + +tip_block=0xb5ddf3dcb55cf5013110acd3c6c8eaffe5c996e7d3c9d4803e36e9efccdbce47 # 43150000 + -tip_block=0x1253e0b2342239c7e042d87d75974e7824c5503cd2ec34bfc7f5a8b25a1c36b1 # 35M RUST_LOG=INFO ./target/release/reth-bsc node \ --chain=bsc-testnet \ --http --http.api="eth, net, txpool, web3, rpc" \ diff --git a/src/consensus/parlia/validation.rs b/src/consensus/parlia/validation.rs index 3d3e7b4..a833d60 100644 --- a/src/consensus/parlia/validation.rs +++ b/src/consensus/parlia/validation.rs @@ -155,6 +155,10 @@ where let message = Message::from_digest(seal_hash.0); // Parse signature: 64 bytes + 1 recovery byte + if signature.len() != 65 { + return Err(ConsensusError::Other(format!("Invalid signature length: expected 65, got {}", signature.len()).into())); + } + let sig_bytes = &signature[..64]; let recovery_id = signature[64]; @@ -175,54 +179,102 @@ where let hash = keccak256(&public_key_bytes[1..]); // Skip 0x04 prefix let address = Address::from_slice(&hash[12..]); - - Ok(address) } - /// Calculate seal hash for BSC headers (using SealContent struct like double_sign precompile) + /// Calculate seal hash for BSC headers (matching bsc-erigon's EncodeSigHeader exactly) fn calculate_seal_hash(&self, header: &SealedHeader) -> alloy_primitives::B256 { use alloy_primitives::keccak256; - // Use the same approach as the double_sign precompile + // Use the exact same approach as bsc-erigon's EncodeSigHeader const EXTRA_SEAL: usize = 65; let chain_id = self.chain_spec.chain().id(); let extra_data = &header.extra_data(); - // Extract extra data without the seal + // Extract extra data without the seal (matching bsc-erigon line 1761) let extra_without_seal = if extra_data.len() >= EXTRA_SEAL { &extra_data[..extra_data.len() - EXTRA_SEAL] } else { extra_data }; - // Create SealContent exactly like double_sign precompile + // Encode directly as slice like bsc-erigon does (NOT using SealContent struct) + // This matches bsc-erigon's EncodeSigHeader function exactly - let seal_content = crate::evm::precompiles::double_sign::SealContent { - chain_id, - parent_hash: header.parent_hash().0, - uncle_hash: header.ommers_hash().0, - coinbase: header.beneficiary().0 .0, - root: header.state_root().0, - tx_hash: header.transactions_root().0, - receipt_hash: header.receipts_root().0, - bloom: header.logs_bloom().0 .0, - difficulty: header.difficulty().clone(), - number: header.number(), - gas_limit: header.gas_limit(), - gas_used: header.gas_used(), - time: header.timestamp(), - extra: alloy_primitives::Bytes::from(extra_without_seal.to_vec()), - mix_digest: header.mix_hash().unwrap_or_default().0, - nonce: header.nonce().unwrap_or_default().0, - }; + // manual field-by-field encoding + // This matches reth-bsc-trail's encode_header_with_chain_id function exactly + use alloy_rlp::Encodable; + use alloy_primitives::{bytes::BytesMut, U256}; - // Use automatic RLP encoding like double_sign precompile - let encoded = alloy_rlp::encode(seal_content); - let result = keccak256(&encoded); + let mut out = BytesMut::new(); - + // First encode the RLP list header (like reth-bsc-trail's rlp_header function) + let mut rlp_head = alloy_rlp::Header { list: true, payload_length: 0 }; + + // Calculate payload length for all fields + rlp_head.payload_length += U256::from(chain_id).length(); + rlp_head.payload_length += header.parent_hash().length(); + rlp_head.payload_length += header.ommers_hash().length(); + rlp_head.payload_length += header.beneficiary().length(); + rlp_head.payload_length += header.state_root().length(); + rlp_head.payload_length += header.transactions_root().length(); + rlp_head.payload_length += header.receipts_root().length(); + rlp_head.payload_length += header.logs_bloom().length(); + rlp_head.payload_length += header.difficulty().length(); + rlp_head.payload_length += U256::from(header.number()).length(); + rlp_head.payload_length += header.gas_limit().length(); + rlp_head.payload_length += header.gas_used().length(); + rlp_head.payload_length += header.timestamp().length(); + 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 + { + rlp_head.payload_length += U256::from(header.base_fee_per_gas().unwrap_or_default()).length(); + rlp_head.payload_length += header.withdrawals_root().unwrap_or_default().length(); + 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(); + } + + // Encode the RLP list header first + rlp_head.encode(&mut out); + + // Then encode each field individually (exactly like reth-bsc-trail) + Encodable::encode(&U256::from(chain_id), &mut out); + Encodable::encode(&header.parent_hash(), &mut out); + Encodable::encode(&header.ommers_hash(), &mut out); + Encodable::encode(&header.beneficiary(), &mut out); + Encodable::encode(&header.state_root(), &mut out); + Encodable::encode(&header.transactions_root(), &mut out); + Encodable::encode(&header.receipts_root(), &mut out); + Encodable::encode(&header.logs_bloom(), &mut out); + Encodable::encode(&header.difficulty(), &mut out); + Encodable::encode(&U256::from(header.number()), &mut out); + Encodable::encode(&header.gas_limit(), &mut out); + Encodable::encode(&header.gas_used(), &mut out); + Encodable::encode(&header.timestamp(), &mut out); + Encodable::encode(&extra_without_seal, &mut out); + Encodable::encode(&header.mix_hash().unwrap_or_default(), &mut out); + Encodable::encode(&header.nonce().unwrap_or_default(), &mut out); + + // Add conditional fields for post-4844 blocks + if header.parent_beacon_block_root().is_some() && + header.parent_beacon_block_root().unwrap() == alloy_primitives::B256::ZERO + { + Encodable::encode(&U256::from(header.base_fee_per_gas().unwrap_or_default()), &mut out); + Encodable::encode(&header.withdrawals_root().unwrap_or_default(), &mut out); + 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); + } + + let encoded = out.to_vec(); + let result = keccak256(&encoded); result }