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  }