diff --git a/cmd/cmd.go b/cmd/cmd.go index 024934e27..e0ee63e4a 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -18,6 +18,7 @@ import ( "github.com/pactus-project/pactus/config" "github.com/pactus-project/pactus/crypto" "github.com/pactus-project/pactus/crypto/bls" + "github.com/pactus-project/pactus/crypto/bls/hdkeychain" "github.com/pactus-project/pactus/genesis" "github.com/pactus-project/pactus/node" "github.com/pactus-project/pactus/types/account" @@ -25,6 +26,7 @@ import ( "github.com/pactus-project/pactus/types/validator" "github.com/pactus-project/pactus/util" "github.com/pactus-project/pactus/wallet" + "github.com/pactus-project/pactus/wallet/addresspath" ) // terminalSupported returns true if the current terminal supports @@ -290,34 +292,29 @@ func CreateNode(numValidators int, chain genesis.ChainType, workingDir string, case genesis.Mainnet: panic("not yet!") case genesis.Testnet: - err = genesis.TestnetGenesis().SaveToFile(genPath) - if err != nil { + if err := genesis.TestnetGenesis().SaveToFile(genPath); err != nil { return nil, nil, err } - err = config.SaveTestnetConfig(confPath, numValidators) - if err != nil { + if err := config.SaveTestnetConfig(confPath); err != nil { return nil, nil, err } + case genesis.Localnet: - err = makeLocalGenesis(*walletInstance).SaveToFile(genPath) - if err != nil { + if err := makeLocalGenesis(*walletInstance).SaveToFile(genPath); err != nil { return nil, nil, err } - err := config.SaveLocalnetConfig(confPath, numValidators) - if err != nil { + if err := config.SaveLocalnetConfig(confPath); err != nil { return nil, nil, err } } - err = walletInstance.UpdatePassword("", walletPassword) - if err != nil { + if err := walletInstance.UpdatePassword("", walletPassword); err != nil { return nil, nil, err } - err = walletInstance.Save() - if err != nil { + if err := walletInstance.Save(); err != nil { return nil, nil, err } @@ -356,20 +353,28 @@ func StartNode(workingDir string, passwordFetcher func(*wallet.Wallet) (string, if err != nil { return nil, nil, err } - addrLabels := walletInstance.AddressInfos() + allValAddrs := walletInstance.AllValidatorAddresses() + + if len(allValAddrs) < 1 || len(allValAddrs) > 32 { + return nil, nil, fmt.Errorf("number of validators must be between 1 and 32, but it's %d", + len(allValAddrs)) + } - if len(addrLabels) < conf.Node.NumValidators { - return nil, nil, fmt.Errorf("not enough addresses in wallet") + if len(conf.Node.RewardAddresses) > 0 && + len(conf.Node.RewardAddresses) != len(allValAddrs) { + return nil, nil, fmt.Errorf("reward addresses should be %v", len(allValAddrs)) } - validatorAddrs := make([]string, conf.Node.NumValidators) - for i := 0; i < conf.Node.NumValidators; i++ { - valAddr, _ := crypto.AddressFromString(addrLabels[i].Address) + + validatorAddrs := make([]string, len(allValAddrs)) + for i := 0; i < len(validatorAddrs); i++ { + valAddr, _ := crypto.AddressFromString(allValAddrs[i].Address) if !valAddr.IsValidatorAddress() { - return nil, nil, fmt.Errorf("invalid validator address: %s", addrLabels[i].Address) + return nil, nil, fmt.Errorf("invalid validator address: %s", allValAddrs[i].Address) } validatorAddrs[i] = valAddr.String() } - valKeys := make([]*bls.ValidatorKey, conf.Node.NumValidators) + + valKeys := make([]*bls.ValidatorKey, len(allValAddrs)) password, ok := passwordFetcher(walletInstance) if !ok { return nil, nil, fmt.Errorf("aborted") @@ -383,26 +388,27 @@ func StartNode(workingDir string, passwordFetcher func(*wallet.Wallet) (string, } // Create reward addresses - rewardAddrs := make([]crypto.Address, 0, conf.Node.NumValidators) + rewardAddrs := make([]crypto.Address, 0, len(allValAddrs)) if len(conf.Node.RewardAddresses) != 0 { for _, addrStr := range conf.Node.RewardAddresses { addr, _ := crypto.AddressFromString(addrStr) rewardAddrs = append(rewardAddrs, addr) } } else { - for i := conf.Node.NumValidators; i < len(addrLabels); i++ { - addr, _ := crypto.AddressFromString(addrLabels[i].Address) - if addr.IsAccountAddress() { - rewardAddrs = append(rewardAddrs, addr) - if len(rewardAddrs) == conf.Node.NumValidators { - break - } + for i := 0; i < len(allValAddrs); i++ { + valAddrPath, _ := addresspath.NewPathFromString(allValAddrs[i].Path) + accAddrPath := addresspath.NewPath(valAddrPath.Purpose(), valAddrPath.CoinType(), + uint32(crypto.AddressTypeValidator)+hdkeychain.HardenedKeyStart, valAddrPath.AddressIndex()) + + addrInfo := walletInstance.AddressFromPath(accAddrPath.String()) + if addrInfo == nil { + return nil, nil, fmt.Errorf("unable to find reward address for: %s", allValAddrs[i].Address) } + + addr, _ := crypto.AddressFromString(addrInfo.Address) + rewardAddrs = append(rewardAddrs, addr) } } - if len(rewardAddrs) != conf.Node.NumValidators { - return nil, nil, fmt.Errorf("not enough addresses in wallet") - } nodeInstance, err := node.NewNode(gen, conf, valKeys, rewardAddrs) if err != nil { @@ -456,17 +462,13 @@ func tryLoadConfig(chainType genesis.ChainType, confPath string) (*config.Config PrintWarnMsgf("Unable to load the config: %s", err) PrintInfoMsgf("Attempting to restore the config to the default values...") - // First, try to open the old config file in non-strict mode - confBack, err := config.LoadFromFile(confPath, false, defConf) - if err != nil { - return nil, err - } - - // Let's create a backup of the config - confBackupPath := fmt.Sprintf("%v_bak_%s", confPath, time.Now().Format("2006_01_02")) - err = os.Rename(confPath, confBackupPath) - if err != nil { - return nil, err + if util.PathExists(confPath) { + // Let's create a backup of the config + confBackupPath := fmt.Sprintf("%v_bak_%s", confPath, time.Now().Format("2006-01-02T15:04:05")) + err = os.Rename(confPath, confBackupPath) + if err != nil { + return nil, err + } } // Now, attempt to restore the config file with the number of validators from the old config. @@ -475,13 +477,13 @@ func tryLoadConfig(chainType genesis.ChainType, confPath string) (*config.Config panic("not yet implemented!") case genesis.Testnet: - err = config.SaveTestnetConfig(confPath, confBack.Node.NumValidators) + err = config.SaveTestnetConfig(confPath) if err != nil { return nil, err } case genesis.Localnet: - err = config.SaveLocalnetConfig(confPath, confBack.Node.NumValidators) + err = config.SaveLocalnetConfig(confPath) if err != nil { return nil, err } diff --git a/cmd/wallet/address.go b/cmd/wallet/address.go index c93e211f1..a41126f68 100644 --- a/cmd/wallet/address.go +++ b/cmd/wallet/address.go @@ -60,10 +60,6 @@ func buildAllAddressesCmd(parentCmd *cobra.Command) { } line += info.Label - if info.Path == "" { - line += " (Imported)" - } - cmd.PrintInfoMsgf(line) } } diff --git a/config/config.go b/config/config.go index aed17aacf..8e97c952a 100644 --- a/config/config.go +++ b/config/config.go @@ -39,28 +39,16 @@ type Config struct { } type NodeConfig struct { - NumValidators int `toml:"num_validators"` // TODO: we can remove this now RewardAddresses []string `toml:"reward_addresses"` } func DefaultNodeConfig() *NodeConfig { // TODO: We should have default config per network: Testnet, Mainnet. - return &NodeConfig{ - NumValidators: 7, - } + return &NodeConfig{} } // BasicCheck performs basic checks on the configuration. func (conf *NodeConfig) BasicCheck() error { - if conf.NumValidators < 1 || conf.NumValidators > 32 { - return errors.Errorf(errors.ErrInvalidConfig, "number of validators must be between 1 and 32") - } - - if len(conf.RewardAddresses) > 0 && - len(conf.RewardAddresses) != conf.NumValidators { - return errors.Errorf(errors.ErrInvalidConfig, "reward addresses should be %v", conf.NumValidators) - } - for _, addrStr := range conf.RewardAddresses { addr, err := crypto.AddressFromString(addrStr) if err != nil { @@ -171,15 +159,13 @@ func SaveMainnetConfig(path string, numValidators int) error { return util.WriteFile(path, []byte(conf)) } -func SaveTestnetConfig(path string, numValidators int) error { +func SaveTestnetConfig(path string) error { conf := DefaultConfigTestnet() - conf.Node.NumValidators = numValidators return util.WriteFile(path, conf.toTOML()) } -func SaveLocalnetConfig(path string, numValidators int) error { +func SaveLocalnetConfig(path string) error { conf := DefaultConfigLocalnet() - conf.Node.NumValidators = numValidators return util.WriteFile(path, conf.toTOML()) } diff --git a/config/config_test.go b/config/config_test.go index e8b9d4fea..9c73407b0 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -22,7 +22,7 @@ func TestSaveMainnetConfig(t *testing.T) { func TestSaveTestnetConfig(t *testing.T) { path := util.TempFilePath() - assert.NoError(t, SaveTestnetConfig(path, 7)) + assert.NoError(t, SaveTestnetConfig(path)) defConf := DefaultConfigTestnet() conf, err := LoadFromFile(path, true, defConf) @@ -35,7 +35,7 @@ func TestSaveTestnetConfig(t *testing.T) { func TestSaveLocalnetConfig(t *testing.T) { path := util.TempFilePath() - assert.NoError(t, SaveLocalnetConfig(path, 4)) + assert.NoError(t, SaveLocalnetConfig(path)) defConf := DefaultConfigLocalnet() conf, err := LoadFromFile(path, true, defConf) @@ -80,7 +80,6 @@ func TestExampleConfig(t *testing.T) { defaultConf := DefaultConfigMainnet() defaultToml := string(defaultConf.toTOML()) - exampleToml = strings.ReplaceAll(exampleToml, "%num_validators%", "7") exampleToml = strings.ReplaceAll(exampleToml, "##", "") exampleToml = strings.ReplaceAll(exampleToml, "\r\n", "\n") // For Windows exampleToml = strings.ReplaceAll(exampleToml, "\n\n", "\n") @@ -92,25 +91,8 @@ func TestExampleConfig(t *testing.T) { func TestNodeConfigBasicCheck(t *testing.T) { ts := testsuite.NewTestSuite(t) - t.Run("invalid number of validators", func(t *testing.T) { - conf := DefaultNodeConfig() - conf.NumValidators = 0 - - assert.Error(t, conf.BasicCheck()) - }) - - t.Run("invalid number of reward addresses", func(t *testing.T) { - conf := DefaultNodeConfig() - conf.RewardAddresses = []string{ - ts.RandAccAddress().String(), - } - - assert.Error(t, conf.BasicCheck()) - }) - t.Run("invalid reward addresses", func(t *testing.T) { conf := DefaultNodeConfig() - conf.NumValidators = 2 conf.RewardAddresses = []string{ ts.RandAccAddress().String(), "abcd", @@ -121,7 +103,6 @@ func TestNodeConfigBasicCheck(t *testing.T) { t.Run("validator address as reward address", func(t *testing.T) { conf := DefaultNodeConfig() - conf.NumValidators = 1 conf.RewardAddresses = []string{ ts.RandValAddress().String(), } @@ -131,7 +112,6 @@ func TestNodeConfigBasicCheck(t *testing.T) { t.Run("ok", func(t *testing.T) { conf := DefaultNodeConfig() - conf.NumValidators = 2 conf.RewardAddresses = []string{ ts.RandAccAddress().String(), ts.RandAccAddress().String(), @@ -142,7 +122,6 @@ func TestNodeConfigBasicCheck(t *testing.T) { t.Run("no reward addresses inside config, Ok", func(t *testing.T) { conf := DefaultNodeConfig() - conf.NumValidators = 2 conf.RewardAddresses = []string{} assert.NoError(t, conf.BasicCheck()) diff --git a/config/example_config.toml b/config/example_config.toml index aefc12917..5bf955b27 100644 --- a/config/example_config.toml +++ b/config/example_config.toml @@ -3,9 +3,6 @@ # `node` contains configuration options for the Pactus node. [node] - # `num_validators` specifies the number of validators (consensus instances) to run on this node. - num_validators = %num_validators% - # `reward_addresses` specifies the addresses for collecting rewards. # If it is empty, reward addresses will be obtained from the wallet. # The number of reward addresses should be the same as the number of validators. diff --git a/wallet/vault/vault.go b/wallet/vault/vault.go index dbc973a7c..436b354b7 100644 --- a/wallet/vault/vault.go +++ b/wallet/vault/vault.go @@ -1,9 +1,9 @@ package vault import ( + "cmp" "encoding/json" - "sort" - "strings" + "fmt" "github.com/pactus-project/pactus/crypto" "github.com/pactus-project/pactus/crypto/bls" @@ -206,46 +206,90 @@ func (v *Vault) SetLabel(addr, label string) error { } func (v *Vault) AddressInfos() []AddressInfo { - importedAddrs := make([]AddressInfo, 0) - addrs := make([]AddressInfo, 0, v.AddressCount()) - addrsMap := make(map[int][]AddressInfo) - - for _, info := range v.Addresses { - if strings.TrimSpace(info.Path) == "" { - importedAddrs = append(importedAddrs, info) - continue - } + addrs := make([]AddressInfo, 0, 1) + for _, addrInfo := range v.Addresses { + addrs = append(addrs, addrInfo) + } + + v.SortAddressesByAddressIndex(addrs...) + v.SortAddressesByAddressType(addrs...) + v.SortAddressesByPurpose(addrs...) - path, _ := addresspath.NewPathFromString(info.Path) - addressType := path.AddressType() - addrsMap[int(addressType)] = append(addrsMap[int(addressType)], info) + return addrs +} + +func (v *Vault) AllValidatorAddresses() []AddressInfo { + addrs := make([]AddressInfo, 0, v.AddressCount()/2) + for _, addrInfo := range v.Addresses { + addrPath, _ := addresspath.NewPathFromString(addrInfo.Path) + if addrPath.AddressType()-hdkeychain.HardenedKeyStart == uint32(crypto.AddressTypeValidator) { + addrs = append(addrs, addrInfo) + } } - keys := make([]int, 0) - for key := range addrsMap { - keys = append(keys, key) + v.SortAddressesByAddressIndex(addrs...) + v.SortAddressesByPurpose(addrs...) + + return addrs +} + +func (v *Vault) AllBLSAccountAddresses() []AddressInfo { + addrs := make([]AddressInfo, 0, v.AddressCount()/2) + for _, addrInfo := range v.Addresses { + addrPath, _ := addresspath.NewPathFromString(addrInfo.Path) + if addrPath.AddressType()-hdkeychain.HardenedKeyStart == uint32(crypto.AddressTypeBLSAccount) { + addrs = append(addrs, addrInfo) + } } - sort.Ints(keys) - for _, key := range keys { - addrsValue := addrsMap[key] - slices.SortFunc(addrsValue, func(a, b AddressInfo) int { - pathA, _ := addresspath.NewPathFromString(a.Path) - pathB, _ := addresspath.NewPathFromString(b.Path) + v.SortAddressesByAddressIndex(addrs...) + v.SortAddressesByPurpose(addrs...) - if pathA.AddressIndex() < pathB.AddressIndex() { - return -1 - } - return 1 - }) + return addrs +} - addrs = append(addrs, addrsValue...) +func (v *Vault) AllImportedPrivateKeysAddresses() []AddressInfo { + addrs := make([]AddressInfo, 0, v.AddressCount()/2) + for _, addrInfo := range v.Addresses { + addrPath, _ := addresspath.NewPathFromString(addrInfo.Path) + if addrPath.Purpose() == HardenedPurposeImportPrivateKey { + addrs = append(addrs, addrInfo) + } } - addrs = append(addrs, importedAddrs...) + v.SortAddressesByAddressIndex(addrs...) + v.SortAddressesByAddressType(addrs...) + return addrs } +func (v *Vault) SortAddressesByPurpose(addrs ...AddressInfo) { + slices.SortStableFunc(addrs, func(a, b AddressInfo) int { + pathA, _ := addresspath.NewPathFromString(a.Path) + pathB, _ := addresspath.NewPathFromString(b.Path) + + return cmp.Compare(pathA.Purpose(), pathB.Purpose()) + }) +} + +func (v *Vault) SortAddressesByAddressType(addrs ...AddressInfo) { + slices.SortStableFunc(addrs, func(a, b AddressInfo) int { + pathA, _ := addresspath.NewPathFromString(a.Path) + pathB, _ := addresspath.NewPathFromString(b.Path) + + return cmp.Compare(pathA.AddressType(), pathB.AddressType()) + }) +} + +func (v *Vault) SortAddressesByAddressIndex(addrs ...AddressInfo) { + slices.SortStableFunc(addrs, func(a, b AddressInfo) int { + pathA, _ := addresspath.NewPathFromString(a.Path) + pathB, _ := addresspath.NewPathFromString(b.Path) + + return cmp.Compare(pathA.AddressIndex(), pathB.AddressIndex()) + }) +} + func (v *Vault) IsEncrypted() bool { return v.Encrypter.IsEncrypted() } @@ -254,6 +298,15 @@ func (v *Vault) AddressCount() int { return len(v.Addresses) } +func (v *Vault) AddressFromPath(p string) *AddressInfo { + for _, addressInfo := range v.Addresses { + if addressInfo.Path == p { + return &addressInfo + } + } + return nil +} + func (v *Vault) ImportPrivateKey(password string, prv *bls.PrivateKey) error { if v.IsNeutered() { return ErrNeutered @@ -290,15 +343,18 @@ func (v *Vault) ImportPrivateKey(password string, prv *bls.PrivateKey) error { uint32(addressIndex)+hdkeychain.HardenedKeyStart). String() + importedPrvLabelCounter := (len(v.AllImportedPrivateKeysAddresses()) / 2) + 1 v.Addresses[accAddr.String()] = AddressInfo{ Address: accAddr.String(), PublicKey: pub.String(), + Label: fmt.Sprintf("Imported Reward Address %d", importedPrvLabelCounter), Path: blsAccPathStr, } v.Addresses[valAddr.String()] = AddressInfo{ Address: valAddr.String(), PublicKey: pub.String(), + Label: fmt.Sprintf("Imported Validator Address %d", importedPrvLabelCounter), Path: blsValidatorPathStr, } diff --git a/wallet/vault/vault_test.go b/wallet/vault/vault_test.go index 891aa04d1..232edaef5 100644 --- a/wallet/vault/vault_test.go +++ b/wallet/vault/vault_test.go @@ -46,6 +46,8 @@ func setup(t *testing.T) *testData { assert.NoError(t, err) _, err = vault.NewValidatorAddress("addr-3") assert.NoError(t, err) + _, err = vault.NewValidatorAddress("addr-4") + assert.NoError(t, err) assert.NoError(t, vault.ImportPrivateKey("", importedPrv)) assert.False(t, vault.IsEncrypted()) @@ -70,7 +72,7 @@ func setup(t *testing.T) *testData { func TestAddressInfo(t *testing.T) { td := setup(t) - assert.Equal(t, td.vault.AddressCount(), 5) + assert.Equal(t, td.vault.AddressCount(), 6) infos := td.vault.AddressInfos() for _, i := range infos { info := td.vault.AddressInfo(i.Address) @@ -79,10 +81,6 @@ func TestAddressInfo(t *testing.T) { // assert.Equal(t, i.Address, info.PublicKey) addr, _ := crypto.AddressFromString(info.Address) - if info.Path == "" { - continue - } - path, _ := addresspath.NewPathFromString(info.Path) switch path.Purpose() { @@ -111,7 +109,132 @@ func TestAddressInfo(t *testing.T) { // Neutered neutered := td.vault.Neuter() - assert.Equal(t, neutered.AddressCount(), 5) + assert.Equal(t, neutered.AddressCount(), 6) +} + +func TestSortAddressInfo(t *testing.T) { + td := setup(t) + + assert.Equal(t, td.vault.AddressCount(), 6) + infos := td.vault.AddressInfos() + + assert.Equal(t, "m/12381'/21888'/1'/0", infos[0].Path) + assert.Equal(t, "m/65535'/21888'/2'/0'", infos[len(infos)-1].Path) +} + +func TestAllValidatorAddresses(t *testing.T) { + td := setup(t) + + assert.Equal(t, td.vault.AddressCount(), 6) + + validatorAddrs := td.vault.AllValidatorAddresses() + for _, i := range validatorAddrs { + info := td.vault.AddressInfo(i.Address) + assert.Equal(t, i.Address, info.Address) + + path, _ := addresspath.NewPathFromString(info.Path) + + switch path.Purpose() { + case HardenedPurposeBLS12381: + assert.Equal(t, info.Path, fmt.Sprintf("m/%d'/%d'/1'/%d", + PurposeBLS12381, td.vault.CoinType, path.AddressIndex())) + case HardenedPurposeImportPrivateKey: + assert.Equal(t, info.Path, fmt.Sprintf("m/%d'/%d'/1'/%d'", + PurposeImportPrivateKey, td.vault.CoinType, path.AddressIndex()-hdkeychain.HardenedKeyStart)) + } + } +} + +func TestSortAllValidatorAddresses(t *testing.T) { + td := setup(t) + + assert.Equal(t, td.vault.AddressCount(), 6) + validatorAddrs := td.vault.AllValidatorAddresses() + + assert.Equal(t, "m/12381'/21888'/1'/0", validatorAddrs[0].Path) + assert.Equal(t, "m/65535'/21888'/1'/0'", validatorAddrs[len(validatorAddrs)-1].Path) +} + +func TestAllBLSAccountAddresses(t *testing.T) { + td := setup(t) + + assert.Equal(t, td.vault.AddressCount(), 6) + + blsAccountAddrs := td.vault.AllBLSAccountAddresses() + for _, i := range blsAccountAddrs { + info := td.vault.AddressInfo(i.Address) + assert.Equal(t, i.Address, info.Address) + + path, _ := addresspath.NewPathFromString(info.Path) + + switch path.Purpose() { + case HardenedPurposeBLS12381: + assert.Equal(t, info.Path, fmt.Sprintf("m/%d'/%d'/2'/%d", + PurposeBLS12381, td.vault.CoinType, path.AddressIndex())) + case HardenedPurposeImportPrivateKey: + assert.Equal(t, info.Path, fmt.Sprintf("m/%d'/%d'/2'/%d'", + PurposeImportPrivateKey, td.vault.CoinType, path.AddressIndex()-hdkeychain.HardenedKeyStart)) + } + } +} + +func TestSortAllBLSAccountAddresses(t *testing.T) { + td := setup(t) + + assert.Equal(t, td.vault.AddressCount(), 6) + + blsAccountAddrs := td.vault.AllBLSAccountAddresses() + + assert.Equal(t, "m/12381'/21888'/2'/0", blsAccountAddrs[0].Path) + assert.Equal(t, "m/65535'/21888'/2'/0'", blsAccountAddrs[len(blsAccountAddrs)-1].Path) +} + +func TestAddressFromPath(t *testing.T) { + td := setup(t) + assert.Equal(t, td.vault.AddressCount(), 6) + + t.Run("Could not find address from path", func(t *testing.T) { + path := "m/12381'/26888'/983'/0" + assert.Nil(t, td.vault.AddressFromPath(path)) + }) + + t.Run("Ok", func(t *testing.T) { + var address string + var addrInfo AddressInfo + + for addr, ai := range td.vault.Addresses { + address = addr + addrInfo = ai + break + } + + assert.Equal(t, address, td.vault.AddressFromPath(addrInfo.Path).Address) + }) +} + +func TestAllImportedPrivateKeysAddresses(t *testing.T) { + td := setup(t) + + assert.Equal(t, td.vault.AddressCount(), 6) + + importedPrvAddrs := td.vault.AllImportedPrivateKeysAddresses() + for _, i := range importedPrvAddrs { + info := td.vault.AddressInfo(i.Address) + assert.Equal(t, i.Address, info.Address) + + addr, _ := crypto.AddressFromString(info.Address) + path, _ := addresspath.NewPathFromString(info.Path) + + if addr.IsValidatorAddress() { + assert.Equal(t, info.Path, fmt.Sprintf("m/%d'/%d'/1'/%d'", + PurposeImportPrivateKey, td.vault.CoinType, path.AddressIndex()-hdkeychain.HardenedKeyStart)) + } + + if addr.IsAccountAddress() { + assert.Equal(t, info.Path, fmt.Sprintf("m/%d'/%d'/2'/%d'", + PurposeImportPrivateKey, td.vault.CoinType, path.AddressIndex()-hdkeychain.HardenedKeyStart)) + } + } } func TestNewBLSAccountAddress(t *testing.T) { @@ -144,6 +267,8 @@ func TestRecover(t *testing.T) { assert.NoError(t, err) _, err = recovered.NewValidatorAddress("addr-3") assert.NoError(t, err) + _, err = recovered.NewValidatorAddress("addr-4") + assert.NoError(t, err) assert.Equal(t, recovered.Purposes, td.vault.Purposes) }) diff --git a/wallet/wallet.go b/wallet/wallet.go index de858868c..f341e0745 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -391,6 +391,14 @@ func (w *Wallet) AddressCount() int { return w.store.Vault.AddressCount() } +func (w *Wallet) AllValidatorAddresses() []vault.AddressInfo { + return w.store.Vault.AllValidatorAddresses() +} + +func (w *Wallet) AddressFromPath(p string) *vault.AddressInfo { + return w.store.Vault.AddressFromPath(p) +} + func (w *Wallet) ImportPrivateKey(password string, prv *bls.PrivateKey) error { return w.store.Vault.ImportPrivateKey(password, prv) }