mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
Add Fee() to the Transaction interface and introduce VM.IssueTx as the canonical user-tx admission point. The FeePolicy gate refuses zero-fee tx before they reach the parallel quantum verification pool. Consensus-internal callers bypass via txPool.AddTransaction.
288 lines
7.0 KiB
Go
288 lines
7.0 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package quantumvm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/chains/quantumvm/quantum"
|
|
)
|
|
|
|
// Transaction represents a QVM transaction
|
|
type Transaction interface {
|
|
ID() ids.ID
|
|
Bytes() []byte
|
|
Verify() error
|
|
Execute() error
|
|
GetQuantumSignature() *quantum.QuantumSignature
|
|
Timestamp() time.Time
|
|
// Fee returns the user-paid tx burn in nLUX. Must satisfy the
|
|
// chain's FeePolicy (>= fee.MinTxFeeFloor on Q-Chain).
|
|
Fee() uint64
|
|
}
|
|
|
|
// BaseTransaction provides common transaction functionality
|
|
type BaseTransaction struct {
|
|
id ids.ID
|
|
timestamp time.Time
|
|
nonce uint64
|
|
data []byte
|
|
fee uint64
|
|
quantumSignature *quantum.QuantumSignature
|
|
}
|
|
|
|
// ID returns the transaction ID
|
|
func (tx *BaseTransaction) ID() ids.ID {
|
|
if tx.id == ids.Empty {
|
|
tx.id, _ = ids.ToID(tx.Bytes())
|
|
}
|
|
return tx.id
|
|
}
|
|
|
|
// Bytes returns the transaction bytes
|
|
func (tx *BaseTransaction) Bytes() []byte {
|
|
size := 8 + 8 + len(tx.data) // timestamp + nonce + data
|
|
bytes := make([]byte, 0, size)
|
|
|
|
timestampBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(timestampBytes, uint64(tx.timestamp.Unix()))
|
|
bytes = append(bytes, timestampBytes...)
|
|
|
|
nonceBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(nonceBytes, tx.nonce)
|
|
bytes = append(bytes, nonceBytes...)
|
|
|
|
bytes = append(bytes, tx.data...)
|
|
|
|
return bytes
|
|
}
|
|
|
|
// GetQuantumSignature returns the quantum signature
|
|
func (tx *BaseTransaction) GetQuantumSignature() *quantum.QuantumSignature {
|
|
return tx.quantumSignature
|
|
}
|
|
|
|
// Timestamp returns the transaction timestamp
|
|
func (tx *BaseTransaction) Timestamp() time.Time {
|
|
return tx.timestamp
|
|
}
|
|
|
|
// Fee returns the user-paid tx burn (nLUX). Defaults to zero on a
|
|
// zero-value BaseTransaction — callers must set it explicitly for the
|
|
// fee gate to accept the tx.
|
|
func (tx *BaseTransaction) Fee() uint64 {
|
|
return tx.fee
|
|
}
|
|
|
|
// SetFee sets the user-paid tx burn in nLUX. Convenience setter for
|
|
// tests + tx builders; production callers should pass the fee at
|
|
// construction time.
|
|
func (tx *BaseTransaction) SetFee(f uint64) { tx.fee = f }
|
|
|
|
// Verify verifies the transaction
|
|
func (tx *BaseTransaction) Verify() error {
|
|
if tx.quantumSignature == nil {
|
|
return errors.New("missing quantum signature")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Execute executes the transaction
|
|
func (tx *BaseTransaction) Execute() error {
|
|
// Implementation depends on transaction type
|
|
return nil
|
|
}
|
|
|
|
// TransactionPool manages pending transactions
|
|
type TransactionPool struct {
|
|
pending map[ids.ID]Transaction
|
|
queue []Transaction
|
|
maxSize int
|
|
batchSize int
|
|
log log.Logger
|
|
mu sync.RWMutex
|
|
closed bool
|
|
closeChan chan struct{}
|
|
}
|
|
|
|
// NewTransactionPool creates a new transaction pool
|
|
func NewTransactionPool(maxSize, batchSize int, logger log.Logger) *TransactionPool {
|
|
return &TransactionPool{
|
|
pending: make(map[ids.ID]Transaction),
|
|
queue: make([]Transaction, 0, maxSize),
|
|
maxSize: maxSize,
|
|
batchSize: batchSize,
|
|
log: logger,
|
|
closeChan: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// AddTransaction adds a transaction to the pool
|
|
func (p *TransactionPool) AddTransaction(tx Transaction) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.closed {
|
|
return errors.New("pool is closed")
|
|
}
|
|
|
|
if len(p.pending) >= p.maxSize {
|
|
return errors.New("pool is full")
|
|
}
|
|
|
|
txID := tx.ID()
|
|
if _, exists := p.pending[txID]; exists {
|
|
return errors.New("transaction already exists")
|
|
}
|
|
|
|
// Verify transaction
|
|
if err := tx.Verify(); err != nil {
|
|
return err
|
|
}
|
|
|
|
p.pending[txID] = tx
|
|
p.queue = append(p.queue, tx)
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveTransaction removes a transaction from the pool
|
|
func (p *TransactionPool) RemoveTransaction(txID ids.ID) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if _, exists := p.pending[txID]; !exists {
|
|
return errors.New("transaction not found")
|
|
}
|
|
|
|
delete(p.pending, txID)
|
|
|
|
// Remove from queue
|
|
newQueue := make([]Transaction, 0, len(p.queue)-1)
|
|
for _, tx := range p.queue {
|
|
if tx.ID() != txID {
|
|
newQueue = append(newQueue, tx)
|
|
}
|
|
}
|
|
p.queue = newQueue
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetPendingTransactions returns pending transactions up to the limit
|
|
func (p *TransactionPool) GetPendingTransactions(limit int) []Transaction {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
if limit <= 0 || limit > len(p.queue) {
|
|
limit = len(p.queue)
|
|
}
|
|
|
|
txs := make([]Transaction, limit)
|
|
copy(txs, p.queue[:limit])
|
|
|
|
return txs
|
|
}
|
|
|
|
// PendingCount returns the number of pending transactions
|
|
func (p *TransactionPool) PendingCount() int {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
return len(p.pending)
|
|
}
|
|
|
|
// Close closes the transaction pool
|
|
func (p *TransactionPool) Close() {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if !p.closed {
|
|
p.closed = true
|
|
close(p.closeChan)
|
|
p.pending = nil
|
|
p.queue = nil
|
|
}
|
|
}
|
|
|
|
// TransactionWorker processes transactions in parallel
|
|
type TransactionWorker struct {
|
|
vm *VM
|
|
quantumSigner *quantum.QuantumSigner
|
|
}
|
|
|
|
// ProcessBatch processes a batch of transactions.
|
|
// Uses GPU batch ML-DSA verification when available and batch is large enough.
|
|
func (w *TransactionWorker) ProcessBatch(txs []Transaction) ([]Transaction, error) {
|
|
// Phase 1: basic validation (no crypto)
|
|
var verified []Transaction
|
|
for _, tx := range txs {
|
|
if err := tx.Verify(); err != nil {
|
|
w.vm.log.Debug("transaction verification failed", "txID", tx.ID(), "error", err)
|
|
continue
|
|
}
|
|
if w.vm.Config.QuantumStampEnabled && tx.GetQuantumSignature() == nil {
|
|
w.vm.log.Debug("missing quantum signature", "txID", tx.ID())
|
|
continue
|
|
}
|
|
verified = append(verified, tx)
|
|
}
|
|
|
|
if len(verified) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Phase 2: quantum signature verification (GPU batch when possible)
|
|
sigValid := make([]bool, len(verified))
|
|
if w.vm.Config.QuantumStampEnabled {
|
|
msgs := make([][]byte, len(verified))
|
|
sigs := make([]*quantum.QuantumSignature, len(verified))
|
|
for i, tx := range verified {
|
|
msgs[i] = tx.Bytes()
|
|
sigs[i] = tx.GetQuantumSignature()
|
|
}
|
|
|
|
// ParallelVerifyWithThreshold picks GPU or CPU automatically
|
|
err := w.quantumSigner.ParallelVerifyWithThreshold(msgs, sigs, w.vm.Config.GPUBatchThreshold)
|
|
if err == nil {
|
|
// All passed
|
|
for i := range sigValid {
|
|
sigValid[i] = true
|
|
}
|
|
} else {
|
|
// Batch failed -- verify individually to find which ones are bad
|
|
for i := range verified {
|
|
if verr := w.quantumSigner.Verify(msgs[i], sigs[i]); verr == nil {
|
|
sigValid[i] = true
|
|
} else {
|
|
w.vm.log.Debug("quantum signature verification failed", "txID", verified[i].ID(), "error", verr)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for i := range sigValid {
|
|
sigValid[i] = true
|
|
}
|
|
}
|
|
|
|
// Phase 3: execute valid transactions
|
|
validTxs := make([]Transaction, 0, len(verified))
|
|
for i, tx := range verified {
|
|
if !sigValid[i] {
|
|
continue
|
|
}
|
|
if err := tx.Execute(); err != nil {
|
|
w.vm.log.Debug("transaction execution failed", "txID", tx.ID(), "error", err)
|
|
continue
|
|
}
|
|
validTxs = append(validTxs, tx)
|
|
}
|
|
|
|
return validTxs, nil
|
|
}
|