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