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

Pos validator uptime #2379

Merged
merged 39 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2a62249
wip
arturrez Nov 25, 2024
a670cff
rename funcs
arturrez Nov 25, 2024
140138c
code cleanup
arturrez Nov 25, 2024
d360767
PoS validator removal with provided uptime
arturrez Nov 26, 2024
3610541
add uptimeproof r4r
arturrez Nov 26, 2024
943827f
lint
arturrez Nov 26, 2024
f35b55a
lint, switch to regex, cleanup
arturrez Nov 27, 2024
f29392c
increase staking reward
arturrez Nov 27, 2024
2be7ae2
fix typo
arturrez Nov 27, 2024
ed76795
add uptime deductible
arturrez Nov 27, 2024
b3e7089
add debug output to e2e
arturrez Nov 27, 2024
c0e30a3
increse PoS e2e validator balance
arturrez Nov 27, 2024
c1d7415
proper e2e addvalidator config
arturrez Nov 27, 2024
9448b5e
reduce stake amount
arturrez Nov 27, 2024
f9f2d37
disable this check for now
arturrez Nov 27, 2024
326d3d9
increase wait time for reward
arturrez Nov 27, 2024
aa419d1
adjust params
arturrez Nov 27, 2024
03e87c2
add manual uptime for e2e
arturrez Nov 28, 2024
74ccf05
fix
arturrez Nov 28, 2024
4ea0625
5 mins before status
arturrez Nov 28, 2024
b548755
rm sleep and e2e for check rm validator with uptime
arturrez Nov 28, 2024
ce2db5c
Merge branch 'main' into pos-validator-uptime
arturrez Nov 30, 2024
6d04aff
Merge branch 'main' into pos-validator-uptime
arturrez Dec 2, 2024
71c894d
add force flag to remove validators. allow validator removal with 0 r…
arturrez Dec 2, 2024
89654f9
add check for failed removal and also sleep in e2e waiting for min du…
arturrez Dec 2, 2024
4082bdc
rm e2e check - no cli value
arturrez Dec 2, 2024
5b8c0ba
minor refactor
arturrez Dec 2, 2024
ca920d8
Merge branch 'main' into pos-validator-uptime
arturrez Dec 4, 2024
01f1790
put func getBlockchainTimestamp back after bad merge
arturrez Dec 4, 2024
60cbf4e
Merge branch 'main' into pos-validator-uptime
sukantoraymond Dec 6, 2024
4fb9b7a
Merge branch 'main' into pos-validator-uptime
arturrez Dec 6, 2024
b8c3538
parameter stake amount for e2e
arturrez Dec 6, 2024
cb76a0d
Merge branch 'main' into pos-validator-uptime
sukantoraymond Dec 10, 2024
2a54369
fix lint
sukantoraymond Dec 10, 2024
d9259b7
Merge branch 'main' into pos-validator-uptime
sukantoraymond Dec 10, 2024
54d7f07
Merge branch 'main' into pos-validator-uptime
arturrez Dec 11, 2024
ee6279f
address feedback
arturrez Dec 11, 2024
2fc67ff
rm debug
arturrez Dec 11, 2024
d1e496d
revert ApplyDefaultDenomination here
arturrez Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions cmd/blockchaincmd/add_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,22 +681,6 @@ func PromptDuration(start time.Time, network models.Network) (time.Duration, err
}
}

func getMaxValidationTime(network models.Network, nodeID ids.NodeID, startTime time.Time) (time.Duration, error) {
ctx, cancel := utils.GetAPIContext()
defer cancel()
platformCli := platformvm.NewClient(network.Endpoint)
vs, err := platformCli.GetCurrentValidators(ctx, avagoconstants.PrimaryNetworkID, nil)
if err != nil {
return 0, err
}
for _, v := range vs {
if v.NodeID == nodeID {
return time.Unix(int64(v.EndTime), 0).Sub(startTime), nil
}
}
return 0, errors.New("nodeID not found in validator set: " + nodeID.String())
}

