Skip to content

Commit

Permalink
raft.cluster api returns error if no leader is there in the network i…
Browse files Browse the repository at this point in the history
…n version 2.4.0 (#934)
  • Loading branch information
vsmk98 authored Feb 4, 2020
1 parent 27b9a01 commit 0b8ea86
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 13 deletions.
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

0 comments on commit 0b8ea86

Please sign in to comment.