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  }