Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

raft.cluster api returns error if no leader is there in the network in version 2.4.0 #934

Merged
merged 11 commits into from
Feb 4, 2020
192 changes: 192 additions & 0 deletions docs/Consensus/raft/raft-rpc-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Raft RPC API
# APIs
### raft_cluster
Returns the details of all nodes part of the raft cluster
#### Parameters
None
#### Returns
* `hostName`: DNS name or the host IP address
* `nodeActive`: true if the node is active in raft cluster else false
* `nodeId`: enode id of the node
* `p2pPort`: p2p port
* `raftId`: raft id of the node
* `raftPort`: raft port
* `role`: role of the node in raft quorum. Can be minter/ verifier/ learner. In case there is no leader at network level it will be returned as `""`
#### Examples
```jshelllanguage tab="JSON RPC"
// Request
curl -X POST http://127.0.0.1:22001 --data '{"jsonrpc":"2.0","method":"raft_cluster", "id":10}' --header "Content-Type: application/json"

// Response
{"jsonrpc":"2.0","id":10,"result":[{"raftId":1,"nodeId":"ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef","p2pPort":21000,"raftPort":50401,"hostname":"127.0.0.1","role":"minter","nodeActive":true},{"raftId":3,"nodeId":"579f786d4e2830bbcc02815a27e8a9bacccc9605df4dc6f20bcc1a6eb391e7225fff7cb83e5b4ecd1f3a94d8b733803f2f66b7e871961e7b029e22c155c3a778","p2pPort":21002,"raftPort":50403,"hostname":"127.0.0.1","role":"verifier","nodeActive":true},{"raftId":2,"nodeId":"0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416","p2pPort":21001,"raftPort":50402,"hostname":"127.0.0.1","role":"verifier","nodeActive":true}]}
```

```javascript tab="geth console"
> raft.cluster
[{
hostname: "127.0.0.1",
nodeActive: true,
nodeId: "0ba6b9f606a43a95edc6247cdb1c1e105145817be7bcafd6b2c0ba15d58145f0dc1a194f70ba73cd6f4cdd6864edc7687f311254c7555cc32e4d45aeb1b80416",
p2pPort: 21001,
raftId: 2,
raftPort: 50402,
role: "verifier"
}, {
hostname: "127.0.0.1",
nodeActive: true,
nodeId: "579f786d4e2830bbcc02815a27e8a9bacccc9605df4dc6f20bcc1a6eb391e7225fff7cb83e5b4ecd1f3a94d8b733803f2f66b7e871961e7b029e22c155c3a778",
p2pPort: 21002,
raftId: 3,
raftPort: 50403,
role: "verifier"
}, {
hostname: "127.0.0.1",
nodeActive: true,
nodeId: "ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef",
p2pPort: 21000,
raftId: 1,
raftPort: 50401,
role: "minter"
}]
```
### raft_role
Returns the role of the current node in raft cluster
#### Parameters
None
#### Returns
* `result`: role of the node in raft cluster. Can be minter/ verifier/ learner. In case there is no leader at network level it will be returned as `""`
#### Examples
```jshelllanguage tab="JSON RPC"
// Request
curl -X POST http://127.0.0.1:22001 --data '{"jsonrpc":"2.0","method":"raft_role", "id":10}' --header "Content-Type: application/json"

// Response
{"jsonrpc":"2.0","id":10,"result":"verifier"}
```

```javascript tab="geth console"
> raft.role
"minter"
```
### raft_leader
Returns enode id of the leader node
#### Parameters
None
#### Returns
* `result`: enode id of the leader
#### Examples
```jshelllanguage tab="JSON RPC"
// Request
curl -X POST http://127.0.0.1:22001 --data '{"jsonrpc":"2.0","method":"raft_leader", "id":10}' --header "Content-Type: application/json"

// Response
{"jsonrpc":"2.0","id":10,"result":"ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef"}
```


```javascript tab="geth console"
> raft.leader
"ac6b1096ca56b9f6d004b779ae3728bf83f8e22453404cc3cef16a3d9b96608bc67c4b30db88e0a5a6c6390213f7acbe1153ff6d23ce57380104288ae19373ef"
```

If there is no leader at the network level, the call to the api will result in the following error:
```javascript
> raft.leader
Error: no leader is currently elected
at web3.js:3143:20
at web3.js:6347:15
at get (web3.js:6247:38)
at <unknown>
```

