mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
dexd is now the ONLY command in the repo. One binary, two run modes:
dexd run as a luxd rpcchainvm PLUGIN. luxd execs the plugin from
dexd plugin its PluginDir with NO positional args (engine address arrives
via the runtime.EngineAddressKey env var) and probes the
version with `dexd version`. No-subcommand == plugin is
REQUIRED to preserve that contract — byte-for-byte the old
cmd/dchain behavior (rpc.Serve over the plugin transport).
dexd run run the STANDALONE D-Chain venue: open the on-disk zapdb,
dexd standalone serve the frozen dex_* surface over ZAP, and drive the
consensus sealer loop (WaitForEvent->BuildBlock->Verify->
Accept). -addr/-db flags preserved from the old cmd/dvenue.
Relocated logic (engine code NOT duplicated — pkg/dchain + pkg/lx untouched):
cmd/dchain/{main,runner} -> internal/plugin (plugin.Run)
cmd/dvenue/{main,engine_*} -> internal/standalone (standalone.Run + engineName)
cmd/dexd/main.go = thin run-mode dispatcher
Removed cmd dirs (12): dchain, dvenue, demo, dex-api-server, dex-server,
dexfillproof, dexseed, fix-gateway, gateway, monitor, persistent-server,
price-feed. Removed committed build artifacts: gateway, dex-server-linux.
Build/ship ONLY dexd:
- Dockerfile: canonical CPU image (CGO=0, ghcr.io/luxfi/dex) running
`dexd run`; no GPU/MLX required.
- Dockerfile.dvenue: GPU operator bundle repointed cmd/dvenue -> cmd/dexd.
- Deleted Dockerfile.{gateway,devnet,minimal,dvenue-cpu} and
docker/{Dockerfile,Dockerfile.cuda,backend/Dockerfile,compose-simple.yml}.
- Makefile rewritten to build/run/ship dexd only.
- docker.yml: dropped the dex-gateway image; dex-venue-image.yml: dvenue->dexd.
- Deleted legacy scripts that built removed/long-gone cmds; fixed deploy.sh +
dev-setup.sh; .gitignore ignores root build binaries.
Also included (coupled, environment-applied, settled): the in-progress
clob -> dex/orderbook terminology refactor across pkg/* + sdk/* + tests/*
(identifier + file renames, e.g. RegisterCLOB->RegisterDEX,
source_clob.go->source_orderbook.go). The relocated standalone venue calls the
renamed API, so it lands in the same consistent snapshot.
Verified (GOWORK=off CGO_ENABLED=0): `go build ./cmd/dexd`, `go build ./...`,
`go vet ./...` all exit 0; `go test ./pkg/dchain` (renamed RegisterDEX socket
path) green. GPU/cgo path not compiled locally (no CUDA); its rename mirrors
the verified nocgo twins and preserves the C ABI symbol names.
147 lines
4.3 KiB
Go
147 lines
4.3 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package venue
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/dex/pkg/zapwire"
|
|
"github.com/luxfi/rpc"
|
|
)
|
|
|
|
// fakeDEX stands up a real rpc.Listen server with canned dex_* handlers, so the
|
|
// production zapVenue is exercised over a REAL ZAP socket (the same transport
|
|
// production uses) without standing up the cgo matcher. It proves zapVenue's frame
|
|
// encode + ack/fill decode + error mapping, end to end over the wire.
|
|
type fakeDEX struct {
|
|
server rpc.Server
|
|
cancel context.CancelFunc
|
|
placeID uint64
|
|
fills []zapwire.Fill
|
|
cancelOK bool
|
|
}
|
|
|
|
func startFakeDEX(t *testing.T) (*fakeDEX, string) {
|
|
t.Helper()
|
|
srv, err := rpc.Listen("127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("rpc.Listen: %v", err)
|
|
}
|
|
f := &fakeDEX{server: srv, placeID: 42, cancelOK: true}
|
|
|
|
must := func(method string, h rpc.RawHandler) {
|
|
if err := srv.RegisterRaw(method, h); err != nil {
|
|
t.Fatalf("register %s: %v", method, err)
|
|
}
|
|
}
|
|
must(zapwire.MethodEnsureMarket, func(_ context.Context, _ []byte) ([]byte, error) {
|
|
return zapwire.EncodeAck(0, zapwire.StatusPlaced, 0), nil
|
|
})
|
|
must(zapwire.MethodPlace, func(_ context.Context, _ []byte) ([]byte, error) {
|
|
return zapwire.EncodeAck(f.placeID, zapwire.StatusPlaced, 0), nil
|
|
})
|
|
must(zapwire.MethodCancel, func(_ context.Context, _ []byte) ([]byte, error) {
|
|
if f.cancelOK {
|
|
return zapwire.EncodeAck(0, zapwire.StatusCanceled, 0), nil
|
|
}
|
|
return zapwire.EncodeAck(0, zapwire.StatusRejected, 0), nil
|
|
})
|
|
must(zapwire.MethodSubmit, func(_ context.Context, _ []byte) ([]byte, error) {
|
|
return zapwire.EncodeFills(f.fills), nil
|
|
})
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
f.cancel = cancel
|
|
go func() { _ = srv.Serve(ctx) }()
|
|
t.Cleanup(func() { cancel(); _ = srv.Close() })
|
|
return f, srv.Addr()
|
|
}
|
|
|
|
func TestZAPVenueRoundTrip(t *testing.T) {
|
|
f, addr := startFakeDEX(t)
|
|
f.fills = []zapwire.Fill{{Price: 100, Size: 3, TakerSide: zapwire.SideBuy}}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
v, err := DialVenue(ctx, addr)
|
|
if err != nil {
|
|
t.Fatalf("DialVenue: %v", err)
|
|
}
|
|
|
|
var pool [32]byte
|
|
pool[0] = 0xab
|
|
|
|
if err := v.EnsureMarket(ctx, pool); err != nil {
|
|
t.Fatalf("EnsureMarket: %v", err)
|
|
}
|
|
id, err := v.Place(ctx, pool, zapwire.SideSell, 100, 3, "maker")
|
|
if err != nil {
|
|
t.Fatalf("Place: %v", err)
|
|
}
|
|
if id != 42 {
|
|
t.Fatalf("Place orderID=%d, want 42", id)
|
|
}
|
|
fills, err := v.Submit(ctx, pool, zapwire.SideBuy, false, 100, 3, "taker")
|
|
if err != nil {
|
|
t.Fatalf("Submit: %v", err)
|
|
}
|
|
if len(fills) != 1 || fills[0].Size != 3 || fills[0].Price != 100 {
|
|
t.Fatalf("Submit fills = %+v, want one 3@100", fills)
|
|
}
|
|
if err := v.Cancel(ctx, pool, 42); err != nil {
|
|
t.Fatalf("Cancel: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestZAPVenueCancelRejectedMapsToErrRejected proves a non-canceled cancel ack is
|
|
// surfaced as ErrRejected, not a silent success.
|
|
func TestZAPVenueCancelRejectedMapsToErrRejected(t *testing.T) {
|
|
f, addr := startFakeDEX(t)
|
|
f.cancelOK = false
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
v, err := DialVenue(ctx, addr)
|
|
if err != nil {
|
|
t.Fatalf("DialVenue: %v", err)
|
|
}
|
|
var pool [32]byte
|
|
err = v.Cancel(ctx, pool, 7)
|
|
if !errors.Is(err, ErrRejected) {
|
|
t.Fatalf("Cancel of rejected order: want ErrRejected, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestSymbolToPoolIDDistinct proves the symbol resolver is collision-free for
|
|
// distinct symbols and tags the high byte (never a zero id).
|
|
func TestSymbolToPoolIDDistinct(t *testing.T) {
|
|
a := SymbolToPoolID("LUX/LUSD")
|
|
b := SymbolToPoolID("LUX/LETH")
|
|
if a == b {
|
|
t.Fatal("distinct symbols mapped to same poolID")
|
|
}
|
|
if a[31] != 0xD0 || b[31] != 0xD0 {
|
|
t.Fatal("poolID high byte tag missing")
|
|
}
|
|
var zero [32]byte
|
|
if a == zero || b == zero {
|
|
t.Fatal("poolID collided with zero id")
|
|
}
|
|
}
|
|
|
|
// TestUserHandleTruncates proves the user handle is clamped to the ledger's
|
|
// 16-byte key width.
|
|
func TestUserHandleTruncates(t *testing.T) {
|
|
if got := UserHandle("short"); got != "short" {
|
|
t.Fatalf("UserHandle(short)=%q", got)
|
|
}
|
|
long := "0123456789abcdefGHIJ" // 20 bytes
|
|
if got := UserHandle(long); len(got) != zapwire.UserSize || got != long[:zapwire.UserSize] {
|
|
t.Fatalf("UserHandle truncation: %q (len %d)", got, len(got))
|
|
}
|
|
}
|