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 }