gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/consensus/block_validation.go (about)

     1  package consensus
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  
     8  	"gitlab.com/SiaPrime/SiaPrime/modules"
     9  	"gitlab.com/SiaPrime/SiaPrime/persist"
    10  	"gitlab.com/SiaPrime/SiaPrime/types"
    11  )
    12  
    13  var (
    14  	errBadMinerPayouts        = errors.New("miner payout sum does not equal block subsidy")
    15  	errEarlyTimestamp         = errors.New("block timestamp is too early")
    16  	errExtremeFutureTimestamp = errors.New("block timestamp too far in future, discarded")
    17  	errFutureTimestamp        = errors.New("block timestamp too far in future, but saved for later use")
    18  	errLargeBlock             = errors.New("block is too large to be accepted")
    19  )
    20  
    21  // blockValidator validates a Block against a set of block validity rules.
    22  type blockValidator interface {
    23  	// ValidateBlock validates a block against a minimum timestamp, a block
    24  	// target, and a block height.
    25  	ValidateBlock(types.Block, types.BlockID, types.Timestamp, types.Target, types.BlockHeight, *persist.Logger) error
    26  }
    27  
    28  // stdBlockValidator is the standard implementation of blockValidator.
    29  type stdBlockValidator struct {
    30  	// clock is a Clock interface that indicates the current system time.
    31  	clock types.Clock
    32  
    33  	// marshaler encodes and decodes between objects and byte slices.
    34  	marshaler marshaler
    35  }
    36  
    37  // NewBlockValidator creates a new stdBlockValidator with default settings.
    38  func NewBlockValidator() stdBlockValidator {
    39  	return stdBlockValidator{
    40  		clock:     types.StdClock{},
    41  		marshaler: stdMarshaler{},
    42  	}
    43  }
    44  
    45  // checkMinerPayouts compares a block's miner payouts to the block's subsidy and
    46  // returns true if they are equal.
    47  func checkMinerPayoutsWithoutDevFund(b types.Block, height types.BlockHeight) bool {
    48  	// Add up the payouts and check that all values are legal.
    49  	var payoutSum types.Currency
    50  	for _, payout := range b.MinerPayouts {
    51  		if payout.Value.IsZero() {
    52  			return false
    53  		}
    54  		payoutSum = payoutSum.Add(payout.Value)
    55  	}
    56  	return b.CalculateSubsidy(height).Equals(payoutSum)
    57  }
    58  
    59  // checkMinerPayouts compares a block's miner payouts to the block's subsidy and
    60  // returns true if they are equal.
    61  func checkMinerPayoutsWithDevFund(b types.Block, height types.BlockHeight) bool {
    62  	// Make sure we have enough payouts to cover both miner subsidy
    63  	// and the dev fund
    64  	if len(b.MinerPayouts) < 2 {
    65  		return false
    66  	}
    67  	// Add up the payouts and check that all values are legal.
    68  	var minerPayoutSum types.Currency
    69  	for _, minerPayout := range b.MinerPayouts[:len(b.MinerPayouts)-1] {
    70  		if minerPayout.Value.IsZero() {
    71  			return false
    72  		}
    73  		minerPayoutSum = minerPayoutSum.Add(minerPayout.Value)
    74  	}
    75  	// The last payout in a block is for the dev fund
    76  	devSubsidyPayout := b.MinerPayouts[len(b.MinerPayouts)-1]
    77  	// Make sure the dev subsidy is correct
    78  	minerBlockSubsidy, devBlockSubsidy := b.CalculateSubsidies(height)
    79  	if !devSubsidyPayout.Value.Equals(devBlockSubsidy) {
    80  		return false
    81  	}
    82  
    83  	// TODO Keep this or remove?
    84  	subsidyUnlockHash := types.DevFundUnlockHash
    85  	if types.BurnAddressBlockHeight != types.BlockHeight(0) && height >= types.BurnAddressBlockHeight {
    86  		subsidyUnlockHash = types.BurnAddressUnlockHash
    87  	}
    88  	if bytes.Compare(devSubsidyPayout.UnlockHash[:], subsidyUnlockHash[:]) != 0 {
    89  		return false
    90  	}
    91  
    92  	// Finally, make sure the miner subsidy is correct
    93  	return minerBlockSubsidy.Equals(minerPayoutSum)
    94  }
    95  
    96  // check the height vs DevFundInitialBlockHeight. If it is less then use
    97  // checkMinerPayoutsWithoutDevFund to compare a block's miner payouts to
    98  // the block's subsidy and returns true if they are equal. Otherwise use
    99  // checkMinerPayoutsWithDevFund to compare a block's miner payouts to the
   100  // block's subsidy and returns true if they are equal.
   101  func checkMinerPayouts(b types.Block, height types.BlockHeight) bool {
   102  	// If soft fork has occured
   103  	if types.DevFundEnabled && height >= types.DevFundInitialBlockHeight {
   104  		return checkMinerPayoutsWithDevFund(b, height)
   105  	}
   106  	return checkMinerPayoutsWithoutDevFund(b, height)
   107  }
   108  
   109  // checkTarget returns true if the block's ID meets the given target.
   110  func checkTarget(b types.Block, id types.BlockID, target types.Target) bool {
   111  	return bytes.Compare(target[:], id[:]) >= 0
   112  }
   113  
   114  // ValidateBlock validates a block against a minimum timestamp, a block target,
   115  // and a block height. Returns nil if the block is valid and an appropriate
   116  // error otherwise.
   117  func (bv stdBlockValidator) ValidateBlock(b types.Block, id types.BlockID, minTimestamp types.Timestamp, target types.Target, height types.BlockHeight, log *persist.Logger) error {
   118  	// Check that the timestamp is not too far in the past to be acceptable.
   119  	if minTimestamp > b.Timestamp {
   120  		return errEarlyTimestamp
   121  	}
   122  
   123  	// Check that the nonce is a legal nonce.
   124  	if height >= types.ASICHardforkHeight && binary.LittleEndian.Uint64(b.Nonce[:])%types.ASICHardforkFactor != 0 {
   125  		return errors.New("block does not meet nonce requirements")
   126  	}
   127  	// Check that the target of the new block is sufficient.
   128  	if !checkTarget(b, id, target) {
   129  		return modules.ErrBlockUnsolved
   130  	}
   131  
   132  	// Check that the block is below the size limit.
   133  	blockSize := len(bv.marshaler.Marshal(b))
   134  	if uint64(blockSize) > types.BlockSizeLimit {
   135  		return errLargeBlock
   136  	}
   137  
   138  	// Check if the block is in the extreme future. We make a distinction between
   139  	// future and extreme future because there is an assumption that by the time
   140  	// the extreme future arrives, this block will no longer be a part of the
   141  	// longest fork because it will have been ignored by all of the miners.
   142  	if b.Timestamp > bv.clock.Now()+types.ExtremeFutureThreshold {
   143  		return errExtremeFutureTimestamp
   144  	}
   145  
   146  	// Verify that the miner payouts are valid.
   147  	if !checkMinerPayouts(b, height) {
   148  		return errBadMinerPayouts
   149  	}
   150  
   151  	// Check if the block is in the near future, but too far to be acceptable.
   152  	// This is the last check because it's an expensive check, and not worth
   153  	// performing if the payouts are incorrect.
   154  	if b.Timestamp > bv.clock.Now()+types.FutureThreshold {
   155  		return errFutureTimestamp
   156  	}
   157  
   158  	if log != nil {
   159  		log.Debugf("validated block at height %v, block size: %vB", height, blockSize)
   160  	}
   161  	return nil
   162  }