Skip to content

[scanner] PendingCache::drain fires on every NewBlock without checking trigger_tx confirmed in that block #227

@obchain

Description

@obchain

Refs #46

PR: feat/21-mempool-monitor
File: crates/charon-scanner/src/mempool.rs
Function: PendingCache::drain()

Problem

PendingCache::drain() removes and returns every non-stale entry on each call. It is called by the block-listener task on each ChainEvent::NewBlock. The drain logic does NOT verify that PreSignedLiquidation::trigger_tx was actually included in the just-confirmed block.

Scenario:

  1. Block N-1: oracle update tx seen in mempool, pre-sign created for borrower X.
  2. Block N: oracle update tx is NOT included (validator skipped it or it was replaced).
  3. Block N fires NewBlock, drain() returns the pre-sign for borrower X.
  4. Caller broadcasts against state where price has NOT changed.
  5. Borrower X may still be healthy; liquidation tx reverts or worse succeeds incorrectly.

The 30-second TTL only drops entries older than 30s. It does not enforce the semantic "only broadcast if trigger_tx confirmed in the block that caused drain()."

Required fix

Before returning a drained entry for broadcast, verify either:
(a) eth_getTransactionReceipt(trigger_tx).status == 1 for the block that just arrived, or
(b) re-read the current oracle price and confirm it matches OracleUpdate.new_price.

A drain_for_block(block_hash) variant that checks trigger_tx receipt before including entries in the returned vec is one concrete approach. Entries whose trigger_tx did not confirm are re-inserted (if within TTL) rather than returned for broadcast.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinglayer:rustRust crates (core / scanner / protocols / executor / cli)priority:p1-coreCore MVP scope

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions