mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
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.
298 lines
6.4 KiB
Go
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)
|
|
}
|