github.com/MetalBlockchain/metalgo@v1.11.9/chains/atomic/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 atomic
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"slices"
    11  
    12  	"github.com/MetalBlockchain/metalgo/database"
    13  	"github.com/MetalBlockchain/metalgo/database/linkeddb"
    14  	"github.com/MetalBlockchain/metalgo/database/prefixdb"
    15  	"github.com/MetalBlockchain/metalgo/ids"
    16  	"github.com/MetalBlockchain/metalgo/utils/hashing"
    17  	"github.com/MetalBlockchain/metalgo/utils/set"
    18  )
    19  
    20  var (
    21  	errDuplicatePut    = errors.New("duplicate put")
    22  	errDuplicateRemove = errors.New("duplicate remove")
    23  )
    24  
    25  type dbElement struct {
    26  	// Present indicates the value was removed before existing.
    27  	// If set to false, when this element is added to the shared memory, it will
    28  	// be immediately removed.
    29  	// If set to true, then this element will be removed normally when remove is
    30  	// called.
    31  	Present bool `serialize:"true"`
    32  
    33  	// Value is the body of this element.
    34  	Value []byte `serialize:"true"`
    35  
    36  	// Traits are a collection of features that can be used to lookup this
    37  	// element.
    38  	Traits [][]byte `serialize:"true"`
    39  }
    40  
    41  // state is used to maintain a mapping from keys to dbElements and an index that
    42  // maps traits to the keys with those traits.
    43  type state struct {
    44  	// valueDB contains a mapping from key to the corresponding dbElement.
    45  	valueDB database.Database
    46  
    47  	// indexDB stores the trait -> key mappings.
    48  	// To get this mapping, we construct a prefixdb using the trait as a prefix
    49  	// and then construct a linkeddb on the result.
    50  	// The linkeddb contains the keys that the trait maps to as the key and map
    51  	// to nil values.
    52  	indexDB database.Database
    53  }
    54  
    55  // Value returns the Element associated with [key].
    56  func (s *state) Value(key []byte) (*Element, error) {
    57  	value, err := s.loadValue(key)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	// If [key] is indexed, but has been marked as deleted, return
    63  	// [database.ErrNotFound].
    64  	if !value.Present {
    65  		return nil, database.ErrNotFound
    66  	}
    67  
    68  	return &Element{
    69  		Key:    key,
    70  		Value:  value.Value,
    71  		Traits: value.Traits,
    72  	}, nil
    73  }
    74  
    75  // SetValue places the element [e] into the state and maps each of the traits of
    76  // the element to its key, so that the element can be looked up by any of its
    77  // traits.
    78  //
    79  // If the value was preemptively marked as deleted, then the operations cancel
    80  // and the removal marker is deleted.
    81  func (s *state) SetValue(e *Element) error {
    82  	value, err := s.loadValue(e.Key)
    83  	if err == nil {
    84  		// The key was already registered with the state.
    85  
    86  		if !value.Present {
    87  			// This was previously optimistically deleted from the database, so
    88  			// it should be immediately removed.
    89  			return s.valueDB.Delete(e.Key)
    90  		}
    91  
    92  		// This key was written twice, which is invalid
    93  		return fmt.Errorf("%w: Key=0x%x Value=0x%x", errDuplicatePut, e.Key, e.Value)
    94  	}
    95  	if err != database.ErrNotFound {
    96  		// An unexpected error occurred, so we should propagate that error
    97  		return err
    98  	}
    99  
   100  	for _, trait := range e.Traits {
   101  		traitDB := prefixdb.New(trait, s.indexDB)
   102  		traitList := linkeddb.NewDefault(traitDB)
   103  		if err := traitList.Put(e.Key, nil); err != nil {
   104  			return err
   105  		}
   106  	}
   107  
   108  	dbElem := dbElement{
   109  		Present: true,
   110  		Value:   e.Value,
   111  		Traits:  e.Traits,
   112  	}
   113  
   114  	valueBytes, err := Codec.Marshal(CodecVersion, &dbElem)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	return s.valueDB.Put(e.Key, valueBytes)
   119  }
   120  
   121  // RemoveValue removes [key] from the state.
   122  // This operation removes the element associated with the key from both the
   123  // valueDB and the indexDB.
   124  // If [key] has already been marked as removed, an error is returned as the same
   125  // element cannot be removed twice.
   126  // If [key] is not present in the db, then it is marked as deleted so that it
   127  // will be removed immediately when it gets added in the future.
   128  //
   129  // This ensures that we can consume a UTXO before it has been added into shared
   130  // memory in bootstrapping.
   131  // Ex. P-Chain attempts to consume atomic UTXO from the C-Chain in block 100.
   132  // P-Chain executes before the C-Chain, so when bootstrapping it must be able to
   133  // verify and accept this block before the node has processed the block on the
   134  // C-Chain where the atomic UTXO is added to shared memory.
   135  // Additionally, when the C-Chain actually does add the atomic UTXO to shared
   136  // memory, RemoveValue must handle the case that the atomic UTXO was marked as
   137  // deleted before it was actually added.
   138  // To do this, the node essentially adds a tombstone marker when the P-Chain
   139  // consumes the non-existent UTXO, which is deleted when the C-Chain actually
   140  // adds the atomic UTXO to shared memory.
   141  //
   142  // This implies that chains interacting with shared memory must be able to
   143  // generate their chain state without actually performing the read of shared
   144  // memory. Shared memory should only be used to verify that the transition
   145  // being performed is valid. That ensures that such verification can be skipped
   146  // during bootstrapping. It is up to the chain to ensure this based on the
   147  // current engine state.
   148  func (s *state) RemoveValue(key []byte) error {
   149  	value, err := s.loadValue(key)
   150  	if err == database.ErrNotFound {
   151  		// The value doesn't exist, so we should optimistically delete it
   152  		dbElem := dbElement{Present: false}
   153  		valueBytes, err := Codec.Marshal(CodecVersion, &dbElem)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		return s.valueDB.Put(key, valueBytes)
   158  	}
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	// Don't allow the removal of something that was already removed.
   164  	if !value.Present {
   165  		return fmt.Errorf("%w: Key=0x%x", errDuplicateRemove, key)
   166  	}
   167  
   168  	// Remove [key] from the indexDB for each trait that has indexed this key.
   169  	for _, trait := range value.Traits {
   170  		traitDB := prefixdb.New(trait, s.indexDB)
   171  		traitList := linkeddb.NewDefault(traitDB)
   172  		if err := traitList.Delete(key); err != nil {
   173  			return err
   174  		}
   175  	}
   176  	return s.valueDB.Delete(key)
   177  }
   178  
   179  // loadValue retrieves the dbElement corresponding to [key] from the value
   180  // database.
   181  func (s *state) loadValue(key []byte) (*dbElement, error) {
   182  	valueBytes, err := s.valueDB.Get(key)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	// The key was in the database
   188  	value := &dbElement{}
   189  	_, err = Codec.Unmarshal(valueBytes, value)
   190  	return value, err
   191  }
   192  
   193  // getKeys returns up to [limit] keys starting at [startTrait]/[startKey] which
   194  // possess at least one of the specified [traits].
   195  // Returns the set of keys possessing traits and the ending [lastTrait] and
   196  // [lastKey] to use as an index for pagination.
   197  func (s *state) getKeys(traits [][]byte, startTrait, startKey []byte, limit int) ([][]byte, []byte, []byte, error) {
   198  	// Note: We use a reference to [keySet] since otherwise, depending on how
   199  	//       this variable is declared, the map may not be initialized from the
   200  	//       start. The first add to the underlying map of the set would then
   201  	//       result in the map being initialized.
   202  	keySet := set.Set[ids.ID]{}
   203  	keys := [][]byte(nil)
   204  	lastTrait := startTrait
   205  	lastKey := startKey
   206  	// Iterate over the traits in order appending all of the keys that possess
   207  	// the given [traits].
   208  	slices.SortFunc(traits, bytes.Compare)
   209  	for _, trait := range traits {
   210  		switch bytes.Compare(trait, startTrait) {
   211  		case -1:
   212  			continue // Skip the trait, if we have already paginated past it.
   213  		case 1:
   214  			// We are already past [startTrait]/[startKey], so we should now
   215  			// start indexing all of [trait].
   216  			startKey = nil
   217  		}
   218  
   219  		lastTrait = trait
   220  		var err error
   221  		// Add any additional keys that possess [trait] to [keys].
   222  		lastKey, err = s.appendTraitKeys(&keys, &keySet, &limit, trait, startKey)
   223  		if err != nil {
   224  			return nil, nil, nil, err
   225  		}
   226  
   227  		if limit == 0 {
   228  			break
   229  		}
   230  	}
   231  
   232  	// Return the [keys] that we found as well as the index given by [lastTrait]
   233  	// and [lastKey].
   234  	return keys, lastTrait, lastKey, nil
   235  }
   236  
   237  // appendTraitKeys iterates over the indexDB of [trait] starting at [startKey]
   238  // and adds keys that possess [trait] to [keys] until the iteration completes or
   239  // limit hits 0. If a key possesses multiple traits, it will be de-duplicated
   240  // with [keySet].
   241  func (s *state) appendTraitKeys(keys *[][]byte, keySet *set.Set[ids.ID], limit *int, trait, startKey []byte) ([]byte, error) {
   242  	lastKey := startKey
   243  
   244  	// Create the prefixDB for the specified trait.
   245  	traitDB := prefixdb.New(trait, s.indexDB)
   246  	// Interpret [traitDB] as a linkeddb that contains a set of keys.
   247  	traitList := linkeddb.NewDefault(traitDB)
   248  	iter := traitList.NewIteratorWithStart(startKey)
   249  	defer iter.Release()
   250  
   251  	// Iterate over the keys that possess [trait].
   252  	for iter.Next() && *limit > 0 {
   253  		key := iter.Key()
   254  		lastKey = key
   255  
   256  		// Calculate the hash of the key to check against the set and ensure
   257  		// we don't add the same element twice.
   258  		id := hashing.ComputeHash256Array(key)
   259  		if keySet.Contains(id) {
   260  			continue
   261  		}
   262  
   263  		keySet.Add(id)
   264  		*keys = append(*keys, key)
   265  		*limit--
   266  	}
   267  	return lastKey, iter.Error()
   268  }