OSDN Git Service

92c6c11ced3a22586dd2dbd97a8074807c4c062a
[bytom/bytom.git] / protocol / block.go
1 package protocol
2
3 import (
4         log "github.com/sirupsen/logrus"
5
6         "github.com/bytom/errors"
7         "github.com/bytom/protocol/bc"
8         "github.com/bytom/protocol/bc/types"
9         "github.com/bytom/protocol/state"
10         "github.com/bytom/protocol/validation"
11 )
12
13 var (
14         // ErrBadBlock is returned when a block is invalid.
15         ErrBadBlock = errors.New("invalid block")
16
17         // ErrBadStateRoot is returned when the computed assets merkle root
18         // disagrees with the one declared in a block header.
19         ErrBadStateRoot = errors.New("invalid state merkle root")
20 )
21
22 // BlockExist check is a block in chain or orphan
23 func (c *Chain) BlockExist(hash *bc.Hash) bool {
24         return c.orphanManage.BlockExist(hash) || c.store.BlockExist(hash)
25 }
26
27 // GetBlockByHash return a block by given hash
28 func (c *Chain) GetBlockByHash(hash *bc.Hash) (*types.Block, error) {
29         return c.store.GetBlock(hash)
30 }
31
32 // GetBlockByHeight return a block by given height
33 func (c *Chain) GetBlockByHeight(height uint64) (*types.Block, error) {
34         c.state.cond.L.Lock()
35         hash, ok := c.state.mainChain[height]
36         c.state.cond.L.Unlock()
37         if !ok {
38                 return nil, errors.New("can't find block in given hight")
39         }
40         return c.GetBlockByHash(hash)
41 }
42
43 // ConnectBlock append block to end of chain
44 func (c *Chain) ConnectBlock(block *types.Block) error {
45         c.state.cond.L.Lock()
46         defer c.state.cond.L.Unlock()
47         return c.connectBlock(block)
48 }
49
50 func (c *Chain) connectBlock(block *types.Block) (err error) {
51         bcBlock := types.MapBlock(block)
52         utxoView := state.NewUtxoViewpoint()
53         bcBlock.TransactionStatus, err = c.store.GetTransactionStatus(&bcBlock.ID)
54         if err != nil {
55                 return err
56         }
57
58         if err := c.store.GetTransactionsUtxo(utxoView, bcBlock.Transactions); err != nil {
59                 return err
60         }
61         if err := utxoView.ApplyBlock(bcBlock, bcBlock.TransactionStatus); err != nil {
62                 return err
63         }
64
65         blockHash := block.Hash()
66         if err := c.setState(block, utxoView, map[uint64]*bc.Hash{block.Height: &blockHash}); err != nil {
67                 return err
68         }
69
70         for _, tx := range block.Transactions {
71                 c.txPool.RemoveTransaction(&tx.Tx.ID)
72         }
73         return nil
74 }
75
76 func (c *Chain) getReorganizeBlocks(block *types.Block) ([]*types.Block, []*types.Block) {
77         attachBlocks := []*types.Block{}
78         detachBlocks := []*types.Block{}
79         ancestor := block
80
81         for !c.inMainchain(ancestor) {
82                 attachBlocks = append([]*types.Block{ancestor}, attachBlocks...)
83                 ancestor, _ = c.GetBlockByHash(&ancestor.PreviousBlockHash)
84         }
85
86         for d := c.state.block; d.Hash() != ancestor.Hash(); d, _ = c.GetBlockByHash(&d.PreviousBlockHash) {
87                 detachBlocks = append(detachBlocks, d)
88         }
89
90         return attachBlocks, detachBlocks
91 }
92
93 func (c *Chain) reorganizeChain(block *types.Block) error {
94         attachBlocks, detachBlocks := c.getReorganizeBlocks(block)
95         utxoView := state.NewUtxoViewpoint()
96         chainChanges := map[uint64]*bc.Hash{}
97
98         for _, d := range detachBlocks {
99                 detachBlock := types.MapBlock(d)
100                 if err := c.store.GetTransactionsUtxo(utxoView, detachBlock.Transactions); err != nil {
101                         return err
102                 }
103                 txStatus, err := c.GetTransactionStatus(&detachBlock.ID)
104                 if err != nil {
105                         return err
106                 }
107                 if err := utxoView.DetachBlock(detachBlock, txStatus); err != nil {
108                         return err
109                 }
110         }
111
112         for _, a := range attachBlocks {
113                 attachBlock := types.MapBlock(a)
114                 if err := c.store.GetTransactionsUtxo(utxoView, attachBlock.Transactions); err != nil {
115                         return err
116                 }
117                 txStatus, err := c.GetTransactionStatus(&attachBlock.ID)
118                 if err != nil {
119                         return err
120                 }
121
122                 if err := utxoView.ApplyBlock(attachBlock, txStatus); err != nil {
123                         return err
124                 }
125                 chainChanges[a.Height] = &attachBlock.ID
126         }
127
128         return c.setState(block, utxoView, chainChanges)
129 }
130
131 // SaveBlock will validate and save block into storage
132 func (c *Chain) SaveBlock(block *types.Block) error {
133         preBlock, _ := c.GetBlockByHash(&block.PreviousBlockHash)
134
135         blockEnts := types.MapBlock(block)
136         prevEnts := types.MapBlock(preBlock)
137
138         seed, err := c.GetSeed(block.Height, &block.PreviousBlockHash)
139         if err != nil {
140                 return err
141         }
142
143         if err := validation.ValidateBlock(blockEnts, prevEnts, seed, c.store); err != nil {
144                 return errors.Sub(ErrBadBlock, err)
145         }
146
147         if err := c.store.SaveBlock(block, blockEnts.TransactionStatus, seed); err != nil {
148                 return err
149         }
150
151         blockHash := block.Hash()
152         log.WithFields(log.Fields{"height": block.Height, "hash": blockHash.String()}).Info("Block saved on disk")
153         return nil
154 }
155
156 func (c *Chain) findBestChainTail(block *types.Block) (bestBlock *types.Block) {
157         bestBlock = block
158         blockHash := block.Hash()
159         preorphans, ok := c.orphanManage.preOrphans[blockHash]
160         if !ok {
161                 return
162         }
163
164         for _, preorphan := range preorphans {
165                 orphanBlock, ok := c.orphanManage.Get(preorphan)
166                 if !ok {
167                         continue
168                 }
169
170                 if err := c.SaveBlock(orphanBlock); err != nil {
171                         log.WithFields(log.Fields{
172                                 "height": block.Height,
173                                 "hash":   blockHash.String(),
174                         }).Errorf("findBestChainTail fail on save block %v", err)
175                         continue
176                 }
177
178                 if subResult := c.findBestChainTail(orphanBlock); subResult.Height > bestBlock.Height {
179                         bestBlock = subResult
180                 }
181         }
182
183         c.orphanManage.Delete(&blockHash)
184         return
185 }
186
187 // ProcessBlock is the entry for handle block insert
188 func (c *Chain) ProcessBlock(block *types.Block) (bool, error) {
189         blockHash := block.Hash()
190         if c.BlockExist(&blockHash) {
191                 log.WithField("hash", blockHash.String()).Info("Skip process due to block already been handled")
192                 return false, nil
193         }
194         if !c.store.BlockExist(&block.PreviousBlockHash) {
195                 c.orphanManage.Add(block)
196                 return true, nil
197         }
198         if err := c.SaveBlock(block); err != nil {
199                 return false, err
200         }
201
202         bestBlock := c.findBestChainTail(block)
203         c.state.cond.L.Lock()
204         defer c.state.cond.L.Unlock()
205         if c.state.block.Hash() == bestBlock.PreviousBlockHash {
206                 return false, c.connectBlock(bestBlock)
207         }
208
209         if bestBlock.Height > c.state.block.Height && bestBlock.Bits >= c.state.block.Bits {
210                 return false, c.reorganizeChain(bestBlock)
211         }
212
213         return false, nil
214 }