mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
ParseBlock demanded a field the wire has no room for. The block format is timestamp, height, parentID, tx lengths, tx blob; the stamp lived only on the object the builder made and was gone the moment the block was serialized, which is every time it leaves the process. So the gate refused every block that ever crossed the wire, genesis included, and Q-Chain accepted nothing on any network for as long as it ran -- certAccepted=0, appliedHead=0, everywhere. The two doors into the format disagreed, which is what made it a loop rather than a stall. A responder answers a catch-up request through vm.parseBlock, which has no gate and succeeds; the requester admits the reply through VM.ParseBlock, which had the gate and failed. Same bytes, two verdicts: every node served the block every node refused. Putting the stamp on the wire would be worse than leaving it off. The block id is sha256 of its own bytes, so a stamp in those bytes makes the id depend on who signed, and two honest nodes compute two ids for one block. seedGenesis already refuses wall-clock time for exactly this reason. The stamp could not have been load-bearing anyway: it was made with a key generated fresh for each block, and a signature that ships its own throwaway public key states nothing an attacker cannot also state. Its thirty-second validity window is also the opposite of what catch-up needs, which is to serve blocks from hours ago. A block-level signature belongs to consensus, where it already is: a cert is a quorum's statement about a block, checkable by anyone holding the validator set, and it is not a field on the block. Quasar still signs there. Transaction-level quantum signatures are untouched -- those are serialized, and they are verified. The tests stayed green throughout because none of them sent a block through Bytes() and back. Three do now: parse what we serialize, parse GENESIS, and the two parse paths reach the same verdict on identical bytes. Restoring the gate fails all three. Co-authored-by: Hanzo Dev <[email protected]>
165 lines
3.8 KiB
Go
165 lines
3.8 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package quantumvm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/luxfi/chains/quantumvm/quantum"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
var (
|
|
errBlockVerificationFailed = errors.New("block verification failed")
|
|
errInvalidBlockHeight = errors.New("invalid block height")
|
|
errInvalidParentID = errors.New("invalid parent ID")
|
|
)
|
|
|
|
// Block represents a QVM block with quantum features
|
|
type Block struct {
|
|
id ids.ID
|
|
timestamp time.Time
|
|
height uint64
|
|
parentID ids.ID
|
|
transactions []Transaction
|
|
vm *VM
|
|
bytes []byte
|
|
}
|
|
|
|
// ID returns the block ID
|
|
func (b *Block) ID() ids.ID {
|
|
return b.id
|
|
}
|
|
|
|
// Accept marks the block as accepted
|
|
func (b *Block) Accept(context.Context) error {
|
|
b.vm.lock.Lock()
|
|
defer b.vm.lock.Unlock()
|
|
|
|
// Store block in database
|
|
if err := b.vm.state.Put(b.id[:], b.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update last accepted
|
|
if err := b.vm.state.Put([]byte("lastAccepted"), b.id[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update height
|
|
heightBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(heightBytes, b.height)
|
|
if err := b.vm.state.Put([]byte("height"), heightBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Process transactions
|
|
for _, tx := range b.transactions {
|
|
if err := b.vm.txPool.RemoveTransaction(tx.ID()); err != nil {
|
|
b.vm.log.Error("failed to remove tx from pool", "txID", tx.ID(), "error", err)
|
|
}
|
|
}
|
|
|
|
b.vm.log.Info("block accepted",
|
|
"blockID", b.id,
|
|
"height", b.height,
|
|
"txCount", len(b.transactions),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reject marks the block as rejected
|
|
func (b *Block) Reject(context.Context) error {
|
|
b.vm.lock.Lock()
|
|
defer b.vm.lock.Unlock()
|
|
|
|
// Return transactions to pool
|
|
for _, tx := range b.transactions {
|
|
if err := b.vm.txPool.AddTransaction(tx); err != nil {
|
|
b.vm.log.Error("failed to return tx to pool", "txID", tx.ID(), "error", err)
|
|
}
|
|
}
|
|
|
|
b.vm.log.Debug("block rejected", "blockID", b.id, "height", b.height)
|
|
return nil
|
|
}
|
|
|
|
// Verify verifies the block validity
|
|
func (b *Block) Verify(ctx context.Context) error {
|
|
b.vm.lock.RLock()
|
|
defer b.vm.lock.RUnlock()
|
|
|
|
// Verify height
|
|
if b.height == 0 {
|
|
return errInvalidBlockHeight
|
|
}
|
|
|
|
// Verify parent exists (except for genesis)
|
|
if b.height > 1 {
|
|
if _, err := b.vm.GetBlock(ctx, b.parentID); err != nil {
|
|
return errInvalidParentID
|
|
}
|
|
}
|
|
|
|
// Verify transactions in parallel
|
|
if len(b.transactions) > 0 {
|
|
msgs := make([][]byte, len(b.transactions))
|
|
sigs := make([]*quantum.QuantumSignature, len(b.transactions))
|
|
|
|
for i, tx := range b.transactions {
|
|
msgs[i] = tx.Bytes()
|
|
sigs[i] = tx.GetQuantumSignature()
|
|
}
|
|
|
|
if b.vm.Config.CoronaEnabled {
|
|
if err := b.vm.quantumSigner.ParallelVerify(msgs, sigs); err != nil {
|
|
return errBlockVerificationFailed
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Parent returns the parent block ID
|
|
func (b *Block) Parent() ids.ID {
|
|
return b.parentID
|
|
}
|
|
|
|
// ParentID returns the parent block ID (implements consensus Block interface)
|
|
func (b *Block) ParentID() ids.ID {
|
|
return b.parentID
|
|
}
|
|
|
|
// Height returns the block height
|
|
func (b *Block) Height() uint64 {
|
|
return b.height
|
|
}
|
|
|
|
// Timestamp returns the block timestamp (implements block.Block interface)
|
|
func (b *Block) Timestamp() time.Time {
|
|
return b.timestamp
|
|
}
|
|
|
|
// TimestampUnix returns the block timestamp as Unix seconds.
|
|
func (b *Block) TimestampUnix() int64 {
|
|
return b.timestamp.Unix()
|
|
}
|
|
|
|
// Status returns the block status. Q-Chain does not yet track per-block
|
|
// status separately (all blocks reaching here are processing or accepted),
|
|
// so we return 0 (Unknown) until a status field is added.
|
|
func (b *Block) Status() uint8 {
|
|
return 0
|
|
}
|
|
|
|
// String returns a string representation of the block
|
|
func (b *Block) String() string {
|
|
return b.id.String()
|
|
}
|