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