Files
zeekay 9ce714fd3f e2e(dex): assert canonical DEXFill settlement, not the stale 0x01 code proxy
0x9999 is AlwaysOn from genesis and writes NO code marker (github.com/luxfi/
precompile/dex: DEXFill is THE native-CLOB settlement signal). The old step-3
eth_getCode==0x01 proxy was a false invariant — mainnet carries a legacy 0x01
with zero fills, a fresh-genesis net has empty code with real fills. Step 3 is
now informational; step 4 (a DEXFill was emitted) is the hard settlement gate.

Proven: testnet (96368) TestDEXBootSpec GREEN — 65 DEXFills, 3 validators
converge on block 360 hash 0x7d5a66b1…84eb.
2026-07-26 15:46:02 -07:00

287 lines
9.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package dex is the deterministic DEX boot verification GATE for the Lux
// network. It is the read-path half of the boot/reset/upgrade harness: given one
// or more C-Chain JSON-RPC endpoints (LUX_RPC, comma-separated — one per
// validator), it asserts the locked-in spec is live:
//
// 1. chainId matches the expected EVM chain id (LUX_CHAIN_ID),
// 2. the C-Chain has booted past genesis (block height > 0) and is producing
// blocks,
// 3. the 0x9999 DEX settlement precompile is ACTIVE and has settled at least
// once (eth_getCode == 0x01 — the vault marker written on first settlement),
// 4. a DEXFill has been emitted by 0x9999 (the DEX has actually traded), and
// 5. every endpoint converges on an identical block hash at a common height
// (all validators agree — the determinism proof).
//
// It is dependency-free (stdlib JSON-RPC) so it runs against ANY target: a fresh
// disposable localnet booted by boot.sh (after `trader prove` writes a fill), an
// in-cluster throwaway namespace, or — for spec verification — the live
// v1.31.2 nets. The destructive deploy/open/fill leg is the `trader prove`
// binary, invoked by boot.sh; this file never writes to the chain.
//
// Skipped (not failed) when LUX_RPC is unset, so it is CI-safe.
package dex
import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"net/http"
"os"
"strings"
"testing"
"time"
)
const (
// dexPoolManager is the 0x9999 V4 settlement precompile, identical on every
// Lux EVM chain (github.com/luxfi/precompile/dex DEXPoolManagerAddress).
dexPoolManager = "0x0000000000000000000000000000000000009999"
// dexActiveMarker is eth_getCode(0x9999) once the precompile has settled at
// least once — the one-byte vault marker. Empty ("0x") before first fill.
dexActiveMarker = "0x01"
// dexFillTopic = keccak256("DEXFill(bytes32,address,uint256,uint256)") — the
// canonical settlement signal emitted by 0x9999.
dexFillTopic = "0x6f744d074efc4fa8512636853f8a4c67842f230d4279421c7599dc5c5daf9874"
)
// endpoints returns the LUX_RPC validator endpoints, or skips the test.
func endpoints(t *testing.T) []string {
t.Helper()
raw := os.Getenv("LUX_RPC")
if strings.TrimSpace(raw) == "" {
t.Skip("LUX_RPC unset — set it to one or more comma-separated C-Chain RPC " +
"endpoints (one per validator) to run the DEX boot gate")
}
var out []string
for _, u := range strings.Split(raw, ",") {
if u = strings.TrimSpace(u); u != "" {
out = append(out, u)
}
}
return out
}
// rpc is a minimal eth JSON-RPC client over one endpoint.
type rpc struct {
url string
c *http.Client
}
func dial(url string) *rpc { return &rpc{url: url, c: &http.Client{Timeout: 20 * time.Second}} }
func (r *rpc) call(method string, params ...any) (json.RawMessage, error) {
if params == nil {
params = []any{}
}
body, _ := json.Marshal(map[string]any{
"jsonrpc": "2.0", "id": 1, "method": method, "params": params,
})
resp, err := r.c.Post(r.url, "application/json", bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("%s: %w", method, err)
}
defer resp.Body.Close()
var out struct {
Result json.RawMessage `json:"result"`
Error *struct {
Message string `json:"message"`
} `json:"error"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return nil, fmt.Errorf("%s: decode: %w", method, err)
}
if out.Error != nil {
return nil, fmt.Errorf("%s: rpc error: %s", method, out.Error.Message)
}
return out.Result, nil
}
// hexString decodes a JSON-RPC quoted string result (e.g. "0x1f").
func (r *rpc) hexString(method string, params ...any) (string, error) {
raw, err := r.call(method, params...)
if err != nil {
return "", err
}
var s string
if err := json.Unmarshal(raw, &s); err != nil {
return "", fmt.Errorf("%s: not a string: %w", method, err)
}
return s, nil
}
// hexUint decodes a 0x-quantity result.
func (r *rpc) hexUint(method string, params ...any) (uint64, error) {
s, err := r.hexString(method, params...)
if err != nil {
return 0, err
}
n, ok := new(big.Int).SetString(strings.TrimPrefix(s, "0x"), 16)
if !ok {
return 0, fmt.Errorf("%s: bad hex %q", method, s)
}
return n.Uint64(), nil
}
func (r *rpc) chainID() (uint64, error) { return r.hexUint("eth_chainId") }
func (r *rpc) blockNumber() (uint64, error) { return r.hexUint("eth_blockNumber") }
func (r *rpc) code(addr string) (string, error) {
return r.hexString("eth_getCode", addr, "latest")
}
func (r *rpc) blockHash(height uint64) (string, error) {
raw, err := r.call("eth_getBlockByNumber", fmt.Sprintf("0x%x", height), false)
if err != nil {
return "", err
}
var blk struct {
Hash string `json:"hash"`
}
if err := json.Unmarshal(raw, &blk); err != nil {
return "", fmt.Errorf("getBlockByNumber: %w", err)
}
return blk.Hash, nil
}
// dexFills counts DEXFill logs emitted by 0x9999 over [from, latest].
func (r *rpc) dexFills(from uint64) (int, error) {
raw, err := r.call("eth_getLogs", map[string]any{
"address": dexPoolManager,
"topics": []any{dexFillTopic},
"fromBlock": fmt.Sprintf("0x%x", from),
"toBlock": "latest",
})
if err != nil {
return 0, err
}
var logs []json.RawMessage
if err := json.Unmarshal(raw, &logs); err != nil {
return 0, fmt.Errorf("getLogs: %w", err)
}
return len(logs), nil
}
// TestDEXBootSpec is the boot gate. Green == a known-good, taggable spec version.
func TestDEXBootSpec(t *testing.T) {
eps := endpoints(t)
primary := dial(eps[0])
// 1) chainId.
cid, err := primary.chainID()
if err != nil {
t.Fatalf("eth_chainId on %s: %v", eps[0], err)
}
if want := os.Getenv("LUX_CHAIN_ID"); want != "" {
wc, ok := new(big.Int).SetString(want, 10)
if !ok {
t.Fatalf("LUX_CHAIN_ID=%q is not decimal", want)
}
if wc.Uint64() != cid {
t.Fatalf("chainId = %d, want %s", cid, want)
}
}
t.Logf("chainId = %d ✓", cid)
// 2) booted past genesis + producing blocks.
h0, err := primary.blockNumber()
if err != nil {
t.Fatalf("eth_blockNumber: %v", err)
}
if h0 == 0 {
t.Fatalf("C-Chain at block 0 — not booted past genesis (no blocks built)")
}
t.Logf("C-Chain height = %d (booted past genesis) ✓", h0)
// 3) 0x9999 is AlwaysOn from genesis. Per github.com/luxfi/precompile/dex the
// settlement precompile is active from genesis and writes NO code marker —
// eth_getCode is informational only (a legacy net may carry a stray 0x01, a
// fresh-genesis net is empty; neither is the settlement signal). The canonical
// "has settled / has traded" proof is the DEXFill log asserted in (4):
// DEXFill is THE indexable native-CLOB settlement signal the precompile emits
// on every filled swap (dex/events.go, dex/swap_sync.go).
codeHex, _ := primary.code(dexPoolManager)
t.Logf("0x9999 AlwaysOn from genesis; eth_getCode=%q (informational, not the settlement signal) ✓", codeHex)
// 4) a DEXFill was emitted (the DEX actually traded) — the canonical
// settlement proof. Query window is [LUX_FROM_BLOCK, latest]; defaults to
// the whole chain. A public/load-balanced node may cap the getLogs range —
// if the query ERRORS we surface a NOTE (use a direct validator endpoint);
// but zero fills over a served range is a hard FAIL: the money path is
// unproven on a net that has never settled a trade.
from := uint64(0)
if f := os.Getenv("LUX_FROM_BLOCK"); f != "" {
if v, ok := new(big.Int).SetString(f, 10); ok {
from = v.Uint64()
}
}
n, err := primary.dexFills(from)
switch {
case err != nil:
if os.Getenv("LUX_REQUIRE_FILL") == "1" {
t.Fatalf("eth_getLogs(DEXFill) from %d: %v", from, err)
}
t.Logf("NOTE: DEXFill log query over [%d,latest] errored (%v) — likely a "+
"public getLogs range cap. Point LUX_RPC at a validator endpoint (no cap).", from, err)
case n == 0:
t.Fatalf("zero DEXFill logs over [%d,latest] — 0x9999 has not settled a "+
"trade on this net (the DEX has not traded); DEXFill is the canonical "+
"native-CLOB settlement signal, a coherent DEX net must have ≥1", from)
default:
t.Logf("DEXFill logs over [%d,latest] = %d (DEX has traded) ✓", from, n)
}
// 5) convergence: every validator endpoint agrees on the block hash at a
// common settled height. This is the determinism proof for a multi-node
// boot. Single endpoint (live ingress) trivially converges.
if len(eps) > 1 {
assertConvergence(t, eps)
} else {
t.Logf("single endpoint — convergence check is for multi-validator targets")
}
t.Logf("DEX is trading ✓ — spec on %s is a known-good, taggable boot", eps[0])
}
// assertConvergence picks a settled height present on all endpoints and asserts
// they report an identical block hash there.
func assertConvergence(t *testing.T, eps []string) {
t.Helper()
minH := ^uint64(0)
for _, ep := range eps {
h, err := dial(ep).blockNumber()
if err != nil {
t.Fatalf("blockNumber on %s: %v", ep, err)
}
if h < minH {
minH = h
}
}
if minH < 3 {
t.Fatalf("min height %d too low to pick a settled common block", minH)
}
target := minH - 2 // a height every node has accepted
var want string
for i, ep := range eps {
hash, err := dial(ep).blockHash(target)
if err != nil {
t.Fatalf("blockHash(%d) on %s: %v", target, ep, err)
}
if i == 0 {
want = hash
continue
}
if hash != want {
t.Fatalf("DIVERGENCE at block %d: %s reports %s, %s reports %s",
target, eps[0], want, ep, hash)
}
}
t.Logf("all %d validators converge on block %d hash %s ✓", len(eps), target, want)
}