Skip to content

Commit

Permalink
Merge pull request #108 from SurfingNerd/key-gen-round-counter
Browse files Browse the repository at this point in the history
Key gen round counter
Merged. 
There are known issues with the backward compatibility that will get fixed soon.
  • Loading branch information
SurfingNerd authored Dec 9, 2021
2 parents 42362ed + e257f91 commit 72957ec
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 81 deletions.
53 changes: 46 additions & 7 deletions contracts/KeyGenHistory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ contract KeyGenHistory is UpgradeabilityAdmin, IKeyGenHistory {
// WARNING: since this contract is upgradeable, do not remove
// existing storage variables and do not change their types!

// the current validator addresses
address[] public validatorSet;
mapping(address => bytes) public parts;
mapping(address => bytes[]) public acks;

Expand All @@ -27,6 +25,16 @@ contract KeyGenHistory is UpgradeabilityAdmin, IKeyGenHistory {
/// @dev The address of the `ValidatorSetHbbft` contract.
IValidatorSetHbbft public validatorSetContract;

/// @dev round counter for key generation rounds.
/// in an ideal world, every key generation only requires one try,
/// and all validators manage to write their acks and parts,
/// so it is possible to achieve this goal in round 0.
/// in the real world, there are failures,
/// this mechanics helps covering that,
/// by revoking transactions, that were targeted for an earlier key gen round.
/// more infos: https://github.com/DMDcoin/hbbft-posdao-contracts/issues/106
uint256 public currentKeyGenRound;

event NewValidatorsSet(address[] newValidatorSet);

/// @dev Ensures the `initialize` function was called before.
Expand Down Expand Up @@ -55,6 +63,14 @@ contract KeyGenHistory is UpgradeabilityAdmin, IKeyGenHistory {
_;
}

/// @dev ensures that Key Generation functions are called with wrong _epoch
/// parameter to prevent old and wrong transactions get picked up.
modifier onlyCorrectRound(uint _roundCounter) {
require(currentKeyGenRound == _roundCounter,
"Key Generation function called with wrong _roundCounter parameter.");
_;
}

/// @dev Clears the state (acks and parts of previous validators.
/// @param _prevValidators The list of previous validators.
function clearPrevKeyGenState(address[] calldata _prevValidators)
Expand All @@ -69,6 +85,19 @@ contract KeyGenHistory is UpgradeabilityAdmin, IKeyGenHistory {
numberOfAcksWritten = 0;
}

function notifyKeyGenFailed()
external
onlyValidatorSet {
currentKeyGenRound = currentKeyGenRound + 1;
}

function notifyNewEpoch()
external
onlyValidatorSet {
currentKeyGenRound = 1;
}


function initialize(
address _validatorSetContract,
address[] memory _validators,
Expand All @@ -85,17 +114,19 @@ contract KeyGenHistory is UpgradeabilityAdmin, IKeyGenHistory {
require(_validatorSetContract != address(0), "Validator contract address cannot be 0.");

validatorSetContract = IValidatorSetHbbft(_validatorSetContract);
validatorSet = _validators;

for (uint256 i = 0; i < _validators.length; i++) {
parts[_validators[i]] = _parts[i];
acks[_validators[i]] = _acks[i];
}

currentKeyGenRound = 1;
}

function writePart(uint256 _upcommingEpoch, bytes memory _part)
function writePart(uint256 _upcommingEpoch, uint256 _roundCounter, bytes memory _part)
public
onlyUpcommingEpoch(_upcommingEpoch) {
onlyUpcommingEpoch(_upcommingEpoch)
onlyCorrectRound(_roundCounter) {
// It can only be called by a new validator which is elected but not yet finalized...
// ...or by a validator which is already in the validator set.
require(validatorSetContract.isPendingValidator(msg.sender), "Sender is not a pending validator");
Expand All @@ -104,9 +135,10 @@ contract KeyGenHistory is UpgradeabilityAdmin, IKeyGenHistory {
numberOfPartsWritten++;
}

function writeAcks(uint256 _upcommingEpoch, bytes[] memory _acks)
function writeAcks(uint256 _upcommingEpoch, uint256 _roundCounter, bytes[] memory _acks)
public
onlyUpcommingEpoch(_upcommingEpoch) {
onlyUpcommingEpoch(_upcommingEpoch)
onlyCorrectRound(_roundCounter) {
// It can only be called by a new validator which is elected but not yet finalized...
// ...or by a validator which is already in the validator set.
require(validatorSetContract.isPendingValidator(msg.sender), "Sender is not a pending validator");
Expand All @@ -129,6 +161,13 @@ contract KeyGenHistory is UpgradeabilityAdmin, IKeyGenHistory {
return acks[val].length;
}

function getCurrentKeyGenRound()
external
view
returns(uint256) {
return currentKeyGenRound;
}

function getNumberOfKeyFragmentsWritten()
external
view
Expand Down
20 changes: 14 additions & 6 deletions contracts/TxPermissionHbbft.sol
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,15 @@ contract TxPermissionHbbft is UpgradeableOwned, ITxPermission {
} else {
return (NONE, false);
}

uint256 roundCounter = _getSliceUInt256(36, _data);

if (roundCounter == IStakingHbbft(validatorSetContract.stakingContract()).stakingEpoch() + 1) {
return (CALL, false);
} else {
return (NONE, false);
}

} else {
// we want to write the Acks, but it's not time for write the Acks.
// so this transaction is not allowed.
Expand Down Expand Up @@ -367,14 +376,13 @@ contract TxPermissionHbbft is UpgradeableOwned, ITxPermission {
// bytes4(keccak256("reportMalicious(address,uint256,bytes)"))
bytes4 public constant REPORT_MALICIOUS_SIGNATURE = 0xc476dd40;

// bytes4(keccak256("writePart(uint256,bytes)"))
bytes4 public constant WRITE_PART_SIGNATURE = 0x0334657d;
// bytes4(keccak256("writePart(uint256,uint256,bytes)"))
bytes4 public constant WRITE_PART_SIGNATURE = 0x2d4de124;

// bytes4(keccak256("writeAcks(uint256,bytes[])"))
bytes4 public constant WRITE_ACKS_SIGNATURE = 0xc56aef48;
// bytes4(keccak256("writeAcks(uint256,uint256,bytes[])"))
bytes4 public constant WRITE_ACKS_SIGNATURE = 0x5623208e;

// bytes4(keccak256("announceAvailability()"))
bytes4 public constant ANNOUNCE_AVAILABILITY_SIGNATURE = 0x292525af;
bytes4 public constant ANNOUNCE_AVAILABILITY_SIGNATURE = 0x43bcce9f;

/// @dev An internal function used by the `addAllowedSender` and `initialize` functions.
/// @param _sender The address for which transactions of any type must be allowed.
Expand Down
44 changes: 40 additions & 4 deletions contracts/ValidatorSetHbbft.sol
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ contract ValidatorSetHbbft is UpgradeableOwned, IValidatorSetHbbft {
_setStakingAddress(miningAddress, _initialStakingAddresses[i]);
}

maxValidators = 7;

maxValidators = 25;
}

/// @dev Called by the system when a pending validator set is ready to be activated.
Expand All @@ -224,6 +223,7 @@ contract ValidatorSetHbbft is UpgradeableOwned, IValidatorSetHbbft {

// new epoch starts
stakingContract.incrementStakingEpoch();
keyGenHistoryContract.notifyNewEpoch();
delete _pendingValidators;
stakingContract.setStakingEpochStartTime(this.getCurrentTimestamp());

Expand All @@ -250,9 +250,17 @@ contract ValidatorSetHbbft is UpgradeableOwned, IValidatorSetHbbft {

/// @dev called by validators when a validator comes online after
/// getting marked as unavailable caused by a failed key generation.
function announceAvailability()
function announceAvailability(uint256 _blockNumber, bytes32 _blockhash)
external {
require(canCallAnnounceAvailability(msg.sender), 'Announcing availability not possible');

require(canCallAnnounceAvailability(msg.sender), 'Announcing availability not possible.');
require(_blockNumber < block.number, '_blockNumber argument must be in the past.');
// 255 is a technical limitation of EVM ability to look into the past.
// however, we query just for 16 blocks here.
// this provides a time window big enough for valid nodes.
require(_blockNumber + 16 > block.number, '_blockNumber argument must be in the past within the last 255 blocks.');
// we have ensured now that we technicaly able to query the blockhash for that block
require(blockhash(_blockNumber) == _blockhash, 'provided blockhash must match blockchains blockhash');

uint timestamp = this.getCurrentTimestamp();
validatorAvailableSince[msg.sender] = timestamp;
Expand Down Expand Up @@ -321,6 +329,8 @@ contract ValidatorSetHbbft is UpgradeableOwned, IValidatorSetHbbft {
= keyGenHistoryContract.getNumberOfKeyFragmentsWritten();


//address[] memory badValidators = new address[];

for(uint i = 0; i < _pendingValidators.length; i++) {

// get mining address for this pool.
Expand Down Expand Up @@ -364,6 +374,9 @@ contract ValidatorSetHbbft is UpgradeableOwned, IValidatorSetHbbft {
}
}

keyGenHistoryContract.clearPrevKeyGenState(_pendingValidators);
keyGenHistoryContract.notifyKeyGenFailed();

// we might only set a subset to the newValidatorSet function,
// since the last indexes of the array are holding unused slots.
address[] memory forcedPools = new address[](goodValidatorsCount);
Expand Down Expand Up @@ -842,6 +855,29 @@ contract ValidatorSetHbbft is UpgradeableOwned, IValidatorSetHbbft {
// Remove pools marked as `to be removed`
stakingContract.removePools();
}

// a new validator set can get choosen already outside the timeframe for phase 2.
// this can happen if the network got stuck and get's repaired.
// and the repair takes longer than a single epoch.
// we detect this case here and grant an extra time window
// so the selected nodes also get their chance to write their keys.
// more about: https://github.com/DMDcoin/hbbft-posdao-contracts/issues/96

// timescale:
// epoch start time ..... phase 2 transition .... current end of phase 2 ..... now ..... new end of phase 2.

// new extra window size has to cover the difference between phase2 transition and now.
// to reach the new end of phase 2.

// current end of phase 2 : stakingContract.stakingFixedEpochEndTime()

// now: validatorSetContract.getCurrentTimestamp();

if (this.getCurrentTimestamp() > stakingContract.stakingFixedEpochEndTime()) {
stakingContract.notifyNetworkOfftimeDetected(this.getCurrentTimestamp()
- stakingContract.stakingFixedEpochEndTime());
}

}

/// @dev Sets a new validator set stored in `_pendingValidators` array.
Expand Down
24 changes: 21 additions & 3 deletions contracts/base/BlockRewardHbbftBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ contract BlockRewardHbbftBase is UpgradeableOwned, IBlockRewardHbbft {
/// starting a new staking epoch, snapshotting staking amounts for the upcoming staking epoch,
/// and rewards distributing at the end of a staking epoch.
/// @param _isEpochEndBlock Indicates if this is the last block of the current epoch i.e.
/// just before the pending validators are fiinalized.
/// just before the pending validators are finalized.
function reward(bool _isEpochEndBlock)
external
onlySystem
Expand Down Expand Up @@ -247,7 +247,9 @@ contract BlockRewardHbbftBase is UpgradeableOwned, IBlockRewardHbbft {

uint256 phaseTransitionTime = stakingContract.startTimeOfNextPhaseTransition();
uint256 currentTimestamp = validatorSetContract.getCurrentTimestamp();


// TODO: Problem occurs here if there are not regular blocks:
// https://github.com/DMDcoin/hbbft-posdao-contracts/issues/96

//we are in a transition to phase 2 if the time for it arrived,
// and we do not have pendingValidators yet.
Expand All @@ -258,9 +260,25 @@ contract BlockRewardHbbftBase is UpgradeableOwned, IBlockRewardHbbft {
if (isPhaseTransition) {
// Choose new validators
validatorSetContract.newValidatorSet();
} else if (currentTimestamp >= stakingContract.stakingFixedEpochEndTime() ) {
} else if (currentTimestamp >= stakingContract.stakingFixedEpochEndTime()) {
validatorSetContract.handleFailedKeyGeneration();
}
// } else {

// // check for faster validator set upscaling
// // https://github.com/DMDcoin/hbbft-posdao-contracts/issues/90

// address[] memory miningAddresses = validatorSetContract.getValidators();

// // if there is a miningset that is smaller than the 2/3 of the maxValidators,
// // then we choose the next epoch set.
// if (miningAddresses.length < (validatorSetContract.maxValidators() / 3) * 2) {
// address[] memory poolsToBeElected = stakingContract.getPoolsToBeElected();
// if (poolsToBeElected.length > miningAddresses.length) {
// validatorSetContract.newValidatorSet();
// }
// }
// }
}
}

Expand Down
16 changes: 16 additions & 0 deletions contracts/base/StakingHbbftBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,21 @@ contract StakingHbbftBase is UpgradeableOwned, IStakingHbbft {
currentKeyGenExtraTimeWindow += stakingTransitionTimeframeLength;
}

/// @dev Notifies hbbft staking contract about a detected
/// network offline time.
/// if there is no handling for this,
/// validators got chosen outside the transition timewindow
/// and get banned immediatly, since they never got their chance
/// to write their keys.
/// more about: https://github.com/DMDcoin/hbbft-posdao-contracts/issues/96
function notifyNetworkOfftimeDetected(uint256 detectedOfflineTime)
public
onlyValidatorSetContract {
currentKeyGenExtraTimeWindow = currentKeyGenExtraTimeWindow
+ detectedOfflineTime
+ stakingTransitionTimeframeLength;
}

/// @dev Notifies hbbft staking contract that a validator
/// asociated with the given `_stakingAddress` became
/// available again and can be put on to the list
Expand All @@ -621,6 +636,7 @@ contract StakingHbbftBase is UpgradeableOwned, IStakingHbbft {
{
if (stakeAmount[_stakingAddress][_stakingAddress] >= candidateMinStake) {
_addPoolActive(_stakingAddress, true);
_setLikelihood(_stakingAddress);
}
}

Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/IKeyGenHistory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ interface IKeyGenHistory {
function clearPrevKeyGenState(address[] calldata) external;
function getAcksLength(address val) external view returns(uint256);
function getPart(address val) external view returns (bytes memory);
function getCurrentKeyGenRound() external view returns(uint256);
function getNumberOfKeyFragmentsWritten() external view returns(uint128, uint128);
function notifyNewEpoch() external;
function notifyKeyGenFailed() external;

}
1 change: 1 addition & 0 deletions contracts/interfaces/IStakingHbbft.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface IStakingHbbft {
function setStakingEpochStartTime(uint256) external;
function notifyKeyGenFailed() external;
function notifyAvailability(address _stakingAddress) external;
function notifyNetworkOfftimeDetected(uint256) external;
function getPoolPublicKey(address _poolAddress) external view returns (bytes memory);
function getPoolsLikelihood() external view returns(uint256[] memory, uint256);
function getPoolsToBeElected() external view returns(address[] memory);
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IValidatorSetHbbft.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface IValidatorSetHbbft {
address[] calldata,
address[] calldata
) external;
function announceAvailability() external;
function announceAvailability(uint256, bytes32) external;
function finalizeChange() external;
function newValidatorSet() external;
function removeMaliciousValidators(address[] calldata) external;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"type": "git",
"url": "git+https://github.com/poanetwork/posdao-contracts.git"
},
"author": "vadim@poa.network",
"author": "thomashaller@gmx.at",
"bugs": {
"url": "https://github.com/poanetwork/posdao-contracts/issues"
},
Expand Down
39 changes: 39 additions & 0 deletions scripts/check-node-state.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

#queryOptions='-H "Content-Type: application/json" -X POST localhost:8540'
#echo $queryOptions

# 0x60e5c520000000000000000000000000f8a7d150d9290a665792f9af15d17df477bacb9e
address=

availableSince=$(curl -s --data '{"method":"eth_call","params":[{"from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1","to":"0x1000000000000000000000000000000000000001","data":"0x60e5c520000000000000000000000000f8a7d150d9290a665792f9af15d17df477bacb9e"}],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8540)
# availableSince=$(curl -s --data '{"method":"eth_call","params":[{"from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1","to":"0x1000000000000000000000000000000000000001","data":"0x60e5c520000000000000000000000000f8a7d150d9290a665792f9af15d17df477bacb9e"}],"id":1,"jsonrpc":"2.0"}' $queryOptions)

# if availaible since contains zero, then we know that we are not available.


if [[ $availableSince == *"0x0000000000000000000000000000000000000000000000000000000000000000"* ]]; then
echo "we need to restart!"
PID=pidof openethereum
echo "kill $PID"
sleep 15
echo "starting node again."
sh start-node.sh &
fi


#curl -s --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8540 | grep -Po '"result":.*?[^\\]",'
#curl -s --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8540 | grep -Po 's/"result"://; s/^"//; s/",$//'



# blockNumber=$(curl -s --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8540 | jq -r '.result')

# echo "we are on block number $blockNumber"

# blockTime=$(curl -s --data '{"method":"eth_getBlockByNumber","params":["'$blockNumber'", true],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8540 | jq -r '.result.timestamp')

# echo "The decimal value of $blockTime=%d\n" $((16#$blockTime))

# echo $blockTime

Loading

0 comments on commit 72957ec

Please sign in to comment.