OSDN Git Service

Bump golang.org/x/text from 0.3.6 to 0.3.8 in /lib/golang.org/x/net
[bytom/bytom.git] / test / chain_test_util.go
1 package test
2
3 import (
4         "fmt"
5         "os"
6         "time"
7
8         "github.com/bytom/bytom/blockchain/txbuilder"
9         "github.com/bytom/bytom/consensus"
10         "github.com/bytom/bytom/database"
11         dbm "github.com/bytom/bytom/database/leveldb"
12         "github.com/bytom/bytom/database/storage"
13         "github.com/bytom/bytom/protocol"
14         "github.com/bytom/bytom/protocol/bc"
15         "github.com/bytom/bytom/protocol/bc/types"
16         "github.com/bytom/bytom/protocol/vm"
17         "github.com/golang/protobuf/proto"
18 )
19
20 const utxoPrefix = "UT:"
21
22 type chainTestContext struct {
23         Chain *protocol.Chain
24         DB    dbm.DB
25         Store *database.Store
26 }
27
28 func (ctx *chainTestContext) validateStatus(block *types.Block) error {
29         // validate in mainchain
30         if !ctx.Chain.InMainChain(block.Hash()) {
31                 return fmt.Errorf("block %d is not in mainchain", block.Height)
32         }
33
34         // validate chain status and saved block
35         bestBlockHeader := ctx.Chain.BestBlockHeader()
36         chainBlock, err := ctx.Chain.GetBlockByHeight(block.Height)
37         if err != nil {
38                 return err
39         }
40
41         blockHash := block.Hash()
42         if bestBlockHeader.Hash() != blockHash || chainBlock.Hash() != blockHash {
43                 return fmt.Errorf("chain status error")
44         }
45
46         return nil
47 }
48
49 func (ctx *chainTestContext) validateExecution(block *types.Block) error {
50         for _, tx := range block.Transactions {
51                 for _, spentOutputID := range tx.SpentOutputIDs {
52                         utxoEntry, _ := ctx.Store.GetUtxo(&spentOutputID)
53                         if utxoEntry == nil {
54                                 continue
55                         }
56                         if utxoEntry.Type != storage.CoinbaseUTXOType {
57                                 return fmt.Errorf("found non-coinbase spent utxo entry")
58                         }
59                         if !utxoEntry.Spent {
60                                 return fmt.Errorf("utxo entry status should be spent")
61                         }
62                 }
63
64                 for _, outputID := range tx.ResultIds {
65                         utxoEntry, _ := ctx.Store.GetUtxo(outputID)
66                         if utxoEntry == nil && isSpent(outputID, block) {
67                                 continue
68                         }
69                         if utxoEntry.BlockHeight != block.Height {
70                                 return fmt.Errorf("block height error, expected: %d, have: %d", block.Height, utxoEntry.BlockHeight)
71                         }
72                         if utxoEntry.Spent {
73                                 return fmt.Errorf("utxo entry status should not be spent")
74                         }
75                 }
76         }
77         return nil
78 }
79
80 func (ctx *chainTestContext) getUtxoEntries() map[string]*storage.UtxoEntry {
81         utxoEntries := make(map[string]*storage.UtxoEntry)
82         iter := ctx.DB.IteratorPrefix([]byte(utxoPrefix))
83         defer iter.Release()
84
85         for iter.Next() {
86                 utxoEntry := storage.UtxoEntry{}
87                 if err := proto.Unmarshal(iter.Value(), &utxoEntry); err != nil {
88                         return nil
89                 }
90                 key := string(iter.Key())
91                 utxoEntries[key] = &utxoEntry
92         }
93         return utxoEntries
94 }
95
96 func (ctx *chainTestContext) validateRollback(utxoEntries map[string]*storage.UtxoEntry) error {
97         newUtxoEntries := ctx.getUtxoEntries()
98         for key := range utxoEntries {
99                 entry, ok := newUtxoEntries[key]
100                 if !ok {
101                         return fmt.Errorf("can't find utxo after rollback")
102                 }
103                 if entry.Spent != utxoEntries[key].Spent {
104                         return fmt.Errorf("utxo status dismatch after rollback")
105                 }
106         }
107         return nil
108 }
109
110 type chainTestConfig struct {
111         RollbackTo uint64     `json:"rollback_to"`
112         Blocks     []*ctBlock `json:"blocks"`
113 }
114
115 type ctBlock struct {
116         Transactions []*ctTransaction `json:"transactions"`
117         Append       uint64           `json:"append"`
118         Invalid      bool             `json:"invalid"`
119 }
120
121 func (b *ctBlock) createBlock(ctx *chainTestContext) (*types.Block, error) {
122         txs := make([]*types.Tx, 0, len(b.Transactions))
123         for _, t := range b.Transactions {
124                 tx, err := t.createTransaction(ctx, txs)
125                 if err != nil {
126                         return nil, err
127                 }
128                 txs = append(txs, tx)
129         }
130         return NewBlock(ctx.Chain, txs, []byte{byte(vm.OP_TRUE)})
131 }
132
133 type ctTransaction struct {
134         Inputs  []*ctInput `json:"inputs"`
135         Outputs []uint64   `json:"outputs"`
136 }
137
138 type ctInput struct {
139         Height      uint64 `json:"height"`
140         TxIndex     uint64 `json:"tx_index"`
141         OutputIndex uint64 `json:"output_index"`
142 }
143
144 func (input *ctInput) createTxInput(ctx *chainTestContext) (*types.TxInput, error) {
145         block, err := ctx.Chain.GetBlockByHeight(input.Height)
146         if err != nil {
147                 return nil, err
148         }
149
150         spendInput, err := CreateSpendInput(block.Transactions[input.TxIndex], input.OutputIndex)
151         if err != nil {
152                 return nil, err
153         }
154
155         return &types.TxInput{
156                 AssetVersion: assetVersion,
157                 TypedInput:   spendInput,
158         }, nil
159 }
160
161 // create tx input spent previous tx output in the same block
162 func (input *ctInput) createDependencyTxInput(txs []*types.Tx) (*types.TxInput, error) {
163         // sub 1 because of coinbase tx is not included in txs
164         spendInput, err := CreateSpendInput(txs[input.TxIndex-1], input.OutputIndex)
165         if err != nil {
166                 return nil, err
167         }
168
169         return &types.TxInput{
170                 AssetVersion: assetVersion,
171                 TypedInput:   spendInput,
172         }, nil
173 }
174
175 func (t *ctTransaction) createTransaction(ctx *chainTestContext, txs []*types.Tx) (*types.Tx, error) {
176         builder := txbuilder.NewBuilder(time.Now())
177         sigInst := &txbuilder.SigningInstruction{}
178         currentHeight := ctx.Chain.BestBlockHeight()
179         for _, input := range t.Inputs {
180                 var txInput *types.TxInput
181                 var err error
182                 if input.Height == currentHeight+1 {
183                         txInput, err = input.createDependencyTxInput(txs)
184                 } else {
185                         txInput, err = input.createTxInput(ctx)
186                 }
187                 if err != nil {
188                         return nil, err
189                 }
190                 err = builder.AddInput(txInput, sigInst)
191                 if err != nil {
192                         return nil, err
193                 }
194         }
195
196         for _, amount := range t.Outputs {
197                 output := types.NewOriginalTxOutput(*consensus.BTMAssetID, amount, []byte{byte(vm.OP_TRUE)}, nil)
198                 if err := builder.AddOutput(output); err != nil {
199                         return nil, err
200                 }
201         }
202
203         tpl, _, err := builder.Build()
204         if err != nil {
205                 return nil, err
206         }
207
208         txSerialized, err := tpl.Transaction.MarshalText()
209         if err != nil {
210                 return nil, err
211         }
212
213         tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
214         tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
215         return tpl.Transaction, err
216 }
217
218 func (cfg *chainTestConfig) Run() error {
219         db := dbm.NewDB("chain_test_db", "leveldb", "chain_test_db")
220         defer os.RemoveAll("chain_test_db")
221         chain, store, _, err := MockChain(db)
222         if err != nil {
223                 return err
224         }
225         ctx := &chainTestContext{
226                 Chain: chain,
227                 DB:    db,
228                 Store: store,
229         }
230
231         var utxoEntries map[string]*storage.UtxoEntry
232         var rollbackBlock *types.Block
233         for _, blk := range cfg.Blocks {
234                 block, err := blk.createBlock(ctx)
235                 if err != nil {
236                         return err
237                 }
238                 _, err = ctx.Chain.ProcessBlock(block)
239                 if err != nil && blk.Invalid {
240                         continue
241                 }
242                 if err != nil {
243                         return err
244                 }
245                 if err := ctx.validateStatus(block); err != nil {
246                         return err
247                 }
248                 if err := ctx.validateExecution(block); err != nil {
249                         return err
250                 }
251                 if block.Height <= cfg.RollbackTo && cfg.RollbackTo <= block.Height+blk.Append {
252                         utxoEntries = ctx.getUtxoEntries()
253                         rollbackBlock = block
254                 }
255                 if err := AppendBlocks(ctx.Chain, blk.Append); err != nil {
256                         return err
257                 }
258         }
259
260         if rollbackBlock == nil {
261                 return nil
262         }
263
264         // rollback and validate
265         forkedChain, err := declChain("forked_chain", ctx.Chain, rollbackBlock.Height, ctx.Chain.BestBlockHeight()+1)
266         defer os.RemoveAll("forked_chain")
267         if err != nil {
268                 return err
269         }
270
271         if err := merge(forkedChain, ctx.Chain); err != nil {
272                 return err
273         }
274         return ctx.validateRollback(utxoEntries)
275 }
276
277 // if the output(hash) was spent in block
278 func isSpent(hash *bc.Hash, block *types.Block) bool {
279         for _, tx := range block.Transactions {
280                 for _, spendOutputID := range tx.SpentOutputIDs {
281                         if spendOutputID == *hash {
282                                 return true
283                         }
284                 }
285         }
286
287         return false
288 }