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

[Fix #710] Use network_id to identify faucet_url #715

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

ckeshava
Copy link
Collaborator

High Level Overview of Change

  • use network_id to disambiguate between different XRPL chains
  • move the utility function _get_network_id_and_build_version into client.py file for convinience

Context of Change

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Tests (You added tests for code that already exists, or your new feature included in this PR)
  • Documentation Updates
  • Release

Did you update CHANGELOG.md?

  • Yes
  • No, this change does not impact library users
    This change should not modify the experience for external developers

Test Plan

Tests have been modified to accomodate the new API for grabbing the faucet_url

- move the utility function _get_network_id_and_build_version into client.py file for convinience
@@ -51,3 +54,24 @@ async def _request_impl(
:meta private:
"""
pass


async def _get_network_id_and_build_version(client: Client) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not automatically run this on every client upon instantiation, instead of having it be a helper function? Then it only needs to be run once, instead of every time a function that uses this is called.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We are not duplicating invocations of this function. Here are two examples where the network_id information persists in the client object.

But I agree, this could be done in a cleaner way.
I can only invoke _get_network_id_and_build_version function after a client has opened the socket-connection. So, I'll need to invoke this in this open() method of all the child classes of Client

let me try it out

Copy link
Collaborator

@mvadari mvadari Jun 28, 2024

Choose a reason for hiding this comment

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

I'm open to the option of doing it lazily - i.e. only if and as needed, but only once. All that would require is checking if the network ID has been instantiated before running the request.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

okay, I've added a check: 23d3eb3

import math
from typing import Any, Dict, Optional, cast

from typing_extensions import Final

from xrpl.asyncio.account import get_next_valid_seq_number
from xrpl.asyncio.clients import Client, XRPLRequestFailureException
from xrpl.asyncio.clients.client import _get_network_id_and_build_version
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any function that gets imported elsewhere shouldn't start with a _.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed in ab918dc

Comment on lines 186 to 187
elif network_id < 0:
raise XRPLFaucetException("network_id cannot be negative.")
Copy link
Collaborator

@mvadari mvadari Jul 3, 2024

Choose a reason for hiding this comment

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

Does rippled even allow this scenario?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the config file does not allow negative numbers as input for network_id. But it accepts really large numbers (for eg: 4294967295 which could be negative numbers, if typecast into smaller signed-types)

also, rippled unit tests allow negative numbers - XRPLF/rippled@develop...ckeshava:rippled:negNetworkID

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think xrpl-py needs any specific checks for this, "we don't understand this network ID" is fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated in f06e5c1

Comment on lines 189 to 191
raise XRPLFaucetException(
"Cannot fund an account with a client that is not on the testnet or devnet."
"Cannot create faucet URL without network_id or the faucet_host information."
)
Copy link
Collaborator

@mvadari mvadari Jul 3, 2024

Choose a reason for hiding this comment

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

Thoughts on throwing this error in the main generate_faucet_wallet function instead of here? e.g.

if network_id is None and faucet_host is None:
  raise XRPLFaucetException(...)

Otherwise, this error currently executes on an unknown devnet as well, which may have a network ID.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't object to that idea, but how is that different from the current implementation?

At line 189 (the last line of the get_faucet_url method), we have established that faucet_host == None. You are correct, an unknown devnet might have a networkID, but how can I determine the host_url from a networkID (say 1345 ?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

if network_id is None and faucet_host is None:
  raise XRPLFaucetException(...)

The said devnet will have a networkID, hence this condition needs to be:

if faucet_host is None and networkID >= 1025:
  raise XRPLFaucetException(...)

The current implementation is equivalent to the above if-condition

Copy link
Collaborator

Choose a reason for hiding this comment

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

The difference comes from typing and failing fast. IMO it makes more sense for get_faucet_url to not accept None as a valid network ID.

You can't determine the host URL from an unknown Devnet, but the error message should be different.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we cannot expect all networks to have a valid NetworkID isn't it?
If we force get_faucet_url to accept valid networkIDs only, then some of the processing logic shifts into generate_faucet_wallet method.

def get_faucet_url(network_id: int) -> str:
    if network_id == 1:
        return _TEST_FAUCET_URL
    elif network_id == 2:
        return _DEV_FAUCET_URL
    elif network_id == 0:
        raise XRPLFaucetException("Cannot create faucet with a client on mainnet.")
    elif network_id < 0:
        raise XRPLFaucetException("network_id cannot be negative.")

    # unreachable code
    return ""

async def generate_faucet_wallet(
    client: Client,
    wallet: Optional[Wallet] = None,
    debug: bool = False,
    faucet_host: Optional[str] = None,
    usage_context: Optional[str] = None,
    user_agent: Optional[str] = "xrpl-py",
) -> Wallet:
    if not client.network_id:
        await get_network_id_and_build_version(client)

    if client.network_id is None and faucet_host is None:
        raise XRPLFaucetException(
            "Cannot create faucet URL without network_id or the faucet_host information"
        )

    if faucet_host is not None:
        faucet_url = process_faucet_host_url(faucet_host)
    else:
        faucet_url = get_faucet_url(client.network_id)
        
    # remaining code in the function ...

Copy link
Collaborator

Choose a reason for hiding this comment

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

That flow makes more sense to me, personally.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

alrighty

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated sometime before f06e5c1

Comment on lines 176 to 177
if faucet_host is not None:
return process_faucet_host_url(faucet_host)
Copy link
Collaborator

@mvadari mvadari Jul 3, 2024

Choose a reason for hiding this comment

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

Upon further thought, I think it might make more sense to bring this code out of this function and have get_faucet_url be entirely based on the network_id.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah, I put down a snippet in a comment below

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated somewhere prior to f06e5c1

self.assertEqual(get_faucet_url(json_client_url, custom_host), expected_faucet)
self.assertEqual(get_faucet_url(ws_client_url, custom_host), expected_faucet)

def test_get_faucet_wallet_test(self):
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

these test cases are already covered in the below class TestProcessFaucetHostURL

@ckeshava ckeshava requested a review from mvadari July 3, 2024 20:24
raise XRPLFaucetException("Unable to parse the specified network_id.")

# this line is unreachable. Custom devnets must specify a faucet_host input
return ""
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should raise an error here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed in f06e5c1

Comment on lines 172 to 176
if "sidechain-net2" in url: # sidechain issuing chain devnet
raise XRPLFaucetException(
"Cannot fund an account on an issuing chain. Accounts must be created via "
"the bridge."
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This piece still needs to be included. The network ID is 262.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

thanks. fixed in f06e5c1

Comment on lines 169 to 180
return process_faucet_host_url(faucet_host)
if "altnet" in url or "testnet" in url: # testnet
if network_id == 1:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can this if-else be replaced with a dictionary? It'll be easier to maintain that way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated in f06e5c1

CHANGELOG.md Outdated Show resolved Hide resolved
if "altnet" in url or "testnet" in url: # testnet
return _TEST_FAUCET_URL
if "sidechain-net2" in url: # sidechain issuing chain devnet
map_network_id_to_url: Dict[int, str] = {1: _TEST_FAUCET_URL, 2: _DEV_FAUCET_URL}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This object should be outside of the function, since it's not dependent on the data inside. And the name should be in uppercase.

@@ -17,6 +17,7 @@
_DEV_FAUCET_URL: Final[str] = "https://faucet.devnet.rippletest.net/accounts"

_TIMEOUT_SECONDS: Final[int] = 40
_MAP_NETWORK_ID_TO_URL: Dict[int, str] = {1: _TEST_FAUCET_URL, 2: _DEV_FAUCET_URL}
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit:

Suggested change
_MAP_NETWORK_ID_TO_URL: Dict[int, str] = {1: _TEST_FAUCET_URL, 2: _DEV_FAUCET_URL}
_NETWORK_ID_URL_MAP: Dict[int, str] = {1: _TEST_FAUCET_URL, 2: _DEV_FAUCET_URL}

@ckeshava ckeshava requested a review from khancode July 17, 2024 20:55
@mvadari mvadari linked an issue Sep 17, 2024 that may be closed by this pull request
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.

Use Network ID to determine the correct faucet to use
3 participants