github.com/nmanchovski/burrow@v0.25.0/bcm/blockchain.go (about) 1 // Copyright 2017 Monax Industries Limited 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package bcm 16 17 import ( 18 "bytes" 19 "fmt" 20 "sync" 21 "time" 22 23 "github.com/tendermint/tendermint/types" 24 25 "github.com/hyperledger/burrow/genesis" 26 "github.com/hyperledger/burrow/logging" 27 amino "github.com/tendermint/go-amino" 28 dbm "github.com/tendermint/tendermint/libs/db" 29 ) 30 31 var stateKey = []byte("BlockchainState") 32 33 type BlockchainInfo interface { 34 GenesisHash() []byte 35 GenesisDoc() genesis.GenesisDoc 36 ChainID() string 37 LastBlockHeight() uint64 38 LastBlockTime() time.Time 39 LastCommitTime() time.Time 40 LastCommitDuration() time.Duration 41 LastBlockHash() []byte 42 AppHashAfterLastBlock() []byte 43 // Gets the BlockHash at a height (or nil if no BlockStore mounted or block could not be found) 44 BlockHash(height uint64) []byte 45 // GetBlockHash returns hash of the specific block 46 GetBlockHeader(blockNumber uint64) (*types.Header, error) 47 } 48 49 type Blockchain struct { 50 sync.RWMutex 51 db dbm.DB 52 genesisHash []byte 53 genesisDoc genesis.GenesisDoc 54 chainID string 55 lastBlockHeight uint64 56 lastBlockTime time.Time 57 lastBlockHash []byte 58 lastCommitTime time.Time 59 lastCommitDuration time.Duration 60 appHashAfterLastBlock []byte 61 blockStore *BlockStore 62 } 63 64 var _ BlockchainInfo = &Blockchain{} 65 66 type PersistedState struct { 67 AppHashAfterLastBlock []byte 68 LastBlockHeight uint64 69 GenesisDoc genesis.GenesisDoc 70 } 71 72 func LoadOrNewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc, logger *logging.Logger) (bool, *Blockchain, error) { 73 logger = logger.WithScope("LoadOrNewBlockchain") 74 logger.InfoMsg("Trying to load blockchain state from database", 75 "database_key", stateKey) 76 bc, err := loadBlockchain(db) 77 if err != nil { 78 return false, nil, fmt.Errorf("error loading blockchain state from database: %v", err) 79 } 80 if bc != nil { 81 dbHash := bc.genesisDoc.Hash() 82 argHash := genesisDoc.Hash() 83 if !bytes.Equal(dbHash, argHash) { 84 return false, nil, fmt.Errorf("GenesisDoc passed to LoadOrNewBlockchain has hash: 0x%X, which does not "+ 85 "match the one found in database: 0x%X, database genesis:\n%v\npassed genesis:\n%v\n", 86 argHash, dbHash, bc.genesisDoc.JSONString(), genesisDoc.JSONString()) 87 } 88 return true, bc, nil 89 } 90 91 logger.InfoMsg("No existing blockchain state found in database, making new blockchain") 92 return false, NewBlockchain(db, genesisDoc), nil 93 } 94 95 // Pointer to blockchain state initialised from genesis 96 func NewBlockchain(db dbm.DB, genesisDoc *genesis.GenesisDoc) *Blockchain { 97 bc := &Blockchain{ 98 db: db, 99 genesisHash: genesisDoc.Hash(), 100 genesisDoc: *genesisDoc, 101 chainID: genesisDoc.ChainID(), 102 lastBlockTime: genesisDoc.GenesisTime, 103 appHashAfterLastBlock: genesisDoc.Hash(), 104 } 105 return bc 106 } 107 108 func GetSyncInfo(blockchain BlockchainInfo) *SyncInfo { 109 return &SyncInfo{ 110 LatestBlockHeight: blockchain.LastBlockHeight(), 111 LatestBlockHash: blockchain.LastBlockHash(), 112 LatestAppHash: blockchain.AppHashAfterLastBlock(), 113 LatestBlockTime: blockchain.LastBlockTime(), 114 LatestBlockSeenTime: blockchain.LastCommitTime(), 115 LatestBlockDuration: blockchain.LastCommitDuration(), 116 } 117 } 118 119 func loadBlockchain(db dbm.DB) (*Blockchain, error) { 120 buf := db.Get(stateKey) 121 if len(buf) == 0 { 122 return nil, nil 123 } 124 bc, err := DecodeBlockchain(buf) 125 if err != nil { 126 return nil, err 127 } 128 bc.db = db 129 return bc, nil 130 } 131 132 func (bc *Blockchain) CommitBlock(blockTime time.Time, blockHash, appHash []byte) error { 133 return bc.CommitBlockAtHeight(blockTime, blockHash, appHash, bc.lastBlockHeight+1) 134 } 135 136 func (bc *Blockchain) CommitBlockAtHeight(blockTime time.Time, blockHash, appHash []byte, height uint64) error { 137 bc.Lock() 138 defer bc.Unlock() 139 // Checkpoint on the _previous_ block. If we die, this is where we will resume since we know all intervening state 140 // has been written successfully since we are committing the next block. 141 // If we fall over we can resume a safe committed state and Tendermint will catch us up 142 err := bc.save() 143 if err != nil { 144 return err 145 } 146 bc.lastCommitDuration = blockTime.Sub(bc.lastBlockTime) 147 bc.lastBlockHeight = height 148 bc.lastBlockTime = blockTime 149 bc.lastBlockHash = blockHash 150 bc.appHashAfterLastBlock = appHash 151 bc.lastCommitTime = time.Now().UTC() 152 return nil 153 } 154 155 func (bc *Blockchain) CommitWithAppHash(appHash []byte) error { 156 bc.appHashAfterLastBlock = appHash 157 bc.Lock() 158 defer bc.Unlock() 159 160 return bc.save() 161 } 162 163 func (bc *Blockchain) save() error { 164 if bc.db != nil { 165 encodedState, err := bc.Encode() 166 if err != nil { 167 return err 168 } 169 bc.db.SetSync(stateKey, encodedState) 170 } 171 return nil 172 } 173 174 var cdc = amino.NewCodec() 175 176 func (bc *Blockchain) Encode() ([]byte, error) { 177 persistedState := &PersistedState{ 178 GenesisDoc: bc.genesisDoc, 179 AppHashAfterLastBlock: bc.appHashAfterLastBlock, 180 LastBlockHeight: bc.lastBlockHeight, 181 } 182 encodedState, err := cdc.MarshalBinaryBare(persistedState) 183 if err != nil { 184 return nil, err 185 } 186 return encodedState, nil 187 } 188 189 func DecodeBlockchain(encodedState []byte) (*Blockchain, error) { 190 persistedState := new(PersistedState) 191 err := cdc.UnmarshalBinaryBare(encodedState, persistedState) 192 if err != nil { 193 return nil, err 194 } 195 bc := NewBlockchain(nil, &persistedState.GenesisDoc) 196 //bc.lastBlockHeight = persistedState.LastBlockHeight 197 bc.lastBlockHeight = persistedState.LastBlockHeight 198 bc.appHashAfterLastBlock = persistedState.AppHashAfterLastBlock 199 return bc, nil 200 } 201 202 func (bc *Blockchain) GenesisHash() []byte { 203 return bc.genesisHash 204 } 205 206 func (bc *Blockchain) GenesisDoc() genesis.GenesisDoc { 207 return bc.genesisDoc 208 } 209 210 func (bc *Blockchain) ChainID() string { 211 return bc.chainID 212 } 213 214 func (bc *Blockchain) LastBlockHeight() uint64 { 215 if bc == nil { 216 return 0 217 } 218 bc.RLock() 219 defer bc.RUnlock() 220 return bc.lastBlockHeight 221 } 222 223 func (bc *Blockchain) LastBlockTime() time.Time { 224 bc.RLock() 225 defer bc.RUnlock() 226 return bc.lastBlockTime 227 } 228 229 func (bc *Blockchain) LastCommitTime() time.Time { 230 bc.RLock() 231 defer bc.RUnlock() 232 return bc.lastCommitTime 233 } 234 235 func (bc *Blockchain) LastCommitDuration() time.Duration { 236 bc.RLock() 237 defer bc.RUnlock() 238 return bc.lastCommitDuration 239 } 240 241 func (bc *Blockchain) LastBlockHash() []byte { 242 bc.RLock() 243 defer bc.RUnlock() 244 return bc.lastBlockHash 245 } 246 247 func (bc *Blockchain) AppHashAfterLastBlock() []byte { 248 bc.RLock() 249 defer bc.RUnlock() 250 return bc.appHashAfterLastBlock 251 } 252 253 // Tendermint block access 254 255 func (bc *Blockchain) SetBlockStore(bs *BlockStore) { 256 bc.blockStore = bs 257 } 258 259 func (bc *Blockchain) BlockHash(height uint64) []byte { 260 header, err := bc.GetBlockHeader(height) 261 if err != nil { 262 return nil 263 } 264 return header.Hash() 265 } 266 267 func (bc *Blockchain) GetBlockHeader(height uint64) (*types.Header, error) { 268 const errHeader = "GetBlockHeader():" 269 if bc == nil { 270 return nil, fmt.Errorf("%s could not get block hash because Blockchain has not been given access to "+ 271 "tendermint BlockStore", errHeader) 272 } 273 blockMeta, err := bc.blockStore.BlockMeta(int64(height)) 274 if err != nil { 275 return nil, fmt.Errorf("%s could not get BlockMeta: %v", errHeader, err) 276 } 277 return &blockMeta.Header, nil 278 }