-
Notifications
You must be signed in to change notification settings - Fork 62
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
feat: capture env variables into sigleton and reusable constants #2956
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
# Conflicts: # packages/relay/src/lib/services/hbarLimitService/index.ts # packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts
219bc0d
to
d75063a
Compare
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Quality Gate failedFailed conditions |
3ccaf6b
to
3b2c5e8
Compare
Signed-off-by: nikolay <n.atanasow94@gmail.com>
# Conflicts: # packages/relay/src/lib/constants.ts # packages/relay/src/lib/eth.ts # packages/relay/tests/lib/eth/eth_call.spec.ts # packages/relay/tests/lib/ethGetBlockBy.spec.ts # packages/relay/tests/lib/repositories/hbarLimiter/ethAddressHbarSpendingPlanRepository.spec.ts # packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts # packages/relay/tests/lib/services/cacheService/cacheService.spec.ts
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Signed-off-by: nikolay <n.atanasow94@gmail.com>
# Conflicts: # packages/relay/src/lib/services/hbarLimitService/index.ts
cf8b876
to
8ba4974
Compare
# Conflicts: # packages/relay/src/lib/clients/sdkClient.ts # packages/relay/tests/lib/hbarLimiter.spec.ts # packages/relay/tests/lib/sdkClient.spec.ts # packages/server/tests/acceptance/hbarLimiter.spec.ts
Signed-off-by: nikolay <n.atanasow94@gmail.com>
Quality Gate failedFailed conditions |
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.
Mostly looks good, thank you for putting in the effort! Let's keep in mind that the whole purpose of this issue is to make the env variables readonly and unmodifiable, hence I've left some suggestions:
* Copied envs from process.env | ||
* @private | ||
*/ | ||
private envs: JSON; |
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.
This property is declared as type JSON
, which is incorrect. JSON
is not a valid type for an object; instead, you should type it as Record<string, string | undefined>
or NodeJS.ProcessEnv
since process.env
is inherently a Record<string, string | undefined>
.
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.
Let's also make it readonly
- we should never want to override this.
private envs: JSON; | |
private readonly envs: Record<string, string | undefined>; |
* Get the singleton instance of the current service | ||
* @public | ||
*/ | ||
public static getInstance(): IEnvProviderService { |
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.
Let's make this method private and add a static get
method which uses it:
public static get(name: string): string | undefined {
return EnvProvider.getInstance().get(name);
}
In this way usages of this EnvProvider
class would be shorter and simpler to follow and we avoid the need to chain getInstance()
every time we want to use any of the methods:
const shouldDefaultToConsensus = EnvProvider.get('ETH_CALL_DEFAULT_TO_CONSENSUS_NODE') === 'true';
import dotenv from 'dotenv'; | ||
import findConfig from 'find-config'; | ||
|
||
export class EnvProviderService implements IEnvProviderService { |
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.
Let's make this shorter: EnvProviderService
-> EnvProvider
*/ | ||
private constructor() { | ||
dotenv.config({ path: findConfig('.env') || '' }); | ||
this.envs = JSON.parse(JSON.stringify(process.env)); |
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.
QQ: Why are we stringifying and then parsing the process.env
?
If the purpose is to copy the process.env
object, you can use object de-structuring since it's only containing strings, we don't need to do a "deep" clone with JSON.parse
and JSON.stringify
:
this.envs = JSON.parse(JSON.stringify(process.env)); | |
this.envs = { ...process.env }; |
public remove(name: string): void { | ||
delete this.envs[name]; | ||
} |
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.
QQ: Why would we ever want to delete an env variable, isn't hotReload
used for reloading the original configurations in those scenarios?
Also declaring it as a public method which makes it accessible through any class in the source code only for testing purposes is not really advisable.
public appendEnvsFromPath(configPath: string): void { | ||
dotenv.config({ path: configPath, override: true }); | ||
|
||
const envsToAppend = JSON.parse(JSON.stringify(process.env)); | ||
const merged = Object.assign(this.envs, envsToAppend); | ||
|
||
this.envs = JSON.parse(JSON.stringify(merged)); | ||
} |
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.
Again, we don't need to use JSON.parse
and JSON.stringify
here, and we shouldn't replace the this.envs
object, but only modify it:
public appendEnvsFromPath(configPath: string): void { | |
dotenv.config({ path: configPath, override: true }); | |
const envsToAppend = JSON.parse(JSON.stringify(process.env)); | |
const merged = Object.assign(this.envs, envsToAppend); | |
this.envs = JSON.parse(JSON.stringify(merged)); | |
} | |
public appendEnvsFromPath(configPath: string): void { | |
dotenv.config({ path: configPath, override: true }); | |
const instance = EnvProvider.getInstance(); | |
Object.entries(process.env).forEach(([key, value]) => { | |
instance.dynamicOverride(key, value); | |
}); | |
} |
* @param value string | ||
* @returns void | ||
*/ | ||
public dynamicOverride(name: string, value: string | undefined): void { |
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.
We should move this together with hotReload
, remove
and appendEnvsFromPath
to a helper class in the test
directory, these shouldn't be accessible as public methods in the source code.
* @private | ||
*/ | ||
private constructor() { | ||
dotenv.config({ path: findConfig('.env') || '' }); |
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.
You are using findConfig('.env') || ''
, which means that if no .env
file is found, an empty string is passed to dotenv.config()
. This isn't necessarily a problem, but it might be clearer to raise an error or log a warning when .env
is not found, as this might not be the desired behavior.
* @param name string | ||
* @returns string | undefined | ||
*/ | ||
public get(name: string): string | undefined { |
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.
We can implement a default value or fallback mechanism to handle missing environment variables:
public static getWithDefault(name: string, defaultValue: string): string {
const value = EnvProvider.get(name);
if (!value) {
console.warn(`Environment variable ${name} is not set, using default: ${defaultValue}`);
return defaultValue;
}
return value;
}
We can also throw an error or log a warning when a required environment variable is not set:
public static getRequired(name: string): string {
const value = EnvProvider.get(name);
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
Description:
Currently, we are dynamically getting the environment variables and parsing/casting them every time. This is inefficient and also, more importantly, allows env configurations to be dynamically changed, which can modify the behavior of the relay based on the current values of the env vars, which can be a potential exploit.
Solution:
We should change this to only capture them once when the relay is initialized and reuse those as constants
Related issue(s):
Fixes #2686
Notes for reviewer:
env-provider
singletonenv-provider
package is added as a dependency torelay
,server
, andws-server
env-provider
must be immutable and should not provide an interface for overriding or extending once-loaded environment variables but there are many tests where we dynamically override envs in order to test specific cases so we should add these interfaces as wellenv-provider
packageprocess.env.<VAR_NAME>
toEnvProviderService.getInstance().get(<VAR_NAME>)
process.env.CHAIN_ID = '0x160c'
is transformed toEnvProviderService.getInstance().dynamicOverride('CHAIN_ID', '0x160c')
Checklist