Files
evm/warp/warptest/block_client.go
zeekayandHanzo Dev 9c6d66ef6b eth,warp: gate EXPORT surfaces on the ⅔-stake Quasar tier, not the Nova accept tip (v1.36)
The two-tier consensus (v1.36) advances VM.Accept at a BARE MAJORITY (Nova), which is reorgable
and can fork under f=1 equivocation. The Ethereum `finalized`/`safe` tags and warp cross-chain
exports were still reading the accept tip — the "semantic collapse" the split exists to prevent.
Re-point every EXPORT surface onto the export-final (Quasar, ⅔-by-stake) frontier consensus
already exposes.

#1 CRITICAL — eth `finalized`/`safe` tags:
- eth.Ethereum gains a DURABLE, monotone lastQuasarHeight (SetLastQuasarHeight/LastQuasarHeight),
  pushed by consensus strictly AFTER accept. The plugin VM's SetLastQuasarFinalized persists it to
  acceptedBlockDB and reloads it on boot (readLastQuasarHeight), so the export surface never
  regresses on restart.
- api_backend HeaderByNumber/BlockByNumber resolve Finalized(-3)/Safe(-4) to GetBlockByNumber(
  LastQuasarHeight) BEFORE the IsAccepted() catch-all — NEVER a block above the Quasar height.
  `latest`/`pending` stay on the accept tip (correct — not finality tags).
- Test: TestFinalizedSafeResolveToQuasarNeverAboveIt proves finalized == the Quasar height,
  monotone, and never above the accepted tip.

#2 HIGH — warp cross-chain export double-spend:
- warp.verifyBlockMessage now requires the block be EXPORT-final (height ≤ BlockClient.
  LastQuasarHeight), so a validator signs a block hash only at Quasar finality, never on a
  reorgable Nova accept (two conflicting Nova forks could otherwise each aggregate a valid export).
- WarpQuorumNumeratorMinimum floored 33 → 67 (⅔): any warp message needs ≥⅔ stake to verify, so at
  most one of two conflicting exports can validate (two ⅔ sets intersect on >⅓ honest stake) —
  restoring the ⅔-accept safety the pre-split accept tier gave for free.

The consensus engine pushes the frontier via NetworkConfig.QuasarObserver; the node bridges it into
the VM (separate node commit). Nova-only chains (no export surface) are unaffected.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 04:58:54 -07:00

104 lines
2.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// warptest exposes common functionality for testing the warp package.
package warptest
import (
"context"
"errors"
"slices"
"time"
"github.com/luxfi/consensus/core/choices"
"github.com/luxfi/ids"
"github.com/luxfi/vm/chain"
)
var ErrNotFound = errors.New("not found")
// mockBlock implements chain.Block for testing
type mockBlock struct {
id ids.ID
}
// ID returns the block ID
func (b *mockBlock) ID() ids.ID {
return b.id
}
// Accept marks the block as accepted
func (b *mockBlock) Accept(context.Context) error {
return nil
}
// Reject marks the block as rejected
func (b *mockBlock) Reject(context.Context) error {
return nil
}
// Status returns the block status
func (b *mockBlock) Status() uint8 {
return uint8(choices.Accepted)
}
// Parent returns the parent block ID
func (b *mockBlock) Parent() ids.ID {
return ids.Empty
}
// ParentID returns the parent block ID
func (b *mockBlock) ParentID() ids.ID {
return ids.Empty
}
// Verify verifies the block
func (b *mockBlock) Verify(context.Context) error {
return nil
}
// Bytes returns the block bytes
func (b *mockBlock) Bytes() []byte {
return nil
}
// Height returns the block height
func (b *mockBlock) Height() uint64 {
return 0
}
// Timestamp returns the block timestamp
func (b *mockBlock) Timestamp() time.Time {
return time.Time{}
}
// EmptyBlockClient returns an error if a block is requested
var EmptyBlockClient BlockClient = MakeBlockClient()
type BlockClient func(ctx context.Context, blockID ids.ID) (chain.Block, error)
func (f BlockClient) GetAcceptedBlock(ctx context.Context, blockID ids.ID) (chain.Block, error) {
return f(ctx, blockID)
}
// LastQuasarHeight reports the export-final (Quasar) height for the test client. It returns the
// max uint64 so a mock block is always treated as EXPORT-final — these tests exercise the warp
// signature/verification path, not the two-tier export gate (which is covered by its own test).
func (f BlockClient) LastQuasarHeight() uint64 { return ^uint64(0) }
// MakeBlockClient returns a new BlockClient that returns the provided blocks.
// If a block is requested that isn't part of the provided blocks, an error is
// returned.
func MakeBlockClient(blkIDs ...ids.ID) BlockClient {
return func(_ context.Context, blkID ids.ID) (chain.Block, error) {
if !slices.Contains(blkIDs, blkID) {
return nil, ErrNotFound
}
// Create a mock block with the requested ID
return &mockBlock{
id: blkID,
}, nil
}
}