Skip to content

Commit 86f4214

Browse files
authored
Merge pull request #6 from Kotsin/i-90-validator-set-upscaling
I 90 validator set upscaling
2 parents 1d97a53 + f92d28e commit 86f4214

File tree

4 files changed

+164
-23
lines changed

4 files changed

+164
-23
lines changed

contracts/base/BlockRewardHbbftBase.sol

+28-20
Original file line numberDiff line numberDiff line change
@@ -255,38 +255,45 @@ contract BlockRewardHbbftBase is UpgradeableOwned, IBlockRewardHbbft {
255255
uint256 currentTimestamp = validatorSetContract
256256
.getCurrentTimestamp();
257257

258+
address[] memory miningAddresses = validatorSetContract
259+
.getValidators();
260+
258261
// TODO: Problem occurs here if there are not regular blocks:
259262
// https://github.com/DMDcoin/hbbft-posdao-contracts/issues/96
260263

261264
//we are in a transition to phase 2 if the time for it arrived,
262265
// and we do not have pendingValidators yet.
263-
bool isPhaseTransition = currentTimestamp >= phaseTransitionTime &&
264-
validatorSetContract.getPendingValidators().length == 0;
266+
bool isPhaseTransition = currentTimestamp >= phaseTransitionTime;
267+
bool toBeUpscaled;
268+
if (
269+
miningAddresses.length <=
270+
(validatorSetContract.maxValidators() / 3) * 2
271+
) {
272+
uint256 amountToBeElected = stakingContract
273+
.getPoolsToBeElected()
274+
.length;
275+
if (
276+
(amountToBeElected > 0) &&
277+
validatorSetContract.getValidatorCountSweetSpot(
278+
amountToBeElected
279+
) >
280+
miningAddresses.length
281+
) {
282+
toBeUpscaled = true;
283+
}
284+
}
265285

266-
if (isPhaseTransition) {
286+
if (
287+
(isPhaseTransition || toBeUpscaled) &&
288+
validatorSetContract.getPendingValidators().length == 0
289+
) {
267290
// Choose new validators
268291
validatorSetContract.newValidatorSet();
269292
} else if (
270293
currentTimestamp >= stakingContract.stakingFixedEpochEndTime()
271294
) {
272295
validatorSetContract.handleFailedKeyGeneration();
273296
}
274-
// } else {
275-
276-
// // check for faster validator set upscaling
277-
// // https://github.com/DMDcoin/hbbft-posdao-contracts/issues/90
278-
279-
// address[] memory miningAddresses = validatorSetContract.getValidators();
280-
281-
// // if there is a miningset that is smaller than the 2/3 of the maxValidators,
282-
// // then we choose the next epoch set.
283-
// if (miningAddresses.length < (validatorSetContract.maxValidators() / 3) * 2) {
284-
// address[] memory poolsToBeElected = stakingContract.getPoolsToBeElected();
285-
// if (poolsToBeElected.length > miningAddresses.length) {
286-
// validatorSetContract.newValidatorSet();
287-
// }
288-
// }
289-
// }
290297
}
291298
}
292299

@@ -499,6 +506,7 @@ contract BlockRewardHbbftBase is UpgradeableOwned, IBlockRewardHbbft {
499506
}
500507

