Skip to content

feat(ethflow-watcher): observe + verify orderbook indexing (BLEU-833 redesign, COW-1076)#62

Closed
brunota20 wants to merge 1 commit into
feat/ethflow-watcher-decode-bleu-832from
bruno/cow-1076-ethflow-observe-design
Closed

feat(ethflow-watcher): observe + verify orderbook indexing (BLEU-833 redesign, COW-1076)#62
brunota20 wants to merge 1 commit into
feat/ethflow-watcher-decode-bleu-832from
bruno/cow-1076-ethflow-observe-design

Conversation

@brunota20

Copy link
Copy Markdown

Why this PR exists

Sibling to PR #10. Same base (BLEU-832 decode), different design choice at the BLEU-833 step. Proposes that the M2 review choose observe + verify instead of the original submit + retry approach, on empirical evidence that the submit approach cannot succeed against the real orderbook.

Evidence collected from a 2026-06-22 Sepolia soak

The watcher observed 3 OrderPlacement events during the soak (18:43, 18:53, 21:19 UTC). Each was logged as ethflow dropped … ExcessiveValidTo under the BLEU-833 + COW-1074/1075/1076 patches. Hitting the orderbook for the same UIDs:

UID prefix status executedSellAmount creationDate
0x86de7326… fulfilled 0.03 WETH 2026-06-22T18:43:36Z
0x873f2083… fulfilled 0.10 WETH 2026-06-22T18:53:12Z
0x2dc21c6a… fulfilled 0.01 WETH 2026-06-22T21:19:24Z

All carry validTo: 4294967295 (u32::MAX) — the shape we believed the Sepolia orderbook rejects. It doesn't. The orderbook accepts that shape, but only through its native indexer, not through POST /api/v1/orders.

The fulfilled order entries include fields that do not exist on the OrderCreation request body in cowprotocol-orderbook:

"onchainUser": "0x536a844ef215dd8a13a06023f24a568e4ee3cb6b",
"onchainOrderData": { "sender": "0x536a844e…", "placementError": null },
"ethflowData": { "userValidTo": 1782155600, "refundTxHash": null, "isRefunded": false },

ethflowData.userValidTo is a real Unix timestamp — extracted from the EthFlowOrder.data payload embedded in the on-chain OrderPlacement event. Only the orderbook backend's own indexer writes those fields. The public POST /api/v1/orders endpoint shepherd targets applies the generic validity cap and rejects with ExcessiveValidTo regardless of chain, signing scheme, or from address.

What changes

Replace submit_placement with observe_placement:

Outcome Marker Log level
Orderbook GET returns 200 observed:{uid} written Info — "orderbook indexed"
Orderbook GET returns 404 (none — allow re-check) Info — "not yet indexed; will recheck on re-delivery"
Orderbook GET returns other err (none) Warn with code + message

observed:{uid} makes log re-delivery a no-op once verified. 404 is treated as expected indexer lag — shepherd's WebSocket can deliver the log a few hundred milliseconds before the orderbook's own indexer commits — and intentionally does not write the marker, so a future log re-delivery (or a block-tick poll, out of scope here) can re-verify.

Capability surface trimmed: this revision uses local-store, logging, and the cow-api::request host import. It no longer uses cow-api::submit-order. That call site can be retired entirely once the rest of the stack is updated.

Tests (10 total, all pass natively + wasm32-wasip2)

  • BLEU-832 decode tests preserved verbatim: well-formed placement, staging address, wrong contract, wrong topic, truncated address / data, empty topics.
  • New: compute_uid_pins_owner_to_ethflow_contract_and_validto — asserts the 56-byte UID layout digest || owner || valid_to, owner = EthFlow contract, valid_to = on-chain validTo.
  • New: compute_uid_returns_none_on_unsupported_chain.
  • New: gpv2_to_order_data_rejects_unknown_kind_marker — defensive against unrecognised enum markers.
running 10 tests
test result: ok. 10 passed; 0 failed; 0 ignored

cargo clippy -p ethflow-watcher --all-targets clean. cargo fmt --check clean. cargo build -p ethflow-watcher --target wasm32-wasip2 --release clean.

Stack impact

This invalidates the design that PRs #10 (BLEU-833 submit), #47 (COW-1074 appData resolve), #48 (COW-1075 error envelope forwarding for cow-api errors), and #49 (COW-1076 ExcessiveValidTo log downgrade) were patching. Once this lands as the M2 EthFlow design:

Out of scope (follow-up)

  • Block-tick poll for stuck indexer lag. Today the 404 path relies on log re-delivery for a re-check, which is rare. A follow-up can add a [[subscription]] kind = "block" to the manifest and a pending:{uid} marker swept on each block, so persistent indexer downtime surfaces as a Warn after N blocks. Tracked as a new follow-up.
  • ADR-0006 prose update. ADR-0006 currently says the EthFlow module "constructs the OrderCreation … and submits the same way" — this needs revision to reflect that on-chain EthFlow orders are outside the host's submit_order contract. Out of scope here, will land in the M2 epic doc bundle.

AI assistance disclosure: drafted by Claude (Opus 4.7, 1M context) following a 2026-06-22 soak inspection.

@linear-code

linear-code Bot commented Jun 23, 2026

Copy link
Copy Markdown

COW-1076

…redesign, COW-1076)

The original BLEU-833 design lifted the on-chain GPv2OrderData into an
OrderCreation body and POSTed it to /api/v1/orders with the EthFlow
contract as the EIP-1271 owner. Empirical evidence collected during a
2026-06-22 Sepolia soak shows that path cannot succeed:

  - The 3 OrderPlacement events the watcher observed during the run
    (creationDate 18:43, 18:53, 21:19 UTC, UIDs ending in
    ba3cb449...EAdec|ffffffff) are *fulfilled* on the orderbook with
    validTo = u32::MAX — the same shape the orderbook rejects when
    shepherd POSTs it.
  - The orderbook entries carry onchainUser, onchainOrderData and
    ethflowData.userValidTo (a real unix timestamp), and these fields
    do not exist on the OrderCreation request body in
    cowprotocol-orderbook. They are populated by the orderbook backend
    itself, which indexes the OrderPlacement event server-side and
    creates the order with dual-validTo bookkeeping.

So /api/v1/orders is structurally the wrong endpoint for on-chain
EthFlow placements: the orderbook's native indexer is the one that
creates the order entry, and the public POST endpoint applies the
generic validTo cap and rejects with ExcessiveValidTo every time.
Continuing to submit produces only garbage outcomes per real EthFlow
trade (misleading dropped/backoff markers and a misleading
shepherd_cow_api_submit_total{outcome="err"} increment).

This commit replaces the submit path with the contract the orderbook
actually offers: shepherd decodes the OrderPlacement event, computes
the canonical UID, GETs /api/v1/orders/{uid}, and records the outcome:

  - 200 → write observed:{uid}, log Info ("orderbook indexed").
  - 404 → log Info ("not yet indexed; will recheck on re-delivery")
    and do not write the marker, so a future log re-delivery or a
    block-tick poll (out of scope here) can re-verify.
  - other err → log Warn with the host's code and message.

Idempotency stays event-driven: once observed:{uid} is written, log
re-delivery is a no-op. Block-tick-driven retries for stuck indexer
lag are intentionally out of scope and tracked separately.

Test surface: the existing BLEU-832 decode tests are preserved verbatim
and the GPv2OrderData fixture is reused with the canonical EthFlow
markers (OrderKind::SELL, SellTokenSource::ERC20, BuyTokenDestination::ERC20).
Two new tests cover UID computation invariants: the owner suffix pins
to the EthFlow contract and the validTo suffix pins to the on-chain
order's validTo. A third asserts compute_uid returns None on unsupported
chains so callers fall back to a Warn log rather than panic.

This invalidates the design that PRs #10 (BLEU-833), #47 (COW-1074),
#48 (COW-1075) and #49 (COW-1076) were all patching. After review,
those PRs should be closed or substantially rewritten — none of the
appData resolution / error-envelope forwarding / log-downgrade work is
needed once the broken endpoint is no longer called.

AI assistance disclosure: drafted by Claude (Opus 4.7, 1M context)
following the soak inspection that surfaced the divergence.
@brunota20

Copy link
Copy Markdown
Author

Live validation note (2026-06-23 14:00 UTC)

Combined with the rest of the redesign + fix bundle on deploy/cow-1076-observe-on-m5 and deployed to the Sepolia soak VM. Container Up healthy with the new image since 13:56:50 UTC. Will accumulate live evidence in the next observation window.

Companion PRs (same redesign / soak deploy bundle)

PR Concern Layer
#62 ethflow-watcher: observe + verify (replaces submit) M2 (BLEU-833 alternative)
#63 ethflow-watcher: same redesign with BLEU-855 split M3/M4 (COW-1074 base)
#64 twap-monitor: skip submit on already-submitted UID M5 (COW-1085)
#65 nexum-engine: gap-closed log on alloy-internal reconnect M5 (COW-1086)

Each is single-concern; the deploy branch is the cumulative integration point and will fold into latest once the stack lands.

@brunota20

Copy link
Copy Markdown
Author

Superseded by deploy branch commit 6215810 deploy: ethflow-watcher observe + verify redesign rebased onto M5 (now squashed into dev/m5-base). Closing as merged via different commit.

@brunota20 brunota20 closed this Jun 24, 2026
brunota20 added a commit that referenced this pull request Jun 25, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
brunota20 added a commit that referenced this pull request Jun 25, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
brunota20 added a commit that referenced this pull request Jun 25, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
brunota20 added a commit that referenced this pull request Jun 25, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
brunota20 added a commit that referenced this pull request Jun 26, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
jean-neiverth pushed a commit that referenced this pull request Jun 29, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
jean-neiverth pushed a commit that referenced this pull request Jun 30, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
jean-neiverth pushed a commit that referenced this pull request Jun 30, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
jean-neiverth pushed a commit that referenced this pull request Jun 30, 2026
Cherry-pick of PR #62 + PR #63's redesign onto the M5 host runtime
(env-var substitution in engine.toml, healthcheck fixes, etc) for the
Sepolia soak VM. The PR review continues on the proper layered
branches:

  - PR #62 — M2/BLEU-833 layer observe design
  - PR #63 — M3/M4 BLEU-855 split + COW-1074 cow_api_request integration

This branch is deploy-only: it lets the soak run on the redesigned
ethflow-watcher with the latest host runtime while review iterates on
the layered PRs. After merge, this branch can be deleted; CI will
republish ghcr.io/bleu/nullis-shepherd:latest with the merged design
and the VM rolls forward to the official image.

See COW-1076 for the full empirical evidence.
jean-neiverth added a commit that referenced this pull request Jul 6, 2026
One-pass sweep replacing stale nexum.toml references with module.toml
across docs, WIT comments, CLI help text, and the example engine config.
Removes the duplicate modules/example/nexum.toml (module.toml already
exists). Historical references in the migration guide and ADR-0001
are preserved; the supervisor's legacy fallback path is unchanged.

Closes nullislabs#108
Closes #62
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant