github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/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 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 }