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 }