mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-10 15:03:33 +00:00
fix(chain_state): dont allow same hash+msgId on a different blockId
This commit is contained in:
parent
caf38ebc0a
commit
1feebc1d91
@ -1,7 +1,7 @@
|
|||||||
//! Two-tier chain state: a reorg-able `head` the sequencer builds on, plus an
|
//! Two-tier chain state: a reorg-able `head` the sequencer builds on, plus an
|
||||||
//! irreversible `final` tier.
|
//! irreversible `final` tier.
|
||||||
|
|
||||||
use common::{HashType, block::Block};
|
use common::block::Block;
|
||||||
use lee::V03State;
|
use lee::V03State;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use logos_blockchain_core::mantle::ops::channel::MsgId;
|
use logos_blockchain_core::mantle::ops::channel::MsgId;
|
||||||
@ -91,11 +91,14 @@ impl ChainState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Position of a head entry, matched by `MsgId` or block hash (restored
|
/// Position of a head entry, matched by `MsgId` or block hash (restored
|
||||||
/// entries carry sentinel `MsgId`s; re-inscriptions arrive under fresh ones).
|
/// entries carry sentinel `MsgId`s; re-inscriptions arrive under fresh ones)
|
||||||
fn head_position_of(&self, this_msg: MsgId, block_hash: HashType) -> Option<usize> {
|
/// — always at the same claimed height: a hash or `MsgId` collision with a
|
||||||
self.head_blocks
|
/// different `block_id` is malformed and must fall through to validation.
|
||||||
.iter()
|
fn head_position_of(&self, this_msg: MsgId, block: &Block) -> Option<usize> {
|
||||||
.position(|entry| entry.this_msg == this_msg || entry.block.header.hash == block_hash)
|
self.head_blocks.iter().position(|entry| {
|
||||||
|
entry.block.header.block_id == block.header.block_id
|
||||||
|
&& (entry.this_msg == this_msg || entry.block.header.hash == block.header.hash)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies an adopted head block.
|
/// Applies an adopted head block.
|
||||||
@ -106,7 +109,7 @@ impl ChainState {
|
|||||||
///
|
///
|
||||||
/// On failure the head stays unchanged and no stall is recorded.
|
/// On failure the head stays unchanged and no stall is recorded.
|
||||||
pub fn apply_adopted(&mut self, this_msg: MsgId, block: &Block) -> AcceptOutcome {
|
pub fn apply_adopted(&mut self, this_msg: MsgId, block: &Block) -> AcceptOutcome {
|
||||||
if self.head_position_of(this_msg, block.header.hash).is_some() {
|
if self.head_position_of(this_msg, block).is_some() {
|
||||||
return AcceptOutcome::AlreadyApplied;
|
return AcceptOutcome::AlreadyApplied;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +163,7 @@ impl ChainState {
|
|||||||
|
|
||||||
/// Reverts an orphaned head block and everything after it, then re-derives head.
|
/// Reverts an orphaned head block and everything after it, then re-derives head.
|
||||||
pub fn revert_orphan(&mut self, this_msg: MsgId, block: &Block) {
|
pub fn revert_orphan(&mut self, this_msg: MsgId, block: &Block) {
|
||||||
if let Some(idx) = self.head_position_of(this_msg, block.header.hash) {
|
if let Some(idx) = self.head_position_of(this_msg, block) {
|
||||||
self.head_blocks.truncate(idx);
|
self.head_blocks.truncate(idx);
|
||||||
self.rederive_head();
|
self.rederive_head();
|
||||||
}
|
}
|
||||||
@ -175,7 +178,7 @@ impl ChainState {
|
|||||||
) -> Vec<AcceptOutcome> {
|
) -> Vec<AcceptOutcome> {
|
||||||
let earliest = orphaned
|
let earliest = orphaned
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(msg, block)| self.head_position_of(*msg, block.header.hash))
|
.filter_map(|(msg, block)| self.head_position_of(*msg, block))
|
||||||
.min();
|
.min();
|
||||||
if let Some(idx) = earliest {
|
if let Some(idx) = earliest {
|
||||||
self.head_blocks.truncate(idx);
|
self.head_blocks.truncate(idx);
|
||||||
@ -210,7 +213,7 @@ impl ChainState {
|
|||||||
l1_slot: Slot,
|
l1_slot: Slot,
|
||||||
) -> AcceptOutcome {
|
) -> AcceptOutcome {
|
||||||
// Match by `MsgId` or block hash (re-inscriptions, restored entries).
|
// Match by `MsgId` or block hash (re-inscriptions, restored entries).
|
||||||
if let Some(idx) = self.head_position_of(this_msg, block.header.hash) {
|
if let Some(idx) = self.head_position_of(this_msg, block) {
|
||||||
self.finalize_through(idx);
|
self.finalize_through(idx);
|
||||||
return AcceptOutcome::Applied;
|
return AcceptOutcome::Applied;
|
||||||
}
|
}
|
||||||
@ -720,6 +723,26 @@ mod tests {
|
|||||||
assert!(chain.restore_head_block(skipped).is_err());
|
assert!(chain.restore_head_block(skipped).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finalized_hash_alias_with_wrong_id_is_not_absorbed() {
|
||||||
|
let mut chain = ChainState::new(initial_state());
|
||||||
|
let genesis = produce_dummy_block(1, None, vec![]);
|
||||||
|
chain.apply_adopted(msg(1), &genesis);
|
||||||
|
|
||||||
|
// A malformed message reusing genesis's hash under a different claimed
|
||||||
|
// id must not match the held entry as a re-delivery; it falls through
|
||||||
|
// to validation and parks.
|
||||||
|
let mut alias = genesis.clone();
|
||||||
|
alias.header.block_id = 6;
|
||||||
|
assert!(matches!(
|
||||||
|
chain.apply_finalized(msg(66), &alias, slot(10)),
|
||||||
|
AcceptOutcome::Parked(_)
|
||||||
|
));
|
||||||
|
assert_eq!(chain.head_tip().expect("head tip").block_id, 1);
|
||||||
|
assert!(chain.final_tip().is_none());
|
||||||
|
assert_head_matches_replay(&chain);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn finalized_reinscription_matches_by_block_hash() {
|
fn finalized_reinscription_matches_by_block_hash() {
|
||||||
let mut chain = ChainState::new(initial_state());
|
let mut chain = ChainState::new(initial_state());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user