Files
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

298 lines
6.4 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package lux
import (
"errors"
"github.com/luxfi/database"
"github.com/luxfi/database/linkeddb"
"github.com/luxfi/database/prefixdb"
"github.com/luxfi/ids"
"github.com/luxfi/metric"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/cache/metercacher"
)
const (
utxoCacheSize = 8192
indexCacheSize = 64
)
var (
utxoPrefix = []byte("utxo")
indexPrefix = []byte("index")
)
// UTXOState is a thin wrapper around a database to provide, caching,
// serialization, and de-serialization for UTXOs.
type UTXOState interface {
UTXOReader
UTXOWriter
// Checksum returns the current UTXOChecksum.
Checksum() ids.ID
}
// UTXOReader is a thin wrapper around a database to provide fetching of UTXOs.
type UTXOReader interface {
UTXOGetter
// UTXOIDs returns the slice of IDs associated with [addr], starting after
// [previous].
// If [previous] is not in the list, starts at beginning.
// Returns at most [limit] IDs.
UTXOIDs(addr []byte, previous ids.ID, limit int) ([]ids.ID, error)
}
// UTXOGetter is a thin wrapper around a database to provide fetching of a UTXO.
type UTXOGetter interface {
// GetUTXO attempts to load a utxo.
GetUTXO(utxoID ids.ID) (*UTXO, error)
}
type UTXOAdder interface {
AddUTXO(utxo *UTXO)
}
type UTXODeleter interface {
DeleteUTXO(utxoID ids.ID)
}
// UTXOWriter is a thin wrapper around a database to provide storage and
// deletion of UTXOs.
type UTXOWriter interface {
// PutUTXO saves the provided utxo to storage.
PutUTXO(utxo *UTXO) error
// DeleteUTXO deletes the provided utxo.
DeleteUTXO(utxoID ids.ID) error
}
type utxoState struct {
// UTXO ID -> *UTXO. If the *UTXO is nil the UTXO doesn't exist
utxoCache cache.Cacher[ids.ID, *UTXO]
utxoDB database.Database
indexDB database.Database
indexCache cache.Cacher[string, linkeddb.LinkedDB]
trackChecksum bool
checksum ids.ID
}
// NewUTXOState returns a UTXOState backed by ZAP-native wire bytes (no
// codec.Manager): UTXO.Marshal on write, UTXO.Unmarshal on read. The
// fx-aware output dispatch that reconstructs the polymorphic Out is wired
// via this package's init() (utxo_parser.go) plus the stakeable
// LockedOutputHandler registration.
func NewUTXOState(
db database.Database,
trackChecksum bool,
) (UTXOState, error) {
s := &utxoState{
utxoCache: &cache.LRU[ids.ID, *UTXO]{Size: utxoCacheSize},
utxoDB: prefixdb.New(utxoPrefix, db),
indexDB: prefixdb.New(indexPrefix, db),
indexCache: &cache.LRU[string, linkeddb.LinkedDB]{Size: indexCacheSize},
trackChecksum: trackChecksum,
}
return s, s.initChecksum()
}
func NewMeteredUTXOState(
db database.Database,
metrics metric.Registerer,
trackChecksum bool,
) (UTXOState, error) {
registry, ok := metrics.(metric.Registry)
if !ok {
return nil, errors.New("metrics must be a Registry")
}
utxoCache, err := metercacher.New[ids.ID, *UTXO](
"utxo_cache",
registry,
&cache.LRU[ids.ID, *UTXO]{Size: utxoCacheSize},
)
if err != nil {
return nil, err
}
indexCache, err := metercacher.New[string, linkeddb.LinkedDB](
"index_cache",
registry,
&cache.LRU[string, linkeddb.LinkedDB]{
Size: indexCacheSize,
},
)
if err != nil {
return nil, err
}
s := &utxoState{
utxoCache: utxoCache,
utxoDB: prefixdb.New(utxoPrefix, db),
indexDB: prefixdb.New(indexPrefix, db),
indexCache: indexCache,
trackChecksum: trackChecksum,
}
return s, s.initChecksum()
}
func (s *utxoState) GetUTXO(utxoID ids.ID) (*UTXO, error) {
if utxo, found := s.utxoCache.Get(utxoID); found {
if utxo == nil {
return nil, database.ErrNotFound
}
return utxo, nil
}
bytes, err := s.utxoDB.Get(utxoID[:])
if err == database.ErrNotFound {
s.utxoCache.Put(utxoID, nil)
return nil, database.ErrNotFound
}
if err != nil {
return nil, err
}
// The key was in the database — ZAP-native decode (fx-aware Out dispatch).
utxo := &UTXO{}
if err := utxo.Unmarshal(bytes); err != nil {
return nil, err
}
s.utxoCache.Put(utxoID, utxo)
return utxo, nil
}
func (s *utxoState) PutUTXO(utxo *UTXO) error {
// ZAP-native: the same wire bytes flow to disk and across chains. No
// separate codec.Marshal step.
utxoBytes, err := utxo.Marshal()
if err != nil {
return err
}
utxoID := utxo.InputID()
s.updateChecksum(utxoID)
s.utxoCache.Put(utxoID, utxo)
if err := s.utxoDB.Put(utxoID[:], utxoBytes); err != nil {
return err
}
addressable, ok := utxo.Out.(Addressable)
if !ok {
return nil
}
addresses := addressable.Addresses()
for _, addr := range addresses {
indexList := s.getIndexDB(addr)
if err := indexList.Put(utxoID[:], nil); err != nil {
return err
}
}
return nil
}
func (s *utxoState) DeleteUTXO(utxoID ids.ID) error {
utxo, err := s.GetUTXO(utxoID)
if err == database.ErrNotFound {
return nil
}
if err != nil {
return err
}
s.updateChecksum(utxoID)
s.utxoCache.Put(utxoID, nil)
if err := s.utxoDB.Delete(utxoID[:]); err != nil {
return err
}
addressable, ok := utxo.Out.(Addressable)
if !ok {
return nil
}
addresses := addressable.Addresses()
for _, addr := range addresses {
indexList := s.getIndexDB(addr)
if err := indexList.Delete(utxoID[:]); err != nil {
return err
}
}
return nil
}
func (s *utxoState) UTXOIDs(addr []byte, start ids.ID, limit int) ([]ids.ID, error) {
indexList := s.getIndexDB(addr)
iter := indexList.NewIteratorWithStart(start[:])
defer iter.Release()
utxoIDs := []ids.ID(nil)
for len(utxoIDs) < limit && iter.Next() {
utxoID, err := ids.ToID(iter.Key())
if err != nil {
return nil, err
}
if utxoID == start {
continue
}
start = ids.Empty
utxoIDs = append(utxoIDs, utxoID)
}
return utxoIDs, iter.Error()
}
func (s *utxoState) Checksum() ids.ID {
return s.checksum
}
func (s *utxoState) getIndexDB(addr []byte) linkeddb.LinkedDB {
addrStr := string(addr)
if indexList, exists := s.indexCache.Get(addrStr); exists {
return indexList
}
indexDB := prefixdb.NewNested(addr, s.indexDB)
indexList := linkeddb.NewDefault(indexDB)
s.indexCache.Put(addrStr, indexList)
return indexList
}
func (s *utxoState) initChecksum() error {
if !s.trackChecksum {
return nil
}
it := s.utxoDB.NewIterator()
defer it.Release()
for it.Next() {
utxoID, err := ids.ToID(it.Key())
if err != nil {
return err
}
s.updateChecksum(utxoID)
}
return it.Error()
}
func (s *utxoState) updateChecksum(modifiedID ids.ID) {
if !s.trackChecksum {
return
}
s.checksum = s.checksum.XOR(modifiedID)
}