Files
proto/x/txs/executor/syntactic_verifier.go
zeekayandHanzo Dev 84e442e154 Depend on the renamed utility package
github.com/luxfi/utils is now github.com/luxfi/util. Import paths and the
module requirement follow the rename; the old module stays as an indirect
requirement that published dependencies still pull in.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 11:46:00 -07:00

298 lines
6.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"errors"
"fmt"
"strings"
"unicode"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
lux "github.com/luxfi/utxo"
"github.com/luxfi/proto/x/txs"
"github.com/luxfi/util"
)
const (
minNameLen = 1
maxNameLen = 128
minSymbolLen = 1
maxSymbolLen = 4
maxDenomination = 32
)
var (
_ txs.Visitor = (*SyntacticVerifier)(nil)
errWrongNumberOfCredentials = errors.New("wrong number of credentials")
errInitialStatesNotSortedUnique = errors.New("initial states not sorted and unique")
errNameTooShort = fmt.Errorf("name is too short, minimum size is %d", minNameLen)
errNameTooLong = fmt.Errorf("name is too long, maximum size is %d", maxNameLen)
errSymbolTooShort = fmt.Errorf("symbol is too short, minimum size is %d", minSymbolLen)
errSymbolTooLong = fmt.Errorf("symbol is too long, maximum size is %d", maxSymbolLen)
errNoFxs = errors.New("assets must support at least one Fx")
errIllegalNameCharacter = errors.New("asset's name must be made up of only letters and numbers")
errIllegalSymbolCharacter = errors.New("asset's symbol must be all upper case letters")
errUnexpectedWhitespace = errors.New("unexpected whitespace provided")
errDenominationTooLarge = errors.New("denomination is too large")
errOperationsNotSortedUnique = errors.New("operations not sorted and unique")
errNoOperations = errors.New("an operationTx must have at least one operation")
errDoubleSpend = errors.New("inputs attempt to double spend an input")
errNoImportInputs = errors.New("no import inputs")
errNoExportOutputs = errors.New("no export outputs")
)
type SyntacticVerifier struct {
*Backend
Tx *txs.Tx
}
func (v *SyntacticVerifier) BaseTx(tx *txs.BaseTx) error {
if err := tx.BaseTx.Verify(v.LuxCtx); err != nil {
return err
}
err := lux.VerifyTx(
v.Config.TxFee,
v.FeeAssetID,
[][]*lux.TransferableInput{tx.Ins},
[][]*lux.TransferableOutput{tx.Outs},
)
if err != nil {
return err
}
for _, cred := range v.Tx.Creds {
if err := cred.Verify(); err != nil {
return err
}
}
numCreds := len(v.Tx.Creds)
numInputs := len(tx.Ins)
if numCreds != numInputs {
return fmt.Errorf("%w: %d != %d",
errWrongNumberOfCredentials,
numCreds,
numInputs,
)
}
return nil
}
func (v *SyntacticVerifier) CreateAssetTx(tx *txs.CreateAssetTx) error {
switch {
case len(tx.Name) < minNameLen:
return errNameTooShort
case len(tx.Name) > maxNameLen:
return errNameTooLong
case len(tx.Symbol) < minSymbolLen:
return errSymbolTooShort
case len(tx.Symbol) > maxSymbolLen:
return errSymbolTooLong
case len(tx.States) == 0:
return errNoFxs
case tx.Denomination > maxDenomination:
return errDenominationTooLarge
case strings.TrimSpace(tx.Name) != tx.Name:
return errUnexpectedWhitespace
}
for _, r := range tx.Name {
if r > unicode.MaxASCII || (!unicode.IsLetter(r) && !unicode.IsNumber(r) && r != ' ') {
return errIllegalNameCharacter
}
}
for _, r := range tx.Symbol {
if r > unicode.MaxASCII || !unicode.IsUpper(r) {
return errIllegalSymbolCharacter
}
}
if err := tx.BaseTx.BaseTx.Verify(v.LuxCtx); err != nil {
return err
}
err := lux.VerifyTx(
v.Config.CreateAssetTxFee,
v.FeeAssetID,
[][]*lux.TransferableInput{tx.Ins},
[][]*lux.TransferableOutput{tx.Outs},
)
if err != nil {
return err
}
for _, state := range tx.States {
if err := state.Verify(len(v.Fxs)); err != nil {
return err
}
}
if !utils.IsSortedAndUnique(tx.States) {
return errInitialStatesNotSortedUnique
}
for _, cred := range v.Tx.Creds {
if err := cred.Verify(); err != nil {
return err
}
}
numCreds := len(v.Tx.Creds)
numInputs := len(tx.Ins)
if numCreds != numInputs {
return fmt.Errorf("%w: %d != %d",
errWrongNumberOfCredentials,
numCreds,
numInputs,
)
}
return nil
}
func (v *SyntacticVerifier) OperationTx(tx *txs.OperationTx) error {
if len(tx.Ops) == 0 {
return errNoOperations
}
if err := tx.BaseTx.BaseTx.Verify(v.LuxCtx); err != nil {
return err
}
err := lux.VerifyTx(
v.Config.TxFee,
v.FeeAssetID,
[][]*lux.TransferableInput{tx.Ins},
[][]*lux.TransferableOutput{tx.Outs},
)
if err != nil {
return err
}
inputs := set.NewSet[ids.ID](len(tx.Ins))
for _, in := range tx.Ins {
inputs.Add(in.InputID())
}
for _, op := range tx.Ops {
if err := op.Verify(); err != nil {
return err
}
for _, utxoID := range op.UTXOIDs {
inputID := utxoID.InputID()
if inputs.Contains(inputID) {
return errDoubleSpend
}
inputs.Add(inputID)
}
}
if !txs.IsSortedAndUniqueOperations(tx.Ops) {
return errOperationsNotSortedUnique
}
for _, cred := range v.Tx.Creds {
if err := cred.Verify(); err != nil {
return err
}
}
numCreds := len(v.Tx.Creds)
numInputs := len(tx.Ins) + len(tx.Ops)
if numCreds != numInputs {
return fmt.Errorf("%w: %d != %d",
errWrongNumberOfCredentials,
numCreds,
numInputs,
)
}
return nil
}
func (v *SyntacticVerifier) ImportTx(tx *txs.ImportTx) error {
if len(tx.ImportedIns) == 0 {
return errNoImportInputs
}
if err := tx.BaseTx.BaseTx.Verify(v.LuxCtx); err != nil {
return err
}
err := lux.VerifyTx(
v.Config.TxFee,
v.FeeAssetID,
[][]*lux.TransferableInput{
tx.Ins,
tx.ImportedIns,
},
[][]*lux.TransferableOutput{tx.Outs},
)
if err != nil {
return err
}
for _, cred := range v.Tx.Creds {
if err := cred.Verify(); err != nil {
return err
}
}
numCreds := len(v.Tx.Creds)
numInputs := len(tx.Ins) + len(tx.ImportedIns)
if numCreds != numInputs {
return fmt.Errorf("%w: %d != %d",
errWrongNumberOfCredentials,
numCreds,
numInputs,
)
}
return nil
}
func (v *SyntacticVerifier) ExportTx(tx *txs.ExportTx) error {
if len(tx.ExportedOuts) == 0 {
return errNoExportOutputs
}
if err := tx.BaseTx.BaseTx.Verify(v.LuxCtx); err != nil {
return err
}
err := lux.VerifyTx(
v.Config.TxFee,
v.FeeAssetID,
[][]*lux.TransferableInput{tx.Ins},
[][]*lux.TransferableOutput{
tx.Outs,
tx.ExportedOuts,
},
)
if err != nil {
return err
}
for _, cred := range v.Tx.Creds {
if err := cred.Verify(); err != nil {
return err
}
}
numCreds := len(v.Tx.Creds)
numInputs := len(tx.Ins)
if numCreds != numInputs {
return fmt.Errorf("%w: %d != %d",
errWrongNumberOfCredentials,
numCreds,
numInputs,
)
}
return nil
}