const logModule = "leveldb"
var (
- blockStoreKey = []byte("blockStore")
- blockPrefix = []byte("B:")
- blockHeaderPrefix = []byte("BH:")
- txStatusPrefix = []byte("BTS:")
+ BlockStoreKey = []byte("blockStore")
+ BlockPrefix = []byte("B:")
+ BlockHeaderPrefix = []byte("BH:")
+ TxStatusPrefix = []byte("BTS:")
)
func loadBlockStoreStateJSON(db dbm.DB) *protocol.BlockStoreState {
- bytes := db.Get(blockStoreKey)
+ bytes := db.Get(BlockStoreKey)
if bytes == nil {
return nil
}
cache blockCache
}
-func calcBlockKey(hash *bc.Hash) []byte {
- return append(blockPrefix, hash.Bytes()...)
+func CalcBlockKey(hash *bc.Hash) []byte {
+ return append(BlockPrefix, hash.Bytes()...)
}
-func calcBlockHeaderKey(height uint64, hash *bc.Hash) []byte {
+func CalcBlockHeaderKey(height uint64, hash *bc.Hash) []byte {
buf := [8]byte{}
binary.BigEndian.PutUint64(buf[:], height)
- key := append(blockHeaderPrefix, buf[:]...)
+ key := append(BlockHeaderPrefix, buf[:]...)
return append(key, hash.Bytes()...)
}
-func calcTxStatusKey(hash *bc.Hash) []byte {
- return append(txStatusPrefix, hash.Bytes()...)
+func CalcTxStatusKey(hash *bc.Hash) []byte {
+ return append(TxStatusPrefix, hash.Bytes()...)
}
// GetBlock return the block by given hash
func GetBlock(db dbm.DB, hash *bc.Hash) (*types.Block, error) {
- bytez := db.Get(calcBlockKey(hash))
+ bytez := db.Get(CalcBlockKey(hash))
if bytez == nil {
return nil, nil
}
// GetTransactionStatus will return the utxo that related to the block hash
func (s *Store) GetTransactionStatus(hash *bc.Hash) (*bc.TransactionStatus, error) {
- data := s.db.Get(calcTxStatusKey(hash))
+ data := s.db.Get(CalcTxStatusKey(hash))
if data == nil {
return nil, errors.New("can't find the transaction status by given hash")
}
func (s *Store) LoadBlockIndex(stateBestHeight uint64) (*state.BlockIndex, error) {
startTime := time.Now()
blockIndex := state.NewBlockIndex()
- bhIter := s.db.IteratorPrefix(blockHeaderPrefix)
+ bhIter := s.db.IteratorPrefix(BlockHeaderPrefix)
defer bhIter.Release()
var lastNode *state.BlockNode
blockHash := block.Hash()
batch := s.db.NewBatch()
- batch.Set(calcBlockKey(&blockHash), binaryBlock)
- batch.Set(calcBlockHeaderKey(block.Height, &blockHash), binaryBlockHeader)
- batch.Set(calcTxStatusKey(&blockHash), binaryTxStatus)
+ batch.Set(CalcBlockKey(&blockHash), binaryBlock)
+ batch.Set(CalcBlockHeaderKey(block.Height, &blockHash), binaryBlockHeader)
+ batch.Set(CalcTxStatusKey(&blockHash), binaryTxStatus)
batch.Write()
log.WithFields(log.Fields{
return err
}
- batch.Set(blockStoreKey, bytes)
+ batch.Set(BlockStoreKey, bytes)
batch.Write()
return nil
}
t.Errorf("got status:%v, expect status:%v", gotStatus, status)
}
- data := store.db.Get(calcBlockHeaderKey(block.Height, &blockHash))
+ data := store.db.Get(CalcBlockHeaderKey(block.Height, &blockHash))
gotBlockHeader := types.BlockHeader{}
if err := gotBlockHeader.UnmarshalText(data); err != nil {
t.Fatal(err)
dbm "github.com/bytom/database/leveldb"
)
-const utxoPreFix = "UT:"
+const UtxoPreFix = "UT:"
-func calcUtxoKey(hash *bc.Hash) []byte {
- return []byte(utxoPreFix + hash.String())
+func CalcUtxoKey(hash *bc.Hash) []byte {
+ return []byte(UtxoPreFix + hash.String())
}
func getTransactionsUtxo(db dbm.DB, view *state.UtxoViewpoint, txs []*bc.Tx) error {
continue
}
- data := db.Get(calcUtxoKey(&prevout))
+ data := db.Get(CalcUtxoKey(&prevout))
if data == nil {
continue
}
func getUtxo(db dbm.DB, hash *bc.Hash) (*storage.UtxoEntry, error) {
var utxo storage.UtxoEntry
- data := db.Get(calcUtxoKey(hash))
+ data := db.Get(CalcUtxoKey(hash))
if data == nil {
return nil, errors.New("can't find utxo in db")
}
func saveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
for key, entry := range view.Entries {
if entry.Spent && !entry.IsCoinBase {
- batch.Delete(calcUtxoKey(&key))
+ batch.Delete(CalcUtxoKey(&key))
continue
}
if err != nil {
return errors.Wrap(err, "marshaling utxo entry")
}
- batch.Set(calcUtxoKey(&key), b)
+ batch.Set(CalcUtxoKey(&key), b)
}
return nil
}
"github.com/bytom/protocol/bc"
"github.com/bytom/protocol/bc/types"
+ "github.com/bytom/testutil"
)
var (
return o
}
-// BlockExist check is the block in OrphanManage
-func (o *OrphanManage) BlockExist(hash *bc.Hash) bool {
- o.mtx.RLock()
- _, ok := o.orphan[*hash]
- o.mtx.RUnlock()
- return ok
-}
-
// Add will add the block to OrphanManage
func (o *OrphanManage) Add(block *types.Block) {
blockHash := block.Hash()
log.WithFields(log.Fields{"module": logModule, "hash": blockHash.String(), "height": block.Height}).Info("add block to orphan")
}
+// BlockExist check is the block in OrphanManage
+func (o *OrphanManage) BlockExist(hash *bc.Hash) bool {
+ o.mtx.RLock()
+ _, ok := o.orphan[*hash]
+ o.mtx.RUnlock()
+ return ok
+}
+
// Delete will delete the block from OrphanManage
func (o *OrphanManage) Delete(hash *bc.Hash) {
o.mtx.Lock()
o.delete(hash)
}
+func (o *OrphanManage) Equals(o1 *OrphanManage) bool {
+ if o1 == nil {
+ return false
+ }
+ return testutil.DeepEqual(o.orphan, o1.orphan) && testutil.DeepEqual(o.prevOrphans, o1.prevOrphans)
+}
+
// Get return the orphan block by hash
func (o *OrphanManage) Get(hash *bc.Hash) (*types.Block, bool) {
o.mtx.RLock()
// NewChain returns a new Chain using store as the underlying storage.
func NewChain(store Store, txPool *TxPool) (*Chain, error) {
+ return NewChainWithOrphanManage(store, txPool, NewOrphanManage())
+}
+
+func NewChainWithOrphanManage(store Store, txPool *TxPool, manage *OrphanManage) (*Chain, error) {
c := &Chain{
- orphanManage: NewOrphanManage(),
+ orphanManage: manage,
txPool: txPool,
store: store,
processBlockCh: make(chan *processBlockMsg, maxProcessBlockChSize),
return node.CalcNextBits(), nil
}
+func (c *Chain) GetBlockIndex() *state.BlockIndex {
+ return c.index
+}
+
// This function must be called with mu lock in above level
func (c *Chain) setState(node *state.BlockNode, view *state.UtxoViewpoint) error {
if err := c.store.SaveChainStatus(node, view); err != nil {
"github.com/bytom/consensus/difficulty"
"github.com/bytom/protocol/bc"
"github.com/bytom/protocol/bc/types"
+ "github.com/bytom/testutil"
)
// approxNodesPerDay is an approximation of the number of new blocks there are
}
}
+func NewBlockIndexWithInitData(index map[bc.Hash]*BlockNode, mainChain []*BlockNode) *BlockIndex {
+ return &BlockIndex{index: index, mainChain: mainChain}
+}
+
// AddNode will add node to the index map
func (bi *BlockIndex) AddNode(node *BlockNode) {
bi.Lock()
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 ok
}
+func (bi *BlockIndex) Equals(bi1 *BlockIndex) bool {
+ if bi1 == nil {
+ return false
+ }
+ return testutil.DeepEqual(bi.index, bi1.index) && testutil.DeepEqual(bi.mainChain, bi1.mainChain)
+}
+
+// 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]
+}
+
// TODO: THIS FUNCTION MIGHT BE DELETED
func (bi *BlockIndex) InMainchain(hash bc.Hash) bool {
bi.RLock()
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()
node = node.Parent
}
}
+
+func (bi *BlockIndex) nodeByHeight(height uint64) *BlockNode {
+ if height >= uint64(len(bi.mainChain)) {
+ return nil
+ }
+ return bi.mainChain[height]
+}
--- /dev/null
+package integration
+
+import (
+ "testing"
+
+ "github.com/bytom/config"
+ "github.com/bytom/database"
+ "github.com/bytom/database/storage"
+ "github.com/bytom/protocol"
+ "github.com/bytom/protocol/bc"
+ "github.com/bytom/protocol/bc/types"
+ "github.com/bytom/protocol/state"
+)
+
+func TestProcessBlock(t *testing.T) {
+ gensisBlock := config.GenesisBlock()
+ genesisBlockHash := gensisBlock.Hash()
+ fillTransactionSize(gensisBlock)
+
+ cases := []*processBlockTestCase{
+ {
+ desc: "process a invalid block",
+ newBlock: &types.Block{
+ BlockHeader: types.BlockHeader{
+ Height: 1,
+ Version: 1,
+ PreviousBlockHash: genesisBlockHash,
+ },
+ },
+ wantStore: storeItems{
+ {
+ key: database.BlockStoreKey,
+ val: &protocol.BlockStoreState{Height: 0, Hash: &genesisBlockHash},
+ },
+ {
+ key: database.CalcBlockKey(&genesisBlockHash),
+ val: gensisBlock,
+ },
+ {
+ key: database.CalcTxStatusKey(&genesisBlockHash),
+ val: &bc.TransactionStatus{Version: 1, VerifyStatus: []*bc.TxVerifyResult{{StatusFail: false}}},
+ },
+ {
+ key: database.CalcBlockHeaderKey(gensisBlock.Height, &genesisBlockHash),
+ val: gensisBlock.BlockHeader,
+ },
+ {
+ key: database.CalcUtxoKey(gensisBlock.Transactions[0].Tx.ResultIds[0]),
+ val: &storage.UtxoEntry{IsCoinBase: true, BlockHeight: 0, Spent: false},
+ },
+ },
+ wantBlockIndex: state.NewBlockIndexWithInitData(
+ map[bc.Hash]*state.BlockNode{
+ genesisBlockHash: mustNewBlockNode(&gensisBlock.BlockHeader, nil),
+ },
+ []*state.BlockNode{
+ mustNewBlockNode(&gensisBlock.BlockHeader, nil),
+ },
+ ),
+ wantOrphanManage: protocol.NewOrphanManage(),
+ wantError: true,
+ },
+ }
+
+ for _, c := range cases {
+ if err := c.Run(); err != nil {
+ panic(err)
+ }
+ }
+}
+
+func mustNewBlockNode(h *types.BlockHeader, parent *state.BlockNode) *state.BlockNode {
+ node, err := state.NewBlockNode(h, parent)
+ if err != nil {
+ panic(err)
+ }
+ return node
+}
+
+func fillTransactionSize(block *types.Block) {
+ for _, tx := range block.Transactions {
+ bytes, err := tx.MarshalText()
+ if err != nil {
+ panic(err)
+ }
+ tx.TxData.SerializedSize = uint64(len(bytes) / 2)
+ tx.Tx.SerializedSize = uint64(len(bytes) / 2)
+ }
+}
--- /dev/null
+package integration
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "reflect"
+ "strings"
+
+ "github.com/golang/protobuf/proto"
+
+ "github.com/bytom/database"
+ dbm "github.com/bytom/database/leveldb"
+ "github.com/bytom/database/storage"
+ "github.com/bytom/event"
+ "github.com/bytom/protocol"
+ "github.com/bytom/protocol/bc"
+ "github.com/bytom/protocol/bc/types"
+ "github.com/bytom/protocol/state"
+ "github.com/bytom/testutil"
+)
+
+const (
+ dbDir = "temp"
+)
+
+type storeItem struct {
+ key []byte
+ val interface{}
+}
+
+type serialFun func(obj interface{}) ([]byte, error)
+type deserialFun func(data []byte) (interface{}, error)
+
+func getSerialFun(item interface{}) (serialFun, error) {
+ switch item.(type) {
+ case *protocol.BlockStoreState:
+ return json.Marshal, nil
+ case *types.Block:
+ return func(obj interface{}) ([]byte, error) {
+ block := obj.(*types.Block)
+ return block.MarshalText()
+ }, nil
+ case types.BlockHeader:
+ return func(obj interface{}) ([]byte, error) {
+ bh := obj.(types.BlockHeader)
+ return bh.MarshalText()
+ }, nil
+ case *bc.TransactionStatus:
+ return func(obj interface{}) ([]byte, error) {
+ status := obj.(*bc.TransactionStatus)
+ return proto.Marshal(status)
+ }, nil
+ case *storage.UtxoEntry:
+ return func(obj interface{}) ([]byte, error) {
+ utxo := obj.(*storage.UtxoEntry)
+ return proto.Marshal(utxo)
+ }, nil
+ }
+ typ := reflect.TypeOf(item)
+ return nil, fmt.Errorf("can not found any serialization function for type:%s", typ.Name())
+}
+
+func getDeserialFun(key []byte) (deserialFun, error) {
+ funMap := map[string]deserialFun{
+ string(database.BlockStoreKey): func(data []byte) (interface{}, error) {
+ storeState := &protocol.BlockStoreState{}
+ err := json.Unmarshal(data, storeState)
+ return storeState, err
+ },
+ string(database.TxStatusPrefix): func(data []byte) (interface{}, error) {
+ status := &bc.TransactionStatus{}
+ err := proto.Unmarshal(data, status)
+ return status, err
+ },
+ string(database.BlockPrefix): func(data []byte) (interface{}, error) {
+ block := &types.Block{}
+ err := block.UnmarshalText(data)
+ return block, err
+ },
+ string(database.BlockHeaderPrefix): func(data []byte) (interface{}, error) {
+ bh := types.BlockHeader{}
+ err := bh.UnmarshalText(data)
+ return bh, err
+ },
+ database.UtxoPreFix: func(data []byte) (interface{}, error) {
+ utxo := &storage.UtxoEntry{}
+ err := proto.Unmarshal(data, utxo)
+ return utxo, err
+ },
+ }
+
+ for prefix, converter := range funMap {
+ if strings.HasPrefix(string(key), prefix) {
+ return converter, nil
+ }
+ }
+ return nil, fmt.Errorf("can not found any deserialization function for key:%s", string(key))
+}
+
+type storeItems []*storeItem
+
+func (s1 storeItems) equals(s2 storeItems) bool {
+ if s2 == nil {
+ return false
+ }
+
+ itemMap1 := make(map[string]interface{}, len(s1))
+ for _, item := range s1 {
+ itemMap1[string(item.key)] = item.val
+ }
+
+ itemMap2 := make(map[string]interface{}, len(s2))
+ for _, item := range s2 {
+ itemMap2[string(item.key)] = item.val
+ }
+
+ return testutil.DeepEqual(itemMap1, itemMap2)
+}
+
+type processBlockTestCase struct {
+ desc string
+ initStore []*storeItem
+ wantStore []*storeItem
+ wantBlockIndex *state.BlockIndex
+ initOrphanManage *protocol.OrphanManage
+ wantOrphanManage *protocol.OrphanManage
+ wantIsOrphan bool
+ wantError bool
+ newBlock *types.Block
+}
+
+func (p *processBlockTestCase) Run() error {
+ defer os.RemoveAll(dbDir)
+ if p.initStore == nil {
+ p.initStore = make([]*storeItem, 0)
+ }
+ store, db, err := initStore(p)
+ if err != nil {
+ return err
+ }
+
+ orphanManage := p.initOrphanManage
+ if orphanManage == nil {
+ orphanManage = protocol.NewOrphanManage()
+ }
+
+ txPool := protocol.NewTxPool(store, event.NewDispatcher())
+ chain, err := protocol.NewChainWithOrphanManage(store, txPool, orphanManage)
+ if err != nil {
+ return err
+ }
+
+ isOrphan, err := chain.ProcessBlock(p.newBlock)
+ if p.wantError != (err != nil) {
+ return fmt.Errorf("#case(%s) want error:%t, got error:%t", p.desc, p.wantError, err != nil)
+ }
+
+ if isOrphan != p.wantIsOrphan {
+ return fmt.Errorf("#case(%s) want orphan:%t, got orphan:%t", p.desc, p.wantIsOrphan, isOrphan)
+ }
+
+ if p.wantStore != nil {
+ gotStoreItems, err := loadStoreItems(db)
+ if err != nil {
+ return err
+ }
+
+ if !storeItems(gotStoreItems).equals(p.wantStore) {
+ return fmt.Errorf("#case(%s) want store:%v, got store:%v", p.desc, p.wantStore, gotStoreItems)
+ }
+ }
+
+ if p.wantBlockIndex != nil {
+ blockIndex := chain.GetBlockIndex()
+ if !blockIndex.Equals(p.wantBlockIndex) {
+ return fmt.Errorf("#case(%s) want block index:%v, got block index:%v", p.desc, *p.wantBlockIndex, *blockIndex)
+ }
+ }
+
+ if p.wantOrphanManage != nil {
+ if !orphanManage.Equals(p.wantOrphanManage) {
+ return fmt.Errorf("#case(%s) want orphan manage:%v, got orphan manage:%v", p.desc, *p.wantOrphanManage, *orphanManage)
+ }
+ }
+ return nil
+}
+
+func loadStoreItems(db dbm.DB) ([]*storeItem, error) {
+ iter := db.Iterator()
+ defer iter.Release()
+
+ var items []*storeItem
+ for iter.Next() {
+ item := &storeItem{key: iter.Key()}
+ fun, err := getDeserialFun(iter.Key())
+ if err != nil {
+ return nil, err
+ }
+
+ val, err := fun(iter.Value())
+ if err != nil {
+ return nil, err
+ }
+
+ item.val = val
+ items = append(items, item)
+ }
+ return items, nil
+}
+
+func initStore(c *processBlockTestCase) (protocol.Store, dbm.DB, error) {
+ testDB := dbm.NewDB("testdb", "leveldb", dbDir)
+ batch := testDB.NewBatch()
+ for _, item := range c.initStore {
+ fun, err := getSerialFun(item.val)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ bytes, err := fun(item.val)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ batch.Set(item.key, bytes)
+ }
+ batch.Write()
+ return database.NewStore(testDB), testDB, nil
+}