Commit 7405a29
committed
fix: stop misclassifying partial fills as full when rawOnChain cache is stale
The XRP-BTS bot went stale on 2026-07-03 around 00:34:51 because order
1.7.572840453 (slot-109) was a 2.5788 XRP sell that received 5 partial fills
totaling exactly 2.5788 XRP. The bot only saw 4 of the 5 fills, marked the
order as fully filled on the FIRST fill, and dropped the remaining 4 at
debug level — leaving a 0.0030 XRP residual on chain with no in-memory
record.
Root cause was in syncFromFillHistory (modules/order/sync_engine.ts):
`isEffectivelyFull = (newSizeInt <= 0)` trusted the cached rawOnChain.for_sale
unconditionally. When the cache had a stale (smaller) value from a prior
in-memory reduction, the subtraction crossed zero on a partial fill and the
order was misclassified as full. Subsequent fills then hit the "order not
found in active grid" branch and were silently dropped.
## What changed
- `modules/chain_orders.ts`: new `readSingleOrder(orderId, timeoutMs)` helper
that wraps `get_objects([id])`. Returns `null` only on a real "order is gone"
(authoritative empty signal); throws on connection/RPC failure so the caller
can distinguish "tried-and-gone" from "tried-and-failed".
- `modules/order/sync_engine.ts`: `syncFromFillHistory` now refetches via
`readSingleOrder` only when the cached `for_sale` is *smaller* than the
grid's own size (a "drift signal" — chain can only decrease). No TTL, no
time-based refetch. The cache is the source of truth; the chain is only
consulted when the cache and the grid demonstrably disagree. `isEffectivelyFull`
now requires either a chain-confirmed empty (drift refetch returned null),
the grid also being at 0, or the other side rounding to 0. The legacy
`newSizeInt <= 0` fast-path is gone.
- Three `rawOnChain` population sites in `_doSyncFromOpenOrders` now stamp
`fetchedAt: Date.now()` so the partial-fill branch can use the refetched
value as a fresh baseline.
- `tests/test_ghost_order_fix.ts`: seeds a fresh `rawOnChain` so the test
no longer depends on a live chain connection.
- `tests/test_sync_fill_drift_refetch.ts` (new): 5 sub-cases for the new
behaviour — drift + working refetch, drift + null refetch, drift +
refetch error, no drift (self-correcting), no cache (legacy fallback).
## Why drift-only, not TTL
A stale-but-consistent cache is still correct; a fresh-but-wrong cache is
the actual bug we're guarding against, and the drift signal catches that.
A time-based TTL would refetch on every open-orders sync window even when
the cache is correct. Open-orders sync remains the ultimate recovery
channel for missed events.
## Testing notes
- `npm run typecheck` clean
- `tests/test_sync_fill_drift_refetch.ts` (new) ✓
- `tests/test_ghost_order_fix.ts` ✓
- `tests/test_precision_integration.ts` ✓
- `tests/test_multifill_opposite_partial.ts` ✓
- `tests/test_main_loop_sync_fill_rebalance.ts` ✓
- `tests/test_cow_structural_resync.ts` ✓
- `tests/test_cow_concurrent_fills.ts` ✓
- `tests/test_fill_batch_chunking.ts` ✓
- `tests/test_boundary_sync_logic.ts` ✓
- Pre-existing failures (unrelated): `tests/test_fill_replay_guards.ts`,
`claw/tests/test_dexbot_profiles.ts` — confirmed by stashing the diff
and running them on the unmodified `test` branch.1 parent 5ab8cce commit 7405a29
4 files changed
Lines changed: 450 additions & 27 deletions
File tree
- modules
- order
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
468 | 468 | | |
469 | 469 | | |
470 | 470 | | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
471 | 509 | | |
472 | 510 | | |
473 | | - | |
| 511 | + | |
474 | 512 | | |
475 | 513 | | |
476 | 514 | | |
| |||
1210 | 1248 | | |
1211 | 1249 | | |
1212 | 1250 | | |
| 1251 | + | |
1213 | 1252 | | |
1214 | 1253 | | |
1215 | 1254 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
612 | 612 | | |
613 | 613 | | |
614 | 614 | | |
615 | | - | |
616 | | - | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
617 | 625 | | |
618 | 626 | | |
619 | 627 | | |
| |||
719 | 727 | | |
720 | 728 | | |
721 | 729 | | |
722 | | - | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
723 | 734 | | |
724 | 735 | | |
725 | 736 | | |
| |||
849 | 860 | | |
850 | 861 | | |
851 | 862 | | |
852 | | - | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
853 | 866 | | |
854 | 867 | | |
855 | 868 | | |
| |||
995 | 1008 | | |
996 | 1009 | | |
997 | 1010 | | |
998 | | - | |
999 | | - | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
1000 | 1081 | | |
1001 | 1082 | | |
1002 | 1083 | | |
1003 | 1084 | | |
1004 | 1085 | | |
1005 | | - | |
| 1086 | + | |
1006 | 1087 | | |
1007 | | - | |
| 1088 | + | |
| 1089 | + | |
1008 | 1090 | | |
1009 | 1091 | | |
1010 | 1092 | | |
1011 | 1093 | | |
1012 | | - | |
1013 | | - | |
1014 | | - | |
1015 | | - | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
1016 | 1110 | | |
1017 | 1111 | | |
1018 | | - | |
| 1112 | + | |
1019 | 1113 | | |
1020 | 1114 | | |
1021 | 1115 | | |
1022 | 1116 | | |
1023 | | - | |
| 1117 | + | |
1024 | 1118 | | |
1025 | 1119 | | |
1026 | 1120 | | |
| |||
1058 | 1152 | | |
1059 | 1153 | | |
1060 | 1154 | | |
1061 | | - | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
1062 | 1159 | | |
1063 | | - | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
1064 | 1164 | | |
1065 | 1165 | | |
1066 | | - | |
| 1166 | + | |
| 1167 | + | |
1067 | 1168 | | |
1068 | 1169 | | |
1069 | 1170 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
42 | 46 | | |
43 | | - | |
44 | | - | |
| 47 | + | |
| 48 | + | |
45 | 49 | | |
46 | | - | |
47 | | - | |
48 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
49 | 54 | | |
50 | 55 | | |
51 | 56 | | |
52 | 57 | | |
53 | | - | |
| 58 | + | |
54 | 59 | | |
55 | 60 | | |
56 | 61 | | |
| |||
63 | 68 | | |
64 | 69 | | |
65 | 70 | | |
66 | | - | |
| 71 | + | |
67 | 72 | | |
68 | 73 | | |
69 | 74 | | |
70 | | - | |
| 75 | + | |
71 | 76 | | |
72 | 77 | | |
73 | 78 | | |
| |||
0 commit comments