github.com/devwanda/aphelion-staking@v0.33.9/store/store.go (about) 1 package store 2 3 import ( 4 "fmt" 5 "strconv" 6 "sync" 7 8 "github.com/pkg/errors" 9 10 db "github.com/tendermint/tm-db" 11 dbm "github.com/tendermint/tm-db" 12 13 "github.com/devwanda/aphelion-staking/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 sync.RWMutex 37 base int64 38 height int64 39 } 40 41 // NewBlockStore returns a new BlockStore with the given DB, 42 // initialized to the last height that was committed to the DB. 43 func NewBlockStore(db dbm.DB) *BlockStore { 44 bsjson := LoadBlockStoreStateJSON(db) 45 return &BlockStore{ 46 base: bsjson.Base, 47 height: bsjson.Height, 48 db: db, 49 } 50 } 51 52 // Base returns the first known contiguous block height, or 0 for empty block stores. 53 func (bs *BlockStore) Base() int64 { 54 bs.mtx.RLock() 55 defer bs.mtx.RUnlock() 56 return bs.base 57 } 58 59 // Height returns the last known contiguous block height, or 0 for empty block stores. 60 func (bs *BlockStore) Height() int64 { 61 bs.mtx.RLock() 62 defer bs.mtx.RUnlock() 63 return bs.height 64 } 65 66 // Size returns the number of blocks in the block store. 67 func (bs *BlockStore) Size() int64 { 68 bs.mtx.RLock() 69 defer bs.mtx.RUnlock() 70 if bs.height == 0 { 71 return 0 72 } 73 return bs.height - bs.base + 1 74 } 75 76 // LoadBlock returns the block with the given height. 77 // If no block is found for that height, it returns nil. 78 func (bs *BlockStore) LoadBlock(height int64) *types.Block { 79 var blockMeta = bs.LoadBlockMeta(height) 80 if blockMeta == nil { 81 return nil 82 } 83 84 var block = new(types.Block) 85 buf := []byte{} 86 for i := 0; i < blockMeta.BlockID.PartsHeader.Total; i++ { 87 part := bs.LoadBlockPart(height, i) 88 buf = append(buf, part.Bytes...) 89 } 90 err := cdc.UnmarshalBinaryLengthPrefixed(buf, block) 91 if err != nil { 92 // NOTE: The existence of meta should imply the existence of the 93 // block. So, make sure meta is only saved after blocks are saved. 94 panic(errors.Wrap(err, "Error reading block")) 95 } 96 return block 97 } 98 99 // LoadBlockByHash returns the block with the given hash. 100 // If no block is found for that hash, it returns nil. 101 // Panics if it fails to parse height associated with the given hash. 102 func (bs *BlockStore) LoadBlockByHash(hash []byte) *types.Block { 103 bz, err := bs.db.Get(calcBlockHashKey(hash)) 104 if err != nil { 105 panic(err) 106 } 107 if len(bz) == 0 { 108 return nil 109 } 110 111 s := string(bz) 112 height, err := strconv.ParseInt(s, 10, 64) 113 114 if err != nil { 115 panic(errors.Wrapf(err, "failed to extract height from %s", s)) 116 } 117 return bs.LoadBlock(height) 118 } 119 120 // LoadBlockPart returns the Part at the given index 121 // from the block at the given height. 122 // If no part is found for the given height and index, it returns nil. 123 func (bs *BlockStore) LoadBlockPart(height int64, index int) *types.Part { 124 var part = new(types.Part) 125 bz, err := bs.db.Get(calcBlockPartKey(height, index)) 126 if err != nil { 127 panic(err) 128 } 129 if len(bz) == 0 { 130 return nil 131 } 132 err = cdc.UnmarshalBinaryBare(bz, part) 133 if err != nil { 134 panic(errors.Wrap(err, "Error reading block part")) 135 } 136 return part 137 } 138 139 // LoadBlockMeta returns the BlockMeta for the given height. 140 // If no block is found for the given height, it returns nil. 141 func (bs *BlockStore) LoadBlockMeta(height int64) *types.BlockMeta { 142 var blockMeta = new(types.BlockMeta) 143 bz, err := bs.db.Get(calcBlockMetaKey(height)) 144 if err != nil { 145 panic(err) 146 } 147 if len(bz) == 0 { 148 return nil 149 } 150 err = cdc.UnmarshalBinaryBare(bz, blockMeta) 151 if err != nil { 152 panic(errors.Wrap(err, "Error reading block meta")) 153 } 154 return blockMeta 155 } 156 157 // LoadBlockCommit returns the Commit for the given height. 158 // This commit consists of the +2/3 and other Precommit-votes for block at `height`, 159 // and it comes from the block.LastCommit for `height+1`. 160 // If no commit is found for the given height, it returns nil. 161 func (bs *BlockStore) LoadBlockCommit(height int64) *types.Commit { 162 var commit = new(types.Commit) 163 bz, err := bs.db.Get(calcBlockCommitKey(height)) 164 if err != nil { 165 panic(err) 166 } 167 if len(bz) == 0 { 168 return nil 169 } 170 err = cdc.UnmarshalBinaryBare(bz, commit) 171 if err != nil { 172 panic(errors.Wrap(err, "Error reading block commit")) 173 } 174 return commit 175 } 176 177 // LoadSeenCommit returns the locally seen Commit for the given height. 178 // This is useful when we've seen a commit, but there has not yet been 179 // a new block at `height + 1` that includes this commit in its block.LastCommit. 180 func (bs *BlockStore) LoadSeenCommit(height int64) *types.Commit { 181 var commit = new(types.Commit) 182 bz, err := bs.db.Get(calcSeenCommitKey(height)) 183 if err != nil { 184 panic(err) 185 } 186 if len(bz) == 0 { 187 return nil 188 } 189 err = cdc.UnmarshalBinaryBare(bz, commit) 190 if err != nil { 191 panic(errors.Wrap(err, "Error reading block seen commit")) 192 } 193 return commit 194 } 195 196 // PruneBlocks removes block up to (but not including) a height. It returns number of blocks pruned. 197 func (bs *BlockStore) PruneBlocks(height int64) (uint64, error) { 198 if height <= 0 { 199 return 0, fmt.Errorf("height must be greater than 0") 200 } 201 bs.mtx.RLock() 202 if height > bs.height { 203 bs.mtx.RUnlock() 204 return 0, fmt.Errorf("cannot prune beyond the latest height %v", bs.height) 205 } 206 base := bs.base 207 bs.mtx.RUnlock() 208 if height < base { 209 return 0, fmt.Errorf("cannot prune to height %v, it is lower than base height %v", 210 height, base) 211 } 212 213 pruned := uint64(0) 214 batch := bs.db.NewBatch() 215 defer batch.Close() 216 flush := func(batch db.Batch, base int64) error { 217 // We can't trust batches to be atomic, so update base first to make sure noone 218 // tries to access missing blocks. 219 bs.mtx.Lock() 220 bs.base = base 221 bs.mtx.Unlock() 222 bs.saveState() 223 224 err := batch.WriteSync() 225 if err != nil { 226 return fmt.Errorf("failed to prune up to height %v: %w", base, err) 227 } 228 batch.Close() 229 return nil 230 } 231 232 for h := base; h < height; h++ { 233 meta := bs.LoadBlockMeta(h) 234 if meta == nil { // assume already deleted 235 continue 236 } 237 batch.Delete(calcBlockMetaKey(h)) 238 batch.Delete(calcBlockHashKey(meta.BlockID.Hash)) 239 batch.Delete(calcBlockCommitKey(h)) 240 batch.Delete(calcSeenCommitKey(h)) 241 for p := 0; p < meta.BlockID.PartsHeader.Total; p++ { 242 batch.Delete(calcBlockPartKey(h, p)) 243 } 244 pruned++ 245 246 // flush every 1000 blocks to avoid batches becoming too large 247 if pruned%1000 == 0 && pruned > 0 { 248 err := flush(batch, h) 249 if err != nil { 250 return 0, err 251 } 252 batch = bs.db.NewBatch() 253 defer batch.Close() 254 } 255 } 256 257 err := flush(batch, height) 258 if err != nil { 259 return 0, err 260 } 261 return pruned, nil 262 } 263 264 // SaveBlock persists the given block, blockParts, and seenCommit to the underlying db. 265 // blockParts: Must be parts of the block 266 // seenCommit: The +2/3 precommits that were seen which committed at height. 267 // If all the nodes restart after committing a block, 268 // we need this to reload the precommits to catch-up nodes to the 269 // most recent height. Otherwise they'd stall at H-1. 270 func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) { 271 if block == nil { 272 panic("BlockStore can only save a non-nil block") 273 } 274 275 height := block.Height 276 hash := block.Hash() 277 278 if g, w := height, bs.Height()+1; bs.Base() > 0 && g != w { 279 panic(fmt.Sprintf("BlockStore can only save contiguous blocks. Wanted %v, got %v", w, g)) 280 } 281 if !blockParts.IsComplete() { 282 panic(fmt.Sprintf("BlockStore can only save complete block part sets")) 283 } 284 285 // Save block meta 286 blockMeta := types.NewBlockMeta(block, blockParts) 287 metaBytes := cdc.MustMarshalBinaryBare(blockMeta) 288 bs.db.Set(calcBlockMetaKey(height), metaBytes) 289 bs.db.Set(calcBlockHashKey(hash), []byte(fmt.Sprintf("%d", height))) 290 291 // Save block parts 292 for i := 0; i < blockParts.Total(); i++ { 293 part := blockParts.GetPart(i) 294 bs.saveBlockPart(height, i, part) 295 } 296 297 // Save block commit (duplicate and separate from the Block) 298 blockCommitBytes := cdc.MustMarshalBinaryBare(block.LastCommit) 299 bs.db.Set(calcBlockCommitKey(height-1), blockCommitBytes) 300 301 // Save seen commit (seen +2/3 precommits for block) 302 // NOTE: we can delete this at a later height 303 seenCommitBytes := cdc.MustMarshalBinaryBare(seenCommit) 304 bs.db.Set(calcSeenCommitKey(height), seenCommitBytes) 305 306 // Done! 307 bs.mtx.Lock() 308 bs.height = height 309 if bs.base == 0 { 310 bs.base = height 311 } 312 bs.mtx.Unlock() 313 314 // Save new BlockStoreStateJSON descriptor 315 bs.saveState() 316 317 // Flush 318 bs.db.SetSync(nil, nil) 319 } 320 321 func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) { 322 partBytes := cdc.MustMarshalBinaryBare(part) 323 bs.db.Set(calcBlockPartKey(height, index), partBytes) 324 } 325 326 func (bs *BlockStore) saveState() { 327 bs.mtx.RLock() 328 bsJSON := BlockStoreStateJSON{ 329 Base: bs.base, 330 Height: bs.height, 331 } 332 bs.mtx.RUnlock() 333 bsJSON.Save(bs.db) 334 } 335 336 //----------------------------------------------------------------------------- 337 338 func calcBlockMetaKey(height int64) []byte { 339 return []byte(fmt.Sprintf("H:%v", height)) 340 } 341 342 func calcBlockPartKey(height int64, partIndex int) []byte { 343 return []byte(fmt.Sprintf("P:%v:%v", height, partIndex)) 344 } 345 346 func calcBlockCommitKey(height int64) []byte { 347 return []byte(fmt.Sprintf("C:%v", height)) 348 } 349 350 func calcSeenCommitKey(height int64) []byte { 351 return []byte(fmt.Sprintf("SC:%v", height)) 352 } 353 354 func calcBlockHashKey(hash []byte) []byte { 355 return []byte(fmt.Sprintf("BH:%x", hash)) 356 } 357 358 //----------------------------------------------------------------------------- 359 360 var blockStoreKey = []byte("blockStore") 361 362 // BlockStoreStateJSON is the block store state JSON structure. 363 type BlockStoreStateJSON struct { 364 Base int64 `json:"base"` 365 Height int64 `json:"height"` 366 } 367 368 // Save persists the blockStore state to the database as JSON. 369 func (bsj BlockStoreStateJSON) Save(db dbm.DB) { 370 bytes, err := cdc.MarshalJSON(bsj) 371 if err != nil { 372 panic(fmt.Sprintf("Could not marshal state bytes: %v", err)) 373 } 374 db.SetSync(blockStoreKey, bytes) 375 } 376 377 // LoadBlockStoreStateJSON returns the BlockStoreStateJSON as loaded from disk. 378 // If no BlockStoreStateJSON was previously persisted, it returns the zero value. 379 func LoadBlockStoreStateJSON(db dbm.DB) BlockStoreStateJSON { 380 bytes, err := db.Get(blockStoreKey) 381 if err != nil { 382 panic(err) 383 } 384 if len(bytes) == 0 { 385 return BlockStoreStateJSON{ 386 Base: 0, 387 Height: 0, 388 } 389 } 390 bsj := BlockStoreStateJSON{} 391 err = cdc.UnmarshalJSON(bytes, &bsj) 392 if err != nil { 393 panic(fmt.Sprintf("Could not unmarshal bytes: %X", bytes)) 394 } 395 // Backwards compatibility with persisted data from before Base existed. 396 if bsj.Height > 0 && bsj.Base == 0 { 397 bsj.Base = 1 398 } 399 return bsj 400 }