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  }