Skip to content

Comments

[Test Coverage Improvement] Validator Manager Flags#2690

Merged
sukantoraymond merged 36 commits intomainfrom
validator-manager-falgs
Apr 14, 2025
Merged

[Test Coverage Improvement] Validator Manager Flags#2690
sukantoraymond merged 36 commits intomainfrom
validator-manager-falgs

Conversation

@sukantoraymond
Copy link
Contributor

@sukantoraymond sukantoraymond commented Mar 17, 2025

This PR is the first PR in Test Coverage Improvement Epic. It finds common flags across commands and consolidates them into a common file to prevent inconsistencies in flag name and values.

Consolidate flags below across all commands into 1 file.

AggregatorLogLevel
AggregatorLogToStdout
rpcURL

Removes the flags below, as they are no longer needed. They were initially implemented for the earliest implementation of ACP-77.

AggregatorExtraEndpoints
AggregatorAllowPrivatePeers

This PR is tested by manually creating an L1 on Fuji using local machine as bootstrap validator as well as using remote AWS nodes as bootstrap validators.

@sukantoraymond sukantoraymond marked this pull request as draft March 17, 2025 23:06
@sukantoraymond sukantoraymond marked this pull request as ready for review March 18, 2025 19:34
@sukantoraymond sukantoraymond changed the title Validator manager Flags [Test Coverage Improvement] Validator Manager Flags Mar 18, 2025
Copy link
Collaborator

@felipemadero felipemadero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have preferred to move the flags removal to another PR, it seems an easy step to be done, and we can focus here in the flags redesign phase

if validatorKind == validatorsdk.NonValidator {
// it may be unregistered from P-Chain, but registered on validator manager
// due to a previous partial removal operation
if rpcURL == "" {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great that we can remove these checks!

Copy link
Collaborator

@felipemadero felipemadero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making general observations and questions on this design. For @sukantoraymond @learyce to take into account. But having not strong opinion on either way, the subject is complicated and it is needed some tradeoff between being practical and having a design that takes into account all cases. Will happily agree with team opinion that surely will
keep the balance.

remainingBalanceOwnerAddr string,
disableOwnerAddr string,
sc models.Sidecar,
rpcURL string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of passing this and other global variables (flags in this case) to this function as arguments as opposed to just using them directly?

Copy link
Collaborator

@felipemadero felipemadero Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the goal is to be able to call this command from another command, and pass to it only the needed values. this concept is best represented in the tokentransferrer deploy cmd, here the implementation is partial.
secondary goal is to avoid using global variables at all: refactoring of common code, and different dev styles, showed that the risk to use/set global variables among several functions is possible and happens, so, to avoid secondary effects, and difficultty to understand the code, it is best to just not use them.

var err error
// TODO: modify check below to extend prompting for rpc to commands other than addValidator
if *rpc == "" {
if cmd.Name() == "addValidator" && len(args) == 0 {
Copy link
Contributor

@artemanosov artemanosov Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to check length of args here? Also, I think if we expect multiple cases here we should use switch on cmd.Name() and in that case addValidator should be a constant

}
return nil
}
return prompts.ValidateURLFormat(*rpc)
Copy link
Contributor

@artemanosov artemanosov Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I noticed CaptureURL is doing validation (which is not explicitly mentioned, even though I think it should be) by calling ValidateURL which calls ValidateURLFormat and also calls the url expecting 2xx. I was wondering why don't we use ValidateURL here instead of ValidateUrlFormat?

addValidatorFlags BlockchainAddValidatorFlags
)

type BlockchainAddValidatorFlags struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of grouping these flags here?

Copy link
Collaborator

@felipemadero felipemadero Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • to isolate variables among commands with the same flag, because cobra initializes all commands in sequence and
    the variable retains only the value of the command that was last initialized (a conflict for command that have different initialization values)
  • to be able in a subsequent PR, to make clean calls into the command logic from another command, by passing the flags object to it (see eg cmd/interchaincmd/tokentransaferrercmd/deploy.go for CallDeploy)
    the idea is for this struct to contain all command flags in the future

}
}

