github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/store/store.go (about) 1 package store 2 3 import ( 4 "fmt" 5 "strconv" 6 7 "github.com/gogo/protobuf/proto" 8 ipld "github.com/ipfs/go-ipld-format" 9 10 dbm "github.com/lazyledger/lazyledger-core/libs/db" 11 tmsync "github.com/lazyledger/lazyledger-core/libs/sync" 12 tmstore "github.com/lazyledger/lazyledger-core/proto/tendermint/store" 13 tmproto "github.com/lazyledger/lazyledger-core/proto/tendermint/types" 14 "github.com/lazyledger/lazyledger-core/types" 15 ) 16 17 /* 18 BlockStore is a simple low level store for blocks. 19 20 There are three types of information stored: 21 - BlockMeta: Meta information about each block 22 - Block part: Parts of each block, aggregated w/ PartSet 23 - Commit: The commit part of each block, for gossiping precommit votes 24 25 Currently the precommit signatures are duplicated in the Block parts as 26 well as the Commit. In the future this may change, perhaps by moving 27 the Commit data outside the Block. (TODO) 28 29 The store can be assumed to contain all contiguous blocks between base and height (inclusive). 30 31 // NOTE: BlockStore methods will panic if they encounter errors 32 // deserializing loaded data, indicating probable corruption on disk. 33 */ 34 type BlockStore struct { 35 db dbm.DB 36 37 // mtx guards access to the struct fields listed below it. We rely on the database to enforce 38 // fine-grained concurrency control for its data, and thus this mutex does not apply to 39 // database contents. The only reason for keeping these fields in the struct is that the data 40 // can't efficiently be queried from the database since the key encoding we use is not 41 // lexicographically ordered (see https://github.com/tendermint/tendermint/issues/4567). 42 mtx tmsync.RWMutex 43 base int64 44 height int64 45 46 ipfsDagAPI ipld.DAGService 47 } 48 49 // NewBlockStore returns a new BlockStore with the given DB, 50 // initialized to the last height that was committed to the DB. 51 func NewBlockStore(db dbm.DB, dagAPI ipld.DAGService) *BlockStore { 52 bs := LoadBlockStoreState(db) 53 return &BlockStore{ 54 base: bs.Base, 55 height: bs.Height, 56 db: db, 57 ipfsDagAPI: dagAPI, 58 } 59 } 60 61 // Base returns the first known contiguous block height, or 0 for empty block stores. 62 func (bs *BlockStore) Base() int64 { 63 bs.mtx.RLock() 64 defer bs.mtx.RUnlock() 65 return bs.base 66 } 67 68 // Height returns the last known contiguous block height, or 0 for empty block stores. 69 func (bs *BlockStore) Height() int64 { 70 bs.mtx.RLock() 71 defer bs.mtx.RUnlock() 72 return bs.height 73 } 74 75 // Size returns the number of blocks in the block store. 76 func (bs *BlockStore) Size() int64 { 77 bs.mtx.RLock() 78 defer bs.mtx.RUnlock() 79 if bs.height == 0 { 80 return 0 81 } 82 return bs.height - bs.base + 1 83 } 84 85 // LoadBase atomically loads the base block meta, or returns nil if no base is found. 86 func (bs *BlockStore) LoadBaseMeta() *types.BlockMeta { 87 bs.mtx.RLock() 88 defer bs.mtx.RUnlock() 89 if bs.base == 0 { 90 return nil 91 } 92 return bs.LoadBlockMeta(bs.base) 93 } 94 95 // LoadBlock returns the block with the given height. 96 // If no block is found for that height, it returns nil. 97 func (bs *BlockStore) LoadBlock(height int64) *types.Block { 98 var blockMeta = bs.LoadBlockMeta(height) 99 if blockMeta == nil { 100 return nil 101 } 102 103 pbb := new(tmproto.Block) 104 buf := []byte{} 105 for i := 0; i < int(blockMeta.BlockID.PartSetHeader.Total); i++ { 106 part := bs.LoadBlockPart(height, i) 107 // If the part is missing (e.g. since it has been deleted after we 108 // loaded the block meta) we consider the whole block to be missing. 109 if part == nil { 110 return nil 111 } 112 buf = append(buf, part.Bytes...) 113 } 114 err := proto.Unmarshal(buf, pbb) 115 if err != nil { 116 // NOTE: The existence of meta should imply the existence of the 117 // block. So, make sure meta is only saved after blocks are saved. 118 panic(fmt.Sprintf("Error reading block: %v", err)) 119 } 120 121 block, err := types.BlockFromProto(pbb) 122 if err != nil { 123 panic(fmt.Errorf("error from proto block: %w", err)) 124 } 125 126 return block 127 } 128 129 // LoadBlockByHash returns the block with the given hash. 130 // If no block is found for that hash, it returns nil. 131 // Panics if it fails to parse height associated with the given hash. 132 func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block { 133 bz, err := bs.db.Get(calcBlockHashKey(hash)) 134 if err != nil { 135 panic(err) 136 } 137 if len(bz) == 0 { 138 return nil 139 } 140 141 s := string(bz) 142 height, err := strconv.ParseInt(s, 10, 64) 143 144 if err != nil { 145 panic(fmt.Sprintf("failed to extract height from %s: %v", s, err)) 146 } 147 return bs.LoadBlock(height) 148 } 149 150 // LoadBlockPart returns the Part at the given index 151 // from the block at the given height. 152 // If no part is found for the given height and index, it returns nil. 153 func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part { 154 var pbpart = new(tmproto.Part) 155 156 bz, err := bs.db.Get(calcBlockPartKey(height, index)) 157 if err != nil { 158 panic(err) 159 } 160 if len(bz) == 0 { 161 return nil 162 } 163 164 err = proto.Unmarshal(bz, pbpart) 165 if err != nil { 166 panic(fmt.Errorf("unmarshal to tmproto.Part failed: %w", err)) 167 } 168 part, err := types.PartFromProto(pbpart) 169 if err != nil { 170 panic(fmt.Sprintf("Error reading block part: %v", err)) 171 } 172 173 return part 174 } 175 176 // LoadBlockMeta returns the BlockMeta for the given height. 177 // If no block is found for the given height, it returns nil. 178 func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { 179 var pbbm = new(tmproto.BlockMeta) 180 bz, err := bs.db.Get(calcBlockMetaKey(height)) 181 182 if err != nil { 183 panic(err) 184 } 185 186 if len(bz) == 0 { 187 return nil 188 } 189 190 err = proto.Unmarshal(bz, pbbm) 191 if err != nil { 192 panic(fmt.Errorf("unmarshal to tmproto.BlockMeta: %w", err)) 193 } 194 195 blockMeta, err := types.BlockMetaFromProto(pbbm) 196 if err != nil { 197 panic(fmt.Errorf("error from proto blockMeta: %w", err)) 198 } 199 200 return blockMeta 201 } 202 203 // LoadBlockCommit returns the Commit for the given height. 204 // This commit consists of the +2/3 and other Precommit-votes for block at `height`, 205 // and it comes from the block.LastCommit for `height+1`. 206 // If no commit is found for the given height, it returns nil. 207 func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit { 208 var pbc = new(tmproto.Commit) 209 bz, err := bs.db.Get(calcBlockCommitKey(height)) 210 if err != nil { 211 panic(err) 212 } 213 if len(bz) == 0 { 214 return nil 215 } 216 err = proto.Unmarshal(bz, pbc) 217 if err != nil { 218 panic(fmt.Errorf("error reading block commit: %w", err)) 219 } 220 commit, err := types.CommitFromProto(pbc) 221 if err != nil { 222 panic(fmt.Sprintf("Error reading block commit: %v", err)) 223 } 224 return commit 225 } 226 227 // LoadSeenCommit returns the locally seen Commit for the given height. 228 // This is useful when we've seen a commit, but there has not yet been 229 // a new block at `height + 1` that includes this commit in its block.LastCommit. 230 func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit { 231 var pbc = new(tmproto.Commit) 232 bz, err := bs.db.Get(calcSeenCommitKey(height)) 233 if err != nil { 234 panic(err) 235 } 236 if len(bz) == 0 { 237 return nil 238 } 239 err = proto.Unmarshal(bz, pbc) 240 if err != nil { 241 panic(fmt.Sprintf("error reading block seen commit: %v", err)) 242 } 243 244 commit, err := types.CommitFromProto(pbc) 245 if err != nil { 246 panic(fmt.Errorf("error from proto commit: %w", err)) 247 } 248 return commit 249 } 250 251 // PruneBlocks removes block up to (but not including) a height. It returns number of blocks pruned. 252 func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) { 253 if height <= 0 { 254 return 0, fmt.Errorf("height must be greater than 0") 255 } 256 bs.mtx.RLock() 257 if height > bs.height { 258 bs.mtx.RUnlock() 259 return 0, fmt.Errorf("cannot prune beyond the latest height %v", bs.height) 260 } 261 base := bs.base 262 bs.mtx.RUnlock() 263 if height < base { 264 return 0, fmt.Errorf("cannot prune to height %v, it is lower than base height %v", 265 height, base) 266 } 267 268 pruned := uint64(0) 269 batch := bs.db.NewBatch() 270 defer batch.Close() 271 flush := func(batch dbm.Batch, base int64) error { 272 // We can't trust batches to be atomic, so update base first to make sure noone 273 // tries to access missing blocks. 274 bs.mtx.Lock() 275 bs.base = base 276 bs.mtx.Unlock() 277 bs.saveState() 278 279 err := batch.WriteSync() 280 if err != nil { 281 return fmt.Errorf("failed to prune up to height %v: %w", base, err) 282 } 283 batch.Close() 284 return nil 285 } 286 287 for h := base; h < height; h++ { 288 meta := bs.LoadBlockMeta(h) 289 if meta == nil { // assume already deleted 290 continue 291 } 292 if err := batch.Delete(calcBlockMetaKey(h)); err != nil { 293 return 0, err 294 } 295 if err := batch.Delete(calcBlockHashKey(meta.BlockID.Hash)); err != nil { 296 return 0, err 297 } 298 if err := batch.Delete(calcBlockCommitKey(h)); err != nil { 299 return 0, err 300 } 301 if err := batch.Delete(calcSeenCommitKey(h)); err != nil { 302 return 0, err 303 } 304 for p := 0; p < int(meta.BlockID.PartSetHeader.Total); p++ { 305 if err := batch.Delete(calcBlockPartKey(h, p)); err != nil { 306 return 0, err 307 } 308 } 309 pruned++ 310 311 // flush every 1000 blocks to avoid batches becoming too large 312 if pruned%1000 == 0 && pruned > 0 { 313 err := flush(batch, h) 314 if err != nil { 315 return 0, err 316 } 317 batch = bs.db.NewBatch() 318 defer batch.Close() 319 } 320 } 321 322 err := flush(batch, height) 323 if err != nil { 324 return 0, err 325 } 326 return pruned, nil 327 } 328 329 // SaveBlock persists the given block, blockParts, and seenCommit to the underlying db. 330 // blockParts: Must be parts of the block 331 // seenCommit: The +2/3 precommits that were seen which committed at height. 332 // If all the nodes restart after committing a block, 333 // we need this to reload the precommits to catch-up nodes to the 334 // most recent height. Otherwise they'd stall at H-1. 335 func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) { 336 if block == nil { 337 panic("BlockStore can only save a non-nil block") 338 } 339 340 height := block.Height 341 hash := block.Hash() 342 343 if g, w := height, bs.Height()+1; bs.Base() > 0 && g != w { 344 panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g)) 345 } 346 if !blockParts.IsComplete() { 347 panic("BlockStore can only save complete block part sets") 348 } 349 350 // Save block parts. This must be done before the block meta, since callers 351 // typically load the block meta first as an indication that the block exists 352 // and then go on to load block parts - we must make sure the block is 353 // complete as soon as the block meta is written. 354 for i := 0; i < int(blockParts.Total()); i++ { 355 part := blockParts.GetPart(i) 356 bs.saveBlockPart(height, i, part) 357 } 358 359 // Save block meta 360 blockMeta := types.NewBlockMeta(block, blockParts) 361 pbm, err := blockMeta.ToProto() 362 if err != nil { 363 panic(fmt.Errorf("failed to marshal block meta while saving: %w", err)) 364 } 365 if pbm == nil { 366 panic("nil blockmeta") 367 } 368 metaBytes := mustEncode(pbm) 369 if err := bs.db.Set(calcBlockMetaKey(height), metaBytes); err != nil { 370 panic(err) 371 } 372 if err := bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))); err != nil { 373 panic(err) 374 } 375 376 // Save block commit (duplicate and separate from the Block) 377 pbc := block.LastCommit.ToProto() 378 blockCommitBytes := mustEncode(pbc) 379 if err := bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes); err != nil { 380 panic(err) 381 } 382 383 // Save seen commit (seen +2/3 precommits for block) 384 // NOTE: we can delete this at a later height 385 pbsc := seenCommit.ToProto() 386 seenCommitBytes := mustEncode(pbsc) 387 if err := bs.db.Set(calcSeenCommitKey(height), seenCommitBytes); err != nil { 388 panic(err) 389 } 390 391 // Done! 392 bs.mtx.Lock() 393 bs.height = height 394 if bs.base == 0 { 395 bs.base = height 396 } 397 bs.mtx.Unlock() 398 399 // Save new BlockStoreState descriptor. This also flushes the database. 400 bs.saveState() 401 } 402 403 func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) { 404 pbp, err := part.ToProto() 405 if err != nil { 406 panic(fmt.Errorf("unable to make part into proto: %w", err)) 407 } 408 partBytes := mustEncode(pbp) 409 if err := bs.db.Set(calcBlockPartKey(height, index), partBytes); err != nil { 410 panic(err) 411 } 412 } 413 414 func (bs *BlockStore) saveState() { 415 bs.mtx.RLock() 416 bss := tmstore.BlockStoreState{ 417 Base: bs.base, 418 Height: bs.height, 419 } 420 bs.mtx.RUnlock() 421 SaveBlockStoreState(&bss, bs.db) 422 } 423 424 // SaveSeenCommit saves a seen commit, used by e.g. the state sync reactor when bootstrapping node. 425 func (bs *BlockStore) SaveSeenCommit(height int64, seenCommit *types.Commit) error { 426 pbc := seenCommit.ToProto() 427 seenCommitBytes, err := proto.Marshal(pbc) 428 if err != nil { 429 return fmt.Errorf("unable to marshal commit: %w", err) 430 } 431 return bs.db.Set(calcSeenCommitKey(height), seenCommitBytes) 432 } 433 434 //----------------------------------------------------------------------------- 435 436 func calcBlockMetaKey(height int64) []byte { 437 return []byte(fmt.Sprintf("H:%v", height)) 438 } 439 440 func calcBlockPartKey(height int64, partIndex int) []byte { 441 return []byte(fmt.Sprintf("P:%v:%v", height, partIndex)) 442 } 443 444 func calcBlockCommitKey(height int64) []byte { 445 return []byte(fmt.Sprintf("C:%v", height)) 446 } 447 448 func calcSeenCommitKey(height int64) []byte { 449 return []byte(fmt.Sprintf("SC:%v", height)) 450 } 451 452 func calcBlockHashKey(hash []byte) []byte { 453 return []byte(fmt.Sprintf("BH:%x", hash)) 454 } 455 456 //----------------------------------------------------------------------------- 457 458 var blockStoreKey = []byte("blockStore") 459 460 // SaveBlockStoreState persists the blockStore state to the database. 461 func SaveBlockStoreState(bsj *tmstore.BlockStoreState, db dbm.DB) { 462 bytes, err := proto.Marshal(bsj) 463 if err != nil { 464 panic(fmt.Sprintf("Could not marshal state bytes: %v", err)) 465 } 466 if err := db.SetSync(blockStoreKey, bytes); err != nil { 467 panic(err) 468 } 469 } 470 471 // LoadBlockStoreState returns the BlockStoreState as loaded from disk. 472 // If no BlockStoreState was previously persisted, it returns the zero value. 473 func LoadBlockStoreState(db dbm.DB) tmstore.BlockStoreState { 474 bytes, err := db.Get(blockStoreKey) 475 if err != nil { 476 panic(err) 477 } 478 479 if len(bytes) == 0 { 480 return tmstore.BlockStoreState{ 481 Base: 0, 482 Height: 0, 483 } 484 } 485 486 var bsj tmstore.BlockStoreState 487 if err := proto.Unmarshal(bytes, &bsj); err != nil { 488 panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes)) 489 } 490 491 // Backwards compatibility with persisted data from before Base existed. 492 if bsj.Height > 0 && bsj.Base == 0 { 493 bsj.Base = 1 494 } 495 return bsj 496 } 497 498 // mustEncode proto encodes a proto.message and panics if fails 499 func mustEncode(pb proto.Message) []byte { 500 bz, err := proto.Marshal(pb) 501 if err != nil { 502 panic(fmt.Errorf("unable to marshal: %w", err)) 503 } 504 return bz 505 }