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