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

ProviderNotConnectedError trying to connect to multiple networks in Python [APE-616] #1296

Closed
PoCk3T-SPAI opened this issue Feb 10, 2023 · 9 comments · Fixed by #1876
Closed
Labels
category: docs Improvements or additions to documentation

Comments

@PoCk3T-SPAI
Copy link

PoCk3T-SPAI commented Feb 10, 2023

Environment information

  • apeworx/ape:latest Docker image
$ ape --version
0.6.2

$ ape plugins list
Installed Plugins:
  tokens       0.6.0
  alchemy      0.6.0
  trezor       0.6.0
  ledger       0.6.0
  ens          0.6.0
  infura       0.6.0
  hardhat      0.6.0
  etherscan    0.6.0
  foundry      0.6.0
  vyper        0.6.1
  avalanche    0.1.0a3
  solidity     0.6.0
  bsc          0.6.0
  polygon      0.6.0
  template     0.6.0
  • Contents of your ape-config.yaml (NOTE: do not post anything private like RPC urls or secrets!):
$ cat ape-config.yaml
dependencies:
  - name: OpenZeppelin
    github: OpenZeppelin/openzeppelin-contracts
    version: 4.8.1    

  - name: Chainlink
    github: smartcontractkit/chainlink
    branch: master
    contracts_folder: contracts/src/v0.8
    
solidity:
  import_remapping:
    - "@openzeppelin=OpenZeppelin/4.8.1"
    - "@chainlink=Chainlink/master"

What went wrong?

Please include information like:

  • what command you ran:
ape run deploy
curl http://127.0.0.1:80/networks/list
  • the code that caused the failure:
import uvicorn  # type: ignore  
from fastapi import FastAPI     
import os
from ape import accounts, project, networks 
from ape.cli import NetworkBoundCommand, network_option

NETWORKS = [
    "polygon:mumbai:infura",
    "ethereum:goerli:infura"
]

app = FastAPI()
account = accounts.load(os.environ["WALLET_NAME"])
account.set_autosign(True, passphrase=os.environ["WALLET_PASSPHRASE"])


@app.get(path="/networks/list")
async def list_networks():
    connected_networks = []
    for network in NETWORKS:
        with networks.parse_network_choice(network) as provider:
            provider.connect()
            ecosystem_name = provider.network.ecosystem.name
            network_name = provider.network.name
            provider_name = provider.name
            connected_networks.append(network)
            #click.echo(f"You are connected to network '{ecosystem_name}:{network_name}:{provider_name}'.")
    return connected_networks


def main():        
    uvicorn.run(app, host="0.0.0.0", port=os.environ.get("PORT", 8080))
  • full output of the error you received
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/harambe/.local/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 407, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/harambe/.local/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
  File "/home/harambe/.local/lib/python3.9/site-packages/fastapi/applications.py", line 271, in __call__
    await super().__call__(scope, receive, send)
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/applications.py", line 118, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/harambe/.local/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/home/harambe/.local/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/routing.py", line 706, in __call__
    await route.handle(scope, receive, send)
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/home/harambe/.local/lib/python3.9/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
  File "/home/harambe/.local/lib/python3.9/site-packages/fastapi/routing.py", line 237, in app
    raw_response = await run_endpoint_function(
  File "/home/harambe/.local/lib/python3.9/site-packages/fastapi/routing.py", line 163, in run_endpoint_function
    return await dependant.call(**values)
  File "/home/harambe/project/scripts/deploy.py", line 22, in list_networks
    with networks.parse_network_choice(network) as provider:
  File "/home/harambe/.local/lib/python3.9/site-packages/ape/api/networks.py", line 541, in __enter__
    return self.push_provider()
  File "/home/harambe/.local/lib/python3.9/site-packages/ape/api/networks.py", line 553, in push_provider
    raise ProviderNotConnectedError()
ape.exceptions.ProviderNotConnectedError: Not connected to a network provider.

How can it be fixed?

No idea, but I've researched, seen #1055, and I'm not sure how it was resolved since some discussions might have happened on Discord.

Also, I do not want to run ape on a specific network, I'm trying to automate the deployment of smart contracts on multiple networks when receiving an HTTP API request to do so.

Is what I'm trying to do even possible?
Let me know if you have any thoughts :)

@PoCk3T-SPAI PoCk3T-SPAI added the category: bug Something isn't working label Feb 10, 2023
@NotPeopling2day NotPeopling2day changed the title ProviderNotConnectedError trying to connect to multiple networks in Python ProviderNotConnectedError trying to connect to multiple networks in Python [APE-616] Feb 10, 2023
@PoCk3T-SPAI
Copy link
Author

