From f54d91d55d1a65d89d8279d2c0a642a961b8dbf6 Mon Sep 17 00:00:00 2001
From: zramsay <zach@monax.io>
Date: Fri, 11 Nov 2016 15:27:59 -0500
Subject: [PATCH] first pass with some edits

---
 client/cmd/eris-client.go |   4 +
 client/cmd/genesis.go     |  44 +++++++
 genesis/README.md         |  11 ++
 genesis/cli.go            | 267 ++++++++++++++++++++++++++++++++++++++
 genesis/gen_test.go       | 204 +++++++++++++++++++++++++++++
 5 files changed, 530 insertions(+)
 create mode 100644 client/cmd/genesis.go
 create mode 100644 genesis/README.md
 create mode 100644 genesis/cli.go
 create mode 100644 genesis/gen_test.go

diff --git a/client/cmd/eris-client.go b/client/cmd/eris-client.go
index 0c758523..f0731419 100644
--- a/client/cmd/eris-client.go
+++ b/client/cmd/eris-client.go
@@ -62,6 +62,10 @@ func AddGlobalFlags() {
 func AddClientCommands() {
 	ErisClientCmd.AddCommand(buildTransactionCommand())
 	ErisClientCmd.AddCommand(buildStatusCommand())
+
+	buildGenesisGenCommand()
+	ErisClientCmd.AddCommand(GenesisGenCmd)
+
 }
 
 //------------------------------------------------------------------------------
diff --git a/client/cmd/genesis.go b/client/cmd/genesis.go
new file mode 100644
index 00000000..31f2e7c9
--- /dev/null
+++ b/client/cmd/genesis.go
@@ -0,0 +1,44 @@
+package commands
+
+import (
+	"github.com/eris-ltd/eris-db/genesis"
+
+	"github.com/spf13/cobra"
+)
+
+var GenesisGenCmd = &cobra.Command{
+	Use:   "genesis",
+	Short: "eris-client genesis creates a genesis.json with known inputs",
+	Long:  "eris-client genesis creates a genesis.json with known inputs",
+
+	Run: func(cmd *cobra.Command, args []string) {
+
+		genesis.GenerateKnown("thisIsChainID", "", "114234767676")
+
+	},
+}
+
+func buildGenesisGenCommand() {
+	//addTransactionPersistentFlags()
+}
+
+var (
+	DirFlag string
+	//AddrsFlag  string
+	CsvPathFlag       string
+	PubkeyFlag        string
+	RootFlag          string
+	NoValAccountsFlag bool
+)
+
+//var knownCmd = &cobra.Command{
+//	Use:   "known",
+//	Short: "mintgen known <chain_id> [flags] ",
+//	Long:  "Create a genesis.json with --pub <pub_1> <pub_2> <pub_N> or with --csv <path_to_file>, or pass a priv_validator.json on stdin. Two csv file names can be passed (comma separated) to distinguish validators and accounts.",
+//	//Run:   cliKnown,
+//}
+//
+//
+//knownCmd.Flags().StringVarP(&PubkeyFlag, "pub", "", "", "pubkeys to include when generating genesis.json. flag is req'd")
+//knownCmd.Flags().StringVarP(&CsvPathFlag, "csv", "", "", "path to .csv with the following params: (pubkey, starting balance, name, permissions, setbit")
+//
diff --git a/genesis/README.md b/genesis/README.md
new file mode 100644
index 00000000..57e2a9eb
--- /dev/null
+++ b/genesis/README.md
@@ -0,0 +1,11 @@
+# mintgen
+---------
+
+Generate genesis.json files for a tendermint blockchain.
+
+To generate a genesis.json with a single validator/account use 
+
+```
+cat /path/to/priv_validator.json | mintgen single <chain id>
+```
+
diff --git a/genesis/cli.go b/genesis/cli.go
new file mode 100644
index 00000000..b7ae41a5
--- /dev/null
+++ b/genesis/cli.go
@@ -0,0 +1,267 @@
+package genesis
+
+import (
+	"bytes"
+	"encoding/csv"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+
+	. "github.com/eris-ltd/common/go/common"
+	"github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/eris-ltd/tendermint/types"
+	//"github.com/spf13/cobra"
+	wire "github.com/tendermint/go-wire"
+
+	"github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/eris-ltd/tendermint/account"
+	ptypes "github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/eris-ltd/tendermint/permission/types"
+	stypes "github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/eris-ltd/tendermint/state/types"
+)
+
+//------------------------------------------------------------------------------------
+// core functions
+
+func GenerateKnown(chainID, csvFile, pubKeys string) ([]byte, error) {
+	var genDoc *stypes.GenesisDoc
+	var err error
+	// either we pass the name of a csv file or we read a priv_validator over stdin
+	// TODO eliminate reading priv_val
+	if csvFile != "" {
+		var csvValidators, csvAccounts string
+		csvFiles := strings.Split(csvFile, ",")
+		csvValidators = csvFiles[0]
+		if len(csvFiles) > 1 {
+			csvAccounts = csvFiles[1]
+		}
+		pubkeys, amts, names, perms, setbits, err := parseCsv(csvValidators)
+		if err != nil {
+			return nil, err
+		}
+
+		if csvAccounts == "" {
+			genDoc = newGenDoc(chainID, len(pubkeys), len(pubkeys))
+			for i, pk := range pubkeys {
+				genDocAddAccountAndValidator(genDoc, pk, amts[i], names[i], perms[i], setbits[i], i)
+			}
+		} else {
+			pubkeysA, amtsA, namesA, permsA, setbitsA, err := parseCsv(csvAccounts)
+			if err != nil {
+				return nil, err
+			}
+			genDoc = newGenDoc(chainID, len(pubkeys), len(pubkeysA))
+			for i, pk := range pubkeys {
+				genDocAddValidator(genDoc, pk, amts[i], names[i], perms[i], setbits[i], i)
+			}
+			for i, pk := range pubkeysA {
+				genDocAddAccount(genDoc, pk, amtsA[i], namesA[i], permsA[i], setbitsA[i], i)
+			}
+		}
+	} else if pubKeys != "" {
+		pubkeys := strings.Split(pubKeys, ",")
+		amt := int64(1) << 50
+		pubKeys := pubKeyStringsToPubKeys(pubkeys)
+		genDoc = newGenDoc(chainID, len(pubkeys), len(pubkeys))
+
+		for i, pk := range pubKeys {
+			genDocAddAccountAndValidator(genDoc, pk, amt, "", ptypes.DefaultPermFlags, ptypes.DefaultPermFlags, i)
+		}
+	} else {
+		privJSON := readStdinTimeout()
+		genDoc = genesisFromPrivValBytes(chainID, privJSON)
+	}
+
+	buf, buf2, n := new(bytes.Buffer), new(bytes.Buffer), new(int)
+	wire.WriteJSON(genDoc, buf, n, &err)
+	if err != nil {
+		return nil, err
+	}
+	if err := json.Indent(buf2, buf.Bytes(), "", "\t"); err != nil {
+		return nil, err
+	}
+	genesisBytes := buf2.Bytes()
+
+	return genesisBytes, nil
+}
+
+//-----------------------------------------------------------------------------
+// gendoc convenience functions
+
+func newGenDoc(chainID string, nVal, nAcc int) *stypes.GenesisDoc {
+	genDoc := stypes.GenesisDoc{
+		ChainID: chainID,
+		// GenesisTime: time.Now(),
+	}
+	genDoc.Accounts = make([]stypes.GenesisAccount, nAcc)
+	genDoc.Validators = make([]stypes.GenesisValidator, nVal)
+	return &genDoc
+}
+
+// genesis file with only one validator, using priv_validator.json
+func genesisFromPrivValBytes(chainID string, privJSON []byte) *stypes.GenesisDoc {
+	var err error
+	privVal := wire.ReadJSON(&types.PrivValidator{}, privJSON, &err).(*types.PrivValidator)
+	if err != nil {
+		Exit(fmt.Errorf("Error reading PrivValidator on stdin: %v\n", err))
+	}
+	pubKey := privVal.PubKey
+	amt := int64(1) << 50
+
+	genDoc := newGenDoc(chainID, 1, 1)
+
+	genDocAddAccountAndValidator(genDoc, pubKey, amt, "", ptypes.DefaultPermFlags, ptypes.DefaultPermFlags, 0)
+
+	return genDoc
+}
+
+func genDocAddAccount(genDoc *stypes.GenesisDoc, pubKey account.PubKeyEd25519, amt int64, name string, perm, setbit ptypes.PermFlag, index int) {
+	addr := pubKey.Address()
+	acc := stypes.GenesisAccount{
+		Address: addr,
+		Amount:  amt,
+		Name:    name,
+		Permissions: &ptypes.AccountPermissions{
+			Base: ptypes.BasePermissions{
+				Perms:  perm,
+				SetBit: setbit,
+			},
+		},
+	}
+	if index < 0 {
+		genDoc.Accounts = append(genDoc.Accounts, acc)
+	} else {
+		genDoc.Accounts[index] = acc
+	}
+}
+
+func genDocAddValidator(genDoc *stypes.GenesisDoc, pubKey account.PubKeyEd25519, amt int64, name string, perm, setbit ptypes.PermFlag, index int) {
+	addr := pubKey.Address()
+	genDoc.Validators[index] = stypes.GenesisValidator{
+		PubKey: pubKey,
+		Amount: amt,
+		Name:   name,
+		UnbondTo: []stypes.BasicAccount{
+			{
+				Address: addr,
+				Amount:  amt,
+			},
+		},
+	}
+}
+
+func genDocAddAccountAndValidator(genDoc *stypes.GenesisDoc, pubKey account.PubKeyEd25519, amt int64, name string, perm, setbit ptypes.PermFlag, index int) {
+	genDocAddAccount(genDoc, pubKey, amt, name, perm, setbit, index)
+	genDocAddValidator(genDoc, pubKey, amt, name, perm, setbit, index)
+}
+
+//-----------------------------------------------------------------------------
+// util functions
+
+// convert hex strings to ed25519 pubkeys
+func pubKeyStringsToPubKeys(pubkeys []string) []account.PubKeyEd25519 {
+	pubKeys := make([]account.PubKeyEd25519, len(pubkeys))
+	for i, k := range pubkeys {
+		pubBytes, err := hex.DecodeString(k)
+		if err != nil {
+			Exit(fmt.Errorf("Pubkey (%s) is invalid hex: %v", k, err))
+		}
+		copy(pubKeys[i][:], pubBytes)
+	}
+	return pubKeys
+}
+
+// empty is over written
+func ifExistsElse(list []string, index int, defaultValue string) string {
+	if len(list) > index {
+		if list[index] != "" {
+			return list[index]
+		}
+	}
+	return defaultValue
+}
+
+//takes a csv in the format defined [here]
+func parseCsv(filePath string) (pubKeys []account.PubKeyEd25519, amts []int64, names []string, perms, setbits []ptypes.PermFlag, err error) {
+
+	csvFile, err := os.Open(filePath)
+	if err != nil {
+		Exit(fmt.Errorf("Couldn't open file: %s: %v", filePath, err))
+	}
+	defer csvFile.Close()
+
+	r := csv.NewReader(csvFile)
+	//r.FieldsPerRecord = # of records expected
+	params, err := r.ReadAll()
+	if err != nil {
+		Exit(fmt.Errorf("Couldn't read file: %v", err))
+
+	}
+
+	pubkeys := make([]string, len(params))
+	amtS := make([]string, len(params))
+	names = make([]string, len(params))
+	permsS := make([]string, len(params))
+	setbitS := make([]string, len(params))
+	for i, each := range params {
+		pubkeys[i] = each[0]
+		amtS[i] = ifExistsElse(each, 1, "1000")
+		names[i] = ifExistsElse(each, 2, "")
+		permsS[i] = ifExistsElse(each, 3, fmt.Sprintf("%d", ptypes.DefaultPermFlags))
+		setbitS[i] = ifExistsElse(each, 4, permsS[i])
+	}
+
+	//TODO convert int to uint64, see issue #25
+	perms = make([]ptypes.PermFlag, len(permsS))
+	for i, perm := range permsS {
+		pflag, err := strconv.Atoi(perm)
+		if err != nil {
+			Exit(fmt.Errorf("Permissions must be an integer"))
+		}
+		perms[i] = ptypes.PermFlag(pflag)
+	}
+	setbits = make([]ptypes.PermFlag, len(setbitS))
+	for i, setbit := range setbitS {
+		setbitsFlag, err := strconv.Atoi(setbit)
+		if err != nil {
+			Exit(fmt.Errorf("SetBits must be an integer"))
+		}
+		setbits[i] = ptypes.PermFlag(setbitsFlag)
+	}
+
+	// convert amts to ints
+	amts = make([]int64, len(amtS))
+	for i, a := range amtS {
+		if amts[i], err = strconv.ParseInt(a, 10, 64); err != nil {
+			err = fmt.Errorf("Invalid amount: %v", err)
+			return
+		}
+	}
+
+	// convert pubkey hex strings to struct
+	pubKeys = pubKeyStringsToPubKeys(pubkeys)
+
+	return pubKeys, amts, names, perms, setbits, nil
+}
+
+const stdinTimeoutSeconds = 1
+
+// read the priv validator json off stdin or timeout and fail
+func readStdinTimeout() []byte {
+	ch := make(chan []byte, 1)
+	go func() {
+		privJSON, err := ioutil.ReadAll(os.Stdin)
+		IfExit(err)
+		ch <- privJSON
+	}()
+	ticker := time.Tick(time.Second * stdinTimeoutSeconds)
+	select {
+	case <-ticker:
+		Exit(fmt.Errorf("Please pass a priv_validator.json on stdin, or specify either a pubkey with --pub or csv file with --csv"))
+	case privJSON := <-ch:
+		return privJSON
+	}
+	return nil
+}
diff --git a/genesis/gen_test.go b/genesis/gen_test.go
new file mode 100644
index 00000000..6bbb1424
--- /dev/null
+++ b/genesis/gen_test.go
@@ -0,0 +1,204 @@
+package genesis
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"testing"
+
+	ptypes "github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/eris-ltd/tendermint/permission/types"
+	stypes "github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/eris-ltd/tendermint/state/types"
+	"github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/eris-ltd/tendermint/types"
+)
+
+func MakeGenesisDocFromFile(genDocFile string) *stypes.GenesisDoc {
+	jsonBlob, err := ioutil.ReadFile(genDocFile)
+	if err != nil {
+		fmt.Sprintf("Couldn't read GenesisDoc file: %v", err)
+		os.Exit(1)
+	}
+	return stypes.GenesisDocFromJSON(jsonBlob)
+}
+
+func testCoreRandom(N int) error {
+	chainID := "test_chainID"
+
+	genBytes, privVals, err := coreRandom(N, chainID, "", "", "", false)
+	if err != nil {
+		return err
+	}
+
+	if len(privVals) != N {
+		return fmt.Errorf("len(privVals) != N")
+	}
+
+	// make sure each validator is in the genesis and all genesi are the same
+	for i, v := range privVals {
+		dirFlag := DirFlag
+		if N > 1 {
+			dirFlag = path.Join(DirFlag, fmt.Sprintf("%s_%d", chainID, i))
+		}
+
+		b, err := ioutil.ReadFile(path.Join(dirFlag, "genesis.json"))
+		if err != nil {
+			return err
+		}
+		if !bytes.Equal(b, genBytes) {
+			return fmt.Errorf("written genesis.json different from returned by coreRandom")
+		}
+
+		gDoc := MakeGenesisDocFromFile(path.Join(dirFlag, "genesis.json"))
+
+		if len(gDoc.Validators) != N {
+			return fmt.Errorf("Expected %d validators. Got %d", N, len(gDoc.Validators))
+		}
+
+		privVal := types.LoadPrivValidator(path.Join(dirFlag, "priv_validator.json"))
+		if !bytes.Equal(privVal.Address, v.Address) {
+			return fmt.Errorf("priv_validator file contents different than result of coreRandom")
+		}
+		var found bool
+		for _, val := range gDoc.Validators {
+			if bytes.Equal(val.UnbondTo[0].Address, privVal.Address) {
+				found = true
+			}
+		}
+		if !found {
+			return fmt.Errorf("failed to find validator %d:%X in genesis.json", i, v.Address)
+		}
+	}
+	return nil
+}
+
+func TestRandom(t *testing.T) {
+	// make temp dir
+	dir, err := ioutil.TempDir(os.TempDir(), "mintgen-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	DirFlag = dir
+	defer func() {
+		// cleanup
+		os.RemoveAll(DirFlag)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	if err = testCoreRandom(1); err != nil {
+		return
+	}
+	if err = testCoreRandom(3); err != nil {
+		return
+	}
+}
+
+type GenDoc struct {
+	pubkeys []string
+	amts    []int
+	names   []string
+	perms   []int
+	setbits []int
+}
+
+var csv1 = GenDoc{
+	pubkeys: []string{"3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961C09", "3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961C10", "3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961Cff", "3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961Cab"},
+	amts:    []int{10, 100, 1000, 100000},
+	names:   []string{"", "ok", "hi", "hm"},
+	perms:   []int{1, 2, 128, 130},
+	setbits: []int{1, 2, 128, 131},
+}
+
+var csv2 = GenDoc{
+	pubkeys: []string{"3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961C09", "3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961C10", "3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961Cff", "3D64963C2EE465AA3866DAF420FA1D35F54A1C2DDCF4524C587CD7295D961Cab"},
+	amts:    nil,
+	names:   nil,
+	perms:   nil,
+	setbits: nil,
+}
+
+func csv1String() string {
+	buf := new(bytes.Buffer)
+	for i, pub := range csv1.pubkeys {
+		buf.WriteString(fmt.Sprintf("%s,%d,%s,%d,%d\n", pub, csv1.amts[i], csv1.names[i], csv1.perms[i], csv1.setbits[i]))
+	}
+	return string(buf.Bytes())
+}
+
+func csv2String() string {
+	buf := new(bytes.Buffer)
+	for _, pub := range csv2.pubkeys {
+		buf.WriteString(fmt.Sprintf("%s,\n", pub))
+	}
+	return string(buf.Bytes())
+}
+
+func testKnownCSV(csvFile string, csv GenDoc) error {
+	chainID := "test_chainID"
+
+	if err := ioutil.WriteFile(path.Join(DirFlag, "accounts.csv"), []byte(csvFile), 0600); err != nil {
+		return err
+	}
+
+	genBytes, err := coreKnown(chainID, path.Join(DirFlag, "accounts.csv"), "")
+	if err != nil {
+		return err
+	}
+
+	if err := ioutil.WriteFile(path.Join(DirFlag, "genesis.json"), genBytes, 0600); err != nil {
+		return err
+	}
+
+	gDoc := MakeGenesisDocFromFile(path.Join(DirFlag, "genesis.json"))
+
+	N := len(csv.pubkeys)
+	if len(gDoc.Validators) != N {
+		return fmt.Errorf("Expected %d validators. Got %d", N, len(gDoc.Validators))
+	}
+
+	for i, pub := range csv.pubkeys {
+		pubBytes, _ := hex.DecodeString(pub)
+		if !bytes.Equal(gDoc.Validators[i].PubKey[:], pubBytes) {
+			return fmt.Errorf("failed to find validator %d:%X in genesis.json", i, pub)
+		}
+		if len(csv.amts) > 0 && gDoc.Accounts[i].Amount != int64(csv.amts[i]) {
+			return fmt.Errorf("amts dont match. got %d, expected %d", gDoc.Accounts[i].Amount, csv.amts[i])
+		}
+		if len(csv.perms) > 0 && gDoc.Accounts[i].Permissions.Base.Perms != ptypes.PermFlag(csv.perms[i]) {
+			return fmt.Errorf("perms dont match. got %d, expected %d", gDoc.Accounts[i].Permissions.Base.Perms, csv.perms[i])
+		}
+		if len(csv.setbits) > 0 && gDoc.Accounts[i].Permissions.Base.SetBit != ptypes.PermFlag(csv.setbits[i]) {
+			return fmt.Errorf("setbits dont match. got %d, expected %d", gDoc.Accounts[i].Permissions.Base.SetBit, csv.setbits[i])
+		}
+	}
+	return nil
+}
+
+func TestKnownCSV(t *testing.T) {
+	// make temp dir
+	dir, err := ioutil.TempDir(os.TempDir(), "mintgen-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	defer func() {
+		//cleanup
+		os.RemoveAll(DirFlag)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+	}()
+
+	DirFlag = dir
+	if err = testKnownCSV(csv1String(), csv1); err != nil {
+		return
+	}
+	if err = testKnownCSV(csv2String(), csv2); err != nil {
+		return
+	}
+}
-- 
GitLab