OSDN Git Service

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