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