diff --git a/genesis/deterministic_genesis.go b/genesis/deterministic_genesis.go new file mode 100644 index 0000000000000000000000000000000000000000..7eebd858da93ea33b22e816b6bf2f5ae63337a6c --- /dev/null +++ b/genesis/deterministic_genesis.go @@ -0,0 +1,98 @@ +package genesis + +import ( + "math/rand" + "time" + + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" + "github.com/tendermint/ed25519" + "github.com/tendermint/go-crypto" +) + +type deterministicGenesis struct { + random *rand.Rand +} + +// Generates deterministic pseudo-random genesis state +func NewDeterministicGenesis(seed int64) *deterministicGenesis { + return &deterministicGenesis{ + random: rand.New(rand.NewSource(seed)), + } +} + +func (dg *deterministicGenesis) GenesisDoc(numAccounts int, randBalance bool, minBalance uint64, numValidators int, + randBonded bool, minBonded int64) (*GenesisDoc, []acm.PrivateAccount) { + + accounts := make([]Account, numAccounts) + privAccounts := make([]acm.PrivateAccount, numAccounts) + defaultPerms := permission.DefaultAccountPermissions + for i := 0; i < numAccounts; i++ { + account, privAccount := dg.Account(randBalance, minBalance) + accounts[i] = Account{ + BasicAccount: BasicAccount{ + Address: account.Address(), + Amount: account.Balance(), + }, + Permissions: defaultPerms.Clone(), // This will get copied into each state.Account. + } + privAccounts[i] = privAccount + } + validators := make([]Validator, numValidators) + for i := 0; i < numValidators; i++ { + validator := acm.GeneratePrivateAccountFromSecret(fmt.Sprintf("val_%v", i)) + validators[i] = Validator{ + BasicAccount: BasicAccount{ + Address: validator.Address(), + PublicKey: validator.PublicKey(), + Amount: uint64(dg.random.Int63()), + }, + UnbondTo: []BasicAccount{ + { + Address: validator.Address(), + Amount: uint64(dg.random.Int63()), + }, + }, + } + } + return &GenesisDoc{ + ChainName: "TestChain", + GenesisTime: time.Unix(1506172037, 0), + Accounts: accounts, + Validators: validators, + }, privAccounts + +} + +func (dg *deterministicGenesis) Account(randBalance bool, minBalance uint64) (acm.Account, acm.PrivateAccount) { + privKey := dg.PrivateKey() + pubKey := acm.PublicKeyFromPubKey(privKey.PubKey()) + privAccount := &acm.ConcretePrivateAccount{ + PublicKey: pubKey, + PrivateKey: privKey, + Address: pubKey.Address(), + } + perms := permission.DefaultAccountPermissions + acc := &acm.ConcreteAccount{ + Address: privAccount.Address, + PublicKey: privAccount.PublicKey, + Sequence: uint64(dg.random.Int()), + Balance: minBalance, + Permissions: perms, + } + if randBalance { + acc.Balance += uint64(dg.random.Int()) + } + return acc.Account(), privAccount.PrivateAccount() +} + +func (dg *deterministicGenesis) PrivateKey() acm.PrivateKey { + privKeyBytes := new([64]byte) + for i := 0; i < 32; i++ { + privKeyBytes[i] = byte(dg.random.Int() % 256) + } + ed25519.MakePublicKey(privKeyBytes) + return acm.PrivateKeyFromPrivKey(crypto.PrivKeyEd25519(*privKeyBytes).Wrap()) +} diff --git a/genesis/gen_test.go b/genesis/gen_test.go deleted file mode 100644 index 3a55eea7df1cbbba038cfb07be248dc383f5c889..0000000000000000000000000000000000000000 --- a/genesis/gen_test.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genesis - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "testing" - "time" -) - -// set the chain ID -var chainID string = "genesis-file-maker-test" - -var genesisFileExpected string = `{ - "genesis_time": "0001-01-01T00:00:00.000Z", - "chain_id": "genesis-file-maker-test", - "params": null, - "accounts": [ - { - "address": "74417C1BEFB3938B71B22B202050A4C6591FFCF6", - "amount": 9999999999, - "name": "genesis-file-maker-test_developer_000", - "permissions": { - "base": { - "perms": 14430, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "0C9DAEA4046491A661FCE0B41B0CAA2AD3415268", - "amount": 99999999999999, - "name": "genesis-file-maker-test_full_000", - "permissions": { - "base": { - "perms": 16383, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "E1BD50A1B90A15861F5CF0F182D291F556B21A86", - "amount": 9999999999, - "name": "genesis-file-maker-test_participant_000", - "permissions": { - "base": { - "perms": 2118, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "A6C8E2DE652DB8ADB4036293DC21F8FE389D77C2", - "amount": 9999999999, - "name": "genesis-file-maker-test_root_000", - "permissions": { - "base": { - "perms": 16383, - "set": 16383 - }, - "roles": [] - } - }, - { - "address": "E96CB7910001320B6F1E2266A8431D5E98FF0183", - "amount": 9999999999, - "name": "genesis-file-maker-test_validator_000", - "permissions": { - "base": { - "perms": 32, - "set": 16383 - }, - "roles": [] - } - } - ], - "validators": [ - { - "pub_key": [ - 1, - "238E1A77CC7CDCD13F4D77841F1FE4A46A77DB691EC89718CD0D4CB3409F61D2" - ], - "amount": 9999999999, - "name": "genesis-file-maker-test_full_000", - "unbond_to": [ - { - "address": "0C9DAEA4046491A661FCE0B41B0CAA2AD3415268", - "amount": 9999999999 - } - ] - }, - { - "pub_key": [ - 1, - "7F53D78C526F96C87ACBD0D2B9DB2E9FC176981623D26B1DB1CF59748EE9F4CF" - ], - "amount": 9999999998, - "name": "genesis-file-maker-test_validator_000", - "unbond_to": [ - { - "address": "E96CB7910001320B6F1E2266A8431D5E98FF0183", - "amount": 9999999998 - } - ] - } - ] -}` - -var accountsCSV string = `F0BD5CE45D306D61C9AB73CE5268C2B59D52CAF7127EF0E3B65523302254350A,9999999999,genesis-file-maker-test_developer_000,14430,16383 -238E1A77CC7CDCD13F4D77841F1FE4A46A77DB691EC89718CD0D4CB3409F61D2,99999999999999,genesis-file-maker-test_full_000,16383,16383 -E37A655E560D53721C9BB06BA742398323504DFE2EB2C67E71F8D16E71E0471B,9999999999,genesis-file-maker-test_participant_000,2118,16383 -EC0E38CC8308EC9E720EE839242A7BC5C781D1F852E962FAC5A8E0599CE5B224,9999999999,genesis-file-maker-test_root_000,16383,16383 -7F53D78C526F96C87ACBD0D2B9DB2E9FC176981623D26B1DB1CF59748EE9F4CF,9999999999,genesis-file-maker-test_validator_000,32,16383` - -var validatorsCSV string = `238E1A77CC7CDCD13F4D77841F1FE4A46A77DB691EC89718CD0D4CB3409F61D2,9999999999,genesis-file-maker-test_full_000,16383,16383 -7F53D78C526F96C87ACBD0D2B9DB2E9FC176981623D26B1DB1CF59748EE9F4CF,9999999998,genesis-file-maker-test_validator_000,32,16383` - -func TestKnownCSV(t *testing.T) { - // make temp dir - dir, err := ioutil.TempDir(os.TempDir(), "genesis-file-maker-test") - if err != nil { - t.Fatal(err) - } - - defer func() { - //cleanup - os.RemoveAll(dir) - if err != nil { - t.Fatal(err) - } - - }() - - // set the filepaths to be written to - accountsCSVpath := filepath.Join(dir, "accounts.csv") - validatorsCSVpath := filepath.Join(dir, "validators.csv") - - // write the accounts.csv - if err := ioutil.WriteFile(accountsCSVpath, []byte(accountsCSV), 0600); err != nil { - t.Fatal(err) - } - - // write the validators.csv - if err := ioutil.WriteFile(validatorsCSVpath, []byte(validatorsCSV), 0600); err != nil { - t.Fatal(err) - } - - // create the genesis file - // NOTE: [ben] set time to zero time, "genesis_time": "0001-01-01T00:00:00.000Z" - genesisFileWritten, err := generateKnownWithTime(chainID, accountsCSVpath, validatorsCSVpath, time.Time{}) - if err != nil { - t.Fatal(err) - } - - // compare - if !bytes.Equal([]byte(genesisFileExpected), []byte(genesisFileWritten)) { - t.Fatalf("Bad genesis file: got (%s), expected (%s)", genesisFileWritten, genesisFileExpected) - } - -} diff --git a/genesis/genesis.go b/genesis/genesis.go index 4eb99cbbc64c498b45b816386c0295601c905001..7d62594e5f60e35b7ec3055d41f9629874ec6075 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -15,70 +15,214 @@ package genesis import ( - "bytes" + "crypto/sha256" "encoding/json" + "fmt" + "sort" "time" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" - wire "github.com/tendermint/go-wire" ) -// MakeGenesisDocFromAccounts takes a chainName and a slice of pointers to GenesisAccount, -// and a slice of pointers to GenesisValidator to construct a GenesisDoc, or returns an error on -// failure. In particular MakeGenesisDocFromAccount uses the local time as a -// timestamp for the GenesisDoc. -func MakeGenesisDocFromAccounts(chainName string, accounts []*GenesisAccount, - validators []*GenesisValidator) (GenesisDoc, error) { - - // TODO: assert valid accounts and validators - // TODO: [ben] expose setting global permissions - globalPermissions := ptypes.DefaultAccountPermissions.Clone() - genesisParameters := &GenesisParams{ - GlobalPermissions: &globalPermissions, +// How many bytes to take from the front of the GenesisDoc hash to append +// to the ChainName to form the ChainID. The idea is to avoid some classes +// of replay attack between chains with the same name. +const ShortHashSuffixBytes = 3 + +// we store the GenesisDoc in the db under this key + +var GenDocKey = []byte("GenDocKey") + +//------------------------------------------------------------ +// core types for a genesis definition + +type BasicAccount struct { + // Address is convenient to have in file for reference, but otherwise ignored since derived from PublicKey + Address acm.Address + PublicKey acm.PublicKey + Amount uint64 +} + +type Account struct { + BasicAccount + Name string + Permissions ptypes.AccountPermissions +} + +type Validator struct { + BasicAccount + Name string + UnbondTo []BasicAccount +} + +//------------------------------------------------------------ +// GenesisDoc is stored in the state database + +type GenesisDoc struct { + GenesisTime time.Time + ChainName string + Salt []byte `json:",omitempty"` + GlobalPermissions ptypes.AccountPermissions + Accounts []Account + Validators []Validator +} + +// JSONBytes returns the JSON (not-yet) canonical bytes for a given +// GenesisDoc or an error. +func (genesisDoc *GenesisDoc) JSONBytes() ([]byte, error) { + // TODO: write JSON in canonical order + return json.MarshalIndent(genesisDoc, "", "\t") +} + +func (genesisDoc *GenesisDoc) Hash() []byte { + genesisDocBytes, err := genesisDoc.JSONBytes() + if err != nil { + panic(fmt.Errorf("could not create hash of GenesisDoc: %v", err)) } - // copy slice of pointers to accounts into slice of accounts - accountsCopy := make([]GenesisAccount, len(accounts)) - for i, genesisAccount := range accounts { - accountsCopy[i] = genesisAccount.Clone() + hasher := sha256.New() + hasher.Write(genesisDocBytes) + return hasher.Sum(nil) +} + +func (genesisDoc *GenesisDoc) ShortHash() []byte { + return genesisDoc.Hash()[:ShortHashSuffixBytes] +} + +func (genesisDoc *GenesisDoc) ChainID() string { + return fmt.Sprintf("%s-%X", genesisDoc.ChainName, genesisDoc.ShortHash()) +} + +//------------------------------------------------------------ +// Make genesis state from file + +func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) { + genDoc := new(GenesisDoc) + err := json.Unmarshal(jsonBlob, genDoc) + if err != nil { + return nil, fmt.Errorf("couldn't read GenesisDoc: %v", err) } - // copy slice of pointers to validators into slice of validators - validatorsCopy := make([]GenesisValidator, len(validators)) - for i, genesisValidator := range validators { - genesisValidatorCopy, err := genesisValidator.Clone() - if err != nil { - return GenesisDoc{}, err - } - validatorsCopy[i] = genesisValidatorCopy + return genDoc, nil +} + +//------------------------------------------------------------ +// Account methods + +func GenesisAccountFromAccount(name string, account acm.Account) Account { + return Account{ + Name: name, + Permissions: account.Permissions(), + BasicAccount: BasicAccount{ + Address: account.Address(), + Amount: account.Balance(), + }, } - genesisDoc := GenesisDoc{ - GenesisTime: time.Now(), - // TODO: this needs to be corrected for ChainName, and ChainId - // is the derived hash from the GenesisDoc serialised bytes - ChainID: chainName, - Params: genesisParameters, - Accounts: accountsCopy, - Validators: validatorsCopy, +} + +// Clone clones the genesis account +func (genesisAccount *Account) Clone() Account { + // clone the account permissions + return Account{ + BasicAccount: BasicAccount{ + Address: genesisAccount.Address, + Amount: genesisAccount.Amount, + }, + Name: genesisAccount.Name, + Permissions: genesisAccount.Permissions.Clone(), } - return genesisDoc, nil } -// GetGenesisFileBytes returns the JSON (not-yet) canonical bytes for a given -// GenesisDoc or an error. In a first rewrite, rely on go-wire -// for the JSON serialisation with type-bytes. -func GetGenesisFileBytes(genesisDoc *GenesisDoc) ([]byte, error) { +//------------------------------------------------------------ +// Validator methods - // TODO: write JSON in canonical order - var err error - buffer, n := new(bytes.Buffer), new(int) - // write JSON with go-wire type-bytes (for public keys) - wire.WriteJSON(genesisDoc, buffer, n, &err) - if err != nil { - return nil, err +func (gv *Validator) Validator() acm.Validator { + return acm.ConcreteValidator{ + Address: gv.PublicKey.Address(), + PublicKey: gv.PublicKey, + Power: uint64(gv.Amount), + }.Validator() +} + +// Clone clones the genesis validator +func (gv *Validator) Clone() Validator { + // clone the addresses to unbond to + unbondToClone := make([]BasicAccount, len(gv.UnbondTo)) + for i, basicAccount := range gv.UnbondTo { + unbondToClone[i] = basicAccount.Clone() + } + return Validator{ + BasicAccount: BasicAccount{ + PublicKey: gv.PublicKey, + Amount: gv.Amount, + }, + Name: gv.Name, + UnbondTo: unbondToClone, + } +} + +//------------------------------------------------------------ +// BasicAccount methods + +// Clone clones the basic account +func (basicAccount *BasicAccount) Clone() BasicAccount { + return BasicAccount{ + Address: basicAccount.Address, + Amount: basicAccount.Amount, + } +} + +// MakeGenesisDocFromAccounts takes a chainName and a slice of pointers to Account, +// and a slice of pointers to Validator to construct a GenesisDoc, or returns an error on +// failure. In particular MakeGenesisDocFromAccount uses the local time as a +// timestamp for the GenesisDoc. +func MakeGenesisDocFromAccounts(chainName string, salt []byte, genesisTime time.Time, accounts map[string]acm.Account, + validators map[string]acm.Validator) *GenesisDoc { + + // Establish deterministic order of accounts by name so we obtain identical GenesisDoc + // from identical input + names := make([]string, 0, len(accounts)) + for name := range accounts { + names = append(names, name) + } + sort.Strings(names) + // copy slice of pointers to accounts into slice of accounts + genesisAccounts := make([]Account, 0, len(accounts)) + for _, name := range names { + genesisAccounts = append(genesisAccounts, GenesisAccountFromAccount(name, accounts[name])) + } + // Sigh... + names = names[:0] + for name := range validators { + names = append(names, name) + } + sort.Strings(names) + // copy slice of pointers to validators into slice of validators + genesisValidators := make([]Validator, 0, len(validators)) + for _, name := range names { + val := validators[name] + genesisValidators = append(genesisValidators, Validator{ + Name: name, + BasicAccount: BasicAccount{ + Address: val.Address(), + PublicKey: val.PublicKey(), + Amount: val.Power(), + }, + // Simpler to just do this by convention + UnbondTo: []BasicAccount{ + { + Amount: val.Power(), + Address: val.Address(), + }, + }, + }) } - // rewrite buffer with indentation - indentedBuffer := new(bytes.Buffer) - if err := json.Indent(indentedBuffer, buffer.Bytes(), "", "\t"); err != nil { - return nil, err + return &GenesisDoc{ + ChainName: chainName, + Salt: salt, + GenesisTime: genesisTime, + GlobalPermissions: permission.DefaultAccountPermissions.Clone(), + Accounts: genesisAccounts, + Validators: genesisValidators, } - return indentedBuffer.Bytes(), nil } diff --git a/genesis/genesis_test.go b/genesis/genesis_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0c5021bb0a0b07f3ad6d10a96e003d807cbbf305 --- /dev/null +++ b/genesis/genesis_test.go @@ -0,0 +1,59 @@ +package genesis + +import ( + "fmt" + "testing" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" +) + +var genesisTime, _ = time.Parse("02-01-2006", "27-10-2017") + +func TestMakeGenesisDocFromAccounts(t *testing.T) { + genDoc := MakeGenesisDocFromAccounts("test-chain", nil, genesisTime, + accountMap("Tinkie-winkie", "Lala", "Po", "Dipsy"), + validatorMap("Foo", "Bar", "Baz"), + ) + + // Check we have matching serialisation after a round trip + bs, err := genDoc.JSONBytes() + assert.NoError(t, err) + + genDocOut, err := GenesisDocFromJSON(bs) + assert.NoError(t, err) + + bsOut, err := genDocOut.JSONBytes() + assert.NoError(t, err) + + assert.Equal(t, bs, bsOut) + assert.Equal(t, genDoc.Hash(), genDocOut.Hash()) + fmt.Println(string(bs)) +} + +func accountMap(names ...string) map[string]acm.Account { + accounts := make(map[string]acm.Account, len(names)) + for _, name := range names { + accounts[name] = accountFromName(name) + } + return accounts +} + +func validatorMap(names ...string) map[string]acm.Validator { + validators := make(map[string]acm.Validator, len(names)) + for _, name := range names { + validators[name] = acm.AsValidator(accountFromName(name)) + } + return validators +} + +func accountFromName(name string) acm.Account { + ca := acm.NewConcreteAccountFromSecret(name) + for _, c := range name { + ca.Balance += uint64(c) + } + ca.Permissions = permission.AllAccountPermissions.Clone() + return ca.Account() +} diff --git a/genesis/make_genesis_file.go b/genesis/make_genesis_file.go deleted file mode 100644 index 624b73d23f351ad1649aecd44287e792d4ebe054..0000000000000000000000000000000000000000 --- a/genesis/make_genesis_file.go +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genesis - -import ( - "bytes" - "encoding/csv" - "encoding/hex" - "encoding/json" - "fmt" - "os" - "strconv" - "time" - - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/util" - - "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" -) - -//------------------------------------------------------------------------------------ -// interface functions that are consumed by monax tooling -// TODO: [ben] these interfaces will be deprecated from v0.17 - -func GenerateKnown(chainID, accountsPathCSV, validatorsPathCSV string) (string, error) { - return generateKnownWithTime(chainID, accountsPathCSV, validatorsPathCSV, - // set the timestamp for the genesis - time.Now()) -} - -//------------------------------------------------------------------------------------ -// interface functions that are consumed by monax tooling - -func GenerateGenesisFileBytes(chainName string, genesisAccounts []*GenesisAccount, - genesisValidators []*GenesisValidator) ([]byte, error) { - genesisDoc, err := MakeGenesisDocFromAccounts(chainName, genesisAccounts, genesisValidators) - - buf, buf2, n := new(bytes.Buffer), new(bytes.Buffer), new(int) - wire.WriteJSON(genesisDoc, buf, n, &err) - if err != nil { - return nil, err - } - if err := json.Indent(buf2, buf.Bytes(), "", "\t"); err != nil { - return nil, err - } - - return buf2.Bytes(), nil -} - -//------------------------------------------------------------------------------------ -// core functions that provide functionality for monax tooling in v0.16 - -// GenerateKnownWithTime takes chainId, an accounts and validators CSV filepath -// and a timestamp to generate the string of `genesis.json` -// NOTE: [ben] is introduced as technical debt to preserve the signature -// of GenerateKnown but in order to introduce the timestamp gradually -// This will be deprecated in v0.17 -func generateKnownWithTime(chainID, accountsPathCSV, validatorsPathCSV string, - genesisTime time.Time) (string, error) { - var genDoc *GenesisDoc - - // TODO [eb] eliminate reading priv_val ... [zr] where? - if accountsPathCSV == "" || validatorsPathCSV == "" { - return "", fmt.Errorf("both accounts.csv and validators.csv is required") - } - - pubkeys, amts, names, perms, setbits, err := parseCsv(validatorsPathCSV) - if err != nil { - return "", err - } - - pubkeysA, amtsA, namesA, permsA, setbitsA, err := parseCsv(accountsPathCSV) - if err != nil { - return "", err - } - - genDoc = newGenDoc(chainID, genesisTime, 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) - } - - buf, buf2, n := new(bytes.Buffer), new(bytes.Buffer), new(int) - wire.WriteJSON(genDoc, buf, n, &err) - if err != nil { - return "", err - } - if err := json.Indent(buf2, buf.Bytes(), "", "\t"); err != nil { - return "", err - } - - return buf2.String(), nil -} - -//----------------------------------------------------------------------------- -// gendoc convenience functions - -func newGenDoc(chainID string, genesisTime time.Time, nVal, nAcc int) *GenesisDoc { - genDoc := GenesisDoc{ - ChainID: chainID, - GenesisTime: genesisTime, - } - genDoc.Accounts = make([]GenesisAccount, nAcc) - genDoc.Validators = make([]GenesisValidator, nVal) - return &genDoc -} - -func genDocAddAccount(genDoc *GenesisDoc, pubKey crypto.PubKeyEd25519, amt int64, name string, perm, setbit ptypes.PermFlag, index int) { - addr := pubKey.Address() - acc := 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 *GenesisDoc, pubKey crypto.PubKeyEd25519, amt int64, name string, perm, setbit ptypes.PermFlag, index int) { - addr := pubKey.Address() - genDoc.Validators[index] = GenesisValidator{ - PubKey: pubKey, - Amount: amt, - Name: name, - UnbondTo: []BasicAccount{ - { - Address: addr, - Amount: amt, - }, - }, - } - // [zr] why no index < 0 like in genDocAddAccount? -} - -//----------------------------------------------------------------------------- -// util functions - -// convert hex strings to ed25519 pubkeys -func pubKeyStringsToPubKeys(pubkeys []string) ([]crypto.PubKeyEd25519, error) { - pubKeys := make([]crypto.PubKeyEd25519, len(pubkeys)) - for i, k := range pubkeys { - pubBytes, err := hex.DecodeString(k) - if err != nil { - return pubKeys, err - } - copy(pubKeys[i][:], pubBytes) - } - return pubKeys, nil -} - -// 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 following format: pubkey, starting balance, name, permissions, setbit -func parseCsv(filePath string) (pubKeys []crypto.PubKeyEd25519, amts []int64, names []string, perms, setbits []ptypes.PermFlag, err error) { - - csvFile, err := os.Open(filePath) - if err != nil { - util.Fatalf("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 { - util.Fatalf("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 { - util.Fatalf("Permissions (%v) must be an integer", perm) - } - perms[i] = ptypes.PermFlag(pflag) - } - setbits = make([]ptypes.PermFlag, len(setbitS)) - for i, setbit := range setbitS { - setbitsFlag, err := strconv.Atoi(setbit) - if err != nil { - util.Fatalf("SetBits (%v) must be an integer", setbit) - } - 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, err = pubKeyStringsToPubKeys(pubkeys) - if err != nil { - return - } - - return pubKeys, amts, names, perms, setbits, nil -} diff --git a/genesis/maker.go b/genesis/maker.go index 48f09c563e0cd49ad786be7e235091d82946e436..7b722b46062e93ce4be13987c14d939d4df284a2 100644 --- a/genesis/maker.go +++ b/genesis/maker.go @@ -15,11 +15,21 @@ package genesis import ( + "bytes" + "encoding/csv" + "encoding/hex" + "encoding/json" "fmt" + "os" + "strconv" + "time" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" - + "github.com/hyperledger/burrow/util" "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" ) const ( @@ -27,19 +37,21 @@ const ( PublicKeySecp256k1ByteLength int = 64 ) -// NewGenesisAccount returns a new GenesisAccount -func NewGenesisAccount(address []byte, amount int64, name string, - permissions *ptypes.AccountPermissions) *GenesisAccount { - return &GenesisAccount{ - Address: address, - Amount: amount, +// NewGenesisAccount returns a new Account +func NewGenesisAccount(address acm.Address, amount uint64, name string, + permissions *ptypes.AccountPermissions) *Account { + return &Account{ + BasicAccount: BasicAccount{ + Address: address, + Amount: amount, + }, Name: name, - Permissions: permissions, + Permissions: permissions.Clone(), } } -func NewGenesisValidator(amount int64, name string, unbondToAddress []byte, - unbondAmount int64, keyType string, publicKeyBytes []byte) (*GenesisValidator, error) { +func NewGenesisValidator(amount uint64, name string, unbondToAddress acm.Address, + unbondAmount uint64, keyType string, publicKeyBytes []byte) (*Validator, error) { // convert the key bytes into a typed fixed size byte array var typedPublicKeyBytes []byte switch keyType { @@ -61,20 +73,252 @@ func NewGenesisValidator(amount int64, name string, unbondToAddress []byte, default: return nil, fmt.Errorf("Unsupported key type (%s)", keyType) } - newPublicKey, err := crypto.PubKeyFromBytes(typedPublicKeyBytes) + newPubKey, err := crypto.PubKeyFromBytes(typedPublicKeyBytes) if err != nil { return nil, err } // ability to unbond to multiple accounts currently unused var unbondTo []BasicAccount - return &GenesisValidator{ - PubKey: newPublicKey, - Amount: unbondAmount, - Name: name, + address, err := acm.AddressFromBytes(newPubKey.Address()) + if err != nil { + return nil, err + } + return &Validator{ + BasicAccount: BasicAccount{ + Address: address, + PublicKey: acm.PublicKeyFromPubKey(newPubKey), + Amount: amount, + }, + Name: name, UnbondTo: append(unbondTo, BasicAccount{ Address: unbondToAddress, Amount: unbondAmount, }), }, nil } + +//------------------------------------------------------------------------------------ +// interface functions that are consumed by monax tooling + +func GenerateKnown(chainName, accountsPathCSV, validatorsPathCSV string) (string, error) { + return generateKnownWithTime(chainName, accountsPathCSV, validatorsPathCSV, + // set the timestamp for the genesis + time.Now()) +} + +//------------------------------------------------------------------------------------ +// interface functions that are consumed by monax tooling + +func GenerateGenesisFileBytes(chainName string, salt []byte, genesisTime time.Time, genesisAccounts map[string]acm.Account, + genesisValidators map[string]acm.Validator) ([]byte, error) { + + genesisDoc := MakeGenesisDocFromAccounts(chainName, salt, genesisTime, genesisAccounts, genesisValidators) + + var err error + buf, buf2, n := new(bytes.Buffer), new(bytes.Buffer), new(int) + wire.WriteJSON(genesisDoc, buf, n, &err) + if err != nil { + return nil, err + } + if err := json.Indent(buf2, buf.Bytes(), "", "\t"); err != nil { + return nil, err + } + + return buf2.Bytes(), nil +} + +//------------------------------------------------------------------------------------ +// core functions that provide functionality for monax tooling in v0.16 + +// GenerateKnownWithTime takes chainName, an accounts and validators CSV filepath +// and a timestamp to generate the string of `genesis.json` +// NOTE: [ben] is introduced as technical debt to preserve the signature +// of GenerateKnown but in order to introduce the timestamp gradually +// This will be deprecated in v0.17 +func generateKnownWithTime(chainName, accountsPathCSV, validatorsPathCSV string, + genesisTime time.Time) (string, error) { + var genDoc *GenesisDoc + + // TODO [eb] eliminate reading priv_val ... [zr] where? + if accountsPathCSV == "" || validatorsPathCSV == "" { + return "", fmt.Errorf("both accounts.csv and validators.csv is required") + } + + pubkeys, amts, names, perms, setbits, err := parseCsv(validatorsPathCSV) + if err != nil { + return "", err + } + + pubkeysA, amtsA, namesA, permsA, setbitsA, err := parseCsv(accountsPathCSV) + if err != nil { + return "", err + } + + genDoc = newGenDoc(chainName, genesisTime, 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) + } + + buf, buf2, n := new(bytes.Buffer), new(bytes.Buffer), new(int) + wire.WriteJSON(genDoc, buf, n, &err) + if err != nil { + return "", err + } + if err := json.Indent(buf2, buf.Bytes(), "", "\t"); err != nil { + return "", err + } + + return buf2.String(), nil +} + +//----------------------------------------------------------------------------- +// gendoc convenience functions + +func newGenDoc(chainName string, genesisTime time.Time, nVal, nAcc int) *GenesisDoc { + genDoc := GenesisDoc{ + ChainName: chainName, + GenesisTime: genesisTime, + } + genDoc.Accounts = make([]Account, nAcc) + genDoc.Validators = make([]Validator, nVal) + return &genDoc +} + +func genDocAddAccount(genDoc *GenesisDoc, pubKey crypto.PubKeyEd25519, amt uint64, name string, + perm, setbit ptypes.PermFlag, index int) { + addr, _ := acm.AddressFromBytes(pubKey.Address()) + acc := Account{ + BasicAccount: BasicAccount{ + 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 *GenesisDoc, pubKey crypto.PubKeyEd25519, amt uint64, name string, + perm, setbit ptypes.PermFlag, index int) { + addr, _ := acm.AddressFromBytes(pubKey.Address()) + genDoc.Validators[index] = Validator{ + BasicAccount: BasicAccount{ + Address: acm.MustAddressFromBytes(pubKey.Address()), + PublicKey: acm.PublicKeyFromPubKey(pubKey.Wrap()), + Amount: amt, + }, + Name: name, + UnbondTo: []BasicAccount{ + { + Address: addr, + Amount: amt, + }, + }, + } + // [zr] why no index < 0 like in genDocAddAccount? +} + +//----------------------------------------------------------------------------- +// util functions + +// convert hex strings to ed25519 pubkeys +func pubKeyStringsToPubKeys(pubkeys []string) ([]crypto.PubKeyEd25519, error) { + pubKeys := make([]crypto.PubKeyEd25519, len(pubkeys)) + for i, k := range pubkeys { + pubBytes, err := hex.DecodeString(k) + if err != nil { + return pubKeys, err + } + copy(pubKeys[i][:], pubBytes) + } + return pubKeys, nil +} + +// 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 following format: pubkey, starting balance, name, permissions, setbit +func parseCsv(filePath string) (pubKeys []crypto.PubKeyEd25519, amts []uint64, names []string, perms, setbits []ptypes.PermFlag, err error) { + + csvFile, err := os.Open(filePath) + if err != nil { + util.Fatalf("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 { + util.Fatalf("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", permission.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 { + util.Fatalf("Permissions (%v) must be an integer", perm) + } + perms[i] = ptypes.PermFlag(pflag) + } + setbits = make([]ptypes.PermFlag, len(setbitS)) + for i, setbit := range setbitS { + setbitsFlag, err := strconv.Atoi(setbit) + if err != nil { + util.Fatalf("SetBits (%v) must be an integer", setbit) + } + setbits[i] = ptypes.PermFlag(setbitsFlag) + } + + // convert amts to ints + amts = make([]uint64, len(amtS)) + for i, a := range amtS { + if amts[i], err = strconv.ParseUint(a, 10, 64); err != nil { + err = fmt.Errorf("Invalid amount: %v", err) + return + } + } + + // convert pubkey hex strings to struct + pubKeys, err = pubKeyStringsToPubKeys(pubkeys) + if err != nil { + return + } + + return pubKeys, amts, names, perms, setbits, nil +} diff --git a/genesis/spec/genesis_spec.go b/genesis/spec/genesis_spec.go new file mode 100644 index 0000000000000000000000000000000000000000..be653182b652a63522e27cb158ed3f7462a48265 --- /dev/null +++ b/genesis/spec/genesis_spec.go @@ -0,0 +1,233 @@ +package spec + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" +) + +const DefaultAmount uint64 = 1000000 +const DefaultAmountBonded uint64 = 10000 + +// A GenesisSpec is schematic representation of a genesis state, that is it is a template +// for a GenesisDoc excluding that which needs to be instantiated at the point of genesis +// so it describes the type and number of accounts, the genesis salt, but not the +// account keys or addresses, or the GenesisTime. It is responsible for generating keys +// by interacting with the KeysClient it is passed and other information not known at +// specification time +type GenesisSpec struct { + GenesisTime *time.Time `json:",omitempty"` + ChainName string `json:",omitempty"` + Salt []byte `json:",omitempty"` + GlobalPermissions []string `json:",omitempty"` + Accounts []TemplateAccount `json:",omitempty"` +} + +type TemplateAccount struct { + // Address is convenient to have in file for reference, but otherwise ignored since derived from PublicKey + Address *acm.Address `json:",omitempty"` + PubKey *acm.PublicKey `json:",omitempty"` + Amount *uint64 `json:",omitempty"` + // If any bonded amount then this account is also a Validator + AmountBonded *uint64 `json:",omitempty"` + Name string `json:",omitempty"` + Permissions []string `json:",omitempty"` + Roles []string `json:",omitempty"` +} + +func (ta TemplateAccount) Validator(keyClient keys.KeyClient, index int) (*genesis.Validator, error) { + var err error + gv := new(genesis.Validator) + gv.PublicKey, gv.Address, err = ta.RealisePubKeyAndAddress(keyClient) + if err != nil { + return nil, err + } + if ta.AmountBonded == nil { + gv.Amount = DefaultAmountBonded + } else { + gv.Amount = *ta.AmountBonded + } + if ta.Name == "" { + gv.Name = accountNameFromIndex(index) + } else { + gv.Name = ta.Name + } + + gv.UnbondTo = []genesis.BasicAccount{{ + Address: gv.Address, + PublicKey: gv.PublicKey, + Amount: gv.Amount, + }} + return gv, nil +} + +func (ta TemplateAccount) AccountPermissions() (ptypes.AccountPermissions, error) { + basePerms, err := permission.BasePermissionsFromStringList(ta.Permissions) + if err != nil { + return permission.ZeroAccountPermissions, nil + } + return ptypes.AccountPermissions{ + Base: basePerms, + Roles: ta.Roles, + }, nil +} + +func (ta TemplateAccount) Account(keyClient keys.KeyClient, index int) (*genesis.Account, error) { + var err error + ga := new(genesis.Account) + ga.PublicKey, ga.Address, err = ta.RealisePubKeyAndAddress(keyClient) + if err != nil { + return nil, err + } + if ta.Amount == nil { + ga.Amount = DefaultAmount + } else { + ga.Amount = *ta.Amount + } + if ta.Name == "" { + ga.Name = accountNameFromIndex(index) + } else { + ga.Name = ta.Name + } + if ta.Permissions == nil { + ga.Permissions = permission.DefaultAccountPermissions.Clone() + } else { + ga.Permissions, err = ta.AccountPermissions() + if err != nil { + return nil, err + } + } + return ga, nil +} + +// Adds a public key and address to the template. If PublicKey will try to fetch it by Address. +// If both PublicKey and Address are not set will use the keyClient to generate a new keypair +func (ta TemplateAccount) RealisePubKeyAndAddress(keyClient keys.KeyClient) (pubKey acm.PublicKey, address acm.Address, err error) { + if ta.PubKey == nil { + if ta.Address == nil { + // If neither PublicKey or Address set then generate a new one + address, err = keyClient.Generate(ta.Name, keys.KeyTypeEd25519Ripemd160) + if err != nil { + return + } + } else { + address = *ta.Address + } + // Get the (possibly existing) key + pubKey, err = keyClient.PublicKey(address) + if err != nil { + return + } + } else { + address := ta.PubKey.Address() + if ta.Address != nil && *ta.Address != address { + err = fmt.Errorf("template address %s does not match public key derived address %s", ta.Address, + ta.PubKey) + } + } + return +} + +// Produce a fully realised GenesisDoc from a template GenesisDoc that may omit values +func (gs *GenesisSpec) GenesisDoc(keyClient keys.KeyClient) (*genesis.GenesisDoc, error) { + genesisDoc := new(genesis.GenesisDoc) + if gs.GenesisTime == nil { + genesisDoc.GenesisTime = time.Now() + } else { + genesisDoc.GenesisTime = *gs.GenesisTime + } + + if gs.ChainName == "" { + genesisDoc.ChainName = fmt.Sprintf("BurrowChain_%X", gs.ShortHash()) + } else { + genesisDoc.ChainName = gs.ChainName + } + + if len(gs.GlobalPermissions) == 0 { + genesisDoc.GlobalPermissions = permission.DefaultAccountPermissions.Clone() + } else { + basePerms, err := permission.BasePermissionsFromStringList(gs.GlobalPermissions) + if err != nil { + return nil, err + } + genesisDoc.GlobalPermissions = ptypes.AccountPermissions{ + Base: basePerms, + } + } + + templateAccounts := gs.Accounts + if len(gs.Accounts) == 0 { + amountBonded := DefaultAmountBonded + templateAccounts = append(templateAccounts, TemplateAccount{ + AmountBonded: &amountBonded, + }) + } + + for i, templateAccount := range templateAccounts { + account, err := templateAccount.Account(keyClient, i) + if err != nil { + return nil, fmt.Errorf("could not create Account from template: %v", err) + } + genesisDoc.Accounts = append(genesisDoc.Accounts, *account) + // Create a corresponding validator + if templateAccount.AmountBonded != nil { + // Note this does not modify the input template + templateAccount.Address = &account.Address + validator, err := templateAccount.Validator(keyClient, i) + if err != nil { + return nil, fmt.Errorf("could not create Validator from template: %v", err) + } + genesisDoc.Validators = append(genesisDoc.Validators, *validator) + } + } + + return genesisDoc, nil +} + +func (gs *GenesisSpec) JSONBytes() ([]byte, error) { + bs, err := json.Marshal(gs) + if err != nil { + return nil, err + } + // rewrite buffer with indentation + indentedBuffer := new(bytes.Buffer) + if err := json.Indent(indentedBuffer, bs, "", "\t"); err != nil { + return nil, err + } + return indentedBuffer.Bytes(), nil +} + +func (gs *GenesisSpec) Hash() []byte { + gsBytes, err := gs.JSONBytes() + if err != nil { + panic(fmt.Errorf("could not create hash of GenesisDoc: %v", err)) + } + hasher := sha256.New() + hasher.Write(gsBytes) + return hasher.Sum(nil) +} + +func (gs *GenesisSpec) ShortHash() []byte { + return gs.Hash()[:genesis.ShortHashSuffixBytes] +} + +func GenesisSpecFromJSON(jsonBlob []byte) (*GenesisSpec, error) { + genDoc := new(GenesisSpec) + err := json.Unmarshal(jsonBlob, genDoc) + if err != nil { + return nil, fmt.Errorf("couldn't read GenesisSpec: %v", err) + } + return genDoc, nil +} + +func accountNameFromIndex(index int) string { + return fmt.Sprintf("Account_%v", index) +} diff --git a/genesis/spec/genesis_spec_test.go b/genesis/spec/genesis_spec_test.go new file mode 100644 index 0000000000000000000000000000000000000000..19dae19ffe1a6b1bf03ca69c8c5e3c16600545d9 --- /dev/null +++ b/genesis/spec/genesis_spec_test.go @@ -0,0 +1,91 @@ +package spec + +import ( + "fmt" + "testing" + + "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/keys/mock" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenesisSpec_GenesisDoc(t *testing.T) { + keyClient := mock.NewMockKeyClient() + + // Try a spec with a single account/validator + amtBonded := uint64(100) + genesisSpec := GenesisSpec{ + Accounts: []TemplateAccount{{ + AmountBonded: &amtBonded, + }}, + } + + genesisDoc, err := genesisSpec.GenesisDoc(keyClient) + require.NoError(t, err) + require.Len(t, genesisDoc.Accounts, 1) + // Should create validator + require.Len(t, genesisDoc.Validators, 1) + assert.NotZero(t, genesisDoc.Accounts[0].Address) + assert.NotZero(t, genesisDoc.Accounts[0].PublicKey) + assert.Equal(t, genesisDoc.Accounts[0].Address, genesisDoc.Validators[0].Address) + assert.Equal(t, genesisDoc.Accounts[0].PublicKey, genesisDoc.Validators[0].PublicKey) + assert.Equal(t, amtBonded, genesisDoc.Validators[0].Amount) + assert.NotEmpty(t, genesisDoc.ChainName, "Chain name should not be empty") + + address, err := keyClient.Generate("test-lookup-of-key", keys.KeyTypeEd25519Ripemd160) + require.NoError(t, err) + pubKey, err := keyClient.PublicKey(address) + require.NoError(t, err) + + // Try a spec with two accounts and no validators + amt := uint64(99299299) + genesisSpec = GenesisSpec{ + Accounts: []TemplateAccount{ + { + Address: &address, + }, + { + Amount: &amt, + Permissions: []string{permission.CreateAccountString, permission.CallString}, + }}, + } + + genesisDoc, err = genesisSpec.GenesisDoc(keyClient) + require.NoError(t, err) + + require.Len(t, genesisDoc.Accounts, 2) + // Nothing bonded so no validators + require.Len(t, genesisDoc.Validators, 0) + assert.Equal(t, pubKey, genesisDoc.Accounts[0].PublicKey) + assert.Equal(t, amt, genesisDoc.Accounts[1].Amount) + permFlag := permission.CreateAccount | permission.Call + assert.Equal(t, permFlag, genesisDoc.Accounts[1].Permissions.Base.Perms) + assert.Equal(t, permFlag, genesisDoc.Accounts[1].Permissions.Base.SetBit) + + // Try an empty spec + genesisSpec = GenesisSpec{} + + genesisDoc, err = genesisSpec.GenesisDoc(keyClient) + require.NoError(t, err) + + // Similar assersions to first case - should generate our default single identity chain + require.Len(t, genesisDoc.Accounts, 1) + // Should create validator + require.Len(t, genesisDoc.Validators, 1) + assert.NotZero(t, genesisDoc.Accounts[0].Address) + assert.NotZero(t, genesisDoc.Accounts[0].PublicKey) + assert.Equal(t, genesisDoc.Accounts[0].Address, genesisDoc.Validators[0].Address) + assert.Equal(t, genesisDoc.Accounts[0].PublicKey, genesisDoc.Validators[0].PublicKey) +} + +func TestJSONRoundTrip(t *testing.T) { + + var a []byte + + b := []byte{1, 2, 3} + + c := append(b, a...) + fmt.Println(c) +} diff --git a/genesis/spec/presets.go b/genesis/spec/presets.go new file mode 100644 index 0000000000000000000000000000000000000000..727b9236e869b5ac1ede7374226155f17dbc9c81 --- /dev/null +++ b/genesis/spec/presets.go @@ -0,0 +1,74 @@ +package spec + +import ( + "fmt" + + "sort" + + "github.com/hyperledger/burrow/permission" +) + +// Files here can be used as starting points for building various 'chain types' but are otherwise +// a fairly unprincipled collection of GenesisSpecs that we find useful in testing and development + +func FullAccount(index int) GenesisSpec { + // Inheriting from the arbitrary figures used by monax tool for now + amount := uint64(99999999999999) + amountBonded := uint64(9999999999) + return GenesisSpec{ + Accounts: []TemplateAccount{{ + Name: fmt.Sprintf("Full_%v", index), + Amount: &amount, + AmountBonded: &amountBonded, + Permissions: []string{permission.AllString}, + }, + }, + } +} + +func ParticipantAccount(index int) GenesisSpec { + amount := uint64(9999999999) + return GenesisSpec{ + Accounts: []TemplateAccount{{ + Name: fmt.Sprintf("Participant_%v", index), + Amount: &amount, + Permissions: []string{permission.SendString, permission.CallString, permission.NameString, permission.HasRoleString}, + }}, + } +} + +func MergeGenesisSpecs(genesisSpecs ...GenesisSpec) GenesisSpec { + mergedGenesisSpec := GenesisSpec{} + // We will deduplicate and merge global permissions flags + permSet := make(map[string]bool) + + for _, genesisSpec := range genesisSpecs { + // We'll overwrite chain name for later specs + if genesisSpec.ChainName != "" { + mergedGenesisSpec.ChainName = genesisSpec.ChainName + } + // Take the max genesis time + if mergedGenesisSpec.GenesisTime == nil || + (genesisSpec.GenesisTime != nil && genesisSpec.GenesisTime.After(*mergedGenesisSpec.GenesisTime)) { + mergedGenesisSpec.GenesisTime = genesisSpec.GenesisTime + } + + for _, permString := range genesisSpec.GlobalPermissions { + permSet[permString] = true + } + + mergedGenesisSpec.Salt = append(mergedGenesisSpec.Salt, genesisSpec.Salt...) + mergedGenesisSpec.Accounts = append(mergedGenesisSpec.Accounts, genesisSpec.Accounts...) + } + + mergedGenesisSpec.GlobalPermissions = make([]string, 0, len(permSet)) + + for permString := range permSet { + mergedGenesisSpec.GlobalPermissions = append(mergedGenesisSpec.GlobalPermissions, permString) + } + + // Make sure merged GenesisSpec is deterministic on inputs + sort.Strings(mergedGenesisSpec.GlobalPermissions) + + return mergedGenesisSpec +} diff --git a/genesis/spec/presets_test.go b/genesis/spec/presets_test.go new file mode 100644 index 0000000000000000000000000000000000000000..dc65fd9e92c34671439f0d5e21744ee2b83db7dc --- /dev/null +++ b/genesis/spec/presets_test.go @@ -0,0 +1,35 @@ +package spec + +import ( + "testing" + + "github.com/hyperledger/burrow/keys/mock" + "github.com/hyperledger/burrow/permission" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMergeGenesisSpecAccounts(t *testing.T) { + keyClient := mock.NewMockKeyClient() + gs := MergeGenesisSpecs(FullAccount(0), ParticipantAccount(1), ParticipantAccount(2)) + gd, err := gs.GenesisDoc(keyClient) + require.NoError(t, err) + assert.Len(t, gd.Validators, 1) + assert.Len(t, gd.Accounts, 3) + //bs, err := gd.JSONBytes() + //require.NoError(t, err) + //fmt.Println(string(bs)) +} + +func TestMergeGenesisSpecGlobalPermissions(t *testing.T) { + gs1 := GenesisSpec{ + GlobalPermissions: []string{permission.CreateAccountString, permission.CreateAccountString}, + } + gs2 := GenesisSpec{ + GlobalPermissions: []string{permission.SendString, permission.CreateAccountString, permission.HasRoleString}, + } + + gsMerged := MergeGenesisSpecs(gs1, gs2) + assert.Equal(t, []string{permission.CreateAccountString, permission.HasRoleString, permission.SendString}, + gsMerged.GlobalPermissions) +} diff --git a/genesis/types.go b/genesis/types.go deleted file mode 100644 index 5f1d4ada090dc8465e3b43f76e8fa8414de35390..0000000000000000000000000000000000000000 --- a/genesis/types.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genesis - -import ( - "fmt" - "os" - "time" - - ptypes "github.com/hyperledger/burrow/permission/types" - - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" -) - -//------------------------------------------------------------ -// we store the GenesisDoc in the db under this key - -var GenDocKey = []byte("GenDocKey") - -//------------------------------------------------------------ -// core types for a genesis definition - -type BasicAccount struct { - Address []byte `json:"address"` - Amount int64 `json:"amount"` -} - -type GenesisAccount struct { - Address []byte `json:"address"` - Amount int64 `json:"amount"` - Name string `json:"name"` - Permissions *ptypes.AccountPermissions `json:"permissions"` -} - -type GenesisValidator struct { - PubKey crypto.PubKey `json:"pub_key"` - Amount int64 `json:"amount"` - Name string `json:"name"` - UnbondTo []BasicAccount `json:"unbond_to"` -} - -// GenesisPrivateValidator marshals the state of the private -// validator for the purpose of Genesis creation; and hence -// is defined in genesis and not under consensus, where -// PrivateValidator (currently inherited from Tendermint) is. -type GenesisPrivateValidator struct { - Address string `json:"address"` - PubKey []interface{} `json:"pub_key"` - PrivKey []interface{} `json:"priv_key"` - LastHeight int64 `json:"last_height"` - LastRound int64 `json:"last_round"` - LastStep int64 `json:"last_step"` -} - -type GenesisParams struct { - GlobalPermissions *ptypes.AccountPermissions `json:"global_permissions"` -} - -//------------------------------------------------------------ -// GenesisDoc is stored in the state database - -type GenesisDoc struct { - GenesisTime time.Time `json:"genesis_time"` - ChainID string `json:"chain_id"` - Params *GenesisParams `json:"params"` - Accounts []GenesisAccount `json:"accounts"` - Validators []GenesisValidator `json:"validators"` -} - -//------------------------------------------------------------ -// Make genesis state from file - -func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) { - var err error - wire.ReadJSONPtr(&genState, jsonBlob, &err) - if err != nil { - fmt.Printf("Couldn't read GenesisDoc: %v", err) - // TODO: on error return error, not exit - os.Exit(1) - } - return -} - -//------------------------------------------------------------ -// Methods for genesis types -// NOTE: breaks formatting convention -// TODO: split each genesis type in its own file definition - -//------------------------------------------------------------ -// GenesisAccount methods - -// Clone clones the genesis account -func (genesisAccount *GenesisAccount) Clone() GenesisAccount { - // clone the address - addressClone := make([]byte, len(genesisAccount.Address)) - copy(addressClone, genesisAccount.Address) - // clone the account permissions - accountPermissionsClone := genesisAccount.Permissions.Clone() - return GenesisAccount{ - Address: addressClone, - Amount: genesisAccount.Amount, - Name: genesisAccount.Name, - Permissions: &accountPermissionsClone, - } -} - -//------------------------------------------------------------ -// GenesisValidator methods - -// Clone clones the genesis validator -func (genesisValidator *GenesisValidator) Clone() (GenesisValidator, error) { - if genesisValidator == nil { - return GenesisValidator{}, fmt.Errorf("Cannot clone nil GenesisValidator.") - } - if genesisValidator.PubKey == nil { - return GenesisValidator{}, fmt.Errorf("Invalid GenesisValidator %s with nil public key.", - genesisValidator.Name) - } - // clone the addresses to unbond to - unbondToClone := make([]BasicAccount, len(genesisValidator.UnbondTo)) - for i, basicAccount := range genesisValidator.UnbondTo { - unbondToClone[i] = basicAccount.Clone() - } - return GenesisValidator{ - PubKey: genesisValidator.PubKey, - Amount: genesisValidator.Amount, - Name: genesisValidator.Name, - UnbondTo: unbondToClone, - }, nil -} - -//------------------------------------------------------------ -// BasicAccount methods - -// Clone clones the basic account -func (basicAccount *BasicAccount) Clone() BasicAccount { - // clone the address - addressClone := make([]byte, len(basicAccount.Address)) - copy(addressClone, basicAccount.Address) - return BasicAccount{ - Address: addressClone, - Amount: basicAccount.Amount, - } -}