2018-07-12 03:00:19 -07:00
package permissions
import (
"fmt"
2018-07-16 03:34:20 -07:00
"encoding/json"
2018-07-12 03:00:19 -07:00
"io/ioutil"
"math/big"
"path/filepath"
2018-07-16 03:34:20 -07:00
"os"
2018-07-12 03:00:19 -07:00
"strings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/cmd/utils"
2018-07-13 18:22:43 -07:00
"github.com/ethereum/go-ethereum/params"
2018-07-12 03:00:19 -07:00
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"gopkg.in/urfave/cli.v1"
)
2018-07-16 03:34:20 -07:00
const (
PERMISSIONED_CONFIG = "permissioned-nodes.json"
)
2018-07-13 18:22:43 -07:00
//This function first adds the node list from permissioned-nodes.json to
//the permissiones contract deployed as a precompile via genesis.json
func QuorumPermissioning ( ctx * cli . Context , stack * node . Node ) {
//Create a new ethclient to for interfacing with the contract
e , stateReader := createEthClient ( stack )
//call populate nodes to populate the nodes into contract
populateNodesToContract ( ctx , stack , e , stateReader )
//monitor for new nodes addition via smart contract
2018-07-16 19:13:33 -07:00
go monitorNewNodeAdd ( stack , stateReader )
2018-07-26 00:46:53 -07:00
//monitor for nodes deletiin via smart contract
go monitorNodeDelete ( stack , stateReader )
2018-07-13 18:22:43 -07:00
}
2018-07-12 03:00:19 -07:00
//populates the nodes list from permissioned-nodes.json into the permissions
//smart contract
2018-07-13 18:22:43 -07:00
func populateNodesToContract ( ctx * cli . Context , stack * node . Node , e * eth . Ethereum , stateReader * ethclient . Client ) {
//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 )
}
2018-07-16 19:13:33 -07:00
2018-07-13 18:22:43 -07:00
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 ) ,
} ,
}
2018-07-16 19:13:33 -07:00
// datadir := ctx.GlobalString(utils.DataDirFlag.Name)
datadir := stack . DataDir ( )
2018-07-13 18:22:43 -07:00
nodes := p2p . ParsePermissionedNodes ( datadir )
for _ , node := range nodes {
enodeID := fmt . Sprintf ( "%x" , node . ID [ : ] )
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 , true , true )
if err != nil {
log . Warn ( "Failed to propose node" , "err" , err )
}
log . Debug ( "Transaction pending" , "tx hash" , tx . Hash ( ) )
}
}
//This functions listens on the channel for new node approval via smart contract and
// adds the same into permissioned-nodes.json
2018-07-16 19:13:33 -07:00
func monitorNewNodeAdd ( stack * node . Node , stateReader * ethclient . Client ) {
2018-07-13 18:22:43 -07:00
permissions , err := NewPermissionsFilterer ( params . QuorumPermissionsContract , stateReader )
if err != nil {
utils . Fatalf ( "Failed to instantiate a Permissions Filterer: %v" , err )
}
2018-07-16 19:13:33 -07:00
datadir := stack . DataDir ( )
2018-07-13 18:22:43 -07:00
ch := make ( chan * PermissionsNewNodeProposed )
opts := & bind . WatchOpts { }
var blockNumber uint64 = 1
opts . Start = & blockNumber
2018-07-26 00:46:53 -07:00
var operation string = "ADD"
2018-07-13 18:22:43 -07:00
for {
_ , err = permissions . WatchNewNodeProposed ( opts , ch )
if err != nil {
log . Info ( "Failed NewNodeProposed: %v" , err )
}
var newEvent * PermissionsNewNodeProposed = <- ch
2018-07-26 00:46:53 -07:00
updatePermissionedNodes ( newEvent . EnodeId , datadir , operation )
2018-07-13 18:22:43 -07:00
}
}
2018-07-26 00:46:53 -07:00
//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 ) {
permissions , err := NewPermissionsFilterer ( params . QuorumPermissionsContract , stateReader )
if err != nil {
utils . Fatalf ( "Failed to instantiate a Permissions Filterer: %v" , err )
}
datadir := stack . DataDir ( )
ch := make ( chan * PermissionsNodeDeactivated )
opts := & bind . WatchOpts { }
var blockNumber uint64 = 1
opts . Start = & blockNumber
var operation string = "DEL"
for {
_ , err = permissions . WatchNodeDeactivated ( opts , ch )
if err != nil {
log . Info ( "Failed NodeDeactivated: %v" , err )
}
var newEvent * PermissionsNodeDeactivated = <- ch
updatePermissionedNodes ( newEvent . EnodeId , datadir , operation )
}
}
2018-07-13 18:22:43 -07:00
//Create an RPC client for the contract interface
func createEthClient ( stack * node . Node ) ( * eth . Ethereum , * ethclient . Client ) {
var e * eth . Ethereum
if err := stack . Service ( & e ) ; err != nil {
utils . Fatalf ( "Ethereum service not running: %v" , err )
}
rpcClient , err := stack . Attach ( )
if err != nil {
utils . Fatalf ( "Failed to attach to self: %v" , err )
}
return e , ethclient . NewClient ( rpcClient )
}
//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 )
}
2018-07-16 19:13:33 -07:00
// HACK: here we always use the first key as transactor
2018-07-13 18:22:43 -07:00
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 := len ( keyBlob )
return string ( keyBlob [ : n ] )
2018-07-12 03:00:19 -07:00
}
2018-07-16 19:13:33 -07:00
2018-07-16 03:34:20 -07:00
//this function populates the new node information into the permissioned-nodes.json file
2018-07-26 00:46:53 -07:00
func updatePermissionedNodes ( enodeId string , dataDir string , operation string ) {
log . Debug ( "updatePermissionedNodes" , "DataDir" , dataDir , "file" , PERMISSIONED_CONFIG )
2018-07-16 03:34:20 -07:00
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 {
2018-07-26 00:46:53 -07:00
log . Error ( "updatePermissionedNodes: Failed to access permissioned-nodes.json" , "err" , err )
2018-07-16 03:34:20 -07:00
return
}
nodelist := [ ] string { }
if err := json . Unmarshal ( blob , & nodelist ) ; err != nil {
2018-07-26 00:46:53 -07:00
log . Error ( "parsePermissionedNodes: Failed to load nodes list" , "err" , err )
2018-07-16 03:34:20 -07:00
return
}
2018-07-16 19:13:33 -07:00
// HACK: currently the ip, discpot and raft port are hard coded. Need to enhance the
//contract to pass these variables as part of the event and change this
newEnodeId := "enode://" + enodeId + "@127.0.0.1:21005?discport=0&raftport=50406"
2018-07-26 00:46:53 -07:00
if ( operation == "ADD" ) {
nodelist = append ( nodelist , newEnodeId )
} else {
index := 0
for i , enodeId := range nodelist {
if ( enodeId == newEnodeId ) {
index = i
break
}
}
nodelist = append ( nodelist [ : index ] , nodelist [ index + 1 : ] ... )
}
2018-07-16 03:34:20 -07:00
blob , _ = json . Marshal ( nodelist )
if err := ioutil . WriteFile ( path , blob , 0644 ) ; err != nil {
2018-07-26 00:46:53 -07:00
log . Error ( "updatePermissionedNodes: Error writing new node info to file" , "err" , err )
2018-07-16 03:34:20 -07:00
}
}