### raft_addPeer
API for adding a new peer to the network.
#### Parameters
* `enodeId`: enode id of the node to be added to the network
#### Returns
* `result`: raft id for the node being added
#### Examples
```jshelllanguage tab="JSON RPC"
// Request
curl -X POST http://127.0.0.1:22001 --data '{"jsonrpc":"2.0","method":"raft_addPeer","params": ["enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405"], "id":10}' --header "Content-Type: application/json"

// Response
{"jsonrpc":"2.0","id":10,"result":5}
```

```javascript tab="geth console"
> raft.addPeer("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405")
5
```

The new node can join the network with `geth` option of `--raftjoinexisting <<raftId>>`

If the node being added is already part of the network the of the network, the following error is thrown:
```javascript
> raft.addPeer("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405")
Error: node with this enode has already been added to the cluster: f06c06f1e958cb2edf90d8bfb912de287f9b047b4228436e94b5b78e3ee16171
at web3.js:3143:20
at web3.js:6347:15
at web3.js:5081:36
at <anonymous>:1:1
```
### raft_removePeer
API to remove a node from raft cluster
#### Parameters
* `raftId` : raft id of the node to be removed from the cluster
#### Returns
* `result`: null
#### Examples
```jshelllanguage tab="JSON RPC"
// Request
curl -X POST http://127.0.0.1:22001 --data '{"jsonrpc":"2.0","method":"raft_removePeer","params": [4], "id":10}' --header "Content-Type: application/json"

// Response
{"jsonrpc":"2.0","id":10,"result":null}
```

```javascript tab="geth console"
> raft.removePeer(4)
null
```

### raft_addLearner
API to add a new node to the network as a learner node. The learner node syncs with network and can transact but will not be part of raft quorum and hence will not provide block confirmation to minter node.
#### Parameters
* `enodeId`
#### Returns
* `result`: raft id for the node being added
#### Examples
```jshelllanguage tab="JSON RPC"
// Request
curl -X POST http://127.0.0.1:22001 --data '{"jsonrpc":"2.0","method":"raft_addLearner","params": ["enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405"], "id":10}' --header "Content-Type: application/json"

// Response
{"jsonrpc":"2.0","id":10,"result":5}

```

```javascript tab="geth console"
> raft.addLearner("enode://3701f007bfa4cb26512d7df18e6bbd202e8484a6e11d387af6e482b525fa25542d46ff9c99db87bd419b980c24a086117a397f6d8f88e74351b41693880ea0cb@127.0.0.1:21004?discport=0&raftport=50405")
5
```
### raft_promoteToPeer
API for promoting a learner node to peer and thus be part of the raft quorum.
#### Parameters
* `raftId`: raft id of the node to be promoted
#### Returns
* `result`: true or false
#### Examples
```jshelllanguage tab="JSON RPC"
// Request
curl -X POST http://127.0.0.1:22001 --data '{"jsonrpc":"2.0","method":"raft_promoteToPeer","params": [4], "id":10}' --header "Content-Type: application/json"

// Response
{// Response
{"jsonrpc":"2.0","id":10,"result":true}
```

```javascript tab="geth console"
> raft.promoteToPeer(4)
true
```
4 changes: 2 additions & 2 deletions docs/Consensus/raft.md → docs/Consensus/raft/raft.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Raft-based consensus for Ethereum/Quorum
# Raft Consensus Overview

## Introduction

Expand Down Expand Up @@ -190,4 +190,4 @@ Note that like the enode IDs listed in the static peers JSON file, this enode ID

## FAQ

Answers to frequently asked questions can be found on the main [Quorum FAQ page](../FAQ.md).
Answers to frequently asked questions can be found on the main [Quorum FAQ page](../../FAQ.md).
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ nav:
- Quorum API: Getting Started/api.md
- Consensus:
- Consensus: Consensus/Consensus.md
- Raft: Consensus/raft.md
- Raft BFT:
- Consensus/raft/raft.md
- Consensus/raft/raft-rpc-api.md
- Istanbul BFT:
- Consensus/ibft/istanbul-rpc-api.md
- Consensus/ibft/ibft-parameters.md
Expand Down
74 changes: 65 additions & 9 deletions raft/api.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package raft

import (
"errors"
"github.com/coreos/etcd/pkg/types"
)

