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 }