Skip to content

Commit

Permalink
feat(config): one reward address in config for all validators (#1178)
Browse files Browse the repository at this point in the history
  • Loading branch information
b00f authored Mar 29, 2024
1 parent 3ba0150 commit 20d5d83
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 58 deletions.
143 changes: 87 additions & 56 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/pactus-project/pactus/util"
"github.com/pactus-project/pactus/wallet"
"github.com/pactus-project/pactus/wallet/addresspath"
"github.com/pactus-project/pactus/wallet/vault"
)

const (
Expand Down Expand Up @@ -276,7 +277,6 @@ func TrapSignal(cleanupFunc func()) {
}()
}

// TODO: write test for me.
func CreateNode(numValidators int, chain genesis.ChainType, workingDir string,
mnemonic string, walletPassword string,
) ([]string, []string, error) {
Expand Down Expand Up @@ -329,6 +329,9 @@ func CreateNode(numValidators int, chain genesis.ChainType, workingDir string,
}

case genesis.Localnet:
if numValidators < 4 {
return nil, nil, fmt.Errorf("LocalNeed needs at least 4 validators")
}
genDoc := makeLocalGenesis(*walletInstance)
if err := genDoc.SaveToFile(genPath); err != nil {
return nil, nil, err
Expand Down Expand Up @@ -397,65 +400,14 @@ func StartNode(workingDir string, passwordFetcher func(*wallet.Wallet) (string,
valAddrsInfo = valAddrsInfo[:32]
}

if len(conf.Node.RewardAddresses) > 0 &&
len(conf.Node.RewardAddresses) != len(valAddrsInfo) {
return nil, nil, fmt.Errorf("reward addresses should be %v", len(valAddrsInfo))
}

valAddrs := make([]string, len(valAddrsInfo))
for i := 0; i < len(valAddrs); i++ {
valAddr, _ := crypto.AddressFromString(valAddrsInfo[i].Address)
if !valAddr.IsValidatorAddress() {
return nil, nil, fmt.Errorf("invalid validator address: %s", valAddrsInfo[i].Address)
}
valAddrs[i] = valAddr.String()
}

valKeys := make([]*bls.ValidatorKey, len(valAddrsInfo))
password, ok := passwordFetcher(walletInstance)
if !ok {
return nil, nil, fmt.Errorf("aborted")
}
prvKeys, err := walletInstance.PrivateKeys(password, valAddrs)
rewardAddrs, err := MakeRewardAddresses(walletInstance, valAddrsInfo, conf.Node.RewardAddresses)
if err != nil {
return nil, nil, err
}
for i, prv := range prvKeys {
valKeys[i] = bls.NewValidatorKey(prv.(*bls.PrivateKey))
}

// Create reward addresses
rewardAddrs := make([]crypto.Address, 0, len(valAddrsInfo))
if len(conf.Node.RewardAddresses) != 0 {
for _, addrStr := range conf.Node.RewardAddresses {
addr, _ := crypto.AddressFromString(addrStr)
rewardAddrs = append(rewardAddrs, addr)
}
} else {
for i := 0; i < len(valAddrsInfo); i++ {
valAddrPath, _ := addresspath.FromString(valAddrsInfo[i].Path)
accAddrPath := addresspath.NewPath(
valAddrPath.Purpose(),
valAddrPath.CoinType(),
uint32(crypto.AddressTypeBLSAccount)+hdkeychain.HardenedKeyStart,
valAddrPath.AddressIndex())

addrInfo := walletInstance.AddressFromPath(accAddrPath.String())
if addrInfo == nil {
return nil, nil, fmt.Errorf("unable to find reward address for: %s [%s]",
valAddrsInfo[i].Address, accAddrPath)
}

addr, _ := crypto.AddressFromString(addrInfo.Address)
rewardAddrs = append(rewardAddrs, addr)
}
}

// Check if reward addresses are account address
for _, addr := range rewardAddrs {
if !addr.IsAccountAddress() {
return nil, nil, fmt.Errorf("reward address is not an account address: %s", addr)
}
valKeys, err := MakeValidatorKey(walletInstance, valAddrsInfo, passwordFetcher)
if err != nil {
return nil, nil, err
}

nodeInstance, err := node.NewNode(gen, conf, valKeys, rewardAddrs)
Expand Down Expand Up @@ -575,3 +527,82 @@ func RecoverConfig(confPath string, defConf *config.Config, chainType genesis.Ch

return conf, err
}

func MakeRewardAddresses(walletInstance *wallet.Wallet,
valAddrsInfo []vault.AddressInfo, confRewardAddresses []string,
) ([]crypto.Address, error) {
if len(confRewardAddresses) > 1 &&
len(confRewardAddresses) != len(valAddrsInfo) {
return nil, fmt.Errorf("reward addresses should be %v", len(valAddrsInfo))
}

// Create reward addresses
rewardAddrs := make([]crypto.Address, 0, len(valAddrsInfo))
if len(confRewardAddresses) != 0 {
for _, addrStr := range confRewardAddresses {
addr, _ := crypto.AddressFromString(addrStr)
rewardAddrs = append(rewardAddrs, addr)
}

if len(rewardAddrs) == 1 {
for i := 1; i < len(valAddrsInfo); i++ {
rewardAddrs = append(rewardAddrs, rewardAddrs[0])
}
}
} else {
for i := 0; i < len(valAddrsInfo); i++ {
valAddrPath, _ := addresspath.FromString(valAddrsInfo[i].Path)
accAddrPath := addresspath.NewPath(
valAddrPath.Purpose(),
valAddrPath.CoinType(),
uint32(crypto.AddressTypeBLSAccount)+hdkeychain.HardenedKeyStart,
valAddrPath.AddressIndex())

addrInfo := walletInstance.AddressFromPath(accAddrPath.String())
if addrInfo == nil {
return nil, fmt.Errorf("unable to find reward address for: %s [%s]",
valAddrsInfo[i].Address, accAddrPath)
}

addr, _ := crypto.AddressFromString(addrInfo.Address)
rewardAddrs = append(rewardAddrs, addr)
}
}

// Check if reward addresses are account address
for _, addr := range rewardAddrs {
if !addr.IsAccountAddress() {
return nil, fmt.Errorf("reward address is not an account address: %s", addr)
}
}

return rewardAddrs, nil
}

func MakeValidatorKey(walletInstance *wallet.Wallet, valAddrsInfo []vault.AddressInfo,
passwordFetcher func(*wallet.Wallet) (string, bool),
) ([]*bls.ValidatorKey, error) {
valAddrs := make([]string, len(valAddrsInfo))
for i := 0; i < len(valAddrs); i++ {
valAddr, _ := crypto.AddressFromString(valAddrsInfo[i].Address)
if !valAddr.IsValidatorAddress() {
return nil, fmt.Errorf("invalid validator address: %s", valAddrsInfo[i].Address)
}
valAddrs[i] = valAddr.String()
}

valKeys := make([]*bls.ValidatorKey, len(valAddrsInfo))
password, ok := passwordFetcher(walletInstance)
if !ok {
return nil, fmt.Errorf("aborted")
}
prvKeys, err := walletInstance.PrivateKeys(password, valAddrs)
if err != nil {
return nil, err
}
for i, prv := range prvKeys {
valKeys[i] = bls.NewValidatorKey(prv.(*bls.PrivateKey))
}

return valKeys, nil
}
162 changes: 162 additions & 0 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import (
"runtime"
"testing"

"github.com/pactus-project/pactus/genesis"
"github.com/pactus-project/pactus/util"
"github.com/pactus-project/pactus/util/testsuite"
"github.com/pactus-project/pactus/wallet"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -161,3 +165,161 @@ func TestPathsWindows(t *testing.T) {
assert.Equal(t, test.expectedConfigPath, configPath)
}
}

func TestMakeRewardAddresses(t *testing.T) {
ts := testsuite.NewTestSuite(t)

walletPath := util.TempFilePath()
mnemonic, _ := wallet.GenerateMnemonic(128)
walletInstance, err := wallet.Create(walletPath, mnemonic, "", genesis.Mainnet)
assert.NoError(t, err)

_, _ = walletInstance.NewValidatorAddress("")
_, _ = walletInstance.NewValidatorAddress("")
_, _ = walletInstance.NewValidatorAddress("")

// Test 1 - Wallet without reward addresses
valAddrsInfo := walletInstance.AllValidatorAddresses()
confRewardAddresses := []string{}
_, err = MakeRewardAddresses(walletInstance, valAddrsInfo, confRewardAddresses)
assert.ErrorContains(t, err, "unable to find reward address for")

// Test 2 - Not enough reward addresses in wallet
rewardAddr1, _ := walletInstance.NewBLSAccountAddress("")
rewardAddr2, _ := walletInstance.NewBLSAccountAddress("")

_, err = MakeRewardAddresses(walletInstance, valAddrsInfo, confRewardAddresses)
assert.ErrorContains(t, err, "unable to find reward address for")

// Test 3 - Get reward addresses from wallet
rewardAddr3, _ := walletInstance.NewBLSAccountAddress("")

rewardAddrs, err := MakeRewardAddresses(walletInstance, valAddrsInfo, confRewardAddresses)
assert.NoError(t, err)
assert.Equal(t, rewardAddrs[0].String(), rewardAddr1)
assert.Equal(t, rewardAddrs[1].String(), rewardAddr2)
assert.Equal(t, rewardAddrs[2].String(), rewardAddr3)

// Test 4 - Not enough reward addresses in config
confRewardAddr1 := ts.RandAccAddress().String()
confRewardAddr2 := ts.RandAccAddress().String()
confRewardAddresses = []string{confRewardAddr1, confRewardAddr2}

_, err = MakeRewardAddresses(walletInstance, valAddrsInfo, confRewardAddresses)
assert.ErrorContains(t, err, "reward addresses should be 3")

// Test 5 - Get reward addresses from config
confRewardAddr3 := ts.RandAccAddress().String()
confRewardAddresses = []string{confRewardAddr1, confRewardAddr2, confRewardAddr3}

rewardAddrs, err = MakeRewardAddresses(walletInstance, valAddrsInfo, confRewardAddresses)
assert.NoError(t, err)
assert.Equal(t, rewardAddrs[0].String(), confRewardAddr1)
assert.Equal(t, rewardAddrs[1].String(), confRewardAddr2)
assert.Equal(t, rewardAddrs[2].String(), confRewardAddr3)

// Test 6 - Set one reward addresses in config
confRewardAddr := ts.RandAccAddress().String()
confRewardAddresses = []string{confRewardAddr}

rewardAddrs, err = MakeRewardAddresses(walletInstance, valAddrsInfo, confRewardAddresses)
assert.NoError(t, err)
assert.Equal(t, rewardAddrs[0].String(), confRewardAddr)
assert.Equal(t, rewardAddrs[1].String(), confRewardAddr)
assert.Equal(t, rewardAddrs[2].String(), confRewardAddr)

// Test 7 - Set validator address as reward addresses in config
confRewardAddr = ts.RandValAddress().String()
confRewardAddresses = []string{confRewardAddr}

_, err = MakeRewardAddresses(walletInstance, valAddrsInfo, confRewardAddresses)
assert.ErrorContains(t, err, "reward address is not an account address")
}

func TestCreateNode(t *testing.T) {
tests := []struct {
name string
numValidators int
chain genesis.ChainType
workingDir string
mnemonic string
withErr bool
validatorAddrs []string
rewardAddrs []string
}{
{
name: "Create node for Mainnet",
numValidators: 1,
chain: genesis.Mainnet,
workingDir: util.TempDirPath(),
mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow",
validatorAddrs: []string{"pc1pqpu5tkuctj6ecxjs85f9apm802hhc65amwhuyw"},
rewardAddrs: []string{"pc1zmpnme0xrgzhml77e3k70ey9hwwwsfed6l04pqc"},
withErr: false,
},
{
name: "Create node for Testnet",
numValidators: 1,
chain: genesis.Testnet,
workingDir: util.TempDirPath(),
mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow",
validatorAddrs: []string{"tpc1p54ex6jvqkz6qyld5wgm77qm7walgy664hxz2pc"},
rewardAddrs: []string{"tpc1zlkjrgfkrh7f9enpt730tp5vgx7tgtqzplhfksa"},
withErr: false,
},

{
name: "Create node for Localnet",
numValidators: 4,
chain: genesis.Localnet,
workingDir: util.TempDirPath(),
mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow",
validatorAddrs: []string{
"tpc1p54ex6jvqkz6qyld5wgm77qm7walgy664hxz2pc",
"tpc1pdf5e0q4d6eaww3uq5pmw5aayqpaqplra0pj8z2",
"tpc1pe5px2dddn6g4zgnu3wpwgrqpdjrufvda57a4wm",
"tpc1p8yyhysp380j9q9gxa6vlhstgkd94238kunttpr",
},
rewardAddrs: []string{
"tpc1zlkjrgfkrh7f9enpt730tp5vgx7tgtqzplhfksa",
"tpc1ztzwc9x98j88wctmzm5t09z592lqw0sqc3rn6lu",
"tpc1zslef8hjkwqxdcekcqxra6djgjr5gryrj8l3fyf",
"tpc1zru3xxmgz5dqqkv0mesqq3t3luepzg3e6jeqkeu",
},
withErr: false,
},
{
name: "Localnet with one validator",
numValidators: 1,
chain: genesis.Localnet,
workingDir: util.TempDirPath(),
mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow",
validatorAddrs: nil,
rewardAddrs: nil,
withErr: true,
},
{
name: "Invalid mnemonic",
numValidators: 4,
chain: genesis.Mainnet,
workingDir: util.TempDirPath(),
mnemonic: "",
validatorAddrs: nil,
rewardAddrs: nil,
withErr: true,
},
}

for _, test := range tests {
validatorAddrs, rewardAddrs, err := CreateNode(
test.numValidators, test.chain, test.workingDir, test.mnemonic, "")

if test.withErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, test.validatorAddrs, validatorAddrs)
assert.Equal(t, test.rewardAddrs, rewardAddrs)
}
}
}
5 changes: 3 additions & 2 deletions config/example_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
[node]

# `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.
# If empty, reward addresses will be obtained from the wallet.
# If it has only one address, it is used for all validators.
# Otherwise, the number of reward addresses should be the same as the number of validators.
## reward_addresses = []

# `store` contains configuration options for the store module, which manages storage and retrieval of blockchain data.
Expand Down

0 comments on commit 20d5d83

Please sign in to comment.