mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-24 06:33:20 +00:00
fix(sequencer): add apply_produced which is like apply_adopted but for our own block, which is not in channel yet
This commit is contained in:
parent
1feebc1d91
commit
71b59ac1b8
@ -161,6 +161,22 @@ impl ChainState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a block we produced ourselves.
|
||||
///
|
||||
/// Unlike [`Self::apply_adopted`] this never reorgs: our block is not on
|
||||
/// the channel yet, so it may only *extend* the head. A head already at
|
||||
/// (or past) this height means a peer's block won the race on the
|
||||
/// channel — ours is stale and the caller drops it.
|
||||
pub fn apply_produced(&mut self, this_msg: MsgId, block: &Block) -> AcceptOutcome {
|
||||
if self
|
||||
.head_tip()
|
||||
.is_some_and(|tip| block.header.block_id <= tip.block_id)
|
||||
{
|
||||
return AcceptOutcome::AlreadyApplied;
|
||||
}
|
||||
self.apply_adopted(this_msg, block)
|
||||
}
|
||||
|
||||
/// Reverts an orphaned head block and everything after it, then re-derives head.
|
||||
pub fn revert_orphan(&mut self, this_msg: MsgId, block: &Block) {
|
||||
if let Some(idx) = self.head_position_of(this_msg, block) {
|
||||
@ -632,6 +648,49 @@ mod tests {
|
||||
assert_head_matches_replay(&chain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn produced_block_losing_a_race_does_not_reorg_the_head() {
|
||||
let accounts = initial_pub_accounts_private_keys();
|
||||
let from = accounts[0].account_id;
|
||||
let to = accounts[1].account_id;
|
||||
let sign_key = accounts[0].pub_sign_key.clone();
|
||||
|
||||
let mut chain = ChainState::new(initial_state());
|
||||
let genesis = produce_dummy_block(1, None, vec![]);
|
||||
chain.apply_adopted(msg(1), &genesis);
|
||||
|
||||
// A peer's block wins height 2 on the channel.
|
||||
let peer = produce_dummy_block(2, Some(genesis.header.hash), vec![]);
|
||||
chain.apply_adopted(msg(2), &peer);
|
||||
|
||||
// Our own block at that height is not on the channel, so — unlike an
|
||||
// adopted competitor — it must not reorg the head onto itself.
|
||||
let tx = create_transaction_native_token_transfer(from, 0, to, 10, &sign_key);
|
||||
let ours = produce_dummy_block(2, Some(genesis.header.hash), vec![tx]);
|
||||
assert!(matches!(
|
||||
chain.apply_produced(msg(12), &ours),
|
||||
AcceptOutcome::AlreadyApplied
|
||||
));
|
||||
assert_eq!(chain.head_tip().expect("head tip").hash, peer.header.hash);
|
||||
assert_eq!(chain.head_state().get_account_by_id(to).balance, 20000);
|
||||
assert_head_matches_replay(&chain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn produced_block_extending_the_head_applies() {
|
||||
let mut chain = ChainState::new(initial_state());
|
||||
let genesis = produce_dummy_block(1, None, vec![]);
|
||||
chain.apply_adopted(msg(1), &genesis);
|
||||
|
||||
let ours = produce_dummy_block(2, Some(genesis.header.hash), vec![]);
|
||||
assert!(matches!(
|
||||
chain.apply_produced(msg(2), &ours),
|
||||
AcceptOutcome::Applied
|
||||
));
|
||||
assert_eq!(chain.head_tip().expect("head tip").hash, ours.header.hash);
|
||||
assert_head_matches_replay(&chain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_adopted_competitor_leaves_head_intact() {
|
||||
let mut chain = ChainState::new(initial_state());
|
||||
|
||||
@ -668,7 +668,7 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
|
||||
withdrawal_reconciliation_keys: Vec<WithdrawalReconciliationKey>,
|
||||
) -> Result<()> {
|
||||
let mut chain = self.chain.lock().expect("chain state mutex poisoned");
|
||||
match chain.apply_adopted(this_msg, block) {
|
||||
match chain.apply_produced(this_msg, block) {
|
||||
AcceptOutcome::Applied => {
|
||||
// Persisted under the lock so disk writes land in apply order
|
||||
// with the follow path.
|
||||
|
||||
@ -1680,7 +1680,15 @@ async fn record_produced_block_skips_persistence_on_lost_race() {
|
||||
.apply_adopted(MsgId::from([9; 32]), &peer_block);
|
||||
|
||||
// Our competing block at the same height: same parent, different content.
|
||||
let tx = common::test_utils::produce_dummy_empty_transaction();
|
||||
let acc1 = initial_public_user_accounts()[0].account_id;
|
||||
let acc2 = initial_public_user_accounts()[1].account_id;
|
||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||
acc1,
|
||||
0,
|
||||
acc2,
|
||||
10,
|
||||
&create_signing_key_for_account1(),
|
||||
);
|
||||
let our_block = common::test_utils::produce_dummy_block(2, Some(genesis_meta.hash), vec![tx]);
|
||||
sequencer
|
||||
.record_produced_block(
|
||||
|
||||
@ -749,21 +749,26 @@ impl RocksDBIO {
|
||||
self.put_block_payload(&to_write, &mut batch)?;
|
||||
}
|
||||
|
||||
// `head_tip` is `None` only for a chain holding no blocks at all, which
|
||||
// implies nothing was applied — and the store, created with genesis,
|
||||
// cannot represent it. No tip to pin, nothing to persist.
|
||||
let Some(tip) = head_tip else {
|
||||
debug_assert!(batch.is_empty() && final_snapshot.is_none());
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// A shrink-only update (orphans without adopted replacements) has no
|
||||
// payloads to write but must still rewind the tip meta, or the stored
|
||||
// state tears against the stale disk head on the next produce.
|
||||
let tip_rewound = head_tip.is_some_and(|tip| tip.id < last_block_in_db);
|
||||
if batch.is_empty() && final_snapshot.is_none() && !tip_rewound {
|
||||
if batch.is_empty() && final_snapshot.is_none() && tip.id == last_block_in_db {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(tip) = head_tip {
|
||||
for stale_id in tip.id.saturating_add(1)..=last_block_in_db {
|
||||
self.delete_block_payload(stale_id, &mut batch)?;
|
||||
}
|
||||
self.put_meta_last_block_in_db_batch(tip.id, &mut batch)?;
|
||||
self.put_meta_latest_block_meta_batch(tip, &mut batch)?;
|
||||
for stale_id in tip.id.saturating_add(1)..=last_block_in_db {
|
||||
self.delete_block_payload(stale_id, &mut batch)?;
|
||||
}
|
||||
self.put_meta_last_block_in_db_batch(tip.id, &mut batch)?;
|
||||
self.put_meta_latest_block_meta_batch(tip, &mut batch)?;
|
||||
self.put_lee_state_in_db_batch(state, &mut batch)?;
|
||||
if let Some((final_state, final_meta)) = final_snapshot {
|
||||
self.put_final_snapshot_batch(final_state, final_meta, &mut batch)?;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user