OSDN Git Service

6a115a7a4478841218b5665940353e0bb6e690d6
[bytom/vapor.git] / proposal / proposal.go
1 package proposal
2
3 import (
4         "sort"
5         "strconv"
6         "time"
7
8         log "github.com/sirupsen/logrus"
9
10         "github.com/bytom/vapor/account"
11         "github.com/bytom/vapor/blockchain/txbuilder"
12         "github.com/bytom/vapor/consensus"
13         "github.com/bytom/vapor/errors"
14         "github.com/bytom/vapor/protocol"
15         "github.com/bytom/vapor/protocol/bc"
16         "github.com/bytom/vapor/protocol/bc/types"
17         "github.com/bytom/vapor/protocol/state"
18         "github.com/bytom/vapor/protocol/validation"
19         "github.com/bytom/vapor/protocol/vm/vmutil"
20 )
21
22 const (
23         logModule     = "mining"
24         batchApplyNum = 64
25
26         timeoutOk = iota + 1
27         timeoutWarn
28         timeoutCritical
29 )
30
31 // NewBlockTemplate returns a new block template that is ready to be solved
32 func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
33         builder := newBlockBuilder(chain, accountManager, timestamp, warnDuration, criticalDuration)
34         return builder.build()
35 }
36
37 type blockBuilder struct {
38         chain          *protocol.Chain
39         accountManager *account.Manager
40
41         block    *types.Block
42         txStatus *bc.TransactionStatus
43         utxoView *state.UtxoViewpoint
44
45         warnTimeoutCh     <-chan time.Time
46         criticalTimeoutCh <-chan time.Time
47         timeoutStatus     uint8
48         gasLeft           int64
49 }
50
51 func newBlockBuilder(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) *blockBuilder {
52         preBlockHeader := chain.BestBlockHeader()
53         block := &types.Block{
54                 BlockHeader: types.BlockHeader{
55                         Version:           1,
56                         Height:            preBlockHeader.Height + 1,
57                         PreviousBlockHash: preBlockHeader.Hash(),
58                         Timestamp:         timestamp,
59                         BlockCommitment:   types.BlockCommitment{},
60                         BlockWitness:      types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
61                 },
62         }
63
64         builder := &blockBuilder{
65                 chain:             chain,
66                 accountManager:    accountManager,
67                 block:             block,
68                 txStatus:          bc.NewTransactionStatus(),
69                 utxoView:          state.NewUtxoViewpoint(),
70                 warnTimeoutCh:     time.After(warnDuration),
71                 criticalTimeoutCh: time.After(criticalDuration),
72                 gasLeft:           int64(consensus.ActiveNetParams.MaxBlockGas),
73                 timeoutStatus:     timeoutOk,
74         }
75         return builder
76 }
77
78 func (b *blockBuilder) applyCoinbaseTransaction() error {
79         coinbaseTx, err := b.createCoinbaseTx()
80         if err != nil {
81                 return errors.Wrap(err, "fail on create coinbase tx")
82         }
83
84         gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: b.block.Height}, Transactions: []*bc.Tx{coinbaseTx.Tx}})
85         if err != nil {
86                 return err
87         }
88
89         b.block.Transactions = append(b.block.Transactions, coinbaseTx)
90         if err := b.txStatus.SetStatus(0, false); err != nil {
91                 return err
92         }
93
94         b.gasLeft -= gasState.GasUsed
95         return nil
96 }
97 func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) error {
98         tempTxs := []*types.Tx{}
99         for i := 0; i < len(txs); i++ {
100                 if tempTxs = append(tempTxs, txs[i]); len(tempTxs) < batchApplyNum && i != len(txs)-1 {
101                         continue
102                 }
103
104                 results, gasLeft := preValidateTxs(tempTxs, b.chain, b.utxoView, b.gasLeft)
105                 for _, result := range results {
106                         if result.err != nil && !result.gasOnly {
107                                 log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("mining block generation: skip tx due to")
108                                 b.chain.GetTxPool().RemoveTransaction(&result.tx.ID)
109                                 continue
110                         }
111
112                         if err := b.txStatus.SetStatus(len(b.block.Transactions), result.gasOnly); err != nil {
113                                 return err
114                         }
115
116                         b.block.Transactions = append(b.block.Transactions, result.tx)
117                 }
118
119                 b.gasLeft = gasLeft
120                 tempTxs = []*types.Tx{}
121                 if b.getTimeoutStatus() >= timeoutStatus {
122                         break
123                 }
124         }
125         return nil
126 }
127
128 func (b *blockBuilder) applyTransactionFromPool() error {
129         txDescList := b.chain.GetTxPool().GetTransactions()
130         sort.Sort(byTime(txDescList))
131
132         poolTxs := make([]*types.Tx, len(txDescList))
133         for i, txDesc := range txDescList {
134                 poolTxs[i] = txDesc.Tx
135         }
136
137         return b.applyTransactions(poolTxs, timeoutWarn)
138 }
139
140 func (b *blockBuilder) applyTransactionFromSubProtocol() error {
141         cp, err := b.accountManager.GetCoinbaseControlProgram()
142         if err != nil {
143                 return err
144         }
145
146         isTimeout := func() bool {
147                 return b.getTimeoutStatus() > timeoutOk
148         }
149
150         for i, p := range b.chain.SubProtocols() {
151                 if b.gasLeft <= 0 || isTimeout() {
152                         break
153                 }
154
155                 subTxs, err := p.BeforeProposalBlock(b.block.Transactions, cp, b.block.Height, b.gasLeft, isTimeout)
156                 if err != nil {
157                         log.WithFields(log.Fields{"module": logModule, "index": i, "error": err}).Error("failed on sub protocol txs package")
158                         continue
159                 }
160
161                 if err := b.applyTransactions(subTxs, timeoutCritical); err != nil {
162                         return err
163                 }
164         }
165         return nil
166 }
167
168 func (b *blockBuilder) build() (*types.Block, error) {
169         if err := b.applyCoinbaseTransaction(); err != nil {
170                 return nil, err
171         }
172
173         if err := b.applyTransactionFromPool(); err != nil {
174                 return nil, err
175         }
176
177         if err := b.applyTransactionFromSubProtocol(); err != nil {
178                 return nil, err
179         }
180
181         if err := b.calcBlockCommitment(); err != nil {
182                 return nil, err
183         }
184
185         if err := b.chain.SignBlockHeader(&b.block.BlockHeader); err != nil {
186                 return nil, err
187         }
188
189         return b.block, nil
190 }
191
192 func (b *blockBuilder) calcBlockCommitment() (err error) {
193         var txEntries []*bc.Tx
194         for _, tx := range b.block.Transactions {
195                 txEntries = append(txEntries, tx.Tx)
196         }
197
198         b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
199         if err != nil {
200                 return err
201         }
202
203         b.block.BlockHeader.BlockCommitment.TransactionStatusHash, err = types.TxStatusMerkleRoot(b.txStatus.VerifyStatus)
204         return err
205 }
206
207 // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
208 // based on the passed block height to the provided address.  When the address
209 // is nil, the coinbase transaction will instead be redeemable by anyone.
210 func (b *blockBuilder) createCoinbaseTx() (*types.Tx, error) {
211         consensusResult, err := b.chain.GetConsensusResultByHash(&b.block.PreviousBlockHash)
212         if err != nil {
213                 return nil, err
214         }
215
216         rewards, err := consensusResult.GetCoinbaseRewards(b.block.Height - 1)
217         if err != nil {
218                 return nil, err
219         }
220
221         return createCoinbaseTxByReward(b.accountManager, b.block.Height, rewards)
222 }
223
224 func (b *blockBuilder) getTimeoutStatus() uint8 {
225         if b.timeoutStatus == timeoutCritical {
226                 return b.timeoutStatus
227         }
228
229         select {
230         case <-b.criticalTimeoutCh:
231                 b.timeoutStatus = timeoutCritical
232         case <-b.warnTimeoutCh:
233                 b.timeoutStatus = timeoutWarn
234         default:
235         }
236
237         return b.timeoutStatus
238 }
239
240 func createCoinbaseTxByReward(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
241         arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(blockHeight, 10))...)
242         var script []byte
243         if accountManager == nil {
244                 script, err = vmutil.DefaultCoinbaseProgram()
245         } else {
246                 script, err = accountManager.GetCoinbaseControlProgram()
247                 arbitrary = append(arbitrary, accountManager.GetCoinbaseArbitrary()...)
248         }
249         if err != nil {
250                 return nil, err
251         }
252
253         if len(arbitrary) > consensus.ActiveNetParams.CoinbaseArbitrarySizeLimit {
254                 return nil, validation.ErrCoinbaseArbitraryOversize
255         }
256
257         builder := txbuilder.NewBuilder(time.Now())
258         if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
259                 return nil, err
260         }
261         if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, 0, script)); err != nil {
262                 return nil, err
263         }
264
265         for _, r := range rewards {
266                 if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram)); err != nil {
267                         return nil, err
268                 }
269         }
270
271         _, txData, err := builder.Build()
272         if err != nil {
273                 return nil, err
274         }
275
276         byteData, err := txData.MarshalText()
277         if err != nil {
278                 return nil, err
279         }
280
281         txData.SerializedSize = uint64(len(byteData))
282         tx = &types.Tx{
283                 TxData: *txData,
284                 Tx:     types.MapTx(txData),
285         }
286         return tx, nil
287 }
288
289 type validateTxResult struct {
290         tx      *types.Tx
291         gasOnly bool
292         err     error
293 }
294
295 func preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
296         var results []*validateTxResult
297
298         bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
299         bcTxs := make([]*bc.Tx, len(txs))
300         for i, tx := range txs {
301                 bcTxs[i] = tx.Tx
302         }
303
304         validateResults := validation.ValidateTxs(bcTxs, bcBlock)
305         for i := 0; i < len(validateResults) && gasLeft > 0; i++ {
306                 gasOnlyTx := false
307                 gasStatus := validateResults[i].GetGasState()
308                 if err := validateResults[i].GetError(); err != nil {
309                         if !gasStatus.GasValid {
310                                 results = append(results, &validateTxResult{tx: txs[i], err: err})
311                                 continue
312                         }
313                         gasOnlyTx = true
314                 }
315
316                 if err := chain.GetTransactionsUtxo(view, []*bc.Tx{bcTxs[i]}); err != nil {
317                         results = append(results, &validateTxResult{tx: txs[i], err: err})
318                         continue
319                 }
320
321                 if gasLeft-gasStatus.GasUsed < 0 {
322                         break
323                 }
324
325                 if err := view.ApplyTransaction(bcBlock, bcTxs[i], gasOnlyTx); err != nil {
326                         results = append(results, &validateTxResult{tx: txs[i], err: err})
327                         continue
328                 }
329
330                 if err := validateBySubProtocols(txs[i], validateResults[i].GetError() != nil, chain.SubProtocols()); err != nil {
331                         results = append(results, &validateTxResult{tx: txs[i], err: err})
332                         continue
333                 }
334
335                 results = append(results, &validateTxResult{tx: txs[i], gasOnly: gasOnlyTx, err: validateResults[i].GetError()})
336                 gasLeft -= gasStatus.GasUsed
337         }
338         return results, gasLeft
339 }
340
341 func validateBySubProtocols(tx *types.Tx, statusFail bool, subProtocols []protocol.Protocoler) error {
342         for _, subProtocol := range subProtocols {
343                 verifyResult := &bc.TxVerifyResult{StatusFail: statusFail}
344                 if err := subProtocol.ValidateTx(tx, verifyResult); err != nil {
345                         return err
346                 }
347         }
348         return nil
349 }