OSDN Git Service

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