quorum/controls/permissions/permissions.go

451 lines
14 KiB
Go
Raw Normal View History

2018-07-12 03:00:19 -07:00
package permissions
import (
"fmt"
"encoding/json"
2018-07-12 03:00:19 -07:00
"io/ioutil"
"path/filepath"
"math/big"
"os"
"sync"
"strings"
2018-07-12 03:00:19 -07:00
2018-08-05 22:26:29 -07:00
"github.com/ethereum/go-ethereum/core/types"
2018-07-12 03:00:19 -07:00
"github.com/ethereum/go-ethereum/accounts/abi/bind"
2018-07-13 18:22:43 -07:00
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/eth"
2018-07-12 03:00:19 -07:00
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
2018-09-19 18:51:36 -07:00
"github.com/ethereum/go-ethereum/p2p/discover"
2018-09-28 01:15:59 -07:00
"github.com/ethereum/go-ethereum/controls"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/raft"
2018-07-12 03:00:19 -07:00
"gopkg.in/urfave/cli.v1"
)
const (
PERMISSIONED_CONFIG = "permissioned-nodes.json"
2018-08-31 04:35:35 -07:00
BLACKLIST_CONFIG = "disallowed-nodes.json"
RAFT = "raft"
ISTANBUL = "istanbul"
)
2018-07-13 18:22:43 -07:00
type NodeOperation uint8
const (
NodeAdd NodeOperation = iota
NodeDelete
)
// This function first adds the node list from permissioned-nodes.json to
// the permissiones contract deployed as a precompile via genesis.json
2018-08-05 22:26:29 -07:00
func QuorumPermissioning(ctx *cli.Context, stack *node.Node ) error {
// Create a new ethclient to for interfacing with the contract
stateReader, e, err := controls.CreateEthClient(stack)
2018-08-05 22:26:29 -07:00
if err != nil {
log.Error ("Unable to create ethereum client for permissions check : ", "err" , err)
2018-08-05 22:26:29 -07:00
return err
}
2018-07-13 18:22:43 -07:00
// check if permissioning contract is there at address. If not return from here
if _ , err = NewPermissionsFilterer(params.QuorumPermissionsContract, stateReader); err != nil {
log.Error ("Permissions not enabled for the network : ", "err" , err)
return nil
}
consensusEngine := ISTANBUL
if ctx.GlobalBool(utils.RaftModeFlag.Name) {
consensusEngine = RAFT
}
// Monitors node addition and decativation from network
manageNodePermissions(ctx, stack, e, stateReader, consensusEngine);
// Monitors account level persmissions update from smart contarct
manageAccountPermissions(stack, stateReader);
return nil
}
2018-07-13 18:22:43 -07:00
// Manages node addition and decavtivation from network
func manageNodePermissions(ctx *cli.Context, stack *node.Node, e *eth.Ethereum, stateReader *ethclient.Client, consensusEngine string) {
// populate the initial list of nodes into the smart contract
// from permissioned-nodes.json
populateNodesToContract(ctx, stack, e, stateReader)
2018-07-13 18:22:43 -07:00
//monitor for new nodes addition via smart contract
2018-09-19 22:55:57 -07:00
go monitorNewNodeAdd(stack, stateReader, consensusEngine)
//monitor for nodes deletiin via smart contract
2018-09-19 22:55:57 -07:00
go monitorNodeDeactivation(stack, stateReader, consensusEngine)
2018-08-31 04:35:35 -07:00
//monitor for nodes blacklisting via smart contract
go monitorNodeBlacklisting(stack, stateReader, consensusEngine)
2018-07-13 18:22:43 -07:00
}
// This functions listens on the channel for new node approval via smart contract and
2018-07-13 18:22:43 -07:00
// adds the same into permissioned-nodes.json
2018-09-19 22:55:57 -07:00
func monitorNewNodeAdd(stack *node.Node, stateReader *ethclient.Client, consensusEngine string) {
2018-07-13 18:22:43 -07:00
permissions, err := NewPermissionsFilterer(params.QuorumPermissionsContract, stateReader)
if err != nil {
log.Error ("failed to monitor new node add : ", "err" , err)
2018-07-13 18:22:43 -07:00
}
2018-07-16 19:13:33 -07:00
datadir := stack.DataDir()
2018-07-13 18:22:43 -07:00
ch := make(chan *PermissionsNodeApproved, 1)
2018-07-13 18:22:43 -07:00
opts := &bind.WatchOpts{}
var blockNumber uint64 = 1
opts.Start = &blockNumber
var nodeAddEvent *PermissionsNodeApproved
2018-07-13 18:22:43 -07:00
2018-08-16 20:20:33 -07:00
_, err = permissions.WatchNodeApproved(opts, ch)
if err != nil {
log.Info("Failed WatchNodeApproved: %v", err)
}
2018-08-16 20:20:33 -07:00
for {
select {
case nodeAddEvent = <-ch:
2018-09-19 22:55:57 -07:00
updatePermissionedNodes(nodeAddEvent.EnodeId, nodeAddEvent.IpAddrPort, nodeAddEvent.DiscPort, nodeAddEvent.RaftPort, datadir, consensusEngine, NodeAdd)
}
2018-07-13 18:22:43 -07:00
}
}
// This functions listens on the channel for new node approval via smart contract and
// adds the same into permissioned-nodes.json
2018-09-19 22:55:57 -07:00
func monitorNodeDeactivation(stack *node.Node, stateReader *ethclient.Client, consensusEngine string) {
permissions, err := NewPermissionsFilterer(params.QuorumPermissionsContract, stateReader)
if err != nil {
2018-08-05 22:26:29 -07:00
log.Error ("Failed to monitor node delete: ", "err" , err)
}
datadir := stack.DataDir()
ch := make(chan *PermissionsNodeDeactivated)
opts := &bind.WatchOpts{}
var blockNumber uint64 = 1
opts.Start = &blockNumber
2018-08-16 20:20:33 -07:00
var newNodeDeleteEvent *PermissionsNodeDeactivated
2018-08-16 20:20:33 -07:00
_, err = permissions.WatchNodeDeactivated(opts, ch)
if err != nil {
log.Info("Failed NodeDeactivated: %v", err)
}
2018-08-16 20:20:33 -07:00
for {
select {
case newNodeDeleteEvent = <-ch:
2018-09-19 22:55:57 -07:00
updatePermissionedNodes(newNodeDeleteEvent.EnodeId, newNodeDeleteEvent.IpAddrPort, newNodeDeleteEvent.DiscPort, newNodeDeleteEvent.RaftPort, datadir, consensusEngine, NodeDelete)
2018-08-16 20:20:33 -07:00
}
2018-08-16 20:20:33 -07:00
}
}
2018-08-05 22:26:29 -07:00
2018-08-31 04:35:35 -07:00
// This function listnes on the channel for any node blacklisting event via smart contract
// and adds the same disallowed-nodes.json
func monitorNodeBlacklisting(stack *node.Node, stateReader *ethclient.Client, consensusEngine string) {
2018-08-31 04:35:35 -07:00
permissions, err := NewPermissionsFilterer(params.QuorumPermissionsContract, stateReader)
if err != nil {
log.Error ("failed to monitor new node add : ", "err" , err)
}
ch := make(chan *PermissionsNodeBlacklisted, 1)
opts := &bind.WatchOpts{}
var blockNumber uint64 = 1
opts.Start = &blockNumber
var nodeBlacklistEvent *PermissionsNodeBlacklisted
_, err = permissions.WatchNodeBlacklisted(opts, ch)
if err != nil {
log.Info("Failed WatchNodeBlacklisted: %v", err)
}
for {
select {
case nodeBlacklistEvent = <-ch:
updateDisallowedNodes(nodeBlacklistEvent, stack, consensusEngine)
2018-08-31 04:35:35 -07:00
}
}
}
//this function populates the new node information into the permissioned-nodes.json file
2018-09-19 22:55:57 -07:00
func updatePermissionedNodes(enodeId , ipAddrPort, discPort, raftPort, dataDir, consensusEngine string, operation NodeOperation){
log.Debug("updatePermissionedNodes", "DataDir", dataDir, "file", PERMISSIONED_CONFIG)
path := filepath.Join(dataDir, PERMISSIONED_CONFIG)
if _, err := os.Stat(path); err != nil {
log.Error("Read Error for permissioned-nodes.json file. This is because 'permissioned' flag is specified but no permissioned-nodes.json file is present.", "err", err)
return
}
// Load the nodes from the config file
blob, err := ioutil.ReadFile(path)
if err != nil {
log.Error("updatePermissionedNodes: Failed to access permissioned-nodes.json", "err", err)
return
}
nodelist := []string{}
if err := json.Unmarshal(blob, &nodelist); err != nil {
2018-08-31 04:35:35 -07:00
log.Error("updatePermissionedNodes: Failed to load nodes list", "err", err)
return
}
2018-09-19 22:55:57 -07:00
newEnodeId := formatEnodeId(enodeId, ipAddrPort, discPort, raftPort, consensusEngine)
if (operation == NodeAdd){
2018-08-16 20:20:33 -07:00
nodelist = append(nodelist, newEnodeId)
} else {
2018-08-16 20:20:33 -07:00
index := 0
for i, enodeId := range nodelist {
if (enodeId == newEnodeId){
index = i
break
}
}
nodelist = append(nodelist[:index], nodelist[index+1:]...)
}
mu := sync.RWMutex{}
blob, _ = json.Marshal(nodelist)
mu.Lock()
if err:= ioutil.WriteFile(path, blob, 0644); err!= nil{
log.Error("updatePermissionedNodes: Error writing new node info to file", "err", err)
}
mu.Unlock()
}
2018-08-31 04:35:35 -07:00
//this function populates the new node information into the permissioned-nodes.json file
func updateDisallowedNodes(nodeBlacklistEvent *PermissionsNodeBlacklisted, stack *node.Node, consensusEngine string){
dataDir := stack.DataDir()
2018-08-31 04:35:35 -07:00
log.Debug("updateDisallowedNodes", "DataDir", dataDir, "file", BLACKLIST_CONFIG)
2018-09-02 23:59:45 -07:00
fileExisted := true
2018-08-31 04:35:35 -07:00
path := filepath.Join(dataDir, BLACKLIST_CONFIG)
2018-09-02 23:59:45 -07:00
// Check if the file is existing. If the file is not existing create the file
2018-08-31 04:35:35 -07:00
if _, err := os.Stat(path); err != nil {
log.Error("Read Error for disallowed-nodes.json file." , "err", err)
if _, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644); err != nil {
log.Error("Failed to create disallowed-nodes.json file ", "err", err)
return
}
2018-09-02 23:59:45 -07:00
fileExisted = false
2018-08-31 04:35:35 -07:00
}
nodelist := []string{}
2018-09-02 23:59:45 -07:00
// Load the nodes from the config file
if fileExisted == true {
blob, err := ioutil.ReadFile(path)
if err != nil {
log.Error("updateDisallowedNodes Failed to access disallowed-nodes.json", "err", err)
return
}
if (blob != nil) {
if err := json.Unmarshal(blob, &nodelist); err != nil {
log.Error("updateDisallowedNodes: Failed to load nodes list", "err", err)
return
}
2018-08-31 04:35:35 -07:00
}
}
2018-09-19 22:55:57 -07:00
newEnodeId := formatEnodeId (nodeBlacklistEvent.EnodeId, nodeBlacklistEvent.IpAddrPort, nodeBlacklistEvent.DiscPort, nodeBlacklistEvent.RaftPort, consensusEngine )
2018-08-31 04:35:35 -07:00
nodelist = append(nodelist, newEnodeId)
mu := sync.RWMutex{}
2018-09-02 23:59:45 -07:00
blob, _ := json.Marshal(nodelist)
2018-08-31 04:35:35 -07:00
mu.Lock()
if err:= ioutil.WriteFile(path, blob, 0644); err!= nil{
log.Error("updateDisallowedNodes: Error writing new node info to file", "err", err)
}
mu.Unlock()
// Disconnect the peer if it is already connected
2018-09-19 18:51:36 -07:00
disconnectNode(stack, newEnodeId, consensusEngine)
2018-08-31 04:35:35 -07:00
}
// Manages account level permissions update
func manageAccountPermissions(stack *node.Node, stateReader *ethclient.Client) error {
//call populate nodes to populate the nodes into contract
if err := populateAcctPermissions (stack, stateReader); err != nil {
2018-09-28 01:15:59 -07:00
return err
}
//monitor for nodes deletiin via smart contract
go monitorAccountPermissions(stack, stateReader)
return nil
}
// populates the nodes list from permissioned-nodes.json into the permissions
// smart contract
func populateAcctPermissions(stack *node.Node, stateReader *ethclient.Client) error{
permissions, err := NewPermissionsFilterer(params.QuorumPermissionsContract, stateReader)
if err != nil {
log.Error ("Failed to monitor node delete: ", "err" , err)
return err
}
opts := &bind.FilterOpts{}
2018-09-05 19:40:21 -07:00
pastEvents, err := permissions.FilterAccountAccessModified(opts)
recExists := true
for recExists {
recExists = pastEvents.Next()
if recExists {
2018-09-05 19:40:21 -07:00
types.AddAccountAccess(pastEvents.Event.Address, pastEvents.Event.Access)
}
}
return nil
}
// Monitors permissions changes at acount level and uodate the global permissions
// map with the same
func monitorAccountPermissions(stack *node.Node, stateReader *ethclient.Client) {
permissions, err := NewPermissionsFilterer(params.QuorumPermissionsContract, stateReader)
if err != nil {
log.Error ("Failed to monitor Account permissions : ", "err" , err)
}
2018-09-05 19:40:21 -07:00
ch := make(chan *PermissionsAccountAccessModified)
opts := &bind.WatchOpts{}
var blockNumber uint64 = 1
opts.Start = &blockNumber
2018-09-05 19:40:21 -07:00
var newEvent *PermissionsAccountAccessModified
2018-09-05 19:40:21 -07:00
_, err = permissions.WatchAccountAccessModified(opts, ch)
2018-08-16 20:20:33 -07:00
if err != nil {
log.Info("Failed NewNodeProposed: %v", err)
}
for {
2018-08-16 20:20:33 -07:00
select {
case newEvent = <-ch:
2018-09-05 19:40:21 -07:00
types.AddAccountAccess(newEvent.Address, newEvent.Access)
}
}
}
2018-09-19 18:51:36 -07:00
// Disconnect the node from the network
func disconnectNode (stack *node.Node, enodeId, consensusEngine string){
if consensusEngine == RAFT {
var raftService *raft.RaftService
if err := stack.Service(&raftService); err == nil {
raftApi := raft.NewPublicRaftAPI(raftService)
//get the raftId for the given enodeId
raftId, err := raftApi.GetRaftId(enodeId)
if err == nil {
raftApi.RemovePeer(raftId)
}
}
} else {
// Istanbul - disconnect the peer
server := stack.Server()
if server != nil {
node, err := discover.ParseNode(enodeId)
if err == nil {
server.RemovePeer(node)
}
}
}
}
2018-09-19 22:55:57 -07:00
// helper function to format EnodeId
2018-10-23 08:00:57 -07:00
// This will format the EnodeId and return
2018-09-19 22:55:57 -07:00
func formatEnodeId( enodeId , ipAddrPort, discPort, raftPort, consensusEngine string) string {
newEnodeId := "enode://" + enodeId + "@" + ipAddrPort + "?discPort=" + discPort
if consensusEngine == RAFT {
2018-10-23 07:58:39 -07:00
newEnodeId += "&raftport=" + raftPort
2018-09-19 22:55:57 -07:00
}
return newEnodeId
}
//populates the nodes list from permissioned-nodes.json into the permissions
//smart contract
func populateNodesToContract(ctx *cli.Context, stack *node.Node, e *eth.Ethereum, stateReader *ethclient.Client){
log.Info("SMK - inside populateNodesToContract @363")
//Read the key file from key store. SHOULD WE MAKE IT CONFIG value
key := getKeyFromKeyStore(ctx)
permissionsContract, err := NewPermissions(params.QuorumPermissionsContract, stateReader)
if err != nil {
utils.Fatalf("Failed to instantiate a Permissions contract: %v", err)
}
auth, err := bind.NewTransactor(strings.NewReader(key), "")
if err != nil {
utils.Fatalf("Failed to create authorized transactor: %v", err)
}
permissionsSession := &PermissionsSession{
Contract: permissionsContract,
CallOpts: bind.CallOpts{
Pending: true,
},
TransactOpts: bind.TransactOpts{
From: auth.From,
Signer: auth.Signer,
GasLimit: 3558096384,
GasPrice: big.NewInt(0),
},
}
datadir := ctx.GlobalString(utils.DataDirFlag.Name)
nodes := p2p.ParsePermissionedNodes(datadir)
for _, node := range nodes {
enodeID := fmt.Sprintf("%x", node.ID[:])
ipAddr := fmt.Sprintf("%v", node.IP)
port := fmt.Sprintf("%v", node.TCP)
discPort := fmt.Sprintf("%v", node.UDP)
raftPort := fmt.Sprintf("%v", node.RaftPort)
ipAddrPort := ipAddr + ":" + port
log.Info("SMK-values are : ", "enodeId", enodeID, "ipAddrPort", ipAddrPort, "discPort", discPort, "raftPort", raftPort)
log.Trace("Adding node to permissions contract", "enodeID", enodeID)
nonce := e.TxPool().Nonce(permissionsSession.TransactOpts.From)
permissionsSession.TransactOpts.Nonce = new(big.Int).SetUint64(nonce)
tx, err := permissionsSession.ProposeNode(enodeID, ipAddrPort, discPort, raftPort)
if err != nil {
log.Warn("Failed to propose node", "err", err)
}
log.Debug("Transaction pending", "tx hash", tx.Hash())
}
// update the network boot status to true
nonce := e.TxPool().Nonce(permissionsSession.TransactOpts.From)
permissionsSession.TransactOpts.Nonce = new(big.Int).SetUint64(nonce)
tx, err := permissionsSession.UpdateNetworkBootStatus()
if err != nil {
log.Warn("Failed to udpate network boot status ", "err", err)
}
log.Debug("Transaction pending", "tx hash", tx.Hash())
}
//This functions reads the first file in key store directory, reads the key
//value and returns the same
func getKeyFromKeyStore(ctx *cli.Context) string {
datadir := ctx.GlobalString(utils.DataDirFlag.Name)
files, err := ioutil.ReadDir(filepath.Join(datadir, "keystore"))
if err != nil {
utils.Fatalf("Failed to read keystore directory: %v", err)
}
// HACK: here we always use the first key as transactor
var keyPath string
for _, f := range files {
keyPath = filepath.Join(datadir, "keystore", f.Name())
break
}
keyBlob, err := ioutil.ReadFile(keyPath)
if err != nil {
utils.Fatalf("Failed to read key file: %v", err)
}
// n := bytes.IndexByte(keyBlob, 0)
n := len(keyBlob)
return string(keyBlob[:n])
}