Closing and answering the solution to myself:

  • do NOT mention any default_network or any default_ecoystem in ape-config.yaml ; somehow this is creating a lot of errors
  • use networks.ethereum.mainnet.use_provider("alchemy") instead of networks.parse_network_choice("ethereum:mainnet:alchemy") (same thing for all other networks, it seems like parse_network_choice just doesn't work how it's supposed to)

@fubuloubu
Copy link
Member

I'm reopening this one because it seems like our documentation needs Improvement for how to use multi-chain environments inside scripts especially creating FastAPI apps

@fubuloubu fubuloubu reopened this Feb 11, 2023
@fubuloubu fubuloubu added category: docs Improvements or additions to documentation and removed category: bug Something isn't working labels Feb 11, 2023
@PoCk3T-SPAI
Copy link
Author

PoCk3T-SPAI commented Feb 11, 2023

@fubuloubu thank you for all your efforts and contribution on ApeWorX/ape issues, not everyone post or comment but I'm sure a lot of people like me appreciate your feedbacks, it helps building trust with the devs and the community a lot; I'm very grateful for it.

Here's a working version of FastAPI + multi-network connections established when receiving an HTTP REST API call:

# ApeX/ape dependencies
from ape import accounts, project, networks
import click

# Web server dependencies
import uvicorn
from fastapi import FastAPI
import os
app = FastAPI()

TESTNETS_IN_APE = [
    networks.ethereum.goerli.use_provider("infura"),
    networks.bsc.testnet.use_provider("geth"),
    networks.avalanche.fuji.use_provider("geth"),
    networks.polygon.mumbai.use_provider("geth"),
    networks.fantom.testnet.use_provider("geth"),
    networks.optimism.goerli.use_provider("alchemy"),
    networks.arbitrum.goerli.use_provider("alchemy"),    
]

def main():
    uvicorn.run(app, host="0.0.0.0", port=os.environ.get("PORT", 8080))

@app.get(path="/networks/list/testnets")
async def list_testnet_networks():
    initial_list = []
    for _provider in TESTNETS_IN_APE:
        try:
            with _provider as provider:            
                initial_list.append({"ecosystem_name": provider.network.ecosystem.name, "network_name": provider.network.name, "provider_name": provider.name})    
                click.echo(f"Connected successfully to network '{provider.network.ecosystem.name}:{provider.network.name}:{provider.name}'.")
        except Exception as e:
            click.echo(f"Failed to connect: {e}")
    return initial_list

Feel free to use this snippet in any form or shape for your future documentation effort.

One small thing that also took time for to connect the dots on: Alchemy and Infura don't support all networks, but 'GETH' is not a bad word, it's actually just a way to connect directly to the public RPC endpoints these L1 and L2 projects maintain, or that some other Web3 infra. provider maintain. Here's my ape-config.yaml config for reference:

geth:
  bsc:
    testnet:
      uri: https://bsc-testnet.public.blastapi.io 
    mainnet:
      uri: https://bsc-mainnet.public.blastapi.io 
  avalanche:
    fuji:
      uri: https://api.avax-test.network/ext/bc/C/rpc # https://ava-testnet.public.blastapi.io/ext/bc/C/rpc
    mainnet:
      uri: https://api.avax.network/ext/bc/C/rpc # https://ava-mainnet.public.blastapi.io/ext/bc/C/rpc
  fantom:
    testnet:
      uri: https://fantom-testnet.public.blastapi.io
    opera:
      uri: https://fantom-mainnet.public.blastapi.io
  polygon:
    mumbai:
      uri: https://polygon-testnet.public.blastapi.io
    mainnet:
      uri: https://polygon-mainnet.public.blastapi.io

in case it also helps with the documentation efforts..

@fubuloubu
Copy link
Member

Thanks for the feedback! Yeah, we are looking much more deeply into FastAPI-based applications for a product idea we have, so this will help us debug some issues with this type of setup and help support you in this use case better.

@PoCk3T-SPAI
Copy link
Author

PoCk3T-SPAI commented Feb 11, 2023

Nice! Well, then, the only thing left standing between the current design of ape and its ability to be turned into a REST API micro-service for Web3 tasks = its limitation importing accounts (private key or mnemonic) through CLI only, whereas the preferred way would be through Python (so that the private key can be passed through a secret manager, like that)

@fubuloubu
Copy link
Member

Nice! Well, then, the only thing left standing between the current design of ape and its ability to be turned into a REST API micro-service for Web3 tasks = its limitation importing accounts (private key or mnemonic) through CLI only, whereas the preferred way would be through Python (so that the private key can be passed through a secret manager, like that)

