github.com/ava-labs/avalanchego@v1.11.11/vms/components/avax/utxo_state.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package avax
     5  
     6  import (
     7  	"github.com/prometheus/client_golang/prometheus"
     8  
     9  	"github.com/ava-labs/avalanchego/cache"
    10  	"github.com/ava-labs/avalanchego/cache/metercacher"
    11  	"github.com/ava-labs/avalanchego/codec"
    12  	"github.com/ava-labs/avalanchego/database"
    13  	"github.com/ava-labs/avalanchego/database/linkeddb"
    14  	"github.com/ava-labs/avalanchego/database/prefixdb"
    15  	"github.com/ava-labs/avalanchego/ids"
    16  )
    17  
    18  const (
    19  	utxoCacheSize  = 8192
    20  	indexCacheSize = 64
    21  )
    22  
    23  var (
    24  	utxoPrefix  = []byte("utxo")
    25  	indexPrefix = []byte("index")
    26  )
    27  
    28  // UTXOState is a thin wrapper around a database to provide, caching,
    29  // serialization, and de-serialization for UTXOs.
    30  type UTXOState interface {
    31  	UTXOReader
    32  	UTXOWriter
    33  
    34  	// Checksum returns the current UTXOChecksum.
    35  	Checksum() ids.ID
    36  }
    37  
    38  // UTXOReader is a thin wrapper around a database to provide fetching of UTXOs.
    39  type UTXOReader interface {
    40  	UTXOGetter
    41  
    42  	// UTXOIDs returns the slice of IDs associated with [addr], starting after
    43  	// [previous].
    44  	// If [previous] is not in the list, starts at beginning.
    45  	// Returns at most [limit] IDs.
    46  	UTXOIDs(addr []byte, previous ids.ID, limit int) ([]ids.ID, error)
    47  }
    48  
    49  // UTXOGetter is a thin wrapper around a database to provide fetching of a UTXO.
    50  type UTXOGetter interface {
    51  	// GetUTXO attempts to load a utxo.
    52  	GetUTXO(utxoID ids.ID) (*UTXO, error)
    53  }
    54  
    55  type UTXOAdder interface {
    56  	AddUTXO(utxo *UTXO)
    57  }
    58  
    59  type UTXODeleter interface {
    60  	DeleteUTXO(utxoID ids.ID)
    61  }
    62  
    63  // UTXOWriter is a thin wrapper around a database to provide storage and
    64  // deletion of UTXOs.
    65  type UTXOWriter interface {
    66  	// PutUTXO saves the provided utxo to storage.
    67  	PutUTXO(utxo *UTXO) error
    68  
    69  	// DeleteUTXO deletes the provided utxo.
    70  	DeleteUTXO(utxoID ids.ID) error
    71  }
    72  
    73  type utxoState struct {
    74  	codec codec.Manager
    75  
    76  	// UTXO ID -> *UTXO. If the *UTXO is nil the UTXO doesn't exist
    77  	utxoCache cache.Cacher[ids.ID, *UTXO]
    78  	utxoDB    database.Database
    79  
    80  	indexDB    database.Database
    81  	indexCache cache.Cacher[string, linkeddb.LinkedDB]
    82  
    83  	trackChecksum bool
    84  	checksum      ids.ID
    85  }
    86  
    87  func NewUTXOState(
    88  	db database.Database,
    89  	codec codec.Manager,
    90  	trackChecksum bool,
    91  ) (UTXOState, error) {
    92  	s := &utxoState{
    93  		codec: codec,
    94  
    95  		utxoCache: &cache.LRU[ids.ID, *UTXO]{Size: utxoCacheSize},
    96  		utxoDB:    prefixdb.New(utxoPrefix, db),
    97  
    98  		indexDB:    prefixdb.New(indexPrefix, db),
    99  		indexCache: &cache.LRU[string, linkeddb.LinkedDB]{Size: indexCacheSize},
   100  
   101  		trackChecksum: trackChecksum,
   102  	}
   103  	return s, s.initChecksum()
   104  }
   105  
   106  func NewMeteredUTXOState(
   107  	db database.Database,
   108  	codec codec.Manager,
   109  	metrics prometheus.Registerer,
   110  	trackChecksum bool,
   111  ) (UTXOState, error) {
   112  	utxoCache, err := metercacher.New[ids.ID, *UTXO](
   113  		"utxo_cache",
   114  		metrics,
   115  		&cache.LRU[ids.ID, *UTXO]{Size: utxoCacheSize},
   116  	)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	indexCache, err := metercacher.New[string, linkeddb.LinkedDB](
   122  		"index_cache",
   123  		metrics,
   124  		&cache.LRU[string, linkeddb.LinkedDB]{
   125  			Size: indexCacheSize,
   126  		},
   127  	)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	s := &utxoState{
   133  		codec: codec,
   134  
   135  		utxoCache: utxoCache,
   136  		utxoDB:    prefixdb.New(utxoPrefix, db),
   137  
   138  		indexDB:    prefixdb.New(indexPrefix, db),
   139  		indexCache: indexCache,
   140  
   141  		trackChecksum: trackChecksum,
   142  	}
   143  	return s, s.initChecksum()
   144  }
   145  
   146  func (s *utxoState) GetUTXO(utxoID ids.ID) (*UTXO, error) {
   147  	if utxo, found := s.utxoCache.Get(utxoID); found {
   148  		if utxo == nil {
   149  			return nil, database.ErrNotFound
   150  		}
   151  		return utxo, nil
   152  	}
   153  
   154  	bytes, err := s.utxoDB.Get(utxoID[:])
   155  	if err == database.ErrNotFound {
   156  		s.utxoCache.Put(utxoID, nil)
   157  		return nil, database.ErrNotFound
   158  	}
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	// The key was in the database
   164  	utxo := &UTXO{}
   165  	if _, err := s.codec.Unmarshal(bytes, utxo); err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	s.utxoCache.Put(utxoID, utxo)
   170  	return utxo, nil
   171  }
   172  
   173  func (s *utxoState) PutUTXO(utxo *UTXO) error {
   174  	utxoBytes, err := s.codec.Marshal(codecVersion, utxo)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	utxoID := utxo.InputID()
   180  	s.updateChecksum(utxoID)
   181  
   182  	s.utxoCache.Put(utxoID, utxo)
   183  	if err := s.utxoDB.Put(utxoID[:], utxoBytes); err != nil {
   184  		return err
   185  	}
   186  
   187  	addressable, ok := utxo.Out.(Addressable)
   188  	if !ok {
   189  		return nil
   190  	}
   191  
   192  	addresses := addressable.Addresses()
   193  	for _, addr := range addresses {
   194  		indexList := s.getIndexDB(addr)
   195  		if err := indexList.Put(utxoID[:], nil); err != nil {
   196  			return err
   197  		}
   198  	}
   199  	return nil
   200  }
   201  
   202  func (s *utxoState) DeleteUTXO(utxoID ids.ID) error {
   203  	utxo, err := s.GetUTXO(utxoID)
   204  	if err == database.ErrNotFound {
   205  		return nil
   206  	}
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	s.updateChecksum(utxoID)
   212  
   213  	s.utxoCache.Put(utxoID, nil)
   214  	if err := s.utxoDB.Delete(utxoID[:]); err != nil {
   215  		return err
   216  	}
   217  
   218  	addressable, ok := utxo.Out.(Addressable)
   219  	if !ok {
   220  		return nil
   221  	}
   222  
   223  	addresses := addressable.Addresses()
   224  	for _, addr := range addresses {
   225  		indexList := s.getIndexDB(addr)
   226  		if err := indexList.Delete(utxoID[:]); err != nil {
   227  			return err
   228  		}
   229  	}
   230  	return nil
   231  }
   232  
   233  func (s *utxoState) UTXOIDs(addr []byte, start ids.ID, limit int) ([]ids.ID, error) {
   234  	indexList := s.getIndexDB(addr)
   235  	iter := indexList.NewIteratorWithStart(start[:])
   236  	defer iter.Release()
   237  
   238  	utxoIDs := []ids.ID(nil)
   239  	for len(utxoIDs) < limit && iter.Next() {
   240  		utxoID, err := ids.ToID(iter.Key())
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  		if utxoID == start {
   245  			continue
   246  		}
   247  
   248  		start = ids.Empty
   249  		utxoIDs = append(utxoIDs, utxoID)
   250  	}
   251  	return utxoIDs, iter.Error()
   252  }
   253  
   254  func (s *utxoState) Checksum() ids.ID {
   255  	return s.checksum
   256  }
   257  
   258  func (s *utxoState) getIndexDB(addr []byte) linkeddb.LinkedDB {
   259  	addrStr := string(addr)
   260  	if indexList, exists := s.indexCache.Get(addrStr); exists {
   261  		return indexList
   262  	}
   263  
   264  	indexDB := prefixdb.NewNested(addr, s.indexDB)
   265  	indexList := linkeddb.NewDefault(indexDB)
   266  	s.indexCache.Put(addrStr, indexList)
   267  	return indexList
   268  }
   269  
   270  func (s *utxoState) initChecksum() error {
   271  	if !s.trackChecksum {
   272  		return nil
   273  	}
   274  
   275  	it := s.utxoDB.NewIterator()
   276  	defer it.Release()
   277  
   278  	for it.Next() {
   279  		utxoID, err := ids.ToID(it.Key())
   280  		if err != nil {
   281  			return err
   282  		}
   283  		s.updateChecksum(utxoID)
   284  	}
   285  	return it.Error()
   286  }
   287  
   288  func (s *utxoState) updateChecksum(modifiedID ids.ID) {
   289  	if !s.trackChecksum {
   290  		return
   291  	}
   292  
   293  	s.checksum = s.checksum.XOR(modifiedID)
   294  }