if sc.Sovereign && removeValidatorFlags.RPC == "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we add sc.Sovereign check?

if rpcURL == "" {
rpcURL, err = app.Prompt.CaptureURL("What is the RPC endpoint?", false)
if localValidateFlags.RPC == "" {
localValidateFlags.RPC, err = app.Prompt.CaptureURL("What is the RPC endpoint?", false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since in RPC flag validation we use command specific logic, I think this validation should be done there (like we do for add_validator

Copy link
Collaborator

@felipemadero felipemadero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall idea LGTM. Will approve once all comments for other team members are addressed.

@artemanosov
Copy link
Contributor

artemanosov commented Apr 10, 2025

I strongly believe that we should not mix in special to commands logic inside preRunE ("middleware") functions. Main reason is that if we start putting special to commands logic inside “middleware” function, it can grow ugly with many if/else blocks and will be hard to read. Example:


if cmd.Name() == “AddValidator” || cmd.Name() == “RemoveValidator” || cmd.Name() == “CreateBlockchain” {
  
  // do some common validation
  
  if cmd.Name() == “AddValidation” {
    
    // special to AddValidation logic
   
  }

  
  
  if cmd.Name() == “RemoveValidator” || cmd.Name() == “CreateBlockchain” {
   
    // common logic here
  
  } 

  
  
  if cmd.Name() == “CreateBlockchain” {
  
    // special logic here
  }
  
    // the rest of the validation
}
  

While if we decide to use switch for easier read, there will be redundancy if two commands have most of the special validation logic the same but with one small difference. This is a bit exaggerated example, but I think we could run into some similar case. Another drawback of such design is that it hides the details of special validation logic, thus potentially misleading someone who’s gonna read the code, since some validation will be abstracted away in the flag validation "middleware" and not shown in the linear flow inside the command execution function. Finally, it will be easier to make mistakes since validation is done in a separate place from the actual command execution logic, thus some validation or nuances may be missed.

I propose two alternative approaches for validating input and reducing redundancy that we can go with:

  1. Hybrid
  2. Separation of concerns

Hybrid: First, make validation of a flag optional and regulated using an argument. Second, validation of flag inside preRunE ("middleware") function should be simple and not require any special logic for a command. Third, create validation functions for each flag in a shared package (ex. ../flags/validation) and use those whenever we need a special logic for validation with turned off validation in “middleware”.

Separation of concerns: Alternative design would be to just separate flags values collection and validation completely. Then validation functions will be defined in shared package and called depending on the need from the command execution functions.

Example of simple validation function:

func ValidateRPCURL(app *application.Avalanche, rpcURL string) (err error) {
	if rpcURL == "" {
		return errors.New("RPC URL cannot be empty")
	}

	return prompts.ValidateURL(rpcURL)
}

@sukantoraymond @felipemadero Please let me know what you guys think

@felipemadero
Copy link
Collaborator

I strongly believe that we should not mix in special to commands logic inside preRunE ("middleware") functions. Main reason is that if we start putting special to commands logic inside “middleware” function, it can grow ugly with many if/else blocks and will be hard to read. Example:


if cmd.Name() == “AddValidator” || cmd.Name() == “RemoveValidator” || cmd.Name() == “CreateBlockchain” {
  
  // do some common validation
  
  if cmd.Name() == “AddValidation” {
    
    // special to AddValidation logic
   
  }

  
  
  if cmd.Name() == “RemoveValidator” || cmd.Name() == “CreateBlockchain” {
   
    // common logic here
  
  } 

  
  
  if cmd.Name() == “CreateBlockchain” {
  
    // special logic here
  }
  
    // the rest of the validation
}
  

While if we decide to use switch for easier read, there will be redundancy if two commands have most of the special validation logic the same but with one small difference. This is a bit exaggerated example, but I think we could run into some similar case. Another drawback of such design is that it hides the details of special validation logic, thus potentially misleading someone who’s gonna read the code, since some validation will be abstracted away in the flag validation "middleware" and not shown in the linear flow inside the command execution function. Finally, it will be easier to make mistakes since validation is done in a separate place from the actual command execution logic, thus some validation or nuances may be missed.

I propose two alternative approaches for validating input and reducing redundancy that we can go with:

  1. Hybrid
  2. Separation of concerns

Hybrid: First, make validation of a flag optional and regulated using an argument. Second, validation of flag inside preRunE ("middleware") function should be simple and not require any special logic for a command. Third, create validation functions for each flag in a shared package (ex. ../flags/validation) and use those whenever we need a special logic for validation with turned off validation in “middleware”.

Separation of concerns: Alternative design would be to just separate flags values collection and validation completely. Then validation functions will be defined in shared package and called depending on the need from the command execution functions.

Example of simple validation function:

func ValidateRPCURL(app *application.Avalanche, rpcURL string) (err error) {
	if rpcURL == "" {
		return errors.New("RPC URL cannot be empty")
	}

	return prompts.ValidateURL(rpcURL)
}

@sukantoraymond @felipemadero Please let me know what you guys think

Totally agree with the problem. And this comment was very readable +1!

I would propose something along this line (really the idea here is to continue the discussion):

  • Have a separated shared package for validation functions in general, not only used for flags but also for prompts. Currently we have good amount of validation functions on pkg/prompts/, would rather move it to something like pkg/validations/.

  • Have cmd/flags package (maybe move it to pkg/flags?) to provide:

    • a way of keep track of the set of flags of a command, and set up the command with that set
    • standard flag names, datatypes, descriptions, default values
    • provide a way to set a different default value if needed by the command
    • group of flags definition. a group of flags may all relate to the underlying same value, so it may be queried for it, finding if it was indeed defined on flags, and which was the value.
    • default validation for the full set of flags (eg we want to avoid conflicting flags to be defined at the same time)
  • Have pkg/inputs package, that manage both flags, prompts and validations. To be initialized with the cmdline flags. Containing default validation functions for all flags, but that can be set up (specialization) by the command. Containing default prompt function for all flags, but that can set up (specialization) by the command. With a initial step that checks all flags provided in command line to be valid and either ends execution with error or prompts the user for the invalid values (configurable). Afterwards, it works under cmd request, because the cmd knows when and why to ask for an input (that most generally depends on the previous inputs). So the cmd ask inputs pkg for such an input:

  1. if the user did not provided the value on flags.
    1a) if the default flag value is marked as acceptable (configured at inputs pkg level), tries to validate, if validation do not pass, prompts
    1b) if the default flag value is not marked as acceptable, prompt for it
  2. if the user provided the value on flags, try to validate it, if validation do not pass, prompts
  • Have a way to pass validation functions of interest, located either on pkg/validation, or on some specific-named (if really too specialized, eg validations.go) file belonging to the command code, to the inputs pkg. If not passed, default validation is used.
  • Same for prompts.

@felipemadero
Copy link
Collaborator

I strongly believe that we should not mix in special to commands logic inside preRunE ("middleware") functions. Main reason is that if we start putting special to commands logic inside “middleware” function, it can grow ugly with many if/else blocks and will be hard to read. Example:
>

if cmd.Name() == “AddValidator” || cmd.Name() == “RemoveValidator” || cmd.Name() == “CreateBlockchain” {
>   
  // do some common validation
>   
  if cmd.Name() == “AddValidation” {
>     
    // special to AddValidation logic
>    
  }
> 
>   
  
  if cmd.Name() == “RemoveValidator” || cmd.Name() == “CreateBlockchain” {
>    
    // common logic here
>   
  } 
> 
>   
  
  if cmd.Name() == “CreateBlockchain” {
>   
    // special logic here
  }
>   
    // the rest of the validation
}
  

While if we decide to use switch for easier read, there will be redundancy if two commands have most of the special validation logic the same but with one small difference. This is a bit exaggerated example, but I think we could run into some similar case. Another drawback of such design is that it hides the details of special validation logic, thus potentially misleading someone who’s gonna read the code, since some validation will be abstracted away in the flag validation "middleware" and not shown in the linear flow inside the command execution function. Finally, it will be easier to make mistakes since validation is done in a separate place from the actual command execution logic, thus some validation or nuances may be missed.
I propose two alternative approaches for validating input and reducing redundancy that we can go with:

  1. Hybrid
  2. Separation of concerns

Hybrid: First, make validation of a flag optional and regulated using an argument. Second, validation of flag inside preRunE ("middleware") function should be simple and not require any special logic for a command. Third, create validation functions for each flag in a shared package (ex. ../flags/validation) and use those whenever we need a special logic for validation with turned off validation in “middleware”.
Separation of concerns: Alternative design would be to just separate flags values collection and validation completely. Then validation functions will be defined in shared package and called depending on the need from the command execution functions.
Example of simple validation function:

func ValidateRPCURL(app *application.Avalanche, rpcURL string) (err error) {
	if rpcURL == "" {
		return errors.New("RPC URL cannot be empty")
	}

	return prompts.ValidateURL(rpcURL)
}

@sukantoraymond @felipemadero Please let me know what you guys think

Totally agree with the problem. And this comment was very readable +1!

I would propose something along this line (really the idea here is to continue the discussion):

  • Have a separated shared package for validation functions in general, not only used for flags but also for prompts. Currently we have good amount of validation functions on pkg/prompts/, would rather move it to something like pkg/validations/.

  • Have cmd/flags package (maybe move it to pkg/flags?) to provide:

    • a way of keep track of the set of flags of a command, and set up the command with that set
    • standard flag names, datatypes, descriptions, default values
    • provide a way to set a different default value if needed by the command
    • group of flags definition. a group of flags may all relate to the underlying same value, so it may be queried for it, finding if it was indeed defined on flags, and which was the value.
    • default validation for the full set of flags (eg we want to avoid conflicting flags to be defined at the same time)
  • Have pkg/inputs package, that manage both flags, prompts and validations. To be initialized with the cmdline flags. Containing default validation functions for all flags, but that can be set up (specialization) by the command. Containing default prompt function for all flags, but that can set up (specialization) by the command. With a initial step that checks all flags provided in command line to be valid and either ends execution with error or prompts the user for the invalid values (configurable). Afterwards, it works under cmd request, because the cmd knows when and why to ask for an input (that most generally depends on the previous inputs). So the cmd ask inputs pkg for such an input:

  1. if the user did not provided the value on flags.
    1a) if the default flag value is marked as acceptable (configured at inputs pkg level), tries to validate, if validation do not pass, prompts
    1b) if the default flag value is not marked as acceptable, prompt for it
  2. if the user provided the value on flags, try to validate it, if validation do not pass, prompts
  • Have a way to pass validation functions of interest, located either on pkg/validation, or on some specific-named (if really too specialized, eg validations.go) file belonging to the command code, to the inputs pkg. If not passed, default validation is used.
  • Same for prompts.

