Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add average uptime for validators #83

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions contracts/sfc/ConstantsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ contract ConstantsManager is Ownable {
uint256 public targetGasPowerPerSecond;
uint256 public gasPriceBalancingCounterweight;

// epoch threshold for stop counting alive epochs (avoid diminishing impact of new uptimes) and
// is also the minimum number of epochs necessary for enabling the deactivation.
int32 public numEpochsAliveThreshold;

// minimum average uptime in Q1.30 format; acceptable bounds [0,0.9]
int32 public minAverageUptime;

/**
* @dev Given value is too small
*/
Expand Down Expand Up @@ -150,4 +157,25 @@ contract ConstantsManager is Ownable {
}
gasPriceBalancingCounterweight = v;
}

function updateNumEpochsAliveThreshold(int32 v) external virtual onlyOwner {
if (v < 10) {
revert ValueTooSmall();
}
if (v > 87600) {
revert ValueTooLarge();
}
numEpochsAliveThreshold = v;
}

function updateMinAverageUptime(int32 v) external virtual onlyOwner {
if (v < 0) {
revert ValueTooSmall();
}
if (v > 966367641) {
// 0.9 in Q1.30
revert ValueTooLarge();
}
minAverageUptime = v;
}
}
57 changes: 57 additions & 0 deletions contracts/sfc/SFC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ contract SFC is SFCBase, Version {
return getEpochSnapshot[epoch].accumulatedUptime[validatorID];
}

function getEpochAverageUptime(uint256 epoch, uint256 validatorID) public view returns (int32) {
return getEpochSnapshot[epoch].averageData[validatorID].averageUptime;
}

function getEpochAccumulatedOriginatedTxsFee(uint256 epoch, uint256 validatorID) public view returns (uint256) {
return getEpochSnapshot[epoch].accumulatedOriginatedTxsFee[validatorID];
}
Expand Down Expand Up @@ -351,6 +355,58 @@ contract SFC is SFCBase, Version {
minGasPrice = newMinGasPrice;
}

function _sealEpochAverageUptime(
uint256 epochDuration,
EpochSnapshot storage snapshot,
EpochSnapshot storage prevSnapshot,
uint256[] memory validatorIDs,
uint256[] memory uptimes
) internal {
for (uint256 i = 0; i < validatorIDs.length; i++) {
uint256 validatorID = validatorIDs[i];
// compute normalised uptime as a percentage in the Q1.30 fixed-point format
uint256 normalisedUptime = (uptimes[i] * (1 << 30)) / epochDuration;
if (normalisedUptime < 0) {
normalisedUptime = 0;
} else if (normalisedUptime > 1 << 30) {
normalisedUptime = 1 << 30;
}
// update average uptime data structure
// Assumes that if in the previous snapshot the validator
// does not exist, the map returns zero.
int32 n = prevSnapshot.averageData[validatorID].numEpochsAlive;
int64 tmp;
if (n > 0) {
tmp =
int64(n - 1) *
int64(snapshot.averageData[validatorID].averageUptime) +
int64(uint64(normalisedUptime));
if (n > 1) {
tmp += (int64(n) * int64(prevSnapshot.averageData[validatorID].averageUptimeError)) / int64(n - 1);
}
snapshot.averageData[validatorID].averageUptimeError = int32(tmp % int64(n));
tmp /= int64(n);
} else {
tmp = int64(uint64(normalisedUptime));
}
if (tmp < 0) {
tmp = 0;
} else if (tmp > 1 << 30) {
tmp = 1 << 30;
}
snapshot.averageData[validatorID].averageUptime = int32(tmp);
if (n < c.numEpochsAliveThreshold()) {
snapshot.averageData[validatorID].numEpochsAlive = n + 1;
}
// remove validator if average uptime drops below min average uptime
// (by setting minAverageUptime to zero, this check is ignored)
if (int32(tmp) < c.minAverageUptime() && n >= c.numEpochsAliveThreshold()) {
_setValidatorDeactivated(validatorIDs[i], OFFLINE_BIT);
_syncValidator(validatorIDs[i], false);
}
}
}

function sealEpoch(
uint256[] calldata offlineTime,
uint256[] calldata offlineBlocks,
Expand All @@ -370,6 +426,7 @@ contract SFC is SFCBase, Version {
}
_sealEpochRewards(epochDuration, snapshot, prevSnapshot, validatorIDs, uptimes, originatedTxsFee);
_sealEpochMinGasPrice(epochDuration, epochGas);
_sealEpochAverageUptime(epochDuration, snapshot, prevSnapshot, validatorIDs, uptimes);
}

currentSealedEpoch = currentEpoch();
Expand Down
12 changes: 12 additions & 0 deletions contracts/sfc/SFCState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,25 @@ contract SFCState is Initializable, Ownable {
// delegator => validator ID => current stake (locked+unlocked)
mapping(address => mapping(uint256 => uint256)) public getStake;

// data structure to compute average uptime for each active validator
struct AverageData {
// average uptime
int32 averageUptime;
// average uptime error term
int32 averageUptimeError;
// number of alive epochs (counts only up to numEpochsAliveThreshold)
int32 numEpochsAlive;
}

struct EpochSnapshot {
// validator ID => validator weight in the epoch
mapping(uint256 => uint256) receivedStake;
// validator ID => accumulated ( delegatorsReward * 1e18 / receivedStake )
mapping(uint256 => uint256) accumulatedRewardPerToken;
// validator ID => accumulated online time
mapping(uint256 => uint256) accumulatedUptime;
// validator ID => average uptime as a percentage
mapping(uint256 => AverageData) averageData;
// validator ID => gas fees from txs originated by the validator
mapping(uint256 => uint256) accumulatedOriginatedTxsFee;
mapping(uint256 => uint256) offlineTime;
Expand Down
Loading