github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/state/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 state 5 6 import ( 7 "errors" 8 "fmt" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 13 "github.com/MetalBlockchain/metalgo/cache" 14 "github.com/MetalBlockchain/metalgo/cache/metercacher" 15 "github.com/MetalBlockchain/metalgo/database" 16 "github.com/MetalBlockchain/metalgo/database/prefixdb" 17 "github.com/MetalBlockchain/metalgo/database/versiondb" 18 "github.com/MetalBlockchain/metalgo/ids" 19 "github.com/MetalBlockchain/metalgo/vms/avm/block" 20 "github.com/MetalBlockchain/metalgo/vms/avm/txs" 21 "github.com/MetalBlockchain/metalgo/vms/components/avax" 22 ) 23 24 const ( 25 txCacheSize = 8192 26 blockIDCacheSize = 8192 27 blockCacheSize = 2048 28 ) 29 30 var ( 31 utxoPrefix = []byte("utxo") 32 txPrefix = []byte("tx") 33 blockIDPrefix = []byte("blockID") 34 blockPrefix = []byte("block") 35 singletonPrefix = []byte("singleton") 36 37 isInitializedKey = []byte{0x00} 38 timestampKey = []byte{0x01} 39 lastAcceptedKey = []byte{0x02} 40 41 _ State = (*state)(nil) 42 ) 43 44 type ReadOnlyChain interface { 45 avax.UTXOGetter 46 47 GetTx(txID ids.ID) (*txs.Tx, error) 48 GetBlockIDAtHeight(height uint64) (ids.ID, error) 49 GetBlock(blkID ids.ID) (block.Block, error) 50 GetLastAccepted() ids.ID 51 GetTimestamp() time.Time 52 } 53 54 type Chain interface { 55 ReadOnlyChain 56 avax.UTXOAdder 57 avax.UTXODeleter 58 59 AddTx(tx *txs.Tx) 60 AddBlock(block block.Block) 61 SetLastAccepted(blkID ids.ID) 62 SetTimestamp(t time.Time) 63 } 64 65 // State persistently maintains a set of UTXOs, transaction, statuses, and 66 // singletons. 67 type State interface { 68 Chain 69 avax.UTXOReader 70 71 IsInitialized() (bool, error) 72 SetInitialized() error 73 74 // InitializeChainState is called after the VM has been linearized. Calling 75 // [GetLastAccepted] or [GetTimestamp] before calling this function will 76 // return uninitialized data. 77 // 78 // Invariant: After the chain is linearized, this function is expected to be 79 // called during startup. 80 InitializeChainState(stopVertexID ids.ID, genesisTimestamp time.Time) error 81 82 // Discard uncommitted changes to the database. 83 Abort() 84 85 // Commit changes to the base database. 86 Commit() error 87 88 // Returns a batch of unwritten changes that, when written, will commit all 89 // pending changes to the base database. 90 CommitBatch() (database.Batch, error) 91 92 // Checksums returns the current TxChecksum and UTXOChecksum. 93 Checksums() (txChecksum ids.ID, utxoChecksum ids.ID) 94 95 Close() error 96 } 97 98 /* 99 * VMDB 100 * |- utxos 101 * | '-- utxoDB 102 * |-. txs 103 * | '-- txID -> tx bytes 104 * |-. blockIDs 105 * | '-- height -> blockID 106 * |-. blocks 107 * | '-- blockID -> block bytes 108 * '-. singletons 109 * |-- initializedKey -> nil 110 * |-- timestampKey -> timestamp 111 * '-- lastAcceptedKey -> lastAccepted 112 */ 113 type state struct { 114 parser block.Parser 115 db *versiondb.Database 116 117 modifiedUTXOs map[ids.ID]*avax.UTXO // map of modified UTXOID -> *UTXO if the UTXO is nil, it has been removed 118 utxoDB database.Database 119 utxoState avax.UTXOState 120 121 addedTxs map[ids.ID]*txs.Tx // map of txID -> *txs.Tx 122 txCache cache.Cacher[ids.ID, *txs.Tx] // cache of txID -> *txs.Tx. If the entry is nil, it is not in the database 123 txDB database.Database 124 125 addedBlockIDs map[uint64]ids.ID // map of height -> blockID 126 blockIDCache cache.Cacher[uint64, ids.ID] // cache of height -> blockID. If the entry is ids.Empty, it is not in the database 127 blockIDDB database.Database 128 129 addedBlocks map[ids.ID]block.Block // map of blockID -> Block 130 blockCache cache.Cacher[ids.ID, block.Block] // cache of blockID -> Block. If the entry is nil, it is not in the database 131 blockDB database.Database 132 133 // [lastAccepted] is the most recently accepted block. 134 lastAccepted, persistedLastAccepted ids.ID 135 timestamp, persistedTimestamp time.Time 136 singletonDB database.Database 137 138 trackChecksum bool 139 txChecksum ids.ID 140 } 141 142 func New( 143 db *versiondb.Database, 144 parser block.Parser, 145 metrics prometheus.Registerer, 146 trackChecksums bool, 147 ) (State, error) { 148 utxoDB := prefixdb.New(utxoPrefix, db) 149 txDB := prefixdb.New(txPrefix, db) 150 blockIDDB := prefixdb.New(blockIDPrefix, db) 151 blockDB := prefixdb.New(blockPrefix, db) 152 singletonDB := prefixdb.New(singletonPrefix, db) 153 154 txCache, err := metercacher.New[ids.ID, *txs.Tx]( 155 "tx_cache", 156 metrics, 157 &cache.LRU[ids.ID, *txs.Tx]{Size: txCacheSize}, 158 ) 159 if err != nil { 160 return nil, err 161 } 162 163 blockIDCache, err := metercacher.New[uint64, ids.ID]( 164 "block_id_cache", 165 metrics, 166 &cache.LRU[uint64, ids.ID]{Size: blockIDCacheSize}, 167 ) 168 if err != nil { 169 return nil, err 170 } 171 172 blockCache, err := metercacher.New[ids.ID, block.Block]( 173 "block_cache", 174 metrics, 175 &cache.LRU[ids.ID, block.Block]{Size: blockCacheSize}, 176 ) 177 if err != nil { 178 return nil, err 179 } 180 181 utxoState, err := avax.NewMeteredUTXOState(utxoDB, parser.Codec(), metrics, trackChecksums) 182 if err != nil { 183 return nil, err 184 } 185 186 s := &state{ 187 parser: parser, 188 db: db, 189 190 modifiedUTXOs: make(map[ids.ID]*avax.UTXO), 191 utxoDB: utxoDB, 192 utxoState: utxoState, 193 194 addedTxs: make(map[ids.ID]*txs.Tx), 195 txCache: txCache, 196 txDB: txDB, 197 198 addedBlockIDs: make(map[uint64]ids.ID), 199 blockIDCache: blockIDCache, 200 blockIDDB: blockIDDB, 201 202 addedBlocks: make(map[ids.ID]block.Block), 203 blockCache: blockCache, 204 blockDB: blockDB, 205 206 singletonDB: singletonDB, 207 208 trackChecksum: trackChecksums, 209 } 210 return s, s.initTxChecksum() 211 } 212 213 func (s *state) GetUTXO(utxoID ids.ID) (*avax.UTXO, error) { 214 if utxo, exists := s.modifiedUTXOs[utxoID]; exists { 215 if utxo == nil { 216 return nil, database.ErrNotFound 217 } 218 return utxo, nil 219 } 220 return s.utxoState.GetUTXO(utxoID) 221 } 222 223 func (s *state) UTXOIDs(addr []byte, start ids.ID, limit int) ([]ids.ID, error) { 224 return s.utxoState.UTXOIDs(addr, start, limit) 225 } 226 227 func (s *state) AddUTXO(utxo *avax.UTXO) { 228 s.modifiedUTXOs[utxo.InputID()] = utxo 229 } 230 231 func (s *state) DeleteUTXO(utxoID ids.ID) { 232 s.modifiedUTXOs[utxoID] = nil 233 } 234 235 func (s *state) GetTx(txID ids.ID) (*txs.Tx, error) { 236 if tx, exists := s.addedTxs[txID]; exists { 237 return tx, nil 238 } 239 if tx, exists := s.txCache.Get(txID); exists { 240 if tx == nil { 241 return nil, database.ErrNotFound 242 } 243 return tx, nil 244 } 245 246 txBytes, err := s.txDB.Get(txID[:]) 247 if err == database.ErrNotFound { 248 s.txCache.Put(txID, nil) 249 return nil, database.ErrNotFound 250 } 251 if err != nil { 252 return nil, err 253 } 254 255 // The key was in the database 256 tx, err := s.parser.ParseGenesisTx(txBytes) 257 if err != nil { 258 return nil, err 259 } 260 261 s.txCache.Put(txID, tx) 262 return tx, nil 263 } 264 265 func (s *state) AddTx(tx *txs.Tx) { 266 txID := tx.ID() 267 s.updateTxChecksum(txID) 268 s.addedTxs[txID] = tx 269 } 270 271 func (s *state) GetBlockIDAtHeight(height uint64) (ids.ID, error) { 272 if blkID, exists := s.addedBlockIDs[height]; exists { 273 return blkID, nil 274 } 275 if blkID, cached := s.blockIDCache.Get(height); cached { 276 if blkID == ids.Empty { 277 return ids.Empty, database.ErrNotFound 278 } 279 280 return blkID, nil 281 } 282 283 heightKey := database.PackUInt64(height) 284 285 blkID, err := database.GetID(s.blockIDDB, heightKey) 286 if err == database.ErrNotFound { 287 s.blockIDCache.Put(height, ids.Empty) 288 return ids.Empty, database.ErrNotFound 289 } 290 if err != nil { 291 return ids.Empty, err 292 } 293 294 s.blockIDCache.Put(height, blkID) 295 return blkID, nil 296 } 297 298 func (s *state) GetBlock(blkID ids.ID) (block.Block, error) { 299 if blk, exists := s.addedBlocks[blkID]; exists { 300 return blk, nil 301 } 302 if blk, cached := s.blockCache.Get(blkID); cached { 303 if blk == nil { 304 return nil, database.ErrNotFound 305 } 306 307 return blk, nil 308 } 309 310 blkBytes, err := s.blockDB.Get(blkID[:]) 311 if err == database.ErrNotFound { 312 s.blockCache.Put(blkID, nil) 313 return nil, database.ErrNotFound 314 } 315 if err != nil { 316 return nil, err 317 } 318 319 blk, err := s.parser.ParseBlock(blkBytes) 320 if err != nil { 321 return nil, err 322 } 323 324 s.blockCache.Put(blkID, blk) 325 return blk, nil 326 } 327 328 func (s *state) AddBlock(block block.Block) { 329 blkID := block.ID() 330 s.addedBlockIDs[block.Height()] = blkID 331 s.addedBlocks[blkID] = block 332 } 333 334 func (s *state) InitializeChainState(stopVertexID ids.ID, genesisTimestamp time.Time) error { 335 lastAccepted, err := database.GetID(s.singletonDB, lastAcceptedKey) 336 if err == database.ErrNotFound { 337 return s.initializeChainState(stopVertexID, genesisTimestamp) 338 } else if err != nil { 339 return err 340 } 341 s.lastAccepted = lastAccepted 342 s.persistedLastAccepted = lastAccepted 343 s.timestamp, err = database.GetTimestamp(s.singletonDB, timestampKey) 344 s.persistedTimestamp = s.timestamp 345 return err 346 } 347 348 func (s *state) initializeChainState(stopVertexID ids.ID, genesisTimestamp time.Time) error { 349 genesis, err := block.NewStandardBlock( 350 stopVertexID, 351 0, 352 genesisTimestamp, 353 nil, 354 s.parser.Codec(), 355 ) 356 if err != nil { 357 return err 358 } 359 360 s.SetLastAccepted(genesis.ID()) 361 s.SetTimestamp(genesis.Timestamp()) 362 s.AddBlock(genesis) 363 return s.Commit() 364 } 365 366 func (s *state) IsInitialized() (bool, error) { 367 return s.singletonDB.Has(isInitializedKey) 368 } 369 370 func (s *state) SetInitialized() error { 371 return s.singletonDB.Put(isInitializedKey, nil) 372 } 373 374 func (s *state) GetLastAccepted() ids.ID { 375 return s.lastAccepted 376 } 377 378 func (s *state) SetLastAccepted(lastAccepted ids.ID) { 379 s.lastAccepted = lastAccepted 380 } 381 382 func (s *state) GetTimestamp() time.Time { 383 return s.timestamp 384 } 385 386 func (s *state) SetTimestamp(t time.Time) { 387 s.timestamp = t 388 } 389 390 func (s *state) Commit() error { 391 defer s.Abort() 392 batch, err := s.CommitBatch() 393 if err != nil { 394 return err 395 } 396 return batch.Write() 397 } 398 399 func (s *state) Abort() { 400 s.db.Abort() 401 } 402 403 func (s *state) CommitBatch() (database.Batch, error) { 404 if err := s.write(); err != nil { 405 return nil, err 406 } 407 return s.db.CommitBatch() 408 } 409 410 func (s *state) Close() error { 411 return errors.Join( 412 s.utxoDB.Close(), 413 s.txDB.Close(), 414 s.blockIDDB.Close(), 415 s.blockDB.Close(), 416 s.singletonDB.Close(), 417 s.db.Close(), 418 ) 419 } 420 421 func (s *state) write() error { 422 return errors.Join( 423 s.writeUTXOs(), 424 s.writeTxs(), 425 s.writeBlockIDs(), 426 s.writeBlocks(), 427 s.writeMetadata(), 428 ) 429 } 430 431 func (s *state) writeUTXOs() error { 432 for utxoID, utxo := range s.modifiedUTXOs { 433 delete(s.modifiedUTXOs, utxoID) 434 435 if utxo != nil { 436 if err := s.utxoState.PutUTXO(utxo); err != nil { 437 return fmt.Errorf("failed to add utxo: %w", err) 438 } 439 } else { 440 if err := s.utxoState.DeleteUTXO(utxoID); err != nil { 441 return fmt.Errorf("failed to remove utxo: %w", err) 442 } 443 } 444 } 445 return nil 446 } 447 448 func (s *state) writeTxs() error { 449 for txID, tx := range s.addedTxs { 450 txID := txID 451 txBytes := tx.Bytes() 452 453 delete(s.addedTxs, txID) 454 s.txCache.Put(txID, tx) 455 if err := s.txDB.Put(txID[:], txBytes); err != nil { 456 return fmt.Errorf("failed to add tx: %w", err) 457 } 458 } 459 return nil 460 } 461 462 func (s *state) writeBlockIDs() error { 463 for height, blkID := range s.addedBlockIDs { 464 heightKey := database.PackUInt64(height) 465 466 delete(s.addedBlockIDs, height) 467 s.blockIDCache.Put(height, blkID) 468 if err := database.PutID(s.blockIDDB, heightKey, blkID); err != nil { 469 return fmt.Errorf("failed to add blockID: %w", err) 470 } 471 } 472 return nil 473 } 474 475 func (s *state) writeBlocks() error { 476 for blkID, blk := range s.addedBlocks { 477 blkID := blkID 478 blkBytes := blk.Bytes() 479 480 delete(s.addedBlocks, blkID) 481 s.blockCache.Put(blkID, blk) 482 if err := s.blockDB.Put(blkID[:], blkBytes); err != nil { 483 return fmt.Errorf("failed to add block: %w", err) 484 } 485 } 486 return nil 487 } 488 489 func (s *state) writeMetadata() error { 490 if !s.persistedTimestamp.Equal(s.timestamp) { 491 if err := database.PutTimestamp(s.singletonDB, timestampKey, s.timestamp); err != nil { 492 return fmt.Errorf("failed to write timestamp: %w", err) 493 } 494 s.persistedTimestamp = s.timestamp 495 } 496 if s.persistedLastAccepted != s.lastAccepted { 497 if err := database.PutID(s.singletonDB, lastAcceptedKey, s.lastAccepted); err != nil { 498 return fmt.Errorf("failed to write last accepted: %w", err) 499 } 500 s.persistedLastAccepted = s.lastAccepted 501 } 502 return nil 503 } 504 505 func (s *state) Checksums() (ids.ID, ids.ID) { 506 return s.txChecksum, s.utxoState.Checksum() 507 } 508 509 func (s *state) initTxChecksum() error { 510 if !s.trackChecksum { 511 return nil 512 } 513 514 txIt := s.txDB.NewIterator() 515 defer txIt.Release() 516 517 for txIt.Next() { 518 txIDBytes := txIt.Key() 519 520 txID, err := ids.ToID(txIDBytes) 521 if err != nil { 522 return err 523 } 524 525 s.updateTxChecksum(txID) 526 } 527 528 return txIt.Error() 529 } 530 531 func (s *state) updateTxChecksum(modifiedID ids.ID) { 532 if !s.trackChecksum { 533 return 534 } 535 536 s.txChecksum = s.txChecksum.XOR(modifiedID) 537 }