Would like clarity something. Cobra requires default values for all cmdline flags. But on CLI code, we use flags in one of this two different ways:

    1. either the default value is used, or a cmdline flag overwrites it, without prompting (DEFAULT OR FLAG)
    1. the default value is not used, so either a cmdline flag was received, or we prompt the user (FLAG OR PROMPT)

That is reason why I made comments such as "the default flag value is not, marked as acceptable" (because cobra always require a default value).

--Note: in 2) an explicit user action is always required only if the value is found to be needed by the cmd flow--

So I believe we can have an input abstraction, of different "types" as given by:

  • flag (or group of flags) that can be queried if the value was set, and obtain the value
  • default value (or not)
  • default validation function
  • default prompt function

Where the last 3 can be modified by the user when instantiating it (eg remove default value or change it)

So the command spec should start by defining a list of input instances for the command. Then use a function to
automatically add the flags associated to those inputs to the cmd.
Then at start of command execution do automatic validation of flags (mutual exclusions and value validations)
Then let the cmd ask for the different inputs on due time.

With some rules:

  • if a default value is defined, use validation function on it, if validation fails, quit with error (input package)
  • when starting, validate all given flag regarding prohibited flag combinations (flag package)
  • when starting, validate all given flag values, if someone is invalid, quit with error (input package)
  • when asked for the value: (input package)
    • if default value is defined, use it, unless a flag value was given
    • if no default value is defined, use a flag value if defined, if not, prompt (and validate prompt)

