OSDN Git Service

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