Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/pip/pycryptodome-3.18.0
Browse files Browse the repository at this point in the history
  • Loading branch information
justinr1234 committed Jun 20, 2023
2 parents 5fd783b + 972177d commit 3a3768e
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 18 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [[Unreleased]]

## [1.9.0] - 2023-06-13
### Added:
- Added `submit_and_wait` to sign (if needed), autofill, submit a transaction and wait for its final outcome
- `submit` and `send_reliable_submission` now accept an optional boolean param `fail_hard` (if `True` halt the submission if it's not immediately validated)
- Added sidechain devnet support to faucet generation
- Added `user_agent` and `usage_context` to `generate_faucet_wallet`

### Changed:
- Allowed keypairs.sign to take a hex string in addition to bytes

### Fixed:
- Refactored `does_account_exist` and `get_balance` to avoid deprecated methods and use `ledger_index` parameter
- Fixed crashes in the SignerListSet validation
- Fixed crashes in the `SignerListSet` validation
- Improved error messages in `send_reliable_submission`
- Better error handling in reliable submission

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "xrpl-py"
version = "1.8.0"
version = "1.9.0"
description = "A complete Python library for interacting with the XRP ledger"
readme = "README.md"
repository = "https://github.com/XRPLF/xrpl-py"
Expand Down
42 changes: 32 additions & 10 deletions tests/integration/sugar/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
time_of_last_hooks_faucet_call = 0


def sync_generate_faucet_wallet_and_fund_again(self, client, faucet_host=None):
wallet = sync_generate_faucet_wallet(client, faucet_host=faucet_host)
def sync_generate_faucet_wallet_and_fund_again(
self, client, faucet_host=None, usage_context="integration_test"
):
wallet = sync_generate_faucet_wallet(
client, faucet_host=faucet_host, usage_context=usage_context
)
result = client.request(
AccountInfo(
account=wallet.classic_address,
Expand All @@ -25,7 +29,9 @@ def sync_generate_faucet_wallet_and_fund_again(self, client, faucet_host=None):
balance = int(result.result["account_data"]["Balance"])
self.assertTrue(balance > 0)

new_wallet = sync_generate_faucet_wallet(client, wallet, faucet_host=faucet_host)
new_wallet = sync_generate_faucet_wallet(
client, wallet, faucet_host=faucet_host, usage_context="integration_test"
)
new_result = client.request(
AccountInfo(
account=new_wallet.classic_address,
Expand All @@ -35,8 +41,12 @@ def sync_generate_faucet_wallet_and_fund_again(self, client, faucet_host=None):
self.assertTrue(new_balance > balance)


async def generate_faucet_wallet_and_fund_again(self, client, faucet_host=None):
wallet = await generate_faucet_wallet(client, faucet_host=faucet_host)
async def generate_faucet_wallet_and_fund_again(
self, client, faucet_host=None, usage_context="integration_test"
):
wallet = await generate_faucet_wallet(
client, faucet_host=faucet_host, usage_context=usage_context
)
result = await client.request(
AccountInfo(
account=wallet.classic_address,
Expand All @@ -45,7 +55,9 @@ async def generate_faucet_wallet_and_fund_again(self, client, faucet_host=None):
balance = int(result.result["account_data"]["Balance"])
self.assertTrue(balance > 0)

new_wallet = await generate_faucet_wallet(client, wallet, faucet_host=faucet_host)
new_wallet = await generate_faucet_wallet(
client, wallet, faucet_host=faucet_host, usage_context=usage_context
)
new_result = await client.request(
AccountInfo(
account=new_wallet.classic_address,
Expand Down Expand Up @@ -106,13 +118,19 @@ async def _parallel_test_generate_faucet_wallet_custom_host_async_websockets(sel
"wss://s.devnet.rippletest.net:51233"
) as client:
await generate_faucet_wallet_and_fund_again(
self, client, "faucet.devnet.rippletest.net"
self,
client,
"faucet.devnet.rippletest.net",
usage_context="integration_test",
)

async def _parallel_test_generate_faucet_wallet_custom_host_async_json_rpc(self):
client = AsyncJsonRpcClient("https://s.devnet.rippletest.net:51234")
await generate_faucet_wallet_and_fund_again(
self, client, "faucet.devnet.rippletest.net"
self,
client,
"faucet.devnet.rippletest.net",
usage_context="integration_test",
)

def _parallel_test_generate_faucet_wallet_custom_host_sync_websockets(self):
Expand Down Expand Up @@ -167,7 +185,9 @@ async def _parallel_test_generate_faucet_wallet_hooks_v3_testnet_async_websocket
if time_since_last_hooks_call < 10:
time.sleep(11 - time_since_last_hooks_call)

wallet = await generate_faucet_wallet(client)
wallet = await generate_faucet_wallet(
client, usage_context="integration_test"
)
time_of_last_hooks_faucet_call = time.time()

result = await client.request(
Expand Down Expand Up @@ -205,7 +225,9 @@ async def test_fund_given_wallet_hooks_v3_testnet_async_websockets(self):
time.sleep(11 - time_since_last_hooks_call)
time_of_last_hooks_faucet_call = time.time()

new_wallet = await generate_faucet_wallet(client, wallet)
new_wallet = await generate_faucet_wallet(
client, wallet, usage_context="integration_test"
)
new_result = await client.request(
AccountInfo(
account=new_wallet.classic_address,
Expand Down
68 changes: 68 additions & 0 deletions tests/unit/models/transactions/test_autofill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from unittest import TestCase

from xrpl.asyncio.transaction.main import _RESTRICTED_NETWORKS
from xrpl.clients import JsonRpcClient, WebsocketClient
from xrpl.models.transactions import AccountSet
from xrpl.transaction import autofill
from xrpl.wallet.wallet_generation import generate_faucet_wallet

_FEE = "0.00001"


class TestAutofill(TestCase):
# Autofill should override tx networkID for network with ID > 1024
# and build_version from 1.11.0 or later.
def test_networkid_override(self):
client = JsonRpcClient("https://sidechain-net1.devnet.rippletest.net:51234")
wallet = generate_faucet_wallet(client, debug=True)
# Override client build_version since 1.11.0 is not released yet.
client.build_version = "1.11.0"
tx = AccountSet(
account=wallet.classic_address,
fee=_FEE,
domain="www.example.com",
)
tx_autofilled = autofill(tx, client)
self.assertGreaterEqual(client.network_id, _RESTRICTED_NETWORKS)
self.assertEqual(tx_autofilled.network_id, client.network_id)

# Autofill should ignore tx network_id for build version earlier than 1.11.0.
def test_networkid_ignore_early_version(self):
client = JsonRpcClient("https://sidechain-net1.devnet.rippletest.net:51234")
wallet = generate_faucet_wallet(client, debug=True)
# Override client build_version since 1.11.0 is not released yet.
client.build_version = "1.10.0"
tx = AccountSet(
account=wallet.classic_address,
fee=_FEE,
domain="www.example.com",
)
tx_autofilled = autofill(tx, client)
self.assertEqual(tx_autofilled.network_id, None)

# Autofill should ignore tx network_id for networks with ID <= 1024.
def test_networkid_ignore_restricted_networks(self):
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
wallet = generate_faucet_wallet(client, debug=True)
# Override client build_version since 1.11.0 is not released yet.
client.build_version = "1.11.0"
tx = AccountSet(
account=wallet.classic_address,
fee=_FEE,
domain="www.example.com",
)
tx_autofilled = autofill(tx, client)
self.assertLessEqual(client.network_id, _RESTRICTED_NETWORKS)
self.assertEqual(tx_autofilled.network_id, None)

# Autofill should override tx networkID for hooks-testnet.
def test_networkid_override_hooks_testnet(self):
with WebsocketClient("wss://hooks-testnet-v3.xrpl-labs.com") as client:
wallet = generate_faucet_wallet(client, debug=True)
tx = AccountSet(
account=wallet.classic_address,
fee=_FEE,
domain="www.example.com",
)
tx_autofilled = autofill(tx, client)
self.assertEqual(tx_autofilled.network_id, client.network_id)
3 changes: 3 additions & 0 deletions xrpl/asyncio/clients/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Optional

from xrpl.models.requests.request import Request
from xrpl.models.response import Response
Expand All @@ -22,6 +23,8 @@ def __init__(self: Client, url: str) -> None:
url: The url to which this client will connect
"""
self.url = url
self.network_id: Optional[int] = None
self.build_version: Optional[str] = None

@abstractmethod
async def _request_impl(self: Client, request: Request) -> Response:
Expand Down
114 changes: 112 additions & 2 deletions xrpl/asyncio/transaction/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from xrpl.core.addresscodec import is_valid_xaddress, xaddress_to_classic_address
from xrpl.core.binarycodec import encode, encode_for_multisigning, encode_for_signing
from xrpl.core.keypairs.main import sign as keypairs_sign
from xrpl.models.requests import ServerState, SubmitOnly
from xrpl.models.requests import ServerInfo, ServerState, SubmitOnly
from xrpl.models.response import Response
from xrpl.models.transactions import EscrowFinish
from xrpl.models.transactions.transaction import Signer, Transaction
Expand All @@ -23,7 +23,14 @@
from xrpl.wallet.main import Wallet

_LEDGER_OFFSET: Final[int] = 20

# Sidechains are expected to have network IDs above this.
# Networks with ID above this restricted number are expected to specify an
# accurate NetworkID field in every transaction to that chain to prevent replay attacks.
# Mainnet and testnet are exceptions.
# More context: https://github.com/XRPLF/rippled/pull/4370
_RESTRICTED_NETWORKS = 1024
_REQUIRED_NETWORKID_VERSION = "1.11.0"
_HOOKS_TESTNET_ID = 21338
# TODO: make this dynamic based on the current ledger fee
_ACCOUNT_DELETE_FEE: Final[int] = int(xrp_to_drops(2))

Expand Down Expand Up @@ -235,6 +242,10 @@ async def autofill(
The autofilled transaction.
"""
transaction_json = transaction.to_dict()
if not client.network_id:
await _get_network_id_and_build_version(client)
if "network_id" not in transaction_json and _tx_needs_networkID(client):
transaction_json["network_id"] = client.network_id
if "sequence" not in transaction_json:
sequence = await get_next_valid_seq_number(transaction_json["account"], client)
transaction_json["sequence"] = sequence
Expand All @@ -248,6 +259,105 @@ async def autofill(
return Transaction.from_dict(transaction_json)


async def _get_network_id_and_build_version(client: Client) -> None:
"""
Get the network id and build version of the connected server.
Args:
client: The network client to use to send the request.
Raises:
XRPLRequestFailureException: if the rippled API call fails.
"""
response = await client._request_impl(ServerInfo())
if response.is_successful():
if "network_id" in response.result["info"]:
client.network_id = response.result["info"]["network_id"]
if not client.build_version and "build_version" in response.result["info"]:
client.build_version = response.result["info"]["build_version"]
return

raise XRPLRequestFailureException(response.result)


def _tx_needs_networkID(client: Client) -> bool:
"""
Determines whether the transactions required network ID to be valid.
Transaction needs networkID if later than restricted ID and either
the network is hooks testnet or build version is >= 1.11.0.
More context: https://github.com/XRPLF/rippled/pull/4370
Args:
client (Client): The network client to use to send the request.
Returns:
bool: whether the transactions required network ID to be valid
"""
if client.network_id and client.network_id > _RESTRICTED_NETWORKS:
# TODO: remove the buildVersion logic when 1.11.0 is out and widely used.
# Issue: https://github.com/XRPLF/xrpl-py/issues/595
if (
client.build_version
and _is_not_later_rippled_version(
_REQUIRED_NETWORKID_VERSION, client.build_version
)
) or client.network_id == _HOOKS_TESTNET_ID:
return True
return False


def _is_not_later_rippled_version(source: str, target: str) -> bool:
"""
Determines whether the source version is not a later release than the
target version.
Args:
source: the source rippled version.
target: the target rippled version.
Returns:
bool: true if source is earlier, false otherwise.
"""
if source == target:
return True
source_decomp = source.split(".")
target_decomp = target.split(".")
source_major, source_minor = int(source_decomp[0]), int(source_decomp[1])
target_major, target_minor = int(target_decomp[0]), int(target_decomp[1])

# Compare major version
if source_major != target_major:
return source_major < target_major

# Compare minor version
if source_minor != target_minor:
return source_minor < target_minor

source_patch = source_decomp[2].split("-")
target_patch = target_decomp[2].split("-")
source_patch_version = int(source_patch[0])
target_patch_version = int(target_patch[0])

# Compare patch version
if source_patch_version != target_patch_version:
return source_patch_version < target_patch_version

# Compare release version
if len(source_patch) != len(target_patch):
return len(source_patch) > len(target_patch)

if len(source_patch) == 2:
# Compare release types
if not source_patch[1][0].startswith(target_patch[1][0]):
return source_patch[1] < target_patch[1]
# Compare beta versions
if source_patch[1].startswith("b"):
return int(source_patch[1][1:]) < int(target_patch[1][1:])
# Compare rc versions
return int(source_patch[1][2:]) < int(target_patch[1][2:])
return False


def _validate_account_xaddress(
json: Dict[str, Any], account_field: str, tag_field: str
) -> None:
Expand Down
Loading

0 comments on commit 3a3768e

Please sign in to comment.