Skip to content
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

getNetwork returns "could not detect network" when running as a Cloudflare Worker #1757

Closed
fafrd opened this issue Jul 8, 2021 · 20 comments
Closed
Labels
v5 Issues regarding legacy-v5

Comments

@fafrd
Copy link

fafrd commented Jul 8, 2021

I'm seeing a strange issue only when I use Ethers as a Cloudflare worker. I can create the provider, but then i'm getting a noNetwork bug when it runs:

export async function handleRequest(request) {
  const provider = new ethers.providers.JsonRpcProvider("https://cloudflare-eth.com");
  let n = await provider.getNetwork()

results in the following:

Uncaught (in promise)
Error: could not detect network (event="noNetwork", code=NETWORK_ERROR, version=providers/5.4.1)
    at d.makeError (worker.js:2:161713)
    at d.throwError (worker.js:2:161833)
    at E.<anonymous> (worker.js:2:239261)
    at Generator.throw (<anonymous>)
    at a (worker.js:2:232620)
Uncaught (in response)
Error: could not detect network (event="noNetwork", code=NETWORK_ERROR, version=providers/5.4.1)

This only happens when running as a Cloudflare worker. When running in the browser or repl this code works fine.

I've created a branch to minimally reproduce this bug here: https:/fafrd/ens-reverse-lookup/tree/ethers-bug. See handleRequest.js.

@fafrd fafrd added the investigate Under investigation and may be a bug. label Jul 8, 2021
@ricmoo
Copy link
Member

ricmoo commented Jul 9, 2021

Can you manually call eth_chainId? The Cloudflare endpoints have historically been wildly unreliable, so I’m wondering if their backend is returning bad data… :s

@ricmoo ricmoo added discussion Questions, feedback and general information. and removed investigate Under investigation and may be a bug. labels Jul 9, 2021
@fafrd
Copy link
Author

fafrd commented Jul 9, 2021

Cloudflare is just an example here.... I get the same result when I use my own node. (Didn't want to post the address publicly here)

anyways the returned chainId looks correct when calling both cloudflare and my own node via curl: {"jsonrpc":"2.0","result":"0x1","id":1}

@zemse
Copy link
Collaborator

zemse commented Jul 25, 2021

I get the same result when I use my own node.

If the node is offline or not reachable for some reason then you'll get the error could not detect network.

If you're still facing this issue, can you try if this runs or what error do you get?

const result = await provider.send('eth_chainId', []);

@ricmoo
Copy link
Member

ricmoo commented Jul 26, 2021

It is hard to debug some of these issues, especially with Cloudflare. They often dial-up their anti-bot rules, during which time if the call looks like it is coming from a script (e.g. the user agent matches a vague regex, or certain headers are missing) they block it.

It is often intermittent, but if you can put a few console.log statements in (check out the fetchJson function) I can look more into it. You can probably also just use the provider.on("debug", console.log) to get a better idea of what is happening inside.

I generally advise against the Cloudflare endpoints as they have often been unreliable… :s

@emadak47
Copy link

emadak47 commented Aug 4, 2021

I’m facing a similar issue still.

I’m writing my code in cloudflare wrangler typescript template to query a ERC20 function of a smart contract using “ether.js”. Actually, it doesn’t matter what provider I use (I tried both “infura”, and “cloudflare-eth”), only when I invoke the ERC20 function "await contract.totalSupply()" do I get the same error as above.

By the way, I’m using webpack to bundle everything.

import {ethers} from "ethers";

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
})

async function handleRequest(request: Request): Promise<Response> {

  const tokenAddress = “XXX”;
  const tokenABI = [
    "function totalSupply() external view returns (uint256)"
  ];
  const nodeURL = “XXX”;
  const provider = new ethers.providers.JsonRpcProvider(nodeURL);
  const contract = new ethers.Contract(tokenAddress, tokenABI, provider);
  const bigNumber = await contract.totalSupply();
  const totalSupply = ethers.utils.formatUnits(bigNumber);
  console.log(totalSupply);

  return new Response(“whatever”);
}
  • Removing "addEventListener" and running the rest of the code in a separate file with “tsc index.ts” gives me correct output.

A while ago, I worked around this issue by byte-encoding the function and passing it to the method ‘eth_call’ (my provider was infura in this case), but this is not sustainable. See below.

const message = await fetch(nodeURL, {
    method: "POST",
    body: JSON.stringify({
        jsonrpc:2.0,
        method:'eth_call',
        params:[{
          "to":contractAddress,
          "data": byteEncodedFunction,
        }, "latest"],
        id:1
    })
  })

Any explanation would be appreciated.

@zemse
Copy link
Collaborator

zemse commented Aug 6, 2021

A while ago, I worked around this issue by byte-encoding the function and passing it to the method ‘eth_call’ (my provider was infura in this case), but this is not sustainable.

