mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
Consensus-correctness fix for the native DEX settlement VM. Root cause of the launch-blocking forks: order size/price were IEEE-754 float64 on the ZAP wire, so every validator re-derived the settlement integer via uint64(float)/int64(float*1e8), whose out-of-range result is implementation-defined per the Go spec and DIFFERS BY CPU ARCHITECTURE (arm64 saturates to 2^64-1; amd64 wraps to 2^63). Two honest validators on different archs then derived different reserves/balances/fills from the SAME ordered block. F1/F3/F5 — exact-integer size/price end to end: - pkg/zapwire: Place/Submit/Fill carry price as uint64 fixed-point (×1e8 = PriceInt) and size as uint64 atomic base units. No float64 on the wire. Removed PutFloat64/ Float64. Decode is exact bytes->uint64 (deterministic on every arch). - pkg/dchain/execute: decode the exact integers, set the authoritative PriceUnits/ SizeUnits lane; the float Price/Size become derived projections only (never a source of a consensus value). Reject price==0/size==0/price>maxPriceUnits. - pkg/dex/persist: the DEXOrder/DEXTrade row stores the EXACT PriceInt (in DEXPrice.Integer) and EXACT base units (Quantity/Remaining); RowToOrder restores the integer lane verbatim (identity — no float round-trip) and Filled=Qty-Remaining. Removed floatToFixedQty/fixedQtyToFloat/floatToFixedPrice (the non-idempotent round(size*1e8)/Q64.64 encoding that clamped at ~1.845e11 and forked on restart). - pkg/dex matcher: cross and level-key on the exact integer PriceUnits. - pkg/dchain/settle: orderLock/floorsToZeroLock take exact integers; removed sizeToUnits/priceToInt float conversions and the dead restoreIntegerLane. F2 — commit the custody ledger into the execution root: - state.ComposeRoot/ExecutionRoot gain a ledgerRoot term (Merkle over balance:/ locked:/orderasset: rows). Validators can no longer finalize agreeing on the book while holding different money. F4 — release the ceil-lock/floor-charge residual on full fill: - decrementMakerReserves now closes a fully-filled maker's reserve and returns the ceil(size*price)-floor residual to the owner (a filled order can never be cancelled), through the same unlock path a cancel uses. No more permanently frozen quote. PROVEN: the two-phase cross-arch harness (arm64 build / amd64 Rosetta verify) now derives byte-identical execRoots AND byte-identical custody ledgers for the identical block (uint64(1e20) still differs at the language level — 0xffff.. vs 0x8000.. — but the exact-integer wire freezes the value in the bytes so no per-validator float->int conversion remains). Red guards flipped to assert the fix; conservation/byzantine/batch-parity/KAT/e2e all green (go test ./... -count=1), go vet clean. OUT OF SCOPE (flagged, NOT fixed here): redteam_unbacked_deposit_test documents a SEPARATE critical finding (F9) — dex_deposit credits an arbitrary self-signed amount with no C-side backing proof, exposed on the public RPC. That needs an access-control/ custody-model decision, not a mechanical fix, and is left for a follow-up. Co-authored-by: Hanzo Dev <[email protected]>
75 lines
2.9 KiB
Go
75 lines
2.9 KiB
Go
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
|
// RED-TEAM (consensus fork) at the ACTUAL dchain root function — now a REGRESSION
|
|
// GUARD for the fix: a resting order with a large EXACT wire size yields an
|
|
// IDENTICAL bookRoot / ExecutionRoot before vs after a restart-forced rebuild.
|
|
// The old float64 encoding (round(size*1e8) reloaded via fixedQtyToFloat) drifted
|
|
// by 1 unit and forked a restarted validator; the exact-integer row eliminates it.
|
|
package dchain
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/dex/pkg/dex"
|
|
)
|
|
|
|
func TestRedteam_RestartForksExecutionRoot(t *testing.T) {
|
|
const symbol = "LUX-LUSD"
|
|
// A large EXACT base-unit size above float64's 2^53 exact ceiling — the regime
|
|
// that used to fork. The wire now carries this integer verbatim (no float64).
|
|
const bigSize = uint64(0x0020000000000001) // 2^53 + 1
|
|
priceUnits := dex.PriceInt(25) * dex.PriceInt(dex.PriceMultiplier)
|
|
|
|
// FRESH validator: rest the order and serialize the canonical rows.
|
|
obFresh := dex.NewOrderBook(symbol)
|
|
ord := &dex.Order{
|
|
ID: 0xABCDEF01,
|
|
Type: dex.Limit,
|
|
Side: dex.Sell,
|
|
PriceUnits: priceUnits,
|
|
Price: float64(int64(priceUnits)) / float64(dex.PriceMultiplier),
|
|
Size: float64(bigSize),
|
|
RemainingSize: float64(bigSize),
|
|
SizeUnits: new(big.Int).SetUint64(bigSize),
|
|
User: "victimmaker",
|
|
UserID: "victimmaker",
|
|
Symbol: symbol,
|
|
Timestamp: time.Unix(0, 1_000_000_000).UTC(),
|
|
Status: dex.Open,
|
|
}
|
|
if obFresh.ConsensusAddOrder(ord) == 0 {
|
|
t.Fatalf("fresh: order rejected")
|
|
}
|
|
rowsFresh := dex.BookToRows(obFresh)
|
|
|
|
// RESTARTED validator: rebuild from the SAME committed rows (VM.Initialize
|
|
// path), then re-serialize — exactly what the next block's root computation
|
|
// consumes.
|
|
obRestart := dex.RowsToBook(symbol, rowsFresh)
|
|
rowsRestart := dex.BookToRows(obRestart)
|
|
|
|
// Compute the ACTUAL consensus roots the VM commits (state.go).
|
|
sortRowsCanonical(rowsFresh)
|
|
sortRowsCanonical(rowsRestart)
|
|
bookFresh := bookRoot(rowsFresh)
|
|
bookRestart := bookRoot(rowsRestart)
|
|
|
|
var parent, tr, txr, ledger [Size]byte
|
|
execFresh := ComposeRoot(parent, bookFresh, tr, txr, ledger, 2)
|
|
execRestart := ComposeRoot(parent, bookRestart, tr, txr, ledger, 2)
|
|
|
|
if rowsFresh[0].Quantity != bigSize {
|
|
t.Fatalf("row did not commit the exact size: Quantity=%d want %d", rowsFresh[0].Quantity, bigSize)
|
|
}
|
|
if bookFresh != bookRestart || execFresh != execRestart {
|
|
t.Fatalf(`RESTART FORK REGRESSION (dchain root function):
|
|
bookRoot fresh=%x restart=%x
|
|
execRoot fresh=%x restart=%x
|
|
A restarted validator derives a DIFFERENT execution root than a non-restarted
|
|
one from the SAME committed order set — the exact-integer row round-trip broke.`,
|
|
bookFresh, bookRestart, execFresh, execRestart)
|
|
}
|
|
t.Logf("GUARD OK: bookRoot/execRoot byte-stable across restart for a 2^53+1 size (exact-integer wire)")
|
|
}
|