Skip to content

Validator registry integration#84

Merged
shev-titan merged 2 commits into
devfrom
feature/validator-registry
Feb 2, 2025
Merged

Validator registry integration#84
shev-titan merged 2 commits into
devfrom
feature/validator-registry

Conversation

@bohdan-titan
Copy link
Copy Markdown
Contributor

No description provided.

@bohdan-titan bohdan-titan changed the title Added new plugin [WIP] Added new plugin Jan 24, 2025
Comment thread src/index.js
Comment on lines 14 to 18
require('./plugins/proxy-router'),
require('./plugins/contracts'),
require('./plugins/devices'),
require('./plugins/validator-registry')
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Performance and Maintainability Issue

The synchronous loading of plugins using require within the pluginCreators array can lead to performance bottlenecks, especially if the initialization of these plugins is resource-intensive. This approach also makes the system less flexible and harder to maintain because any modifications to the plugins require changes to this core file.

Recommendation: Consider using an asynchronous plugin loading mechanism, such as dynamic imports (import()) which can be used to load modules on demand. This would not only improve the load times but also enhance the system's ability to scale and adapt to changes more gracefully.

Comment thread src/index.js
Comment on lines 14 to 18
require('./plugins/proxy-router'),
require('./plugins/contracts'),
require('./plugins/devices'),
require('./plugins/validator-registry')
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Modularity and Scalability Issue

Hardcoding the paths of plugin modules in the pluginCreators array reduces the system's modularity and scalability. This approach requires manual updates to the core system file whenever plugins are added or removed, increasing the risk of errors and complicating the maintenance process.

Recommendation: Implement a more dynamic plugin registration system, possibly through a configuration file or a directory scanning mechanism that automatically registers all plugins. This would enhance modularity and make the system more robust and easier to scale as the number of plugins grows.

Comment thread src/plugins/contracts/api.js Outdated
Comment on lines +360 to +361
if (isDead) {
throw new Error('Contract is deleted already')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The error handling in purchaseContractV2 uses throw new Error('Contract is deleted already') which will cause the function to exit and the error to propagate up the call stack. This approach is generally acceptable, but it could be improved by implementing a more robust error handling strategy. For instance, instead of throwing a generic error, you could return a specific error object or use a custom error class that provides more context about the error. This would make it easier for the calling functions to handle specific cases based on the type of error.

Recommended Solution:
Consider defining custom error classes that extend the base Error class. This way, you can handle specific errors more gracefully and provide additional information in the error object. For example:

class ContractDeletedError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ContractDeletedError';
  }
}

if (isDead) {
  throw new ContractDeletedError('Contract is deleted already');
}

Comment thread src/plugins/contracts/api.js Outdated
Comment on lines 328 to 412
}
}

/**
*
* @param {import('web3').default} web3
* @param {import('contracts-js').CloneFactoryContext} cloneFactory
* @param {import('contracts-js').LumerinContext} lumerin
* @returns
*/
function purchaseContractV2(web3, cloneFactory, lumerin) {
return async (params) => {
const { walletId, contractId, url, privateKey, price, version, validator, validatorUrl } = params
const sendOptions = { from: walletId }

//getting pubkey from contract to be purchased
const implementationContract = Implementation(web3, contractId)

const pubKey = await implementationContract.methods.pubKey().call()

//encrypting plaintext url parameter
const ciphertext = await encrypt(
Buffer.from(add65BytesPrefix(pubKey), 'hex'),
Buffer.from(url)
)

const account = web3.eth.accounts.privateKeyToAccount(privateKey)
web3.eth.accounts.wallet.create(0).add(account)

const {
data: { isDead, price: p },
} = await _loadContractInstance(web3, contractId)
if (isDead) {
throw new Error('Contract is deleted already')
}

const increaseAllowanceEstimate = await lumerin.methods
.increaseAllowance(cloneFactory.options.address, price)
.estimateGas({
from: walletId,
})

await lumerin.methods
.increaseAllowance(cloneFactory.options.address, price)
.send({
from: walletId,
gas: increaseAllowanceEstimate,
})

const marketplaceFee = await cloneFactory.methods.marketplaceFee().call()

const purchaseGas = await cloneFactory.methods
.setPurchaseRentalContractV2(
contractId,
validator,
validatorUrl,
ciphertext.toString('hex'),
version
)
.estimateGas({
from: sendOptions.from,
value: marketplaceFee,
})

const purchaseResult = await cloneFactory.methods
.setPurchaseRentalContractV2(
contractId,
validator,
validatorUrl,
ciphertext.toString('hex'),
version
)
.send({
...sendOptions,
gas: purchaseGas,
value: marketplaceFee,
})

logger.debug('Finished puchase transaction', purchaseResult)
}
}

/**
*
* @param {import('web3').default} web3
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The functions purchaseContract and purchaseContractV2 share a significant amount of code, which suggests an opportunity for refactoring to reduce duplication and improve modularity. Both functions perform similar steps such as loading the contract instance, checking if the contract is deleted, estimating gas for transactions, and handling the purchase transaction. This redundancy can be minimized by extracting common functionality into separate, reusable functions or by using a more advanced design pattern such as a template method.

Recommended Solution:
Refactor the common code into a separate function that both purchaseContract and purchaseContractV2 can call. This not only reduces duplication but also simplifies maintenance and testing. For example, you could create a function preparePurchase that handles the loading of the contract instance, checking its status, and estimating the necessary gas. Here's a conceptual example:

async function preparePurchase(web3, contractId, price) {
  const contractInstance = await _loadContractInstance(web3, contractId);
  if (contractInstance.data.isDead) {
    throw new Error('Contract is deleted already');
  }
  const gasEstimate = await estimatePurchaseGas(contractInstance, price);
  return { contractInstance, gasEstimate };
}

const { AbortSignal } = require('@azure/abort-controller')

const { AntMinerStrategy } = require('./ant-miner-strategy')
// const { AntMinerStrategy } = require('./ant-miner-strategy')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The import statement for AntMinerStrategy is commented out. If this import is not needed, it should be removed to clean up the codebase and avoid confusion. If it is temporarily disabled, consider adding a comment explaining why it is commented out and under what conditions it should be re-enabled.

Comment thread src/plugins/validator-registry/api.js Outdated
Comment on lines +53 to +56
const privateKey = request.privateKey;
const tempWallet = ethereumWallet.fromPrivateKey(
Buffer.from(remove0xPrefix(privateKey), 'hex')
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Exposing the private key directly from the request object (request.privateKey) and using it to create a wallet (ethereumWallet.fromPrivateKey(...)) raises significant security concerns. This approach can lead to accidental exposure of sensitive information if the request object is logged or mishandled.

Recommendation:
Consider using a more secure method to handle private keys, such as environment variables or secure vault solutions. Additionally, ensure that all operations involving private keys are conducted in a secure environment and that the keys are never logged or exposed through error messages.

Comment thread src/plugins/validator-registry/api.js Outdated
Comment on lines +41 to +42
const deregisterValidator = (registry) => async (walletId) => {
await registry.methods.validatorDeregister().send({ from: walletId });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The deregisterValidator function sends a transaction to deregister a validator without performing any checks or validations on the walletId. This could lead to unauthorized or unintended actions if the walletId is not properly validated or if the function is called inappropriately.

Recommendation:
Implement checks to ensure that the walletId belongs to the caller or has appropriate permissions to perform this action. Additionally, consider adding error handling to manage and respond to failures in the transaction process.

Comment on lines +23 to +26
const lumerin = Lumerin(web3, lmrTokenAddress)
const cloneFactory = CloneFactory(web3, cloneFactoryAddress)

const registry = ValidatorRegistry(web3, validatorRegistryAddress)
Copy link
Copy Markdown

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 contract instantiation calls (Lumerin, CloneFactory, ValidatorRegistry). These functions are critical as they interact with blockchain contracts and can fail for various reasons such as network issues or invalid contract addresses.

Recommendation: Implement try-catch blocks around these calls to handle potential errors gracefully. This will improve the robustness of your application by preventing it from crashing and allowing it to deal with errors more gracefully.

Example:

try {
  const lumerin = Lumerin(web3, lmrTokenAddress);
  // similar for others
} catch (error) {
  logger.error('Failed to instantiate contracts:', error);
}

Comment on lines +15 to +17
lmrTokenAddress,
cloneFactoryAddress,
validatorRegistryAddress
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lack of Input Validation

The contract addresses (lmrTokenAddress, cloneFactoryAddress, validatorRegistryAddress) are used directly from the configuration without any validation. In blockchain applications, using invalid addresses can lead to failed transactions or other critical issues.

Recommendation: Validate these addresses before using them in your contract calls. Ensure they are non-empty, correctly formatted, and represent valid addresses on the blockchain network you are interacting with.

Example:

if (!web3.utils.isAddress(lmrTokenAddress)) {
  throw new Error('Invalid LMR token address');
}
// similar checks for other addresses

@shev-titan shev-titan changed the title [WIP] Added new plugin Validator registry integration Feb 2, 2025
@shev-titan shev-titan merged commit 536a357 into dev Feb 2, 2025
Comment on lines 256 to 331
}
}


/**
*
* @param {import('web3').default} web3
* @param {import('contracts-js').CloneFactoryContext} cloneFactory
* @param {import('contracts-js').LumerinContext} lumerin
* @returns
* @returns {(p: {validatorUrl: string, validatorAddr: string, destUrl: string, validatorPubKeyYparity: boolean, validatorPubKeyX: `0x${string}`, contractAddr: string, price: string,version: number, privateKey: string}) => Promise<void>}
*/
function purchaseContract(web3, cloneFactory, lumerin) {
return async (params) => {
const { walletId, contractId, url, privateKey, price, version } = params
const sendOptions = { from: walletId }
const { validatorUrl, destUrl, validatorAddr, validatorPubKeyYparity, validatorPubKeyX, privateKey, contractAddr, price, version } = params;

//getting pubkey from contract to be purchased
const implementationContract = Implementation(web3, contractId)

const pubKey = await implementationContract.methods.pubKey().call()

//encrypting plaintext url parameter
const ciphertext = await encrypt(
Buffer.from(add65BytesPrefix(pubKey), 'hex'),
Buffer.from(url)
)

const account = web3.eth.accounts.privateKeyToAccount(privateKey)
web3.eth.accounts.wallet.create(0).add(account)

const {
data: { isDead, price: p },
} = await _loadContractInstance(web3, contractId)
if (isDead) {
throw new Error('Contract is deleted already')
}
const implementationContract = Implementation(web3, contractAddr)
const sellerPubKey = await implementationContract.methods.pubKey().call()

const isThirdPartyValidator = validatorAddr !== ZERO_ADDRESS;
const encrDestPubKey = isThirdPartyValidator ? decompressPublicKey(validatorPubKeyYparity, validatorPubKeyX) : sellerPubKey;

const encrValidatorUrl = await encrypt(
Buffer.from(add65BytesPrefix(sellerPubKey), 'hex'),
Buffer.from(validatorUrl)
).then(res => res.toString('hex'))

const encrDestUrl = await encrypt(
Buffer.from(add65BytesPrefix(encrDestPubKey), 'hex'),
Buffer.from(destUrl)
).then(res => res.toString('hex'))

const increaseAllowanceEstimate = await lumerin.methods
.increaseAllowance(cloneFactory.options.address, price)
.estimateGas({
from: walletId,
from: account.address,
})

await lumerin.methods
.increaseAllowance(cloneFactory.options.address, price)
.send({
from: walletId,
from: account.address,
gas: increaseAllowanceEstimate,
})

const marketplaceFee = await cloneFactory.methods.marketplaceFee().call()

const purchaseGas = await cloneFactory.methods
.setPurchaseRentalContract(
contractId,
ciphertext.toString('hex'),
.setPurchaseRentalContractV2(
contractAddr,
validatorAddr,
encrValidatorUrl,
encrDestUrl,
version
)
.estimateGas({
from: sendOptions.from,
from: account.address,
value: marketplaceFee,
})

const purchaseResult = await cloneFactory.methods
.setPurchaseRentalContract(
contractId,
ciphertext.toString('hex'),
.setPurchaseRentalContractV2(
contractAddr,
validatorAddr,
encrValidatorUrl,
encrDestUrl,
version
)
.send({
...sendOptions,
from: account.address,
gas: purchaseGas,
value: marketplaceFee,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Security Concern: Insufficient Input Validation

The function purchaseContract does not perform any validation on the inputs it receives, such as validatorUrl, destUrl, validatorAddr, validatorPubKeyYparity, validatorPubKeyX, privateKey, contractAddr, price, and version. This lack of validation could lead to various issues, including sending incorrect data to the blockchain, which can be costly and irreversible.

Recommended Solution:
Implement input validation checks at the beginning of the purchaseContract function. Ensure that all inputs are of the expected type and format before proceeding with further processing. This will help prevent potential errors or malicious inputs from affecting the contract interaction logic.

Comment on lines 363 to 369
.estimateGas({
from: sendOptions.from,
});

const editResult = await cloneFactory.methods
.setUpdateContractInformationV2(contractId, price, limit, speed, duration, +profit)
.send({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Performance Optimization: Redundant Transaction Estimation

In the editContract function, the gas estimation for the transaction is performed twice (lines 363 and 367) with the same parameters. This redundancy not only wastes computational resources but also increases the time the function takes to execute, which could lead to a poorer user experience.

Recommended Solution:
Remove the redundant gas estimation call. Perform the gas estimation once and use the result for the transaction submission. This change will optimize the function's performance by reducing unnecessary blockchain calls and speeding up the transaction process.

Comment on lines 3 to +16
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lack of Input Validation and Error Handling

Both remove0xPrefix and add65BytesPrefix functions do not perform any input validation or error handling. This can lead to issues if non-string inputs are passed or if the strings do not conform to expected formats, potentially causing runtime errors or incorrect processing.

Recommendation:
Implement input validation to ensure that the functions receive properly formatted strings. For instance, check if the input is a string and if it contains the expected characters before processing. This will make the functions more robust and prevent errors during runtime.

const add65BytesPrefix = key => `04${key}`;
/** @param {string} key */
const add65BytesPrefix = key => {
key = key.replace("0x", "")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundancy in Code

The remove0xPrefix functionality is duplicated in add65BytesPrefix. This redundancy can be eliminated by using remove0xPrefix within add65BytesPrefix, which would improve code maintainability and consistency.

Recommendation:
Refactor add65BytesPrefix to use remove0xPrefix for removing the '0x' prefix. This will ensure that any modifications to the prefix removal logic are centralized, making the code easier to maintain and less error-prone.

@@ -1,5 +1,5 @@
const AxiosDigestAuth = require('@mhoc/axios-digest-auth')
const cheerio = require('cheerio')
// const cheerio = require('cheerio')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The commented-out import of cheerio on line 2 might be confusing. If cheerio is not needed, it's better to remove this line to avoid confusion and keep the code clean. If it might be needed in the future, consider adding a comment explaining its intended use.

Comment on lines 27 to 35
message.includes('too many requests') ||
message.includes('rate limit exceeded') ||
message.includes('reached maximum qps limit') ||
message.includes('rate limit reached') ||
message.includes('rate limit reached') ||
message.includes("we can't execute this request") ||
message.includes("max message response size exceed") ||
message.includes("max message response size exceed") ||
message.includes("upgrade your plan") ||
message.includes("Failed to validate quota usage")
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The method isRateLimitError uses multiple string includes checks to determine if the error message indicates rate limiting. This approach can be inefficient, especially if the error message is long or the number of substrings to check is large. Consider consolidating these checks into a single regular expression or using a more efficient string searching algorithm to improve performance.

Suggested Solution:

const rateLimitPatterns = /too many requests|rate limit exceeded|reached maximum qps limit|rate limit reached|we can't execute this request|max message response size exceed|upgrade your plan|Failed to validate quota usage/;
return rateLimitPatterns.test(message);

Comment on lines +22 to +27
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Performance Issue: API Method Initialization

The API methods (getValidator, getValidators, registerValidator, deregisterValidator, getValidatorsMinimalStake, getValidatorsRegisterStake) are initialized directly within the start function's return object. This approach means that each time the start function is called, these methods are re-initialized, which could lead to performance issues if the start function is called multiple times during the lifecycle of the application.

Recommendation: Consider initializing these API methods outside of the start function or caching them if their initialization is expensive. This change could improve performance by reducing unnecessary re-initializations.

Comment on lines +6 to +42
function createPlugin() {

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',
}
}

function stop() {
logger.debug('Plugin stopping')
}

return {
start,
stop,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maintainability Issue: Plugin Structure

The start and stop functions are defined within the createPlugin function. This structure, while encapsulating the plugin logic, can make the code less modular and harder to maintain, especially as the complexity of the plugin grows.

Recommendation: Consider separating the start and stop functions into their own modules or files. This separation can enhance the modularity and maintainability of the code, making it easier to manage and extend in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants