gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/consensus/block_validation_test.go (about) 1 package consensus 2 3 import ( 4 "testing" 5 6 "gitlab.com/SiaPrime/SiaPrime/types" 7 ) 8 9 // mockMarshaler is a mock implementation of the encoding.GenericMarshaler 10 // interface that allows the client to pre-define the length of the marshaled 11 // data. 12 type mockMarshaler struct { 13 marshalLength uint64 14 } 15 16 // Marshal marshals an object into an empty byte slice of marshalLength. 17 func (m mockMarshaler) Marshal(interface{}) []byte { 18 return make([]byte, m.marshalLength) 19 } 20 21 // Unmarshal is not implemented. 22 func (m mockMarshaler) Unmarshal([]byte, interface{}) error { 23 panic("not implemented") 24 } 25 26 // mockClock is a mock implementation of the types.Clock interface that allows 27 // the client to pre-define a return value for Now(). 28 type mockClock struct { 29 now types.Timestamp 30 } 31 32 // Now returns mockClock's pre-defined Timestamp. 33 func (c mockClock) Now() types.Timestamp { 34 return c.now 35 } 36 37 var validateBlockTests = []struct { 38 now types.Timestamp 39 minTimestamp types.Timestamp 40 blockTimestamp types.Timestamp 41 blockSize uint64 42 errWant error 43 msg string 44 }{ 45 { 46 minTimestamp: types.Timestamp(5), 47 blockTimestamp: types.Timestamp(4), 48 errWant: errEarlyTimestamp, 49 msg: "ValidateBlock should reject blocks with timestamps that are too early", 50 }, 51 { 52 blockSize: types.BlockSizeLimit + 1, 53 errWant: errLargeBlock, 54 msg: "ValidateBlock should reject excessively large blocks", 55 }, 56 { 57 now: types.Timestamp(50), 58 blockTimestamp: types.Timestamp(50) + types.ExtremeFutureThreshold + 1, 59 errWant: errExtremeFutureTimestamp, 60 msg: "ValidateBlock should reject blocks timestamped in the extreme future", 61 }, 62 } 63 64 // TestUnitValidateBlock runs a series of unit tests for ValidateBlock. 65 func TestUnitValidateBlock(t *testing.T) { 66 // TODO(mtlynch): Populate all parameters to ValidateBlock so that everything 67 // is valid except for the attribute that causes validation to fail. (i.e. 68 // don't assume an ordering to the implementation of the validation function). 69 for _, tt := range validateBlockTests { 70 b := types.Block{ 71 Timestamp: tt.blockTimestamp, 72 } 73 blockValidator := stdBlockValidator{ 74 marshaler: mockMarshaler{ 75 marshalLength: tt.blockSize, 76 }, 77 clock: mockClock{ 78 now: tt.now, 79 }, 80 } 81 err := blockValidator.ValidateBlock(b, b.ID(), tt.minTimestamp, types.RootDepth, 0, nil) 82 if err != tt.errWant { 83 t.Errorf("%s: got %v, want %v", tt.msg, err, tt.errWant) 84 } 85 } 86 } 87 88 // TestCheckMinerPayoutsWithoutDevFee probes the checkMinerPayouts function. 89 func TestCheckMinerPayoutsWithoutDevFee(t *testing.T) { 90 // All tests are done at height = 0 91 height := types.BlockHeight(0) 92 coinbase := types.CalculateCoinbase(height) 93 devFundEnabled := types.DevFundEnabled 94 devFundInitialBlockHeight := types.DevFundInitialBlockHeight 95 devFundDecayStartBlockHeight := uint64(types.DevFundDecayStartBlockHeight) 96 devFundDecayEndBlockHeight := uint64(types.DevFundDecayEndBlockHeight) 97 devFundInitialPercentage := types.DevFundInitialPercentage 98 devFundFinalPercentage := types.DevFundFinalPercentage 99 devFundPercentageRange := devFundInitialPercentage - devFundFinalPercentage 100 devFundDecayPercentage := uint64(100) 101 if uint64(height) >= devFundDecayEndBlockHeight { 102 devFundDecayPercentage = uint64(0) 103 } else if uint64(height) >= devFundDecayStartBlockHeight { 104 devFundDecayPercentage = uint64(100) - (uint64(height)-devFundDecayStartBlockHeight)*uint64(100)/(devFundDecayEndBlockHeight-devFundDecayStartBlockHeight) 105 } 106 devFundPercentage := devFundFinalPercentage*uint64(100) + devFundPercentageRange*devFundDecayPercentage 107 devSubsidy := coinbase.MulFloat(0) 108 if devFundEnabled && height >= devFundInitialBlockHeight { 109 devSubsidy = coinbase.Mul(types.NewCurrency64(devFundPercentage).Div(types.NewCurrency64(10000))) 110 } 111 minerSubsidy := coinbase.Sub(devSubsidy) 112 113 // Create a block with a single coinbase payout, and no dev fund payout. 114 b := types.Block{ 115 MinerPayouts: []types.SiacoinOutput{ 116 {Value: minerSubsidy}, 117 }, 118 } 119 if !checkMinerPayouts(b, 0) { 120 t.Error("payouts evaluated incorrectly when there is only one payout.") 121 } 122 123 // Try a block with an incorrect payout. 124 b = types.Block{ 125 MinerPayouts: []types.SiacoinOutput{ 126 {Value: minerSubsidy.Sub(types.NewCurrency64(1))}, 127 }, 128 } 129 if checkMinerPayouts(b, 0) { 130 t.Error("payouts evaluated incorrectly when there is a too-small payout") 131 } 132 133 // Try a block with 2 payouts. 134 b = types.Block{ 135 MinerPayouts: []types.SiacoinOutput{ 136 {Value: minerSubsidy.Sub(types.NewCurrency64(1))}, 137 {Value: types.NewCurrency64(1)}, 138 }, 139 } 140 if !checkMinerPayouts(b, 0) { 141 t.Error("payouts evaluated incorrectly when there are 2 payouts") 142 } 143 144 // Try a block with 2 payouts that are too large. 145 b = types.Block{ 146 MinerPayouts: []types.SiacoinOutput{ 147 {Value: minerSubsidy}, 148 {Value: minerSubsidy}, 149 }, 150 } 151 if checkMinerPayouts(b, 0) { 152 t.Error("payouts evaluated incorrectly when there are two large payouts") 153 } 154 155 // Create a block with an empty payout. 156 b = types.Block{ 157 MinerPayouts: []types.SiacoinOutput{ 158 {Value: minerSubsidy}, 159 {}, 160 }, 161 } 162 if checkMinerPayouts(b, 0) { 163 t.Error("payouts evaluated incorrectly when there is only one payout.") 164 } 165 } 166 167 // TestCheckMinerPayoutsWithDevFee probes the checkMinerPayouts function. 168 func TestCheckMinerPayoutsWithDevFee(t *testing.T) { 169 // All tests are done at height = 1. 170 height := types.BlockHeight(80000) 171 coinbase := types.CalculateCoinbase(height) 172 devFundEnabled := types.DevFundEnabled 173 devFundInitialBlockHeight := types.DevFundInitialBlockHeight 174 devFundDecayStartBlockHeight := uint64(types.DevFundDecayStartBlockHeight) 175 devFundDecayEndBlockHeight := uint64(types.DevFundDecayEndBlockHeight) 176 devFundInitialPercentage := types.DevFundInitialPercentage 177 devFundFinalPercentage := types.DevFundFinalPercentage 178 devFundPercentageRange := devFundInitialPercentage - devFundFinalPercentage 179 devFundDecayPercentage := uint64(100) 180 if uint64(height) >= devFundDecayEndBlockHeight { 181 devFundDecayPercentage = uint64(0) 182 } else if uint64(height) >= devFundDecayStartBlockHeight { 183 devFundDecayPercentage = uint64(100) - (uint64(height)-devFundDecayStartBlockHeight)*uint64(100)/(devFundDecayEndBlockHeight-devFundDecayStartBlockHeight) 184 } 185 devFundPercentage := devFundFinalPercentage*uint64(100) + devFundPercentageRange*devFundDecayPercentage 186 devSubsidy := coinbase.MulFloat(0) 187 if devFundEnabled && height >= devFundInitialBlockHeight { 188 devSubsidy = coinbase.Mul(types.NewCurrency64(devFundPercentage)).Div(types.NewCurrency64(uint64(10000))) 189 } 190 minerSubsidy := coinbase.Sub(devSubsidy) 191 192 // Create a block with a single coinbase payout, and no dev fund payout. 193 b := types.Block{ 194 MinerPayouts: []types.SiacoinOutput{ 195 {Value: coinbase}, 196 }, 197 } 198 if devFundEnabled && checkMinerPayouts(b, height) { 199 t.Error("payouts evaluated incorrectly when the dev fund is enabled and there is a coinbase payout but not a dev fund payout.") 200 } 201 if !devFundEnabled && !checkMinerPayouts(b, height) { 202 t.Error("payouts evaluated incorrectly when the dev fund is disabled and there is a coinbase payout and a dev fund payout.") 203 } 204 // Create a block with a valid miner payout, and a dev fund payout with no unlock hash. 205 b = types.Block{ 206 MinerPayouts: []types.SiacoinOutput{ 207 {Value: minerSubsidy}, 208 {Value: devSubsidy}, 209 }, 210 } 211 if checkMinerPayouts(b, height) { 212 t.Error("payouts evaluated incorrectly when we are missing the dev fund unlock hash.") 213 } 214 // Create a block with a valid miner payout, and a dev fund payout with an incorrect unlock hash. 215 b = types.Block{ 216 MinerPayouts: []types.SiacoinOutput{ 217 {Value: minerSubsidy}, 218 {Value: devSubsidy, UnlockHash: types.UnlockHash{0, 1}}, 219 }, 220 } 221 if checkMinerPayouts(b, height) { 222 t.Error("payouts evaluated incorrectly when we have an incorrect dev fund unlock hash.") 223 } 224 // Create a block with a valid miner payout, but no dev fund payout. 225 b = types.Block{ 226 MinerPayouts: []types.SiacoinOutput{ 227 {Value: minerSubsidy}, 228 }, 229 } 230 if devFundEnabled && checkMinerPayouts(b, height) { 231 t.Error("payouts evaluated incorrectly when the dev fund is enabled and we are missing the dev fund payout but have a proper miner payout.") 232 } 233 if !devFundEnabled && !checkMinerPayouts(b, height) { 234 t.Error("payouts evaluated incorrectly when the dev fund is disabled and we have a proper miner payout.") 235 } 236 // Create a block with a valid dev fund payout, but no miner payout. 237 b = types.Block{ 238 MinerPayouts: []types.SiacoinOutput{ 239 {Value: devSubsidy, UnlockHash: types.DevFundUnlockHash}, 240 }, 241 } 242 if checkMinerPayouts(b, height) { 243 t.Error("payouts evaluated incorrectly when we are missing the miner payout but have a proper dev fund payout.") 244 } 245 // Create a block with a valid miner payout and a valid dev fund payout. 246 b = types.Block{ 247 MinerPayouts: []types.SiacoinOutput{ 248 {Value: minerSubsidy}, 249 {Value: devSubsidy, UnlockHash: types.DevFundUnlockHash}, 250 }, 251 } 252 if devFundEnabled && !checkMinerPayouts(b, height) { 253 t.Error("payouts evaluated incorrectly when there are only two payouts and the dev fund is enabled.") 254 } 255 if !devFundEnabled && checkMinerPayouts(b, height) { 256 t.Error("payouts evaluated incorrectly when there are only two payouts and the dev fund is disabled.") 257 } 258 259 // Try a block with an incorrect payout. 260 b = types.Block{ 261 MinerPayouts: []types.SiacoinOutput{ 262 {Value: coinbase.Sub(types.NewCurrency64(1))}, 263 }, 264 } 265 if checkMinerPayouts(b, height) { 266 t.Error("payouts evaluated incorrectly when there is a too-small payout") 267 } 268 269 minerPayout := coinbase.Sub(devSubsidy).Sub(types.NewCurrency64(1)) 270 secondMinerPayout := types.NewCurrency64(1) 271 // Try a block with 3 payouts. 272 b = types.Block{ 273 MinerPayouts: []types.SiacoinOutput{ 274 {Value: minerPayout}, 275 {Value: secondMinerPayout}, 276 {Value: devSubsidy, UnlockHash: types.DevFundUnlockHash}, 277 }, 278 } 279 if devFundEnabled && !checkMinerPayouts(b, height) { 280 t.Error("payouts evaluated incorrectly when there are 3 payouts and the dev fund is enabled.") 281 } 282 if !devFundEnabled && checkMinerPayouts(b, height) { 283 t.Error("payouts evaluated incorrectly when there are 3 payouts and the dev fund is disabled.") 284 } 285 286 // Try a block with 2 payouts that are too large. 287 b = types.Block{ 288 MinerPayouts: []types.SiacoinOutput{ 289 {Value: coinbase}, 290 {Value: coinbase}, 291 }, 292 } 293 if checkMinerPayouts(b, height) { 294 t.Error("payouts evaluated incorrectly when there are two large payouts") 295 } 296 297 // Create a block with an empty payout. 298 b = types.Block{ 299 MinerPayouts: []types.SiacoinOutput{ 300 {Value: coinbase}, 301 {}, 302 }, 303 } 304 if checkMinerPayouts(b, height) { 305 t.Error("payouts evaluated incorrectly when there is only one payout.") 306 } 307 } 308 309 // TestCheckTarget probes the checkTarget function. 310 func TestCheckTarget(t *testing.T) { 311 var b types.Block 312 lowTarget := types.RootDepth 313 highTarget := types.Target{} 314 sameTarget := types.Target(b.ID()) 315 316 if !checkTarget(b, b.ID(), lowTarget) { 317 t.Error("CheckTarget failed for a low target") 318 } 319 if checkTarget(b, b.ID(), highTarget) { 320 t.Error("CheckTarget passed for a high target") 321 } 322 if !checkTarget(b, b.ID(), sameTarget) { 323 t.Error("CheckTarget failed for a same target") 324 } 325 }