-
Notifications
You must be signed in to change notification settings - Fork 3
Validator UI Update #365
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
Validator UI Update #365
Conversation
|
|
||
| lmrTokenAddress: process.env.LUMERIN_TOKEN_ADDRESS, | ||
| cloneFactoryAddress: process.env.CLONE_FACTORY_ADDRESS, | ||
| validatorRegistryAddress: process.env.VALIDATOR_REGISTRY_ADDRESS, | ||
|
|
||
| proxyRouterUrl: process.env.PROXY_ROUTER_URL, | ||
| explorerUrl: process.env.EXPLORER_URL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The configuration values such as lmrTokenAddress, cloneFactoryAddress, and others are directly assigned from environment variables without any validation or default values. This approach can lead to runtime errors if these variables are not set, potentially causing the application to crash or behave unpredictably.
Recommendation: Implement a validation layer that checks if these environment variables are set and are in the correct format. Provide default values where applicable and throw clear errors if critical configuration values are missing. This will enhance the robustness and maintainability of the application.
|
|
||
| bypassAuth: process.env.BYPASS_AUTH === "true", | ||
| sellerWhitelistUrl: process.env.SELLER_WHITELIST_URL || 'https://forms.gle/wEcAgppfK2p9YZ3g7' | ||
| bypassAuth: process.env.BYPASS_AUTH === "true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bypassAuth configuration is derived directly from an environment variable and converts the string 'true' to a boolean. This is a security-sensitive setting as it controls whether authentication is required. Misconfiguration or misuse of this setting could lead to unauthorized access.
Recommendation: Ensure that the handling of BYPASS_AUTH is secure. Consider implementing stricter checks or a more robust method of setting this flag, such as requiring explicit administrator intervention to enable bypassing authentication. Additionally, log any usage of this flag to monitor for potential security issues.
| @@ -366,6 +366,50 @@ function getPastTransactions({ address, page, pageSize }, { api }) { | |||
| return api.explorer.getPastCoinTransactions(0, undefined, address, page, pageSize); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function getPastTransactions uses hardcoded values 0 and undefined for the first two parameters of getPastCoinTransactions. This could lead to unexpected behavior if the API expects specific values for these parameters.
Recommendation:
Ensure that the correct values are passed to getPastCoinTransactions based on the context or requirements of the function. If these values are meant to be dynamic, consider modifying the function to accept these as parameters or set them based on the function's logic.
| if (typeof data.walletId !== "string") { | ||
| throw new WalletError("WalletId is not defined"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling for checking if data.walletId is a string is repeated in both registerValidator and deregisterValidator functions. This redundancy could be reduced by abstracting this check into a separate function or middleware.
Recommendation:
Create a utility function or middleware that performs this check and use it across all functions that require this validation. This will improve code maintainability and reduce the chance of discrepancies in error handling across different parts of the codebase.
| coreListeners[core.chain] = {}; | ||
| Object.keys(listeners).forEach(function(key) { | ||
| Object.keys(listeners).forEach(function (key) { | ||
| coreListeners[core.chain][key] = withCore(core)(listeners[key]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function subscribeSingleCore does not handle the scenario where listeners[key] might be undefined. This can lead to runtime errors if any key in listeners does not have a corresponding handler function.
Recommended Solution:
Add a check before assigning the handler to ensure that listeners[key] is defined. If not, either log a warning or skip the subscription for that key. This will prevent potential crashes or undefined behaviors.
Example:
if (listeners[key]) {
coreListeners[core.chain][key] = withCore(core)(listeners[key]);
} else {
console.warn(`No handler defined for ${key}`);
}| const validator = await getValidator({ address }).catch(err => { | ||
| context.toast('error', 'Failed to get validator'); | ||
| console.error('Failed to get validator', err); | ||
| }); | ||
| if (validator === undefined) { | ||
| return; | ||
| } | ||
| setValidator(validator); | ||
|
|
||
| if (validator === null) { | ||
| const registerStake = await getValidatorsRegisterStake().catch(err => { | ||
| context.toast('error', 'Failed to get register stake'); | ||
| console.error('Failed to get register stake', err); | ||
| }); | ||
| if (registerStake === undefined) { | ||
| return; | ||
| } | ||
| setRegisterStake(registerStake); | ||
| setStakeString(fromTokenBaseUnitsToLMR(registerStake).toString()); | ||
| } else { | ||
| const minimalStake = await getValidatorsMinimalStake().catch(err => { | ||
| context.toast('error', 'Failed to get minimal stake'); | ||
| console.error('Failed to get minimal stake', err); | ||
| }); | ||
| if (minimalStake === undefined) { | ||
| return; | ||
| } | ||
| setMinimalStake(Number(minimalStake)); | ||
| } | ||
| }; | ||
|
|
||
| setLoading(true); | ||
| fetchValidator().then(() => setLoading(false)); | ||
| }, [refreshTrigger]); | ||
|
|
||
| const onRegister = async () => { | ||
| if (isHostError || isStakeError) { | ||
| return; | ||
| } | ||
|
|
||
| setLoading(true); | ||
| const [ip, port] = host.split(':'); | ||
| try { | ||
| await isProxyPortPublic({ | ||
| ip, | ||
| port: Number(port) | ||
| }); | ||
| } catch (e) { | ||
| context.toast('error', 'Failed to check port availability'); | ||
| setLoading(false); | ||
| setHost(''); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await registerValidator({ | ||
| host, | ||
| stake: fromLMRToTokenBaseUnits(stakeString) | ||
| }); | ||
| context.toast('success', 'Validator registered successfully'); | ||
| refreshValidator(); | ||
| } catch (e) { | ||
| context.toast('error', 'Failed to register validator: ' + e.message); | ||
| console.error('Failed to register validator', e); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| const onDeregister = async () => { | ||
| try { | ||
| await deregisterValidator({ address }); | ||
| context.toast('success', 'Validator deregistered successfully'); | ||
| refreshValidator(); | ||
| } catch (e) { | ||
| context.toast('error', 'Failed to deregister validator: ' + e.message); | ||
| console.error('Failed to deregister validator', e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved Error Handling and User Feedback
The current implementation of error handling in asynchronous operations (e.g., getValidator, registerValidator, deregisterValidator) primarily logs the error and displays a generic toast notification. This approach might not provide enough information for the user to understand or rectify the issue.
Recommendation:
- Enhance error handling by categorizing errors (e.g., network errors, validation errors) and providing more descriptive, actionable error messages. This can be achieved by inspecting the error object and tailoring the message accordingly.
- Consider implementing a more robust error handling strategy that includes retries for recoverable errors, particularly for network-related issues.
| const refreshValidator = () => { | ||
| setRefreshTrigger(refreshTrigger + 1); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| const fetchValidator = async () => { | ||
| const validator = await getValidator({ address }).catch(err => { | ||
| context.toast('error', 'Failed to get validator'); | ||
| console.error('Failed to get validator', err); | ||
| }); | ||
| if (validator === undefined) { | ||
| return; | ||
| } | ||
| setValidator(validator); | ||
|
|
||
| if (validator === null) { | ||
| const registerStake = await getValidatorsRegisterStake().catch(err => { | ||
| context.toast('error', 'Failed to get register stake'); | ||
| console.error('Failed to get register stake', err); | ||
| }); | ||
| if (registerStake === undefined) { | ||
| return; | ||
| } | ||
| setRegisterStake(registerStake); | ||
| setStakeString(fromTokenBaseUnitsToLMR(registerStake).toString()); | ||
| } else { | ||
| const minimalStake = await getValidatorsMinimalStake().catch(err => { | ||
| context.toast('error', 'Failed to get minimal stake'); | ||
| console.error('Failed to get minimal stake', err); | ||
| }); | ||
| if (minimalStake === undefined) { | ||
| return; | ||
| } | ||
| setMinimalStake(Number(minimalStake)); | ||
| } | ||
| }; | ||
|
|
||
| setLoading(true); | ||
| fetchValidator().then(() => setLoading(false)); | ||
| }, [refreshTrigger]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optimization of State Management and Component Re-rendering
The refreshTrigger state is used to trigger re-fetching of validator data by incrementing its value, which indirectly causes the useEffect to execute again. This method can lead to unnecessary component re-renders and might not be the most efficient way to handle data fetching.
Recommendation:
- Instead of using a state variable to trigger effects, directly call the
fetchValidatorfunction when needed. This approach reduces the dependency on state changes for effect execution and can help in minimizing unnecessary re-renders, thus improving performance.
| const portNumber = parseInt(port); | ||
| if (portNumber < 1 || portNumber > 65535) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parseInt function is used without specifying a radix, which can lead to unexpected results if the port string contains leading zeros (interpreted as octal in some environments). This can cause incorrect validation of port numbers.
Recommended Solution:
Always specify a radix of 10 when using parseInt to ensure the string is interpreted as a decimal number:
const portNumber = parseInt(port, 10);| const domainRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; | ||
| if (!domainRegex.test(host)) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regular expression used to validate domain names is overly restrictive as it does not allow for top-level domains (TLDs) longer than 4 characters. This excludes valid TLDs such as .museum or .photography.
Recommended Solution:
Adjust the regular expression to allow for longer TLDs. For example, you could modify the regex to:
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])(\.[A-Za-z]{2,})$/| ethCoinPrice: selectors.getRateEth(state), | ||
| btcCoinPrice: selectors.getRateBtc(state), | ||
| selectedCurrency: selectors.getSellerSelectedCurrency(state), | ||
| formUrl: selectors.getSellerWhitelistForm(state), | ||
| autoAdjustPriceInterval: selectors.getAutoAdjustPriceInterval(state), | ||
| autoAdjustContractPriceTimeout: selectors.getAutoAdjustContractPriceTimeout( | ||
| state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance Optimization with Selectors
The selectors used for fetching cryptocurrency rates and settings might benefit from memoization to avoid redundant recalculations if they involve complex computations. Consider using Reselect to create memoized selectors. This can enhance performance, especially if these selectors are called frequently or involve heavy computations.
Suggestion for Consistency in Naming
The naming convention for selectors such as getRate, getRateEth, and getRateBtc could be made more consistent. Renaming getRateEth to getEthRate and similarly for Bitcoin to getBtcRate would align with the naming of getRate, enhancing readability and maintainability of the codebase.
|
|
||
| lmrTokenAddress: process.env.LUMERIN_TOKEN_ADDRESS, | ||
| cloneFactoryAddress: process.env.CLONE_FACTORY_ADDRESS, | ||
| validatorRegistryAddress: process.env.VALIDATOR_REGISTRY_ADDRESS, | ||
|
|
||
| proxyRouterUrl: process.env.PROXY_ROUTER_URL, | ||
| explorerUrl: process.env.EXPLORER_URL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue: Potential Use of Undefined Environment Variables
The configuration parameters from lines 23 to 29 are directly assigned from environment variables without any fallbacks or default values (except for proxyRouterUrl and explorerUrl). This could lead to runtime errors if any of these environment variables are not set, as the application might attempt to use these undefined values.
Recommended Solution:
Implement default values or checks to ensure that these variables are defined before use. For critical configurations that must be set, consider logging a clear error or even halting the application startup if they are missing.
|
|
||
| bypassAuth: process.env.BYPASS_AUTH === "true", | ||
| sellerWhitelistUrl: process.env.SELLER_WHITELIST_URL || 'https://forms.gle/wEcAgppfK2p9YZ3g7' | ||
| bypassAuth: process.env.BYPASS_AUTH === "true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security Issue: Bypass Authentication Feature
The bypassAuth configuration on line 60 uses an environment variable (BYPASS_AUTH) to potentially bypass authentication mechanisms. This is a significant security risk, especially if this variable is mistakenly set to true in a production environment, as it could allow unauthorized access.
Recommended Solution:
Carefully review the necessity and implementation of this feature. If it must exist, ensure it is safeguarded with additional checks or constraints, and consider using a more secure method to enable such features only in non-production environments.
| return api.explorer.getPastCoinTransactions(0, undefined, address, page, pageSize); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function getPastTransactions directly passes parameters to the api.explorer.getPastCoinTransactions without any validation or sanitization. This could lead to security vulnerabilities such as injection attacks.
Recommendation:
- Implement input validation to ensure that the parameters are in the expected format and range.
- Add error handling to manage and log potential API call failures, improving the maintainability and reliability of the function.
| const registerValidator = async (data, { api }) => { | ||
| data.walletId = wallet.getAddress().address; | ||
| data.password = await auth.getSessionPassword(); | ||
|
|
||
| if (typeof data.walletId !== "string") { | ||
| throw new WalletError("WalletId is not defined"); | ||
| } | ||
| return withAuth((privateKey) => | ||
| api["validator-registry"].registerValidator({ | ||
| ...data, | ||
| walletAddress: data.walletAddress, | ||
| privateKey, | ||
| }) | ||
| )(data, { api }); | ||
| }; | ||
|
|
||
| const deregisterValidator = async (data, { api }) => { | ||
| data.walletId = wallet.getAddress().address; | ||
| data.password = await auth.getSessionPassword(); | ||
|
|
||
| return withAuth((privateKey) => | ||
| api["validator-registry"].deregisterValidator({ | ||
| ...data, | ||
| privateKey, | ||
| }) | ||
| )(data, { api }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The functions registerValidator and deregisterValidator lack robust error handling and input validation. This could lead to unhandled exceptions and security issues if the input data is manipulated.
Recommendation:
- Validate the input data to ensure it meets the expected criteria before passing it to the API.
- Implement try-catch blocks to handle potential exceptions from the API calls, and log these exceptions for easier debugging and maintenance.
| function subscribeSingleCore(core) { | ||
| coreListeners[core.chain] = {}; | ||
| Object.keys(listeners).forEach(function(key) { | ||
| Object.keys(listeners).forEach(function (key) { | ||
| coreListeners[core.chain][key] = withCore(core)(listeners[key]); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function subscribeSingleCore lacks error handling, which could lead to unhandled exceptions if listeners[key] is undefined or if withCore(core)(listeners[key]) throws an error. Recommended Solution: Implement try-catch blocks around the critical sections of the code to handle potential exceptions gracefully and maintain the application's stability.
| const onRegister = async () => { | ||
| if (isHostError || isStakeError) { | ||
| return; | ||
| } | ||
|
|
||
| setLoading(true); | ||
| const [ip, port] = host.split(':'); | ||
| try { | ||
| await isProxyPortPublic({ | ||
| ip, | ||
| port: Number(port) | ||
| }); | ||
| } catch (e) { | ||
| context.toast('error', 'Failed to check port availability'); | ||
| setLoading(false); | ||
| setHost(''); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await registerValidator({ | ||
| host, | ||
| stake: fromLMRToTokenBaseUnits(stakeString) | ||
| }); | ||
| context.toast('success', 'Validator registered successfully'); | ||
| refreshValidator(); | ||
| } catch (e) { | ||
| context.toast('error', 'Failed to register validator: ' + e.message); | ||
| console.error('Failed to register validator', e); | ||
| } finally { | ||
| setLoading(false); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The onRegister function could benefit from enhanced error handling. While it correctly handles the asynchronous operations with try-catch blocks and provides user feedback through toasts, it does not manage the component's state in case of errors effectively. For instance, after an error, the host input is cleared (line 97), but no specific error message or state indicates why the registration failed directly in the UI. Consider maintaining an error state that can be used to display more detailed feedback in the form, helping users understand what needs to be corrected to proceed successfully.
| export function isHostPortValid(hostPort) { | ||
| const [host, port] = hostPort.split(':'); | ||
| if (!host || !port) { | ||
| return false; | ||
| } | ||
| const portNumber = parseInt(port); | ||
| if (portNumber < 1 || portNumber > 65535) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function isHostPortValid does not handle cases where the input might contain multiple colons, which is a possibility especially with IPv6 addresses or erroneous inputs. This could lead to incorrect splitting and validation of the host and port.
Recommendation:
Enhance the splitting logic to accommodate potential multiple colons and consider validating against both IPv4 and IPv6 standards. Additionally, ensure that the port number is a valid integer by checking the result of parseInt for NaN explicitly using isNaN(portNumber) before proceeding with range checks.
| function isIPAddress(ip) { | ||
| const elems = ip.split('.'); | ||
| if (elems.length !== 4) { | ||
| return false; | ||
| } | ||
| for (const elem of elems) { | ||
| const num = parseInt(elem); | ||
| if (isNaN(num)) { | ||
| return false; | ||
| } | ||
| if (num < 0 || num > 255) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function isIPAddress does not explicitly check for empty segments in the IP address, which could lead to incorrect validation if the input contains consecutive dots (e.g., '192..168.1.1').
Recommendation:
Add a check to ensure that none of the segments are empty before proceeding with the numerical validation. This can be done by adding a condition to check if elem is an empty string within the loop that processes each segment.
| ethCoinPrice: selectors.getRateEth(state), | ||
| btcCoinPrice: selectors.getRateBtc(state), | ||
| selectedCurrency: selectors.getSellerSelectedCurrency(state), | ||
| formUrl: selectors.getSellerWhitelistForm(state), | ||
| autoAdjustPriceInterval: selectors.getAutoAdjustPriceInterval(state), | ||
| autoAdjustContractPriceTimeout: selectors.getAutoAdjustContractPriceTimeout( | ||
| state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance and Modularity Issue
The mapStateToProps function calls multiple selectors individually for each cryptocurrency rate and contract setting. This approach might lead to performance issues if these selectors perform complex computations or access deeply nested properties. It could also cause redundant computations if multiple selectors compute similar data.
Recommendation: Consider creating a composite selector that fetches all necessary data in a single call. This would reduce the number of function invocations and potentially minimize the computational overhead, leading to better performance and cleaner code.
| ethCoinPrice: selectors.getRateEth(state), | ||
| btcCoinPrice: selectors.getRateBtc(state), | ||
| selectedCurrency: selectors.getSellerSelectedCurrency(state), | ||
| formUrl: selectors.getSellerWhitelistForm(state), | ||
| autoAdjustPriceInterval: selectors.getAutoAdjustPriceInterval(state), | ||
| autoAdjustContractPriceTimeout: selectors.getAutoAdjustContractPriceTimeout( | ||
| state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maintainability Issue
The current implementation of mapStateToProps maps each state property individually, which could lead to a bloated and less maintainable function as more properties are added over time. This approach can make the function difficult to manage and modify, especially in a large application.
Recommendation: Utilize a more structured approach or utility functions to map state properties. This could involve creating helper functions or using libraries that simplify the mapping process, thereby enhancing the readability and maintainability of the code.
No description provided.