const (
maxCachedBlockHeaders = 4096
maxCachedBlockTransactions = 1024
+ maxCachedBlockHashes = 8192
+ maxCachedMainChainHashes = 8192
maxCachedVoteResults = 128
)
-type fillBlockHeaderFn func(hash *bc.Hash, height uint64) (*types.BlockHeader, error)
+type fillBlockHeaderFn func(hash *bc.Hash) (*types.BlockHeader, error)
type fillBlockTransactionsFn func(hash *bc.Hash) ([]*types.Tx, error)
+type fillBlockHashesFn func(height uint64) ([]*bc.Hash, error)
+type fillMainChainHashFn func(height uint64) (*bc.Hash, error)
type fillVoteResultFn func(seq uint64) (*state.VoteResult, error)
-func newCache(fillBlockHeader fillBlockHeaderFn, fillBlockTxs fillBlockTransactionsFn, fillVoteResult fillVoteResultFn) cache {
+func newCache(fillBlockHeader fillBlockHeaderFn, fillBlockTxs fillBlockTransactionsFn, fillBlockHashes fillBlockHashesFn, fillMainChainHash fillMainChainHashFn, fillVoteResult fillVoteResultFn) cache {
return cache{
- lruBlockHeaders: common.NewCache(maxCachedBlockHeaders),
- lruBlockTxs: common.NewCache(maxCachedBlockTransactions),
- lruVoteResults: common.NewCache(maxCachedVoteResults),
+ lruBlockHeaders: common.NewCache(maxCachedBlockHeaders),
+ lruBlockTxs: common.NewCache(maxCachedBlockTransactions),
+ lruBlockHashes: common.NewCache(maxCachedBlockHashes),
+ lruMainChainHashes: common.NewCache(maxCachedMainChainHashes),
+ lruVoteResults: common.NewCache(maxCachedVoteResults),
fillBlockHeaderFn: fillBlockHeader,
fillBlockTransactionFn: fillBlockTxs,
+ fillBlockHashesFn: fillBlockHashes,
+ fillMainChainHashFn: fillMainChainHash,
fillVoteResultFn: fillVoteResult,
}
}
type cache struct {
- lruBlockHeaders *common.Cache
- lruBlockTxs *common.Cache
- lruVoteResults *common.Cache
+ lruBlockHeaders *common.Cache
+ lruBlockTxs *common.Cache
+ lruBlockHashes *common.Cache
+ lruMainChainHashes *common.Cache
+ lruVoteResults *common.Cache
- fillBlockHeaderFn func(hash *bc.Hash, height uint64) (*types.BlockHeader, error)
+ fillBlockHeaderFn func(hash *bc.Hash) (*types.BlockHeader, error)
fillBlockTransactionFn func(hash *bc.Hash) ([]*types.Tx, error)
+ fillBlockHashesFn func(uint64) ([]*bc.Hash, error)
+ fillMainChainHashFn func(uint64) (*bc.Hash, error)
fillVoteResultFn func(seq uint64) (*state.VoteResult, error)
sf singleflight.Group
}
-func (c *cache) lookupBlockHeader(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
+func (c *cache) lookupBlockHeader(hash *bc.Hash) (*types.BlockHeader, error) {
if data, ok := c.lruBlockHeaders.Get(*hash); ok {
return data.(*types.BlockHeader), nil
}
blockHeader, err := c.sf.Do("BlockHeader:"+hash.String(), func() (interface{}, error) {
- blockHeader, err := c.fillBlockHeaderFn(hash, height)
+ blockHeader, err := c.fillBlockHeaderFn(hash)
if err != nil {
return nil, err
}
return nil, err
}
- c.lruBlockTxs.Add(hash, blockTxs)
+ c.lruBlockTxs.Add(*hash, blockTxs)
return blockTxs, nil
})
if err != nil {
return voteResult.(*state.VoteResult).Fork(), nil
}
+func (c *cache) lookupMainChainHash(height uint64) (*bc.Hash, error) {
+ if hash, ok := c.lruMainChainHashes.Get(height); ok {
+ return hash.(*bc.Hash), nil
+ }
+
+ heightStr := strconv.FormatUint(height, 10)
+ hash, err := c.sf.Do("BlockHashByHeight:"+heightStr, func() (interface{}, error) {
+ hash, err := c.fillMainChainHashFn(height)
+ if err != nil {
+ return nil, err
+ }
+
+ c.lruMainChainHashes.Add(height, hash)
+ return hash, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return hash.(*bc.Hash), nil
+}
+
+func (c *cache) lookupBlockHashesByHeight(height uint64) ([]*bc.Hash, error) {
+ if hashes, ok := c.lruBlockHashes.Get(height); ok {
+ return hashes.([]*bc.Hash), nil
+ }
+
+ heightStr := strconv.FormatUint(height, 10)
+ hashes, err := c.sf.Do("BlockHashesByHeight:"+heightStr, func() (interface{}, error) {
+ hashes, err := c.fillBlockHashesFn(height)
+ if err != nil {
+ return nil, err
+ }
+
+ c.lruBlockHashes.Add(height, hashes)
+ return hashes, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ return hashes.([]*bc.Hash), nil
+}
+
func (c *cache) removeBlockHeader(blockHeader *types.BlockHeader) {
c.lruBlockHeaders.Remove(blockHeader.Hash())
}
+func (c *cache) removeBlockHashes(height uint64) {
+ c.lruBlockHashes.Remove(height)
+}
+
+func (c *cache) removeMainChainHash(height uint64) {
+ c.lruMainChainHashes.Remove(height)
+}
+
func (c *cache) removeVoteResult(voteResult *state.VoteResult) {
c.lruVoteResults.Remove(voteResult.Seq)
}
},
}
}
+
newVoteResult := func(seq uint64) *state.VoteResult {
return &state.VoteResult{
Seq: seq,
}
}
+
blocks := make(map[bc.Hash]*types.Block)
+ blockHashes := make(map[uint64]*bc.Hash)
+ blockIndexHashes := make(map[uint64][]*bc.Hash)
for i := 0; i < maxCachedBlockHeaders+10; i++ {
block := newBlock(uint64(i))
- blocks[block.Hash()] = block
+ hash := block.Hash()
+ blocks[hash] = block
+ blockHashes[block.Height] = &hash
+ blockIndexHashes[block.Height] = append(blockIndexHashes[block.Height], &hash)
}
+
voteResults := make(map[uint64]*state.VoteResult)
for i := 0; i < maxCachedVoteResults+10; i++ {
voteResult := newVoteResult(uint64(i))
voteResults[voteResult.Seq] = voteResult
}
- fillBlockHeaderFn := func(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
+ fillBlockHeaderFn := func(hash *bc.Hash) (*types.BlockHeader, error) {
return &blocks[*hash].BlockHeader, nil
}
return blocks[*hash].Transactions, nil
}
+ fillBlockHashesFn := func(height uint64) ([]*bc.Hash, error) {
+ return blockIndexHashes[height], nil
+ }
+
+ fillMainChainHashFn := func(height uint64) (*bc.Hash, error) {
+ return blockHashes[height], nil
+ }
+
fillVoteResultFn := func(seq uint64) (*state.VoteResult, error) {
return voteResults[seq], nil
}
- cache := newCache(fillBlockHeaderFn, fillBlockTxsFn, fillVoteResultFn)
-
+ cache := newCache(fillBlockHeaderFn, fillBlockTxsFn, fillBlockHashesFn, fillMainChainHashFn, fillVoteResultFn)
for i := 0; i < maxCachedBlockHeaders+10; i++ {
block := newBlock(uint64(i))
hash := block.Hash()
- cache.lookupBlockHeader(&hash, block.Height)
+ cache.lookupBlockHeader(&hash)
}
for i := 0; i < 10; i++ {
block := newBlock(uint64(i))
hash := block.Hash()
if _, ok := cache.lruBlockHeaders.Get(hash); ok {
- t.Fatalf("find old block")
+ t.Fatalf("find old block header")
}
}
block := newBlock(uint64(i))
hash := block.Hash()
if _, ok := cache.lruBlockHeaders.Get(hash); !ok {
- t.Fatalf("can't find new block")
+ t.Fatalf("can't find new block header")
+ }
+ }
+
+ for i := 0; i < maxCachedBlockTransactions+10; i++ {
+ block := newBlock(uint64(i))
+ hash := block.Hash()
+ cache.lookupBlockTxs(&hash)
+ }
+
+ for i := 0; i < 10; i++ {
+ block := newBlock(uint64(i))
+ hash := block.Hash()
+ if _, ok := cache.lruBlockTxs.Get(hash); ok {
+ t.Fatalf("find old block transactions")
+ }
+ }
+
+ for i := 10; i < maxCachedBlockTransactions+10; i++ {
+ block := newBlock(uint64(i))
+ hash := block.Hash()
+ if _, ok := cache.lruBlockTxs.Get(hash); !ok {
+ t.Fatalf("can't find new block transactions")
+ }
+ }
+
+ for i := 0; i < maxCachedBlockHashes+10; i++ {
+ block := newBlock(uint64(i))
+ cache.lookupBlockHashesByHeight(block.Height)
+ }
+
+ for i := 0; i < 10; i++ {
+ block := newBlock(uint64(i))
+ if _, ok := cache.lruBlockHashes.Get(block.Height); ok {
+ t.Fatalf("find old block Hashes for specified height")
+ }
+ }
+
+ for i := 10; i < maxCachedBlockHashes+10; i++ {
+ block := newBlock(uint64(i))
+ if _, ok := cache.lruBlockHashes.Get(block.Height); !ok {
+ t.Fatalf("can't find new block Hashes for specified height")
+ }
+ }
+
+ for i := 0; i < maxCachedMainChainHashes+10; i++ {
+ block := newBlock(uint64(i))
+ cache.lookupMainChainHash(block.Height)
+ }
+
+ for i := 0; i < 10; i++ {
+ block := newBlock(uint64(i))
+ if _, ok := cache.lruMainChainHashes.Get(block.Height); ok {
+ t.Fatalf("find old main chain block Hash for specified height")
+ }
+ }
+
+ for i := 10; i < maxCachedMainChainHashes+10; i++ {
+ block := newBlock(uint64(i))
+ if _, ok := cache.lruMainChainHashes.Get(block.Height); !ok {
+ t.Fatalf("can't find new main chain block Hash for specified height")
}
}
"github.com/vapor/protocol/state"
)
-const logModule = "leveldb"
+const (
+ // log module
+ logModule = "leveldb"
+ // the byte of colon(:)
+ colon = byte(0x3a)
+)
+
+const (
+ blockStore byte = iota
+ blockHashes
+ blockHeader
+ blockTransactons
+ mainChainIndex
+ txStatus
+ voteResult
+)
var (
- blockStoreKey = []byte("blockStore")
- blockHeaderPrefix = []byte("BH:")
- blockTransactonsPrefix = []byte("BTXS:")
- txStatusPrefix = []byte("BTS:")
- voteResultPrefix = []byte("VR:")
+ blockStoreKey = []byte{blockStore}
+ blockHashesPrefix = []byte{blockHashes, colon}
+ blockHeaderPrefix = []byte{blockHeader, colon}
+ blockTransactonsPrefix = []byte{blockTransactons, colon}
+ mainChainIndexPrefix = []byte{mainChainIndex, colon}
+ txStatusPrefix = []byte{txStatus, colon}
+ voteResultPrefix = []byte{voteResult, colon}
)
func loadBlockStoreStateJSON(db dbm.DB) *protocol.BlockStoreState {
cache cache
}
-func calcBlockHeaderKey(height uint64, hash *bc.Hash) []byte {
+func calcMainChainIndexPrefix(height uint64) []byte {
+ buf := [8]byte{}
+ binary.BigEndian.PutUint64(buf[:], height)
+ return append(mainChainIndexPrefix, buf[:]...)
+}
+
+func calcBlockHashesPrefix(height uint64) []byte {
buf := [8]byte{}
binary.BigEndian.PutUint64(buf[:], height)
- key := append(blockHeaderPrefix, buf[:]...)
- return append(key, hash.Bytes()...)
+ return append(blockHashesPrefix, buf[:]...)
+}
+
+func calcBlockHeaderKey(hash *bc.Hash) []byte {
+ return append(blockHeaderPrefix, hash.Bytes()...)
}
func calcBlockTransactionsKey(hash *bc.Hash) []byte {
return append(voteResultPrefix, buf[:]...)
}
-// GetBlockHeader return the block header by given hash and height
-func GetBlockHeader(db dbm.DB, hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
- binaryBlockHeader := db.Get(calcBlockHeaderKey(height, hash))
+// GetBlockHeader return the block header by given hash
+func GetBlockHeader(db dbm.DB, hash *bc.Hash) (*types.BlockHeader, error) {
+ binaryBlockHeader := db.Get(calcBlockHeaderKey(hash))
if binaryBlockHeader == nil {
return nil, fmt.Errorf("There are no blockHeader with given hash %s", hash.String())
}
- block := &types.Block{}
- if err := block.UnmarshalText(binaryBlockHeader); err != nil {
+ blockHeader := &types.BlockHeader{}
+ if err := blockHeader.UnmarshalText(binaryBlockHeader); err != nil {
return nil, err
}
- return &block.BlockHeader, nil
+ return blockHeader, nil
}
// GetBlockTransactions return the block transactions by given hash
return block.Transactions, nil
}
+// GetBlockHashesByHeight return block hashes by given height
+func GetBlockHashesByHeight(db dbm.DB, height uint64) ([]*bc.Hash, error) {
+ binaryHashes := db.Get(calcBlockHashesPrefix(height))
+ if binaryHashes == nil {
+ return []*bc.Hash{}, nil
+ }
+
+ hashes := []*bc.Hash{}
+ if err := json.Unmarshal(binaryHashes, &hashes); err != nil {
+ return nil, err
+ }
+ return hashes, nil
+}
+
+// GetMainChainHash return BlockHash by given height
+func GetMainChainHash(db dbm.DB, height uint64) (*bc.Hash, error) {
+ binaryHash := db.Get(calcMainChainIndexPrefix(height))
+ if binaryHash == nil {
+ return nil, fmt.Errorf("There are no BlockHash with given height %d", height)
+ }
+
+ hash := &bc.Hash{}
+ if err := hash.UnmarshalText(binaryHash); err != nil {
+ return nil, err
+ }
+ return hash, nil
+}
+
// GetVoteResult return the vote result by given sequence
func GetVoteResult(db dbm.DB, seq uint64) (*state.VoteResult, error) {
data := db.Get(calcVoteResultKey(seq))
// NewStore creates and returns a new Store object.
func NewStore(db dbm.DB) *Store {
- fillBlockHeaderFn := func(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
- return GetBlockHeader(db, hash, height)
+ fillBlockHeaderFn := func(hash *bc.Hash) (*types.BlockHeader, error) {
+ return GetBlockHeader(db, hash)
}
fillBlockTxsFn := func(hash *bc.Hash) ([]*types.Tx, error) {
return GetBlockTransactions(db, hash)
}
+
+ fillBlockHashesFn := func(height uint64) ([]*bc.Hash, error) {
+ return GetBlockHashesByHeight(db, height)
+ }
+
+ fillMainChainHashFn := func(height uint64) (*bc.Hash, error) {
+ return GetMainChainHash(db, height)
+ }
+
fillVoteResultFn := func(seq uint64) (*state.VoteResult, error) {
return GetVoteResult(db, seq)
}
- bc := newCache(fillBlockHeaderFn, fillBlockTxsFn, fillVoteResultFn)
+
+ cache := newCache(fillBlockHeaderFn, fillBlockTxsFn, fillBlockHashesFn, fillMainChainHashFn, fillVoteResultFn)
return &Store{
db: db,
- cache: bc,
+ cache: cache,
}
}
// BlockExist check if the block is stored in disk
-func (s *Store) BlockExist(hash *bc.Hash, height uint64) bool {
- _, err := s.cache.lookupBlockHeader(hash, height)
+func (s *Store) BlockExist(hash *bc.Hash) bool {
+ _, err := s.cache.lookupBlockHeader(hash)
return err == nil
}
// GetBlock return the block by given hash
-func (s *Store) GetBlock(hash *bc.Hash, height uint64) (*types.Block, error) {
- blockHeader, err := s.GetBlockHeader(hash, height)
+func (s *Store) GetBlock(hash *bc.Hash) (*types.Block, error) {
+ blockHeader, err := s.GetBlockHeader(hash)
if err != nil {
return nil, err
}
}
// GetBlockHeader return the BlockHeader by given hash
-func (s *Store) GetBlockHeader(hash *bc.Hash, height uint64) (*types.BlockHeader, error) {
- return s.cache.lookupBlockHeader(hash, height)
+func (s *Store) GetBlockHeader(hash *bc.Hash) (*types.BlockHeader, error) {
+ return s.cache.lookupBlockHeader(hash)
}
// GetBlockTransactions return the Block transactions by given hash
return s.cache.lookupBlockTxs(hash)
}
+// GetBlockHashesByHeight return the block hash by the specified height
+func (s *Store) GetBlockHashesByHeight(height uint64) ([]*bc.Hash, error) {
+ return s.cache.lookupBlockHashesByHeight(height)
+}
+
+// GetMainChainHash return the block hash by the specified height
+func (s *Store) GetMainChainHash(height uint64) (*bc.Hash, error) {
+ return s.cache.lookupMainChainHash(height)
+}
+
// GetStoreStatus return the BlockStoreStateJSON
func (s *Store) GetStoreStatus() *protocol.BlockStoreState {
return loadBlockStoreStateJSON(s.db)
return s.cache.lookupVoteResult(seq)
}
-func (s *Store) LoadBlockIndex(stateBestHeight uint64) (*state.BlockIndex, error) {
- startTime := time.Now()
- blockIndex := state.NewBlockIndex()
- bhIter := s.db.IteratorPrefix(blockHeaderPrefix)
- defer bhIter.Release()
-
- var lastNode *state.BlockNode
- for bhIter.Next() {
- bh := &types.BlockHeader{}
- if err := bh.UnmarshalText(bhIter.Value()); err != nil {
- return nil, err
- }
-
- // If a block with a height greater than the best height of state is added to the index,
- // It may cause a bug that the new block cant not be process properly.
- if bh.Height > stateBestHeight {
- break
- }
-
- var parent *state.BlockNode
- if lastNode == nil || lastNode.Hash == bh.PreviousBlockHash {
- parent = lastNode
- } else {
- parent = blockIndex.GetNode(&bh.PreviousBlockHash)
- }
-
- node, err := state.NewBlockNode(bh, parent)
- if err != nil {
- return nil, err
- }
-
- blockIndex.AddNode(node)
- lastNode = node
- }
-
- log.WithFields(log.Fields{
- "module": logModule,
- "height": stateBestHeight,
- "duration": time.Since(startTime),
- }).Debug("initialize load history block index from database")
- return blockIndex, nil
-}
-
// SaveBlock persists a new block in the protocol.
func (s *Store) SaveBlock(block *types.Block, ts *bc.TransactionStatus) error {
startTime := time.Now()
binaryTxStatus, err := proto.Marshal(ts)
if err != nil {
- return errors.Wrap(err, "marshal block transaction status")
+ return errors.Wrap(err, "Marshal block transaction status")
}
+ blockHashes := []*bc.Hash{}
+ hashes, err := s.GetBlockHashesByHeight(block.Height)
+ if err != nil {
+ return err
+ }
+ blockHashes = append(blockHashes, hashes...)
blockHash := block.Hash()
+ blockHashes = append(blockHashes, &blockHash)
+ binaryBlockHashes, err := json.Marshal(blockHashes)
+ if err != nil {
+ return errors.Wrap(err, "Marshal block hashes")
+ }
+
batch := s.db.NewBatch()
- batch.Set(calcBlockHeaderKey(block.Height, &blockHash), binaryBlockHeader)
+ batch.Set(calcBlockHashesPrefix(block.Height), binaryBlockHashes)
+ batch.Set(calcBlockHeaderKey(&blockHash), binaryBlockHeader)
batch.Set(calcBlockTransactionsKey(&blockHash), binaryBlockTxs)
batch.Set(calcTxStatusKey(&blockHash), binaryTxStatus)
batch.Write()
+ s.cache.removeBlockHashes(block.Height)
log.WithFields(log.Fields{
"module": logModule,
"height": block.Height,
}
blockHash := blockHeader.Hash()
- s.db.Set(calcBlockHeaderKey(blockHeader.Height, &blockHash), binaryBlockHeader)
+ s.db.Set(calcBlockHeaderKey(&blockHash), binaryBlockHeader)
s.cache.removeBlockHeader(blockHeader)
return nil
}
// SaveChainStatus save the core's newest status && delete old status
-func (s *Store) SaveChainStatus(node, irreversibleNode *state.BlockNode, view *state.UtxoViewpoint, voteResults []*state.VoteResult) error {
+func (s *Store) SaveChainStatus(blockHeader, irrBlockHeader *types.BlockHeader, mainBlockHeaders []*types.BlockHeader, view *state.UtxoViewpoint, voteResults []*state.VoteResult) error {
batch := s.db.NewBatch()
if err := saveUtxoView(batch, view); err != nil {
return err
s.cache.removeVoteResult(vote)
}
+ blockHash := blockHeader.Hash()
+ irrBlockHash := irrBlockHeader.Hash()
bytes, err := json.Marshal(protocol.BlockStoreState{
- Height: node.Height,
- Hash: &node.Hash,
- IrreversibleHeight: irreversibleNode.Height,
- IrreversibleHash: &irreversibleNode.Hash,
+ Height: blockHeader.Height,
+ Hash: &blockHash,
+ IrreversibleHeight: irrBlockHeader.Height,
+ IrreversibleHash: &irrBlockHash,
})
if err != nil {
return err
}
-
batch.Set(blockStoreKey, bytes)
+
+ // save main chain blockHeaders
+ for _, bh := range mainBlockHeaders {
+ blockHash := bh.Hash()
+ binaryBlockHash, err := blockHash.MarshalText()
+ if err != nil {
+ return errors.Wrap(err, "Marshal block hash")
+ }
+
+ batch.Set(calcMainChainIndexPrefix(bh.Height), binaryBlockHash)
+ s.cache.removeMainChainHash(bh.Height)
+ }
batch.Write()
return nil
}
"os"
"testing"
- "github.com/vapor/config"
dbm "github.com/vapor/database/leveldb"
"github.com/vapor/database/storage"
"github.com/vapor/protocol"
"github.com/vapor/testutil"
)
-func TestLoadBlockIndex(t *testing.T) {
- config.CommonConfig = config.DefaultConfig()
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
- store := NewStore(testDB)
- defer func() {
- testDB.Close()
- os.RemoveAll("temp")
- }()
-
- block := config.GenesisBlock()
- txStatus := bc.NewTransactionStatus()
-
- if err := store.SaveBlock(block, txStatus); err != nil {
- t.Fatal(err)
- }
-
- for block.Height <= 128 {
- preHash := block.Hash()
- block.PreviousBlockHash = preHash
- block.Height++
- if err := store.SaveBlock(block, txStatus); err != nil {
- t.Fatal(err)
- }
-
- if block.Height%32 != 0 {
- continue
- }
-
- for i := uint64(0); i < block.Height/32; i++ {
- block.Version++
- if err := store.SaveBlock(block, txStatus); err != nil {
- t.Fatal(err)
- }
- }
- }
-
- if _, err := store.LoadBlockIndex(128); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestLoadBlockIndexBestHeight(t *testing.T) {
- cases := []struct {
- blockBestHeight uint64
- stateBestHeight uint64
- }{
- {
- blockBestHeight: 100,
- stateBestHeight: 90,
- },
- {
- blockBestHeight: 100,
- stateBestHeight: 0,
- },
- {
- blockBestHeight: 100,
- stateBestHeight: 100,
- },
- }
-
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
- defer func() {
- testDB.Close()
- os.RemoveAll("temp")
- }()
- store := NewStore(testDB)
- var savedBlocks []types.Block
-
- for _, c := range cases {
- block := config.GenesisBlock()
- txStatus := bc.NewTransactionStatus()
-
- for i := uint64(0); i < c.blockBestHeight; i++ {
- if err := store.SaveBlock(block, txStatus); err != nil {
- t.Fatal(err)
- }
-
- savedBlocks = append(savedBlocks, *block)
- block.PreviousBlockHash = block.Hash()
- block.Height++
- }
-
- index, err := store.LoadBlockIndex(c.stateBestHeight)
- if err != nil {
- t.Fatal(err)
- }
-
- for _, block := range savedBlocks {
- blockHash := block.Hash()
- if block.Height <= c.stateBestHeight != index.BlockExist(&blockHash) {
- t.Errorf("Error in load block index")
- }
- }
- }
-}
-
-func TestLoadBlockIndexEquals(t *testing.T) {
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
- store := NewStore(testDB)
- defer func() {
- testDB.Close()
- os.RemoveAll("temp")
- }()
-
- block := config.GenesisBlock()
- txStatus := bc.NewTransactionStatus()
- expectBlockIndex := state.NewBlockIndex()
- var parent *state.BlockNode
-
- for block.Height <= 100 {
- if err := store.SaveBlock(block, txStatus); err != nil {
- t.Fatal(err)
- }
-
- if block.Height != 0 {
- parent = expectBlockIndex.GetNode(&block.PreviousBlockHash)
- }
-
- node, err := state.NewBlockNode(&block.BlockHeader, parent)
- if err != nil {
- t.Fatal(err)
- }
-
- expectBlockIndex.AddNode(node)
- block.PreviousBlockHash = block.Hash()
- block.Height++
- }
-
- index, err := store.LoadBlockIndex(100)
- if err != nil {
- t.Fatal(err)
- }
-
- if !testutil.DeepEqual(expectBlockIndex, index) {
- t.Errorf("got block index:%v, expect block index:%v", index, expectBlockIndex)
- }
-}
-
func TestSaveChainStatus(t *testing.T) {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
defer func() {
store := NewStore(testDB)
- node := &state.BlockNode{Height: 100, Hash: bc.Hash{V0: 0, V1: 1, V2: 2, V3: 3}}
+ blockHeader := &types.BlockHeader{Height: 100}
+ blockHash := blockHeader.Hash() //Hash: bc.Hash{V0: 0, V1: 1, V2: 2, V3: 3}
view := &state.UtxoViewpoint{
Entries: map[bc.Hash]*storage.UtxoEntry{
bc.Hash{V0: 1, V1: 2, V2: 3, V3: 4}: &storage.UtxoEntry{Type: storage.NormalUTXOType, BlockHeight: 100, Spent: false},
},
}
- if err := store.SaveChainStatus(node, node, view, []*state.VoteResult{}); err != nil {
+ if err := store.SaveChainStatus(blockHeader, blockHeader, []*types.BlockHeader{blockHeader}, view, []*state.VoteResult{}); err != nil {
t.Fatal(err)
}
- expectStatus := &protocol.BlockStoreState{Height: node.Height, Hash: &node.Hash, IrreversibleHeight: node.Height, IrreversibleHash: &node.Hash}
+ expectStatus := &protocol.BlockStoreState{Height: blockHeader.Height, Hash: &blockHash, IrreversibleHeight: blockHeader.Height, IrreversibleHash: &blockHash}
if !testutil.DeepEqual(store.GetStoreStatus(), expectStatus) {
t.Errorf("got block status:%v, expect block status:%v", store.GetStoreStatus(), expectStatus)
}
}()
store := NewStore(testDB)
-
- block := config.GenesisBlock()
+ block := mockGenesisBlock()
status := &bc.TransactionStatus{VerifyStatus: []*bc.TxVerifyResult{{StatusFail: true}}}
if err := store.SaveBlock(block, status); err != nil {
t.Fatal(err)
}
blockHash := block.Hash()
- gotBlock, err := store.GetBlock(&blockHash, block.Height)
+ gotBlock, err := store.GetBlock(&blockHash)
if err != nil {
t.Fatal(err)
}
t.Errorf("got status:%v, expect status:%v", gotStatus, status)
}
- data := store.db.Get(calcBlockHeaderKey(block.Height, &blockHash))
+ data := store.db.Get(calcBlockHeaderKey(&blockHash))
gotBlockHeader := types.BlockHeader{}
if err := gotBlockHeader.UnmarshalText(data); err != nil {
t.Fatal(err)
t.Errorf("got block header:%v, expect block header:%v", gotBlockHeader, block.BlockHeader)
}
}
+
+func mockGenesisBlock() *types.Block {
+ txData := types.TxData{
+ Version: 1,
+ Inputs: []*types.TxInput{
+ types.NewCoinbaseInput([]byte("Information is power. -- Jan/11/2013. Computing is power. -- Apr/24/2018.")),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewVoteOutput(bc.AssetID{V0: 1}, uint64(10000), []byte{0x51}, []byte{0x51}),
+ },
+ }
+ tx := types.NewTx(txData)
+ txStatus := bc.NewTransactionStatus()
+ txStatus.SetStatus(0, false)
+ txStatusHash, _ := types.TxStatusMerkleRoot(txStatus.VerifyStatus)
+ merkleRoot, _ := types.TxMerkleRoot([]*bc.Tx{tx.Tx})
+ block := &types.Block{
+ BlockHeader: types.BlockHeader{
+ Version: 1,
+ Height: 0,
+ Timestamp: 1528945000,
+ BlockCommitment: types.BlockCommitment{
+ TransactionsMerkleRoot: merkleRoot,
+ TransactionStatusHash: txStatusHash,
+ },
+ },
+ Transactions: []*types.Tx{tx},
+ }
+ return block
+}
return fmt.Sprintf("%s:%s", blockHash, pubkey)
}
-func (c *Chain) isIrreversible(blockNode *state.BlockNode) bool {
- consensusNodes, err := c.consensusNodeManager.getConsensusNodes(&blockNode.Parent.Hash)
+func (c *Chain) isIrreversible(blockHeader *types.BlockHeader) bool {
+ consensusNodes, err := c.getConsensusNodes(&blockHeader.PreviousBlockHash)
if err != nil {
return false
}
signCount := 0
for i := 0; i < len(consensusNodes); i++ {
- if ok, _ := blockNode.BlockWitness.Test(uint32(i)); ok {
+ if blockHeader.BlockWitness.Get(uint64(i)) != nil {
signCount++
}
}
// GetVoteResultByHash return vote result by block hash
func (c *Chain) GetVoteResultByHash(blockHash *bc.Hash) (*state.VoteResult, error) {
- blockNode := c.index.GetNode(blockHash)
- return c.consensusNodeManager.getVoteResult(state.CalcVoteSeq(blockNode.Height), blockNode)
+ blockHeader, err := c.store.GetBlockHeader(blockHash)
+ if err != nil {
+ return nil, err
+ }
+ return c.getVoteResult(state.CalcVoteSeq(blockHeader.Height), blockHeader)
}
// IsBlocker returns whether the consensus node is a blocker at the specified time
func (c *Chain) IsBlocker(prevBlockHash *bc.Hash, pubKey string, timeStamp uint64) (bool, error) {
- xPub, err := c.consensusNodeManager.getBlocker(prevBlockHash, timeStamp)
+ xPub, err := c.GetBlocker(prevBlockHash, timeStamp)
if err != nil {
return false, err
}
return xPub == pubKey, nil
}
-// GetBlock return blocker by specified timestamp
-func (c *Chain) GetBlocker(prevBlockHash *bc.Hash, timestamp uint64) (string, error) {
- return c.consensusNodeManager.getBlocker(prevBlockHash, timestamp)
-}
-
// ProcessBlockSignature process the received block signature messages
// return whether a block become irreversible, if so, the chain module must update status
func (c *Chain) ProcessBlockSignature(signature, xPub []byte, blockHash *bc.Hash) error {
xpubStr := hex.EncodeToString(xPub[:])
- blockNode := c.index.GetNode(blockHash)
+ blockHeader, _ := c.store.GetBlockHeader(blockHash)
+
// save the signature if the block is not exist
- if blockNode == nil {
+ if blockHeader == nil {
cacheKey := signCacheKey(blockHash.String(), xpubStr)
c.signatureCache.Add(cacheKey, signature)
return nil
}
- consensusNode, err := c.consensusNodeManager.getConsensusNode(&blockNode.Parent.Hash, xpubStr)
+ consensusNode, err := c.getConsensusNode(&blockHeader.PreviousBlockHash, xpubStr)
if err != nil {
return err
}
- if exist, _ := blockNode.BlockWitness.Test(uint32(consensusNode.Order)); exist {
+ if blockHeader.BlockWitness.Get(consensusNode.Order) != nil {
return nil
}
c.cond.L.Lock()
defer c.cond.L.Unlock()
- if err := c.checkNodeSign(blockNode.BlockHeader(), consensusNode, signature); err != nil {
+ if err := c.checkNodeSign(blockHeader, consensusNode, signature); err != nil {
return err
}
- if err := c.updateBlockSignature(blockNode, consensusNode.Order, signature); err != nil {
+ if err := c.updateBlockSignature(blockHeader, consensusNode.Order, signature); err != nil {
return err
}
return c.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: *blockHash, Signature: signature, XPub: xPub})
// if some signature is invalid, they will be reset to nil
// if the block has not the signature of blocker, it will return error
func (c *Chain) validateSign(block *types.Block) error {
- consensusNodeMap, err := c.consensusNodeManager.getConsensusNodes(&block.PreviousBlockHash)
+ consensusNodeMap, err := c.getConsensusNodes(&block.PreviousBlockHash)
if err != nil {
return err
}
return errInvalidSignature
}
- blockNodes := c.consensusNodeManager.blockIndex.NodesByHeight(bh.Height)
- for _, blockNode := range blockNodes {
- if blockNode.Hash == bh.Hash() {
+ blockHashes, err := c.store.GetBlockHashesByHeight(bh.Height)
+ if err != nil {
+ return err
+ }
+
+ for _, blockHash := range blockHashes {
+ if *blockHash == bh.Hash() {
continue
}
- consensusNode, err := c.consensusNodeManager.getConsensusNode(&blockNode.Parent.Hash, consensusNode.XPub.String())
+ blockHeader, err := c.store.GetBlockHeader(blockHash)
+ if err != nil {
+ return err
+ }
+
+ consensusNode, err := c.getConsensusNode(&blockHeader.PreviousBlockHash, consensusNode.XPub.String())
if err != nil && err != errNotFoundConsensusNode {
return err
}
continue
}
- if ok, err := blockNode.BlockWitness.Test(uint32(consensusNode.Order)); err == nil && ok {
+ if blockHeader.BlockWitness.Get(consensusNode.Order) != nil {
return errDoubleSignBlock
}
}
func (c *Chain) SignBlock(block *types.Block) ([]byte, error) {
xprv := config.CommonConfig.PrivateKey()
xpubStr := xprv.XPub().String()
- node, err := c.consensusNodeManager.getConsensusNode(&block.PreviousBlockHash, xpubStr)
+ node, err := c.getConsensusNode(&block.PreviousBlockHash, xpubStr)
if err == errNotFoundConsensusNode {
return nil, nil
} else if err != nil {
return nil, err
}
- c.cond.L.Lock()
- defer c.cond.L.Unlock()
//check double sign in same block height
- blockNodes := c.consensusNodeManager.blockIndex.NodesByHeight(block.Height)
- for _, blockNode := range blockNodes {
- // Has already signed the same height block
- if ok, err := blockNode.BlockWitness.Test(uint32(node.Order)); err == nil && ok {
- return nil, nil
- }
+ blockHashes, err := c.store.GetBlockHashesByHeight(block.Height)
+ if err != nil {
+ return nil, err
}
- for blockNode := c.index.GetNode(&block.PreviousBlockHash); !c.index.InMainchain(blockNode.Hash); blockNode = blockNode.Parent {
- if blockNode.Height <= c.bestIrreversibleNode.Height {
- return nil, errSignForkChain
+ for _, hash := range blockHashes {
+ blockHeader, err := c.store.GetBlockHeader(hash)
+ if err != nil {
+ return nil, err
+ }
+
+ // Has already signed the same height block
+ if blockHeader.BlockWitness.Get(node.Order) != nil {
+ return nil, nil
}
}
return signature, nil
}
-func (c *Chain) updateBlockSignature(blockNode *state.BlockNode, nodeOrder uint64, signature []byte) error {
- if err := blockNode.BlockWitness.Set(uint32(nodeOrder)); err != nil {
- return err
- }
-
- blockHeader, err := c.store.GetBlockHeader(&blockNode.Hash, blockNode.Height)
- if err != nil {
- return err
- }
-
+func (c *Chain) updateBlockSignature(blockHeader *types.BlockHeader, nodeOrder uint64, signature []byte) error {
blockHeader.Set(nodeOrder, signature)
-
if err := c.store.SaveBlockHeader(blockHeader); err != nil {
return err
}
- if c.isIrreversible(blockNode) && blockNode.Height > c.bestIrreversibleNode.Height {
- if err := c.store.SaveChainStatus(c.bestNode, blockNode, state.NewUtxoViewpoint(), []*state.VoteResult{}); err != nil {
+ if c.isIrreversible(blockHeader) && blockHeader.Height > c.bestIrrBlockHeader.Height {
+ if err := c.store.SaveChainStatus(c.bestBlockHeader, blockHeader, []*types.BlockHeader{}, state.NewUtxoViewpoint(), []*state.VoteResult{}); err != nil {
return err
}
-
- c.bestIrreversibleNode = blockNode
+ c.bestIrrBlockHeader = blockHeader
}
return nil
}
ErrBadBlock = errors.New("invalid block")
// ErrBadStateRoot is returned when the computed assets merkle root
// disagrees with the one declared in a block header.
- ErrBadStateRoot = errors.New("invalid state merkle root")
- errBelowIrreversibleBlock = errors.New("the height of block below the height of irreversible block")
+ ErrBadStateRoot = errors.New("invalid state merkle root")
)
// BlockExist check is a block in chain or orphan
func (c *Chain) BlockExist(hash *bc.Hash) bool {
- return c.index.BlockExist(hash) || c.orphanManage.BlockExist(hash)
+ if _, err := c.store.GetBlockHeader(hash); err == nil {
+ return true
+ }
+ return c.orphanManage.BlockExist(hash)
}
// GetBlockByHash return a block by given hash
func (c *Chain) GetBlockByHash(hash *bc.Hash) (*types.Block, error) {
- node := c.index.GetNode(hash)
- if node == nil {
- return nil, errors.New("can't find block in given hash")
- }
- return c.store.GetBlock(hash, node.Height)
+ return c.store.GetBlock(hash)
}
-// GetBlockByHeight return a block header by given height
+// GetBlockByHeight return a block by given height
func (c *Chain) GetBlockByHeight(height uint64) (*types.Block, error) {
- node := c.index.NodeByHeight(height)
- if node == nil {
- return nil, errors.New("can't find block in given height")
+ hash, err := c.store.GetMainChainHash(height)
+ if err != nil {
+ return nil, errors.Wrap(err, "can't find block in given height")
}
- return c.store.GetBlock(&node.Hash, height)
+ return c.store.GetBlock(hash)
}
// GetHeaderByHash return a block header by given hash
func (c *Chain) GetHeaderByHash(hash *bc.Hash) (*types.BlockHeader, error) {
- node := c.index.GetNode(hash)
- if node == nil {
- return nil, errors.New("can't find block header in given hash")
- }
- return node.BlockHeader(), nil
+ return c.store.GetBlockHeader(hash)
}
// GetHeaderByHeight return a block header by given height
func (c *Chain) GetHeaderByHeight(height uint64) (*types.BlockHeader, error) {
- node := c.index.NodeByHeight(height)
- if node == nil {
- return nil, errors.New("can't find block header in given height")
+ hash, err := c.store.GetMainChainHash(height)
+ if err != nil {
+ return nil, errors.Wrap(err, "can't find block header in given height")
}
- return node.BlockHeader(), nil
+ return c.store.GetBlockHeader(hash)
}
-func (c *Chain) calcReorganizeNodes(node *state.BlockNode) ([]*state.BlockNode, []*state.BlockNode) {
- var attachNodes []*state.BlockNode
- var detachNodes []*state.BlockNode
+func (c *Chain) calcReorganizeChain(beginAttach *types.BlockHeader, beginDetach *types.BlockHeader) ([]*types.BlockHeader, []*types.BlockHeader, error) {
+ var err error
+ var attachBlockHeaders []*types.BlockHeader
+ var detachBlockHeaders []*types.BlockHeader
- attachNode := node
- for c.index.NodeByHeight(attachNode.Height) != attachNode {
- attachNodes = append([]*state.BlockNode{attachNode}, attachNodes...)
- attachNode = attachNode.Parent
- }
+ for attachBlockHeader, detachBlockHeader := beginAttach, beginDetach; detachBlockHeader.Hash() != attachBlockHeader.Hash(); {
+ var attachRollback, detachRollBack bool
+ if attachRollback = attachBlockHeader.Height >= detachBlockHeader.Height; attachRollback {
+ attachBlockHeaders = append([]*types.BlockHeader{attachBlockHeader}, attachBlockHeaders...)
+ }
- detachNode := c.bestNode
- for detachNode != attachNode {
- detachNodes = append(detachNodes, detachNode)
- detachNode = detachNode.Parent
+ if detachRollBack = attachBlockHeader.Height <= detachBlockHeader.Height; detachRollBack {
+ detachBlockHeaders = append(detachBlockHeaders, detachBlockHeader)
+ }
+
+ if attachRollback {
+ attachBlockHeader, err = c.store.GetBlockHeader(&attachBlockHeader.PreviousBlockHash)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ if detachRollBack {
+ detachBlockHeader, err = c.store.GetBlockHeader(&detachBlockHeader.PreviousBlockHash)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
}
- return attachNodes, detachNodes
+ return attachBlockHeaders, detachBlockHeaders, nil
}
func (c *Chain) connectBlock(block *types.Block) (err error) {
- irreversibleNode := c.bestIrreversibleNode
bcBlock := types.MapBlock(block)
if bcBlock.TransactionStatus, err = c.store.GetTransactionStatus(&bcBlock.ID); err != nil {
return err
return err
}
- voteResult, err := c.consensusNodeManager.getBestVoteResult()
+ voteResult, err := c.getBestVoteResult()
if err != nil {
return err
}
return err
}
- node := c.index.GetNode(&bcBlock.ID)
- if c.isIrreversible(node) && block.Height > irreversibleNode.Height {
- irreversibleNode = node
+ irrBlockHeader := c.bestIrrBlockHeader
+ if c.isIrreversible(&block.BlockHeader) && block.Height > irrBlockHeader.Height {
+ irrBlockHeader = &block.BlockHeader
}
- if err := c.setState(node, irreversibleNode, utxoView, []*state.VoteResult{voteResult}); err != nil {
+ if err := c.setState(&block.BlockHeader, irrBlockHeader, []*types.BlockHeader{&block.BlockHeader}, utxoView, []*state.VoteResult{voteResult}); err != nil {
return err
}
return nil
}
-func (c *Chain) reorganizeChain(node *state.BlockNode) error {
- attachNodes, detachNodes := c.calcReorganizeNodes(node)
+func (c *Chain) reorganizeChain(blockHeader *types.BlockHeader) error {
+ attachBlockHeaders, detachBlockHeaders, err := c.calcReorganizeChain(blockHeader, c.bestBlockHeader)
+ if err != nil {
+ return err
+ }
+
utxoView := state.NewUtxoViewpoint()
voteResults := []*state.VoteResult{}
- irreversibleNode := c.bestIrreversibleNode
- voteResult, err := c.consensusNodeManager.getBestVoteResult()
+ voteResult, err := c.getBestVoteResult()
if err != nil {
return err
}
- for _, detachNode := range detachNodes {
- b, err := c.store.GetBlock(&detachNode.Hash, detachNode.Height)
+ for _, detachBlockHeader := range detachBlockHeaders {
+ detachHash := detachBlockHeader.Hash()
+ b, err := c.store.GetBlock(&detachHash)
if err != nil {
return err
}
return err
}
- log.WithFields(log.Fields{"module": logModule, "height": node.Height, "hash": node.Hash.String()}).Debug("detach from mainchain")
+ blockHash := blockHeader.Hash()
+ log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height, "hash": blockHash.String()}).Debug("detach from mainchain")
}
- for _, attachNode := range attachNodes {
- b, err := c.store.GetBlock(&attachNode.Hash, attachNode.Height)
+ irrBlockHeader := c.bestIrrBlockHeader
+ for _, attachBlockHeader := range attachBlockHeaders {
+ attachHash := attachBlockHeader.Hash()
+ b, err := c.store.GetBlock(&attachHash)
if err != nil {
return err
}
voteResults = append(voteResults, voteResult.Fork())
}
- if c.isIrreversible(attachNode) && attachNode.Height > irreversibleNode.Height {
- irreversibleNode = attachNode
+ if c.isIrreversible(attachBlockHeader) && attachBlockHeader.Height > irrBlockHeader.Height {
+ irrBlockHeader = attachBlockHeader
}
- log.WithFields(log.Fields{"module": logModule, "height": node.Height, "hash": node.Hash.String()}).Debug("attach from mainchain")
+ blockHash := blockHeader.Hash()
+ log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height, "hash": blockHash.String()}).Debug("attach from mainchain")
}
- if detachNodes[len(detachNodes)-1].Height <= c.bestIrreversibleNode.Height && irreversibleNode.Height <= c.bestIrreversibleNode.Height {
+ if detachBlockHeaders[len(detachBlockHeaders)-1].Height <= c.bestIrrBlockHeader.Height && irrBlockHeader.Height <= c.bestIrrBlockHeader.Height {
return errors.New("rollback block below the height of irreversible block")
}
voteResults = append(voteResults, voteResult.Fork())
- return c.setState(node, irreversibleNode, utxoView, voteResults)
+ return c.setState(blockHeader, irrBlockHeader, attachBlockHeaders, utxoView, voteResults)
}
// SaveBlock will validate and save block into storage
return errors.Sub(ErrBadBlock, err)
}
- parent := c.index.GetNode(&block.PreviousBlockHash)
+ parent, err := c.store.GetBlockHeader(&block.PreviousBlockHash)
+ if err != nil {
+ return err
+ }
+
bcBlock := types.MapBlock(block)
if err := validation.ValidateBlock(bcBlock, parent); err != nil {
return errors.Sub(ErrBadBlock, err)
if err := c.store.SaveBlock(block, bcBlock.TransactionStatus); err != nil {
return err
}
-
c.orphanManage.Delete(&bcBlock.ID)
- node, err := state.NewBlockNode(&block.BlockHeader, parent)
- if err != nil {
- return err
- }
-
- c.index.AddNode(node)
if len(signature) != 0 {
xPub := config.CommonConfig.PrivateKey().XPub()
return c.orphanManage.BlockExist(&blockHash), nil
}
- if parent := c.index.GetNode(&block.PreviousBlockHash); parent == nil {
+ if _, err := c.store.GetBlockHeader(&block.PreviousBlockHash); err != nil {
c.orphanManage.Add(block)
return true, nil
}
}
bestBlock := c.saveSubBlock(block)
- bestBlockHash := bestBlock.Hash()
- bestNode := c.index.GetNode(&bestBlockHash)
+ bestBlockHeader := &bestBlock.BlockHeader
c.cond.L.Lock()
defer c.cond.L.Unlock()
- if bestNode.Parent == c.bestNode {
+ if bestBlockHeader.PreviousBlockHash == c.bestBlockHeader.Hash() {
log.WithFields(log.Fields{"module": logModule}).Debug("append block to the end of mainchain")
return false, c.connectBlock(bestBlock)
}
- if bestNode.Height > c.bestNode.Height {
+ if bestBlockHeader.Height > c.bestBlockHeader.Height {
log.WithFields(log.Fields{"module": logModule}).Debug("start to reorganize chain")
- return false, c.reorganizeChain(bestNode)
+ return false, c.reorganizeChain(bestBlockHeader)
}
return false, nil
}
import (
"testing"
- "github.com/vapor/config"
+ "github.com/vapor/database/storage"
"github.com/vapor/protocol/bc"
+ "github.com/vapor/protocol/bc/types"
"github.com/vapor/protocol/state"
"github.com/vapor/testutil"
)
-func TestCalcReorganizeNodes(t *testing.T) {
- config.CommonConfig = config.DefaultConfig()
- c := &Chain{index: state.NewBlockIndex()}
- header := config.GenesisBlock().BlockHeader
- initNode, err := state.NewBlockNode(&header, nil)
- if err != nil {
- t.Fatal(err)
- }
-
- c.index.AddNode(initNode)
- var wantAttachNodes []*state.BlockNode
- var wantDetachNodes []*state.BlockNode
-
- mainChainNode := initNode
- for i := 1; i <= 7; i++ {
- header.Height = uint64(i)
- mainChainNode, err = state.NewBlockNode(&header, mainChainNode)
- if err != nil {
- t.Fatal(err)
- }
- wantDetachNodes = append([]*state.BlockNode{mainChainNode}, wantDetachNodes...)
- c.index.AddNode(mainChainNode)
- }
- c.bestNode = mainChainNode
- c.index.SetMainChain(mainChainNode)
+type mStore struct {
+ blockHeaders map[bc.Hash]*types.BlockHeader
+}
- sideChainNode := initNode
- for i := 1; i <= 13; i++ {
- header.Height = uint64(i)
- sideChainNode, err = state.NewBlockNode(&header, sideChainNode)
- if err != nil {
- t.Fatal(err)
- }
- wantAttachNodes = append(wantAttachNodes, sideChainNode)
- c.index.AddNode(sideChainNode)
- }
+func (s *mStore) BlockExist(hash *bc.Hash) bool { return false }
+func (s *mStore) GetBlock(*bc.Hash) (*types.Block, error) { return nil, nil }
+func (s *mStore) GetBlockHeader(hash *bc.Hash) (*types.BlockHeader, error) {
+ return s.blockHeaders[*hash], nil
+}
+func (s *mStore) GetStoreStatus() *BlockStoreState { return nil }
+func (s *mStore) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil }
+func (s *mStore) GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error { return nil }
+func (s *mStore) GetUtxo(*bc.Hash) (*storage.UtxoEntry, error) { return nil, nil }
+func (s *mStore) GetVoteResult(uint64) (*state.VoteResult, error) { return nil, nil }
+func (s *mStore) GetMainChainHash(uint64) (*bc.Hash, error) { return nil, nil }
+func (s *mStore) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error) { return nil, nil }
+func (s *mStore) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
+func (s *mStore) SaveBlockHeader(blockHeader *types.BlockHeader) error {
+ s.blockHeaders[blockHeader.Hash()] = blockHeader
+ return nil
+}
+func (s *mStore) SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.VoteResult) error {
+ return nil
+}
- getAttachNodes, getDetachNodes := c.calcReorganizeNodes(sideChainNode)
- if !testutil.DeepEqual(wantAttachNodes, getAttachNodes) {
- t.Errorf("attach nodes want %v but get %v", wantAttachNodes, getAttachNodes)
- }
- if !testutil.DeepEqual(wantDetachNodes, getDetachNodes) {
- t.Errorf("detach nodes want %v but get %v", wantDetachNodes, getDetachNodes)
+func TestCalcReorganizeChain(t *testing.T) {
+ c := &Chain{
+ store: &mStore{
+ blockHeaders: make(map[bc.Hash]*types.BlockHeader),
+ },
}
-}
-func TestEdgeCalcReorganizeNodes(t *testing.T) {
- config.CommonConfig = config.DefaultConfig()
- header := config.GenesisBlock().BlockHeader
- initNode, err := state.NewBlockNode(&header, nil)
- if err != nil {
- t.Fatal(err)
+ initBlockHeader := &types.BlockHeader{
+ Height: 0,
+ Version: 1,
}
+ c.store.SaveBlockHeader(initBlockHeader)
- testNodes := []*state.BlockNode{initNode}
- testNewNodes := []*state.BlockNode{initNode}
- for i := uint64(1); i <= 5; i++ {
- node := &state.BlockNode{
- Height: i,
- Hash: bc.Hash{V0: uint64(i)},
- Parent: testNodes[i-1],
+ var wantAttachBlockHeaders []*types.BlockHeader
+ var wantDetachBlockHeaders []*types.BlockHeader
+ mainChainBlockHeader := initBlockHeader
+ newChainBlockHeader := initBlockHeader
+ for i := 1; i <= 7; i++ {
+ mainChainBlockHeader = &types.BlockHeader{
+ PreviousBlockHash: mainChainBlockHeader.Hash(),
+ Height: uint64(i),
}
- testNodes = append(testNodes, node)
+ wantDetachBlockHeaders = append([]*types.BlockHeader{mainChainBlockHeader}, wantDetachBlockHeaders...)
+ c.store.SaveBlockHeader(mainChainBlockHeader)
+ }
- newNode := &state.BlockNode{
- Height: i,
- Hash: bc.Hash{V1: uint64(i)},
- Parent: testNewNodes[i-1],
+ for i := 1; i <= 13; i++ {
+ newChainBlockHeader = &types.BlockHeader{
+ PreviousBlockHash: newChainBlockHeader.Hash(),
+ Height: uint64(i),
+ Version: 1,
}
- testNewNodes = append(testNewNodes, newNode)
+ wantAttachBlockHeaders = append(wantAttachBlockHeaders, newChainBlockHeader)
+ c.store.SaveBlockHeader(newChainBlockHeader)
}
- cases := []struct {
- mainChainNode *state.BlockNode
- newNode *state.BlockNode
- wantAttachNodes []*state.BlockNode
- wantDetachNodes []*state.BlockNode
- }{
- {
- mainChainNode: testNodes[1],
- newNode: testNodes[5],
- wantAttachNodes: testNodes[2:],
- wantDetachNodes: []*state.BlockNode{},
- },
- {
- mainChainNode: testNodes[5],
- newNode: testNodes[2],
- wantAttachNodes: []*state.BlockNode{},
- wantDetachNodes: []*state.BlockNode{testNodes[5], testNodes[4], testNodes[3]},
- },
- {
- mainChainNode: testNodes[2],
- newNode: testNodes[2],
- wantAttachNodes: []*state.BlockNode{},
- wantDetachNodes: []*state.BlockNode{},
- },
- {
- mainChainNode: testNewNodes[3],
- newNode: testNodes[2],
- wantAttachNodes: testNodes[1:3],
- wantDetachNodes: []*state.BlockNode{testNewNodes[3], testNewNodes[2], testNewNodes[1]},
- },
- {
- mainChainNode: testNewNodes[2],
- newNode: testNodes[3],
- wantAttachNodes: testNodes[1:4],
- wantDetachNodes: []*state.BlockNode{testNewNodes[2], testNewNodes[1]},
- },
- {
- mainChainNode: testNodes[5],
- newNode: testNewNodes[3],
- wantAttachNodes: testNewNodes[1:4],
- wantDetachNodes: []*state.BlockNode{testNodes[5], testNodes[4], testNodes[3], testNodes[2], testNodes[1]},
- },
+ getAttachBlockHeaders, getDetachBlockHeaders, _ := c.calcReorganizeChain(newChainBlockHeader, mainChainBlockHeader)
+ if !testutil.DeepEqual(wantAttachBlockHeaders, getAttachBlockHeaders) {
+ t.Errorf("attach headers want %v but get %v", wantAttachBlockHeaders, getAttachBlockHeaders)
}
- for i, c := range cases {
- chain := &Chain{index: state.NewBlockIndex()}
- chain.index.AddNode(initNode)
- for i := uint64(1); i <= c.mainChainNode.Height; i++ {
- chain.index.AddNode(testNodes[i])
- }
- chain.bestNode = c.mainChainNode
- chain.index.SetMainChain(c.mainChainNode)
- getAttachNodes, getDetachNodes := chain.calcReorganizeNodes(c.newNode)
-
- if !testutil.DeepEqual(c.wantAttachNodes, getAttachNodes) {
- t.Errorf("test case %d, attach nodes want %v but get %v", i, c.wantAttachNodes, getAttachNodes)
- }
-
- if !testutil.DeepEqual(c.wantDetachNodes, getDetachNodes) {
- t.Errorf("test case %d, detach nodes want %v but get %v", i, c.wantDetachNodes, getDetachNodes)
- }
+ if !testutil.DeepEqual(wantDetachBlockHeaders, getDetachBlockHeaders) {
+ t.Errorf("detach headers want %v but get %v", wantDetachBlockHeaders, getDetachBlockHeaders)
}
}
"github.com/vapor/consensus"
"github.com/vapor/errors"
"github.com/vapor/protocol/bc"
+ "github.com/vapor/protocol/bc/types"
"github.com/vapor/protocol/state"
)
errNotFoundBlockNode = errors.New("can not find block node")
)
-type consensusNodeManager struct {
- store Store
- blockIndex *state.BlockIndex
-}
-
-func newConsensusNodeManager(store Store, blockIndex *state.BlockIndex) *consensusNodeManager {
- return &consensusNodeManager{
- store: store,
- blockIndex: blockIndex,
- }
-}
-
-func (c *consensusNodeManager) getConsensusNode(prevBlockHash *bc.Hash, pubkey string) (*state.ConsensusNode, error) {
+func (c *Chain) getConsensusNode(prevBlockHash *bc.Hash, pubkey string) (*state.ConsensusNode, error) {
consensusNodeMap, err := c.getConsensusNodes(prevBlockHash)
if err != nil {
return nil, err
return node, nil
}
-func (c *consensusNodeManager) getBlocker(prevBlockHash *bc.Hash, timeStamp uint64) (string, error) {
+// GetBlocker return blocker by specified timestamp
+func (c *Chain) GetBlocker(prevBlockHash *bc.Hash, timeStamp uint64) (string, error) {
consensusNodeMap, err := c.getConsensusNodes(prevBlockHash)
if err != nil {
return "", err
return (blockTimestamp - lastRoundStartTime) / (consensus.BlockNumEachNode * consensus.BlockTimeInterval)
}
-func (c *consensusNodeManager) getPrevRoundLastBlock(prevBlockHash *bc.Hash) (*state.BlockNode, error) {
- node := c.blockIndex.GetNode(prevBlockHash)
- if node == nil {
+func (c *Chain) getPrevRoundLastBlock(prevBlockHash *bc.Hash) (*types.BlockHeader, error) {
+ blockHeader, err := c.store.GetBlockHeader(prevBlockHash)
+ if err != nil {
return nil, errNotFoundBlockNode
}
- for node.Height%consensus.RoundVoteBlockNums != 0 {
- node = node.Parent
+ for blockHeader.Height%consensus.RoundVoteBlockNums != 0 {
+ blockHeader, err = c.store.GetBlockHeader(&blockHeader.PreviousBlockHash)
+ if err != nil {
+ return nil, err
+ }
}
- return node, nil
+ return blockHeader, nil
}
-func (c *consensusNodeManager) getConsensusNodes(prevBlockHash *bc.Hash) (map[string]*state.ConsensusNode, error) {
- prevBlockNode := c.blockIndex.GetNode(prevBlockHash)
- if prevBlockNode == nil {
+func (c *Chain) getConsensusNodes(prevBlockHash *bc.Hash) (map[string]*state.ConsensusNode, error) {
+ prevBlockHeader, err := c.store.GetBlockHeader(prevBlockHash)
+ if err != nil {
return nil, errNotFoundBlockNode
}
- preSeq := state.CalcVoteSeq(prevBlockNode.Height+1) - 1
- if bestSeq := state.CalcVoteSeq(c.blockIndex.BestNode().Height); preSeq > bestSeq {
+ bestBlockHeader := c.bestBlockHeader
+ preSeq := state.CalcVoteSeq(prevBlockHeader.Height+1) - 1
+ if bestSeq := state.CalcVoteSeq(bestBlockHeader.Height); preSeq > bestSeq {
preSeq = bestSeq
}
- lastBlockNode, err := c.getPrevRoundLastBlock(prevBlockHash)
+ lastBlockHeader, err := c.getPrevRoundLastBlock(prevBlockHash)
if err != nil {
return nil, err
}
- voteResult, err := c.getVoteResult(preSeq, lastBlockNode)
+ voteResult, err := c.getVoteResult(preSeq, lastBlockHeader)
if err != nil {
return nil, err
}
return voteResult.ConsensusNodes()
}
-func (c *consensusNodeManager) getBestVoteResult() (*state.VoteResult, error) {
- blockNode := c.blockIndex.BestNode()
- seq := state.CalcVoteSeq(blockNode.Height)
- return c.getVoteResult(seq, blockNode)
+func (c *Chain) getBestVoteResult() (*state.VoteResult, error) {
+ bestBlockHeader := c.bestBlockHeader
+ seq := state.CalcVoteSeq(bestBlockHeader.Height)
+ return c.getVoteResult(seq, bestBlockHeader)
}
// getVoteResult return the vote result
// seq represent the sequence of vote
-// blockNode represent the chain in which the result of the vote is located
+// blockHeader represent the chain in which the result of the vote is located
// Voting results need to be adjusted according to the chain
-func (c *consensusNodeManager) getVoteResult(seq uint64, blockNode *state.BlockNode) (*state.VoteResult, error) {
+func (c *Chain) getVoteResult(seq uint64, blockHeader *types.BlockHeader) (*state.VoteResult, error) {
voteResult, err := c.store.GetVoteResult(seq)
if err != nil {
return nil, err
}
- if err := c.reorganizeVoteResult(voteResult, blockNode); err != nil {
+ if err := c.reorganizeVoteResult(voteResult, blockHeader); err != nil {
return nil, err
}
return voteResult, nil
}
-func (c *consensusNodeManager) reorganizeVoteResult(voteResult *state.VoteResult, node *state.BlockNode) error {
- mainChainNode := c.blockIndex.GetNode(&voteResult.BlockHash)
- var attachNodes []*state.BlockNode
- var detachNodes []*state.BlockNode
- for forkChainNode := node; mainChainNode != forkChainNode; {
- var forChainRollback, mainChainRollBack bool
- if forChainRollback = forkChainNode.Height >= mainChainNode.Height; forChainRollback {
- attachNodes = append([]*state.BlockNode{forkChainNode}, attachNodes...)
- }
- if mainChainRollBack = forkChainNode.Height <= mainChainNode.Height; mainChainRollBack {
- detachNodes = append(detachNodes, mainChainNode)
- }
- if forChainRollback {
- forkChainNode = forkChainNode.Parent
- }
- if mainChainRollBack {
- mainChainNode = mainChainNode.Parent
- }
+func (c *Chain) reorganizeVoteResult(voteResult *state.VoteResult, blockHeader *types.BlockHeader) error {
+ mainChainBlockHeader, err := c.store.GetBlockHeader(&voteResult.BlockHash)
+ if err != nil {
+ return err
+ }
+
+ attachBlockHeaders, detachBlockHeaders, err := c.calcReorganizeChain(blockHeader, mainChainBlockHeader)
+ if err != nil {
+ return err
}
- for _, node := range detachNodes {
- block, err := c.store.GetBlock(&node.Hash, node.Height)
+ for _, bh := range detachBlockHeaders {
+ blockHash := bh.Hash()
+ block, err := c.store.GetBlock(&blockHash)
if err != nil {
return err
}
}
}
- for _, node := range attachNodes {
- block, err := c.store.GetBlock(&node.Hash, node.Height)
+ for _, bh := range attachBlockHeaders {
+ blockHash := bh.Hash()
+ block, err := c.store.GetBlock(&blockHash)
if err != nil {
return err
}
// Chain provides functions for working with the Bytom block chain.
type Chain struct {
- index *state.BlockIndex
orphanManage *OrphanManage
txPool *TxPool
store Store
processBlockCh chan *processBlockMsg
- consensusNodeManager *consensusNodeManager
- signatureCache *common.Cache
- eventDispatcher *event.Dispatcher
+ signatureCache *common.Cache
+ eventDispatcher *event.Dispatcher
- cond sync.Cond
- bestNode *state.BlockNode
- bestIrreversibleNode *state.BlockNode
+ cond sync.Cond
+ bestBlockHeader *types.BlockHeader
+ bestIrrBlockHeader *types.BlockHeader
}
// NewChain returns a new Chain using store as the underlying storage.
}
var err error
- if c.index, err = store.LoadBlockIndex(storeStatus.Height); err != nil {
+ c.bestBlockHeader, err = c.store.GetBlockHeader(storeStatus.Hash)
+ if err != nil {
return nil, err
}
- c.bestNode = c.index.GetNode(storeStatus.Hash)
- c.bestIrreversibleNode = c.index.GetNode(storeStatus.IrreversibleHash)
- c.consensusNodeManager = newConsensusNodeManager(store, c.index)
- c.index.SetMainChain(c.bestNode)
+ c.bestIrrBlockHeader, err = c.store.GetBlockHeader(storeStatus.IrreversibleHash)
+ if err != nil {
+ return nil, err
+ }
go c.blockProcesser()
return c, nil
}
BlockHash: genesisBlock.Hash(),
BlockHeight: 0,
}}
- node, err := state.NewBlockNode(&genesisBlock.BlockHeader, nil)
- if err != nil {
- return err
- }
- return c.store.SaveChainStatus(node, node, utxoView, voteResults)
+ genesisBlockHeader := &genesisBlock.BlockHeader
+ return c.store.SaveChainStatus(genesisBlockHeader, genesisBlockHeader, []*types.BlockHeader{genesisBlockHeader}, utxoView, voteResults)
}
// BestBlockHeight returns the current height of the blockchain.
func (c *Chain) BestBlockHeight() uint64 {
c.cond.L.Lock()
defer c.cond.L.Unlock()
- return c.bestNode.Height
+ return c.bestBlockHeader.Height
}
// BestBlockHash return the hash of the chain tail block
func (c *Chain) BestBlockHash() *bc.Hash {
c.cond.L.Lock()
defer c.cond.L.Unlock()
- return &c.bestNode.Hash
+ bestHash := c.bestBlockHeader.Hash()
+ return &bestHash
}
-// BestIrreversibleHeader returns the chain best irreversible block
+// BestIrreversibleHeader returns the chain best irreversible block header
func (c *Chain) BestIrreversibleHeader() *types.BlockHeader {
- return c.bestIrreversibleNode.BlockHeader()
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
+ return c.bestIrrBlockHeader
}
+// BestBlockHeader returns the chain best block header
func (c *Chain) BestBlockHeader() *types.BlockHeader {
- node := c.index.BestNode()
- return node.BlockHeader()
+ c.cond.L.Lock()
+ defer c.cond.L.Unlock()
+ return c.bestBlockHeader
}
// InMainChain checks wheather a block is in the main chain
func (c *Chain) InMainChain(hash bc.Hash) bool {
- return c.index.InMainchain(hash)
+ blockHeader, err := c.store.GetBlockHeader(&hash)
+ if err != nil {
+ return false
+ }
+
+ blockHash, err := c.store.GetMainChainHash(blockHeader.Height)
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height}).Debug("not contain block hash in main chain for specified height")
+ return false
+ }
+ return *blockHash == hash
}
// This function must be called with mu lock in above level
-func (c *Chain) setState(node, irreversibleNode *state.BlockNode, view *state.UtxoViewpoint, voteResults []*state.VoteResult) error {
- if err := c.store.SaveChainStatus(node, irreversibleNode, view, voteResults); err != nil {
+func (c *Chain) setState(blockHeader, irrBlockHeader *types.BlockHeader, mainBlockHeaders []*types.BlockHeader, view *state.UtxoViewpoint, voteResults []*state.VoteResult) error {
+ if err := c.store.SaveChainStatus(blockHeader, irrBlockHeader, mainBlockHeaders, view, voteResults); err != nil {
return err
}
- c.index.SetMainChain(node)
- c.bestNode = node
- c.bestIrreversibleNode = irreversibleNode
+ c.bestBlockHeader = blockHeader
+ c.bestIrrBlockHeader = irrBlockHeader
- log.WithFields(log.Fields{"module": logModule, "height": c.bestNode.Height, "hash": c.bestNode.Hash.String()}).Debug("chain best status has been update")
+ blockHash := blockHeader.Hash()
+ log.WithFields(log.Fields{"module": logModule, "height": blockHeader.Height, "hash": blockHash.String()}).Debug("chain best status has been update")
c.cond.Broadcast()
return nil
}
go func() {
c.cond.L.Lock()
defer c.cond.L.Unlock()
- for c.bestNode.Height < height {
+ for c.bestBlockHeader.Height < height {
c.cond.Wait()
}
ch <- struct{}{}
+++ /dev/null
-package state
-
-import (
- "errors"
- "sort"
- "sync"
-
- "github.com/vapor/common"
- "github.com/vapor/consensus"
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
-)
-
-// approxNodesPerDay is an approximation of the number of new blocks there are
-// in a day on average.
-const approxNodesPerDay = 24 * 24
-
-// BlockNode represents a block within the block chain and is primarily used to
-// aid in selecting the best chain to be the main chain.
-type BlockNode struct {
- Parent *BlockNode // parent is the parent block for this node.
- Hash bc.Hash // hash of the block.
-
- Version uint64
- Height uint64
- Timestamp uint64
- BlockWitness *common.BitMap
- TransactionsMerkleRoot bc.Hash
- TransactionStatusHash bc.Hash
-}
-
-func NewBlockNode(bh *types.BlockHeader, parent *BlockNode) (*BlockNode, error) {
- if bh.Height != 0 && parent == nil {
- return nil, errors.New("parent node can not be nil")
- }
-
- node := &BlockNode{
- Parent: parent,
- Hash: bh.Hash(),
- Version: bh.Version,
- Height: bh.Height,
- Timestamp: bh.Timestamp,
- TransactionsMerkleRoot: bh.TransactionsMerkleRoot,
- TransactionStatusHash: bh.TransactionStatusHash,
- }
-
- node.BlockWitness = common.NewBitMap(uint32(len(bh.Witness)))
- for i, witness := range bh.Witness {
- if len(witness) != 0 {
- if err := node.BlockWitness.Set(uint32(i)); err != nil {
- return nil, err
- }
- }
- }
- return node, nil
-}
-
-// blockHeader convert a node to the header struct
-func (node *BlockNode) BlockHeader() *types.BlockHeader {
- previousBlockHash := bc.Hash{}
- if node.Parent != nil {
- previousBlockHash = node.Parent.Hash
- }
- return &types.BlockHeader{
- Version: node.Version,
- Height: node.Height,
- PreviousBlockHash: previousBlockHash,
- Timestamp: node.Timestamp,
- BlockCommitment: types.BlockCommitment{
- TransactionsMerkleRoot: node.TransactionsMerkleRoot,
- TransactionStatusHash: node.TransactionStatusHash,
- },
- }
-}
-
-func (node *BlockNode) CalcPastMedianTime() uint64 {
- timestamps := []uint64{}
- iterNode := node
- for i := 0; i < consensus.MedianTimeBlocks && iterNode != nil; i++ {
- timestamps = append(timestamps, iterNode.Timestamp)
- iterNode = iterNode.Parent
- }
-
- sort.Sort(common.TimeSorter(timestamps))
- return timestamps[len(timestamps)/2]
-}
-
-// BlockIndex is the struct for help chain trace block chain as tree
-type BlockIndex struct {
- sync.RWMutex
-
- index map[bc.Hash]*BlockNode
- heightIndex map[uint64][]*BlockNode
- mainChain []*BlockNode
-}
-
-// NewBlockIndex will create a empty BlockIndex
-func NewBlockIndex() *BlockIndex {
- return &BlockIndex{
- index: make(map[bc.Hash]*BlockNode),
- heightIndex: make(map[uint64][]*BlockNode),
- mainChain: make([]*BlockNode, 0, approxNodesPerDay),
- }
-}
-
-// AddNode will add node to the index map
-func (bi *BlockIndex) AddNode(node *BlockNode) {
- bi.Lock()
- bi.index[node.Hash] = node
- bi.heightIndex[node.Height] = append(bi.heightIndex[node.Height], node)
- bi.Unlock()
-}
-
-// GetNode will search node from the index map
-func (bi *BlockIndex) GetNode(hash *bc.Hash) *BlockNode {
- bi.RLock()
- defer bi.RUnlock()
- return bi.index[*hash]
-}
-
-func (bi *BlockIndex) BestNode() *BlockNode {
- bi.RLock()
- defer bi.RUnlock()
- return bi.mainChain[len(bi.mainChain)-1]
-}
-
-// BlockExist check does the block existed in blockIndex
-func (bi *BlockIndex) BlockExist(hash *bc.Hash) bool {
- bi.RLock()
- _, ok := bi.index[*hash]
- bi.RUnlock()
- return ok
-}
-
-// TODO: THIS FUNCTION MIGHT BE DELETED
-func (bi *BlockIndex) InMainchain(hash bc.Hash) bool {
- bi.RLock()
- defer bi.RUnlock()
-
- node, ok := bi.index[hash]
- if !ok {
- return false
- }
- return bi.nodeByHeight(node.Height) == node
-}
-
-func (bi *BlockIndex) nodeByHeight(height uint64) *BlockNode {
- if height >= uint64(len(bi.mainChain)) {
- return nil
- }
- return bi.mainChain[height]
-}
-
-// NodeByHeight returns the block node at the specified height.
-func (bi *BlockIndex) NodeByHeight(height uint64) *BlockNode {
- bi.RLock()
- defer bi.RUnlock()
- return bi.nodeByHeight(height)
-}
-
-// NodesByHeight return all block nodes at the specified height.
-func (bi *BlockIndex) NodesByHeight(height uint64) []*BlockNode {
- bi.RLock()
- defer bi.RUnlock()
- return bi.heightIndex[height]
-}
-
-// SetMainChain will set the the mainChain array
-func (bi *BlockIndex) SetMainChain(node *BlockNode) {
- bi.Lock()
- defer bi.Unlock()
-
- needed := node.Height + 1
- if uint64(cap(bi.mainChain)) < needed {
- nodes := make([]*BlockNode, needed, needed+approxNodesPerDay)
- copy(nodes, bi.mainChain)
- bi.mainChain = nodes
- } else {
- i := uint64(len(bi.mainChain))
- bi.mainChain = bi.mainChain[0:needed]
- for ; i < needed; i++ {
- bi.mainChain[i] = nil
- }
- }
-
- for node != nil && bi.mainChain[node.Height] != node {
- bi.mainChain[node.Height] = node
- node = node.Parent
- }
-}
+++ /dev/null
-package state
-
-import (
- "testing"
-
- "github.com/davecgh/go-spew/spew"
-
- "github.com/vapor/protocol/bc"
- "github.com/vapor/protocol/bc/types"
-)
-
-func TestCalcPastMedianTime(t *testing.T) {
- cases := []struct {
- Timestamps []uint64
- MedianTime uint64
- }{
- {
- Timestamps: []uint64{1},
- MedianTime: 1,
- },
- {
- Timestamps: []uint64{1, 2},
- MedianTime: 2,
- },
- {
- Timestamps: []uint64{1, 3, 2},
- MedianTime: 2,
- },
- {
- Timestamps: []uint64{1, 3, 2, 3},
- MedianTime: 3,
- },
- {
- Timestamps: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 11, 10, 9},
- MedianTime: 6,
- },
- {
- Timestamps: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 11, 10, 9, 11, 11, 11, 14},
- MedianTime: 10,
- },
- }
-
- for idx, c := range cases {
- var parentNode *BlockNode
- for i := range c.Timestamps {
- blockHeader := &types.BlockHeader{
- Height: uint64(i),
- Timestamp: c.Timestamps[i],
- }
-
- blockNode, err := NewBlockNode(blockHeader, parentNode)
- if err != nil {
- t.Fatal(err)
- }
- parentNode = blockNode
- }
-
- medianTime := parentNode.CalcPastMedianTime()
- if medianTime != c.MedianTime {
- t.Fatalf("calc median timestamp failed, index: %d, expected: %d, have: %d", idx, c.MedianTime, medianTime)
- }
- }
-}
-
-func TestSetMainChain(t *testing.T) {
- blockIndex := NewBlockIndex()
- var lastNode *BlockNode
- for i := uint64(0); i < 4; i++ {
- node := &BlockNode{
- Height: i,
- Hash: bc.Hash{V0: i},
- Parent: lastNode,
- }
- blockIndex.AddNode(node)
- lastNode = node
- }
-
- tailNode := lastNode
- blockIndex.SetMainChain(lastNode)
- for lastNode.Parent != nil {
- if !blockIndex.InMainchain(lastNode.Hash) {
- t.Fatalf("block %d, hash %v is not in main chain", lastNode.Height, lastNode.Hash)
- }
- lastNode = lastNode.Parent
- }
-
- // fork and set main chain
- forkHeight := uint64(1)
- lastNode = blockIndex.nodeByHeight(forkHeight)
- for i := uint64(1); i <= 3; i++ {
- node := &BlockNode{
- Height: lastNode.Height + 1,
- Hash: bc.Hash{V1: uint64(i)},
- Parent: lastNode,
- }
- blockIndex.AddNode(node)
- lastNode = node
- }
-
- bestNode := lastNode
- blockIndex.SetMainChain(lastNode)
- for lastNode.Parent != nil {
- if !blockIndex.InMainchain(lastNode.Hash) {
- t.Fatalf("after fork, block %d, hash %v is not in main chain", lastNode.Height, lastNode.Hash)
- }
- lastNode = lastNode.Parent
- }
-
- if bestNode != blockIndex.BestNode() {
- t.Fatalf("check best node failed")
- }
-
- for tailNode.Parent != nil && tailNode.Height > forkHeight {
- if blockIndex.InMainchain(tailNode.Hash) {
- t.Fatalf("old chain block %d, hash %v still in main chain", tailNode.Height, tailNode.Hash)
- }
- tailNode = tailNode.Parent
- }
-}
-
-// MockBlockIndex will mock a empty BlockIndex
-func MockBlockIndex() *BlockIndex {
- return &BlockIndex{
- index: make(map[bc.Hash]*BlockNode),
- heightIndex: make(map[uint64][]*BlockNode),
- mainChain: make([]*BlockNode, 0, 2),
- }
-}
-
-func TestSetMainChainExtendCap(t *testing.T) {
- blockIndex := MockBlockIndex()
- var lastNode *BlockNode
-
- cases := []struct {
- start uint64
- stop uint64
- wantLen int
- wantCap int
- }{
- {
- start: 0,
- stop: 500,
- wantLen: 500,
- wantCap: 500 + approxNodesPerDay,
- },
- {
- start: 500,
- stop: 1000,
- wantLen: 1000,
- wantCap: 500 + approxNodesPerDay,
- },
- {
- start: 1000,
- stop: 2000,
- wantLen: 2000,
- wantCap: 2000 + approxNodesPerDay,
- },
- }
-
- for num, c := range cases {
- for i := c.start; i < c.stop; i++ {
- node := &BlockNode{
- Height: i,
- Hash: bc.Hash{V0: i},
- Parent: lastNode,
- }
- blockIndex.AddNode(node)
- lastNode = node
- }
- blockIndex.SetMainChain(lastNode)
- if c.wantLen != len(blockIndex.mainChain) || c.wantCap != cap(blockIndex.mainChain) {
- t.Fatalf("SetMainChain extended capacity error, index: %d, got len: %d, got cap: %d, want len: %d, want cap: %d", num, len(blockIndex.mainChain), cap(blockIndex.mainChain), c.wantLen, c.wantCap)
- }
- }
-
- for i := 0; i < len(blockIndex.mainChain); i++ {
- if blockIndex.mainChain[i] != blockIndex.index[blockIndex.mainChain[i].Hash] {
- t.Fatal("SetMainChain extended capacity error, index:", i, "want:", spew.Sdump(blockIndex.mainChain[i]), "got:", spew.Sdump(blockIndex.index[blockIndex.mainChain[i].Hash]))
- }
- }
-}
// Store provides storage interface for blockchain data
type Store interface {
- BlockExist(*bc.Hash, uint64) bool
+ BlockExist(*bc.Hash) bool
- GetBlock(*bc.Hash, uint64) (*types.Block, error)
- GetBlockHeader(*bc.Hash, uint64) (*types.BlockHeader, error)
+ GetBlock(*bc.Hash) (*types.Block, error)
+ GetBlockHeader(*bc.Hash) (*types.BlockHeader, error)
GetStoreStatus() *BlockStoreState
GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error)
GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error
GetUtxo(*bc.Hash) (*storage.UtxoEntry, error)
GetVoteResult(uint64) (*state.VoteResult, error)
+ GetMainChainHash(uint64) (*bc.Hash, error)
+ GetBlockHashesByHeight(uint64) ([]*bc.Hash, error)
- LoadBlockIndex(uint64) (*state.BlockIndex, error)
SaveBlock(*types.Block, *bc.TransactionStatus) error
SaveBlockHeader(*types.BlockHeader) error
- SaveChainStatus(*state.BlockNode, *state.BlockNode, *state.UtxoViewpoint, []*state.VoteResult) error
+ SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.VoteResult) error
}
// BlockStoreState represents the core's db status
type mockStore struct{}
-func (s *mockStore) BlockExist(hash *bc.Hash, height uint64) bool { return false }
-func (s *mockStore) GetBlock(*bc.Hash, uint64) (*types.Block, error) { return nil, nil }
-func (s *mockStore) GetBlockHeader(*bc.Hash, uint64) (*types.BlockHeader, error) { return nil, nil }
+func (s *mockStore) BlockExist(hash *bc.Hash) bool { return false }
+func (s *mockStore) GetBlock(*bc.Hash) (*types.Block, error) { return nil, nil }
+func (s *mockStore) GetBlockHeader(*bc.Hash) (*types.BlockHeader, error) { return nil, nil }
func (s *mockStore) GetStoreStatus() *BlockStoreState { return nil }
func (s *mockStore) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil }
func (s *mockStore) GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error { return nil }
func (s *mockStore) GetUtxo(*bc.Hash) (*storage.UtxoEntry, error) { return nil, nil }
func (s *mockStore) GetVoteResult(uint64) (*state.VoteResult, error) { return nil, nil }
-func (s *mockStore) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil }
+func (s *mockStore) GetMainChainHash(uint64) (*bc.Hash, error) { return nil, nil }
+func (s *mockStore) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error) { return nil, nil }
func (s *mockStore) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
func (s *mockStore) SaveBlockHeader(*types.BlockHeader) error { return nil }
-func (s *mockStore) SaveChainStatus(*state.BlockNode, *state.BlockNode, *state.UtxoViewpoint, []*state.VoteResult) error {
+func (s *mockStore) SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.VoteResult) error {
return nil
}
type mockStore1 struct{}
-func (s *mockStore1) BlockExist(hash *bc.Hash, height uint64) bool { return false }
-func (s *mockStore1) GetBlock(*bc.Hash, uint64) (*types.Block, error) { return nil, nil }
-func (s *mockStore1) GetBlockHeader(*bc.Hash, uint64) (*types.BlockHeader, error) { return nil, nil }
+func (s *mockStore1) BlockExist(hash *bc.Hash) bool { return false }
+func (s *mockStore1) GetBlock(*bc.Hash) (*types.Block, error) { return nil, nil }
+func (s *mockStore1) GetBlockHeader(*bc.Hash) (*types.BlockHeader, error) { return nil, nil }
func (s *mockStore1) GetStoreStatus() *BlockStoreState { return nil }
func (s *mockStore1) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil }
func (s *mockStore1) GetTransactionsUtxo(utxoView *state.UtxoViewpoint, tx []*bc.Tx) error {
}
func (s *mockStore1) GetUtxo(*bc.Hash) (*storage.UtxoEntry, error) { return nil, nil }
func (s *mockStore1) GetVoteResult(uint64) (*state.VoteResult, error) { return nil, nil }
-func (s *mockStore1) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil }
+func (s *mockStore1) GetMainChainHash(uint64) (*bc.Hash, error) { return nil, nil }
+func (s *mockStore1) GetBlockHashesByHeight(uint64) ([]*bc.Hash, error) { return nil, nil }
func (s *mockStore1) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil }
func (s *mockStore1) SaveBlockHeader(*types.BlockHeader) error { return nil }
-func (s *mockStore1) SaveChainStatus(*state.BlockNode, *state.BlockNode, *state.UtxoViewpoint, []*state.VoteResult) error {
+func (s *mockStore1) SaveChainStatus(*types.BlockHeader, *types.BlockHeader, []*types.BlockHeader, *state.UtxoViewpoint, []*state.VoteResult) error {
return nil
}
"github.com/vapor/errors"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/state"
)
const logModule = "leveldb"
errVersionRegression = errors.New("version regression")
)
-func checkBlockTime(b *bc.Block, parent *state.BlockNode) error {
+func checkBlockTime(b *bc.Block, parent *types.BlockHeader) error {
now := uint64(time.Now().UnixNano() / 1e6)
if b.Timestamp < (parent.Timestamp + consensus.BlockTimeInterval) {
return errBadTimestamp
}
// ValidateBlockHeader check the block's header
-func ValidateBlockHeader(b *bc.Block, parent *state.BlockNode) error {
+func ValidateBlockHeader(b *bc.Block, parent *types.BlockHeader) error {
if b.Version != 1 {
return errors.WithDetailf(errVersionRegression, "previous block verson %d, current block version %d", parent.Version, b.Version)
}
if b.Height != parent.Height+1 {
return errors.WithDetailf(errMisorderedBlockHeight, "previous block height %d, current block height %d", parent.Height, b.Height)
}
- if parent.Hash != *b.PreviousBlockId {
- return errors.WithDetailf(errMismatchedBlock, "previous block ID %x, current block wants %x", parent.Hash.Bytes(), b.PreviousBlockId.Bytes())
+ if parent.Hash() != *b.PreviousBlockId {
+ return errors.WithDetailf(errMismatchedBlock, "previous block ID %x, current block wants %x", parent.Hash().Bytes(), b.PreviousBlockId.Bytes())
}
return checkBlockTime(b, parent)
}
// ValidateBlock validates a block and the transactions within.
-func ValidateBlock(b *bc.Block, parent *state.BlockNode) error {
+func ValidateBlock(b *bc.Block, parent *types.BlockHeader) error {
startTime := time.Now()
if err := ValidateBlockHeader(b, parent); err != nil {
return err
"github.com/vapor/consensus"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/state"
"github.com/vapor/protocol/vm"
"github.com/vapor/protocol/vm/vmutil"
)
},
}
- parent := &state.BlockNode{Version: 1}
+ parent := &types.BlockHeader{Version: 1}
block := &bc.Block{
BlockHeader: &bc.BlockHeader{Version: 1},
}
parent.Timestamp = c.parentTime[0]
parentSuccessor := parent
for i := 1; i < len(c.parentTime); i++ {
- parentSuccessor.Parent = &state.BlockNode{Version: 1, Timestamp: c.parentTime[i]}
- parentSuccessor = parentSuccessor.Parent
+ Previous := &types.BlockHeader{Version: 1, Timestamp: c.parentTime[i]}
+ parentSuccessor.PreviousBlockHash = Previous.Hash()
+ parentSuccessor = Previous
}
block.Timestamp = c.blockTime
}
func TestValidateBlockHeader(t *testing.T) {
+ parent := &types.BlockHeader{
+ Version: 1,
+ Height: 0,
+ Timestamp: 1523352600000,
+ }
+ parentHash := parent.Hash()
+
cases := []struct {
desc string
block *bc.Block
- parent *state.BlockNode
+ parent *types.BlockHeader
err error
}{
{
block: &bc.Block{BlockHeader: &bc.BlockHeader{
Version: 2,
}},
- parent: &state.BlockNode{
+ parent: &types.BlockHeader{
Version: 1,
},
err: errVersionRegression,
Version: 1,
Height: 20,
}},
- parent: &state.BlockNode{
+ parent: &types.BlockHeader{
Version: 1,
Height: 18,
},
block: &bc.Block{BlockHeader: &bc.BlockHeader{
Version: 1,
Height: 20,
- PreviousBlockId: &bc.Hash{V0: 18},
+ PreviousBlockId: &bc.Hash{V0: 20},
}},
- parent: &state.BlockNode{
- Version: 1,
- Height: 19,
- Hash: bc.Hash{V0: 19},
+ parent: &types.BlockHeader{
+ Version: 1,
+ Height: 19,
+ PreviousBlockHash: bc.Hash{V0: 19},
},
err: errMismatchedBlock,
},
Version: 1,
Height: 1,
Timestamp: 1523352601000,
- PreviousBlockId: &bc.Hash{V0: 0},
+ PreviousBlockId: &parentHash,
},
},
- parent: &state.BlockNode{
- Version: 1,
- Height: 0,
- Timestamp: 1523352600000,
- Hash: bc.Hash{V0: 0},
- },
- err: nil,
+ parent: parent,
+ err: nil,
},
{
desc: "version greater than 1 (blocktest#1001)",
Version: 2,
},
},
- parent: &state.BlockNode{
+ parent: &types.BlockHeader{
Version: 1,
},
err: errVersionRegression,
Version: 0,
},
},
- parent: &state.BlockNode{
+ parent: &types.BlockHeader{
Version: 1,
},
err: errVersionRegression,
Version: math.MaxUint64,
},
},
- parent: &state.BlockNode{
+ parent: &types.BlockHeader{
Version: 1,
},
err: errVersionRegression,
// TestValidateBlock test the ValidateBlock function
func TestValidateBlock(t *testing.T) {
cp, _ := vmutil.DefaultCoinbaseProgram()
+ parent := &types.BlockHeader{
+ Version: 1,
+ Height: 0,
+ Timestamp: 1523352600000,
+ PreviousBlockHash: bc.Hash{V0: 0},
+ }
+ parentHash := parent.Hash()
+
cases := []struct {
desc string
block *bc.Block
- parent *state.BlockNode
+ parent *types.BlockHeader
err error
}{
{
Version: 1,
Height: 1,
Timestamp: 1523352601000,
- PreviousBlockId: &bc.Hash{V0: 0},
+ PreviousBlockId: &parentHash,
TransactionsRoot: &bc.Hash{V0: 1},
},
Transactions: []*bc.Tx{
}),
},
},
- parent: &state.BlockNode{
- Version: 1,
- Height: 0,
- Timestamp: 1523352600000,
- Hash: bc.Hash{V0: 0},
- },
- err: errMismatchedMerkleRoot,
+ parent: parent,
+ err: errMismatchedMerkleRoot,
},
{
desc: "The calculated transaction status merkel root hash is not equals to the hash of the block header (blocktest#1009)",
Version: 1,
Height: 1,
Timestamp: 1523352601000,
- PreviousBlockId: &bc.Hash{V0: 0},
+ PreviousBlockId: &parentHash,
TransactionsRoot: &bc.Hash{V0: 6294987741126419124, V1: 12520373106916389157, V2: 5040806596198303681, V3: 1151748423853876189},
TransactionStatusHash: &bc.Hash{V0: 1},
},
}),
},
},
- parent: &state.BlockNode{
- Version: 1,
- Height: 0,
- Timestamp: 1523352600000,
- Hash: bc.Hash{V0: 0},
- },
- err: errMismatchedMerkleRoot,
+ parent: parent,
+ err: errMismatchedMerkleRoot,
},
{
desc: "the coinbase amount is less than the real coinbase amount (txtest#1014)",
Version: 1,
Height: 1,
Timestamp: 1523352601000,
- PreviousBlockId: &bc.Hash{V0: 0},
+ PreviousBlockId: &parentHash,
},
Transactions: []*bc.Tx{
types.MapTx(&types.TxData{
}),
},
},
- parent: &state.BlockNode{
- Version: 1,
- Height: 0,
- Timestamp: 1523352600000,
- Hash: bc.Hash{V0: 0},
- },
- err: ErrWrongCoinbaseTransaction,
+ parent: parent,
+ err: ErrWrongCoinbaseTransaction,
},
}
// TestGasOverBlockLimit check if the gas of the block has the max limit (blocktest#1012)
func TestGasOverBlockLimit(t *testing.T) {
cp, _ := vmutil.DefaultCoinbaseProgram()
- parent := &state.BlockNode{
- Version: 1,
- Height: 0,
- Timestamp: 1523352600000,
- Hash: bc.Hash{V0: 0},
+ parent := &types.BlockHeader{
+ Version: 1,
+ Height: 0,
+ Timestamp: 1523352600000,
+ PreviousBlockHash: bc.Hash{V0: 0},
}
+ parentHash := parent.Hash()
+
block := &bc.Block{
ID: bc.Hash{V0: 1},
BlockHeader: &bc.BlockHeader{
Version: 1,
Height: 1,
Timestamp: 1523352601000,
- PreviousBlockId: &bc.Hash{V0: 0},
+ PreviousBlockId: &parentHash,
TransactionsRoot: &bc.Hash{V0: 1},
},
Transactions: []*bc.Tx{
// TestSetTransactionStatus verify the transaction status is set correctly (blocktest#1010)
func TestSetTransactionStatus(t *testing.T) {
cp, _ := vmutil.DefaultCoinbaseProgram()
- parent := &state.BlockNode{
- Version: 1,
- Height: 0,
- Timestamp: 1523352600000,
- Hash: bc.Hash{V0: 0},
+ parent := &types.BlockHeader{
+ Version: 1,
+ Height: 0,
+ Timestamp: 1523352600000,
+ PreviousBlockHash: bc.Hash{V0: 0},
}
+ parentHash := parent.Hash()
+
block := &bc.Block{
ID: bc.Hash{V0: 1},
BlockHeader: &bc.BlockHeader{
Version: 1,
Height: 1,
Timestamp: 1523352601000,
- PreviousBlockId: &bc.Hash{V0: 0},
+ PreviousBlockId: &parentHash,
TransactionsRoot: &bc.Hash{V0: 12212572290317752069, V1: 8979003395977198825, V2: 3978010681554327084, V3: 12322462500143540195},
TransactionStatusHash: &bc.Hash{V0: 8682965660674182538, V1: 8424137560837623409, V2: 6979974817894224946, V3: 4673809519342015041},
},
},
},
}
- node := blockNode(types.MapBlock(&mockBlocks[0].Block).BlockHeader)
+
+ mockBlockHeader := &mockBlocks[0].Block.BlockHeader
defer os.RemoveAll("temp")
for index, c := range cases {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
for k, v := range c.before {
utxoViewpoint.Entries[k] = v
}
- if err := store.SaveChainStatus(node, node, utxoViewpoint, []*state.VoteResult{}); err != nil {
+ if err := store.SaveChainStatus(mockBlockHeader, mockBlockHeader, []*types.BlockHeader{mockBlockHeader}, utxoViewpoint, []*state.VoteResult{}); err != nil {
t.Error(err)
}
t.Error(err)
}
}
- if err := store.SaveChainStatus(node, node, utxoViewpoint, []*state.VoteResult{}); err != nil {
+ if err := store.SaveChainStatus(mockBlockHeader, mockBlockHeader, []*types.BlockHeader{mockBlockHeader}, utxoViewpoint, []*state.VoteResult{}); err != nil {
t.Error(err)
}
"github.com/vapor/consensus"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/state"
"github.com/vapor/testutil"
)
return t.Tx.ResultIds[outIndex]
}
-func blockNode(header *bc.BlockHeader) *state.BlockNode {
- h := types.BlockHeader{
- Version: header.Version,
- Height: header.Height,
- PreviousBlockHash: *header.PreviousBlockId,
- Timestamp: header.Timestamp,
- }
- return &state.BlockNode{
- Parent: nil,
- Hash: h.Hash(),
- Version: h.Version,
- Height: h.Height,
- Timestamp: h.Timestamp,
- }
-}
-
func mustDecodeHex(str string) []byte {
data, err := hex.DecodeString(str)
if err != nil {