Tell me what do you think on this last piece (ofc validation functions should be on pkg/validations, prompt functions
on pkg/prompts, flag definitions on pkg/flags and input definitions on pkg/inputs)

@artemanosov
Copy link
Contributor

artemanosov commented Apr 11, 2025

Reply to the first comment from @felipemadero:
I agree, prompts should not have built in validation. I would argue that this structure is better: internal/input/flags, internal/input/prompts and internal/input/validation (this way we indicate that this kind of input processing is specific to this avalanche-cli repo and cannot be imported. Also, everything related to input is grouped under the same path)

The /flags should contain all possible flags, while /validation would contain validation for them. The reason for having all flags under this package is that if we put there only the flags that are reused, it may create inconsistencies, since some non-reused flags may have validation and may also need a prompt. Also every new flag will have to be checked first to make sure it's does not exist, which in this case I think may cause mistakes and redundancy.

Agreed, all basic flag related things should be defined in that package: standard flag names, datatypes, descriptions, default values, as well as provide a way to set a different default value if needed by the command and grouping the flags. I am not sure about having default validation in flags package. As I mentioned, we should decide if we want to completely separate the validation from data collection.

for pkg/inputs, I think this stuff will be defined in the command files. Since they are command specific, I think the logic should be shown in those files for logical integrity and explicitness. Additionally, based on looking at the code for some time, the validation is often quite complex and depends on some processing done in command execution functions. That is why I think internal/input packages should provide just the tools and be simple, while command execution function should use these tools, but maintain the linear explicit logic. That said, I also wonder if it's possible to not call prompts from command execution functions (maybe by using a some input argument to a flag to prompt or not)? I believe we should collect all inputs at first and then do the validation as opposed to getting all the flags, then do some processing, then check if we still need to prompt etc. (if the user did not provided the value on flags. 1a) if the default flag value is marked as acceptable (configured at inputs pkg level), tries to validate, if validation do not pass, prompts 1b) if the default flag value is not marked as acceptable, prompt for it I think this can replace calling prompts from commands files)

