mirror of https://github.com/poanetwork/quorum.git
Contract changes for majority voting
This commit is contained in:
parent
5de9b000b7
commit
78bd7e606a
|
@ -2,98 +2,345 @@ pragma solidity ^0.4.23;
|
|||
|
||||
contract Permissions {
|
||||
|
||||
enum NodeStatus { NotInList, PendingApproval, Approved, PendingDeactivation, Deactivated, PendingBlacklisting, Blacklisted}
|
||||
|
||||
enum AccountAccess {FullAccess, ReadOnly, Transact, ContractDeploy}
|
||||
|
||||
struct nodeDetails {
|
||||
string enodeId;
|
||||
//e.g. 127.0.0.1:20005
|
||||
// enum and struct declaration
|
||||
enum NodeStatus { NotInList, PendingApproval, Approved, PendingDeactivation, Deactivated, PendingBlacklisting, Blacklisted }
|
||||
enum AccountAccess { FullAccess, ReadOnly, Transact, ContractDeploy }
|
||||
struct NodeDetails {
|
||||
string enodeId; //e.g. 127.0.0.1:20005
|
||||
string ipAddrPort;
|
||||
string discPort;
|
||||
string raftPort;
|
||||
bool canWrite;
|
||||
bool canLead;
|
||||
NodeStatus status;
|
||||
}
|
||||
mapping (bytes32 => nodeDetails) nodeList;
|
||||
|
||||
struct acctAccess {
|
||||
address acctId;
|
||||
AccountAccess access;
|
||||
}
|
||||
mapping (address => acctAccess) acctAccessList;
|
||||
// use an array to store node details
|
||||
// if we want to list all node one day, mapping is not capable
|
||||
NodeDetails[] private nodeList;
|
||||
// use a mapping of enodeid to array index to track node
|
||||
mapping (bytes32 => uint) private nodeIdToIndex;
|
||||
// keep track of node number
|
||||
uint private numberOfNodes;
|
||||
|
||||
event NewNodeProposed (string _enodeId);
|
||||
// use an array to store account details
|
||||
// if we want to list all account one day, mapping is not capable
|
||||
address[] private accountList;
|
||||
|
||||
// store node approval, deactivation and blacklisting vote status (prevent double vote)
|
||||
mapping (uint => mapping (address => bool)) private voteStatus;
|
||||
// valid vote count
|
||||
mapping (uint => uint) private voteCount;
|
||||
|
||||
// node permission events
|
||||
event NewNodeProposed(string _enodeId);
|
||||
event VoteNodeApproval(string _enodeId, address _accountAddress);
|
||||
event NodeApproved(string _enodeId, string _ipAddrPort, string _discPort, string _raftPort);
|
||||
event NodePendingDeactivation (string _enodeId);
|
||||
event VoteNodeDeactivation(string _enodeId, address _accountAddress);
|
||||
event NodeDeactivated(string _enodeId, string _ipAddrPort, string _discPort, string _raftPort);
|
||||
event AcctAccessModified (address acctId, AccountAccess access);
|
||||
event NodePendingBlacklisting(string _enodeId);
|
||||
event VoteNodeBlacklisting(string _enodeId, address _accountAddress);
|
||||
event NodeBlacklisted(string _enodeId, string _ipAddrPort, string _discPort, string _raftPort);
|
||||
// account permission events
|
||||
event AccountAccessModified(address _address, AccountAccess _access);
|
||||
event NoVotingAccount();
|
||||
event VoterAdded(address _address);
|
||||
event VoterRemoved(address _address);
|
||||
|
||||
// Checks if the Node is already added. If yes then returns true
|
||||
function getNodeStatus (string _enodeId) public view returns (NodeStatus _status) {
|
||||
return nodeList[keccak256(abi.encodePacked(_enodeId))].status;
|
||||
modifier enodeInList(string _enodeId)
|
||||
{
|
||||
require(nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] != 0, "Enode is not in the list");
|
||||
_;
|
||||
}
|
||||
modifier enodeNotInList(string _enodeId)
|
||||
{
|
||||
require(nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] == 0, "Enode is in the list");
|
||||
_;
|
||||
}
|
||||
modifier canVote()
|
||||
{
|
||||
bool flag = false;
|
||||
for (uint i=0; i<accountList.length; i++){
|
||||
if (accountList[i] == msg.sender){
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
require(flag, "Account can not vote");
|
||||
_;
|
||||
}
|
||||
|
||||
/* public and external functions */
|
||||
|
||||
// view functions
|
||||
|
||||
// Get number of nodes
|
||||
function getNumberOfNodes()
|
||||
public view
|
||||
returns (uint)
|
||||
{
|
||||
return numberOfNodes;
|
||||
}
|
||||
// Get number of accounts and voting accounts
|
||||
function getNumberOfAccounts()
|
||||
public view
|
||||
returns (uint)
|
||||
{
|
||||
return accountList.length;
|
||||
}
|
||||
// Get node status by enode id
|
||||
function getNodeStatus(string _enodeId)
|
||||
public view
|
||||
enodeInList(_enodeId)
|
||||
returns (NodeStatus)
|
||||
{
|
||||
return nodeList[getNodeIndex(_enodeId)].status;
|
||||
}
|
||||
// Get vote count by enode id
|
||||
function getVoteCount(string _enodeId)
|
||||
public view
|
||||
enodeInList(_enodeId)
|
||||
returns (uint)
|
||||
{
|
||||
return voteCount[getNodeIndex(_enodeId)];
|
||||
}
|
||||
// Get vote status by enode id and voter address
|
||||
function getVoteStatus(string _enodeId, address _voter)
|
||||
public view
|
||||
enodeInList(_enodeId)
|
||||
returns (bool)
|
||||
{
|
||||
return voteStatus[getNodeIndex(_enodeId)][_voter];
|
||||
}
|
||||
// for potential external use
|
||||
// Get enode id by index
|
||||
function getEnodeId(uint _index)
|
||||
external view
|
||||
returns (string)
|
||||
{
|
||||
if (_index <= numberOfNodes){
|
||||
return nodeList[_index].enodeId;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
// Get account address by index
|
||||
function getAccountAddress(uint _index)
|
||||
external view
|
||||
returns (address)
|
||||
{
|
||||
if (_index <= accountList.length){
|
||||
return accountList[_index];
|
||||
} else {
|
||||
return address(0);
|
||||
}
|
||||
}
|
||||
|
||||
// state change functions
|
||||
|
||||
// propose a new node to the network
|
||||
function ProposeNode(string _enodeId, bool _canWrite, bool _canLead, string _ipAddrPort, string _discPort, string _raftPort) public {
|
||||
require(getNodeStatus(_enodeId) == NodeStatus.NotInList, "New node cannot be in the list");
|
||||
nodeList[keccak256(abi.encodePacked(_enodeId))] = nodeDetails(_enodeId, _ipAddrPort,_discPort, _raftPort, _canWrite, _canLead, NodeStatus.PendingApproval);
|
||||
emit NewNodeProposed (_enodeId);
|
||||
function proposeNode(string _enodeId, string _ipAddrPort, string _discPort, string _raftPort, bool _canLead)
|
||||
external
|
||||
enodeNotInList(_enodeId)
|
||||
{
|
||||
if (checkVotingAccountExist()){
|
||||
// increment node number, add node to the list
|
||||
numberOfNodes++;
|
||||
nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] = numberOfNodes;
|
||||
nodeList.push(NodeDetails(_enodeId, _ipAddrPort,_discPort, _raftPort, _canLead, NodeStatus.PendingApproval));
|
||||
// add voting status, numberOfNodes is the index of current proposed node
|
||||
for (uint i = 0; i < accountList.length; i++){
|
||||
voteStatus[numberOfNodes][accountList[i]] = false;
|
||||
}
|
||||
voteCount[numberOfNodes] = 0;
|
||||
// emit event
|
||||
emit NewNodeProposed(_enodeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a node to the nodeList mapping and emits node added event if successfully and node exists event of node is already present
|
||||
function ApproveNode(string _enodeId) public {
|
||||
require(getNodeStatus(_enodeId) == NodeStatus.PendingApproval);
|
||||
|
||||
bytes32 i;
|
||||
i = keccak256(abi.encodePacked(_enodeId));
|
||||
nodeList[i].status = NodeStatus.Approved;
|
||||
emit NodeApproved(nodeList[i].enodeId, nodeList[i].ipAddrPort, nodeList[i].discPort, nodeList[i].raftPort);
|
||||
function approveNode(string _enodeId)
|
||||
external
|
||||
canVote
|
||||
{
|
||||
require(getNodeStatus(_enodeId) == NodeStatus.PendingApproval, "Node need to be in PendingApproval status");
|
||||
uint nodeIndex = getNodeIndex(_enodeId);
|
||||
require(voteStatus[nodeIndex][msg.sender] == false, "Node can not double vote");
|
||||
// vote node
|
||||
voteStatus[nodeIndex][msg.sender] = true;
|
||||
voteCount[nodeIndex]++;
|
||||
// emit event
|
||||
emit VoteNodeApproval(_enodeId, msg.sender);
|
||||
// check if node vote reach majority
|
||||
checkNodeApproval(_enodeId);
|
||||
}
|
||||
|
||||
// Propose a node for deactivation from network
|
||||
function ProposeDeactivation(string _enodeId) public {
|
||||
require(getNodeStatus(_enodeId) == NodeStatus.Approved, "Node need to be in Approved status");
|
||||
nodeList[keccak256(abi.encodePacked(_enodeId))].status = NodeStatus.PendingDeactivation;
|
||||
emit NodePendingDeactivation(_enodeId);
|
||||
function ProposeDeactivation(string _enodeId)
|
||||
external
|
||||
enodeInList(_enodeId)
|
||||
{
|
||||
if (checkVotingAccountExist()){
|
||||
require(getNodeStatus(_enodeId) == NodeStatus.Approved, "Node need to be in Approved status");
|
||||
uint nodeIndex = getNodeIndex(_enodeId);
|
||||
nodeList[nodeIndex].status = NodeStatus.PendingDeactivation;
|
||||
// add voting status, numberOfNodes is the index of current proposed node
|
||||
for (uint i = 0; i < accountList.length; i++){
|
||||
voteStatus[nodeIndex][accountList[i]] = false;
|
||||
}
|
||||
voteCount[nodeIndex] = 0;
|
||||
// emit event
|
||||
emit NodePendingDeactivation(_enodeId);
|
||||
}
|
||||
}
|
||||
|
||||
//deactivates a given Enode and emits the decativation event
|
||||
function DeactivateNode (string _enodeId) public {
|
||||
function DeactivateNode(string _enodeId)
|
||||
external
|
||||
canVote
|
||||
{
|
||||
require(getNodeStatus(_enodeId) == NodeStatus.PendingDeactivation, "Node need to be in PendingDeactivation status");
|
||||
bytes32 i;
|
||||
i = keccak256(abi.encodePacked(_enodeId));
|
||||
nodeList[i].status = NodeStatus.Deactivated;
|
||||
emit NodeDeactivated(nodeList[i].enodeId, nodeList[i].ipAddrPort, nodeList[i].discPort, nodeList[i].raftPort);
|
||||
uint nodeIndex = getNodeIndex(_enodeId);
|
||||
require(voteStatus[nodeIndex][msg.sender] == false, "Node can not double vote");
|
||||
// vote node
|
||||
voteStatus[nodeIndex][msg.sender] = true;
|
||||
voteCount[nodeIndex]++;
|
||||
// emit event
|
||||
emit VoteNodeDeactivation(_enodeId, msg.sender);
|
||||
// check if node vote reach majority
|
||||
checkNodeDeactivation(_enodeId);
|
||||
}
|
||||
|
||||
// Propose node for blacklisting
|
||||
function ProposeNodeBlacklisting(string _enodeId, string _ipAddrPort, string _discPort, string _raftPort) public {
|
||||
if (getNodeStatus(_enodeId) == NodeStatus.NotInList){
|
||||
nodeList[keccak256(abi.encodePacked(_enodeId))] = nodeDetails(_enodeId, _ipAddrPort,_discPort, _raftPort, false, false, NodeStatus.PendingBlacklisting);
|
||||
// Propose node for blacklisting
|
||||
function ProposeNodeBlacklisting(string _enodeId, string _ipAddrPort, string _discPort, string _raftPort)
|
||||
external
|
||||
{
|
||||
if (checkVotingAccountExist()){
|
||||
uint nodeIndex;
|
||||
// check if node is in the nodeList
|
||||
if (nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] != 0){
|
||||
// no matter what status the node is in, vote will reset and node status change to PendingBlacklisting
|
||||
nodeList[nodeIndex].status = NodeStatus.PendingBlacklisting;
|
||||
nodeIndex = getNodeIndex(_enodeId);
|
||||
} else {
|
||||
// increment node number, add node to the list
|
||||
numberOfNodes++;
|
||||
nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] = numberOfNodes;
|
||||
nodeList.push(NodeDetails(_enodeId, _ipAddrPort,_discPort, _raftPort, false, NodeStatus.PendingBlacklisting));
|
||||
nodeIndex = numberOfNodes;
|
||||
}
|
||||
// add voting status, numberOfNodes is the index of current proposed node
|
||||
for (uint i = 0; i < accountList.length; i++){
|
||||
voteStatus[nodeIndex][accountList[i]] = false;
|
||||
}
|
||||
voteCount[nodeIndex] = 0;
|
||||
// emit event
|
||||
emit NodePendingBlacklisting(_enodeId);
|
||||
}
|
||||
else {
|
||||
nodeList[keccak256(abi.encodePacked(_enodeId))].status = NodeStatus.PendingBlacklisting;
|
||||
}
|
||||
emit NodePendingBlacklisting (_enodeId);
|
||||
}
|
||||
|
||||
//Approve node blacklisting
|
||||
function BlacklistNode (string _enodeId) public {
|
||||
function BlacklistNode(string _enodeId)
|
||||
external
|
||||
canVote
|
||||
{
|
||||
require(getNodeStatus(_enodeId) == NodeStatus.PendingBlacklisting, "Node need to be in PendingBlacklisting status");
|
||||
bytes32 i;
|
||||
i = keccak256(abi.encodePacked(_enodeId));
|
||||
nodeList[i].status = NodeStatus.Blacklisted;
|
||||
emit NodeBlacklisted(nodeList[i].enodeId, nodeList[i].ipAddrPort, nodeList[i].discPort, nodeList[i].raftPort);
|
||||
uint nodeIndex = getNodeIndex(_enodeId);
|
||||
require(voteStatus[nodeIndex][msg.sender] == false, "Node can not double vote");
|
||||
// vote node
|
||||
voteStatus[nodeIndex][msg.sender] = true;
|
||||
voteCount[nodeIndex]++;
|
||||
// emit event
|
||||
emit VoteNodeBlacklisting(_enodeId, msg.sender);
|
||||
// check if node vote reach majority
|
||||
checkNodeBlacklisting(_enodeId);
|
||||
}
|
||||
|
||||
// Checks if the Node is already added. If yes then returns true
|
||||
function updateAcctAccess (address _acctId, AccountAccess access) public {
|
||||
acctAccessList[_acctId] = acctAccess(_acctId, access);
|
||||
emit AcctAccessModified(_acctId, access);
|
||||
function updateAccountAccess(address _address, AccountAccess _accountAccess)
|
||||
external
|
||||
{
|
||||
emit AccountAccessModified(_address, _accountAccess);
|
||||
}
|
||||
|
||||
// Add voting account
|
||||
function addVoter(address _address)
|
||||
external
|
||||
{
|
||||
// Check if account already exists
|
||||
for (uint i=0; i<accountList.length; i++){
|
||||
if (accountList[i] == _address){
|
||||
return;
|
||||
}
|
||||
}
|
||||
accountList.push(_address);
|
||||
emit VoterAdded(_address);
|
||||
}
|
||||
// Remove voting account
|
||||
function removeVoter(address _address)
|
||||
external
|
||||
{
|
||||
// Check if account already exists
|
||||
for (uint i=0; i<accountList.length; i++){
|
||||
if (accountList[i] == _address){
|
||||
for (uint j=i+1; j<accountList.length; j++){
|
||||
accountList[j-1] = accountList[j];
|
||||
}
|
||||
delete accountList[accountList.length];
|
||||
emit VoterRemoved(_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* private functions */
|
||||
|
||||
function getNodeIndex(string _enodeId)
|
||||
internal view
|
||||
returns (uint)
|
||||
{
|
||||
return nodeIdToIndex[keccak256(abi.encodePacked(_enodeId))] - 1;
|
||||
}
|
||||
|
||||
function checkVotingAccountExist()
|
||||
internal
|
||||
returns (bool)
|
||||
{
|
||||
if (accountList.length == 0){
|
||||
emit NoVotingAccount();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function checkNodeApproval(string _enodeId)
|
||||
internal
|
||||
{
|
||||
uint nodeIndex = getNodeIndex(_enodeId);
|
||||
if (voteCount[nodeIndex] > accountList.length / 2){
|
||||
nodeList[nodeIndex].status = NodeStatus.Approved;
|
||||
emit NodeApproved(nodeList[nodeIndex].enodeId, nodeList[nodeIndex].ipAddrPort, nodeList[nodeIndex].discPort, nodeList[nodeIndex].raftPort);
|
||||
}
|
||||
}
|
||||
|
||||
function checkNodeDeactivation(string _enodeId)
|
||||
internal
|
||||
{
|
||||
uint nodeIndex = getNodeIndex(_enodeId);
|
||||
if (voteCount[nodeIndex] > accountList.length / 2){
|
||||
nodeList[nodeIndex].status = NodeStatus.Deactivated;
|
||||
emit NodeDeactivated(nodeList[nodeIndex].enodeId, nodeList[nodeIndex].ipAddrPort, nodeList[nodeIndex].discPort, nodeList[nodeIndex].raftPort);
|
||||
}
|
||||
}
|
||||
|
||||
function checkNodeBlacklisting(string _enodeId)
|
||||
internal
|
||||
{
|
||||
uint nodeIndex = getNodeIndex(_enodeId);
|
||||
if (voteCount[nodeIndex] > accountList.length / 2){
|
||||
nodeList[nodeIndex].status = NodeStatus.Blacklisted;
|
||||
emit NodeBlacklisted(nodeList[nodeIndex].enodeId, nodeList[nodeIndex].ipAddrPort, nodeList[nodeIndex].discPort, nodeList[nodeIndex].raftPort);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -76,7 +76,7 @@ func manageNodePermissions(stack *node.Node, stateReader *ethclient.Client) {
|
|||
go monitorNewNodeAdd(stack, stateReader)
|
||||
|
||||
//monitor for nodes deletiin via smart contract
|
||||
go monitorNodeDelete(stack, stateReader)
|
||||
go monitorNodeDeactivation(stack, stateReader)
|
||||
|
||||
//monitor for nodes blacklisting via smart contract
|
||||
go monitorNodeBlacklisting(stack, stateReader)
|
||||
|
@ -114,7 +114,7 @@ func monitorNewNodeAdd(stack *node.Node, stateReader *ethclient.Client) {
|
|||
|
||||
// This functions listens on the channel for new node approval via smart contract and
|
||||
// adds the same into permissioned-nodes.json
|
||||
func monitorNodeDelete(stack *node.Node, stateReader *ethclient.Client) {
|
||||
func monitorNodeDeactivation(stack *node.Node, stateReader *ethclient.Client) {
|
||||
|
||||
permissions, err := NewPermissionsFilterer(params.QuorumPermissionsContract, stateReader)
|
||||
if err != nil {
|
||||
|
@ -297,13 +297,13 @@ func populateAcctPermissions(stack *node.Node, stateReader *ethclient.Client) er
|
|||
|
||||
opts := &bind.FilterOpts{}
|
||||
|
||||
pastEvents, err := permissions.FilterAcctAccessModified(opts)
|
||||
pastEvents, err := permissions.FilterAccountAccessModified(opts)
|
||||
|
||||
recExists := true
|
||||
for recExists {
|
||||
recExists = pastEvents.Next()
|
||||
if recExists {
|
||||
types.AddAccountAccess(pastEvents.Event.AcctId, pastEvents.Event.Access)
|
||||
types.AddAccountAccess(pastEvents.Event.Address, pastEvents.Event.Access)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -320,14 +320,14 @@ func monitorAccountPermissions(stack *node.Node, stateReader *ethclient.Client)
|
|||
if err != nil {
|
||||
log.Error ("Failed to monitor Account permissions : ", "err" , err)
|
||||
}
|
||||
ch := make(chan *PermissionsAcctAccessModified)
|
||||
ch := make(chan *PermissionsAccountAccessModified)
|
||||
|
||||
opts := &bind.WatchOpts{}
|
||||
var blockNumber uint64 = 1
|
||||
opts.Start = &blockNumber
|
||||
var newEvent *PermissionsAcctAccessModified
|
||||
var newEvent *PermissionsAccountAccessModified
|
||||
|
||||
_, err = permissions.WatchAcctAccessModified(opts, ch)
|
||||
_, err = permissions.WatchAccountAccessModified(opts, ch)
|
||||
if err != nil {
|
||||
log.Info("Failed NewNodeProposed: %v", err)
|
||||
}
|
||||
|
@ -335,7 +335,7 @@ func monitorAccountPermissions(stack *node.Node, stateReader *ethclient.Client)
|
|||
for {
|
||||
select {
|
||||
case newEvent = <-ch:
|
||||
types.AddAccountAccess(newEvent.AcctId, newEvent.Access)
|
||||
types.AddAccountAccess(newEvent.Address, newEvent.Access)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue