Files
dex/pkg
zeekayandHanzo Dev 3925b347f1 fix(lx,dchain): persist self-trade-prevented maker removal (self-cross desync)
Fixes the fail-safe liveness edge the Byzantine suite found: a self-cross-heavy
stream could wedge one later submit at BuildBlock with "resting order missing
full settlement identity (orderuser)" / "insufficient locked balance" (seed 42,
~step 153; same class as seed-4242).

Root cause: pkg/lx/orderbook.go tryMatchImmediateLocked's self-trade skip removed
the self-crossed maker from the IN-MEMORY book (removeOrder + delete Orders +
ordersMap.Delete) without a matching consensus state write. The in-memory book
feeds the execution root via BookToRows(ob), but the durable order:/orderuser:/
reserve rows were left untouched — so the two diverged, and a later cross tripped
the settle-time invariants.

Fix (one and one way — route through the existing cancel-persist path):
- pkg/lx/orderbook.go: the matcher RECORDS each self-cancelled maker id
  (ob.selfCanceled, reset per match) and exposes DrainSelfCanceled(). No fill is
  produced; self-trade prevention is unchanged.
- pkg/dchain/execute.go: applySubmit drains the self-cancelled ids into
  applyResult.SelfCanceled.
- pkg/dchain/block.go: extract releaseOrderReserve() — the single canonical
  cancel-persist primitive (release reserve locked->available, delete reserve +
  orderuser: rows, fail-closed on missing identity). TxCancel now calls it; a
  submit's self-cancelled makers call it too, and execute deletes their order:
  rows in lockstep. In-memory and durable state now move together within the block.

Properties preserved:
- Determinism: the self-cross block's execRoot is UNCHANGED (BookToRows already
  excluded the maker); every validator drops the same maker + releases the same
  reserve. No fork.
- Conservation-exact: the released reserve moves locked->available; sum unchanged,
  no mint/burn.
- Self-trade prevention: self-crosses still never trade (zero fills).
- Anti-theft: owner resolved exclusively via the orderuser: index, fail-closed.

TDD (CGO_ENABLED=0 GOWORK=off GOFLAGS=-mod=mod):
- TestByzantineConservation_AdversarialMatcherStream: 1280 ops, 10 -> 0 fail-safe
  refusals (now asserts zero as a regression guard); I=A+L + no-fork every op.
- TestByzantineMatcher_SelfCrossCancelsRestingMakerConsistently: new pin — the
  partial-fill -> self-cross -> external-cross wedge shape now cancels the maker
  (8 locked base -> available), later crosses run clean, collateral withdrawable.
- Full pkg/lx, pkg/dchain, pkg/consensus suites green; chains/dexvm unaffected.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-01 21:11:39 -07:00
..