diff --git a/contracts/sfc/ConstantsManager.sol b/contracts/sfc/ConstantsManager.sol index b5283ea..d99082a 100644 --- a/contracts/sfc/ConstantsManager.sol +++ b/contracts/sfc/ConstantsManager.sol @@ -32,6 +32,9 @@ contract ConstantsManager is Ownable { uint256 public targetGasPowerPerSecond; uint256 public gasPriceBalancingCounterweight; + // number of epochs for counting the average uptime of validators + int32 public numEpochsAliveThreshold; + /** * @dev Given value is too small */ @@ -186,4 +189,14 @@ 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; + } } diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index 0135406..dd67607 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -368,6 +368,47 @@ 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]; + uint256 normalisedUptime = uptimes[i] * (1 << 30)/ epochDuration; + if (normalisedUptime < 0) { + normalisedUptime = 0; + } else if (normalisedUptime > 1 << 30) { + normalisedUptime = 1 << 30; + } + // Assumes that if in the previous snapshot the validator + // does not exist, the map returns zero. + int32 n = prevSnapshot.numEpochsAlive[validatorID]; + int64 tmp; + if (n > 0) { + tmp = int64(n-1) * int64(snapshot.averageUptime[validatorID]) + int64(uint64(normalisedUptime)); + if (n > 1) { + tmp += (int64(n) * int64(prevSnapshot.averageUptimeError[validatorID])) / int64(n-1); + } + snapshot.averageUptimeError[validatorID] = 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.averageUptime[validatorID] = int32(tmp); + if (n < c.numEpochsAliveThreshold()) { + snapshot.numEpochsAlive[validatorID] = n + 1; + } + } + } + function sealEpoch( uint256[] calldata offlineTime, uint256[] calldata offlineBlocks, @@ -387,6 +428,7 @@ contract SFC is SFCBase, Version { } _sealEpochRewards(epochDuration, snapshot, prevSnapshot, validatorIDs, uptimes, originatedTxsFee); _sealEpochMinGasPrice(epochDuration, epochGas); + _sealEpochAverageUptime(epochDuration, snapshot, prevSnapshot, validatorIDs, uptimes); } currentSealedEpoch = currentEpoch(); diff --git a/contracts/sfc/SFCState.sol b/contracts/sfc/SFCState.sol index 68906db..271368c 100644 --- a/contracts/sfc/SFCState.sol +++ b/contracts/sfc/SFCState.sol @@ -78,6 +78,12 @@ contract SFCState is Initializable, Ownable { mapping(uint256 => uint256) accumulatedRewardPerToken; // validator ID => accumulated online time mapping(uint256 => uint256) accumulatedUptime; + // validator ID => average uptime as a percentage + mapping(uint256 => int32) averageUptime; + // validator ID => error term of average uptime + mapping(uint256 => int32) averageUptimeError; + // validator ID => number of epochs alive for average uptime calculation + mapping(uint256 => int32) numEpochsAlive; // validator ID => gas fees from txs originated by the validator mapping(uint256 => uint256) accumulatedOriginatedTxsFee; mapping(uint256 => uint256) offlineTime;