OSDN Git Service

2d data for butxo state (#1921)
[bytom/bytom.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/bytom/account"
11         "github.com/bytom/bytom/blockchain/txbuilder"
12         "github.com/bytom/bytom/consensus"
13         "github.com/bytom/bytom/errors"
14         "github.com/bytom/bytom/protocol"
15         "github.com/bytom/bytom/protocol/bc"
16         "github.com/bytom/bytom/protocol/bc/types"
17         "github.com/bytom/bytom/protocol/state"
18         "github.com/bytom/bytom/protocol/validation"
19         "github.com/bytom/bytom/protocol/vm/vmutil"
20 )
21
22 const (
23         logModule     = "proposal"
24         batchApplyNum = 16
25         softMaxTxNum  = 128
26
27         timeoutOk = iota + 1
28         timeoutWarn
29         timeoutCritical
30 )
31
32 // NewBlockTemplate returns a new block template that is ready to be solved
33 func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
34         builder := newBlockBuilder(chain, accountManager, timestamp, warnDuration, criticalDuration)
35         return builder.build()
36 }
37
38 type blockBuilder struct {
39         chain          *protocol.Chain
40         accountManager *account.Manager
41
42         block    *types.Block
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:      make([]byte, protocol.SignatureLength),
61                 },
62         }
63
64         builder := &blockBuilder{
65                 chain:             chain,
66                 accountManager:    accountManager,
67                 block:             block,
68                 utxoView:          state.NewUtxoViewpoint(),
69                 warnTimeoutCh:     time.After(warnDuration),
70                 criticalTimeoutCh: time.After(criticalDuration),
71                 gasLeft:           int64(consensus.MaxBlockGas),
72                 timeoutStatus:     timeoutOk,
73         }
74         return builder
75 }
76
77 func (b *blockBuilder) build() (*types.Block, error) {
78         if err := b.applyCoinbaseTransaction(); err != nil {
79                 return nil, err
80         }
81
82         if err := b.applyTransactionFromPool(); err != nil {
83                 return nil, err
84         }
85
86         if err := b.calculateBlockCommitment(); err != nil {
87                 return nil, err
88         }
89
90         b.chain.SignBlockHeader(&b.block.BlockHeader)
91         return b.block, nil
92 }
93
94 func (b *blockBuilder) applyCoinbaseTransaction() error {
95         coinbaseTx, err := b.createCoinbaseTx()
96         if err != nil {
97                 return errors.Wrap(err, "fail on create coinbase tx")
98         }
99
100         gasState, err := validation.ValidateTx(coinbaseTx.Tx, &bc.Block{BlockHeader: &bc.BlockHeader{Height: b.block.Height}, Transactions: []*bc.Tx{coinbaseTx.Tx}}, b.chain.ProgramConverter)
101         if err != nil {
102                 return err
103         }
104
105         b.block.Transactions = append(b.block.Transactions, coinbaseTx)
106         b.gasLeft -= gasState.GasUsed
107         return nil
108 }
109
110 func (b *blockBuilder) applyTransactionFromPool() error {
111         txDescList := b.chain.GetTxPool().GetTransactions()
112         sort.Sort(byTime(txDescList))
113
114         poolTxs := make([]*types.Tx, len(txDescList))
115         for i, txDesc := range txDescList {
116                 poolTxs[i] = txDesc.Tx
117         }
118
119         return b.applyTransactions(poolTxs, timeoutWarn)
120 }
121
122 func (b *blockBuilder) calculateBlockCommitment() (err error) {
123         var txEntries []*bc.Tx
124         for _, tx := range b.block.Transactions {
125                 txEntries = append(txEntries, tx.Tx)
126         }
127
128         b.block.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
129         if err != nil {
130                 return err
131         }
132
133         return nil
134 }
135
136 // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
137 // based on the passed block height to the provided address.  When the address
138 // is nil, the coinbase transaction will instead be redeemable by anyone.
139 func (b *blockBuilder) createCoinbaseTx() (tx *types.Tx, err error) {
140         arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(b.block.Height, 10))...)
141         var script []byte
142         if b.accountManager == nil {
143                 script, err = vmutil.DefaultCoinbaseProgram()
144         } else {
145                 script, err = b.accountManager.GetCoinbaseControlProgram()
146                 arbitrary = append(arbitrary, b.accountManager.GetCoinbaseArbitrary()...)
147         }
148         if err != nil {
149                 return nil, err
150         }
151
152         if len(arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
153                 return nil, validation.ErrCoinbaseArbitraryOversize
154         }
155
156         builder := txbuilder.NewBuilder(time.Now())
157         if err = builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
158                 return nil, err
159         }
160
161         if err = builder.AddOutput(types.NewOriginalTxOutput(*consensus.BTMAssetID, 0, script, [][]byte{})); err != nil {
162                 return nil, err
163         }
164         //TODO: calculate reward to proposer
165
166         _, txData, err := builder.Build()
167         if err != nil {
168                 return nil, err
169         }
170
171         byteData, err := txData.MarshalText()
172         if err != nil {
173                 return nil, err
174         }
175
176         txData.SerializedSize = uint64(len(byteData))
177         tx = &types.Tx{
178                 TxData: *txData,
179                 Tx:     types.MapTx(txData),
180         }
181         return tx, nil
182 }
183
184 func (b *blockBuilder) applyTransactions(txs []*types.Tx, timeoutStatus uint8) error {
185         batchTxs := []*types.Tx{}
186         for i := 0; i < len(txs); i++ {
187                 if batchTxs = append(batchTxs, txs[i]); len(batchTxs) < batchApplyNum && i != len(txs)-1 {
188                         continue
189                 }
190
191                 results, gasLeft := b.preValidateTxs(batchTxs, b.chain, b.utxoView, b.gasLeft)
192                 for _, result := range results {
193                         if result.err != nil {
194                                 log.WithFields(log.Fields{"module": logModule, "error": result.err}).Error("propose block generation: skip tx due to")
195                                 b.chain.GetTxPool().RemoveTransaction(&result.tx.ID)
196                                 continue
197                         }
198
199                         b.block.Transactions = append(b.block.Transactions, result.tx)
200                 }
201
202                 b.gasLeft = gasLeft
203                 batchTxs = batchTxs[:0]
204                 if b.getTimeoutStatus() >= timeoutStatus || len(b.block.Transactions) > softMaxTxNum {
205                         break
206                 }
207         }
208         return nil
209 }
210
211 type validateTxResult struct {
212         tx  *types.Tx
213         err error
214 }
215
216 func (b *blockBuilder) preValidateTxs(txs []*types.Tx, chain *protocol.Chain, view *state.UtxoViewpoint, gasLeft int64) ([]*validateTxResult, int64) {
217         var results []*validateTxResult
218         bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: chain.BestBlockHeight() + 1}}
219         bcTxs := make([]*bc.Tx, len(txs))
220         for i, tx := range txs {
221                 bcTxs[i] = tx.Tx
222         }
223
224         validateResults := validation.ValidateTxs(bcTxs, bcBlock, b.chain.ProgramConverter)
225         for i := 0; i < len(validateResults) && gasLeft > 0; i++ {
226                 gasStatus := validateResults[i].GetGasState()
227                 if err := validateResults[i].GetError(); err != nil {
228                         results = append(results, &validateTxResult{tx: txs[i], err: err})
229                         continue
230                 }
231
232                 if err := chain.GetTransactionsUtxo(view, []*bc.Tx{bcTxs[i]}); err != nil {
233                         results = append(results, &validateTxResult{tx: txs[i], err: err})
234                         continue
235                 }
236
237                 if gasLeft-gasStatus.GasUsed < 0 {
238                         break
239                 }
240
241                 if err := view.ApplyTransaction(bcBlock, bcTxs[i]); err != nil {
242                         results = append(results, &validateTxResult{tx: txs[i], err: err})
243                         continue
244                 }
245
246                 results = append(results, &validateTxResult{tx: txs[i], err: validateResults[i].GetError()})
247                 gasLeft -= gasStatus.GasUsed
248         }
249         return results, gasLeft
250 }
251
252 func (b *blockBuilder) getTimeoutStatus() uint8 {
253         if b.timeoutStatus == timeoutCritical {
254                 return b.timeoutStatus
255         }
256
257         select {
258         case <-b.criticalTimeoutCh:
259                 b.timeoutStatus = timeoutCritical
260         case <-b.warnTimeoutCh:
261                 b.timeoutStatus = timeoutWarn
262         default:
263         }
264
265         return b.timeoutStatus
266 }