if the user provided the value on flags, try to validate it, if validation do not pass, prompts, I don't think we should prompt in that case, instead just return an error so user retries the command with "better" input.

Not a fan of higher order functions in golang (Have a way to pass validation functions of interest, located either on pkg/validation, or on some specific-named (if really too specialized, eg validations.go) file belonging to the command code, to the inputs pkg. If not passed, default validation is used.) I think it's better to just call all the "tools" for processing/validating input inside command execution function (maybe extract it into validateInput function) rather than passing a function with special logic to a flag. But that's my subjective opinion, we can debate this.

@artemanosov
Copy link
Contributor

artemanosov commented Apr 11, 2025

Reply to second comment:
If we decide to combine simple validation with input collection than I aggre that we should have flag creating functions accept parameters: defaultValue, prompt, validation. Prompt would be a pointer to string, so if it's nil, means not use prompt. Validation is simple validation and regulated with a boolean flag. End result looks like this:

func AddSomeFlag(defaultValue string, promptString *string, useValidation boolean) {

// add a flag with default value here


   if promptString != nil {
	

      set prompt function in middleware


   }

   if useValidation {
	
      set validation function in middleware

   }

}

Otherwise, if input collection and validation is done separately, I would use the same function but without useValidation input argument. The values will be collected with our without prompt, while validation will be done separately at a later stage.

Signed-off-by: sukantoraymond <rsukanto@umich.edu>
@felipemadero
Copy link
Collaborator

