package blocks import ( "bytes" "encoding/binary" "encoding/json" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/opt" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" ) var ( blockStoreKey = []byte("blockStore") ) //----------------------------------------------------------------------------- type BlockStoreJSON struct { Height uint32 } func (bsj BlockStoreJSON) Save(db *leveldb.DB) { bytes, err := json.Marshal(bsj) if err != nil { Panicf("Could not marshal state bytes: %v", err) } db.Put(blockStoreKey, bytes, nil) } func LoadBlockStoreJSON(db *leveldb.DB) BlockStoreJSON { bytes, err := db.Get(blockStoreKey, nil) if err != nil { Panicf("Could not load BlockStoreJSON from db: %v", err) } if bytes == nil { return BlockStoreJSON{ Height: 0, } } bsj := BlockStoreJSON{} err = json.Unmarshal(bytes, &bsj) if err != nil { Panicf("Could not unmarshal bytes: %X", bytes) } return bsj } //----------------------------------------------------------------------------- /* Simple low level store for blocks, which is actually stored as separte parts (wire format). */ type BlockStore struct { height uint32 db *leveldb.DB } func NewBlockStore(db *leveldb.DB) *BlockStore { bsjson := LoadBlockStoreJSON(db) return &BlockStore{ height: bsjson.Height, db: db, } } // Height() returns the last known contiguous block height. func (bs *BlockStore) Height() uint32 { return bs.height } // LoadBlockPart loads a part of a block. func (bs *BlockStore) LoadBlockPart(height uint32, index uint16) *BlockPart { partBytes, err := bs.db.Get(calcBlockPartKey(height, index), nil) if err != nil { Panicf("Could not load block part: %v", err) } if partBytes == nil { return nil } var n int64 return ReadBlockPart(bytes.NewReader(partBytes), &n, &err) } // Convenience method for loading block parts and merging to a block. func (bs *BlockStore) LoadBlock(height uint32) *Block { // Get the first part. part0 := bs.LoadBlockPart(height, 0) if part0 == nil { return nil } parts := []*BlockPart{part0} for i := uint16(1); i < part0.Total; i++ { part := bs.LoadBlockPart(height, i) if part == nil { Panicf("Failed to retrieve block part %v at height %v", i, height) } parts = append(parts, part) } block, err := BlockPartsToBlock(parts) if err != nil { panic(err) } return block } // NOTE: Assumes that parts as well as the block are valid. See StageBlockParts(). // Writes are synchronous and atomic. func (bs *BlockStore) SaveBlockParts(height uint32, parts []*BlockPart) error { if height != bs.height+1 { return Errorf("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height) } // Save parts batch := new(leveldb.Batch) for _, part := range parts { partBytes := BinaryBytes(part) batch.Put(calcBlockPartKey(uint32(part.Height), uint16(part.Index)), partBytes) } err := bs.db.Write(batch, &opt.WriteOptions{Sync: true}) // Save new BlockStoreJSON descriptor BlockStoreJSON{Height: height}.Save(bs.db) return err } //----------------------------------------------------------------------------- func calcBlockPartKey(height uint32, index uint16) []byte { buf := [11]byte{'B'} binary.BigEndian.PutUint32(buf[1:9], height) binary.BigEndian.PutUint16(buf[9:11], index) return buf[:] }