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  }