-
Notifications
You must be signed in to change notification settings - Fork 674
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
e2e: Migrate staking rewards e2e from kurtosis
- Loading branch information
Showing
4 changed files
with
287 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package p | ||
|
||
import ( | ||
"context" | ||
"math" | ||
"time" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
ginkgo "github.com/onsi/ginkgo/v2" | ||
"github.com/spf13/cast" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ava-labs/avalanchego/api/admin" | ||
"github.com/ava-labs/avalanchego/api/info" | ||
cfg "github.com/ava-labs/avalanchego/config" | ||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/tests" | ||
"github.com/ava-labs/avalanchego/tests/e2e" | ||
"github.com/ava-labs/avalanchego/tests/fixture/testnet" | ||
"github.com/ava-labs/avalanchego/utils/constants" | ||
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1" | ||
"github.com/ava-labs/avalanchego/utils/units" | ||
"github.com/ava-labs/avalanchego/vms/platformvm" | ||
"github.com/ava-labs/avalanchego/vms/platformvm/reward" | ||
"github.com/ava-labs/avalanchego/vms/platformvm/txs" | ||
"github.com/ava-labs/avalanchego/vms/secp256k1fx" | ||
) | ||
|
||
const ( | ||
validatorStartTimeDiff = 20 * time.Second | ||
delegationPeriod = 15 * time.Second | ||
validationPeriod = 30 * time.Second | ||
) | ||
|
||
var _ = ginkgo.Describe("[Staking Rewards]", func() { | ||
require := require.New(ginkgo.GinkgoT()) | ||
|
||
ginkgo.It("should ensure that validator node uptime determines whether a staking reward is issued", func() { | ||
network := e2e.Env.GetNetwork() | ||
|
||
ginkgo.By("checking that the network has a compatible minimum stake duration") | ||
minStakeDuration := cast.ToDuration(network.GetConfig().DefaultFlags[cfg.MinStakeDurationKey]) | ||
require.Equal(minStakeDuration, time.Second) | ||
|
||
ginkgo.By("adding alpha node, whose uptime should result in a staking reward") | ||
alphaNode := e2e.AddTemporaryNode(network, testnet.FlagsMap{}) | ||
ginkgo.By("adding beta node, whose uptime should not result in a staking reward") | ||
betaNode := e2e.AddTemporaryNode(network, testnet.FlagsMap{}) | ||
|
||
// Wait to check health until both nodes have started to minimize the duration | ||
// required for both nodes to report healthy. | ||
ginkgo.By("waiting until alpha node is healthy") | ||
e2e.WaitForHealthy(alphaNode) | ||
ginkgo.By("waiting until beta node is healthy") | ||
e2e.WaitForHealthy(betaNode) | ||
|
||
ginkgo.By("generating reward keys") | ||
factory := secp256k1.Factory{} | ||
alphaValidationRewardKey, err := factory.NewPrivateKey() | ||
require.NoError(err) | ||
alphaDelegationRewardKey, err := factory.NewPrivateKey() | ||
require.NoError(err) | ||
betaValidationRewardKey, err := factory.NewPrivateKey() | ||
require.NoError(err) | ||
betaDelegationRewardKey, err := factory.NewPrivateKey() | ||
require.NoError(err) | ||
gammaDelegationRewardKey, err := factory.NewPrivateKey() | ||
require.NoError(err) | ||
deltaDelegationRewardKey, err := factory.NewPrivateKey() | ||
require.NoError(err) | ||
rewardKeys := []*secp256k1.PrivateKey{ | ||
alphaValidationRewardKey, | ||
alphaDelegationRewardKey, | ||
betaValidationRewardKey, | ||
betaDelegationRewardKey, | ||
gammaDelegationRewardKey, | ||
deltaDelegationRewardKey, | ||
} | ||
|
||
ginkgo.By("creating keychain and P-Chain wallet") | ||
keychain := secp256k1fx.NewKeychain( | ||
rewardKeys..., | ||
) | ||
fundedKey := e2e.Env.AllocateFundedKey() | ||
keychain.Add(fundedKey) | ||
baseWallet := e2e.Env.NewWallet(keychain) | ||
pWallet := baseWallet.P() | ||
|
||
ginkgo.By("retrieving alpha node id and pop") | ||
alphaInfoClient := info.NewClient(alphaNode.GetProcessContext().URI) | ||
alphaNodeID, alphaPOP, err := alphaInfoClient.GetNodeID(context.Background()) | ||
require.NoError(err) | ||
ginkgo.By("retrieving beta node id and pop") | ||
betaInfoClient := info.NewClient(betaNode.GetProcessContext().URI) | ||
betaNodeID, betaPOP, err := betaInfoClient.GetNodeID(context.Background()) | ||
require.NoError(err) | ||
|
||
delegationPercent := 0.10 // 10% | ||
delegationFee := uint32(reward.PercentDenominator * delegationPercent) | ||
weight := 2_000 * units.Avax | ||
|
||
alphaValidatorStartTime := time.Now().Add(validatorStartTimeDiff) | ||
tests.Outf("alpha node validation period starting at: %v\n", alphaValidatorStartTime) | ||
|
||
ginkgo.By("adding alpha node as a validator") | ||
_, err = pWallet.IssueAddPermissionlessValidatorTx( | ||
&txs.SubnetValidator{Validator: txs.Validator{ | ||
NodeID: alphaNodeID, | ||
Start: uint64(alphaValidatorStartTime.Unix()), | ||
End: uint64(alphaValidatorStartTime.Add(validationPeriod).Unix()), | ||
Wght: weight, | ||
}}, | ||
alphaPOP, | ||
pWallet.AVAXAssetID(), | ||
&secp256k1fx.OutputOwners{ | ||
Threshold: 1, | ||
Addrs: []ids.ShortID{alphaValidationRewardKey.Address()}, | ||
}, | ||
&secp256k1fx.OutputOwners{ | ||
Threshold: 1, | ||
Addrs: []ids.ShortID{alphaDelegationRewardKey.Address()}, | ||
}, | ||
delegationFee, | ||
) | ||
require.NoError(err) | ||
|
||
betaValidatorStartTime := time.Now().Add(validatorStartTimeDiff) | ||
betaValidatorEndTime := betaValidatorStartTime.Add(validationPeriod) | ||
tests.Outf("beta node validation period starting at: %v\n", betaValidatorStartTime) | ||
|
||
ginkgo.By("adding beta node as a validator") | ||
_, err = pWallet.IssueAddPermissionlessValidatorTx( | ||
&txs.SubnetValidator{Validator: txs.Validator{ | ||
NodeID: betaNodeID, | ||
Start: uint64(betaValidatorStartTime.Unix()), | ||
End: uint64(betaValidatorEndTime.Unix()), | ||
Wght: weight, | ||
}}, | ||
betaPOP, | ||
pWallet.AVAXAssetID(), | ||
&secp256k1fx.OutputOwners{ | ||
Threshold: 1, | ||
Addrs: []ids.ShortID{betaValidationRewardKey.Address()}, | ||
}, | ||
&secp256k1fx.OutputOwners{ | ||
Threshold: 1, | ||
Addrs: []ids.ShortID{betaDelegationRewardKey.Address()}, | ||
}, | ||
delegationFee, | ||
) | ||
require.NoError(err) | ||
|
||
gammaDelegatorStartTime := time.Now().Add(validatorStartTimeDiff) | ||
tests.Outf("gamma delegation period starting at: %v\n", gammaDelegatorStartTime) | ||
|
||
ginkgo.By("adding gamma delegator") | ||
_, err = pWallet.IssueAddPermissionlessDelegatorTx( | ||
&txs.SubnetValidator{Validator: txs.Validator{ | ||
NodeID: alphaNodeID, | ||
Start: uint64(gammaDelegatorStartTime.Unix()), | ||
End: uint64(gammaDelegatorStartTime.Add(delegationPeriod).Unix()), | ||
Wght: weight, | ||
}}, | ||
pWallet.AVAXAssetID(), | ||
&secp256k1fx.OutputOwners{ | ||
Threshold: 1, | ||
Addrs: []ids.ShortID{gammaDelegationRewardKey.Address()}, | ||
}, | ||
) | ||
require.NoError(err) | ||
|
||
deltaDelegatorStartTime := time.Now().Add(validatorStartTimeDiff) | ||
tests.Outf("delta delegation period starting at: %v\n", deltaDelegatorStartTime) | ||
|
||
ginkgo.By("adding delta delegator") | ||
_, err = pWallet.IssueAddPermissionlessDelegatorTx( | ||
&txs.SubnetValidator{Validator: txs.Validator{ | ||
NodeID: betaNodeID, | ||
Start: uint64(deltaDelegatorStartTime.Unix()), | ||
End: uint64(deltaDelegatorStartTime.Add(delegationPeriod).Unix()), | ||
Wght: weight, | ||
}}, | ||
pWallet.AVAXAssetID(), | ||
&secp256k1fx.OutputOwners{ | ||
Threshold: 1, | ||
Addrs: []ids.ShortID{deltaDelegationRewardKey.Address()}, | ||
}, | ||
) | ||
require.NoError(err) | ||
|
||
ginkgo.By("stopping beta node to prevent it from receiving a validation reward") | ||
require.NoError(betaNode.Stop()) | ||
|
||
ginkgo.By("waiting until all validation periods are over") | ||
// The beta validator was the last added and so has the latest end time. The | ||
// delegation periods are shorter than the validation periods. | ||
time.Sleep(time.Until(betaValidatorEndTime)) | ||
|
||
pvmClient := platformvm.NewClient(alphaNode.GetProcessContext().URI) | ||
|
||
ginkgo.By("waiting until the alpha and beta nodes are no longer validators") | ||
e2e.Eventually(func() bool { | ||
validators, err := pvmClient.GetCurrentValidators(context.Background(), ids.Empty, nil) | ||
require.NoError(err) | ||
for _, validator := range validators { | ||
if validator.NodeID == alphaNodeID || validator.NodeID == betaNodeID { | ||
return false | ||
} | ||
} | ||
return true | ||
}, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "node failed to become a validator before timeout ") | ||
|
||
ginkgo.By("retrieving reward configuration for the network") | ||
// TODO(marun) Enable GetConfig to return *node.Config directly. Currently, due | ||
// to a circular dependency issue, a map-based equivalent is used. | ||
adminClient := admin.NewClient(e2e.Env.GetRandomNodeURI()) | ||
rawNodeConfigMap, err := adminClient.GetConfig(context.Background()) | ||
require.NoError(err) | ||
nodeConfigMap, ok := rawNodeConfigMap.(map[string]interface{}) | ||
require.True(ok) | ||
stakingConfigMap, ok := nodeConfigMap["stakingConfig"].(map[string]interface{}) | ||
require.True(ok) | ||
rawRewardConfig := stakingConfigMap["rewardConfig"] | ||
rewardConfig := reward.Config{} | ||
require.NoError(mapstructure.Decode(rawRewardConfig, &rewardConfig)) | ||
|
||
ginkgo.By("retrieving reward address balances") | ||
rewardBalances := map[ids.ShortID]uint64{} | ||
for _, rewardKey := range rewardKeys { | ||
keychain := secp256k1fx.NewKeychain(rewardKey) | ||
baseWallet := e2e.Env.NewWallet(keychain) | ||
pWallet := baseWallet.P() | ||
balances, err := pWallet.Builder().GetBalance() | ||
require.NoError(err) | ||
rewardBalances[rewardKey.Address()] = balances[pWallet.AVAXAssetID()] | ||
} | ||
require.Len(rewardBalances, len(rewardKeys)) | ||
|
||
ginkgo.By("determining expected validation and delegation rewards") | ||
currentSupply, err := pvmClient.GetCurrentSupply(context.Background(), constants.PrimaryNetworkID) | ||
require.NoError(err) | ||
calculator := reward.NewCalculator(rewardConfig) | ||
expectedValidationReward := calculator.Calculate(validationPeriod, weight, currentSupply) | ||
expectedDelegationReward := calculator.Calculate(delegationPeriod, weight, currentSupply) | ||
expectedDelegationFee := uint64(math.Round(float64(expectedDelegationReward) * delegationPercent)) | ||
|
||
ginkgo.By("checking expected rewards against actual rewards") | ||
expectedRewardBalances := map[ids.ShortID]uint64{ | ||
alphaValidationRewardKey.Address(): expectedValidationReward, | ||
alphaDelegationRewardKey.Address(): expectedDelegationFee, | ||
betaValidationRewardKey.Address(): 0, // Validator didn't meet uptime requirement | ||
betaDelegationRewardKey.Address(): 0, // Validator didn't meet uptime requirement | ||
gammaDelegationRewardKey.Address(): expectedDelegationReward - expectedDelegationFee, | ||
deltaDelegationRewardKey.Address(): 0, // Validator didn't meet uptime requirement | ||
} | ||
for address := range expectedRewardBalances { | ||
require.Equal(expectedRewardBalances[address], rewardBalances[address]) | ||
} | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters