github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/protocol/validation/block.go (about)

     1  package validation
     2  
     3  import (
     4  	"encoding/hex"
     5  	"time"
     6  
     7  	log "github.com/sirupsen/logrus"
     8  
     9  	"github.com/bytom/bytom/consensus"
    10  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    11  	"github.com/bytom/bytom/errors"
    12  	"github.com/bytom/bytom/protocol/bc/types"
    13  	"github.com/bytom/bytom/protocol/state"
    14  )
    15  
    16  const logModule = "validation"
    17  
    18  var (
    19  	errBadTimestamp          = errors.New("block timestamp is not in the valid range")
    20  	errBadBits               = errors.New("block bits is invalid")
    21  	errMismatchedBlock       = errors.New("mismatched block")
    22  	errMismatchedMerkleRoot  = errors.New("mismatched merkle root")
    23  	errMisorderedBlockHeight = errors.New("misordered block height")
    24  	errOverBlockLimit        = errors.New("block's gas is over the limit")
    25  	errVersionRegression     = errors.New("version regression")
    26  )
    27  
    28  func checkBlockTime(b, parent *types.BlockHeader) error {
    29  	now := uint64(time.Now().UnixNano() / 1e6)
    30  	if b.Timestamp < (parent.Timestamp + consensus.ActiveNetParams.BlockTimeInterval) {
    31  		return errBadTimestamp
    32  	}
    33  	if b.Timestamp > (now + consensus.ActiveNetParams.MaxTimeOffsetMs) {
    34  		return errBadTimestamp
    35  	}
    36  
    37  	return nil
    38  }
    39  
    40  func checkCoinbaseAmount(b *types.Block, checkpoint *state.Checkpoint) error {
    41  	if len(b.Transactions) == 0 {
    42  		return errors.Wrap(ErrWrongCoinbaseTransaction, "block is empty")
    43  	}
    44  
    45  	tx := b.Transactions[0]
    46  	for _, output := range tx.Outputs {
    47  		if output.OutputType() != types.OriginalOutputType || *output.AssetId != *consensus.BTMAssetID {
    48  			return errors.Wrap(ErrWrongCoinbaseTransaction, "dismatch output type or asset")
    49  		}
    50  	}
    51  
    52  	if b.Height%consensus.ActiveNetParams.BlocksOfEpoch != 1 {
    53  		if len(tx.Outputs) != 1 || tx.Outputs[0].Amount != 0 {
    54  			return errors.Wrap(ErrWrongCoinbaseTransaction, "dismatch output number or amount")
    55  		}
    56  		return nil
    57  	}
    58  
    59  	return checkoutRewardCoinbase(tx, checkpoint)
    60  }
    61  
    62  func checkoutRewardCoinbase(tx *types.Tx, checkpoint *state.Checkpoint) error {
    63  	outputMap := map[string]uint64{}
    64  	for i, output := range tx.Outputs {
    65  		if i == 0 && output.Amount == 0 {
    66  			continue
    67  		}
    68  
    69  		outputMap[hex.EncodeToString(output.ControlProgram)] += output.Amount
    70  	}
    71  
    72  	if len(outputMap) != len(checkpoint.Rewards) {
    73  		return errors.Wrap(ErrWrongCoinbaseTransaction, "dismatch output number")
    74  	}
    75  
    76  	for cp, amount := range checkpoint.Rewards {
    77  		if outputMap[cp] != amount {
    78  			return errors.Wrap(ErrWrongCoinbaseTransaction, "dismatch output amount")
    79  		}
    80  	}
    81  	return nil
    82  }
    83  
    84  // ValidateBlockHeader check the block's header
    85  func ValidateBlockHeader(b, parent *types.BlockHeader, checkpoint *state.Checkpoint) error {
    86  	if b.Version != 1 {
    87  		return errors.WithDetailf(errVersionRegression, "previous block verson %d, current block version %d", parent.Version, b.Version)
    88  	}
    89  
    90  	if b.Height != parent.Height+1 {
    91  		return errors.WithDetailf(errMisorderedBlockHeight, "previous block height %d, current block height %d", parent.Height, b.Height)
    92  	}
    93  
    94  	if parentHash := parent.Hash(); parentHash != b.PreviousBlockHash {
    95  		return errors.WithDetailf(errMismatchedBlock, "previous block ID %x, current block wants %x", parentHash.Bytes(), b.PreviousBlockHash)
    96  	}
    97  
    98  	if err := checkBlockTime(b, parent); err != nil {
    99  		return err
   100  	}
   101  
   102  	return verifyBlockSignature(b, checkpoint)
   103  }
   104  
   105  func verifyBlockSignature(blockHeader *types.BlockHeader, checkpoint *state.Checkpoint) error {
   106  	validator := checkpoint.GetValidator(blockHeader.Timestamp)
   107  	xPub := chainkd.XPub{}
   108  	pubKey, err := hex.DecodeString(validator.PubKey)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	copy(xPub[:], pubKey)
   114  	if ok := xPub.Verify(blockHeader.Hash().Bytes(), blockHeader.BlockWitness); !ok {
   115  		return errors.New("fail to verify block header signature")
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // ValidateBlock validates a block and the transactions within.
   122  func ValidateBlock(b *types.Block, parent *types.BlockHeader, checkpoint *state.Checkpoint, converter ProgramConverterFunc) error {
   123  	startTime := time.Now()
   124  	if err := ValidateBlockHeader(&b.BlockHeader, parent, checkpoint); err != nil {
   125  		return err
   126  	}
   127  
   128  	bcBlock := types.MapBlock(b)
   129  	blockGasSum := uint64(0)
   130  	validateResults := ValidateTxs(bcBlock.Transactions, bcBlock, converter)
   131  	for i, validateResult := range validateResults {
   132  		if validateResult.err != nil {
   133  			return errors.Wrapf(validateResult.err, "validate of transaction %d of %d", i, len(b.Transactions))
   134  		}
   135  
   136  		if blockGasSum += uint64(validateResult.gasStatus.GasUsed); blockGasSum > consensus.MaxBlockGas {
   137  			return errOverBlockLimit
   138  		}
   139  	}
   140  
   141  	if err := checkCoinbaseAmount(b, checkpoint); err != nil {
   142  		return err
   143  	}
   144  
   145  	txMerkleRoot, err := types.TxMerkleRoot(bcBlock.Transactions)
   146  	if err != nil {
   147  		return errors.Wrap(err, "computing transaction id merkle root")
   148  	}
   149  	if txMerkleRoot != b.TransactionsMerkleRoot {
   150  		return errors.WithDetailf(errMismatchedMerkleRoot, "transaction id merkle root")
   151  	}
   152  
   153  	log.WithFields(log.Fields{
   154  		"module":   logModule,
   155  		"height":   b.Height,
   156  		"hash":     bcBlock.ID.String(),
   157  		"duration": time.Since(startTime),
   158  	}).Debug("finish validate block")
   159  	return nil
   160  }