type RaftNodeInfo struct {
ClusterSize int `json:"clusterSize"`
Role string `json:"role"`
Expand All @@ -19,22 +24,49 @@ func NewPublicRaftAPI(raftService *RaftService) *PublicRaftAPI {
}

func (s *PublicRaftAPI) Role() string {
if err := s.checkIfNodeInCluster(); err != nil {
return ""
}
_, err := s.raftService.raftProtocolManager.LeaderAddress()
if err != nil {
return ""
}
return s.raftService.raftProtocolManager.NodeInfo().Role
}

// helper function to check if self node is part of cluster
func (s *PublicRaftAPI) checkIfNodeInCluster() error {
if s.raftService.raftProtocolManager.IsIDRemoved(uint64(s.raftService.raftProtocolManager.raftId)) {
return errors.New("node not part of raft cluster. operations not allowed")
}
return nil
}

func (s *PublicRaftAPI) AddPeer(enodeId string) (uint16, error) {
if err := s.checkIfNodeInCluster(); err != nil {
return 0, err
}
return s.raftService.raftProtocolManager.ProposeNewPeer(enodeId, false)
}

func (s *PublicRaftAPI) AddLearner(enodeId string) (uint16, error) {
if err := s.checkIfNodeInCluster(); err != nil {
return 0, err
}
return s.raftService.raftProtocolManager.ProposeNewPeer(enodeId, true)
}

func (s *PublicRaftAPI) PromoteToPeer(raftId uint16) (bool, error) {
if err := s.checkIfNodeInCluster(); err != nil {
return false, err
}
return s.raftService.raftProtocolManager.PromoteToPeer(raftId)
}

func (s *PublicRaftAPI) RemovePeer(raftId uint16) error {
if err := s.checkIfNodeInCluster(); err != nil {
return err
}
return s.raftService.raftProtocolManager.ProposePeerRemoval(raftId)
}

Expand All @@ -48,33 +80,57 @@ func (s *PublicRaftAPI) Leader() (string, error) {
}

func (s *PublicRaftAPI) Cluster() ([]ClusterInfo, error) {
// check if the node has already been removed from cluster
// if yes return nil
if err := s.checkIfNodeInCluster(); err != nil {
return []ClusterInfo{}, nil
}

nodeInfo := s.raftService.raftProtocolManager.NodeInfo()
if nodeInfo.Role == "" {
return []ClusterInfo{}, nil
}

noLeader := false
leaderAddr, err := s.raftService.raftProtocolManager.LeaderAddress()
if err != nil {
if err == errNoLeaderElected && s.Role() == "" {
noLeader = true
if s.raftService.raftProtocolManager.NodeInfo().Role == "" {
return []ClusterInfo{}, nil
}
return []ClusterInfo{}, err
}

peerAddresses := append(nodeInfo.PeerAddresses, nodeInfo.Address)
clustInfo := make([]ClusterInfo, len(peerAddresses))
for i, a := range peerAddresses {
role := ""
if a.RaftId == leaderAddr.RaftId {
role = "minter"
} else if s.raftService.raftProtocolManager.isLearner(a.RaftId) {
role = "learner"
} else if s.raftService.raftProtocolManager.isVerifier(a.RaftId) {
role = "verifier"
if !noLeader {
if a.RaftId == leaderAddr.RaftId {
role = "minter"
} else if s.raftService.raftProtocolManager.isLearner(a.RaftId) {
role = "learner"
} else if s.raftService.raftProtocolManager.isVerifier(a.RaftId) {
role = "verifier"
}
}
clustInfo[i] = ClusterInfo{*a, role}
clustInfo[i] = ClusterInfo{*a, role, s.checkIfNodeIsActive(a.RaftId)}
}
return clustInfo, nil
}

// checkIfNodeIsActive checks if the raft node is active
// if the raft node is active ActiveSince returns non-zero time
func (s *PublicRaftAPI) checkIfNodeIsActive(raftId uint16) bool {
if raftId == s.raftService.raftProtocolManager.raftId {
return true
}
activeSince := s.raftService.raftProtocolManager.transport.ActiveSince(types.ID(raftId))
if activeSince.IsZero() {
return false
}
return true
}

func (s *PublicRaftAPI) GetRaftId(enodeId string) (uint16, error) {
return s.raftService.raftProtocolManager.FetchRaftId(enodeId)
}
3 changes: 2 additions & 1 deletion raft/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type Address struct {

type ClusterInfo struct {
Address
Role string `json:"role"`
Role string `json:"role"`
NodeActive bool `json:"nodeActive"`
}

func newAddress(raftId uint16, raftPort int, node *enode.Node, withHostname bool) *Address {
Expand Down