diff --git a/.changelog/3493.feature..md b/.changelog/3493.feature..md new file mode 100644 index 00000000000..8d49f85b103 --- /dev/null +++ b/.changelog/3493.feature..md @@ -0,0 +1,3 @@ +go/oasis-node/cmd: Add `oasis-node stake account validate_address` CLI command + +It can be used to validate a staking account's address. diff --git a/go/oasis-node/cmd/stake/account.go b/go/oasis-node/cmd/stake/account.go index 37582aad457..46788a74b4c 100644 --- a/go/oasis-node/cmd/stake/account.go +++ b/go/oasis-node/cmd/stake/account.go @@ -79,6 +79,12 @@ var ( Run: doAccountNonce, } + accountValidateAddressCmd = &cobra.Command{ + Use: "validate_address", + Short: "validate account address", + Run: doValidateAddress, + } + accountTransferCmd = &cobra.Command{ Use: "gen_transfer", Short: "generate a transfer transaction", @@ -178,6 +184,29 @@ func doAccountNonce(cmd *cobra.Command, args []string) { fmt.Println(acct.General.Nonce) } +func doValidateAddress(cmd *cobra.Command, args []string) { + if err := cmdCommon.Init(); err != nil { + cmdCommon.EarlyLogAndExit(err) + } + + addrStr := viper.GetString(CfgAccountAddr) + var addr api.Address + err := addr.UnmarshalText([]byte(addrStr)) + + switch cmdFlags.Verbose() { + case true: + if err != nil { + fmt.Printf("account address '%s' is not valid: %v\n", addrStr, err) + os.Exit(1) + } + fmt.Printf("account address '%s' is valid", addrStr) + default: + if err != nil { + os.Exit(1) + } + } +} + func doAccountTransfer(cmd *cobra.Command, args []string) { if err := cmdCommon.Init(); err != nil { cmdCommon.EarlyLogAndExit(err) @@ -431,6 +460,7 @@ func registerAccountCmd() { for _, v := range []*cobra.Command{ accountInfoCmd, accountNonceCmd, + accountValidateAddressCmd, accountTransferCmd, accountBurnCmd, accountEscrowCmd, @@ -444,6 +474,8 @@ func registerAccountCmd() { accountInfoCmd.Flags().AddFlagSet(commonAccountFlags) accountNonceCmd.Flags().AddFlagSet(commonAccountFlags) + accountValidateAddressCmd.Flags().AddFlagSet(commonAccountFlags) + accountValidateAddressCmd.Flags().AddFlagSet(cmdFlags.VerboseFlags) accountTransferCmd.Flags().AddFlagSet(accountTransferFlags) accountBurnCmd.Flags().AddFlagSet(accountBurnFlags) accountEscrowCmd.Flags().AddFlagSet(commonEscrowFlags) diff --git a/go/oasis-test-runner/scenario/e2e/stake_cli.go b/go/oasis-test-runner/scenario/e2e/stake_cli.go index 39851546078..fa120fe8df4 100644 --- a/go/oasis-test-runner/scenario/e2e/stake_cli.go +++ b/go/oasis-test-runner/scenario/e2e/stake_cli.go @@ -231,6 +231,35 @@ func (sc *stakeCLIImpl) Run(childEnv *env.Env) error { } } + // Ensure validating account addresses works. + addressWithSpaces := "oasis1 qzm9 xjzq gsdc zc64 v3zp 8jkf ekx7 n8kh y502 pxwq" + validateAddressTestVectors := []struct { + addressText string + expectError bool + }{ + {srcAddress.String(), false}, + {escrowAddress.String(), false}, + // Empty address should be invalid. + {"", true}, + // Address with spaces should be invalid. + {addressWithSpaces, true}, + // Same address without spaces should be valid. + {strings.ReplaceAll(addressWithSpaces, " ", ""), false}, + // Hex-formatted public keys should be invalid. + {srcPubkeyHex, true}, + {escrowPubkeyHex, true}, + } + sc.Logger.Info("test validation of staking account addresses") + for _, vector := range validateAddressTestVectors { + err = sc.testValidateAddress(childEnv, vector.addressText) + if err != nil && !vector.expectError { + return fmt.Errorf("unexpected validate_address error: %w", err) + } + if err == nil && vector.expectError { + return fmt.Errorf("validate_address for address '%s' should error", vector.addressText) + } + } + // Transfer if err = sc.testTransfer(childEnv, cli, srcAddress, beneficiaryAddress); err != nil { return fmt.Errorf("error while running Transfer test: %w", err) @@ -293,6 +322,21 @@ func (sc *stakeCLIImpl) testPubkey2Address(childEnv *env.Env, publicKeyText, add return nil } +func (sc *stakeCLIImpl) testValidateAddress(childEnv *env.Env, addressText string) error { + args := []string{ + "stake", "account", "validate_address", + "--verbose", + "--" + stake.CfgAccountAddr, addressText, + } + + out, err := cli.RunSubCommandWithOutput(childEnv, sc.Logger, "info", sc.Net.Config().NodeBinary, args) + if err != nil { + return fmt.Errorf("failed to validate account address: error: %w output: %s", err, out.String()) + } + + return nil +} + // testTransfer tests transfer of transferAmount base units from src to dst. func (sc *stakeCLIImpl) testTransfer(childEnv *env.Env, cli *cli.Helpers, src, dst api.Address) error { srcNonce, err := sc.getAccountNonce(childEnv, src)