github.com/klaytn/klaytn@v1.10.2/consensus/istanbul/validator/multi_staking_test.go (about)

     1  // Copyright 2019 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  /*
    18  Multiple Staking Contracts
    19  
    20  Validators can deploy multiple staking contracts.
    21  If a validator wants to deploy additional staking contracts, those staking contracts should have same rewardAddress.
    22  StakingAmounts of staking contracts with a same rewardAddress will be added and it is reflected to a probability of becoming a block proposer.
    23  
    24  Testing
    25  
    26  StakingInfos are data from addressBook.
    27  A StakingInfo has lists of addresses and stakingAmount.
    28  They are matched by an index. Values of the lists with a same index are from a same staking contract.
    29  
    30  All addresses used in tests are made by 3 digits number.
    31  NodeAddress : begin with 1
    32  rewardAddress : begin with 2
    33  NodeAddress of additional staking contract : begin with 9
    34  */
    35  package validator
    36  
    37  import (
    38  	"testing"
    39  
    40  	"github.com/klaytn/klaytn/common"
    41  	"github.com/klaytn/klaytn/consensus/istanbul"
    42  	"github.com/klaytn/klaytn/reward"
    43  	"github.com/stretchr/testify/assert"
    44  )
    45  
    46  func newTestWeightedCouncil(nodeAddrs []common.Address) *weightedCouncil {
    47  	return NewWeightedCouncil(nodeAddrs, nil, nil, make([]uint64, len(nodeAddrs)), nil, istanbul.WeightedRandom, 0, 0, 0, nil)
    48  }
    49  
    50  // TestWeightedCouncil_getStakingAmountsOfValidators checks if validators and stakingAmounts from a stakingInfo are matched well.
    51  // stakingAmounts of additional staking contracts will be added to stakingAmounts of validators which have the same reward address.
    52  // input
    53  //  - validator and stakingInfo is matched by a nodeAddress.
    54  // output
    55  //  - weightedValidators are sorted by nodeAddress
    56  //  - stakingAmounts should be same as expectedStakingAmounts
    57  func TestWeightedCouncil_getStakingAmountsOfValidators(t *testing.T) {
    58  	testCases := []struct {
    59  		validators             []common.Address
    60  		stakingInfo            *reward.StakingInfo
    61  		expectedStakingAmounts []float64
    62  	}{
    63  		{
    64  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
    65  			&reward.StakingInfo{
    66  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
    67  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203")},
    68  				CouncilStakingAmounts: []uint64{10000000, 5000000, 5000000},
    69  			},
    70  			[]float64{10000000, 5000000, 5000000},
    71  		},
    72  		{
    73  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
    74  			&reward.StakingInfo{
    75  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
    76  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203")},
    77  				CouncilStakingAmounts: []uint64{7000000, 5000000, 10000000},
    78  			},
    79  			[]float64{7000000, 5000000, 10000000},
    80  		},
    81  		{
    82  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
    83  			&reward.StakingInfo{
    84  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901")},
    85  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201")},
    86  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000, 5000000},
    87  			},
    88  			[]float64{10000000, 5000000, 5000000, 5000000},
    89  		},
    90  		{
    91  			[]common.Address{common.StringToAddress("104"), common.StringToAddress("103"), common.StringToAddress("102"), common.StringToAddress("101")},
    92  			&reward.StakingInfo{
    93  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901"), common.StringToAddress("902")},
    94  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201"), common.StringToAddress("202")},
    95  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000, 5000000, 5000000},
    96  			},
    97  			[]float64{10000000, 10000000, 5000000, 5000000},
    98  		},
    99  	}
   100  	for _, testCase := range testCases {
   101  		council := newTestWeightedCouncil(testCase.validators)
   102  		candidates := append(council.validators, council.demotedValidators...)
   103  		weightedValidators, stakingAmounts, err := getStakingAmountsOfValidators(candidates, testCase.stakingInfo)
   104  
   105  		assert.NoError(t, err)
   106  		assert.Equal(t, len(testCase.validators), len(weightedValidators))
   107  		for _, validator := range weightedValidators {
   108  			assert.Contains(t, testCase.validators, validator.address)
   109  		}
   110  		assert.Equal(t, testCase.expectedStakingAmounts, stakingAmounts)
   111  	}
   112  }
   113  
   114  // TestCalcTotalAmount tests calcTotalAmount that calculates totalAmount of stakingAmounts and gini coefficient if UseGini is true.
   115  // if UseGini is true, gini is calculated and reflected to stakingAmounts.
   116  func TestCalcTotalAmount(t *testing.T) {
   117  	testCases := []struct {
   118  		weightedValidators     []*weightedValidator // Produced by getStakingAmountsOfValidators()
   119  		stakingInfo            *reward.StakingInfo  // Produced by GetStakingInfo()
   120  		stakingAmounts         []float64            // Produced by getStakingAmountsOfValidators()
   121  		expectedGini           float64              // Gini among the []weightedValidators which is a subset of CouncilNodeAddrs
   122  		expectedTotalAmount    float64              // Sum of Gini-adjusted amounts
   123  		expectedStakingAmounts []float64            // Gini-adjusted amounts
   124  	}{
   125  		{ // Gini disabled
   126  			[]*weightedValidator{
   127  				{address: common.StringToAddress("101")}, {address: common.StringToAddress("102")}, {address: common.StringToAddress("103")},
   128  			},
   129  			&reward.StakingInfo{
   130  				CouncilNodeAddrs: []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   131  				UseGini:          false,
   132  				Gini:             reward.DefaultGiniCoefficient, // To see if calcTotalAmount modifies Gini field, set it to -1.
   133  			},
   134  			[]float64{5000000, 5000000, 5000000},
   135  			reward.DefaultGiniCoefficient,
   136  			15000000,
   137  			[]float64{5000000, 5000000, 5000000},
   138  		},
   139  		{ // Gini enabled but equal amounts.
   140  			[]*weightedValidator{
   141  				{address: common.StringToAddress("101")}, {address: common.StringToAddress("102")}, {address: common.StringToAddress("103")},
   142  			},
   143  			&reward.StakingInfo{
   144  				CouncilNodeAddrs: []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   145  				UseGini:          true,
   146  				Gini:             reward.DefaultGiniCoefficient,
   147  			},
   148  			[]float64{5000000, 5000000, 5000000},
   149  			0,
   150  			15000000,
   151  			[]float64{5000000, 5000000, 5000000},
   152  		},
   153  		{ // Gini enabled and unequal amounts.
   154  			[]*weightedValidator{
   155  				{address: common.StringToAddress("101")}, {address: common.StringToAddress("102")}, {address: common.StringToAddress("103")}, {address: common.StringToAddress("104")}, {address: common.StringToAddress("105")},
   156  			},
   157  			&reward.StakingInfo{
   158  				CouncilNodeAddrs: []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   159  				UseGini:          true,
   160  				Gini:             reward.DefaultGiniCoefficient,
   161  			},
   162  			[]float64{10000000, 20000000, 30000000, 40000000, 50000000},
   163  			0.27,
   164  			3779508,
   165  			[]float64{324946, 560845, 771786, 967997, 1153934},
   166  		},
   167  	}
   168  	for _, testCase := range testCases {
   169  		stakingAmounts := testCase.stakingAmounts
   170  		totalAmount, giniUsed := calcTotalAmount(testCase.weightedValidators, testCase.stakingInfo, stakingAmounts)
   171  
   172  		assert.Equal(t, testCase.expectedGini, giniUsed)                          // calcTotalAmount computes gini among validators on its own
   173  		assert.Equal(t, reward.DefaultGiniCoefficient, testCase.stakingInfo.Gini) // stakingInfo.Gini left untouched
   174  		assert.Equal(t, testCase.expectedTotalAmount, totalAmount)
   175  		assert.Equal(t, testCase.expectedStakingAmounts, stakingAmounts)
   176  	}
   177  }
   178  
   179  // TestCalcWeight tests calcWeight that calculates weights and saves them to validators.
   180  // weights are the ratio of each stakingAmount to totalStaking
   181  func TestCalcWeight(t *testing.T) {
   182  	testCases := []struct {
   183  		weightedValidators []*weightedValidator
   184  		stakingAmounts     []float64
   185  		totalStaking       float64
   186  		expectedWeights    []uint64
   187  	}{
   188  		{
   189  			[]*weightedValidator{
   190  				{}, {}, {},
   191  			},
   192  			[]float64{0, 0, 0},
   193  			0,
   194  			[]uint64{0, 0, 0},
   195  		},
   196  		{
   197  			[]*weightedValidator{
   198  				{}, {}, {},
   199  			},
   200  			[]float64{5000000, 5000000, 5000000},
   201  			15000000,
   202  			[]uint64{33, 33, 33},
   203  		},
   204  		{
   205  			[]*weightedValidator{
   206  				{}, {}, {}, {},
   207  			},
   208  			[]float64{5000000, 10000000, 5000000, 5000000},
   209  			25000000,
   210  			[]uint64{20, 40, 20, 20},
   211  		},
   212  		{
   213  			[]*weightedValidator{
   214  				{}, {}, {}, {}, {},
   215  			},
   216  			[]float64{324946, 560845, 771786, 967997, 1153934},
   217  			3779508,
   218  			[]uint64{9, 15, 20, 26, 31},
   219  		},
   220  	}
   221  	for _, testCase := range testCases {
   222  		calcWeight(testCase.weightedValidators, testCase.stakingAmounts, testCase.totalStaking)
   223  		for i, weight := range testCase.expectedWeights {
   224  			assert.Equal(t, weight, testCase.weightedValidators[i].Weight())
   225  		}
   226  	}
   227  }
   228  
   229  // TestWeightedCouncil_validatorWeightWithStakingInfo is union of above tests.
   230  // Weight should be calculated exactly by a validator list and a stakingInfo given
   231  func TestWeightedCouncil_validatorWeightWithStakingInfo(t *testing.T) {
   232  	testCases := []struct {
   233  		validators      []common.Address
   234  		stakingInfo     *reward.StakingInfo
   235  		expectedWeights []uint64
   236  	}{
   237  		{
   238  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   239  			&reward.StakingInfo{
   240  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   241  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203")},
   242  				UseGini:               false,
   243  				CouncilStakingAmounts: []uint64{0, 0, 0},
   244  			},
   245  			[]uint64{0, 0, 0},
   246  		},
   247  		{
   248  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   249  			&reward.StakingInfo{
   250  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   251  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204")},
   252  				UseGini:               true,
   253  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000},
   254  			},
   255  			[]uint64{25, 25, 25, 25},
   256  		},
   257  		{
   258  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   259  			&reward.StakingInfo{
   260  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   261  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("205")},
   262  				UseGini:               true,
   263  				CouncilStakingAmounts: []uint64{10000000, 20000000, 30000000, 40000000, 50000000},
   264  			},
   265  			[]uint64{9, 15, 20, 26, 31},
   266  		},
   267  		{
   268  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   269  			&reward.StakingInfo{
   270  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901")},
   271  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201")},
   272  				UseGini:               false,
   273  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000, 5000000},
   274  			},
   275  			[]uint64{40, 20, 20, 20},
   276  		},
   277  		{
   278  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   279  			&reward.StakingInfo{
   280  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901")},
   281  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201")},
   282  				UseGini:               true,
   283  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000, 5000000},
   284  			},
   285  			[]uint64{38, 21, 21, 21},
   286  		},
   287  		{
   288  			[]common.Address{common.StringToAddress("104"), common.StringToAddress("103"), common.StringToAddress("102"), common.StringToAddress("101")},
   289  			&reward.StakingInfo{
   290  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901"), common.StringToAddress("902")},
   291  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201"), common.StringToAddress("202")},
   292  				UseGini:               true,
   293  				CouncilStakingAmounts: []uint64{10000000, 5000000, 20000000, 5000000, 5000000, 5000000},
   294  			},
   295  			[]uint64{29, 21, 37, 12},
   296  		},
   297  		{
   298  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   299  			&reward.StakingInfo{
   300  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901"), common.StringToAddress("902")},
   301  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201"), common.StringToAddress("202")},
   302  				UseGini:               true,
   303  				CouncilStakingAmounts: []uint64{10000000, 5000000, 20000000, 5000000, 5000000, 5000000},
   304  			},
   305  			[]uint64{29, 21, 37, 12, 1},
   306  		},
   307  	}
   308  	for _, testCase := range testCases {
   309  		council := newTestWeightedCouncil(testCase.validators)
   310  		candidates := append(council.validators, council.demotedValidators...)
   311  		weightedValidators, stakingAmounts, err := getStakingAmountsOfValidators(candidates, testCase.stakingInfo)
   312  		assert.NoError(t, err)
   313  		totalStaking, _ := calcTotalAmount(weightedValidators, testCase.stakingInfo, stakingAmounts)
   314  		calcWeight(weightedValidators, stakingAmounts, totalStaking)
   315  
   316  		for i, weight := range testCase.expectedWeights {
   317  			assert.Equal(t, weight, weightedValidators[i].Weight())
   318  		}
   319  	}
   320  }