Unverified Commit e0c9f7a3 authored by Paladz's avatar Paladz Committed by GitHub

Merge pull request #1743 from Bytom/dev

Dev
parents 881935c9 8d564428
......@@ -209,6 +209,9 @@ func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) e
if a.AssetId.IsZero() {
missing = append(missing, "asset_id")
}
if a.AssetAmount.Amount == 0 {
missing = append(missing, "amount")
}
if len(missing) > 0 {
return txbuilder.MissingFieldsError(missing...)
}
......
......@@ -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
}
......@@ -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)
......
......@@ -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
}
......
......@@ -94,6 +94,10 @@ func (m *MiningPool) GetWork() (*types.BlockHeader, error) {
// SubmitWork will try to submit the result to the blockchain
func (m *MiningPool) SubmitWork(bh *types.BlockHeader) error {
if bh == nil {
return errors.New("can't submit empty block")
}
reply := make(chan error, 1)
m.submitCh <- &submitBlockMsg{blockHeader: bh, reply: reply}
err := <-reply
......
......@@ -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()
......
......@@ -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 {
......
......@@ -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]
}
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)
}
}
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
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment