-
Notifications
You must be signed in to change notification settings - Fork 3
Dev #75
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
base: stg
Are you sure you want to change the base?
Conversation
feat: new proxy api
feat: contract version
* add support for arbitrum * add missing break * increment version
…#76) * use overloaded web3 instance instead of single provider. required for cycling through multiple public nodes * increment version
new retry implementation web3
src/plugins/contracts/index.js
Outdated
| const { lmrTokenAddress, cloneFactoryAddress } = config | ||
| const { eth } = plugins | ||
|
|
||
| const web3 = new Web3(eth.web3Provider) | ||
| const web3 = eth.web3 | ||
| const web3Subscriptionable = new Web3(plugins.eth.web3SubscriptionProvider) | ||
|
|
||
| const lumerin = Lumerin(web3, lmrTokenAddress) |
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 code is creating a new instance of Web3 on line 42, which could potentially lead to performance issues if this operation is expensive and this code is called frequently. It's also not clear if the web3SubscriptionProvider is being properly managed elsewhere in the code. If it's not, this could lead to resource leaks.
To address these issues, consider reusing the web3 instance if possible, or ensure that the new instance is necessary and justified. Also, make sure that the web3SubscriptionProvider is being properly managed and closed when it's no longer needed.
src/plugins/contracts/index.js
Outdated
| return { | ||
| api: { | ||
| refreshContracts: refreshContracts(web3, lumerin, cloneFactory), | ||
| createContract: createContract(web3, cloneFactory, plugins), | ||
| cancelContract: cancelContract(web3), | ||
| createContract: createContract(web3, cloneFactory), | ||
| cancelContract: cancelContract(web3, cloneFactory), | ||
| purchaseContract: purchaseContract(web3, cloneFactory, lumerin), | ||
| editContract: editContract(web3, cloneFactory, lumerin), | ||
| getMarketplaceFee: getMarketplaceFee(cloneFactory), | ||
| setContractDeleteStatus: setContractDeleteStatus( | ||
| web3, | ||
| cloneFactory, |
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 refreshContracts, createContract, cancelContract, purchaseContract, editContract, getMarketplaceFee, and setContractDeleteStatus are all being passed the same or similar arguments (web3, cloneFactory, lumerin). This could be a sign that these functions could be methods of a class that holds these as member variables.
Consider refactoring these functions into methods of a class. This would make the code more modular and easier to maintain.
| purchaseContract, | ||
| setContractDeleteStatus, | ||
| editContract, | ||
| getMarketplaceFee | ||
| } = require('./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 code is using destructuring to import several functions from './api'. However, it's not clear if error handling is being done properly in these functions. If any of these functions throw an error, it could potentially crash the entire application.
Ensure that proper error handling is implemented in these functions. This could be done by wrapping the function bodies in try-catch blocks and handling any errors appropriately.
| web3, | ||
| web3Provider: web3.currentProvider, | ||
| web3SubscriptionProvider: web3SubscribAble.currentProvider, |
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 code is directly exposing the web3 and web3Provider objects. This could potentially lead to security issues as it allows direct access to the web3 provider and could be misused. Instead of directly exposing these objects, consider providing a limited API that only exposes the necessary methods and properties. This way, you can control what operations are allowed and prevent potential misuse.
| function createWeb3(config) { | ||
| // debug.enabled = config.debug | ||
|
|
||
| providers = config.httpApiUrls.map((url) => { | ||
| return new Web3.providers.HttpProvider(url, { | ||
| agent: new https.Agent({ | ||
| rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate | ||
| }), | ||
| }) | ||
| }) | ||
|
|
||
| const web3 = new Web3(providers[0], { | ||
| agent: new https.Agent({ | ||
| rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate | ||
| }), | ||
| }) | ||
|
|
||
| overrideFunctions(web3, providers) | ||
| overrideFunctions(web3.eth, providers) | ||
| web3.subscriptionProvider = subscriptionProvider | ||
|
|
||
| const web3 = new Web3Http(config.httpApiUrls) | ||
| return web3 | ||
| } |
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 createWeb3 function creates a new instance of Web3Http but does not handle any potential errors that might occur during this process. This could lead to unhandled exceptions if, for example, the config.httpApiUrls is not valid or if there are network issues. To improve error handling, consider wrapping the creation of the Web3Http instance in a try-catch block and handle any errors appropriately.
|
|
||
| function destroyWeb3(web3) { | ||
| web3.currentProvider.disconnect() | ||
| web3.currentProvider?.disconnect() |
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 destroyWeb3 function calls the disconnect method on the currentProvider of the web3 object. However, it does not check if the currentProvider is null or undefined before calling disconnect. This could lead to a runtime error if currentProvider is not defined. Consider adding a null check before calling the disconnect method.
|
|
||
| const logger = require('../../logger'); | ||
|
|
||
| const Web3 = require('web3') |
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 Web3 module is imported but never used in the provided code fragment. This could potentially lead to confusion and unnecessary overhead. Consider removing unused imports to improve code readability and maintainability.
| (provider) => | ||
| new Web3.providers.HttpProvider(provider, { | ||
| agent: new https.Agent({ | ||
| rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate |
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 rejectUnauthorized option is set to false which means that the application will not reject any unauthorized certificates. This can lead to potential security risks such as Man-in-the-Middle (MitM) attacks where an attacker can intercept and possibly alter the communication between two parties without them knowing. It's recommended to set rejectUnauthorized to true in production environments to ensure that only authorized certificates are accepted. If you're using self-signed certificates for development, consider using a solution that allows you to trust your own certificates without disabling this important security feature.
| this.currentProvider.send = (payload, callback) => { | ||
| originalSend(payload, (error, response) => { | ||
| if (error) { | ||
| // Avoid infinite loop | ||
| if (this.retryCount >= this.providers.length) { | ||
| callback(error, null) | ||
| this.retryCount = 0 | ||
| return; | ||
| } | ||
| // If the request fails, switch to the next provider and try again | ||
| this.currentIndex = (this.currentIndex + 1) % this.providers.length | ||
| this.setCustomProvider(this.providers[this.currentIndex]) | ||
| logger.error( | ||
| `Switched to provider: ${this.providers[this.currentIndex].host}` | ||
| ) | ||
| this.retryCount += 1 | ||
| this.currentProvider.send(payload, callback) // Retry the request | ||
| } else { | ||
| this.retryCount = 0 | ||
| callback(null, response) | ||
| } | ||
| }) | ||
| } |
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 current implementation of the retry mechanism can lead to a stack overflow if the number of providers is large and all of them are failing. This is because each retry is done recursively by calling this.currentProvider.send(payload, callback) inside the error handling block. A better approach would be to use a loop instead of recursion for retries. This way, you avoid the risk of a stack overflow and make the code easier to understand and maintain.
| setProviderOptions(options) { | ||
| this.currentProvider.host = options.host || this.currentProvider.host | ||
| this.currentProvider.timeout = | ||
| options.timeout || this.currentProvider.timeout | ||
| } |
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 setProviderOptions method directly modifies the properties of this.currentProvider. This can lead to unexpected behavior if other parts of the code rely on the original values of these properties. Instead of directly modifying these properties, consider creating a new provider with the updated options and setting it as the current provider. This way, you ensure that the original provider's properties remain unchanged, which can help prevent potential bugs and make the code easier to reason about.
| case '421613': | ||
| baseURL = 'https://api-goerli.arbiscan.io/api' | ||
| break | ||
| case '42161': | ||
| baseURL = 'https://api.arbiscan.io/api' | ||
| break | ||
| default: | ||
| throw new Error(`Unsupported chain ${chainId}`) |
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 current approach of hardcoding the URLs for different chain IDs is not maintainable. If the URLs change in the future, you will need to update the code and redeploy the application. It would be better to store these URLs in a configuration file or environment variables. This way, you can easily update the URLs without changing the code.
src/plugins/contracts/index.js
Outdated
| const onUpdate = refreshContracts(web3, lumerin, cloneFactory) | ||
| contractEventsListener.setOnUpdate(onUpdate) |
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 onUpdate function is created by calling refreshContracts with certain parameters and then immediately passed to contractEventsListener.setOnUpdate. However, the same function is created again on line 93 with the same parameters, which is redundant and could lead to unnecessary memory usage. It would be more efficient to create the function once and reuse it wherever needed.
src/plugins/contracts/index.js
Outdated
| refreshContracts: refreshContractsFn, | ||
| createContract: createContract(web3, cloneFactory), | ||
| cancelContract: cancelContract(web3, cloneFactory), | ||
| purchaseContract: (params) => purchaseContractFn(params).then(() => refreshContractsFn(params.contractId, params.walletId)), |
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 purchaseContract function is being called with params and upon completion, it calls refreshContractsFn with params.contractId and params.walletId. However, the refreshContractsFn is originally defined to take three parameters, but only two are being passed here. This could potentially lead to unexpected behavior if refreshContractsFn relies on the third parameter. Ensure that the correct number of arguments is passed to the function, and if the third argument is optional, handle its absence appropriately within the refreshContracts function.
| .on('data', async () => { | ||
| logger.debug(`Contract (${id}) updated`) | ||
| if (this.onUpdate){ | ||
| await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
| this.onUpdate(id, this.walletAddress || walletAddress) | ||
| } |
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 use of an arbitrary delay with setTimeout inside the 'data' event listener could lead to potential performance issues, especially if the event is emitted frequently. This delay seems to be used to throttle the calls to this.onUpdate, but it could block the event loop if the 'data' event is emitted in rapid succession. Instead of using a fixed delay, consider implementing a more robust throttling or debouncing mechanism that can handle bursts of events efficiently without unnecessarily delaying the event handling.
| .on('data', async (event) => { | ||
| const contractId = event.returnValues._address | ||
| logger.debug('New contract created', contractId) | ||
| await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
| this.onUpdate(contractId, this.walletAddress) | ||
| }) |
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.
Similar to the previous issue, there is an arbitrary delay introduced with setTimeout in the 'data' event listener for the clone factory events. This can cause performance bottlenecks and does not provide a controlled way to handle bursts of events. It is recommended to replace the fixed delay with a throttling or debouncing mechanism that can manage the event flow more effectively, ensuring that this.onUpdate is not called more often than necessary.
src/plugins/contracts/index.js
Outdated
| const refreshContractsFn = refreshContracts(web3, lumerin, cloneFactory) | ||
| const purchaseContractFn = purchaseContract(web3, cloneFactory, lumerin) | ||
| const cancelContractFn = cancelContract(web3, cloneFactory) | ||
| return { | ||
| api: { | ||
| refreshContracts: refreshContracts(web3, lumerin, cloneFactory), | ||
| createContract: createContract(web3, cloneFactory, plugins), | ||
| cancelContract: cancelContract(web3), | ||
| purchaseContract: purchaseContract(web3, cloneFactory, lumerin), | ||
| refreshContracts: refreshContractsFn, | ||
| createContract: createContract(web3, cloneFactory), | ||
| cancelContract: cancelContractFn, | ||
| purchaseContract: purchaseContractFn, | ||
| editContract: editContract(web3, cloneFactory, lumerin), |
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 refreshContracts function is being initialized twice, which is unnecessary and could lead to performance issues if the initialization is resource-intensive. The first initialization is on line 90, and the second one is on line 93. It would be more efficient to initialize it once and reuse the same instance.
Recommended solution: Initialize the refreshContracts function only once and reuse the same instance for both onUpdate and refreshContracts in the API object. This will save resources and avoid potential performance degradation.
fix: additional error handling, more logs, more retries
| (provider) => | ||
| new Web3.providers.HttpProvider(provider, { | ||
| agent: new https.Agent({ | ||
| rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate |
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 rejectUnauthorized option is set to false, which disables TLS/SSL certificate validation. This is a severe security vulnerability as it allows the client to accept self-signed certificates or certificates from untrusted authorities, making the connection susceptible to man-in-the-middle attacks. To maintain secure communication, the rejectUnauthorized option should be set to true, and proper certificate handling should be implemented, such as using certificates from trusted certificate authorities or implementing certificate pinning if self-signed certificates must be used.
| this.currentProvider.send = (payload, callback) => { | ||
| originalSend(payload, async (error, response) => { | ||
| if (error || isRateLimitError(response, payload)) { | ||
| // Avoid infinite loop | ||
| if (this.retryCount >= MAX_RETRIES) { | ||
| callback(error, response) | ||
| this.retryCount = 0 | ||
| return | ||
| } | ||
| // If the request fails, switch to the next provider and try again | ||
| this.currentIndex = (this.currentIndex + 1) % this.providers.length | ||
| this.setCustomProvider(this.providers[this.currentIndex]) | ||
| logger.error(error || JSON.stringify(response.error)); | ||
| this.retryCount += 1 | ||
| const timeout = timeouts[this.retryCount] || 1000; | ||
| logger.error( | ||
| `Switched to provider: ${this.providers[this.currentIndex].host}, timeout: ${timeout}, retry count: ${this.retryCount}, request payload: ${JSON.stringify(payload)}`, | ||
| ) | ||
| await new Promise((resolve) => setTimeout(resolve, timeout)) | ||
|
|
||
| this.currentProvider.send(payload, callback) | ||
| } else { | ||
| this.retryCount = 0 | ||
| callback(null, response) | ||
| } | ||
| }) | ||
| } |
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 retry logic implemented in the send method of the Web3Http class does not handle potential stack overflow or memory leak issues that could arise from recursive calls to this.currentProvider.send within the same call stack. This could lead to a situation where, if the retry count is high or if there are many concurrent requests, the application could run out of memory or encounter a stack overflow error. To prevent this, the retry mechanism should be refactored to use an iterative approach rather than recursion. Additionally, the retry logic should be carefully reviewed to ensure that it does not lead to an infinite loop in case of persistent errors that are not resolved by switching providers.
| try { | ||
| const implementationContract = Implementation(web3, implementationAddress) | ||
| const contract = await implementationContract.methods | ||
| .getPublicVariables() | ||
| .getPublicVariablesV2() | ||
| .call() | ||
| const stats = await implementationContract.methods.getStats().call() | ||
|
|
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 code performs asynchronous operations (implementationContract.methods.getPublicVariablesV2().call() and implementationContract.methods.getStats().call()) without any error handling mechanism. If any of these operations fail, for example, due to a network issue or an invalid contract address, it will result in an unhandled promise rejection, potentially causing runtime issues. It's recommended to wrap these asynchronous operations in a try-catch block to handle any errors that may occur, ensuring the application can gracefully handle the failure scenario.
| function createContract(web3, cloneFactory) { | ||
| if (!web3) { | ||
| logger.error('Not a valid Web3 instance') | ||
| return |
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 createContract checks if the web3 parameter is not falsy and logs an error if it is. However, it does not stop the execution of the function after logging the error, which could lead to runtime errors when it tries to use web3 later in the function. It's recommended to return from the function immediately after logging the error to prevent further execution when the web3 instance is not valid.
src/plugins/contracts/index.js
Outdated
| const onUpdate = refreshContracts(web3, lumerin, cloneFactory) | ||
| contractEventsListener.setOnUpdate(onUpdate) | ||
|
|
||
| const refreshContractsFn = refreshContracts(web3, lumerin, cloneFactory) |
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 refreshContracts function is being initialized twice, which is unnecessary and could lead to performance issues if the initialization is resource-intensive. The first initialization is on line 90, and the second one is on line 93. It would be more efficient to initialize it once and reuse the same instance.
Recommended solution: Initialize the refreshContracts function only once and reuse the same instance for both onUpdate and refreshContracts in the API object. This will save resources and avoid potential performance degradation.
| const isRateLimitError = (response, payload) => { | ||
| const { result, ...data } = response | ||
| const code = response.error?.code | ||
| if (code === 429 || code === -32029 || code === -32097) { | ||
| return true | ||
| } | ||
|
|
||
| if (payload?.method === 'eth_call' && (response.error?.message?.includes('execution reverted') || response.error?.code === -32000)) { | ||
| return true | ||
| } | ||
|
|
||
| const message = response.error?.message?.toLowerCase() | ||
| if (!message) { | ||
| return false | ||
| } | ||
| return ( | ||
| message.includes('too many requests') || | ||
| message.includes('rate limit exceeded') || | ||
| message.includes('reached maximum qps limit') || | ||
| message.includes('rate limit reached') || | ||
| message.includes("we can't execute this request") || | ||
| message.includes("max message response size exceed") || | ||
| message.includes("upgrade your plan") || | ||
| message.includes("Failed to validate quota usage") | ||
| ); |
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 isRateLimitError checks for various conditions to determine if a response indicates a rate limit error. However, this approach of hardcoding error messages and codes can lead to maintainability issues. If the error messages or codes change on the server side, the function will fail to detect rate limit errors correctly.
To improve maintainability, consider using a more flexible approach to detect rate limit errors. For instance, you could define a list of known rate limit error codes and messages in a configuration file or environment variables. This way, you can easily update the error conditions without modifying the code. Additionally, consider implementing a strategy to handle unexpected error codes or messages gracefully, such as logging them for further analysis.
| expect(1).eq(0) | ||
| } catch (err) { | ||
| expect(err.message.includes('unknown account')).eq(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 test case in lines 74-76 is designed to always fail if it reaches line 74 due to the expect(1).eq(0) assertion. This approach to testing for expected failures is not ideal because it relies on the test reaching a line of code that asserts a failure, rather than explicitly testing for the expected error condition. A better approach would be to use a more direct assertion to check that the error thrown matches the expected error condition, thereby making the test more readable and its intention clearer. This would involve removing the expect(1).eq(0) assertion and directly asserting the expected error condition in the catch block.
| expect(1).eq(0) | ||
| } catch (err) { | ||
| expect(err.message.includes('execution reverted')).eq(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.
Similar to the previous issue, the test case in lines 97-99 uses expect(1).eq(0) as a means to assert failure if the preceding code does not throw an error. This pattern is not ideal for testing expected failures because it can make tests harder to understand and maintain. Instead, the test should be structured to more directly assert the expected error condition, removing the need for the expect(1).eq(0) assertion and making the test's intention and expected outcome clearer. This would involve adjusting the catch block to directly assert the expected error condition.
| const createExplorer = (explorerApiURLs, web3, lumerin, eventBus) => { | ||
| const apis = createExplorerApis(explorerApiURLs); | ||
| return new Explorer({ apis, lumerin, web3, eventBus }) |
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 createExplorer function lacks error handling for the instantiation of the Explorer class. If the Explorer constructor throws an error due to invalid arguments or other issues, it will result in an unhandled exception. To improve the robustness of this function, consider wrapping the Explorer instantiation in a try-catch block and handle potential errors appropriately. This could involve logging the error, returning a default value, or re-throwing a custom error to provide more context to the caller.
| function start({ config, eventBus, plugins }) { | ||
| // debug.enabled = config.debug; | ||
| const { lmrTokenAddress } = config; | ||
|
|
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 start function is using a destructuring assignment to get config, eventBus, and plugins from its parameter. However, it doesn't check if these properties actually exist in the object passed as an argument. This could lead to a TypeError if the function is called with an object that doesn't have one of these properties. To avoid potential errors, you should add checks to ensure that these properties exist before trying to access them.
| const getLocalIp = async () => { | ||
| const baseURL = ipLookupUrl || 'https://ifconfig.io/ip'; | ||
| const { data } = await createAxios({ baseURL })(); | ||
| const stringData = typeof data === 'string' ? data : JSON.stringify(data); | ||
| const baseURL = ipLookupUrl || 'https://ifconfig.io/ip' | ||
| const { data } = await createAxios({ baseURL })() | ||
| const stringData = typeof data === 'string' ? data : JSON.stringify(data) | ||
|
|
||
| const ipRegex = | ||
| /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/ | ||
| const [ip] = stringData.match(ipRegex) | ||
| return ip |
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 getLocalIp function does not handle the case where the IP address is not found in the response data. This could lead to a situation where undefined is returned instead of a string containing the IP address or null, potentially causing issues in the calling code that expects a valid IP address or null. To improve this, you should explicitly check if the IP address was found and return null if it wasn't, ensuring the function's return type is consistent.
| const isProxyPortPublic = async (payload) => { | ||
| const { ip, port } = payload; | ||
| const baseURL = portCheckerUrl || 'https://portchecker.io/api/v1/query' | ||
| const { data } = await axios.post(baseURL, { | ||
| host: ip, | ||
| ports: [port], | ||
| }) | ||
|
|
||
| return !!data.check?.find((c) => c.port === port && c.status === 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 isProxyPortPublic function directly uses the axios.post method without any error handling mechanism. This could lead to unhandled promise rejections if the network request fails due to reasons like network issues, invalid ip or port, or server errors. To mitigate this, you should wrap the network request in a try-catch block and handle errors appropriately, possibly by logging the error and returning a default value that indicates the check could not be completed.
feat: auto-close + indexer
| function createContract(web3, cloneFactory) { | ||
| if (!web3) { | ||
| logger.error('Not a valid Web3 instance') | ||
| return |
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 createContract checks if the web3 parameter is not falsy and logs an error if it is. However, it does not stop the execution of the function after logging the error, which could lead to runtime errors when it tries to use web3 later in the function. It's recommended to return from the function immediately after logging the error to prevent further execution when the web3 instance is not valid. This issue has already been identified, so consider this a reinforcement of the need for a fix.
| const refreshContracts = async (contractId, walletAddress) => { | ||
| if (walletAddress) { | ||
| Indexer.walletAddr = walletAddress | ||
| } | ||
| eventBus.emit('contracts-scan-started', {}) | ||
|
|
||
| try { | ||
| const contracts = contractId | ||
| ? await indexer.getContract(contractId) | ||
| : await indexer.getContracts() | ||
|
|
||
| eventBus.emit('contracts-scan-finished', { | ||
| actives: contracts, | ||
| }) | ||
| } catch (error) { | ||
| logger.error( | ||
| `Could not sync contracts/events, params: ${contractId}, error:`, | ||
| error | ||
| ) | ||
| throw error | ||
| } | ||
| } |
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 refreshContracts function modifies a static property (Indexer.walletAddr) of the Indexer class directly from an instance method, which can lead to unpredictable behavior and is generally considered a bad practice. This approach can cause issues in a multi-threaded environment or when multiple instances of Indexer are used, as the change will affect all instances globally.
Recommended solution: Instead of modifying a static property directly, consider passing the walletAddress as a parameter to the methods of the Indexer instance that require it. This approach encapsulates the data within the instance and avoids unintended side effects.
| setInterval(() => { | ||
| refreshContracts() | ||
| }, pollingInterval) |
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.
Using setInterval to periodically refresh contracts can lead to potential issues if the refreshContracts function takes longer to execute than the pollingInterval. This could cause the function to be called again before the previous execution has completed, potentially leading to performance issues or inconsistent states.
Recommended solution: Consider using setTimeout within refreshContracts to schedule the next execution at the end of the current one. This ensures that there is always a fixed delay between executions, preventing overlap and potential issues arising from concurrent executions.
| getContracts = async () => { | ||
| const params = Indexer.walletAddr ? { walletAddr: Indexer.walletAddr } : {} | ||
|
|
||
| const response = await axios.get(`${this.url}/api/contracts`, { | ||
| params, | ||
| headers: this.headers, | ||
| }) | ||
| return this.mapIndexerContracts(response.data) |
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 method getContracts uses axios.get to fetch data from an external API without any error handling mechanism. This could lead to unhandled promise rejections if the request fails for any reason (e.g., network issues, invalid URL, server errors). It's recommended to wrap the axios.get call in a try-catch block to handle potential errors gracefully, possibly logging the error and returning a default value or an error message to the caller.
| getContract = async (id) => { | ||
| const params = Indexer.walletAddr ? { walletAddr: Indexer.walletAddr } : {} | ||
|
|
||
| const response = await axios.get(`${this.url}/api/contracts/${id}`, { | ||
| params, | ||
| headers: this.headers, | ||
| }) | ||
| return this.mapIndexerContracts([response.data]) |
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.
Similar to the getContracts method, the getContract method also lacks error handling for the axios.get request. This omission can lead to unhandled promise rejections in case of request failures. Implementing a try-catch block around the axios.get call would allow for error handling, ensuring that the application can gracefully manage request failures by logging the error and providing feedback or a fallback to the caller.
…ward feat: dynamic block reward
| const streamFn = async () => { | ||
| return { | ||
| difficulty: await getNetworkDifficulty(), | ||
| reward: await getBlockReward(), | ||
| } |
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 use of await inside the streamFn function for both getNetworkDifficulty() and getBlockReward() calls suggests that these operations are asynchronous and potentially time-consuming. Executing them sequentially could lead to performance bottlenecks, especially if either of these operations involves network requests or other I/O operations that could be executed in parallel. To improve performance, consider refactoring this code to use Promise.all or a similar mechanism to allow these asynchronous operations to run concurrently.
| networkDifficultyStream = createStream(streamFn, ratesUpdateMs) | ||
|
|
||
| networkDifficultyStream.on('data', function (difficulty) { | ||
| networkDifficultyStream.on('data', function (data) { | ||
| const { difficulty, reward } = data; | ||
| eventBus.emit('network-difficulty-updated', { | ||
| difficulty, | ||
| reward, | ||
| }) | ||
| }) |
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 event emission inside the 'data' event listener of networkDifficultyStream does not handle potential errors that could occur during the execution of the event listener. This could lead to unhandled exceptions if an error occurs, for example, in the eventBus.emit call. To improve the robustness and reliability of the application, consider adding error handling mechanisms, such as try-catch blocks, around operations that could throw exceptions or fail, and ensure that errors are either handled appropriately or logged for further investigation.
| const getBlockReward = async () => { | ||
| try { | ||
| const baseUrl = 'https://blockchain.info' | ||
| const res = await axios.get(`${baseUrl}/q/bcperblock`) | ||
| return res?.data | ||
| } catch (err) { | ||
| logger.error('Failed to get block reward:', err) | ||
| } |
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 getBlockReward lacks error handling for the scenario where the axios.get call succeeds but returns an unexpected status code or data format. This could lead to the function returning undefined or an incorrect value without any indication of an issue to the caller. To improve robustness, consider checking the response status code and the format of the returned data before returning it. If the response or data format is not as expected, throw an error or return a default value with an appropriate log message.
| const baseUrl = 'https://blockchain.info' | ||
| const res = await axios.get(`${baseUrl}/q/bcperblock`) | ||
| return res?.data |
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.
Hardcoding the base URL (https://blockchain.info) directly within the getBlockReward function reduces flexibility and maintainability. If the base URL changes or if you need to point to a different endpoint for testing or development purposes, you would have to modify the code directly. To improve maintainability and flexibility, consider defining the base URL as a constant at a higher scope or in a configuration file. This way, it can be easily changed without modifying the function's code.
|
|
||
| const { | ||
| _state: state, | ||
| _price: price, // cost to purchase the contract | ||
| _limit: limit, // max th provided | ||
| _speed: speed, // th/s of contract | ||
| _length: length, // duration of the contract in seconds | ||
| _terms: { | ||
| _price: price, // cost to purchase the contract | ||
| _limit: limit, // max th provided | ||
| _speed: speed, // th/s of contract | ||
| _length: length, // duration of the contract in seconds | ||
| _version: version, | ||
| _profitTarget: profitTarget | ||
| }, | ||
| _startingBlockTimestamp: timestamp, // timestamp of the block at moment of purchase | ||
| _buyer: buyer, // wallet address of the purchasing party | ||
| _seller: seller, // wallet address of the selling party |
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 destructuring assignment from contract in lines 39-56 extracts multiple properties and nested properties. This approach, while concise, can lead to runtime errors if the contract object does not strictly conform to the expected structure (e.g., if _terms is undefined, accessing nested properties like _price will throw a TypeError). To enhance the robustness of the code, consider adding checks or using optional chaining (?.) to safely access nested properties.
| speed: data._speed, | ||
| length: data._length, | ||
| limit: data._limit, | ||
| version: data._version, | ||
| profitTarget: data._profitTarget | ||
| } | ||
| } | ||
|
|
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 conditional block from lines 59-69 checks if walletAddress, hasFutureTerms, and if seller equals walletAddress to decide whether to fetch futureTerms. This logic could be simplified and made more readable by combining these conditions into a single if-statement using logical AND operators. Additionally, consider handling the scenario where implementationContract.methods.futureTerms().call() might fail due to network issues or contract state problems by wrapping it in a try-catch block.
src/plugins/contracts/api.js
Outdated
| throw new Error('Contract is deleted already') | ||
| } | ||
|
|
||
| const totalPrice = (+price + price * 0.01).toString() | ||
| const increaseAllowanceEstimate = await lumerin.methods | ||
| .increaseAllowance(cloneFactory.options.address, price) | ||
| .estimateGas({ | ||
| from: walletId, | ||
| }) | ||
|
|
||
| await lumerin.methods | ||
| .increaseAllowance(cloneFactory.options.address, totalPrice) | ||
| .send(sendOptions) | ||
| .increaseAllowance(cloneFactory.options.address, price) | ||
| .send({ | ||
| from: walletId, | ||
| gas: increaseAllowanceEstimate, | ||
| }) | ||
|
|
||
| const marketplaceFee = await cloneFactory.methods.marketplaceFee().call() | ||
|
|
||
| const purchaseGas = await cloneFactory.methods | ||
| .setPurchaseRentalContract(contractId, ciphertext.toString('hex')) | ||
| .setPurchaseRentalContract( | ||
| contractId, | ||
| ciphertext.toString('hex'), | ||
| version | ||
| ) | ||
| .estimateGas({ | ||
| from: sendOptions.from, | ||
| value: marketplaceFee, | ||
| }) | ||
|
|
||
| const purchaseResult = await cloneFactory.methods | ||
| .setPurchaseRentalContract(contractId, ciphertext.toString('hex')) | ||
| .setPurchaseRentalContract( | ||
| contractId, | ||
| ciphertext.toString('hex'), | ||
| version | ||
| ) | ||
| .send({ | ||
| ...sendOptions, | ||
| gas: purchaseGas, | ||
| value: marketplaceFee, | ||
| }) | ||
|
|
||
| logger.debug('Finished puchase transaction', purchaseResult) |
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 purchaseContract in lines 262-327 performs several blockchain-related operations without handling potential exceptions specifically. For instance, calls like lumerin.methods.increaseAllowance(...).send() and cloneFactory.methods.setPurchaseRentalContract(...).send() could fail due to reasons such as out of gas errors, invalid nonce, or network issues. It's crucial to wrap these operations in try-catch blocks to handle these exceptions gracefully, providing error feedback and preventing the function from crashing the application.
| limit = 0, | ||
| speed, | ||
| duration, | ||
| profit = 0 | ||
| } = params | ||
| const sendOptions = { from: walletId, gas: 1_000_000 } | ||
| const sendOptions = { from: walletId } | ||
|
|
||
| const account = web3.eth.accounts.privateKeyToAccount(privateKey) | ||
| web3.eth.accounts.wallet.create(0).add(account) | ||
|
|
||
| const editGas = await cloneFactory.methods | ||
| .setUpdateContractInformation(contractId, price, limit, speed, duration) | ||
| .setUpdateContractInformationV2(contractId, price, limit, speed, duration, +profit) | ||
| .estimateGas({ | ||
| from: sendOptions.from, | ||
| }) | ||
|
|
||
| }); | ||
| const editResult = await cloneFactory.methods | ||
| .setUpdateContractInformation(contractId, price, limit, speed, duration) | ||
| .setUpdateContractInformationV2(contractId, price, limit, speed, duration, +profit) | ||
| .send({ | ||
| ...sendOptions, | ||
| gas: editGas, |
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.
In the function editContract from lines 345-365, there is a potential risk of reentrancy attacks because it sends a transaction (cloneFactory.methods.setUpdateContractInformationV2(...).send()) that could potentially call back into any other function in this contract. To mitigate this risk, consider implementing a reentrancy guard that prevents the function from being called while it is still executing.
| const { encrypt } = require('ecies-geth') | ||
| const { Implementation } = require('contracts-js') | ||
| const { remove0xPrefix, add65BytesPrefix } = require('./helpers') | ||
| const { ContractEventsListener } = require('./events-listener') | ||
| const ethereumWallet = require('ethereumjs-wallet').default | ||
|
|
||
| /** |
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 code imports several modules and utilities at the top of the file. However, there is a potential security risk with the direct use of ethereumjs-wallet. Using this package involves handling private keys directly, which can be risky if not managed securely. It's recommended to ensure that private keys are never exposed in logs, error messages, or through any form of insecure storage. Consider implementing additional security measures such as encryption-at-rest for stored keys and secure key management practices.
|
|
||
| const { | ||
| _state: state, | ||
| _price: price, // cost to purchase the contract | ||
| _limit: limit, // max th provided | ||
| _speed: speed, // th/s of contract | ||
| _length: length, // duration of the contract in seconds | ||
| _terms: { | ||
| _price: price, // cost to purchase the contract | ||
| _limit: limit, // max th provided | ||
| _speed: speed, // th/s of contract | ||
| _length: length, // duration of the contract in seconds | ||
| _version: version, | ||
| _profitTarget: profitTarget | ||
| }, | ||
| _startingBlockTimestamp: timestamp, // timestamp of the block at moment of purchase | ||
| _buyer: buyer, // wallet address of the purchasing party | ||
| _seller: seller, // wallet address of the selling party |
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 destructuring assignment from contract in lines 39-56 extracts multiple properties and nested properties. This approach, while concise, can lead to runtime errors if the contract object does not strictly conform to the expected structure (e.g., if _terms is undefined, accessing nested properties like _price will throw a TypeError). To enhance the robustness of the code, consider adding checks or using optional chaining (?.) to safely access nested properties.
| (provider) => | ||
| new Web3.providers.HttpProvider(provider, { | ||
| agent: new https.Agent({ | ||
| rejectUnauthorized: false, // Set to false if your HTTPS node endpoint uses a self-signed certificate |
Check failure
Code scanning / CodeQL
Disabling certificate validation High
Validator registry integration
| require('./plugins/proxy-router'), | ||
| require('./plugins/contracts'), | ||
| require('./plugins/devices'), | ||
| require('./plugins/validator-registry') | ||
| ] |
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 Memory Usage Issue
Loading all plugins at the start using require can lead to high memory usage and potentially slow down the application startup, especially if some plugins are not immediately needed. Consider using a lazy loading technique, where plugins are only required when they are actually needed. This can be achieved by storing plugin identifiers or paths in the array and requiring them dynamically when initializing.
Suggested Change:
const pluginCreators = [
'./plugins/rates',
'./plugins/eth',
// other plugins
];
// Later, when initializing:
plugins = pluginCreators.map(pluginPath => require(pluginPath)());| require('./plugins/proxy-router'), | ||
| require('./plugins/contracts'), | ||
| require('./plugins/devices'), | ||
| require('./plugins/validator-registry') | ||
| ] |
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.
Error Handling Issue
There is no error handling for the scenario where a plugin might fail to load due to issues such as missing files or syntax errors. This can cause the application to crash. To improve the robustness of the application, consider wrapping the require calls in a try-catch block and handling the errors appropriately, possibly logging them or attempting a fallback mechanism.
Suggested Change:
const pluginCreators = [];
const pluginPaths = [
'./plugins/rates',
'./plugins/eth',
// other plugins
];
pluginPaths.forEach(path => {
try {
const plugin = require(path);
pluginCreators.push(plugin);
} catch (error) {
logger.error('Failed to load plugin:', path, error);
}
});|
|
||
| const { | ||
| _state: state, | ||
| _price: price, // cost to purchase the contract | ||
| _limit: limit, // max th provided | ||
| _speed: speed, // th/s of contract | ||
| _length: length, // duration of the contract in seconds | ||
| _terms: { | ||
| _price: price, // cost to purchase the contract | ||
| _limit: limit, // max th provided | ||
| _speed: speed, // th/s of contract | ||
| _length: length, // duration of the contract in seconds | ||
| _version: version, | ||
| _profitTarget: profitTarget | ||
| }, | ||
| _startingBlockTimestamp: timestamp, // timestamp of the block at moment of purchase | ||
| _buyer: buyer, // wallet address of the purchasing party | ||
| _seller: seller, // wallet address of the selling party |
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 destructuring assignment from contract in lines 41-54 extracts multiple properties and nested properties. This approach, while concise, can lead to runtime errors if the contract object does not strictly conform to the expected structure (e.g., if _terms is undefined, accessing nested properties like _price will throw a TypeError). To enhance the robustness of the code, consider adding checks or using optional chaining (?.) to safely access nested properties.
Recommended Change:
const { _state: state, _terms: terms = {} } = contract;
const { _price: price, _limit: limit, _speed: speed, _length: length, _version: version, _profitTarget: profitTarget } = terms;This change ensures that if _terms is undefined, it defaults to an empty object, preventing runtime errors when accessing nested properties.
| speed: data._speed, | ||
| length: data._length, | ||
| limit: data._limit, | ||
| version: data._version, | ||
| profitTarget: data._profitTarget | ||
| } | ||
| } | ||
|
|
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 conditional block from lines 66-73 checks if walletAddress, hasFutureTerms, and if seller equals walletAddress to decide whether to fetch futureTerms. This logic could be simplified and made more readable by combining these conditions into a single if-statement using logical AND operators. Additionally, consider handling the scenario where implementationContract.methods.futureTerms().call() might fail due to network issues or contract state problems by wrapping it in a try-catch block.
Recommended Change:
if (walletAddress && hasFutureTerms && seller === walletAddress) {
try {
const data = await implementationContract.methods.futureTerms().call();
futureTerms = {
price: data._price,
speed: data._speed,
length: data._length,
limit: data._limit,
version: data._version,
profitTarget: data._profitTarget
};
} catch (error) {
logger.error('Failed to fetch future terms:', error);
}
}This change ensures that errors during the fetching of future terms are caught and logged, preventing potential crashes and improving the reliability of the application.
| const remove0xPrefix = privateKey => privateKey.replace('0x', ''); | ||
|
|
||
| // https://superuser.com/a/1465498 | ||
| const add65BytesPrefix = key => `04${key}`; | ||
| /** @param {string} key */ | ||
| const add65BytesPrefix = key => { | ||
| key = key.replace("0x", "") | ||
|
|
||
| // 64 bytes hex string (2 char per byte) | ||
| if (key.length === 64 * 2) { | ||
| return `04${key}` | ||
| } | ||
|
|
||
| return 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.
Security and Error Handling Concerns
Both remove0xPrefix and add65BytesPrefix functions manipulate hexadecimal keys without validating the input format or ensuring that '0x' is only removed when it is a prefix. This could lead to incorrect processing of keys if the input is not as expected or if '0x' appears elsewhere in the string.
Recommendation: Implement input validation to check if the string is a valid hexadecimal and ensure '0x' is only removed if it is at the start. This will prevent potential security flaws and data corruption.
| this.currentProvider.send = (payload, callback) => { | ||
| originalSend(payload, async (error, response) => { | ||
| if (error || isRateLimitError(response, payload)) { | ||
| // Avoid infinite loop | ||
| if (this.retryCount >= MAX_RETRIES) { | ||
| callback(error, response) | ||
| this.retryCount = 0 | ||
| return | ||
| } | ||
| // If the request fails, switch to the next provider and try again | ||
| this.currentIndex = (this.currentIndex + 1) % this.providers.length | ||
| this.setCustomProvider(this.providers[this.currentIndex]) | ||
| logger.error(error || JSON.stringify(response.error)); | ||
| this.retryCount += 1 | ||
| const timeout = timeouts[this.retryCount] || 1000; | ||
| logger.error( | ||
| `Switched to provider: ${this.providers[this.currentIndex].host}, timeout: ${timeout}, retry count: ${this.retryCount}, request payload: ${JSON.stringify(payload)}`, | ||
| ) | ||
| await new Promise((resolve) => setTimeout(resolve, timeout)) | ||
|
|
||
| this.currentProvider.send(payload, callback) | ||
| } else { | ||
| this.retryCount = 0 | ||
| callback(null, response) | ||
| } | ||
| }) | ||
| } |
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 retry logic in the send method does not differentiate between transient and permanent errors, potentially leading to unnecessary retries and resource consumption. Additionally, the retry count is reset only when it reaches the maximum retries, which could lead to an infinite loop if the error persists across all providers.
Recommendation: Implement a more sophisticated error handling strategy that differentiates between transient and permanent errors. Consider resetting the retry count after a successful request or when all providers have been tried, to prevent potential infinite loops.
| const registerValidator = (registry, web3, lumerin) => async (request) => { | ||
| const privateKey = request.privateKey; | ||
| const tempWallet = ethereumWallet.fromPrivateKey( | ||
| Buffer.from(remove0xPrefix(privateKey), 'hex') | ||
| ) | ||
| const pubKey = add65BytesPrefix(tempWallet.getPublicKey().toString('hex')); | ||
| const { yParity, x } = compressPublicKey(pubKey); | ||
|
|
||
| const account = web3.eth.accounts.privateKeyToAccount(privateKey) | ||
| web3.eth.accounts.wallet.create(0).add(account) | ||
|
|
||
| const increaseAllowanceEstimate = await lumerin.methods | ||
| .increaseAllowance(registry.options.address, request.stake) | ||
| .estimateGas({ | ||
| from: request.walletId, | ||
| }) | ||
|
|
||
| await lumerin.methods | ||
| .increaseAllowance(registry.options.address, request.stake) | ||
| .send({ | ||
| from: request.walletId, | ||
| gas: increaseAllowanceEstimate, | ||
| }) | ||
|
|
||
| try { | ||
| const estimatedGas = await registry.methods | ||
| .validatorRegister(request.stake, yParity, x, request.host) | ||
| .estimateGas({ from: request.walletId }); | ||
|
|
||
| await registry.methods.validatorRegister( | ||
| request.stake, yParity, x, request.host) | ||
| .send({ from: request.walletId, gas: estimatedGas }); | ||
| } catch (err) { | ||
| console.log("validator register error", err) | ||
| throw err | ||
| } | ||
| } |
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 Concerns with Private Key Handling
The registerValidator function handles sensitive operations involving a private key. It's crucial to ensure that the private key is managed securely throughout the process to prevent any potential security vulnerabilities. Consider implementing additional security measures such as encryption in transit and at rest, and ensure that the private key is never logged or exposed through debugging information.
Recommended Action
- Review and strengthen the security measures around private key handling.
- Ensure that all operations involving private keys are conducted securely, with encryption where applicable.
- Avoid logging private keys or exposing them in any debug outputs.
| const _loadValidator = async (registry, id) => { | ||
| const data = await registry.methods.getValidator(id).call().catch(err => { | ||
| if (err.data === getHash("ValidatorNotFound()")) { | ||
| return null | ||
| } | ||
| throw err | ||
| }); | ||
|
|
||
| if (!data) { | ||
| return null | ||
| } | ||
|
|
||
| return mapValidator(data) | ||
| } |
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.
Error Handling in _loadValidator
The _loadValidator function includes specific error handling for the ValidatorNotFound() error, which is good practice. However, it's important to ensure that all potential errors are handled gracefully. Consider implementing a more comprehensive error handling strategy that covers various failure scenarios, which could include network issues, unexpected data formats, and other runtime exceptions.
Recommended Action
- Expand the error handling in
_loadValidatorto cover more error conditions. - Implement a robust error logging mechanism to aid in debugging and maintaining the system.
- Test the error handling with various scenarios to ensure the system's robustness.
| function start({ config, plugins }) { | ||
| const { | ||
| lmrTokenAddress, | ||
| validatorRegistryAddress | ||
| } = config | ||
| const { eth } = plugins | ||
|
|
||
| const web3 = eth.web3 | ||
|
|
||
| const lumerin = Lumerin(web3, lmrTokenAddress) | ||
| const registry = ValidatorRegistry(web3, validatorRegistryAddress) | ||
|
|
||
| return { | ||
| api: { | ||
| getValidator: api.getValidator(registry), | ||
| getValidators: api.getValidators(registry, web3), | ||
| registerValidator: api.registerValidator(registry, web3, lumerin), | ||
| deregisterValidator: api.deregisterValidator(registry, web3), | ||
| getValidatorsMinimalStake: api.getValidatorsMinimalStake(registry), | ||
| getValidatorsRegisterStake: api.getValidatorsRegisterStake(registry) | ||
| }, | ||
| events: [ | ||
| ], | ||
| name: 'validator-registry', | ||
| } |
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 start function initializes and configures blockchain-related operations but lacks error handling. This could lead to unhandled exceptions if the blockchain connection fails or if there are issues with the smart contract interactions.
Recommendation: Implement try-catch blocks around blockchain interactions and potentially risky operations to handle exceptions gracefully. This would improve the robustness of the plugin and prevent the application from crashing due to unhandled errors.
| function start({ config, plugins }) { | ||
| const { | ||
| lmrTokenAddress, | ||
| validatorRegistryAddress | ||
| } = config | ||
| const { eth } = plugins | ||
|
|
||
| const web3 = eth.web3 | ||
|
|
||
| const lumerin = Lumerin(web3, lmrTokenAddress) | ||
| const registry = ValidatorRegistry(web3, validatorRegistryAddress) | ||
|
|
||
| return { | ||
| api: { | ||
| getValidator: api.getValidator(registry), | ||
| getValidators: api.getValidators(registry, web3), | ||
| registerValidator: api.registerValidator(registry, web3, lumerin), | ||
| deregisterValidator: api.deregisterValidator(registry, web3), | ||
| getValidatorsMinimalStake: api.getValidatorsMinimalStake(registry), | ||
| getValidatorsRegisterStake: api.getValidatorsRegisterStake(registry) | ||
| }, | ||
| events: [ | ||
| ], | ||
| name: 'validator-registry', | ||
| } |
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 start function directly returns an object containing several API methods related to blockchain operations. This approach mixes the logic for setting up the blockchain connections with the API method definitions, which can reduce the maintainability of the code.
Recommendation: Consider separating the concerns by defining the API methods outside of the start function or in a separate module. This would not only improve the readability and maintainability of the code but also make it easier to manage changes and updates to the API methods independently.
fix: move multicall to deps
No description provided.