luxd serves chain routes under /v1/chain, and the node, cli and sdk releases
already ship it. The deployed gateways still answer the old prefix, so remote
reads break until they are redeployed; the estate moves together rather than
sitting half converted.
Lines that record an observation - a probe, a status code, a past release -
keep the prefix they were written against, as do URLs on hosts that are not
ours. Avalanche endpoints move to /ext/bc, which is the path that host serves.
Co-authored-by: Hanzo Dev <[email protected]>
The rename to /v1/chain is real in luxd source, but the deployed fleet has not
taken it yet: probed just now, api.lux.network, api.lux-test.network,
api.zoo.network and api.hanzo.network each answer 200 on /v1/bc/C/rpc and 404 on
/v1/chain/C/rpc. Composing /v1/chain against those hosts reaches nothing, which
is the failure the rename set out to prevent.
The move belongs with the node redeploy, not ahead of it.
Co-authored-by: Hanzo Dev <[email protected]>
luxd serves chain routes under /v1/chain; the /v1/bc prefix it replaced is
gone, so anything composing it reaches nothing. Lines that record an
observation - a probe, a status code, a past release - keep the prefix they
were written against, as do URLs on hosts that are not ours.
Co-authored-by: Hanzo Dev <[email protected]>
The two lineages derive different genesis blocks. A fleet that keeps its v1.5
store keeps working, but any node whose store is wiped — or any validator added
later — adopts the v1.14 genesis, disagrees at height 0, and is stranded there
forever with isBootstrapped:false and no error naming the cause.
Found by wiping one devnet validator's D store after the roll: it came back on
genesis mpxP6xF3... while the fleet carried Muetn.... Wiping D on all five
converged them, and a subsequent wipe of one node then re-derived every block to
the fleet's exact root.
A matching height is not enough to verify this — every node reports height 0
either way. Compare lastAccepted.
Co-authored-by: Hanzo Dev <[email protected]>
The C->D object carried value only, so executeImport credited the taker's
account and stopped. Its one reference to TxSubmit was a comment. Nothing placed
an order, so nothing crossed, so driveSeamExports — which fired only when a
non-input asset appeared — never fired, so Phase B had no object and the settle
reverted. The seam was unreachable by construction, which is why 0x9999 has
emitted zero events on every chain since genesis.
The object now carries the operation the taker authorized on C (market, side,
limit, size) appended to the unchanged 69-byte value head, and the drive builds
the taker's order from it: BuildBlock emits TxImport and TxIntentSubmit as a
pair, in one block, so value is never on D without an order against it.
TxIntentSubmit is the object-authorized sibling of TxSubmit, as TxImport is of
TxDeposit. It cannot be a plain TxSubmit — that type requires a signature at
parse, at the RPC edge, and in authorizeTx, and weakening any of those would let
an unsigned submit move any account's funds. It carries the intent id alone; the
zapwire Submit frame is derived from the escrow by seamSubmitBody, so the
operation is declared once and never restated for the two sides to reconcile.
From there it flows through the identical custody, matcher and settlement path a
signed dex_submit takes.
Three things this fixes on the way:
D could never sync past a settled swap. executeImport read the object from
shared memory during execute() while the consuming Remove landed at Accept, so
any node re-executing the block found it gone, derived a different ledger root
and died on `execution root mismatch`. The object's bytes now ride in the
TxImport body and Block.verifySeamImports authenticates them at Verify, where a
forgery costs the producer a block instead of forking a root. Accept now checks
the root it derives too: that branch is the bootstrap path and it was committing
whatever it computed while persisting the block's claimed root.
The seam ran in the taker's own ledger account, and the export drive settles an
account's balances back to C. A taker holding native D balance would have had
all of it exported, and C credits that out of the shared seam reserve — a raid
on other takers' pooled input. Each intent now runs in its own account, so the
question does not arise and the spent witness becomes exact rather than
inferred.
A zero-fill order left only its input behind, which the old export gate could
never match, so the principal sat on D while C's deadline reclaim refunded the
taker the same principal. The escrow now records that the order RAN — including
when the custody gate refused it — and the drive gates on that fact.
A market id named by the other chain reached bookForPool and aborted the block.
Any C caller could have halted D with it. Unknown market and unbound assets are
now per-tx rejects, and the intent settles back in full.
SeamPendingTrait rotates to .v2 alongside the wire. Old and new nodes cannot
disagree about a block: the TxImport body is 150 bytes and an old node's
bodySize says 32, so ParseBlock refuses the frame outright. It stalls rather
than forking. No historical block anywhere contains a TxImport.
121 tests pass in pkg/dchain, 0 fail; whole repo green.
Co-authored-by: Hanzo Dev <[email protected]>
Two rules for every Go builder stage (pin latest stable patch; set
GOTOOLCHAIN=auto), and why the root `go` directive stays at 1.26.4. Notes
that sdk/lx-trading-go already declares go 1.26.5 — above the root module — so
the GOTOOLCHAIN=local failure mode is live in this repo, not hypothetical.
Co-authored-by: Hanzo Dev <[email protected]>
The matching engine (pkg/lx, 45.5K LOC incl. tests) and the deterministic
core layered on it (pkg/dex) are one matcher spread across two packages
(dex imported lx in 12 files; lx imported neither). Fold lx into dex and
drop the "lx" name entirely — pkg/dex is now the single matcher package.
Pure rename/fold, ZERO logic change. Behavior proven identical: the merged
pkg/dex runs 457 top-level tests (431 former-lx + 26 former-dex) and 489
test funcs (468 + 21), all green — matching, batch (BatchMatchEqualsSequential,
run-to-run determinism, tile boundary), GPU/CPU parity, truncation, and CL
swap math all pass unchanged.
What moved:
- git mv all 101 pkg/lx/*.go + 1 .c into pkg/dex/ (package lx -> package dex).
- lx's swap_test.go -> swap_math_test.go (only filename collision with dex's
existing swap_test.go).
- Rewrote imports repo-wide: 54 pkg/lx import lines removed; 42 external
importers (api, client, consensus, dchain, dpdk, lxgpu, marketdata, test/,
tests/) repointed to pkg/dex with lx.X -> dex.X; the 12 in-package dex files
had lx.X -> bare X. 717 selector lines rewritten. Stale pkg/lx comment refs
updated (pkg/lxgpu left intact).
Collisions resolved (2, both package-level vars):
- ErrNoLiquidity: lx's pool-level error (dexError, used only in swap.go, never
referenced via qualifier) renamed ErrPoolNoLiquidity to match the existing
ErrPool* convention; dex's router-level ErrNoLiquidity (public, has errors.Is
tests) kept.
- priceMultiplierBig: identical after fold (both big.NewInt(PriceMultiplier));
deduped by deleting dex's copy.
Not folded — pkg/orderbook: it is NOT the GPU seam (that is orderbook_gpu.go,
which lives in the engine and folded in here). pkg/orderbook is a zero-importer,
placeholder-cgo island whose 13 core domain identifiers (Order, OrderBook, Trade,
OrderType, OrderStatus, PriceLevel, Buy, Sell, Market, Limit, Filled,
PartiallyFilled, NewOrderBook) collide incompatibly with the engine's. Folding
it verbatim breaks the build; folding via rename reintroduces the exact parallel
duplication this fold removes. Left for owner decision (recommend deletion as
dead code) — see refactor notes.
Follow-on (separate repos, not touched): 19 files under luxfi/dex import
pkg/lx externally — lux/precompile/dex, lux/precompile-dex/dex,
lux/consensus/examples/01-simple-bridge, lux/_consensus-v12537/examples,
lux/cex/pkg/engine — repoint to pkg/dex after this lands.
Verified: CGO_ENABLED=0 GOWORK=off go build ./... clean; go vet ./... clean;
go test ./... -count=1 all green (22 ok / 0 fail).
Co-authored-by: Hanzo Dev <[email protected]>
Reword comparison docs, comments, and design prose to Lux's own terms
(native DEX core, EVM adapter / 0x9999 V4 PoolManager, native HFT surface,
one consensus-finalized C+D transition). Rename the test broker-venue label
and oracle source weight to neutral names. Behavior-preserving: no logic,
ABI, or wire format changes.
SubmitMarketable and MatchOrders derived their returned fills by re-slicing
the shared trade log (ob.Trades[startOffset:]) with an offset captured BEFORE
matching. ob.Trades self-truncates at 100k entries (keeps the last 50k), so a
match that crosses that boundary mid-match trims the log and leaves the
pre-match offset pointing past the end -> panic 'slice bounds out of range'.
The log feeds settlement, so fills had to stay exact.
tryMatchImmediateLocked / MatchOrders now capture each fill as it is produced,
independent of ob.Trades, and return that slice. Decomplects 'this match's
fills' (a value the caller needs) from 'the trade log' (a bounded-retention
buffer): correct across the truncation boundary by construction, deterministic
across validators.
Adds orderbook_truncation_test.go: three regression cases that cross the
boundary through the production match path (single submits, one sweeping
submit, legacy MatchOrders) with exact-fill and value-conservation asserts.
They panic on the old code, pass on the fixed code. No sleeps, no stubs.
Also: 0x9999 D-matches/C-settles docs (LLM.md, README, ARCHITECTURE),
venue-canonical naming, fourpath-live proof tool, drop dead snowman-api key
from run-lux-testnet.sh.
Apply consistent naming conventions throughout:
- Standardize product name to "LX" in documentation
- Update README files and API documentation
- Apply linter fixes to all source files
- Update Hummingbot connector documentation