diff --git a/account/account.go b/account/account.go index 51d5027a..fb65ecd6 100644 --- a/account/account.go +++ b/account/account.go @@ -36,16 +36,6 @@ type Account struct { Balance uint64 } -func NewAccount(pubKey PubKey) *Account { - address := pubKey.Address() - return &Account{ - Address: address, - PubKey: pubKey, - Sequence: uint(0), - Balance: uint64(0), - } -} - func (account *Account) Copy() *Account { accountCopy := *account return &accountCopy diff --git a/account/privkey.go b/account/privkey.go index f555a3b7..b1b49205 100644 --- a/account/privkey.go +++ b/account/privkey.go @@ -26,7 +26,7 @@ const ( func PrivKeyDecoder(r io.Reader, n *int64, err *error) interface{} { switch t := ReadByte(r, n, err); t { case PrivKeyTypeEd25519: - return ReadBinary(&PrivKeyEd25519{}, r, n, err) + return ReadBinary(PrivKeyEd25519{}, r, n, err) default: *err = Errorf("Unknown PrivKey type %X", t) return nil diff --git a/account/pubkey.go b/account/pubkey.go index 3c1f1cae..0d65e4c6 100644 --- a/account/pubkey.go +++ b/account/pubkey.go @@ -18,8 +18,9 @@ type PubKey interface { // Types of PubKey implementations const ( - PubKeyTypeUnknown = byte(0x00) // For pay-to-pubkey-hash txs. - PubKeyTypeEd25519 = byte(0x01) + PubKeyTypeNil = byte(0x00) + PubKeyTypeUnknown = byte(0x01) // For pay-to-pubkey-hash txs. + PubKeyTypeEd25519 = byte(0x02) ) //------------------------------------- @@ -27,10 +28,10 @@ const ( func PubKeyDecoder(r io.Reader, n *int64, err *error) interface{} { switch t := ReadByte(r, n, err); t { - case PubKeyTypeUnknown: - return PubKeyUnknown{} + case PubKeyTypeNil: + return PubKeyNil{} case PubKeyTypeEd25519: - return ReadBinary(&PubKeyEd25519{}, r, n, err) + return ReadBinary(PubKeyEd25519{}, r, n, err) default: *err = Errorf("Unknown PubKey type %X", t) return nil @@ -45,22 +46,16 @@ var _ = RegisterType(&TypeInfo{ //------------------------------------- // Implements PubKey -// For pay-to-pubkey-hash txs, where the TxOutput PubKey -// is not known in advance, only its hash (address). -type PubKeyUnknown struct { - address []byte +type PubKeyNil struct{} + +func (key PubKeyNil) TypeByte() byte { return PubKeyTypeNil } + +func (key PubKeyNil) Address() []byte { + panic("PubKeyNil has no address") } -func NewPubKeyUnknown(address []byte) PubKeyUnknown { return PubKeyUnknown{address} } - -func (key PubKeyUnknown) TypeByte() byte { return PubKeyTypeUnknown } - -func (key PubKeyUnknown) Address() []byte { - return key.address -} - -func (key PubKeyUnknown) VerifyBytes(msg []byte, sig_ Signature) bool { - panic("PubKeyUnknown cannot verify messages") +func (key PubKeyNil) VerifyBytes(msg []byte, sig_ Signature) bool { + panic("PubKeyNil cannot verify messages") } //------------------------------------- diff --git a/account/signature.go b/account/signature.go index aca855ad..7b9d6b5f 100644 --- a/account/signature.go +++ b/account/signature.go @@ -25,7 +25,7 @@ const ( func SignatureDecoder(r io.Reader, n *int64, err *error) interface{} { switch t := ReadByte(r, n, err); t { case SignatureTypeEd25519: - return ReadBinary(&SignatureEd25519{}, r, n, err) + return ReadBinary(SignatureEd25519{}, r, n, err) default: *err = Errorf("Unknown Signature type %X", t) return nil diff --git a/binary/int.go b/binary/int.go index cf2ee2db..a63c9bb6 100644 --- a/binary/int.go +++ b/binary/int.go @@ -159,7 +159,7 @@ func ReadUint64(r io.Reader, n *int64, err *error) uint64 { // Varint func WriteVarint(i int, w io.Writer, n *int64, err *error) { - buf := make([]byte, 9) + buf := make([]byte, binary.MaxVarintLen64) n_ := int64(binary.PutVarint(buf, int64(i))) *n += n_ WriteTo(buf[:n_], w, n, err) @@ -175,7 +175,7 @@ func ReadVarint(r io.Reader, n *int64, err *error) int { // Uvarint func WriteUvarint(i uint, w io.Writer, n *int64, err *error) { - buf := make([]byte, 9) + buf := make([]byte, binary.MaxVarintLen64) n_ := int64(binary.PutUvarint(buf, uint64(i))) *n += n_ WriteTo(buf[:n_], w, n, err) diff --git a/block/tx.go b/block/tx.go index b483eb9a..8739d00b 100644 --- a/block/tx.go +++ b/block/tx.go @@ -15,6 +15,9 @@ var ( ErrTxDuplicateAddress = errors.New("Error duplicate address") ErrTxInvalidAmount = errors.New("Error invalid amount") ErrTxInsufficientFunds = errors.New("Error insufficient funds") + ErrTxUnknownPubKey = errors.New("Error unknown pubkey") + ErrTxInvalidPubKey = errors.New("Error invalid pubkey") + ErrTxRedeclaredPubKey = errors.New("Error redeclared pubkey") ErrTxInvalidSignature = errors.New("Error invalid signature") ErrTxInvalidSequence = errors.New("Error invalid sequence") ) @@ -79,6 +82,7 @@ type TxInput struct { Amount uint64 // Must not exceed account balance Sequence uint // Must be 1 greater than the last committed TxInput Signature Signature // Depends on the PubKey type and the whole Tx + PubKey PubKey // Optional, may be nil } func (txIn *TxInput) ValidateBasic() error { diff --git a/cmd/daemon.go b/cmd/daemon.go index 68940675..d4c38aff 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -26,11 +26,11 @@ type Node struct { func NewNode() *Node { // Get BlockStore - blockStoreDB := db_.NewMemDB() // TODO configurable db. + blockStoreDB := db_.GetDB("blockstore") blockStore := block.NewBlockStore(blockStoreDB) // Get State - stateDB := db_.NewMemDB() // TODO configurable db. + stateDB := db_.GetDB("state") state := state_.LoadState(stateDB) if state == nil { state = state_.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) diff --git a/cmd/gen_validator.go b/cmd/gen_validator.go index 1ae785c5..8145bcea 100644 --- a/cmd/gen_validator.go +++ b/cmd/gen_validator.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "os" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/state" @@ -10,14 +9,15 @@ import ( func gen_validator() { - // If already exists, bail out. - filename := config.PrivValidatorFile() - if _, err := os.Stat(filename); !os.IsNotExist(err) { - fmt.Printf("Cannot generate new validator, file already exists at %v\n", filename) - } - - // Generate private validator privValidator := state.GenPrivValidator() - privValidator.Save() - fmt.Printf("Generated a new validator at %v\n", filename) + privValidatorJSONBytes := privValidator.JSONBytes() + fmt.Printf(`Generated a new validator! +Paste the following JSON into your %v file + +%v + +`, + config.PrivValidatorFile(), + string(privValidatorJSONBytes), + ) } diff --git a/common/cmap.go b/common/cmap.go index 421b9c54..5de6fa2f 100644 --- a/common/cmap.go +++ b/common/cmap.go @@ -2,9 +2,7 @@ package common import "sync" -/* -CMap is a threadsafe map -*/ +// CMap is a goroutine-safe map type CMap struct { m map[string]interface{} l sync.Mutex diff --git a/common/panic.go b/common/panic.go index 8621f163..139f32a2 100644 --- a/common/panic.go +++ b/common/panic.go @@ -2,8 +2,14 @@ package common import ( "fmt" + "os" ) func Panicf(s string, args ...interface{}) { panic(fmt.Sprintf(s, args...)) } + +func Exitf(s string, args ...interface{}) { + fmt.Printf(s+"\n", args...) + os.Exit(1) +} diff --git a/config/config.go b/config/config.go index b8743e41..8131276c 100644 --- a/config/config.go +++ b/config/config.go @@ -6,19 +6,117 @@ import ( "flag" "fmt" "io/ioutil" - "log" "os" "path/filepath" "strings" + + . "github.com/tendermint/tendermint/common" ) +//-----------------------------------------------------------------------------j +// Configuration types + +type ConfigType struct { + Network string + LAddr string + SeedNode string + DB DBConfig + Alert AlertConfig + SMTP SMTPConfig + RPC RPCConfig +} + +type DBConfig struct { + Backend string + Dir string +} + +type AlertConfig struct { + MinInterval int + + TwilioSid string + TwilioToken string + TwilioFrom string + TwilioTo string + + EmailRecipients []string +} + +type SMTPConfig struct { + User string + Password string + Host string + Port uint +} + +type RPCConfig struct { + HTTPPort uint +} + +func (cfg *ConfigType) validate() error { + if cfg.Network == "" { + cfg.Network = defaultConfig.Network + } + if cfg.LAddr == "" { + cfg.LAddr = defaultConfig.LAddr + } + if cfg.SeedNode == "" { + cfg.SeedNode = defaultConfig.SeedNode + } + if cfg.DB.Backend == "" { + return errors.New("DB.Backend must be set") + } + return nil +} + +func (cfg *ConfigType) bytes() []byte { + configBytes, err := json.MarshalIndent(cfg, "", "\t") + if err != nil { + panic(err) + } + return configBytes +} + +func (cfg *ConfigType) write(configFile string) { + if strings.Index(configFile, "/") != -1 { + err := os.MkdirAll(filepath.Dir(configFile), 0700) + if err != nil { + panic(err) + } + } + err := ioutil.WriteFile(configFile, cfg.bytes(), 0600) + if err != nil { + panic(err) + } +} + +//----------------------------------------------------------------------------- + var rootDir string +var defaultConfig ConfigType func init() { + // Get RootDir rootDir = os.Getenv("TMROOT") if rootDir == "" { rootDir = os.Getenv("HOME") + "/.tendermint" } + + // Compute defaultConfig + defaultConfig = ConfigType{ + Network: "tendermint_testnet0", + LAddr: "0.0.0.0:0", + SeedNode: "", + DB: DBConfig{ + Backend: "leveldb", + Dir: DataDir(), + }, + Alert: AlertConfig{}, + SMTP: SMTPConfig{}, + RPC: RPCConfig{ + HTTPPort: 8888, + }, + } } func ConfigFile() string { return rootDir + "/config.json" } @@ -51,11 +149,11 @@ func ParseFlags() { Config = ConfigType{} err = json.Unmarshal(configBytes, &Config) if err != nil { - log.Panicf("Invalid configuration file %s: %v", configFile, err) + Exitf("Invalid configuration file %s: %v", configFile, err) } err = Config.validate() if err != nil { - log.Panicf("Invalid configuration file %s: %v", configFile, err) + Exitf("Invalid configuration file %s: %v", configFile, err) } // try to parse arg flags, which can override file configuration. @@ -67,100 +165,3 @@ func ParseFlags() { os.Exit(0) } } - -//-----------------------------------------------------------------------------j -// Default configuration - -var defaultConfig = ConfigType{ - Network: "tendermint_testnet0", - LAddr: "0.0.0.0:0", - SeedNode: "", - Db: DbConfig{ - Type: "level", - Dir: DataDir(), - }, - Alert: AlertConfig{}, - SMTP: SMTPConfig{}, - RPC: RPCConfig{ - HTTPPort: 8888, - }, -} - -//-----------------------------------------------------------------------------j -// Configuration types - -type ConfigType struct { - Network string - LAddr string - SeedNode string - Db DbConfig - Alert AlertConfig - SMTP SMTPConfig - RPC RPCConfig -} - -type DbConfig struct { - Type string - Dir string -} - -type AlertConfig struct { - MinInterval int - - TwilioSid string - TwilioToken string - TwilioFrom string - TwilioTo string - - EmailRecipients []string -} - -type SMTPConfig struct { - User string - Password string - Host string - Port uint -} - -type RPCConfig struct { - HTTPPort uint -} - -//-----------------------------------------------------------------------------j - -func (cfg *ConfigType) validate() error { - if cfg.Network == "" { - cfg.Network = defaultConfig.Network - } - if cfg.LAddr == "" { - cfg.LAddr = defaultConfig.LAddr - } - if cfg.SeedNode == "" { - cfg.SeedNode = defaultConfig.SeedNode - } - if cfg.Db.Type == "" { - return errors.New("Db.Type must be set") - } - return nil -} - -func (cfg *ConfigType) bytes() []byte { - configBytes, err := json.MarshalIndent(cfg, "", "\t") - if err != nil { - panic(err) - } - return configBytes -} - -func (cfg *ConfigType) write(configFile string) { - if strings.Index(configFile, "/") != -1 { - err := os.MkdirAll(filepath.Dir(configFile), 0700) - if err != nil { - panic(err) - } - } - err := ioutil.WriteFile(configFile, cfg.bytes(), 0600) - if err != nil { - panic(err) - } -} diff --git a/db/db.go b/db/db.go index 122f88a9..011a8a60 100644 --- a/db/db.go +++ b/db/db.go @@ -1,5 +1,12 @@ package db +import ( + "path" + + . "github.com/tendermint/tendermint/common" + . "github.com/tendermint/tendermint/config" +) + type DB interface { Get([]byte) []byte Set([]byte, []byte) @@ -7,3 +14,33 @@ type DB interface { Delete([]byte) DeleteSync([]byte) } + +//----------------------------------------------------------------------------- + +// Database types +const DBBackendMemDB = "memdb" +const DBBackendLevelDB = "leveldb" + +var dbs = NewCMap() + +func GetDB(name string) DB { + db := dbs.Get(name) + if db != nil { + return db.(DB) + } + switch Config.DB.Backend { + case DBBackendMemDB: + db := NewMemDB() + dbs.Set(name, db) + return db + case DBBackendLevelDB: + db, err := NewLevelDB(path.Join(Config.DB.Dir, name+".db")) + if err != nil { + panic(err) + } + dbs.Set(name, db) + return db + default: + panic("Unknown DB backend") + } +} diff --git a/db/level_db.go b/db/level_db.go index aa9a222d..4d3727d1 100644 --- a/db/level_db.go +++ b/db/level_db.go @@ -3,6 +3,7 @@ package db import ( "fmt" "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/opt" "path" ) @@ -24,7 +25,11 @@ func NewLevelDB(name string) (*LevelDB, error) { func (db *LevelDB) Get(key []byte) []byte { res, err := db.db.Get(key, nil) if err != nil { - panic(err) + if err == errors.ErrNotFound { + return nil + } else { + panic(err) + } } return res } @@ -57,7 +62,7 @@ func (db *LevelDB) DeleteSync(key []byte) { } } -func (db *LevelDB) Db() *leveldb.DB { +func (db *LevelDB) DB() *leveldb.DB { return db.db } diff --git a/state/genesis.go b/state/genesis.go index 56b3b6f4..49b7c022 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -1,6 +1,8 @@ package state import ( + "bytes" + "encoding/base64" "encoding/json" "io/ioutil" "time" @@ -13,10 +15,21 @@ import ( "github.com/tendermint/tendermint/merkle" ) +type GenesisAccount struct { + Address string + Amount uint64 +} + +type GenesisValidator struct { + PubKey string + Amount uint64 + UnbondTo []GenesisAccount +} + type GenesisDoc struct { GenesisTime time.Time - Accounts []*Account - Validators []*ValidatorInfo + Accounts []GenesisAccount + Validators []GenesisValidator } func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) { @@ -38,7 +51,7 @@ func MakeGenesisStateFromFile(db db_.DB, genDocFile string) *State { func MakeGenesisState(db db_.DB, genDoc *GenesisDoc) *State { if len(genDoc.Validators) == 0 { - panic("Must have some validators") + Exitf("The genesis file has no validators") } if genDoc.GenesisTime.IsZero() { @@ -48,22 +61,59 @@ func MakeGenesisState(db db_.DB, genDoc *GenesisDoc) *State { // Make accounts state tree accounts := merkle.NewIAVLTree(BasicCodec, AccountCodec, defaultAccountsCacheCapacity, db) for _, acc := range genDoc.Accounts { - accounts.Set(acc.Address, acc) + address, err := base64.StdEncoding.DecodeString(acc.Address) + if err != nil { + Exitf("Invalid account address: %v", acc.Address) + } + account := &Account{ + Address: address, + PubKey: PubKeyNil{}, + Sequence: 0, + Balance: acc.Amount, + } + accounts.Set(address, account) } - // Make validatorInfos state tree + // Make validatorInfos state tree && validators slice validatorInfos := merkle.NewIAVLTree(BasicCodec, ValidatorInfoCodec, 0, db) - for _, valInfo := range genDoc.Validators { - validatorInfos.Set(valInfo.Address, valInfo) - } - - // Make validators validators := make([]*Validator, len(genDoc.Validators)) - for i, valInfo := range genDoc.Validators { + for i, val := range genDoc.Validators { + pubKeyBytes, err := base64.StdEncoding.DecodeString(val.PubKey) + if err != nil { + Exitf("Invalid validator pubkey: %v", val.PubKey) + } + pubKey := ReadBinary(PubKeyEd25519{}, + bytes.NewBuffer(pubKeyBytes), new(int64), &err).(PubKeyEd25519) + if err != nil { + Exitf("Invalid validator pubkey: %v", val.PubKey) + } + address := pubKey.Address() + + // Make ValidatorInfo + valInfo := &ValidatorInfo{ + Address: address, + PubKey: pubKey, + UnbondTo: make([]*TxOutput, len(val.UnbondTo)), + FirstBondHeight: 0, + FirstBondAmount: val.Amount, + } + for i, unbondTo := range val.UnbondTo { + address, err := base64.StdEncoding.DecodeString(unbondTo.Address) + if err != nil { + Exitf("Invalid unbond-to address: %v", unbondTo.Address) + } + valInfo.UnbondTo[i] = &TxOutput{ + Address: address, + Amount: unbondTo.Amount, + } + } + validatorInfos.Set(address, valInfo) + + // Make validator validators[i] = &Validator{ - Address: valInfo.Address, - PubKey: valInfo.PubKey, - VotingPower: valInfo.FirstBondAmount, + Address: address, + PubKey: pubKey, + VotingPower: val.Amount, } } diff --git a/state/priv_validator.go b/state/priv_validator.go index 4ab3f1d1..8272959a 100644 --- a/state/priv_validator.go +++ b/state/priv_validator.go @@ -126,6 +126,14 @@ func (privVal *PrivValidator) Save() { } func (privVal *PrivValidator) save() { + jsonBytes := privVal.JSONBytes() + err := ioutil.WriteFile(privVal.filename, jsonBytes, 0700) + if err != nil { + panic(err) + } +} + +func (privVal *PrivValidator) JSONBytes() []byte { privValJSON := PrivValidatorJSON{ Address: base64.StdEncoding.EncodeToString(privVal.Address), PubKey: base64.StdEncoding.EncodeToString(BinaryBytes(privVal.PubKey)), @@ -134,14 +142,11 @@ func (privVal *PrivValidator) save() { LastRound: privVal.LastRound, LastStep: privVal.LastStep, } - privValJSONBytes, err := json.Marshal(privValJSON) - if err != nil { - panic(err) - } - err = ioutil.WriteFile(privVal.filename, privValJSONBytes, 0700) + privValJSONBytes, err := json.MarshalIndent(privValJSON, "", " ") if err != nil { panic(err) } + return privValJSONBytes } // TODO: test diff --git a/state/state.go b/state/state.go index 5406a31b..64411912 100644 --- a/state/state.go +++ b/state/state.go @@ -108,6 +108,9 @@ func (s *State) Copy() *State { } } +// The accounts from the TxInputs must either already have +// account.PubKey.(type) != PubKeyNil, (it must be known), +// or it must be specified in the TxInput. But not both. func (s *State) GetOrMakeAccounts(ins []*TxInput, outs []*TxOutput) (map[string]*Account, error) { accounts := map[string]*Account{} for _, in := range ins { @@ -119,6 +122,20 @@ func (s *State) GetOrMakeAccounts(ins []*TxInput, outs []*TxOutput) (map[string] if account == nil { return nil, ErrTxInvalidAddress } + // PubKey should be present in either "account" or "in" + if _, isNil := account.PubKey.(PubKeyNil); isNil { + if _, isNil := in.PubKey.(PubKeyNil); isNil { + return nil, ErrTxUnknownPubKey + } + if !bytes.Equal(in.PubKey.Address(), account.Address) { + return nil, ErrTxInvalidPubKey + } + account.PubKey = in.PubKey + } else { + if _, isNil := in.PubKey.(PubKeyNil); !isNil { + return nil, ErrTxRedeclaredPubKey + } + } accounts[string(in.Address)] = account } for _, out := range outs { @@ -129,7 +146,12 @@ func (s *State) GetOrMakeAccounts(ins []*TxInput, outs []*TxOutput) (map[string] account := s.GetAccount(out.Address) // output account may be nil (new) if account == nil { - account = NewAccount(NewPubKeyUnknown(out.Address)) + account = &Account{ + Address: out.Address, + PubKey: PubKeyNil{}, + Sequence: 0, + Balance: 0, + } } accounts[string(out.Address)] = account } diff --git a/state/test.go b/state/test.go index affef40d..2994082d 100644 --- a/state/test.go +++ b/state/test.go @@ -2,9 +2,11 @@ package state import ( "bytes" + "encoding/base64" "sort" . "github.com/tendermint/tendermint/account" + . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/block" . "github.com/tendermint/tendermint/common" db_ "github.com/tendermint/tendermint/db" @@ -24,9 +26,12 @@ func Tempfile(prefix string) (*os.File, string) { func RandAccount(randBalance bool, minBalance uint64) (*Account, *PrivAccount) { privAccount := GenPrivAccount() - account := NewAccount(privAccount.PubKey) - account.Sequence = RandUint() - account.Balance = minBalance + account := &Account{ + Address: privAccount.PubKey.Address(), + PubKey: privAccount.PubKey, + Sequence: RandUint(), + Balance: minBalance, + } if randBalance { account.Balance += uint64(RandUint32()) } @@ -53,20 +58,32 @@ func RandValidator(randBonded bool, minBonded uint64) (*ValidatorInfo, *PrivVali return valInfo, privVal } -// The first numValidators accounts are validators. func RandGenesisState(numAccounts int, randBalance bool, minBalance uint64, numValidators int, randBonded bool, minBonded uint64) (*State, []*PrivAccount, []*PrivValidator) { db := db_.NewMemDB() - accounts := make([]*Account, numAccounts) + accounts := make([]GenesisAccount, numAccounts) privAccounts := make([]*PrivAccount, numAccounts) for i := 0; i < numAccounts; i++ { account, privAccount := RandAccount(randBalance, minBalance) - accounts[i], privAccounts[i] = account, privAccount + accounts[i] = GenesisAccount{ + Address: base64.StdEncoding.EncodeToString(account.Address), + Amount: account.Balance, + } + privAccounts[i] = privAccount } - validators := make([]*ValidatorInfo, numValidators) + validators := make([]GenesisValidator, numValidators) privValidators := make([]*PrivValidator, numValidators) for i := 0; i < numValidators; i++ { valInfo, privVal := RandValidator(randBonded, minBonded) - validators[i] = valInfo + validators[i] = GenesisValidator{ + PubKey: base64.StdEncoding.EncodeToString(BinaryBytes(valInfo.PubKey)), + Amount: valInfo.FirstBondAmount, + UnbondTo: []GenesisAccount{ + { + Address: base64.StdEncoding.EncodeToString(valInfo.PubKey.Address()), + Amount: valInfo.FirstBondAmount, + }, + }, + } privValidators[i] = privVal } sort.Sort(PrivValidatorsByAddress(privValidators))