Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
212 lines
6.7 KiB
Solidity
212 lines
6.7 KiB
Solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
|
|
/**
|
|
* @title AERE Consensus Contract
|
|
* @dev Manages consensus mechanism and validator operations
|
|
*/
|
|
contract AereConsensus is Ownable, ReentrancyGuard {
|
|
|
|
struct Validator {
|
|
address validatorAddress;
|
|
uint256 stake;
|
|
uint256 votingPower;
|
|
bool isActive;
|
|
uint256 joinedAt;
|
|
uint256 rewardsEarned;
|
|
}
|
|
|
|
struct ConsensusRound {
|
|
uint256 roundNumber;
|
|
address proposer;
|
|
uint256 blockHeight;
|
|
uint256 timestamp;
|
|
bool finalized;
|
|
}
|
|
|
|
// State variables
|
|
mapping(address => Validator) public validators;
|
|
mapping(uint256 => ConsensusRound) public consensusRounds;
|
|
address[] public validatorList;
|
|
|
|
uint256 public constant MINIMUM_STAKE = 100000 * 10**18; // 100K AERE
|
|
uint256 public constant BLOCK_TIME = 12; // 12 seconds
|
|
uint256 public currentRound = 0;
|
|
uint256 public totalStaked = 0;
|
|
|
|
// Events
|
|
event ValidatorJoined(address indexed validator, uint256 stake);
|
|
event ValidatorLeft(address indexed validator);
|
|
event ValidatorSlashed(address indexed validator, uint256 amount, string reason);
|
|
event ConsensusReached(uint256 indexed round, address indexed proposer);
|
|
event BlockProposed(uint256 indexed blockHeight, address indexed proposer);
|
|
|
|
constructor() {}
|
|
|
|
/**
|
|
* @dev Join as validator with minimum stake
|
|
*/
|
|
function joinValidator() external payable nonReentrant {
|
|
require(msg.value >= MINIMUM_STAKE, "Insufficient stake");
|
|
require(!validators[msg.sender].isActive, "Already a validator");
|
|
|
|
validators[msg.sender] = Validator({
|
|
validatorAddress: msg.sender,
|
|
stake: msg.value,
|
|
votingPower: calculateVotingPower(msg.value),
|
|
isActive: true,
|
|
joinedAt: block.timestamp,
|
|
rewardsEarned: 0
|
|
});
|
|
|
|
validatorList.push(msg.sender);
|
|
totalStaked += msg.value;
|
|
|
|
emit ValidatorJoined(msg.sender, msg.value);
|
|
}
|
|
|
|
/**
|
|
* @dev Leave validator set and withdraw stake
|
|
*/
|
|
function leaveValidator() external nonReentrant {
|
|
require(validators[msg.sender].isActive, "Not an active validator");
|
|
|
|
Validator storage validator = validators[msg.sender];
|
|
uint256 stakeToReturn = validator.stake;
|
|
|
|
validator.isActive = false;
|
|
totalStaked -= stakeToReturn;
|
|
|
|
// Remove from validator list
|
|
for (uint i = 0; i < validatorList.length; i++) {
|
|
if (validatorList[i] == msg.sender) {
|
|
validatorList[i] = validatorList[validatorList.length - 1];
|
|
validatorList.pop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
payable(msg.sender).transfer(stakeToReturn);
|
|
emit ValidatorLeft(msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @dev Propose new block (only active validators)
|
|
*/
|
|
function proposeBlock(uint256 blockHeight) external {
|
|
require(validators[msg.sender].isActive, "Not an active validator");
|
|
require(isValidProposer(msg.sender), "Not selected as proposer");
|
|
|
|
currentRound++;
|
|
consensusRounds[currentRound] = ConsensusRound({
|
|
roundNumber: currentRound,
|
|
proposer: msg.sender,
|
|
blockHeight: blockHeight,
|
|
timestamp: block.timestamp,
|
|
finalized: false
|
|
});
|
|
|
|
emit BlockProposed(blockHeight, msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @dev Finalize consensus round
|
|
*/
|
|
function finalizeRound(uint256 roundNumber) external onlyOwner {
|
|
require(consensusRounds[roundNumber].proposer != address(0), "Round does not exist");
|
|
require(!consensusRounds[roundNumber].finalized, "Round already finalized");
|
|
|
|
consensusRounds[roundNumber].finalized = true;
|
|
|
|
// Distribute rewards to proposer
|
|
address proposer = consensusRounds[roundNumber].proposer;
|
|
validators[proposer].rewardsEarned += calculateBlockReward();
|
|
|
|
emit ConsensusReached(roundNumber, proposer);
|
|
}
|
|
|
|
/**
|
|
* @dev Slash validator for malicious behavior
|
|
*/
|
|
function slashValidator(address validatorAddress, uint256 slashAmount, string memory reason) external onlyOwner {
|
|
require(validators[validatorAddress].isActive, "Validator not active");
|
|
require(slashAmount <= validators[validatorAddress].stake, "Slash amount too high");
|
|
|
|
validators[validatorAddress].stake -= slashAmount;
|
|
totalStaked -= slashAmount;
|
|
|
|
// If stake falls below minimum, remove validator
|
|
if (validators[validatorAddress].stake < MINIMUM_STAKE) {
|
|
validators[validatorAddress].isActive = false;
|
|
}
|
|
|
|
emit ValidatorSlashed(validatorAddress, slashAmount, reason);
|
|
}
|
|
|
|
/**
|
|
* @dev Calculate voting power based on stake
|
|
*/
|
|
function calculateVotingPower(uint256 stake) public pure returns (uint256) {
|
|
return stake / (10**18); // 1 voting power per AERE
|
|
}
|
|
|
|
/**
|
|
* @dev Calculate block reward
|
|
*/
|
|
function calculateBlockReward() public view returns (uint256) {
|
|
return 16 * 10**18; // 16 AERE per block
|
|
}
|
|
|
|
/**
|
|
* @dev Check if address is valid proposer for current round
|
|
*/
|
|
function isValidProposer(address proposer) public view returns (bool) {
|
|
if (!validators[proposer].isActive) return false;
|
|
|
|
// Simple round-robin selection based on block number
|
|
uint256 proposerIndex = block.number % validatorList.length;
|
|
return validatorList[proposerIndex] == proposer;
|
|
}
|
|
|
|
/**
|
|
* @dev Get validator info
|
|
*/
|
|
function getValidator(address validatorAddress) external view returns (Validator memory) {
|
|
return validators[validatorAddress];
|
|
}
|
|
|
|
/**
|
|
* @dev Get active validator count
|
|
*/
|
|
function getActiveValidatorCount() external view returns (uint256) {
|
|
uint256 count = 0;
|
|
for (uint i = 0; i < validatorList.length; i++) {
|
|
if (validators[validatorList[i]].isActive) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @dev Get consensus stats
|
|
*/
|
|
function getConsensusStats() external view returns (
|
|
uint256 _totalValidators,
|
|
uint256 _totalStaked,
|
|
uint256 _currentRound,
|
|
uint256 _blockTime
|
|
) {
|
|
return (
|
|
validatorList.length,
|
|
totalStaked,
|
|
currentRound,
|
|
BLOCK_TIME
|
|
);
|
|
}
|
|
}
|