You can very easily create a plugin for a custom account type, that can import secrets from various managed key systems (Vault, AWS, GCloud, Fireblocks, etc.). This is definitely a lot safer than using plaintext private keys (even accessed through a secret manager), so I would suggest doing that for your preferred platform of choice.

It's very easy to create an account plugin, just implemented a few methods from this class. Here is an example of an account plugin: https://github.com/ApeWorX/ape-trezor

@sabotagebeats
Copy link
Contributor

sabotagebeats commented Feb 20, 2023

$ ape --version
0.6.2

I was able to get this to work with the similar code to the OP:
$cat ape-config.yaml:

dependencies:
  - name: OpenZeppelin
    github: OpenZeppelin/openzeppelin-contracts
    version: 4.8.1    
    
solidity:
  import_remapping:
    - openzeppelin=OpenZeppelin/4.8.1

default_ecosystem: polygon

polygon:
  default_network: mumbai 
  mumbai:
    default_provider: alchemy

$cat scripts/deploy.py:

$ cat scripts/deploy.py 
import uvicorn  # type: ignore  
from fastapi import FastAPI     
import os
from ape import accounts, project, networks 
from ape.cli import NetworkBoundCommand, network_option

NETWORKS = [
    "polygon:mumbai:alchemy",
    "ethereum:goerli:alchemy"
]

app = FastAPI()
account = accounts.load(os.environ["WALLET_NAME"])
account.set_autosign(True, passphrase=os.environ["WALLET_PASSPHRASE"])


@app.get(path="/networks/list")
async def list_networks():
    connected_networks = []
    for network in NETWORKS:
        with networks.parse_network_choice(network) as provider:
            provider.connect()
            ecosystem_name = provider.network.ecosystem.name
            network_name = provider.network.name
            provider_name = provider.name
            connected_networks.append(network)
            print(f"You are connected to network '{ecosystem_name}:{network_name}:{provider_name}'.")
    return connected_networks


def main():        
    uvicorn.run(app, host="0.0.0.0", port=os.environ.get("PORT", 8080))
$ curl http://127.0.0.1:8080/networks/list
["polygon:mumbai:alchemy","ethereum:goerli:alchemy"]
$ ape run deploy
WARNING: Danger! This account will now sign any transaction it's given.
INFO:     Started server process [12403]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
You are connected to network 'polygon:mumbai:alchemy'.
You are connected to network 'ethereum:goerli:alchemy'

This also works with the default network provider in ape-config.yaml commented out.

However this doesn't work if the default network provider is different from the network provider you are using in scripts/deploy.py:

ape/config.yaml:

...
default_ecosystem: polygon

polygon:
  default_network: mumbai 
  mumbai:
    default_provider: infura

scripts/deploy.py:

...
NETWORKS = [
    "polygon:mumbai:alchemy",
    "ethereum:goerli:alchemy"
]
...
    for network in NETWORKS:
        with networks.parse_network_choice(network) as provider:

results in

$ ape run deploy
ERROR: (ProviderNotConnectedError) Not connected to a network provider.

@fubuloubu
Copy link
Member

@sabotagebeats follow up on this: "it seems like networks.parse_network_choice just doesn't work how it's supposed to" and I think we can close this with the doc update you mentioned here:
"However this doesn't work if the default network provider is different from the network provider you are using in scripts/deploy.py"

@antazoey
Copy link
Member

Updates!

  1. tip: If you use the CLI approach for scripting, you can opt out of the initial network connection! The main-method approach for scripts automatically connects for legacy reasons but the cli approach requires you to set it up that way. more info!.

the main method approach will connect to a network before the main method starts executing, similar to brownie.

  1. That being said, it should have still worked even with an initial connection. I installed Ape 0.6.2 and all the same plugins but for some reason I am not able to reproduce. 🤷🏻‍♀️ I don't have a working Docker setup right now, but I tried what I could. However, I ran into a bunch of other weird problems on this version with networks, so there definitely were bugs. Even the error messages are much better now in the 0.7.0 range. I am pretty sure the bugs have been fixed but don't know for sure because I couldn't reproduce your exact problem.

For example, I know there was a bug when calling .connect() yourself when you didnt need to on some providers, I wouldnt be surprised if that was largely related.

  1. I updated the docs, inspired from your code, providing a multi-chain example and am wondering if we can have that close this issue. PR here: docs: multi-network scripting guide update #1876

Thank you! And sorry for being late on this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category: docs Improvements or additions to documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants