mirror of
https://github.com/luxfi/genesis.git
synced 2026-07-27 04:11:41 +00:00
test: decomplect lp134 split test — one table, one loop, exact alias sets
Was 242 lines / 4 near-identical funcs with per-chain include-lists AND
banned-lists — the redundancy bred two self-contradiction bugs (F and M each
listed a required alias in their own banned list; 'mpcvm' twice on F).
Rob Pike: data dominates. One spec table + one loop. Exact alias-set equality
IS the whole invariant — it subsumes must-include, must-not-claim-the-other's,
and no-collision in a single assertion, and makes contradictory lists
impossible by construction. Exact-match also surfaced K-Chain's real alias set
{k,key,keyvm,kms} that the lenient old test never pinned.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
+104
-203
@@ -7,33 +7,96 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// LP-134 chain-level decomplect invariants.
|
||||
// LP-134 decomplect: the T-Chain braid (one ThresholdVM serving both threshold-FHE
|
||||
// and threshold-MPC) splits into orthogonal chains. Each threshold-family shard
|
||||
// declares exactly what it IS — disjoint aliases, fields, and precompile families.
|
||||
//
|
||||
// The legacy T-Chain (ThresholdVM serving BOTH threshold-FHE and
|
||||
// threshold-MPC) is split into two orthogonal chains — F-Chain (FHE only,
|
||||
// confidential DECRYPT) and M-Chain (MPC only, CGGMP21/FROST bridge-custody
|
||||
// SIGNING) — plus a K-Chain (KeyVM/KMS) cleaned of the MPC conflation
|
||||
// ("K stays keyvm"). Both F and M run the same ThresholdVM substrate but are
|
||||
// distinct chains with disjoint purposes, params, precompile families, and
|
||||
// aliases. These tests fail closed if any concern leaks across the split:
|
||||
// - no MPC field/alias/precompile-family on F-Chain,
|
||||
// - no FHE field/alias/precompile-family on M-Chain,
|
||||
// - no alias claimed by two of {F, M, K},
|
||||
// - the retired tchain.json is gone (forward-only).
|
||||
// One spec table, one loop. Exact-set alias equality is the whole invariant: it
|
||||
// subsumes "must include", "must not claim the other chain's aliases", and
|
||||
// "no two chains share an alias" in a single assertion — and makes the class of
|
||||
// self-contradictory include/exclude lists impossible by construction.
|
||||
|
||||
var lp134Nets = []string{"mainnet", "testnet", "devnet", "localnet"}
|
||||
|
||||
func lp134ShardPath(net, letter string) string {
|
||||
type lp134Spec struct {
|
||||
letter string
|
||||
vm string
|
||||
mode string // "" = no mode field
|
||||
aliases []string // the EXACT alias set (case-insensitive)
|
||||
precompiles []string // exact mpcVerifyPrecompiles set, if any
|
||||
has []string // fields that must be present
|
||||
hasNot []string // fields that must be absent
|
||||
noText string // substring that must not appear in the shard ("" = skip)
|
||||
}
|
||||
|
||||
var lp134Shards = []lp134Spec{
|
||||
{letter: "f", vm: "ThresholdVM", mode: "fhe", aliases: []string{"f", "fhe", "fhevm"},
|
||||
has: []string{"ringDegreeN", "ringModulusQ", "fhePrecompilePrefix"},
|
||||
hasNot: []string{"mpcParties", "mpcThreshold", "mpcVerifyPrecompiles"}, noText: "0x08"},
|
||||
{letter: "m", vm: "ThresholdVM", mode: "mpc", aliases: []string{"m", "mpc", "mpcvm"},
|
||||
precompiles: []string{
|
||||
"0x0800000000000000000000000000000000000002", // FROST
|
||||
"0x0800000000000000000000000000000000000003", // CGGMP21
|
||||
},
|
||||
has: []string{"mpcParties", "mpcThreshold"},
|
||||
hasNot: []string{"ringDegreeN", "ringModulusQ", "dkgParties", "dkgThreshold", "fhePrecompilePrefix"}, noText: "0x07"},
|
||||
{letter: "k", vm: "KeyVM", aliases: []string{"k", "key", "keyvm", "kms"},
|
||||
hasNot: []string{"mpcParties", "mpcThreshold", "mpcVerifyPrecompiles", "mode", "ringDegreeN"}},
|
||||
}
|
||||
|
||||
func TestLP134Split(t *testing.T) {
|
||||
for _, net := range lp134Nets {
|
||||
// Retired T-Chain: forward-only, no tchain.json anywhere.
|
||||
if _, err := os.Stat(lp134Path(net, "t")); !os.IsNotExist(err) {
|
||||
t.Errorf("%s: tchain.json must be retired (LP-134 forward-only split)", net)
|
||||
}
|
||||
for _, s := range lp134Shards {
|
||||
t.Run(net+"/"+s.letter, func(t *testing.T) {
|
||||
text, m := lp134Shard(t, net, s.letter)
|
||||
if got, _ := m["vm"].(string); got != s.vm {
|
||||
t.Errorf("vm = %q, want %q", got, s.vm)
|
||||
}
|
||||
if got, _ := m["mode"].(string); got != s.mode {
|
||||
t.Errorf("mode = %q, want %q", got, s.mode)
|
||||
}
|
||||
for _, f := range s.has {
|
||||
if m[f] == nil {
|
||||
t.Errorf("missing required field %q", f)
|
||||
}
|
||||
}
|
||||
for _, f := range s.hasNot {
|
||||
if m[f] != nil {
|
||||
t.Errorf("must not carry field %q (decomplect leak)", f)
|
||||
}
|
||||
}
|
||||
if s.noText != "" && strings.Contains(text, s.noText) {
|
||||
t.Errorf("must not reference %q (foreign precompile family)", s.noText)
|
||||
}
|
||||
if got := lp134Set(m["aliases"]); !lp134Equal(got, s.aliases) {
|
||||
t.Errorf("aliases = %v, want exactly %v", got, s.aliases)
|
||||
}
|
||||
if s.precompiles != nil {
|
||||
if got := lp134Set(m["mpcVerifyPrecompiles"]); !lp134Equal(got, s.precompiles) {
|
||||
t.Errorf("mpcVerifyPrecompiles = %v, want exactly %v", got, s.precompiles)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lp134Path(net, letter string) string {
|
||||
return filepath.Join("..", "configs", net, letter+"chain.json")
|
||||
}
|
||||
|
||||
func lp134ReadShard(t *testing.T, net, letter string) map[string]any {
|
||||
func lp134Shard(t *testing.T, net, letter string) (string, map[string]any) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(lp134ShardPath(net, letter))
|
||||
data, err := os.ReadFile(lp134Path(net, letter))
|
||||
if err != nil {
|
||||
t.Fatalf("%s/%schain.json: %v", net, letter, err)
|
||||
}
|
||||
@@ -41,201 +104,39 @@ func lp134ReadShard(t *testing.T, net, letter string) map[string]any {
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
t.Fatalf("%s/%schain.json parse: %v", net, letter, err)
|
||||
}
|
||||
return m
|
||||
return string(data), m
|
||||
}
|
||||
|
||||
func lp134ShardText(t *testing.T, net, letter string) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(lp134ShardPath(net, letter))
|
||||
if err != nil {
|
||||
t.Fatalf("%s/%schain.json: %v", net, letter, err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func lp134AliasSet(t *testing.T, m map[string]any) map[string]bool {
|
||||
t.Helper()
|
||||
raw, ok := m["aliases"].([]any)
|
||||
if !ok {
|
||||
t.Fatalf("aliases missing or wrong type: %T", m["aliases"])
|
||||
}
|
||||
out := make(map[string]bool, len(raw))
|
||||
// lp134Set lower-cases a JSON string array into a sorted, deduped slice.
|
||||
func lp134Set(v any) []string {
|
||||
raw, _ := v.([]any)
|
||||
seen := map[string]bool{}
|
||||
for _, a := range raw {
|
||||
s, _ := a.(string)
|
||||
out[strings.ToLower(s)] = true
|
||||
if s, ok := a.(string); ok {
|
||||
seen[strings.ToLower(s)] = true
|
||||
}
|
||||
}
|
||||
out := make([]string, 0, len(seen))
|
||||
for s := range seen {
|
||||
out = append(out, s)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// TestLP134_TChainRetired: the legacy tchain.json is gone on every net
|
||||
// (forward-only split — F-Chain + M-Chain supersede it).
|
||||
func TestLP134_TChainRetired(t *testing.T) {
|
||||
for _, net := range lp134Nets {
|
||||
if _, err := os.Stat(lp134ShardPath(net, "t")); !os.IsNotExist(err) {
|
||||
t.Fatalf("%s/tchain.json must be retired (LP-134 forward-only split); stat err=%v", net, err)
|
||||
func lp134Equal(got, want []string) bool {
|
||||
w := append([]string(nil), want...)
|
||||
for i := range w {
|
||||
w[i] = strings.ToLower(w[i])
|
||||
}
|
||||
sort.Strings(w)
|
||||
if len(got) != len(w) {
|
||||
return false
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != w[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLP134_FChainIsFHEOnly: F-Chain carries FHE concepts only — ThresholdVM
|
||||
// in FHE mode, FHE ring params, the 0x07 FHE precompile family, and NO MPC
|
||||
// field, alias, or 0x08 (threshold-signature) precompile-family reference.
|
||||
func TestLP134_FChainIsFHEOnly(t *testing.T) {
|
||||
for _, net := range lp134Nets {
|
||||
t.Run(net, func(t *testing.T) {
|
||||
m := lp134ReadShard(t, net, "f")
|
||||
if got, _ := m["vm"].(string); got != "ThresholdVM" {
|
||||
t.Fatalf("F-Chain vm = %q, want ThresholdVM", got)
|
||||
}
|
||||
if got, _ := m["mode"].(string); got != "fhe" {
|
||||
t.Fatalf("F-Chain mode = %q, want fhe", got)
|
||||
}
|
||||
if m["ringDegreeN"] == nil || m["ringModulusQ"] == nil {
|
||||
t.Fatalf("F-Chain must carry the FHE ring params (ringDegreeN/ringModulusQ)")
|
||||
}
|
||||
if got, _ := m["fhePrecompilePrefix"].(string); got != "0x07" {
|
||||
t.Fatalf("F-Chain fhePrecompilePrefix = %q, want 0x07", got)
|
||||
}
|
||||
// Decomplect: no MPC concepts on F-Chain.
|
||||
for _, banned := range []string{"mpcParties", "mpcThreshold", "mpcVerifyPrecompiles"} {
|
||||
if m[banned] != nil {
|
||||
t.Fatalf("F-Chain must not carry MPC field %q (LP-134 decomplect)", banned)
|
||||
}
|
||||
}
|
||||
if strings.Contains(lp134ShardText(t, net, "f"), "0x08") {
|
||||
t.Fatalf("F-Chain must not reference the 0x08 threshold-signature precompile family")
|
||||
}
|
||||
al := lp134AliasSet(t, m)
|
||||
for _, want := range []string{"f", "fhe", "fhevm"} {
|
||||
if !al[want] {
|
||||
t.Fatalf("F-Chain aliases must include %q", want)
|
||||
}
|
||||
}
|
||||
for _, banned := range []string{"m", "mpc", "mpcvm", "t", "threshold", "mpcvm"} {
|
||||
if al[banned] {
|
||||
t.Fatalf("F-Chain must not claim alias %q", banned)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLP134_MChainIsMPCOnly: M-Chain carries MPC concepts only — ThresholdVM
|
||||
// in MPC mode, MPC params, the CGGMP21 (0x0800..03) + FROST (0x0800..02)
|
||||
// verify precompiles, and NO FHE field, alias, or 0x07 precompile-family ref.
|
||||
func TestLP134_MChainIsMPCOnly(t *testing.T) {
|
||||
const (
|
||||
frostAddr = "0x0800000000000000000000000000000000000002"
|
||||
cggmp21Addr = "0x0800000000000000000000000000000000000003"
|
||||
)
|
||||
for _, net := range lp134Nets {
|
||||
t.Run(net, func(t *testing.T) {
|
||||
m := lp134ReadShard(t, net, "m")
|
||||
if got, _ := m["vm"].(string); got != "ThresholdVM" {
|
||||
t.Fatalf("M-Chain vm = %q, want ThresholdVM", got)
|
||||
}
|
||||
if got, _ := m["mode"].(string); got != "mpc" {
|
||||
t.Fatalf("M-Chain mode = %q, want mpc", got)
|
||||
}
|
||||
if m["mpcParties"] == nil || m["mpcThreshold"] == nil {
|
||||
t.Fatalf("M-Chain must carry the MPC params (mpcParties/mpcThreshold)")
|
||||
}
|
||||
// EVM verify surface: exactly the two threshold-signature precompiles.
|
||||
pcs, ok := m["mpcVerifyPrecompiles"].([]any)
|
||||
if !ok || len(pcs) != 2 {
|
||||
t.Fatalf("M-Chain must declare its 2 MPC verify precompiles, got %v", m["mpcVerifyPrecompiles"])
|
||||
}
|
||||
seen := map[string]bool{frostAddr: false, cggmp21Addr: false}
|
||||
for _, p := range pcs {
|
||||
ps, _ := p.(string)
|
||||
ps = strings.ToLower(ps)
|
||||
if _, known := seen[ps]; !known {
|
||||
t.Fatalf("M-Chain unexpected precompile %q (want only FROST/CGGMP21)", ps)
|
||||
}
|
||||
seen[ps] = true
|
||||
}
|
||||
for addr, ok := range seen {
|
||||
if !ok {
|
||||
t.Fatalf("M-Chain missing MPC verify precompile %s", addr)
|
||||
}
|
||||
}
|
||||
// Decomplect: no FHE concepts on M-Chain.
|
||||
for _, banned := range []string{"ringDegreeN", "ringModulusQ", "dkgParties", "dkgThreshold", "fhePrecompilePrefix"} {
|
||||
if m[banned] != nil {
|
||||
t.Fatalf("M-Chain must not carry FHE field %q (LP-134 decomplect)", banned)
|
||||
}
|
||||
}
|
||||
if strings.Contains(lp134ShardText(t, net, "m"), "0x07") {
|
||||
t.Fatalf("M-Chain must not reference the 0x07 FHE precompile family")
|
||||
}
|
||||
al := lp134AliasSet(t, m)
|
||||
for _, want := range []string{"m", "mpc", "mpcvm"} {
|
||||
if !al[want] {
|
||||
t.Fatalf("M-Chain aliases must include %q", want)
|
||||
}
|
||||
}
|
||||
// M-Chain IS mpcvm (LP-134), so it claims m/mpc/mpcvm (asserted above);
|
||||
// it must not claim the F-Chain or retired-T aliases. ("mpcvm" was
|
||||
// erroneously in this banned list, contradicting the required set.)
|
||||
for _, banned := range []string{"f", "fhe", "fhevm", "t", "threshold"} {
|
||||
if al[banned] {
|
||||
t.Fatalf("M-Chain must not claim alias %q", banned)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLP134_KChainIsKMSOnly: K-Chain is pure KeyVM/KMS — the M/mpc aliases and
|
||||
// MPC params moved to M-Chain ("K stays keyvm" per LP-134).
|
||||
func TestLP134_KChainIsKMSOnly(t *testing.T) {
|
||||
for _, net := range lp134Nets {
|
||||
t.Run(net, func(t *testing.T) {
|
||||
m := lp134ReadShard(t, net, "k")
|
||||
if got, _ := m["vm"].(string); got != "KeyVM" {
|
||||
t.Fatalf("K-Chain vm = %q, want KeyVM", got)
|
||||
}
|
||||
for _, banned := range []string{"mpcParties", "mpcThreshold", "mpcVerifyPrecompiles", "mode", "ringDegreeN"} {
|
||||
if m[banned] != nil {
|
||||
t.Fatalf("K-Chain must not carry %q (LP-134: MPC/FHE concerns live on M/F)", banned)
|
||||
}
|
||||
}
|
||||
al := lp134AliasSet(t, m)
|
||||
if !al["k"] {
|
||||
t.Fatalf("K-Chain must claim alias K")
|
||||
}
|
||||
for _, banned := range []string{"m", "mpc", "mpcvm", "f", "fhe", "fhevm"} {
|
||||
if al[banned] {
|
||||
t.Fatalf("K-Chain must not claim alias %q (LP-134 decomplect)", banned)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestLP134_NoAliasCollision: the threshold-family chains {F, M, K} claim
|
||||
// mutually-disjoint alias sets — the core decomplect invariant (a single alias
|
||||
// resolving to two chains is the exact bug LP-134 removes).
|
||||
func TestLP134_NoAliasCollision(t *testing.T) {
|
||||
for _, net := range lp134Nets {
|
||||
t.Run(net, func(t *testing.T) {
|
||||
f := lp134AliasSet(t, lp134ReadShard(t, net, "f"))
|
||||
mm := lp134AliasSet(t, lp134ReadShard(t, net, "m"))
|
||||
k := lp134AliasSet(t, lp134ReadShard(t, net, "k"))
|
||||
for _, pair := range []struct {
|
||||
a, b map[string]bool
|
||||
an, bn string
|
||||
}{
|
||||
{f, mm, "F", "M"},
|
||||
{f, k, "F", "K"},
|
||||
{mm, k, "M", "K"},
|
||||
} {
|
||||
for alias := range pair.a {
|
||||
if pair.b[alias] {
|
||||
t.Fatalf("%s-Chain alias %q collides with %s-Chain (LP-134: aliases must be disjoint)", pair.an, alias, pair.bn)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user