mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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]>
104 lines
2.5 KiB
Go
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
|
|
}
|
|
}
|