evm/core: release-gate the cross-repo DexSettleActivationTime DRY guard behind a build tag

The guard TestDexSettleActivationTime_LayerMirrorsMatch references
dex.DexSettleActivationTime, which is defined in luxfi/precompile and post-dates
evm/go.mod's pinned precompile tag (v0.5.54). Under GOWORK=off (CI), evm/core
failed to build (undefined: dex.DexSettleActivationTime); it only compiled via an
untracked local go.work. The guard was therefore both inert in CI and a latent
CI break.

Move the single cross-repo guard into precompile_alwayson_dexsettle_guard_test.go
behind //go:build dexsettle_guard. Default CI excludes it, so evm/core builds and
tests against the pinned precompile tag with no go.work. The release step bumps
evm/go.mod to a precompile tag that exports the symbol and runs
`go test -tags dexsettle_guard ./core/`, enforcing the DRY invariant atomically
with the bump. precompile cannot host the guard (it does not import luxfi/evm; the
graph is strictly evm->precompile, so referencing extras.DexSettleActivationTime
from precompile would create a cycle). All other 0x9999 activation pins in
precompile_alwayson_test.go use only evm-local symbols and run on every CI build.
This commit is contained in:
2026-06-21 13:51:10 -07:00
parent a309d4900a
commit 25daffe90e
2 changed files with 69 additions and 22 deletions
@@ -0,0 +1,59 @@
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build dexsettle_guard
// This file holds the ONE cross-repo DRY guard that pins the 0x9999 activation
// timestamp across the two layers that cannot share a Go symbol:
//
// - the CANONICAL definition, extras.DexSettleActivationTime (this evm layer),
// - the layer-local MIRROR, dex.DexSettleActivationTime (luxfi/precompile).
//
// It is RELEASE-COUPLED on purpose. dex.DexSettleActivationTime lives in
// luxfi/precompile; evm/go.mod must be bumped to a precompile version that
// EXPORTS that symbol before this guard can compile. Because the precompile tag
// is published by CI/CD (never from a dev checkout), keeping this test always-on
// would make evm/core fail to build (`undefined: dex.DexSettleActivationTime`)
// against any precompile tag predating the symbol — a silent CI break that only
// an untracked local go.work could paper over.
//
// The `dexsettle_guard` build tag decouples WHAT the guard asserts from WHEN it
// is compiled. Default CI (`go test ./core/`) excludes this file, so evm/core
// builds and tests against the pinned precompile tag with no go.work. The release
// step that bumps evm/go.mod to precompile >= the symbol-exporting tag runs:
//
// go test -tags dexsettle_guard ./core/
//
// at which point dex.DexSettleActivationTime exists and the DRY invariant is
// enforced atomically with the bump. See the release checklist in
// precompile_alwayson_test.go's package doc and LLM.md.
//
// Everything else about 0x9999 activation (dispatch gate, marker install, the
// override race, replay safety) is pinned UNCONDITIONALLY in
// precompile_alwayson_test.go using only evm-local symbols — those run on every
// CI build. Only this cross-repo symbol equality is tag-gated.
package core
import (
"testing"
"github.com/luxfi/evm/params/extras"
// dex provides the layer-local mirror DexSettleActivationTime asserted equal
// to the canonical extras value here. This is the ONLY reference in evm to a
// precompile symbol that may post-date evm/go.mod's pinned precompile tag,
// which is exactly why this file is behind the dexsettle_guard build tag.
"github.com/luxfi/precompile/dex"
"github.com/stretchr/testify/require"
)
// TestDexSettleActivationTime_LayerMirrorsMatch is the DRY-safety guard for the
// 0x9999 activation timestamp. It imports BOTH layers' constants and fails CI the
// instant either moves without the other. If the two drift, a settlement could
// dispatch (gated by extras) at a timestamp where the precompile withholds its log
// (gated by the stale mirror) — or vice versa — opening a settlement-without-log /
// log-without-settlement window and a consensus split on replay.
func TestDexSettleActivationTime_LayerMirrorsMatch(t *testing.T) {
require.Equal(t, extras.DexSettleActivationTime, dex.DexSettleActivationTime,
"dex.DexSettleActivationTime (precompile layer-local mirror) must equal the canonical extras.DexSettleActivationTime; "+
"the DEX log gate and the EVM dispatch gate MUST fire on the same dated fork")
}
+10 -22
View File
@@ -16,9 +16,6 @@ import (
// registry side-effect: bridges the external DEX settlement module (0x9999)
// into the EVM's internal registry so it is visible as AlwaysOn here.
_ "github.com/luxfi/evm/precompile/registry"
// dex provides the layer-local mirror DexSettleActivationTime asserted equal to
// the canonical extras value by TestDexSettleActivationTime_LayerMirrorsMatch.
"github.com/luxfi/precompile/dex"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/rawdb"
"github.com/luxfi/geth/core/tracing"
@@ -53,25 +50,16 @@ const (
tsPostActivation uint64 = extras.DexSettleActivationTime + 86_400
)
// TestDexSettleActivationTime_LayerMirrorsMatch is the DRY-safety guard for the
// 0x9999 activation timestamp, which exists in TWO layers that cannot share a symbol:
//
// - the CANONICAL definition, extras.DexSettleActivationTime (this evm layer), is what
// the dispatch gate (GetExtrasRules) and the marker-installing state transition use;
// - a layer-local MIRROR, dex.DexSettleActivationTime (luxfi/precompile), which the DEX
// precompile uses to gate its NEW consensus-visible logs (DEXFill, the native V4
// Initialize) as defense in depth — the precompile sits BELOW evm in the import graph
// (evm imports precompile, never the reverse) so it cannot import the extras constant.
//
// If these two drift, a settlement could dispatch (gated by extras) at a timestamp where
// the precompile withholds its log (gated by the stale mirror) — or vice versa — opening a
// settlement-without-log / log-without-settlement window and a consensus split on replay.
// This test imports BOTH and fails CI the instant either constant moves without the other.
func TestDexSettleActivationTime_LayerMirrorsMatch(t *testing.T) {
require.Equal(t, extras.DexSettleActivationTime, dex.DexSettleActivationTime,
"dex.DexSettleActivationTime (precompile layer-local mirror) must equal the canonical extras.DexSettleActivationTime; "+
"the DEX log gate and the EVM dispatch gate MUST fire on the same dated fork")
}
// The cross-layer DRY guard that pins extras.DexSettleActivationTime ==
// dex.DexSettleActivationTime (luxfi/precompile's layer-local mirror) lives in
// precompile_alwayson_dexsettle_guard_test.go behind the `dexsettle_guard` build
// tag. It is RELEASE-COUPLED: dex.DexSettleActivationTime sits BELOW evm in the
// import graph (evm imports precompile, never the reverse), so evm/go.mod must be
// bumped to a precompile tag that EXPORTS the symbol before that guard can compile.
// Default CI excludes it (so evm/core builds against the pinned precompile tag with
// no go.work); the release step runs `go test -tags dexsettle_guard ./core/` after
// the go.mod bump so the invariant is enforced atomically with it. Everything in
// THIS file uses only evm-local symbols and runs on every CI build.
// TestAlwaysOn_9999_Registered asserts the DEX settlement precompile bridges into the
// EVM registry as AlwaysOn and is enumerated by AlwaysOnModules().