Did you get the expected response for the eth_call? Can you also try: method: 'eth_chainId', params: []?

@emadak47
Copy link

@zemse yes I got the expected response for eth_call.

Can you also try: method: 'eth_chainId', params: []?

it works and I get the expected output as well 0x1

@ricmoo
Copy link
Member

ricmoo commented Aug 10, 2021

Can you add provider.on("debug", (...args) => { console.log(args); }) and verify the chatter makes sense? It should provide more information when it errors, such as what the server responded with.

@0x62
Copy link

0x62 commented Sep 3, 2021

I'm having this exact issue, see code and debug below:

const provider = new ethers.providers.AlchemyProvider(this._network, ALCHEMY_KEY)
provider.on("debug", (...args) => { console.log(JSON.stringify(args, null, 2)); })
const block = await provider.getBlockNumber()
 {
    "action": "request",
    "provider": {
      "_emitted": {
        "block": -2
      },
      "_eventLoopCache": {
        "eth_blockNumber": {}
      },
      "_events": [
        {
          "once": false,
          "tag": "debug"
        }
      ],
      "_fastQueryDate": 0,
      "_internalBlockNumber": null,
      "_isProvider": true,
      "_lastBlockNumber": -2,
      "_maxInternalBlockNumber": -1024,
      "_network": {
        "chainId": 4,
        "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
        "name": "rinkeby"
      },
      "_nextId": 43,
      "_pollingInterval": 4000,
      "anyNetwork": false,
      "apiKey": "API_KEY",
      "connection": {
        "allowGzip": true,
        "url": "https://eth-rinkeby.alchemyapi.io/v2/API_KEY"
      },
      "formatter": {
        "formats": {
          "block": {},
          "blockWithTransactions": {},
          "filter": {},
          "filterLog": {},
          "receipt": {},
          "receiptLog": {},
          "transaction": {},
          "transactionRequest": {}
        }
      }
    },
    "request": {
      "id": 42,
      "jsonrpc": "2.0",
      "method": "eth_blockNumber",
      "params": []
    }
  }
{
    "action": "response",
    "error": {
      "code": "SERVER_ERROR",
      "reason": "missing response",
      "requestBody": "{\"method\":\"eth_blockNumber\",\"params\":[],\"id\":42,\"jsonrpc\":\"2.0\"}",
      "requestMethod": "POST",
      "serverError": {},
      "url": "https://eth-rinkeby.alchemyapi.io/v2/API_KEY"
    },
    "provider": {
      "_emitted": {
        "block": -2
      },
      "_eventLoopCache": {
        "eth_blockNumber": null
      },
      "_events": [
        {
          "once": false,
          "tag": "debug"
        }
      ],
      "_fastQueryDate": 0,
      "_internalBlockNumber": null,
      "_isProvider": true,
      "_lastBlockNumber": -2,
      "_maxInternalBlockNumber": -1024,
      "_network": {
        "chainId": 4,
        "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
        "name": "rinkeby"
      },
      "_nextId": 43,
      "_pollingInterval": 4000,
      "anyNetwork": false,
      "apiKey": "API_KEY",
      "connection": {
        "allowGzip": true,
        "url": "https://eth-rinkeby.alchemyapi.io/v2/API_KEY"
      },
      "formatter": {
        "formats": {
          "block": {},
          "blockWithTransactions": {},
          "filter": {},
          "filterLog": {},
          "receipt": {},
          "receiptLog": {},
          "transaction": {},
          "transactionRequest": {}
        }
      }
    },
    "request": {
      "id": 42,
      "jsonrpc": "2.0",
      "method": "eth_blockNumber",
      "params": []
    }
  }

Requesting with curl or outside of CF Worker works as expected

@zemse
Copy link
Collaborator

zemse commented Sep 3, 2021

outside of CF Worker works as expected

I was not familiar with CF worker. Does it execute your code in some remote environment? Are you able to reach the eth-rinkeby.alchemyapi.io from that environment?

const message = await fetch(
  "https://eth-rinkeby.alchemyapi.io/v2/API_KEY", 
  {
    method: "POST",
    body: JSON.stringify({
        jsonrpc:2.0,
        method:'eth_blockNumber',
        params:[],
        id:1
    }
  )
})

@0x62
Copy link

0x62 commented Sep 4, 2021

Yes, this works no problem. As a temporary workaround I implemented my own class which uses the underlying ethers.utils.Interface to construct requests, as I only need a handful of methods (gist)

Here is the base templates I used, very minimal webpack setup that listens for two routes. https:/cloudflare/worker-template-router

It looks like there a similar reports in #1834 and #1886

@0x62
Copy link

0x62 commented Sep 4, 2021

