Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): one reward address in config for all validators #1178

Merged
merged 11 commits into from
Mar 29, 2024
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 @@
"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 @@
}()
}

// 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 @@
}

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 @@
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)

Check warning on line 403 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L403

Added line #L403 was not covered by tests
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

Check warning on line 410 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L408-L410

Added lines #L408 - L410 were not covered by tests
}

nodeInstance, err := node.NewNode(gen, conf, valKeys, rewardAddrs)
Expand Down Expand Up @@ -575,3 +527,82 @@

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))

Check warning on line 585 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L584-L585

Added lines #L584 - L585 were not covered by tests
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)

Check warning on line 589 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L587-L589

Added lines #L587 - L589 were not covered by tests
}
valAddrs[i] = valAddr.String()

Check warning on line 591 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L591

Added line #L591 was not covered by tests
}

valKeys := make([]*bls.ValidatorKey, len(valAddrsInfo))
password, ok := passwordFetcher(walletInstance)
if !ok {
return nil, fmt.Errorf("aborted")

Check warning on line 597 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L594-L597

Added lines #L594 - L597 were not covered by tests
}
prvKeys, err := walletInstance.PrivateKeys(password, valAddrs)
if err != nil {
return nil, err

Check warning on line 601 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L599-L601

Added lines #L599 - L601 were not covered by tests
}
for i, prv := range prvKeys {
valKeys[i] = bls.NewValidatorKey(prv.(*bls.PrivateKey))

Check warning on line 604 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L604

Added line #L604 was not covered by tests
}

return valKeys, nil

Check warning on line 607 in cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

cmd/cmd.go#L607

Added line #L607 was not covered by tests
}
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
Loading