Files
evm/eth/api_backend_quasar_test.go
T
zeekayandHanzo Dev 6cbb326615 consensus v1.36 export-boundary: close 3 LOW items for tag (DRY clamp, one admin_importChain, locked export writes + boot-wiring tests)
Two-tier finality (Nova = local majority accept, drives VM.Accept; Quasar =
⅔-by-stake export) requires that NO export surface ever names a block above the
served accept tip. RED's final pass approved SHIP with three LOW items to close
first.

LOW-1 — DRY the accept-tip clamp into ONE place. New eth.Ethereum.ClampedQuasarHeight()
= min(LastQuasarHeight, LastAccepted.Number) is the single clamp; BOTH export
surfaces read it: EthAPIBackend.quasarHeight() (RPC finalized/safe) and
warpBlockClient.LastQuasarHeight() (warp export gate, via the BlockClient
interface). Removes the second inlined clamp copy that could drift. LastQuasarHeight()
is documented as RAW (node boot re-seed only, where reconcile has already run).

LOW-2 — exactly ONE admin_importChain. Deleted the eth-package AdminAPI.ImportChain
(which neither reconciled the export height nor skipped <=head blocks); the plugin VM's
(plugin/evm/admin_api.go) is the sole one — it runs under vmLock and calls
reconcileQuasarWithAccept after each SetLastAcceptedBlockDirect. Left a NOTE so it is
not re-added and reopens the rewind hole. Dropped the now-unused rlp import.

LOW-3 — exclude concurrent export-height writers + prove the real boot wiring.
SetLastQuasarFinalized now takes vmLock (the same lock reconcileQuasarWithAccept's
admin_importChain caller holds), so the observer callback's durable Put+Store can never
interleave with a re-import's Reset(0)+Delete on the same key. Fixed a boot ordering
gap: the startup auto-import (importBlocksFromFile) runs AFTER initializeChain's
reconcile, so a --import-chain-data rewind could leave a stale export height — added a
reconcile right after the auto-import. New eth.NewTestEthereum test-support ctor.

Tests (all green): eth -race adds TestClampedQuasarHeight_RealRewind (genuine
SetLastAcceptedBlockDirect rewind; belt clamps finalized/safe to the rewound tip while
raw is unchanged); plugin/evm adds TestBootReconcile_RealLastAccepted_ClearsStaleQuasarOnRewind
(drives the REAL restored LastAcceptedBlock through belt + suspenders) and
TestSetLastQuasarFinalized_LockedAgainstReconcile (concurrency: durable key <-> in-mem
always consistent). gofmt/vet/ontology clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 09:22:12 -07:00

164 lines
8.1 KiB
Go

// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package eth
import (
"context"
"math/big"
"testing"
"github.com/luxfi/crypto"
"github.com/luxfi/evm/consensus/dummy"
"github.com/luxfi/evm/core"
"github.com/luxfi/evm/params"
"github.com/luxfi/evm/rpc"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/core/vm"
"github.com/stretchr/testify/require"
)
// TestFinalizedSafeResolveToQuasarNeverAboveIt is the export-boundary gate for the two-tier
// consensus: VM.Accept now advances the local NOVA (bare-majority) tip, which is reorgable and
// MUST NOT be exported. The Ethereum `finalized`(-3)/`safe`(-4) block tags must therefore resolve
// to the EXPORT-FINAL (Quasar, ⅔-by-stake) block — NEVER a block above LastQuasarHeight() (which
// trails, and can never exceed, the accepted tip). This proves `eth_getBlockByNumber("finalized")`
// never returns a block above the Quasar height, that it is monotone (never regresses on a
// restart-seed race), and that `latest` stays on the accepted tip (a correct non-finality tag).
// buildAcceptedChain builds a REAL canonical chain (genesis + n accepted blocks) so the
// `finalized`/`safe` + export-height resolution is exercised against a real chain (LastAccepted /
// GetBlockByNumber / the clamp), not a mock. Accept tip = n.
func buildAcceptedChain(t *testing.T, n int) *core.BlockChain {
t.Helper()
key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
cryptoAddr := crypto.PubkeyToAddress(key.PublicKey)
addr := common.BytesToAddress(cryptoAddr[:])
bal, _ := new(big.Int).SetString("100000000000000000000000", 10)
gspec := &core.Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr: {Balance: bal}}}
engine := dummy.NewETHFaker()
// The dummy engine requires the fee-recipient (blackhole) coinbase on generated blocks.
coinbase := common.HexToAddress("0x0100000000000000000000000000000000000000")
genDb, blocks, _, err := core.GenerateChainWithGenesis(gspec, engine, n, 10, func(i int, b *core.BlockGen) {
b.SetCoinbase(coinbase)
})
require.NoError(t, err)
chain, err := core.NewBlockChain(genDb, core.DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false, nil)
require.NoError(t, err)
t.Cleanup(func() { chain.Stop() })
_, err = chain.InsertChain(blocks)
require.NoError(t, err)
for _, blk := range blocks {
require.NoError(t, chain.Accept(blk))
}
chain.DrainAcceptorQueue()
require.EqualValues(t, n, chain.LastAcceptedBlock().NumberU64())
return chain
}
func TestFinalizedSafeResolveToQuasarNeverAboveIt(t *testing.T) {
const numBlocks = 5
chain := buildAcceptedChain(t, numBlocks)
// The eth backend's blockchain is the canonical chain (genesis..numBlocks). LastAccepted /
// `latest` is the Nova tip (numBlocks); the export (Quasar) height is set separately below.
eth := &Ethereum{blockchain: chain}
backend := &EthAPIBackend{eth: eth}
ctx := context.Background()
tip := chain.CurrentBlock().Number.Uint64()
require.EqualValues(t, numBlocks, tip)
num := func(tag rpc.BlockNumber) uint64 {
blk, berr := backend.BlockByNumber(ctx, tag)
require.NoError(t, berr)
require.NotNil(t, blk, "resolution for tag %d returned nil", tag)
return blk.NumberU64()
}
hdrNum := func(tag rpc.BlockNumber) uint64 {
h, herr := backend.HeaderByNumber(ctx, tag)
require.NoError(t, herr)
require.NotNil(t, h, "header resolution for tag %d returned nil", tag)
return h.Number.Uint64()
}
// Before any export forms, `finalized`/`safe` are genesis (0) — NEVER the accepted tip.
require.Zero(t, num(rpc.FinalizedBlockNumber), "finalized must be genesis before the first export, not the accepted tip")
require.Zero(t, num(rpc.SafeBlockNumber))
require.Zero(t, hdrNum(rpc.FinalizedBlockNumber))
// Export advances to height 3 (strictly below the accepted tip 5).
eth.SetLastQuasarHeight(3)
require.EqualValues(t, 3, num(rpc.FinalizedBlockNumber))
require.EqualValues(t, 3, num(rpc.SafeBlockNumber))
require.EqualValues(t, 3, hdrNum(rpc.FinalizedBlockNumber))
require.Less(t, num(rpc.FinalizedBlockNumber), tip, "finalized MUST NOT exceed the Quasar height (which trails the accepted tip)")
// `latest`/`pending` stay on the accepted tip — they are NOT finality tags.
require.EqualValues(t, tip, num(rpc.LatestBlockNumber), "latest must remain the accepted tip, not the export height")
// Monotone: a lower export height is ignored (never regress, e.g. a boot-seed racing a cert).
eth.SetLastQuasarHeight(2)
require.EqualValues(t, 3, num(rpc.FinalizedBlockNumber), "SetLastQuasarHeight must be monotone (never regress the export frontier)")
// Export catches up to the tip.
eth.SetLastQuasarHeight(numBlocks)
require.EqualValues(t, numBlocks, num(rpc.FinalizedBlockNumber))
// BELT — the case RED flagged as false confidence: seed the export height ABOVE the accept tip
// (the rewind-stale-key scenario, where a re-import lowered the accept tip below a persisted
// quasar height) and assert finalized/safe CLAMP to the accept tip. The code now ENFORCES this;
// previously it was only incidentally true because normal op keeps quasar ≤ accept, so a
// stale-high key would have named a block above (or off) the served chain.
eth.SetLastQuasarHeight(numBlocks + 100) // stale-high, far above the accept tip
require.EqualValues(t, tip, num(rpc.FinalizedBlockNumber), "finalized MUST clamp to the accept tip when the export height is stale-high")
require.EqualValues(t, tip, num(rpc.SafeBlockNumber), "safe MUST clamp to the accept tip when the export height is stale-high")
require.EqualValues(t, tip, hdrNum(rpc.FinalizedBlockNumber))
require.LessOrEqual(t, num(rpc.FinalizedBlockNumber), tip, "finalized must NEVER exceed the accept tip, even with a stale-high export key")
// Restore for the exhaustive loop below.
eth.ResetLastQuasarHeight(numBlocks)
// THE INVARIANT, exhaustively: for EVERY export height, `finalized` resolves to exactly that
// height and NEVER above the accepted tip.
for h := uint64(0); h <= numBlocks; h++ {
e := &Ethereum{blockchain: chain}
e.SetLastQuasarHeight(h)
b := &EthAPIBackend{eth: e}
blk, berr := b.BlockByNumber(ctx, rpc.FinalizedBlockNumber)
require.NoError(t, berr)
require.EqualValues(t, h, blk.NumberU64())
require.LessOrEqual(t, blk.NumberU64(), tip, "finalized must never exceed the accepted tip")
}
}
// TestClampedQuasarHeight_RealRewind exercises the BELT against a GENUINE rewind
// (SetLastAcceptedBlockDirect — the primitive the EVM-rewind→re-import restore runbook uses): with a
// persisted export height of 8 and the accept tip rewound to 5, ClampedQuasarHeight (the ONE shared
// clamp both `finalized`/`safe` and the warp gate read) resolves to 5 — never the stale 8 above the
// served accept tip — WITHOUT relying on the VM's reconcile having run. This is the structural belt
// that the warp gate now shares (LOW-1), verified under -race at the eth level.
func TestClampedQuasarHeight_RealRewind(t *testing.T) {
chain := buildAcceptedChain(t, 10)
eth := &Ethereum{blockchain: chain}
backend := &EthAPIBackend{eth: eth}
ctx := context.Background()
eth.SetLastQuasarHeight(8) // valid, ≤ accept tip 10
require.EqualValues(t, 8, eth.ClampedQuasarHeight())
// REWIND to block 5 (SetLastAcceptedBlockDirect): accept tip drops below the export height.
require.NoError(t, chain.SetLastAcceptedBlockDirect(chain.GetBlockByNumber(5)))
require.EqualValues(t, 5, chain.LastAcceptedBlock().NumberU64())
// The RAW export height is still 8 (clearing it is the VM reconcile's job) — but the BELT clamps.
require.EqualValues(t, 8, eth.LastQuasarHeight())
require.EqualValues(t, 5, eth.ClampedQuasarHeight(), "ClampedQuasarHeight MUST clamp to the rewound accept tip")
// `finalized`/`safe` therefore resolve to block 5, never the stale block 8.
fin, err := backend.BlockByNumber(ctx, rpc.FinalizedBlockNumber)
require.NoError(t, err)
require.EqualValues(t, 5, fin.NumberU64(), "finalized MUST clamp to the rewound accept tip, never the stale export height")
safe, err := backend.BlockByNumber(ctx, rpc.SafeBlockNumber)
require.NoError(t, err)
require.EqualValues(t, 5, safe.NumberU64())
}