mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-08 14:03:31 +00:00
fix(sequencer): don't increment withdrawals count on reconstruction to prevent phantom counts
This commit is contained in:
parent
3e7e9ed4e1
commit
8fdc3f203d
@ -390,21 +390,20 @@ impl<BP: BlockPublisherTrait> SequencerCore<BP> {
|
|||||||
chain_consistency::apply_block(block, &mut scratch)
|
chain_consistency::apply_block(block, &mut scratch)
|
||||||
.map_err(|err| anyhow!("Failed to apply channel block {block_id}: {err}"))?;
|
.map_err(|err| anyhow!("Failed to apply channel block {block_id}: {err}"))?;
|
||||||
|
|
||||||
// Derive bridge bookkeeping from the block's transactions, matching the
|
// Mark the deposits' pending records submitted so the production-time
|
||||||
// production path so the reconciliation counters stay consistent.
|
// guard drops the mints cold-start backfill re-queued for them. Withdraw
|
||||||
let mut deposit_event_ids = Vec::new();
|
// intents are deliberately not counted: backfill already re-delivered and
|
||||||
let mut withdrawals = Vec::new();
|
// dropped their finalized L1 events, so an increment here would never be
|
||||||
for tx in &block.body.transactions {
|
// consumed and would leave a phantom count.
|
||||||
if let Some(deposit_id) = extract_bridge_deposit_id(tx) {
|
let deposit_event_ids: Vec<_> = block
|
||||||
deposit_event_ids.push(deposit_id);
|
.body
|
||||||
}
|
.transactions
|
||||||
if let Some(withdraw) = extract_bridge_withdraw_data(tx) {
|
.iter()
|
||||||
withdrawals.push(withdraw_event_reconciliation_key(&withdraw.outputs)?);
|
.filter_map(extract_bridge_deposit_id)
|
||||||
}
|
.collect();
|
||||||
}
|
|
||||||
|
|
||||||
store
|
store
|
||||||
.update(block, &deposit_event_ids, withdrawals, &scratch)
|
.update(block, &deposit_event_ids, Vec::new(), &scratch)
|
||||||
.context("Failed to persist reconstructed block")?;
|
.context("Failed to persist reconstructed block")?;
|
||||||
*state = scratch;
|
*state = scratch;
|
||||||
store
|
store
|
||||||
|
|||||||
@ -311,15 +311,81 @@ async fn reconstructed_deposit_is_not_reminted_after_backfill_redelivery() {
|
|||||||
"the re-delivered mint must be skipped, not re-included in a block"
|
"the re-delivered mint must be skipped, not re-included in a block"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A reconstructed withdraw's finalized L1 event was already re-delivered (and
|
||||||
|
// dropped) by cold-start backfill, so it will never be consumed again.
|
||||||
|
// Reconstruction must not count it, or the count stays phantom-inflated forever.
|
||||||
let withdraw_arg = crate::extract_bridge_withdraw_data(&withdraw_tx).expect("withdraw data");
|
let withdraw_arg = crate::extract_bridge_withdraw_data(&withdraw_tx).expect("withdraw data");
|
||||||
let key = crate::withdraw_event_reconciliation_key(&withdraw_arg.outputs).expect("recon key");
|
let key = crate::withdraw_event_reconciliation_key(&withdraw_arg.outputs).expect("recon key");
|
||||||
assert!(
|
assert!(
|
||||||
seq_b
|
!seq_b
|
||||||
.block_store()
|
.block_store()
|
||||||
.dbio()
|
.dbio()
|
||||||
.consume_unseen_withdraw_count(key)
|
.consume_unseen_withdraw_count(key)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
"reconstruction must record an unseen withdraw for bridge reconciliation"
|
"reconstruction must not leave a phantom unseen-withdraw count"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reconstructed withdraw block must not touch the unseen-withdraw counter.
|
||||||
|
/// Its finalized L1 Withdraw event was already re-delivered (and dropped as a
|
||||||
|
/// no-op) by cold-start backfill, so counting it during reconstruction would
|
||||||
|
/// leave a permanent phantom that nothing ever consumes.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn reconstructed_withdraw_leaves_no_phantom_unseen_count() {
|
||||||
|
let recipient = initial_public_user_accounts()[0].account_id;
|
||||||
|
let withdraw_amount = 100_u64;
|
||||||
|
let bedrock_account_pk = [0x33_u8; 32];
|
||||||
|
|
||||||
|
// Sequencer A produces a single withdraw block; treat its chain as the channel.
|
||||||
|
let config_a = bridge_funded_config();
|
||||||
|
let (mut seq_a, mempool_a) =
|
||||||
|
SequencerCoreWithMockClients::start_from_config(config_a.clone()).await;
|
||||||
|
let withdraw_tx = build_public_withdraw_tx(
|
||||||
|
recipient,
|
||||||
|
0,
|
||||||
|
withdraw_amount,
|
||||||
|
bedrock_account_pk,
|
||||||
|
&create_signing_key_for_account1(),
|
||||||
|
);
|
||||||
|
mempool_a
|
||||||
|
.push((TransactionOrigin::User, withdraw_tx.clone()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
seq_a.produce_new_block().await.unwrap();
|
||||||
|
|
||||||
|
let withdraw_arg = crate::extract_bridge_withdraw_data(&withdraw_tx).expect("withdraw data");
|
||||||
|
let key = crate::withdraw_event_reconciliation_key(&withdraw_arg.outputs).expect("recon key");
|
||||||
|
// Producing the withdraw counts it as unseen, awaiting its L1 event.
|
||||||
|
assert!(
|
||||||
|
seq_a
|
||||||
|
.block_store()
|
||||||
|
.dbio()
|
||||||
|
.consume_unseen_withdraw_count(key)
|
||||||
|
.unwrap(),
|
||||||
|
"producing a withdraw must count it as unseen"
|
||||||
|
);
|
||||||
|
|
||||||
|
let messages = channel_from_store(seq_a.block_store(), 10);
|
||||||
|
let tip_slot = messages.last().unwrap().1;
|
||||||
|
let channel_id = config_a.bedrock_config.channel_id;
|
||||||
|
|
||||||
|
// Sequencer B reconstructs A's chain from a fresh store.
|
||||||
|
let config_b = bridge_funded_config();
|
||||||
|
let (mut store_b, mut state_b) =
|
||||||
|
SequencerCore::<MockBlockPublisher>::open_or_create_store(&config_b);
|
||||||
|
let mock_b = MockBlockPublisher::with_canned_channel(channel_id, Some(tip_slot), messages);
|
||||||
|
SequencerCore::<MockBlockPublisher>::verify_and_reconstruct(
|
||||||
|
&mock_b,
|
||||||
|
&mut store_b,
|
||||||
|
&mut state_b,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("reconstruct");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!store_b.dbio().consume_unseen_withdraw_count(key).unwrap(),
|
||||||
|
"reconstruction must not leave a phantom unseen-withdraw count"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user