I've just tried adding mode: 'cors' to the fetch call (as per #1886) and fails with The 'mode' field on 'RequestInitializerDict' is not implemented., so I'd guess the fetch call is being caught by some error handling and being reported incorrectly as no response.

@TimTinkers
Copy link

Yes, this works no problem. As a temporary workaround I implemented my own class which uses the underlying ethers.utils.Interface to construct requests, as I only need a handful of methods (gist)

Here is the base templates I used, very minimal webpack setup that listens for two routes. cloudflare/worker-template-router

It looks like there a similar reports in #1834 and #1886

Thanks for sharing your workaround. I too am attempting to use Ethers with Cloudflare Workers and am running into the same issue.

@odyslam
Copy link

odyslam commented Dec 31, 2021

I am encountering the same problem. The method that I use is providers.resolveName().

@stevenpack
Copy link

stevenpack commented Jan 9, 2022

Hitting the same issue. I added provider.on("debug", (...args) => { console.log(args); }) @ricmoo, but unfortunately it doesn't fire (no extra console.logs). I dug into getNetwork and found:

 async _uncachedDetectNetwork(): Promise<Network> {
        await timer(0);

        let chainId = null;
        try {
            chainId = await this.send("eth_chainId", [ ]);
        } catch (error) {
            try {
                chainId = await this.send("net_version", [ ]);
            } catch (error) { }
        }

        if (chainId != null) {
            const getNetwork = getStatic<(network: Networkish) => Network>(this.constructor, "getNetwork");
            try {
                return getNetwork(BigNumber.from(chainId).toNumber());
            } catch (error) {
                return logger.throwError("could not detect network", Logger.errors.NETWORK_ERROR, {
                    chainId: chainId,
                    event: "invalidNetwork",
                    serverError: error
                });
            }
        }

        return logger.throwError("could not detect network", Logger.errors.NETWORK_ERROR, {
            event: "noNetwork"
        });
    }

Trying let chainId = await provider.send("eth_chainId", [ ]); on its own with error handling, I get:

  {
          "reason": "missing response",
          "code": "SERVER_ERROR",
          "requestBody": "{\"method\":\"eth_chainId\",\"params\":[],\"id\":42,\"jsonrpc\":\"2.0\"}",
          "requestMethod": "POST",
          "serverError": {},
          "url": "https://cloudflare-eth.com"
        }

Trying chainId = await this.send("net_version", [ ]); errors also, but with an empty response.

Passing the chainId itself to the constructor like const provider = new ethers.providers.JsonRpcProvider("https://cloudflare-eth.com", 1); fails also.

Using Infura as the provider endpoint gave same results.
const provider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/APIKEY", 1);

Happy to help debug or get you setup with a Workers test environment if that helps...

@stevenpack
Copy link

Yes, this works no problem. As a temporary workaround I implemented my own class which uses the underlying ethers.utils.Interface to construct requests, as I only need a handful of methods (gist)
Here is the base templates I used, very minimal webpack setup that listens for two routes. cloudflare/worker-template-router
It looks like there a similar reports in #1834 and #1886

Thanks for sharing your workaround. I too am attempting to use Ethers with Cloudflare Workers and am running into the same issue.

@TimTinkers do you have any more examples using your work around. The method I'm trying to call takes two params, a string and a uint256 and it doesn't end up getting formatted properly. Playing encoder/decder tools, there's a missing 0x between the function signature and the params in the data field...

@TimTinkers
Copy link

Hi @stevenpack, I think you perhaps mistook me for @0x62 who published a workaround. In a nutshell though, this all boils down to using eth_call on the JSON-RPC API. Here's a really good StackOverflow post about it. Hope that helps--if not feel free to chase me up on Discord. I'm 0x Tim Clancy in the Cloudflare Workers Discord and have been doing an awful lot of Ethereum in Workers stuff recently.

@stevenpack
Copy link

You're right @TimTinkers , thanks for clarifying and thanks @0x62 for the work around. Worked me once I added the latest block property.

async _call(fn, params) {
      return await this._fetch('eth_call', [
         {
            to: this._address,
            data: this._iface.encodeFunctionData(fn, params),
         },
         "latest"
      ]);
    }

Will drop a comment on your gist.

@StrawberryChocolateFudge

same problem using provider.getNetwork(). I guess I have to use fetch , huh

@ricmoo
Copy link
Member

ricmoo commented Aug 31, 2024

Closing older issues. But if this is still happening in v6, please re-open or start a new issue.

Side note though; I personally use ethers v6 in Cloudflare Workers without issue now.

Thanks! :)

@ricmoo ricmoo closed this as completed Aug 31, 2024
@ricmoo ricmoo added v5 Issues regarding legacy-v5 and removed discussion Questions, feedback and general information. labels Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v5 Issues regarding legacy-v5
Projects
None yet
Development

No branches or pull requests

9 participants