github.com/klaytn/klaytn@v1.12.1/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  func TestRewardAddressLookup(t *testing.T) {
   115  	testCases := []struct {
   116  		validators             []common.Address
   117  		stakingInfo            *reward.StakingInfo
   118  		expectedStakingAmounts []float64
   119  	}{
   120  		{
   121  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   122  			&reward.StakingInfo{
   123  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   124  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203")},
   125  				CouncilStakingAmounts: []uint64{10000000, 5000000, 5000000},
   126  			},
   127  			[]float64{10000000, 5000000, 5000000},
   128  		},
   129  	}
   130  	for _, testCase := range testCases {
   131  		council := newTestWeightedCouncil(testCase.validators)
   132  		weightedValidators, _, err := getStakingAmountsOfValidators(council.validators, testCase.stakingInfo)
   133  
   134  		assert.NoError(t, err)
   135  		for _, val := range weightedValidators {
   136  			assert.NotEqual(t, val.RewardAddress(), common.Address{})
   137  		}
   138  	}
   139  }
   140  
   141  // TestCalcTotalAmount tests calcTotalAmount that calculates totalAmount of stakingAmounts and gini coefficient if UseGini is true.
   142  // if UseGini is true, gini is calculated and reflected to stakingAmounts.
   143  func TestCalcTotalAmount(t *testing.T) {
   144  	testCases := []struct {
   145  		weightedValidators     []*weightedValidator // Produced by getStakingAmountsOfValidators()
   146  		stakingInfo            *reward.StakingInfo  // Produced by GetStakingInfo()
   147  		stakingAmounts         []float64            // Produced by getStakingAmountsOfValidators()
   148  		expectedGini           float64              // Gini among the []weightedValidators which is a subset of CouncilNodeAddrs
   149  		expectedTotalAmount    float64              // Sum of Gini-adjusted amounts
   150  		expectedStakingAmounts []float64            // Gini-adjusted amounts
   151  	}{
   152  		{ // Gini disabled
   153  			[]*weightedValidator{
   154  				{address: common.StringToAddress("101")}, {address: common.StringToAddress("102")}, {address: common.StringToAddress("103")},
   155  			},
   156  			&reward.StakingInfo{
   157  				CouncilNodeAddrs: []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   158  				UseGini:          false,
   159  				Gini:             reward.DefaultGiniCoefficient, // To see if calcTotalAmount modifies Gini field, set it to -1.
   160  			},
   161  			[]float64{5000000, 5000000, 5000000},
   162  			reward.DefaultGiniCoefficient,
   163  			15000000,
   164  			[]float64{5000000, 5000000, 5000000},
   165  		},
   166  		{ // Gini enabled but equal amounts.
   167  			[]*weightedValidator{
   168  				{address: common.StringToAddress("101")}, {address: common.StringToAddress("102")}, {address: common.StringToAddress("103")},
   169  			},
   170  			&reward.StakingInfo{
   171  				CouncilNodeAddrs: []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   172  				UseGini:          true,
   173  				Gini:             reward.DefaultGiniCoefficient,
   174  			},
   175  			[]float64{5000000, 5000000, 5000000},
   176  			0,
   177  			15000000,
   178  			[]float64{5000000, 5000000, 5000000},
   179  		},
   180  		{ // Gini enabled and unequal amounts.
   181  			[]*weightedValidator{
   182  				{address: common.StringToAddress("101")}, {address: common.StringToAddress("102")}, {address: common.StringToAddress("103")}, {address: common.StringToAddress("104")}, {address: common.StringToAddress("105")},
   183  			},
   184  			&reward.StakingInfo{
   185  				CouncilNodeAddrs: []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   186  				UseGini:          true,
   187  				Gini:             reward.DefaultGiniCoefficient,
   188  			},
   189  			[]float64{10000000, 20000000, 30000000, 40000000, 50000000},
   190  			0.27,
   191  			3779508,
   192  			[]float64{324946, 560845, 771786, 967997, 1153934},
   193  		},
   194  	}
   195  	for _, testCase := range testCases {
   196  		stakingAmounts := testCase.stakingAmounts
   197  		totalAmount, giniUsed := calcTotalAmount(testCase.weightedValidators, testCase.stakingInfo, stakingAmounts)
   198  
   199  		assert.Equal(t, testCase.expectedGini, giniUsed)                          // calcTotalAmount computes gini among validators on its own
   200  		assert.Equal(t, reward.DefaultGiniCoefficient, testCase.stakingInfo.Gini) // stakingInfo.Gini left untouched
   201  		assert.Equal(t, testCase.expectedTotalAmount, totalAmount)
   202  		assert.Equal(t, testCase.expectedStakingAmounts, stakingAmounts)
   203  	}
   204  }
   205  
   206  // TestCalcWeight tests calcWeight that calculates weights and saves them to validators.
   207  // weights are the ratio of each stakingAmount to totalStaking
   208  func TestCalcWeight(t *testing.T) {
   209  	testCases := []struct {
   210  		weightedValidators []*weightedValidator
   211  		stakingAmounts     []float64
   212  		totalStaking       float64
   213  		expectedWeights    []uint64
   214  	}{
   215  		{
   216  			[]*weightedValidator{
   217  				{}, {}, {},
   218  			},
   219  			[]float64{0, 0, 0},
   220  			0,
   221  			[]uint64{0, 0, 0},
   222  		},
   223  		{
   224  			[]*weightedValidator{
   225  				{}, {}, {},
   226  			},
   227  			[]float64{5000000, 5000000, 5000000},
   228  			15000000,
   229  			[]uint64{33, 33, 33},
   230  		},
   231  		{
   232  			[]*weightedValidator{
   233  				{}, {}, {}, {},
   234  			},
   235  			[]float64{5000000, 10000000, 5000000, 5000000},
   236  			25000000,
   237  			[]uint64{20, 40, 20, 20},
   238  		},
   239  		{
   240  			[]*weightedValidator{
   241  				{}, {}, {}, {}, {},
   242  			},
   243  			[]float64{324946, 560845, 771786, 967997, 1153934},
   244  			3779508,
   245  			[]uint64{9, 15, 20, 26, 31},
   246  		},
   247  	}
   248  	for _, testCase := range testCases {
   249  		calcWeight(testCase.weightedValidators, testCase.stakingAmounts, testCase.totalStaking)
   250  		for i, weight := range testCase.expectedWeights {
   251  			assert.Equal(t, weight, testCase.weightedValidators[i].Weight())
   252  		}
   253  	}
   254  }
   255  
   256  // TestWeightedCouncil_validatorWeightWithStakingInfo is union of above tests.
   257  // Weight should be calculated exactly by a validator list and a stakingInfo given
   258  func TestWeightedCouncil_validatorWeightWithStakingInfo(t *testing.T) {
   259  	testCases := []struct {
   260  		validators      []common.Address
   261  		stakingInfo     *reward.StakingInfo
   262  		expectedWeights []uint64
   263  	}{
   264  		{
   265  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   266  			&reward.StakingInfo{
   267  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103")},
   268  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203")},
   269  				UseGini:               false,
   270  				CouncilStakingAmounts: []uint64{0, 0, 0},
   271  			},
   272  			[]uint64{0, 0, 0},
   273  		},
   274  		{
   275  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   276  			&reward.StakingInfo{
   277  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   278  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204")},
   279  				UseGini:               true,
   280  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000},
   281  			},
   282  			[]uint64{25, 25, 25, 25},
   283  		},
   284  		{
   285  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   286  			&reward.StakingInfo{
   287  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   288  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("205")},
   289  				UseGini:               true,
   290  				CouncilStakingAmounts: []uint64{10000000, 20000000, 30000000, 40000000, 50000000},
   291  			},
   292  			[]uint64{9, 15, 20, 26, 31},
   293  		},
   294  		{
   295  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   296  			&reward.StakingInfo{
   297  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901")},
   298  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201")},
   299  				UseGini:               false,
   300  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000, 5000000},
   301  			},
   302  			[]uint64{40, 20, 20, 20},
   303  		},
   304  		{
   305  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104")},
   306  			&reward.StakingInfo{
   307  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901")},
   308  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201")},
   309  				UseGini:               true,
   310  				CouncilStakingAmounts: []uint64{5000000, 5000000, 5000000, 5000000, 5000000},
   311  			},
   312  			[]uint64{38, 21, 21, 21},
   313  		},
   314  		{
   315  			[]common.Address{common.StringToAddress("104"), common.StringToAddress("103"), common.StringToAddress("102"), common.StringToAddress("101")},
   316  			&reward.StakingInfo{
   317  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901"), common.StringToAddress("902")},
   318  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201"), common.StringToAddress("202")},
   319  				UseGini:               true,
   320  				CouncilStakingAmounts: []uint64{10000000, 5000000, 20000000, 5000000, 5000000, 5000000},
   321  			},
   322  			[]uint64{29, 21, 37, 12},
   323  		},
   324  		{
   325  			[]common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("105")},
   326  			&reward.StakingInfo{
   327  				CouncilNodeAddrs:      []common.Address{common.StringToAddress("101"), common.StringToAddress("102"), common.StringToAddress("103"), common.StringToAddress("104"), common.StringToAddress("901"), common.StringToAddress("902")},
   328  				CouncilRewardAddrs:    []common.Address{common.StringToAddress("201"), common.StringToAddress("202"), common.StringToAddress("203"), common.StringToAddress("204"), common.StringToAddress("201"), common.StringToAddress("202")},
   329  				UseGini:               true,
   330  				CouncilStakingAmounts: []uint64{10000000, 5000000, 20000000, 5000000, 5000000, 5000000},
   331  			},
   332  			[]uint64{29, 21, 37, 12, 1},
   333  		},
   334  	}
   335  	for _, testCase := range testCases {
   336  		council := newTestWeightedCouncil(testCase.validators)
   337  		candidates := append(council.validators, council.demotedValidators...)
   338  		weightedValidators, stakingAmounts, err := getStakingAmountsOfValidators(candidates, testCase.stakingInfo)
   339  		assert.NoError(t, err)
   340  		totalStaking, _ := calcTotalAmount(weightedValidators, testCase.stakingInfo, stakingAmounts)
   341  		calcWeight(weightedValidators, stakingAmounts, totalStaking)
   342  
   343  		for i, weight := range testCase.expectedWeights {
   344  			assert.Equal(t, weight, weightedValidators[i].Weight())
   345  		}
   346  	}
   347  }