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  }