mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
Enable-everything builder surface (public permissionless launch). The geth-layer
strict-PQ profile (LuxStrictPQ) refused ecrecover / p256Verify / sha256 /
ripemd160 / blake2f / bls12381 / kzg under vm.config.PQ — but ecrecover
(secp256k1) is THE Ethereum signature path every dapp depends on, and the rest
are verify-only / hash, key-safe. Refusing them breaks the builder surface.
The chain now installs NO forbidding pq.Profile: chainConfig.PQ stays nil,
which pq.RefuseUnder(nil) treats as permissive. The generic geth mechanism
(chainConfig.PQ + runPrecompile.RefuseUnder + legacypool nil-check) is kept for
any chain that wants it; Lux installs nothing.
This completes the decomplect started in luxfi/precompile: consensus PQ and the
EVM precompile profile were complected through config.PQ / StrictPQTimestamp.
After precompile dropped contract.RefuseUnderStrictPQ from every verify-only
precompile, the whole strict-PQ projection here was dead. Removed:
- plugin/evm/pq_profile.go (LuxStrictPQ)
- plugin/evm/vm.go: the `if vm.config.PQ { chainConfig.PQ = LuxStrictPQ() }`
install + the unconditional StrictPQTimestamp=0 genesis pin
- plugin/evm/config: the config.PQ bool field (no code writer; was JSON-opt-in)
- params/extras/network_upgrades.go: StrictPQTimestamp field + apply + IsStrictPQ
- precompile/registry/bridge.go + core/precompile_overrider.go: the dead
IsStrictPQ adapters (only existed to feed the deleted StrictPQReporter)
- tests: pq_test.go, strict_pq_activation_test.go, and the two StrictPQ
assertions (+ orphaned helper) in precompile_upgrade_rollout_test.go
Lux's PQ security is enforced at consensus / identity (quasar/p3q), not by
refusing an EVM verifier a dapp asked for. The live lux-mainnet upgrade.json
never actually set strictPQTimestamp, so this matches production.
Build: full module green. Tests: params/extras, plugin/evm/config,
precompile/registry pass.
259 lines
11 KiB
Go
259 lines
11 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// External test package so the rollout regression can depend on the
|
|
// luxfi/evm/params/extras public API without dragging in
|
|
// luxfi/evm/precompile/registry (which would create an import cycle
|
|
// via the modules.Register side-effects).
|
|
//
|
|
// The tests parse raw JSON only — they intentionally avoid the typed
|
|
// PrecompileUpgrade UnmarshalJSON path so they do NOT need every
|
|
// precompile module's init() to have run. The contract these tests
|
|
// enforce is the textual JSON shape (key + blockTimestamp) which is
|
|
// what luxd's checkPrecompileCompatible compares; the per-key Config
|
|
// struct identity is verified inside the existing module-level tests.
|
|
package extras_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// luxMainnetLiveUpgradeJSON is the upgrade.json currently inlined in the
|
|
// lux-mainnet StatefulSet startup script
|
|
// (~/work/lux/universe/k8s/lux-mainnet/luxd-startup.yaml line 182). It is
|
|
// the source-of-truth list of precompile activations active on every C-
|
|
// Chain pod today. Every entry here MUST stay present (at the same
|
|
// blockTimestamp) in the canonical upgrade.json shipped by luxfi/genesis,
|
|
// otherwise luxd's checkPrecompileCompatible refuses to boot.
|
|
//
|
|
// If this constant changes, the lux-mainnet StatefulSet manifest changed
|
|
// out-of-band — sync it back to luxfi/genesis BEFORE shipping the new
|
|
// luxd image, never the other way around.
|
|
const luxMainnetLiveUpgradeJSON = `{
|
|
"networkUpgradeOverrides": {
|
|
"durangoTimestamp": 0,
|
|
"quasarTimestamp": 0,
|
|
"fortunaTimestamp": 0,
|
|
"graniteTimestamp": 0
|
|
},
|
|
"precompileUpgrades": [
|
|
{"aiMiningConfig": {"blockTimestamp": 0}},
|
|
{"blake3Config": {"blockTimestamp": 0}},
|
|
{"cggmp21Verify": {"blockTimestamp": 0}},
|
|
{"deadZeroConfig": {"blockTimestamp": 0}},
|
|
{"deadConfig": {"blockTimestamp": 0}},
|
|
{"deadFullConfig": {"blockTimestamp": 0}},
|
|
{"routerConfig": {"blockTimestamp": 0}},
|
|
{"fheConfig": {"blockTimestamp": 0}},
|
|
{"frostVerify": {"blockTimestamp": 0}},
|
|
{"graphConfig": {"blockTimestamp": 0}},
|
|
{"hpkeConfig": {"blockTimestamp": 0}},
|
|
{"mldsaVerify": {"blockTimestamp": 0}},
|
|
{"mlkemConfig": {"blockTimestamp": 0}},
|
|
{"ringConfig": {"blockTimestamp": 0}},
|
|
{"slhdsaVerify": {"blockTimestamp": 0}},
|
|
{"zkConfig": {"blockTimestamp": 0}}
|
|
]
|
|
}`
|
|
|
|
// TestMainnetUpgradeJSON_IsForwardCompatibleWithLiveActivations is the
|
|
// regression gate from red-review finding #7. It asserts that every
|
|
// precompile activation currently live on lux-mainnet is preserved at the
|
|
// same blockTimestamp in the canonical upgrade.json so that
|
|
// checkPrecompileCompatible returns nil at boot.
|
|
//
|
|
// The failure mode this prevents:
|
|
//
|
|
// luxd boots, reads the new upgrade.json, and runs
|
|
// checkPrecompileCompatible against the active configs (the live
|
|
// activations that already shipped). For each live entry it walks the
|
|
// new list looking for the same key at the same timestamp. If it's
|
|
// missing -> "missing PrecompileUpgrade[i]". If it's at a different
|
|
// timestamp -> "mismatching PrecompileUpgrade[i]". Either way, BOOT
|
|
// FAILS. This is the exact wedge that bricked the cluster on the
|
|
// original rollout attempt.
|
|
func TestMainnetUpgradeJSON_IsForwardCompatibleWithLiveActivations(t *testing.T) {
|
|
canonical := readPrecompileUpgradeTimestamps(t, readCanonicalMainnetUpgradeJSONRaw(t))
|
|
live := readPrecompileUpgradeTimestamps(t, []byte(luxMainnetLiveUpgradeJSON))
|
|
|
|
for key, liveTs := range live {
|
|
canonicalTs, ok := canonical[key]
|
|
require.Truef(t, ok,
|
|
"precompile %q is active on lux-mainnet (blockTimestamp=%d) but is missing from canonical upgrade.json — luxd will refuse to boot (checkPrecompileCompatible: missing PrecompileUpgrade)",
|
|
key, liveTs,
|
|
)
|
|
require.Equalf(t, liveTs, canonicalTs,
|
|
"precompile %q is active on lux-mainnet at blockTimestamp=%d but canonical upgrade.json schedules it at blockTimestamp=%d — luxd will refuse to boot (checkPrecompileCompatible: mismatching PrecompileUpgrade). RESCHEDULING AN ALREADY-LIVE PRECOMPILE IS NEVER VALID.",
|
|
key, liveTs, canonicalTs,
|
|
)
|
|
}
|
|
}
|
|
|
|
// TestMainnetUpgradeJSON_PrecompileTimestampsAreMonotonic enforces the
|
|
// validation rule from extras.ChainConfig.verifyPrecompileUpgrades —
|
|
// precompile timestamps must be monotonically increasing across the
|
|
// list (the verify call refuses a config that decreases). If we get this
|
|
// wrong in the rollout config, every luxd boot rejects the JSON before
|
|
// it even runs checkPrecompileCompatible.
|
|
func TestMainnetUpgradeJSON_PrecompileTimestampsAreMonotonic(t *testing.T) {
|
|
raw := readCanonicalMainnetUpgradeJSONRaw(t)
|
|
var doc struct {
|
|
PrecompileUpgrades []map[string]json.RawMessage `json:"precompileUpgrades"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(raw, &doc))
|
|
|
|
var prev uint64
|
|
for i, entry := range doc.PrecompileUpgrades {
|
|
require.Lenf(t, entry, 1, "precompileUpgrades[%d] must have exactly one key", i)
|
|
for key, rawVal := range entry {
|
|
var v struct {
|
|
BlockTimestamp uint64 `json:"blockTimestamp"`
|
|
}
|
|
require.NoErrorf(t, json.Unmarshal(rawVal, &v), "precompileUpgrades[%d][%q] must have blockTimestamp", i, key)
|
|
require.GreaterOrEqualf(t, v.BlockTimestamp, prev,
|
|
"precompileUpgrades[%d][%q] blockTimestamp=%d is < previous %d — verifyPrecompileUpgrades refuses non-monotonic timestamps",
|
|
i, key, v.BlockTimestamp, prev,
|
|
)
|
|
prev = v.BlockTimestamp
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMainnetUpgradeJSON_WarpRequiresPrimaryNetworkSigners enforces the
|
|
// Warp policy from red-review finding #8: the canonical upgrade.json must
|
|
// schedule warp so that, once the strict-PQ fork lands, every cross-chain
|
|
// warp message is signed by primary-network validators (not just a subnet
|
|
// quorum).
|
|
//
|
|
// Warp lives in the genesis chainConfig (cchain.json config.warpConfig at
|
|
// the genesis timestamp, with requirePrimaryNetworkSigners=false during the
|
|
// classical era). The upgrade schedule then carries a two-step toggle at the
|
|
// strict-PQ fork: disable@strictPQ-1, then re-enable@strictPQ with the
|
|
// PQ-hardened policy. Both toggles are strictly AFTER genesis time, so this
|
|
// is a genuine future upgrade — NOT a reschedule of the genesis warp (that
|
|
// would be the relaunch fork hazard; see relaunch_safety_test.go). The
|
|
// policy assertion therefore targets the RE-ENABLE entry (the one that
|
|
// carries quorumNumerator + requirePrimaryNetworkSigners); the disable entry
|
|
// carries only {blockTimestamp, disable:true}.
|
|
func TestMainnetUpgradeJSON_WarpRequiresPrimaryNetworkSigners(t *testing.T) {
|
|
const strictPQ uint64 = 1766708400
|
|
|
|
raw := readCanonicalMainnetUpgradeJSONRaw(t)
|
|
|
|
var doc struct {
|
|
PrecompileUpgrades []map[string]json.RawMessage `json:"precompileUpgrades"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(raw, &doc))
|
|
|
|
var (
|
|
foundDisable bool
|
|
foundReEnable bool
|
|
prevWarpTs *uint64
|
|
)
|
|
for i, entry := range doc.PrecompileUpgrades {
|
|
warpRaw, ok := entry["warpConfig"]
|
|
if !ok {
|
|
continue
|
|
}
|
|
var warp struct {
|
|
BlockTimestamp uint64 `json:"blockTimestamp"`
|
|
Disable bool `json:"disable"`
|
|
QuorumNumerator uint64 `json:"quorumNumerator"`
|
|
RequirePrimaryNetworkSigners bool `json:"requirePrimaryNetworkSigners"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(warpRaw, &warp))
|
|
|
|
// Every warp upgrade entry must be strictly after genesis time —
|
|
// rescheduling the genesis warp into the (0, genesisTime] window is
|
|
// the relaunch fork hazard.
|
|
require.Greaterf(t, warp.BlockTimestamp, uint64(0),
|
|
"warpConfig upgrade entry at index %d has blockTimestamp 0 — warp is declared in the genesis chainConfig; an upgrade entry pinned to 0 would collide with the genesis warp and fail verifyPrecompileUpgrades",
|
|
i,
|
|
)
|
|
// Same-key strict increase (mirrors verifyPrecompileUpgrades).
|
|
if prevWarpTs != nil {
|
|
require.Greaterf(t, warp.BlockTimestamp, *prevWarpTs,
|
|
"warpConfig entries must strictly increase in blockTimestamp (got %d after %d)",
|
|
warp.BlockTimestamp, *prevWarpTs,
|
|
)
|
|
}
|
|
ts := warp.BlockTimestamp
|
|
prevWarpTs = &ts
|
|
|
|
if warp.Disable {
|
|
foundDisable = true
|
|
require.Equalf(t, strictPQ-1, warp.BlockTimestamp,
|
|
"warpConfig disable must fire one second before the strict-PQ fork (%d), got %d",
|
|
strictPQ-1, warp.BlockTimestamp,
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Re-enable entry: carries the PQ-hardened policy.
|
|
foundReEnable = true
|
|
require.Equalf(t, strictPQ, warp.BlockTimestamp,
|
|
"warpConfig re-enable must fire at the strict-PQ fork (%d), got %d", strictPQ, warp.BlockTimestamp,
|
|
)
|
|
require.Equal(t, uint64(67), warp.QuorumNumerator, "warpConfig quorumNumerator must be 67")
|
|
require.Truef(t, warp.RequirePrimaryNetworkSigners,
|
|
"warpConfig.requirePrimaryNetworkSigners must be true on lux-mainnet — every cross-chain warp message MUST be signed by primary-network validators (red-review finding #8)",
|
|
)
|
|
}
|
|
require.True(t, foundDisable, "warpConfig disable entry must be present in canonical upgrade.json")
|
|
require.True(t, foundReEnable, "warpConfig re-enable entry (with PQ signer policy) must be present in canonical upgrade.json")
|
|
}
|
|
|
|
// readPrecompileUpgradeTimestamps decodes the precompileUpgrades array
|
|
// into a {key: blockTimestamp} map, skipping warpConfig (covered by its
|
|
// own test) and feeManagerConfig (not a precompile activation per se).
|
|
func readPrecompileUpgradeTimestamps(t *testing.T, raw []byte) map[string]uint64 {
|
|
t.Helper()
|
|
var doc struct {
|
|
PrecompileUpgrades []map[string]json.RawMessage `json:"precompileUpgrades"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(raw, &doc))
|
|
|
|
out := make(map[string]uint64, len(doc.PrecompileUpgrades))
|
|
for i, entry := range doc.PrecompileUpgrades {
|
|
require.Lenf(t, entry, 1, "precompileUpgrades[%d] must have exactly one key", i)
|
|
for key, rawVal := range entry {
|
|
if key == "warpConfig" || key == "feeManagerConfig" {
|
|
continue
|
|
}
|
|
var v struct {
|
|
BlockTimestamp uint64 `json:"blockTimestamp"`
|
|
}
|
|
require.NoErrorf(t, json.Unmarshal(rawVal, &v), "precompileUpgrades[%d][%q] must have blockTimestamp", i, key)
|
|
_, dup := out[key]
|
|
require.Falsef(t, dup, "precompileUpgrades has duplicate key %q at index %d", key, i)
|
|
out[key] = v.BlockTimestamp
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// readCanonicalMainnetUpgradeJSONRaw returns the canonical upgrade.json
|
|
// bytes. The test resolves the file via the relative path from
|
|
// evm/params/extras up to genesis/configs/mainnet — both luxfi/evm and
|
|
// luxfi/genesis live in the same `~/work/lux` worktree on CI runners.
|
|
func readCanonicalMainnetUpgradeJSONRaw(t *testing.T) []byte {
|
|
t.Helper()
|
|
candidates := []string{
|
|
// luxfi/evm running standalone, sibling luxfi/genesis checkout.
|
|
"../../../../genesis/configs/mainnet/upgrade.json",
|
|
// monorepo layout (e.g. when both repos are inside the same root).
|
|
"../../../genesis/configs/mainnet/upgrade.json",
|
|
}
|
|
for _, candidate := range candidates {
|
|
if data, err := os.ReadFile(candidate); err == nil {
|
|
return data
|
|
}
|
|
}
|
|
t.Skipf("canonical mainnet upgrade.json not reachable from cwd — looked in %v; run from a worktree that contains luxfi/genesis alongside luxfi/evm", candidates)
|
|
return nil
|
|
}
|