From 0eedfdd308b05439a7ecee79c284413894b4bd80 Mon Sep 17 00:00:00 2001 From: Syed Ghufran Hassan Date: Thu, 19 Feb 2026 12:37:11 +0500 Subject: [PATCH] feat: implement weighted voting system based on node energy capacity Add dynamic voting power mechanism where nodes vote with weight proportional to their energy capacity (1 MW = 1 voting power). This ensures grid decisions reflect the stake and importance of each participant. Key features: - Weighted voting: votesFor/votesAgainst track total MW supporting/opposing - Quorum requirement: minimum 30% of total active capacity must participate - Approval threshold: 60% weighted approval needed for execution - Node health monitoring: auto-deactivation after 30 days without heartbeat - Real-time status tracking: approval percentages and quorum calculations New struct fields: - EnergyNode.votingPower: derived from capacity - EnergyNode.totalVotesCast: track node participation - totalActiveCapacity: global sum for quorum calculations Enhanced functions: - voteOnCommand: applies weighted voting power - registerEnergyNode: sets initial voting power from capacity - deactivateNode: removes capacity from total when nodes go offline - getCommandStatus: returns detailed vote breakdown and execution eligibility - checkNodeHealth: auto-maintenance for inactive nodes This improvement ensures democratic governance where influence scales with energy contribution, preventing minority control while maintaining operational efficiency through automated health checks. --- contracts/src/EnergyGridControl.sol | 215 ++++++++++++++++++++-------- 1 file changed, 159 insertions(+), 56 deletions(-) diff --git a/contracts/src/EnergyGridControl.sol b/contracts/src/EnergyGridControl.sol index 30db5818ce..1d2d02828d 100644 --- a/contracts/src/EnergyGridControl.sol +++ b/contracts/src/EnergyGridControl.sol @@ -21,8 +21,8 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { uint256 timestamp; // Proposal time uint256 executionTime; // When to execute bool executed; // Execution status - uint256 votesFor; // Consensus votes - uint256 votesAgainst; + uint256 votesFor; // Consensus votes (weighted by capacity) + uint256 votesAgainst; // Weighted votes against } struct EnergyNode { @@ -31,6 +31,8 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { uint256 capacity; // Energy capacity in MW bool isActive; uint256 lastHeartbeat; + uint256 votingPower; // Derived from capacity (1 MW = 1 voting power) + uint256 totalVotesCast; // Track total votes cast by this node } // State variables @@ -38,28 +40,27 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { mapping(address => EnergyNode) public energyNodes; mapping(uint256 => mapping(address => bool)) public hasVoted; // commandId => voter => voted mapping(address => bool) public authorizedProposers; - mapping(address => bool) public authorizedVoters; uint256 public commandCount; uint256 public constant VOTING_PERIOD = 7 days; uint256 public constant EXECUTION_DELAY = 1 hours; - uint256 public constant MIN_VOTES_REQUIRED = 3; // For demo, low threshold + uint256 public constant MIN_APPROVAL_PERCENTAGE = 60; // 60% weighted vote required + uint256 public constant MIN_PARTICIPATION_PERCENTAGE = 30; // 30% of total capacity must vote + + // New: Track total active capacity for quorum calculations + uint256 public totalActiveCapacity; // Events event CommandProposed(uint256 indexed commandId, string command, string region, address proposer); - event CommandVoted(uint256 indexed commandId, address voter, bool support); - event CommandExecuted(uint256 indexed commandId, bytes photonicData); - event NodeRegistered(address indexed nodeAddress, string location, uint256 capacity); - event PhotonicSignalEmitted(uint256 indexed commandId, bytes data); + event CommandVoted(uint256 indexed commandId, address voter, uint256 votingPower, bool support); + event CommandExecuted(uint256 indexed commandId, bytes photonicData, uint256 totalVotesFor, uint256 totalVotesAgainst); + event NodeRegistered(address indexed nodeAddress, string location, uint256 capacity, uint256 votingPower); + event NodeDeactivated(address indexed nodeAddress); + event VotingPowerUpdated(address indexed nodeAddress, uint256 newVotingPower); // Modifiers - modifier onlyAuthorizedProposer() { - require(authorizedProposers[msg.sender] || owner() == msg.sender, "Not authorized proposer"); - _; - } - - modifier onlyAuthorizedVoter() { - require(authorizedVoters[msg.sender] || owner() == msg.sender, "Not authorized voter"); + modifier onlyActiveNode() { + require(energyNodes[msg.sender].isActive, "Node not active or registered"); _; } @@ -76,9 +77,8 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { } constructor() { - // Initialize owner as authorized + // Initialize owner as authorized proposer authorizedProposers[owner()] = true; - authorizedVoters[owner()] = true; } /** @@ -91,7 +91,7 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { string calldata _command, string calldata _region, bytes calldata _photonicData - ) external onlyAuthorizedProposer nonReentrant { + ) external onlyOwner nonReentrant { // Only owner for demo, expand in production require(bytes(_command).length > 0, "Command cannot be empty"); require(bytes(_region).length > 0, "Region cannot be empty"); require(_photonicData.length > 0, "Photonic data required"); @@ -109,42 +109,82 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { } /** - * @dev Vote on a proposed command + * @dev Vote on a proposed command with weighted voting power * @param _commandId ID of the command to vote on * @param _support True for approval, false for rejection */ function voteOnCommand( uint256 _commandId, bool _support - ) external onlyAuthorizedVoter commandExists(_commandId) votingOpen(_commandId) nonReentrant { + ) external onlyActiveNode commandExists(_commandId) votingOpen(_commandId) nonReentrant { require(!hasVoted[_commandId][msg.sender], "Already voted"); - hasVoted[_commandId][msg.sender] = true; + EnergyNode storage voter = energyNodes[msg.sender]; GridCommand storage cmd = commands[_commandId]; + // Mark as voted + hasVoted[_commandId][msg.sender] = true; + voter.totalVotesCast++; + + // Apply weighted voting based on node capacity + uint256 votingWeight = voter.votingPower; + if (_support) { - cmd.votesFor++; + cmd.votesFor += votingWeight; } else { - cmd.votesAgainst++; + cmd.votesAgainst += votingWeight; } - emit CommandVoted(_commandId, msg.sender, _support); + emit CommandVoted(_commandId, msg.sender, votingWeight, _support); + + // Check if consensus can be reached + _checkAndExecuteCommand(_commandId); + } + + /** + * @dev Check if command meets criteria for execution + * @param _commandId Command to check + */ + function _checkAndExecuteCommand(uint256 _commandId) internal { + GridCommand storage cmd = commands[_commandId]; + + // Calculate total votes cast + uint256 totalVotesCast = cmd.votesFor + cmd.votesAgainst; + + // Check minimum participation (quorum) + if (totalVotesCast < (totalActiveCapacity * MIN_PARTICIPATION_PERCENTAGE) / 100) { + return; // Not enough participation yet + } - // Auto-execute if consensus reached - if (cmd.votesFor >= MIN_VOTES_REQUIRED && cmd.votesFor > cmd.votesAgainst) { - _executeCommand(_commandId); + // Check approval percentage + if (totalVotesCast > 0) { + uint256 approvalPercentage = (cmd.votesFor * 100) / totalVotesCast; + + if (approvalPercentage >= MIN_APPROVAL_PERCENTAGE) { + _executeCommand(_commandId); + } } } /** - * @dev Execute a command after voting period or consensus + * @dev Execute a command after voting period * @param _commandId ID of the command to execute */ function executeCommand(uint256 _commandId) external commandExists(_commandId) nonReentrant { GridCommand storage cmd = commands[_commandId]; require(!cmd.executed, "Command already executed"); require(block.timestamp >= cmd.timestamp + VOTING_PERIOD, "Voting period not ended"); - require(cmd.votesFor > cmd.votesAgainst, "Command not approved"); + + uint256 totalVotesCast = cmd.votesFor + cmd.votesAgainst; + require(totalVotesCast > 0, "No votes cast"); + + // Check quorum + require(totalVotesCast >= (totalActiveCapacity * MIN_PARTICIPATION_PERCENTAGE) / 100, + "Quorum not reached"); + + // Check approval + uint256 approvalPercentage = (cmd.votesFor * 100) / totalVotesCast; + require(approvalPercentage >= MIN_APPROVAL_PERCENTAGE, "Command not approved"); _executeCommand(_commandId); } @@ -155,19 +195,22 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { */ function _executeCommand(uint256 _commandId) internal { GridCommand storage cmd = commands[_commandId]; + require(!cmd.executed, "Command already executed"); + cmd.executed = true; // Emit photonic signal for satellite transmission emit PhotonicSignalEmitted(_commandId, cmd.photonicData); - emit CommandExecuted(_commandId, cmd.photonicData); + emit CommandExecuted(_commandId, cmd.photonicData, cmd.votesFor, cmd.votesAgainst); console.log("Energy grid command executed:", cmd.command); console.log("Region:", cmd.region); - console.log("Photonic data length:", cmd.photonicData.length); + console.log("Votes For (MW):", cmd.votesFor); + console.log("Votes Against (MW):", cmd.votesAgainst); } /** - * @dev Register an energy node in the grid + * @dev Register an energy node in the grid with weighted voting power * @param _location Geographic location * @param _capacity Energy capacity in MW */ @@ -176,67 +219,127 @@ contract EnergyGridControl is Ownable, ReentrancyGuard { uint256 _capacity ) external nonReentrant { require(!energyNodes[msg.sender].isActive, "Node already registered"); + require(_capacity > 0, "Capacity must be greater than 0"); + + // Calculate voting power (1 MW = 1 voting power, with minimum 1) + uint256 votingPower = _capacity; energyNodes[msg.sender] = EnergyNode({ nodeAddress: msg.sender, location: _location, capacity: _capacity, isActive: true, - lastHeartbeat: block.timestamp + lastHeartbeat: block.timestamp, + votingPower: votingPower, + totalVotesCast: 0 }); - emit NodeRegistered(msg.sender, _location, _capacity); + // Update total active capacity + totalActiveCapacity += _capacity; + + emit NodeRegistered(msg.sender, _location, _capacity, votingPower); } /** - * @dev Update node heartbeat + * @dev Update node heartbeat to maintain active status */ - function heartbeat() external { - require(energyNodes[msg.sender].isActive, "Node not registered"); + function heartbeat() external onlyActiveNode { energyNodes[msg.sender].lastHeartbeat = block.timestamp; } /** - * @dev Authorize a proposer - * @param _proposer Address to authorize + * @dev Deactivate node (if heartbeat expired) + * @param _nodeAddress Address of node to deactivate + */ + function deactivateNode(address _nodeAddress) external onlyOwner { + require(energyNodes[_nodeAddress].isActive, "Node not active"); + + uint256 capacity = energyNodes[_nodeAddress].capacity; + energyNodes[_nodeAddress].isActive = false; + + // Update total active capacity + totalActiveCapacity -= capacity; + + emit NodeDeactivated(_nodeAddress); + } + + /** + * @dev Check node health and auto-deactivate if heartbeat too old + * @param _nodeAddress Address of node to check */ - function authorizeProposer(address _proposer) external onlyOwner { - authorizedProposers[_proposer] = true; + function checkNodeHealth(address _nodeAddress) external { + EnergyNode storage node = energyNodes[_nodeAddress]; + require(node.isActive, "Node not active"); + + // Deactivate if no heartbeat for 30 days + if (block.timestamp > node.lastHeartbeat + 30 days) { + totalActiveCapacity -= node.capacity; + node.isActive = false; + emit NodeDeactivated(_nodeAddress); + } } /** - * @dev Authorize a voter - * @param _voter Address to authorize + * @dev Get current voting power for a node + * @param _nodeAddress Address of node + * @return Voting power in MW */ - function authorizeVoter(address _voter) external onlyOwner { - authorizedVoters[_voter] = true; + function getVotingPower(address _nodeAddress) external view returns (uint256) { + if (!energyNodes[_nodeAddress].isActive) { + return 0; + } + return energyNodes[_nodeAddress].votingPower; } /** - * @dev Get command details + * @dev Get command status with vote breakdown * @param _commandId Command ID - * @return Command struct */ - function getCommand(uint256 _commandId) external view commandExists(_commandId) returns (GridCommand memory) { - return commands[_commandId]; + function getCommandStatus(uint256 _commandId) external view commandExists(_commandId) returns ( + string memory command, + string memory region, + uint256 votesFor, + uint256 votesAgainst, + uint256 totalVotes, + uint256 approvalPercentage, + bool executable, + bool executed + ) { + GridCommand storage cmd = commands[_commandId]; + totalVotes = cmd.votesFor + cmd.votesAgainst; + + uint256 approvalPct = 0; + if (totalVotes > 0) { + approvalPct = (cmd.votesFor * 100) / totalVotes; + } + + bool meetsQuorum = totalVotes >= (totalActiveCapacity * MIN_PARTICIPATION_PERCENTAGE) / 100; + bool meetsApproval = totalVotes > 0 && approvalPct >= MIN_APPROVAL_PERCENTAGE; + + return ( + cmd.command, + cmd.region, + cmd.votesFor, + cmd.votesAgainst, + totalVotes, + approvalPct, + (meetsQuorum && meetsApproval && !cmd.executed), + cmd.executed + ); } /** - * @dev Get total energy capacity across all nodes + * @dev Get total active capacity * @return Total capacity in MW */ function getTotalGridCapacity() external view returns (uint256) { - uint256 total = 0; - // In production, iterate through all nodes (requires off-chain indexing) - // For demo, return placeholder - return total; + return totalActiveCapacity; } /** * @dev Emergency shutdown (owner only) */ function emergencyShutdown() external onlyOwner { - // Implement emergency protocols console.log("Emergency shutdown initiated by:", msg.sender); } -} \ No newline at end of file +}