Reply to the first comment from @felipemadero: I agree, prompts should not have built in validation. I would argue that this structure is better: internal/input/flags, internal/input/prompts and internal/input/validation (this way we indicate that this kind of input processing is specific to this avalanche-cli repo and cannot be imported. Also, everything related to input is grouped under the same path)

The /flags should contain all possible flags, while /validation would contain validation for them. The reason for having all flags under this package is that if we put there only the flags that are reused, it may create inconsistencies, since some non-reused flags may have validation and may also need a prompt. Also every new flag will have to be checked first to make sure it's does not exist, which in this case I think may cause mistakes and redundancy.

Agreed, all basic flag related things should be defined in that package: standard flag names, datatypes, descriptions, default values, as well as provide a way to set a different default value if needed by the command and grouping the flags. I am not sure about having default validation in flags package. As I mentioned, we should decide if we want to completely separate the validation from data collection.

for pkg/inputs, I think this stuff will be defined in the command files. Since they are command specific, I think the logic should be shown in those files for logical integrity and explicitness. Additionally, based on looking at the code for some time, the validation is often quite complex and depends on some processing done in command execution functions. That is why I think internal/input packages should provide just the tools and be simple, while command execution function should use these tools, but maintain the linear explicit logic. That said, I also wonder if it's possible to not call prompts from command execution functions (maybe by using a some input argument to a flag to prompt or not)? I believe we should collect all inputs at first and then do the validation as opposed to getting all the flags, then do some processing, then check if we still need to prompt etc. (if the user did not provided the value on flags. 1a) if the default flag value is marked as acceptable (configured at inputs pkg level), tries to validate, if validation do not pass, prompts 1b) if the default flag value is not marked as acceptable, prompt for it I think this can replace calling prompts from commands files)

if the user provided the value on flags, try to validate it, if validation do not pass, prompts, I don't think we should prompt in that case, instead just return an error so user retries the command with "better" input.

Not a fan of higher order functions in golang (Have a way to pass validation functions of interest, located either on pkg/validation, or on some specific-named (if really too specialized, eg validations.go) file belonging to the command code, to the inputs pkg. If not passed, default validation is used.) I think it's better to just call all the "tools" for processing/validating input inside command execution function (maybe extract it into validateInput function) rather than passing a function with special logic to a flag. But that's my subjective opinion, we can debate this.

I believe there is a base for a new design here, at least for default cases of validation and prompting. But the purpose of this PR is not to discuss this. #2736

Regarding dir location, also to be discussed and addressed somewhere. #2737

I personally like having all together under a main inputs package, but should put it on pkg, because we already use pkg for a lot of internal stuff, and really it is expected for
sdk/ to cover external usage.

Copy link
Collaborator

@felipemadero felipemadero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preaproving, but lets move command dependent logic back to the command.

logName string,
logLevelStr string,
defaultLogLevelStr string,
aggregatorLogLevel string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we may want to keep this function a bit more generic, not so aggregator oriented.
It may be possible to use it on another packages. Probably it can be used to set up main app
log with a bit of refactor. For the moment lets keep the previos more generic arg names

Copy link
Contributor Author

@sukantoraymond sukantoraymond Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abstracted into NewSignatureAggregatorLogger


func ValidateRPC(app *application.Avalanche, rpc *string, cmd *cobra.Command, args []string) error {
var err error
// TODO: modify check below to extend prompting for rpc to commands other than addValidator
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets avoid command dependent logic here, and defer how to do automatic prompting to following up work. #2736

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are moving all input collection to cmd/flags. This will be done in subsequent PRs

@github-project-automation github-project-automation bot moved this from Backlog 🗄️ to In Review 👀 in avalanchego Apr 12, 2025
@sukantoraymond sukantoraymond merged commit 44d1454 into main Apr 14, 2025
35 of 36 checks passed
@github-project-automation github-project-automation bot moved this from In Review 👀 to Done ✅ in avalanchego Apr 14, 2025
@sukantoraymond sukantoraymond deleted the validator-manager-falgs branch April 14, 2025 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants