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