OSDN Git Service

Process block integra test (#1740)
authormuscle_boy <shenao.78@163.com>
Thu, 25 Apr 2019 11:17:21 +0000 (19:17 +0800)
committerPaladz <yzhu101@uottawa.ca>
Thu, 25 Apr 2019 11:17:21 +0000 (19:17 +0800)
* block integration test

* fix case

* format code

* change func name

* change func name

database/store.go
database/store_test.go
database/utxo_view.go
protocol/orphan_manage.go
protocol/protocol.go
protocol/state/blockindex.go
test/integration/block_integration_test.go [new file with mode: 0644]
test/integration/block_integration_util.go [new file with mode: 0644]

index 7e7031b..9fbad6c 100644 (file)
@@ -21,14 +21,14 @@ import (
 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
        }
@@ -47,24 +47,24 @@ type Store struct {
        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
        }
@@ -108,7 +108,7 @@ func (s *Store) GetTransactionsUtxo(view *state.UtxoViewpoint, txs []*bc.Tx) err
 
 // 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")
        }
@@ -128,7 +128,7 @@ func (s *Store) GetStoreStatus() *protocol.BlockStoreState {
 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
@@ -188,9 +188,9 @@ func (s *Store) SaveBlock(block *types.Block, ts *bc.TransactionStatus) error {
 
        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{
@@ -214,7 +214,7 @@ func (s *Store) SaveChainStatus(node *state.BlockNode, view *state.UtxoViewpoint
                return err
        }
 
-       batch.Set(blockStoreKey, bytes)
+       batch.Set(BlockStoreKey, bytes)
        batch.Write()
        return nil
 }
index f0da5ba..795d248 100644 (file)
@@ -212,7 +212,7 @@ func TestSaveBlock(t *testing.T) {
                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)
index 032a2f8..a9e1e3f 100644 (file)
@@ -9,10 +9,10 @@ import (
        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 {
@@ -22,7 +22,7 @@ func getTransactionsUtxo(db dbm.DB, view *state.UtxoViewpoint, txs []*bc.Tx) err
                                continue
                        }
 
-                       data := db.Get(calcUtxoKey(&prevout))
+                       data := db.Get(CalcUtxoKey(&prevout))
                        if data == nil {
                                continue
                        }
@@ -41,7 +41,7 @@ func getTransactionsUtxo(db dbm.DB, view *state.UtxoViewpoint, txs []*bc.Tx) err
 
 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")
        }
@@ -54,7 +54,7 @@ func getUtxo(db dbm.DB, hash *bc.Hash) (*storage.UtxoEntry, error) {
 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
                }
 
@@ -62,7 +62,7 @@ func saveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
                if err != nil {
                        return errors.Wrap(err, "marshaling utxo entry")
                }
-               batch.Set(calcUtxoKey(&key), b)
+               batch.Set(CalcUtxoKey(&key), b)
        }
        return nil
 }
index d250269..de15989 100644 (file)
@@ -8,6 +8,7 @@ import (
 
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/types"
+       "github.com/bytom/testutil"
 )
 
 var (
@@ -39,14 +40,6 @@ func NewOrphanManage() *OrphanManage {
        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()
@@ -68,6 +61,14 @@ func (o *OrphanManage) Add(block *types.Block) {
        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()
@@ -75,6 +76,13 @@ func (o *OrphanManage) Delete(hash *bc.Hash) {
        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()
index aa24aff..5f62bf6 100644 (file)
@@ -28,8 +28,12 @@ type Chain struct {
 
 // 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),
@@ -124,6 +128,10 @@ func (c *Chain) CalcNextBits(preBlock *bc.Hash) (uint64, error) {
        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 {
index 4d35fb7..7af5318 100644 (file)
@@ -11,6 +11,7 @@ import (
        "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
@@ -133,6 +134,10 @@ func NewBlockIndex() *BlockIndex {
        }
 }
 
+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()
@@ -140,13 +145,6 @@ func (bi *BlockIndex) AddNode(node *BlockNode) {
        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()
@@ -161,6 +159,20 @@ func (bi *BlockIndex) BlockExist(hash *bc.Hash) bool {
        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()
@@ -173,13 +185,6 @@ func (bi *BlockIndex) InMainchain(hash bc.Hash) bool {
        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()
@@ -210,3 +215,10 @@ func (bi *BlockIndex) SetMainChain(node *BlockNode) {
                node = node.Parent
        }
 }
+
+func (bi *BlockIndex) nodeByHeight(height uint64) *BlockNode {
+       if height >= uint64(len(bi.mainChain)) {
+               return nil
+       }
+       return bi.mainChain[height]
+}
diff --git a/test/integration/block_integration_test.go b/test/integration/block_integration_test.go
new file mode 100644 (file)
index 0000000..da37b5a
--- /dev/null
@@ -0,0 +1,89 @@
+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)
+       }
+}
diff --git a/test/integration/block_integration_util.go b/test/integration/block_integration_util.go
new file mode 100644 (file)
index 0000000..8b80018
--- /dev/null
@@ -0,0 +1,230 @@
+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
+}