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 }