mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
179 lines
5.6 KiB
Go
179 lines
5.6 KiB
Go
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package xvmroot
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"testing"
|
|
)
|
|
|
|
// Canonical KAT fixture — a byte-for-byte port of the deterministic input built
|
|
// by lux-private/gpu-kernels/backends/vulkan/tests/test_xvm_roots_kat.cpp
|
|
// (the input every one of the seven GPU backends + the CPU oracle hash):
|
|
//
|
|
// 8 utxo slots, indices 0..3 occupied (status = kUtxoOccupied), 4..7 zero.
|
|
// 4 asset slots, indices 0..2 occupied (occupied = 1), 3 zero.
|
|
// 4 txs (all folded), i%3 status: 0 accepted, 1 rejected (reject_reason=42),
|
|
// 2 untouched, 3 accepted.
|
|
// parent_execution_root[k] = (uint8_t)(0xEE + k)
|
|
// height = 100
|
|
//
|
|
// kind / status / reject_reason are the fields the leaf preimage binds; the
|
|
// accepted/rejected counters the kernel also returns are NOT part of any root,
|
|
// so this pure-computation mirror does not reproduce them.
|
|
const (
|
|
katParentByte0 = 0xEE // parent_execution_root[k] = 0xEE + k (wraps mod 256)
|
|
katHeight = 100
|
|
|
|
statusAccepted = 1
|
|
statusRejected = 2
|
|
)
|
|
|
|
func katUTXOs() []UTXOLeaf {
|
|
us := make([]UTXOLeaf, 8) // 8 slots; 4..7 left zero (unoccupied)
|
|
for i := 0; i < 4; i++ {
|
|
u := &us[i]
|
|
for k := 0; k < 32; k++ {
|
|
u.UTXOID[k] = byte((uint32(i)*11 + uint32(k)) ^ 0x71)
|
|
u.AssetID[k] = byte(0xA0 + (k & 0xF))
|
|
u.OwnerRoot[k] = byte(0xC0 + (k & 0xF))
|
|
}
|
|
u.AmountLo = uint64(1000 + i)
|
|
u.AmountHi = 0
|
|
u.Locktime = 0
|
|
u.Threshold = 1
|
|
u.Status = UTXOOccupied
|
|
}
|
|
return us
|
|
}
|
|
|
|
func katAssets() []AssetLeaf {
|
|
as := make([]AssetLeaf, 4) // 4 slots; slot 3 left zero
|
|
for i := 0; i < 3; i++ {
|
|
a := &as[i]
|
|
for k := 0; k < 32; k++ {
|
|
a.AssetID[k] = byte((uint32(i)*7 + uint32(k)) ^ 0xC3)
|
|
a.MintAuthorityRoot[k] = byte(0xD0 + (k & 0xF))
|
|
}
|
|
a.TotalSupplyLo = uint64(1000000 + i)
|
|
a.TotalSupplyHi = 0
|
|
a.FreezeFlag = 1
|
|
a.Denomination = 8
|
|
a.Occupied = 1
|
|
}
|
|
return as
|
|
}
|
|
|
|
func katTxs() []TxLeaf {
|
|
ts := make([]TxLeaf, 4)
|
|
for i := 0; i < 4; i++ {
|
|
t := &ts[i]
|
|
for k := 0; k < 32; k++ {
|
|
t.TxID[k] = byte(i + k)
|
|
t.ProofDigest[k] = byte(i*5 + k)
|
|
}
|
|
t.Kind = uint32(i)
|
|
// i%3 status pattern — matches the GPU/.cpp "mixed" KAT fixture
|
|
// (test_xvm_roots_kat.cpp run_case): i%3==0 accepted, ==1 rejected,
|
|
// else untouched. Re-anchors Go==GPU on the same fixture.
|
|
switch i % 3 {
|
|
case 0:
|
|
t.Status = statusAccepted
|
|
case 1:
|
|
t.Status = statusRejected
|
|
t.RejectReason = 42
|
|
}
|
|
}
|
|
return ts
|
|
}
|
|
|
|
func katParent() [Size]byte {
|
|
var p [Size]byte
|
|
for k := 0; k < Size; k++ {
|
|
p[k] = byte(katParentByte0 + k)
|
|
}
|
|
return p
|
|
}
|
|
|
|
// TestExecutionRootKAT is the cross-language byte-parity proof: native Go ==
|
|
// GPU. It computes the full execution_root over the canonical KAT fixture and
|
|
// asserts it equals the value all seven GPU backends + the CPU oracle produce,
|
|
// along with each of the three sub-roots.
|
|
func TestExecutionRootKAT(t *testing.T) {
|
|
const (
|
|
wantExecutionRoot = "4f144ef76dd14d4447ccf9c746d747c21dd4a6b0945e2180bfac489f21af2d77"
|
|
wantUTXORoot = "16663d25" // prefix; full value asserted below (unchanged — utxo leaf layout/occupancy identical)
|
|
wantAssetRoot = "12f47742" // unchanged
|
|
wantTxRoot = "c210388d" // recomputed for the i%3 tx-status fixture (Go==GPU; exec_root match proves it)
|
|
)
|
|
|
|
exec, utxo, asset, tx := ExecutionRoot(
|
|
katParent(),
|
|
katUTXOs(),
|
|
katAssets(),
|
|
katTxs(),
|
|
katHeight,
|
|
)
|
|
|
|
gotExec := hex.EncodeToString(exec[:])
|
|
gotUTXO := hex.EncodeToString(utxo[:])
|
|
gotAsset := hex.EncodeToString(asset[:])
|
|
gotTx := hex.EncodeToString(tx[:])
|
|
|
|
t.Logf("utxo_root: %s", gotUTXO)
|
|
t.Logf("asset_root: %s", gotAsset)
|
|
t.Logf("tx_root: %s", gotTx)
|
|
t.Logf("execution_root: %s", gotExec)
|
|
|
|
if gotExec != wantExecutionRoot {
|
|
t.Errorf("execution_root mismatch:\n got %s\n want %s", gotExec, wantExecutionRoot)
|
|
}
|
|
// Sub-roots: assert the documented prefixes from the KAT match exactly.
|
|
if gotUTXO[:8] != wantUTXORoot {
|
|
t.Errorf("utxo_root prefix mismatch:\n got %s\n want %s…", gotUTXO, wantUTXORoot)
|
|
}
|
|
if gotAsset[:8] != wantAssetRoot {
|
|
t.Errorf("asset_root prefix mismatch:\n got %s\n want %s…", gotAsset, wantAssetRoot)
|
|
}
|
|
if gotTx[:8] != wantTxRoot {
|
|
t.Errorf("tx_root prefix mismatch:\n got %s\n want %s…", gotTx, wantTxRoot)
|
|
}
|
|
}
|
|
|
|
// TestComposeIsUntaggedKeccak guards that the execution_root compose is the
|
|
// fixed-shape un-tagged keccak256 (NOT a Merkle node) over
|
|
// parent ‖ utxo ‖ asset ‖ tx ‖ height_u64_le. A regression to a tagged
|
|
// merkle.NodeHash-style compose would change these bytes.
|
|
func TestComposeIsUntaggedKeccak(t *testing.T) {
|
|
const wantExecutionRoot = "4f144ef76dd14d4447ccf9c746d747c21dd4a6b0945e2180bfac489f21af2d77"
|
|
utxo := UTXORoot(katUTXOs())
|
|
asset := AssetRoot(katAssets())
|
|
tx := TxRoot(katTxs())
|
|
got := Compose(katParent(), utxo, asset, tx, katHeight)
|
|
if h := hex.EncodeToString(got[:]); h != wantExecutionRoot {
|
|
t.Errorf("Compose:\n got %s\n want %s", h, wantExecutionRoot)
|
|
}
|
|
}
|
|
|
|
// TestEmptyFamilyRoots confirms the empty-set sub-root is keccak256("") (the
|
|
// RFC-6962 / merkle.EmptyRoot convention), per the wave-3 spec. An all-empty
|
|
// family must not fold to 0x00…00.
|
|
func TestEmptyFamilyRoots(t *testing.T) {
|
|
const emptyKeccak = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
|
for _, tc := range []struct {
|
|
name string
|
|
got [Size]byte
|
|
}{
|
|
{"utxo", UTXORoot(nil)},
|
|
{"asset", AssetRoot(nil)},
|
|
{"tx", TxRoot(nil)},
|
|
// A UTXO slate of all-unoccupied slots also yields the empty root.
|
|
{"utxo-all-unoccupied", UTXORoot(make([]UTXOLeaf, 4))},
|
|
} {
|
|
if h := hex.EncodeToString(tc.got[:]); h != emptyKeccak {
|
|
t.Errorf("%s empty root:\n got %s\n want %s", tc.name, h, emptyKeccak)
|
|
}
|
|
}
|
|
}
|