func getBlockchainTimestamp(network models.Network) (time.Time, error) {
ctx, cancel := utils.GetAPIContext()
defer cancel()
Expand Down Expand Up @@ -782,7 +766,7 @@ func getTimeParameters(network models.Network, nodeID ids.NodeID, isValidator bo
var selectedDuration time.Duration
if useDefaultDuration {
// avoid setting both globals useDefaultDuration and duration
selectedDuration, err = getMaxValidationTime(network, nodeID, start)
selectedDuration, err = utils.GetRemainingValidationTime(network.Endpoint, nodeID, avagoconstants.PrimaryNetworkID, start)
if err != nil {
return time.Time{}, 0, err
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/blockchaincmd/change_weight.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ func setWeight(_ *cobra.Command, args []string) error {
network,
blockchainName,
nodeID,
0, // automatic uptime
isBootstrapValidatorForNetwork(nodeID, sc.Networks[network.Name()]),
false, // don't force
)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cmd/blockchaincmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,8 +1044,8 @@ func deployBlockchain(cmd *cobra.Command, args []string) error {
aggregatorAllowPrivatePeers,
logLvl,
validatorManagerSDK.PoSParams{
MinimumStakeAmount: utils.ApplyDefaultDenomination(poSMinimumStakeAmount),
MaximumStakeAmount: utils.ApplyDefaultDenomination(poSMaximumStakeAmount),
MinimumStakeAmount: big.NewInt(int64(poSMinimumStakeAmount)),
MaximumStakeAmount: big.NewInt(int64(poSMaximumStakeAmount)),
MinimumStakeDuration: poSMinimumStakeDuration,
MinimumDelegationFee: poSMinimumDelegationFee,
MaximumStakeMultiplier: poSMaximumStakeMultiplier,
Expand Down
80 changes: 66 additions & 14 deletions cmd/blockchaincmd/remove_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import (
"github.com/ava-labs/avalanche-cli/pkg/utils"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/ava-labs/avalanche-cli/pkg/validatormanager"
validatormanagerSDK "github.com/ava-labs/avalanche-cli/sdk/validatormanager"
"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
"github.com/spf13/cobra"
)
Expand All @@ -36,6 +38,11 @@ var removeValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{
networkoptions.EtnaDevnet,
}

var (
uptimeSec uint64
force bool
)

// avalanche blockchain removeValidator
func newRemoveValidatorCmd() *cobra.Command {
cmd := &cobra.Command{
Expand All @@ -62,6 +69,8 @@ these prompts by providing the values with flags.`,
privateKeyFlags.AddToCmd(cmd, "to pay fees for completing the validator's removal (blockchain gas token)")
cmd.Flags().StringVar(&rpcURL, "rpc", "", "connect to validator manager at the given rpc endpoint")
cmd.Flags().StringVar(&aggregatorLogLevel, "aggregator-log-level", "Off", "log level to use with signature aggregator")
cmd.Flags().Uint64Var(&uptimeSec, "uptime", 0, "validator's uptime in seconds. If not provided, it will be automatically calculated")
cmd.Flags().BoolVar(&force, "force", false, "force validator removal even if it's not getting rewarded")
return cmd
}

Expand Down Expand Up @@ -172,15 +181,15 @@ func removeValidator(_ *cobra.Command, args []string) error {
}
return removeValidatorNonSOV(deployer, network, subnetID, kc, blockchainName, nodeID)
}
// check if node is a bootstrap validator to force it to be removed
filteredBootstrapValidators := utils.Filter(scNetwork.BootstrapValidators, func(b models.SubnetValidator) bool {
if id, err := ids.NodeIDFromString(b.NodeID); err == nil && id == nodeID {
return true
}
return false
})
force := len(filteredBootstrapValidators) > 0
if err := removeValidatorSOV(deployer, network, blockchainName, nodeID, force); err != nil {
if err := removeValidatorSOV(
deployer,
network,
blockchainName,
nodeID,
uptimeSec,
isBootstrapValidatorForNetwork(nodeID, scNetwork),
force,
); err != nil {
return err
}
// remove the validator from the list of bootstrap validators
Expand All @@ -199,11 +208,23 @@ func removeValidator(_ *cobra.Command, args []string) error {
return nil
}

func isBootstrapValidatorForNetwork(nodeID ids.NodeID, scNetwork models.NetworkData) bool {
filteredBootstrapValidators := utils.Filter(scNetwork.BootstrapValidators, func(b models.SubnetValidator) bool {
if id, err := ids.NodeIDFromString(b.NodeID); err == nil && id == nodeID {
felipemadero marked this conversation as resolved.
Show resolved Hide resolved
return true
}
return false
})
return len(filteredBootstrapValidators) > 0
}

func removeValidatorSOV(
deployer *subnet.PublicDeployer,
network models.Network,
blockchainName string,
nodeID ids.NodeID,
uptimeSec uint64,
isBootstrapValidator bool,
force bool,
) error {
chainSpec := contract.ChainSpec{
Expand Down Expand Up @@ -250,7 +271,12 @@ func removeValidatorSOV(
ux.Logger.PrintToUser(logging.Yellow.Wrap("Forcing removal of %s as it is a PoS bootstrap validator"), nodeID)
}

signedMessage, validationID, err := validatormanager.InitValidatorRemoval(
var (
signedMessage *warp.Message
validationID ids.ID
)
// try to remove the validator. If err is "delegator ineligible for rewards" confirm with user and force remove
signedMessage, validationID, err = validatormanager.InitValidatorRemoval(
app,
network,
rpcURL,
Expand All @@ -261,13 +287,40 @@ func removeValidatorSOV(
aggregatorAllowPrivatePeers,
aggregatorLogLevel,
sc.PoS(),
force,
uptimeSec,
isBootstrapValidator || force,
)
if err != nil {
if err != nil && errors.Is(err, validatormanagerSDK.ErrValidatorIneligibleForRewards) {
ux.Logger.PrintToUser("Calculated rewards is zero. Validator %s is not eligible for rewards", nodeID)
force, err = app.Prompt.CaptureNoYes("Do you want to continue with validator removal?")
if err != nil {
return err
}
if !force {
return fmt.Errorf("validator %s is not eligible for rewards. Use --force flag to force removal", nodeID)
}
signedMessage, validationID, err = validatormanager.InitValidatorRemoval(
app,
network,
rpcURL,
chainSpec,
ownerPrivateKey,
nodeID,
extraAggregatorPeers,
aggregatorAllowPrivatePeers,
aggregatorLogLevel,
sc.PoS(),
uptimeSec,
true, // force
)
if err != nil {
return err
}
} else if err != nil {
return err
}
ux.Logger.PrintToUser("ValidationID: %s", validationID)

ux.Logger.PrintToUser("ValidationID: %s", validationID)
txID, _, err := deployer.SetL1ValidatorWeight(signedMessage)
if err != nil {
return err
Expand All @@ -287,7 +340,6 @@ func removeValidatorSOV(
); err != nil {
return err
}

ux.Logger.GreenCheckmarkToUser("Validator successfully removed from the Subnet")

return nil
Expand Down
5 changes: 2 additions & 3 deletions cmd/contractcmd/init_validator_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/ava-labs/avalanche-cli/pkg/models"
"github.com/ava-labs/avalanche-cli/pkg/networkoptions"
"github.com/ava-labs/avalanche-cli/pkg/prompts"
"github.com/ava-labs/avalanche-cli/pkg/utils"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/ava-labs/avalanche-cli/pkg/validatormanager"
blockchainSDK "github.com/ava-labs/avalanche-cli/sdk/blockchain"
Expand Down Expand Up @@ -206,8 +205,8 @@ func initValidatorManager(_ *cobra.Command, args []string) error {
validatorManagerFlags.aggregatorAllowPrivatePeers,
validatorManagerFlags.aggregatorLogLevel,
validatorManagerSDK.PoSParams{
MinimumStakeAmount: utils.ApplyDefaultDenomination(initPOSManagerFlags.minimumStakeAmount),
MaximumStakeAmount: utils.ApplyDefaultDenomination(initPOSManagerFlags.maximumStakeAmount),
MinimumStakeAmount: big.NewInt(int64(initPOSManagerFlags.minimumStakeAmount)),
MaximumStakeAmount: big.NewInt(int64(initPOSManagerFlags.maximumStakeAmount)),
MinimumStakeDuration: initPOSManagerFlags.minimumStakeDuration,
MinimumDelegationFee: initPOSManagerFlags.minimumDelegationFee,
MaximumStakeMultiplier: initPOSManagerFlags.maximumStakeMultiplier,
Expand Down
2 changes: 2 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,6 @@ const (
MainnetCChainICMRegistryAddress = "0x7C43605E14F391720e1b37E49C78C4b03A488d98"
FujiCChainICMRegistryAddress = "0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228"
EtnaDevnetCChainICMRegistryAddress = "0xEe40DFF876204A99eCCB783FDc01eE0a2678Ae93"

ValidatorUptimeDeductible = uint64(10) // seconds to make sure all L1 validators would agree on uptime
)
28 changes: 0 additions & 28 deletions pkg/utils/decimals.go

This file was deleted.

23 changes: 23 additions & 0 deletions pkg/utils/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ package utils
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"net/url"
"regexp"
)

// GetUserIPAddress retrieves the IP address of the user.
Expand Down Expand Up @@ -65,3 +67,24 @@ func IsValidIPPort(ipPortPair string) bool {
}
return true
}

// SplitRPCURI splits the RPC URI into `endpoint` and `chain`.
// Reverse operation of `fmt.Sprintf("%s/ext/bc/%s", endpoint, chain)`.
// returns the `uri` and `chain` as strings, or an error if the request URI is invalid.
func SplitAvalanchegoRPCURI(requestURI string) (string, string, error) {
// Define the regex pattern
pattern := `^(https?://[^/]+)/ext/bc/([^/]+)/rpc$`
regex := regexp.MustCompile(pattern)

// Match the pattern
matches := regex.FindStringSubmatch(requestURI)
if matches == nil || len(matches) != 3 {
return "", "", fmt.Errorf("invalid request URI format")
}

// Extract `endpoint` and `chain`
endpoint := matches[1]
chain := matches[2]

return endpoint, chain, nil
}
76 changes: 69 additions & 7 deletions pkg/utils/net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ package utils

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestIsValidIPPort(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"127.0.0.1:8080", true}, // valid IP:port
{"256.0.0.1:8080", false}, // invalid IP address
{"example.com:8080", false}, // only ip address is allowed
{"127.0.0.1", false}, // missing port
{"[::1]:8080", true}, // valid IPv6 address
{"[::1]", false}, // missing port for IPv6
{"", false}, // empty string
{"127.0.0.1:8080", true}, // valid IP:port
{"256.0.0.1:8080", false}, // invalid IP address
{"127.0.0.1:9650:8080", false}, // only ip address is allowed
{"127.0.0.1", false}, // missing port
{"[::1]:8080", true}, // valid IPv6 address
{"[::1]", false}, // missing port for IPv6
{"", false}, // empty string
}

for _, test := range tests {
Expand All @@ -29,3 +31,63 @@ func TestIsValidIPPort(t *testing.T) {
})
}
}

func TestSplitRPCURI(t *testing.T) {
tests := []struct {
name string
requestURI string
expectedEndpoint string
expectedChain string
expectError bool
}{
{
name: "Valid URI",
requestURI: "http://127.0.0.1:9660/ext/bc/nL95ujcHLPFhuQdHYkvS3CSUvDr9EfZduzyJ5Ty6VXXMgyEEF/rpc",
expectedEndpoint: "http://127.0.0.1:9660",
expectedChain: "nL95ujcHLPFhuQdHYkvS3CSUvDr9EfZduzyJ5Ty6VXXMgyEEF",
expectError: false,
},
{
name: "Valid URI with https",
requestURI: "https://example.com:8080/ext/bc/testChain/rpc",
expectedEndpoint: "https://example.com:8080",
expectedChain: "testChain",
expectError: false,
},
{
name: "Invalid URI - missing /rpc",
requestURI: "http://127.0.0.1:9660/ext/bc/nL95ujcHLPFhuQdHYkvS3CSUvDr9EfZduzyJ5Ty6VXXMgyEEF",
expectedEndpoint: "",
expectedChain: "",
expectError: true,
},
{
name: "Invalid URI - missing /ext/bc/",
requestURI: "http://127.0.0.1:9660/some/other/path/rpc",
expectedEndpoint: "",
expectedChain: "",
expectError: true,
},
{
name: "Invalid URI - malformed URL",
requestURI: "127.0.0.1:9660/ext/bc/chainId/rpc",
expectedEndpoint: "",
expectedChain: "",
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
endpoint, chain, err := SplitAvalanchegoRPCURI(tt.requestURI)

if tt.expectError {
felipemadero marked this conversation as resolved.
Show resolved Hide resolved
require.Error(t, err, "expected an error but got nil")
} else {
require.NoError(t, err, "did not expect an error but got one")
require.Equal(t, tt.expectedEndpoint, endpoint, "unexpected Endpoint")
require.Equal(t, tt.expectedChain, chain, "unexpected Chain")
}
})
}
}
Loading
Loading