elixir bug where env variables are not updated

I have the following snippet of code which configures an EthereumClient .

defmodule Network.EthereumMainnet do
  use RPC.EthereumClient,
    otp_app: :refiner,
    client_opts: Application.get_env(:refiner, :rpc_url),
    chain_id: Application.get_env(:refiner, :chain_id),
    description: "Ethereum Foundation Mainnet",
    currency: [name: "Ether", ticker_symbol: "ETH"]
end

## config.exs

config :refiner,
  chain_id: Integer.parse(System.get_env("PROOFCHAIN_CHAIN_ID", "1284")) |> elem(0),
  rpc_url: System.get_env("NODE_ETHEREUM_MAINNET")

I wanted to configure the EthereumMainnet on different URL values and make RPC calls.

-> export NODE_ETHEREUM_MAINNET="https://moonbase-alpha.blastapi.io/<somekey>"
->  iex -S mix
Compiling 32 files (.ex)
iex(1)> Refiner.Network.EthereumMainnet.client()
%Refiner.RPC.JSONRPC.HTTPClient{
  request_uri: "https://moonbase-alpha.blastapi.io/<somekey>",
  ws_request_uri: "https://moonbase-alpha.blastapi.io/ws",
  request_headers: [
    {"host", "moonbase-alpha.blastapi.io"},
    {"user-agent", "covalent/refiner"},
    {"content-type", "application/json"}
  ]
}

Now when I exit the shell, reset the NODE_ETHEREUM_MAINNET variable and restart the iex shell, I expect the application to pick up the new value.

-> export NODE_ETHEREUM_MAINNET="https://ethereumrpc.org/"
-> iex -S mix
iex(1)> Refiner.Network.EthereumMainnet.client()
%Refiner.RPC.JSONRPC.HTTPClient{
  request_uri: "https://moonbase-alpha.blastapi.io/<somekey>",
  ws_request_uri: "https://moonbase-alpha.blastapi.io/ws",
  request_headers: [
    {"host", "moonbase-alpha.blastapi.io"},
    {"user-agent", "covalent/refiner"},
    {"content-type", "application/json"}
  ]
}

Clearly the new env value is not picked up. I thought there’s some lingering value in .envrc which has the older value, but that was not the case.

-> grep 'moonbeam.blastapi.io' -r ./
-> #0 matches

Even closing and restarting a new shell didn’t work out.

The key issue is how use is handled by elixir. The use keyword is a macro that is used to inject code from another module (in this case RPC.EthereumClient) into the current module. It effectively translates to AnotherModule.__using__ which is a macro segment in the AnotherModule.

This suggests that the configuration of EthereumMainnet might be locked in at the compile time, and further runtime changes to the env value is not picked up. Sure enough, there’s a Application.compile_env which would report is the env value changed since the module was compiled.

So using:

defmodule Network.EthereumMainnet do
  use RPC.EthereumClient,
    otp_app: :refiner,
-   client_opts: Application.get_env(:refiner, :rpc_url),
-   chain_id: Application.get_env(:refiner, :chain_id),
+   client_opts: Application.compile_env(:refiner, :rpc_url),
+   chain_id: Application.compile_env(:refiner, :chain_id),
    description: "Ethereum Foundation Mainnet",
    currency: [name: "Ether", ticker_symbol: "ETH"]
end

after this, changing the env value triggers warning on the next restart:

-> export NODE_ETHEREUM_MAINNET="https://newrpc.org/"
-> iex -S mix
** (Mix) the application :refiner has a different value set for key :rpc_url during runtime compared to compile time. Since this application environment entry was marked as compile time, this difference can lead to different behaviour than expected:

  * Compile time value was set to: "https://ethereumrpc.org/"
  * Runtime value was set to: "https://newrpc.org/"

To fix this error, you might:

  * Make the runtime value match the compile time one

  * Recompile your project. If the misconfigured application is a dependency, you may need to run "mix deps.clean refiner --build"

  * Alternatively, you can disable this check. If you are using releases, you can set :validate_compile_env to false in your release configuration. If you are using Mix to start your system, you can pass the --no-validate-compile-env flag

One thing you can do now is to run mix deps.clean refiner --build to relock the env variable values, and then start the iex shell.

-> mix deps.clean refiner --build
* Cleaning refiner

iex(1)> Refiner.Network.EthereumMainnet.client()
%Refiner.RPC.JSONRPC.HTTPClient{
  request_uri: "https://newrpc.org/",
  ws_request_uri: "https://newrpc.org:444/ws",
  request_headers: [
    {"host", "newrpc.org"},
    {"user-agent", "covalent/refiner"},
    {"content-type", "application/json"}
  ]
}