501508
///@dev Calculates and returns the percentage of the current epoch.
509+
/// 100% MAX
502510
function epochPercentage() public view returns (uint256) {
503511
IStakingHbbft stakingContract = IStakingHbbft(
504512
validatorSetContract.getStakingContract()
@@ -509,7 +517,7 @@ contract BlockRewardHbbftBase is UpgradeableOwned, IBlockRewardHbbft {
509517
return
510518
validatorSetContract.getCurrentTimestamp() >
511519
stakingContract.stakingFixedEpochEndTime()
512-
? 1000
520+
? 100
513521
: ((validatorSetContract.getCurrentTimestamp() -
514522
stakingContract.stakingEpochStartTime()) * 100) /
515523
expectedEpochDuration;

contracts/interfaces/IValidatorSetHbbft.sol

+5
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,9 @@ interface IValidatorSetHbbft {
9090
function getCurrentTimestamp() external view returns (uint256);
9191

9292
function validatorAvailableSince(address) external view returns (uint256);
93+
94+
function getValidatorCountSweetSpot(uint256)
95+
external
96+
view
97+
returns (uint256);
9398
}

hardhat.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const config: {} = {
9595
networks: {
9696
hardhat: {
9797
accounts: {
98-
count: 60,
98+
count: 100,
9999
mnemonic,
100100
accountsBalance: "1000000000000000000000000000"
101101
},

test/BlockRewardHbbft.ts

+130-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ValidatorSetHbbftMock,
88
StakingHbbftCoinsMock,
99
KeyGenHistory,
10-
IStakingHbbft
10+
IStakingHbbft,
1111
} from "../src/types";
1212

1313
import fp from 'lodash/fp';
@@ -131,6 +131,7 @@ describe('BlockRewardHbbft', () => {
131131
// The IP addresses are irrelevant for these unit test, just initialize them to 0.
132132
initialValidatorsIpAddresses = ['0x00000000000000000000000000000000', '0x00000000000000000000000000000000', '0x00000000000000000000000000000000'];
133133

134+
await validatorSetHbbft.setCurrentTimestamp((await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp)
134135

135136
// Initialize ValidatorSetHbbft
136137
await validatorSetHbbft.initialize(
@@ -181,6 +182,7 @@ describe('BlockRewardHbbft', () => {
181182
[[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0, 0, 0, 4, 239, 1, 112, 13, 13, 251, 103, 186, 212, 78, 44, 47, 250, 221, 84, 118, 88, 7, 64, 206, 186, 11, 2, 8, 204, 140, 106, 179, 52, 251, 237, 19, 53, 74, 187, 217, 134, 94, 66, 68, 89, 42, 85, 207, 155, 220, 101, 223, 51, 199, 37, 38, 203, 132, 13, 77, 78, 114, 53, 219, 114, 93, 21, 25, 164, 12, 43, 252, 160, 16, 23, 111, 79, 230, 121, 95, 223, 174, 211, 172, 231, 0, 52, 25, 49, 152, 79, 128, 39, 117, 216, 85, 201, 237, 242, 151, 219, 149, 214, 77, 233, 145, 47, 10, 184, 175, 162, 174, 237, 177, 131, 45, 126, 231, 32, 147, 227, 170, 125, 133, 36, 123, 164, 232, 129, 135, 196, 136, 186, 45, 73, 226, 179, 169, 147, 42, 41, 140, 202, 191, 12, 73, 146, 2]],
182183
[[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 145, 0, 0, 0, 0, 0, 0, 0, 4, 239, 1, 112, 13, 13, 251, 103, 186, 212, 78, 44, 47, 250, 221, 84, 118, 88, 7, 64, 206, 186, 11, 2, 8, 204, 140, 106, 179, 52, 251, 237, 19, 53, 74, 187, 217, 134, 94, 66, 68, 89, 42, 85, 207, 155, 220, 101, 223, 51, 199, 37, 38, 203, 132, 13, 77, 78, 114, 53, 219, 114, 93, 21, 25, 164, 12, 43, 252, 160, 16, 23, 111, 79, 230, 121, 95, 223, 174, 211, 172, 231, 0, 52, 25, 49, 152, 79, 128, 39, 117, 216, 85, 201, 237, 242, 151, 219, 149, 214, 77, 233, 145, 47, 10, 184, 175, 162, 174, 237, 177, 131, 45, 126, 231, 32, 147, 227, 170, 125, 133, 36, 123, 164, 232, 129, 135, 196, 136, 186, 45, 73, 226, 179, 169, 147, 42, 41, 140, 202, 191, 12, 73, 146, 2]]]
183184
)
185+
184186
});
185187

186188
it('staking epoch #0 finished', async () => {
@@ -454,6 +456,63 @@ describe('BlockRewardHbbft', () => {
454456

455457
actualValidatorReward.should.be.closeTo(expectedValidatorReward, expectedValidatorReward.div(100000));
456458
})
459+
460+
describe("Upscaling tests", async () => {
461+
it("Add multiple validator pools and upscale if needed.", async () => {
462+
const accountAddresses = accounts.map(item => item.address);
463+
const additionalValidators = accountAddresses.slice(7, 52 + 1); // accounts[7...32]
464+
const additionalStakingAddresses = accountAddresses.slice(53, 99 + 1); // accounts[33...59]
465+
466+
additionalValidators.length.should.be.equal(46);
467+
additionalStakingAddresses.length.should.be.equal(46);
468+
469+
await network.provider.send("evm_setIntervalMining", [8]);
470+
471+
for (let i = 0; i < additionalValidators.length; i++) {
472+
let stakingAddress = await ethers.getSigner(additionalStakingAddresses[i]);
473+
let miningAddress = await ethers.getSigner(additionalValidators[i]);
474+
475+
await stakingHbbft.connect(stakingAddress).addPool(miningAddress.address, '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
476+
'0x00000000000000000000000000000000', { value: MIN_STAKE });
477+
await announceAvailability(miningAddress.address);
478+
await mine();
479+
480+
let toBeElected = (await stakingHbbft.getPoolsToBeElected()).length;
481+
let pendingValidators = (await validatorSetHbbft.getPendingValidators()).length
482+
if (toBeElected > 4 && toBeElected <= 19 && pendingValidators == 0) {
483+
(await validatorSetHbbft.getValidatorCountSweetSpot((await stakingHbbft.getPoolsToBeElected()).length)).should.be.equal((await validatorSetHbbft.getValidators()).length);
484+
}
485+
}
486+
487+
await timeTravelToTransition();
488+
await timeTravelToEndEpoch();
489+
// after epoch was finalized successfully, validator set length is healthy
490+
(await validatorSetHbbft.getValidators()).length.should.be.eq(25);
491+
(await stakingHbbft.getPoolsToBeElected()).length.should.be.eq(49);
492+
})
493+
494+
it("banning validator up to 16", async () => {
495+
await validatorSetHbbft.setSystemAddress(owner.address);
496+
while ((await validatorSetHbbft.getValidators()).length > 16) {
497+
await mine();
498+
await validatorSetHbbft.connect(owner).removeMaliciousValidators([(await validatorSetHbbft.getValidators())[13]]);
499+
}
500+
(await validatorSetHbbft.getValidators()).length.should.be.eq(16);
501+
})
502+
it("mining twice shouldn't change pending validator set", async () => {
503+
await callReward(false);
504+
(await validatorSetHbbft.getPendingValidators()).length.should.be.eq(25);
505+
let pendingValidators = await validatorSetHbbft.getPendingValidators();
506+
await callReward(false);
507+
sortedEqual(pendingValidators, await validatorSetHbbft.getPendingValidators());
508+
})
509+
it("set is scaled to 25", async () => {
510+
await mine();
511+
(await validatorSetHbbft.getValidators()).length.should.be.eq(25);
512+
(await validatorSetHbbft.getPendingValidators()).length.should.be.eq(0);
513+
await network.provider.send("evm_setIntervalMining", [0]);
514+
})
515+
})
457516
});
458517

459518
function sortedEqual<T>(arr1: T[], arr2: T[]): void {
@@ -490,6 +549,8 @@ async function increaseTime(time: number) {
490549

491550
const currentTimestamp = await validatorSetHbbft.getCurrentTimestamp();
492551
const futureTimestamp = currentTimestamp.add(BigNumber.from(time));
552+
await network.provider.send("evm_setNextBlockTimestamp", [futureTimestamp.toNumber()]);
553+
await network.provider.send("evm_mine");
493554
await validatorSetHbbft.setCurrentTimestamp(futureTimestamp);
494555
const currentTimestampAfter = await validatorSetHbbft.getCurrentTimestamp();
495556
futureTimestamp.should.be.equal(currentTimestampAfter);
@@ -501,6 +562,9 @@ async function increaseTime(time: number) {
501562
async function timeTravelToTransition() {
502563
let startTimeOfNextPhaseTransition = await stakingHbbft.startTimeOfNextPhaseTransition();
503564

565+
await network.provider.send("evm_setNextBlockTimestamp", [startTimeOfNextPhaseTransition.toNumber()]);
566+
await network.provider.send("evm_mine");
567+
504568
await validatorSetHbbft.setCurrentTimestamp(startTimeOfNextPhaseTransition);
505569
const currentTS = await validatorSetHbbft.getCurrentTimestamp();
506570
currentTS.should.be.equal(startTimeOfNextPhaseTransition);
@@ -510,14 +574,78 @@ async function timeTravelToTransition() {
510574
async function timeTravelToEndEpoch() {
511575

512576
const endTimeOfCurrentEpoch = await stakingHbbft.stakingFixedEpochEndTime();
577+
await network.provider.send("evm_setNextBlockTimestamp", [endTimeOfCurrentEpoch.toNumber()]);
578+
await network.provider.send("evm_mine");
513579
await validatorSetHbbft.setCurrentTimestamp(endTimeOfCurrentEpoch);
514580
await callReward(true);
515581
}
516582

517583
async function finishEpochPrelim(_percentage: BigNumber) {
518584
const epochDuration = (await stakingHbbft.stakingFixedEpochEndTime()).sub((await stakingHbbft.stakingEpochStartTime())).mul(_percentage).div(100).add(1);
519585
const endTimeOfCurrentEpoch = (await stakingHbbft.stakingEpochStartTime()).add(epochDuration);
586+
await network.provider.send("evm_setNextBlockTimestamp", [endTimeOfCurrentEpoch.toNumber()]);
587+
await network.provider.send("evm_mine");
520588
await validatorSetHbbft.setCurrentTimestamp(endTimeOfCurrentEpoch);
521-
_percentage = await blockRewardHbbft.epochPercentage();
589+
// _percentage = await blockRewardHbbft.epochPercentage();
522590
await callReward(true);
523591
}
592+
593+
async function announceAvailability(pool: string) {
594+
const blockNumber = await ethers.provider.getBlockNumber()
595+
const block = await ethers.provider.getBlock(blockNumber);
596+
const asEncoded = validatorSetHbbft.interface.encodeFunctionData("announceAvailability", [blockNumber, block.hash]);
597+
598+
// we know now, that this call is allowed.
599+
// so we can execute it.
600+
await (await ethers.getSigner(pool)).sendTransaction({ to: validatorSetHbbft.address, data: asEncoded });
601+
}
602+
603+
async function mine() {
604+
let expectedEpochDuration = (await stakingHbbft
605+
.stakingFixedEpochEndTime()).sub(await stakingHbbft.stakingEpochStartTime());
606+
let blocktime = expectedEpochDuration.mul(5).div(100).add(1); //5% of the epoch
607+
// let blocksPerEpoch = 60 * 60 * 12 / blocktime;
608+
await network.provider.send("evm_increaseTime", [blocktime.toNumber()]);
609+
await validatorSetHbbft.setCurrentTimestamp((await validatorSetHbbft.getCurrentTimestamp()).add(blocktime));
610+
if ((await validatorSetHbbft.getPendingValidators()).length > 0) {
611+
const currentValidators = await validatorSetHbbft.getValidators();
612+
const maxValidators = await validatorSetHbbft.maxValidators();
613+
stakingEpoch = await stakingHbbft.stakingEpoch();
614+
615+
const initialGovernancePotBalance = await getCurrentGovernancePotValue();
616+
let deltaPotValue = await blockRewardHbbft.deltaPot();
617+
let reinsertPotValue = await blockRewardHbbft.reinsertPot();
618+
let _epochPercentage = await blockRewardHbbft.epochPercentage();
619+
620+
await callReward(true);
621+
622+
const currentGovernancePotBalance = await getCurrentGovernancePotValue();
623+
const governancePotIncrease = currentGovernancePotBalance.sub(initialGovernancePotBalance);
624+
625+
626+
const deltaPotShare = deltaPotValue.mul(BigNumber.from(currentValidators.length)).mul(_epochPercentage).div(BigNumber.from('6000')).div(maxValidators).div(100);
627+
const reinsertPotShare = reinsertPotValue.mul(BigNumber.from(currentValidators.length)).mul(_epochPercentage).div(BigNumber.from('6000')).div(maxValidators).div(100);
628+
const nativeRewardUndistributed = await blockRewardHbbft.nativeRewardUndistributed();
629+
630+
const totalReward = deltaPotShare.add(reinsertPotShare).add(nativeRewardUndistributed);
631+
const expectedDAOShare = totalReward.div(BigNumber.from('10'));
632+
633+
// we expect 1 wei difference, since the reward combination from 2 pots results in that.
634+
//expectedDAOShare.sub(governancePotIncrease).should.to.be.bignumber.lte(BigNumber.from('1'));
635+
governancePotIncrease.should.to.be.closeTo(expectedDAOShare, expectedDAOShare.div(10000));
636+
637+
//since there are a lot of delegators, we need to calc it on a basis that pays out the validator min reward.
638+
let minValidatorSharePercent = 100;
639+
///first 4 validators have delegators so they receive less DMD
640+
if (currentValidators.length < 4) {
641+
minValidatorSharePercent = 30;
642+
}
643+
644+
const expectedValidatorReward = totalReward.sub(expectedDAOShare).div(BigNumber.from(currentValidators.length)).mul(minValidatorSharePercent).div(BigNumber.from('100'));
645+
const actualValidatorReward = await blockRewardHbbft.getValidatorReward(stakingEpoch, currentValidators[currentValidators.length - 1]);
646+
647+
actualValidatorReward.should.be.closeTo(expectedValidatorReward, expectedValidatorReward.div(10000));
648+
} else {
649+
await callReward(false);
650+
}
651+
}

0 commit comments

Comments
 (0)