github.com/ava-labs/avalanchego@v1.11.11/vms/components/chain/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 chain 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 "github.com/prometheus/client_golang/prometheus" 12 13 "github.com/ava-labs/avalanchego/cache" 14 "github.com/ava-labs/avalanchego/cache/metercacher" 15 "github.com/ava-labs/avalanchego/database" 16 "github.com/ava-labs/avalanchego/ids" 17 "github.com/ava-labs/avalanchego/snow/consensus/snowman" 18 "github.com/ava-labs/avalanchego/snow/engine/snowman/block" 19 "github.com/ava-labs/avalanchego/utils/constants" 20 ) 21 22 func cachedBlockSize(_ ids.ID, bw *BlockWrapper) int { 23 return ids.IDLen + len(bw.Bytes()) + 2*constants.PointerOverhead 24 } 25 26 func cachedBlockBytesSize(blockBytes string, _ ids.ID) int { 27 return len(blockBytes) + ids.IDLen 28 } 29 30 // State implements an efficient caching layer used to wrap a VM 31 // implementation. 32 type State struct { 33 // getBlock retrieves a block from the VM's storage. If getBlock returns 34 // a nil error, then the returned block must not have the status Unknown 35 getBlock func(context.Context, ids.ID) (snowman.Block, error) 36 // unmarshals [b] into a block 37 unmarshalBlock func(context.Context, []byte) (snowman.Block, error) 38 batchedUnmarshalBlock func(context.Context, [][]byte) ([]snowman.Block, error) 39 // buildBlock attempts to build a block on top of the currently preferred block 40 // buildBlock should always return a block with status Processing since it should never 41 // create an unknown block, and building on top of the preferred block should never yield 42 // a block that has already been decided. 43 buildBlock func(context.Context) (snowman.Block, error) 44 45 // If nil, [BuildBlockWithContext] returns [BuildBlock]. 46 buildBlockWithContext func(context.Context, *block.Context) (snowman.Block, error) 47 48 // verifiedBlocks is a map of blocks that have been verified and are 49 // therefore currently in consensus. 50 verifiedBlocks map[ids.ID]*BlockWrapper 51 // decidedBlocks is an LRU cache of decided blocks. 52 decidedBlocks cache.Cacher[ids.ID, *BlockWrapper] 53 // unverifiedBlocks is an LRU cache of blocks with status processing 54 // that have not yet passed verification. 55 unverifiedBlocks cache.Cacher[ids.ID, *BlockWrapper] 56 // missingBlocks is an LRU cache of missing blocks 57 missingBlocks cache.Cacher[ids.ID, struct{}] 58 // string([byte repr. of block]) --> the block's ID 59 bytesToIDCache cache.Cacher[string, ids.ID] 60 lastAcceptedBlock *BlockWrapper 61 } 62 63 // Config defines all of the parameters necessary to initialize State 64 type Config struct { 65 // Cache configuration: 66 DecidedCacheSize, MissingCacheSize, UnverifiedCacheSize, BytesToIDCacheSize int 67 68 LastAcceptedBlock snowman.Block 69 GetBlock func(context.Context, ids.ID) (snowman.Block, error) 70 UnmarshalBlock func(context.Context, []byte) (snowman.Block, error) 71 BatchedUnmarshalBlock func(context.Context, [][]byte) ([]snowman.Block, error) 72 BuildBlock func(context.Context) (snowman.Block, error) 73 BuildBlockWithContext func(context.Context, *block.Context) (snowman.Block, error) 74 } 75 76 func (s *State) initialize(config *Config) { 77 s.verifiedBlocks = make(map[ids.ID]*BlockWrapper) 78 s.getBlock = config.GetBlock 79 s.buildBlock = config.BuildBlock 80 s.buildBlockWithContext = config.BuildBlockWithContext 81 s.unmarshalBlock = config.UnmarshalBlock 82 s.batchedUnmarshalBlock = config.BatchedUnmarshalBlock 83 s.lastAcceptedBlock = &BlockWrapper{ 84 Block: config.LastAcceptedBlock, 85 state: s, 86 } 87 s.decidedBlocks.Put(config.LastAcceptedBlock.ID(), s.lastAcceptedBlock) 88 } 89 90 func NewState(config *Config) *State { 91 c := &State{ 92 verifiedBlocks: make(map[ids.ID]*BlockWrapper), 93 decidedBlocks: cache.NewSizedLRU[ids.ID, *BlockWrapper]( 94 config.DecidedCacheSize, 95 cachedBlockSize, 96 ), 97 missingBlocks: &cache.LRU[ids.ID, struct{}]{Size: config.MissingCacheSize}, 98 unverifiedBlocks: cache.NewSizedLRU[ids.ID, *BlockWrapper]( 99 config.UnverifiedCacheSize, 100 cachedBlockSize, 101 ), 102 bytesToIDCache: cache.NewSizedLRU[string, ids.ID]( 103 config.BytesToIDCacheSize, 104 cachedBlockBytesSize, 105 ), 106 } 107 c.initialize(config) 108 return c 109 } 110 111 func NewMeteredState( 112 registerer prometheus.Registerer, 113 config *Config, 114 ) (*State, error) { 115 decidedCache, err := metercacher.New[ids.ID, *BlockWrapper]( 116 "decided_cache", 117 registerer, 118 cache.NewSizedLRU[ids.ID, *BlockWrapper]( 119 config.DecidedCacheSize, 120 cachedBlockSize, 121 ), 122 ) 123 if err != nil { 124 return nil, err 125 } 126 missingCache, err := metercacher.New[ids.ID, struct{}]( 127 "missing_cache", 128 registerer, 129 &cache.LRU[ids.ID, struct{}]{Size: config.MissingCacheSize}, 130 ) 131 if err != nil { 132 return nil, err 133 } 134 unverifiedCache, err := metercacher.New[ids.ID, *BlockWrapper]( 135 "unverified_cache", 136 registerer, 137 cache.NewSizedLRU[ids.ID, *BlockWrapper]( 138 config.UnverifiedCacheSize, 139 cachedBlockSize, 140 ), 141 ) 142 if err != nil { 143 return nil, err 144 } 145 bytesToIDCache, err := metercacher.New[string, ids.ID]( 146 "bytes_to_id_cache", 147 registerer, 148 cache.NewSizedLRU[string, ids.ID]( 149 config.BytesToIDCacheSize, 150 cachedBlockBytesSize, 151 ), 152 ) 153 if err != nil { 154 return nil, err 155 } 156 c := &State{ 157 verifiedBlocks: make(map[ids.ID]*BlockWrapper), 158 decidedBlocks: decidedCache, 159 missingBlocks: missingCache, 160 unverifiedBlocks: unverifiedCache, 161 bytesToIDCache: bytesToIDCache, 162 } 163 c.initialize(config) 164 return c, nil 165 } 166 167 var errSetAcceptedWithProcessing = errors.New("cannot set last accepted block with blocks processing") 168 169 // SetLastAcceptedBlock sets the last accepted block to [lastAcceptedBlock]. 170 // This should be called with an internal block - not a wrapped block returned 171 // from state. 172 // 173 // This also flushes [lastAcceptedBlock] from missingBlocks and unverifiedBlocks 174 // to ensure that their contents stay valid. 175 func (s *State) SetLastAcceptedBlock(lastAcceptedBlock snowman.Block) error { 176 if len(s.verifiedBlocks) != 0 { 177 return fmt.Errorf("%w: %d", errSetAcceptedWithProcessing, len(s.verifiedBlocks)) 178 } 179 180 // [lastAcceptedBlock] is no longer missing or unverified, so we evict it from the corresponding 181 // caches. 182 // 183 // Note: there's no need to evict from the decided blocks cache or bytesToIDCache since their 184 // contents will still be valid. 185 lastAcceptedBlockID := lastAcceptedBlock.ID() 186 s.missingBlocks.Evict(lastAcceptedBlockID) 187 s.unverifiedBlocks.Evict(lastAcceptedBlockID) 188 s.lastAcceptedBlock = &BlockWrapper{ 189 Block: lastAcceptedBlock, 190 state: s, 191 } 192 s.decidedBlocks.Put(lastAcceptedBlockID, s.lastAcceptedBlock) 193 194 return nil 195 } 196 197 // Flush each block cache 198 func (s *State) Flush() { 199 s.decidedBlocks.Flush() 200 s.missingBlocks.Flush() 201 s.unverifiedBlocks.Flush() 202 s.bytesToIDCache.Flush() 203 } 204 205 // GetBlock returns the BlockWrapper as snowman.Block corresponding to [blkID] 206 func (s *State) GetBlock(ctx context.Context, blkID ids.ID) (snowman.Block, error) { 207 if blk, ok := s.getCachedBlock(blkID); ok { 208 return blk, nil 209 } 210 211 if _, ok := s.missingBlocks.Get(blkID); ok { 212 return nil, database.ErrNotFound 213 } 214 215 blk, err := s.getBlock(ctx, blkID) 216 // If getBlock returns [database.ErrNotFound], State considers 217 // this a cacheable miss. 218 if err == database.ErrNotFound { 219 s.missingBlocks.Put(blkID, struct{}{}) 220 return nil, err 221 } else if err != nil { 222 return nil, err 223 } 224 225 // Since this block is not in consensus, addBlockOutsideConsensus 226 // is called to add [blk] to the correct cache. 227 return s.addBlockOutsideConsensus(blk), nil 228 } 229 230 // getCachedBlock checks the caches for [blkID] by priority. Returning 231 // true if [blkID] is found in one of the caches. 232 func (s *State) getCachedBlock(blkID ids.ID) (snowman.Block, bool) { 233 if blk, ok := s.verifiedBlocks[blkID]; ok { 234 return blk, true 235 } 236 237 if blk, ok := s.decidedBlocks.Get(blkID); ok { 238 return blk, true 239 } 240 241 if blk, ok := s.unverifiedBlocks.Get(blkID); ok { 242 return blk, true 243 } 244 245 return nil, false 246 } 247 248 // GetBlockInternal returns the internal representation of [blkID] 249 func (s *State) GetBlockInternal(ctx context.Context, blkID ids.ID) (snowman.Block, error) { 250 wrappedBlk, err := s.GetBlock(ctx, blkID) 251 if err != nil { 252 return nil, err 253 } 254 255 return wrappedBlk.(*BlockWrapper).Block, nil 256 } 257 258 // ParseBlock attempts to parse [b] into an internal Block and adds it to the 259 // appropriate caching layer if successful. 260 func (s *State) ParseBlock(ctx context.Context, b []byte) (snowman.Block, error) { 261 // See if we've cached this block's ID by its byte repr. 262 cachedBlkID, blkIDCached := s.bytesToIDCache.Get(string(b)) 263 if blkIDCached { 264 // See if we have this block cached 265 if cachedBlk, ok := s.getCachedBlock(cachedBlkID); ok { 266 return cachedBlk, nil 267 } 268 } 269 270 // We don't have this block cached by its byte repr. 271 // Parse the block from bytes 272 blk, err := s.unmarshalBlock(ctx, b) 273 if err != nil { 274 return nil, err 275 } 276 blkID := blk.ID() 277 s.bytesToIDCache.Put(string(b), blkID) 278 279 // Only check the caches if we didn't do so above 280 if !blkIDCached { 281 // Check for an existing block, so we can return a unique block 282 // if processing or simply allow this block to be immediately 283 // garbage collected if it is already cached. 284 if cachedBlk, ok := s.getCachedBlock(blkID); ok { 285 return cachedBlk, nil 286 } 287 } 288 289 s.missingBlocks.Evict(blkID) 290 291 // Since this block is not in consensus, addBlockOutsideConsensus 292 // is called to add [blk] to the correct cache. 293 return s.addBlockOutsideConsensus(blk), nil 294 } 295 296 // BatchedParseBlock implements part of the block.BatchedChainVM interface. In 297 // addition to performing all the caching as the ParseBlock function, it 298 // performs at most one call to the underlying VM if [batchedUnmarshalBlock] was 299 // provided. 300 func (s *State) BatchedParseBlock(ctx context.Context, blksBytes [][]byte) ([]snowman.Block, error) { 301 blks := make([]snowman.Block, len(blksBytes)) 302 idWasCached := make([]bool, len(blksBytes)) 303 unparsedBlksBytes := make([][]byte, 0, len(blksBytes)) 304 for i, blkBytes := range blksBytes { 305 // See if we've cached this block's ID by its byte repr. 306 blkID, blkIDCached := s.bytesToIDCache.Get(string(blkBytes)) 307 idWasCached[i] = blkIDCached 308 if !blkIDCached { 309 unparsedBlksBytes = append(unparsedBlksBytes, blkBytes) 310 continue 311 } 312 313 // See if we have this block cached 314 if cachedBlk, ok := s.getCachedBlock(blkID); ok { 315 blks[i] = cachedBlk 316 } else { 317 unparsedBlksBytes = append(unparsedBlksBytes, blkBytes) 318 } 319 } 320 321 if len(unparsedBlksBytes) == 0 { 322 return blks, nil 323 } 324 325 var ( 326 parsedBlks []snowman.Block 327 err error 328 ) 329 if s.batchedUnmarshalBlock != nil { 330 parsedBlks, err = s.batchedUnmarshalBlock(ctx, unparsedBlksBytes) 331 if err != nil { 332 return nil, err 333 } 334 } else { 335 parsedBlks = make([]snowman.Block, len(unparsedBlksBytes)) 336 for i, blkBytes := range unparsedBlksBytes { 337 parsedBlks[i], err = s.unmarshalBlock(ctx, blkBytes) 338 if err != nil { 339 return nil, err 340 } 341 } 342 } 343 344 i := 0 345 for _, blk := range parsedBlks { 346 for ; ; i++ { 347 if blks[i] == nil { 348 break 349 } 350 } 351 352 blkID := blk.ID() 353 if !idWasCached[i] { 354 blkBytes := blk.Bytes() 355 blkBytesStr := string(blkBytes) 356 s.bytesToIDCache.Put(blkBytesStr, blkID) 357 358 // Check for an existing block, so we can return a unique block 359 // if processing or simply allow this block to be immediately 360 // garbage collected if it is already cached. 361 if cachedBlk, ok := s.getCachedBlock(blkID); ok { 362 blks[i] = cachedBlk 363 continue 364 } 365 } 366 367 s.missingBlocks.Evict(blkID) 368 blks[i] = s.addBlockOutsideConsensus(blk) 369 } 370 return blks, nil 371 } 372 373 // BuildBlockWithContext attempts to build a new internal Block, wraps it, and 374 // adds it to the appropriate caching layer if successful. 375 // If [s.buildBlockWithContext] is nil, returns [BuildBlock]. 376 func (s *State) BuildBlockWithContext(ctx context.Context, blockCtx *block.Context) (snowman.Block, error) { 377 if s.buildBlockWithContext == nil { 378 return s.BuildBlock(ctx) 379 } 380 381 blk, err := s.buildBlockWithContext(ctx, blockCtx) 382 if err != nil { 383 return nil, err 384 } 385 386 return s.deduplicate(blk), nil 387 } 388 389 // BuildBlock attempts to build a new internal Block, wraps it, and adds it 390 // to the appropriate caching layer if successful. 391 func (s *State) BuildBlock(ctx context.Context) (snowman.Block, error) { 392 blk, err := s.buildBlock(ctx) 393 if err != nil { 394 return nil, err 395 } 396 397 return s.deduplicate(blk), nil 398 } 399 400 func (s *State) deduplicate(blk snowman.Block) snowman.Block { 401 blkID := blk.ID() 402 // Defensive: buildBlock should not return a block that has already been verified. 403 // If it does, make sure to return the existing reference to the block. 404 if existingBlk, ok := s.getCachedBlock(blkID); ok { 405 return existingBlk 406 } 407 // Evict the produced block from missing blocks in case it was previously 408 // marked as missing. 409 s.missingBlocks.Evict(blkID) 410 411 // wrap the returned block and add it to the correct cache 412 return s.addBlockOutsideConsensus(blk) 413 } 414 415 // addBlockOutsideConsensus adds [blk] to the correct cache and returns 416 // a wrapped version of [blk] 417 // assumes [blk] is a known, non-wrapped block that is not currently 418 // in consensus. [blk] could be either decided or a block that has not yet 419 // been verified and added to consensus. 420 func (s *State) addBlockOutsideConsensus(blk snowman.Block) snowman.Block { 421 wrappedBlk := &BlockWrapper{ 422 Block: blk, 423 state: s, 424 } 425 426 blkID := blk.ID() 427 if blk.Height() <= s.lastAcceptedBlock.Height() { 428 s.decidedBlocks.Put(blkID, wrappedBlk) 429 } else { 430 s.unverifiedBlocks.Put(blkID, wrappedBlk) 431 } 432 433 return wrappedBlk 434 } 435 436 func (s *State) LastAccepted(context.Context) (ids.ID, error) { 437 return s.lastAcceptedBlock.ID(), nil 438 } 439 440 // LastAcceptedBlock returns the last accepted wrapped block 441 func (s *State) LastAcceptedBlock() *BlockWrapper { 442 return s.lastAcceptedBlock 443 } 444 445 // LastAcceptedBlockInternal returns the internal snowman.Block that was last accepted 446 func (s *State) LastAcceptedBlockInternal() snowman.Block { 447 return s.LastAcceptedBlock().Block 448 } 449 450 // IsProcessing returns whether [blkID] is processing in consensus 451 func (s *State) IsProcessing(blkID ids.ID) bool { 452 _, ok := s.verifiedBlocks[blkID] 453 return ok 454 }