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

Add Provider.readonly and commitment param for .fetch #58

Merged
merged 4 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.7.0
current_version = 0.8.0
commit = True
tag = True
tag_name = {new_version}
Expand Down
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
# Changelog

## [0.8.0] - Unreleased
## [0.8.0] - 2022-03-09

### Added

- `Provider.readonly` constructor for using AnchorPy only to fetch data
[(#58)](https://github.com/kevinheavey/anchorpy/pull/58)
- `commitment` parameter in `.fetch` and `.fetch_multiple` methods
[(#58)](https://github.com/kevinheavey/anchorpy/pull/58)

### Fixed

- Cache some generated Python types to avoid issues with checking equality
[#57](https://github.com/kevinheavey/anchorpy/pull/57)
[(#57)](https://github.com/kevinheavey/anchorpy/pull/57)

## [0.7.0] - 2022-02-07

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 = "anchorpy"
version = "0.7.0"
version = "0.8.0"
description = "The Python Anchor client."
readme = "README.md"
repository = "https://github.com/kevinheavey/anchorpy"
Expand Down
2 changes: 1 addition & 1 deletion src/anchorpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@
]


__version__ = "0.7.0"
__version__ = "0.8.0"
15 changes: 13 additions & 2 deletions src/anchorpy/program/namespace/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from solana.transaction import TransactionInstruction
from solana.publickey import PublicKey
from solana.rpc.types import MemcmpOpts
from solana.rpc.commitment import Commitment

from anchorpy.coder.common import _account_size
from anchorpy.coder.accounts import (
Expand Down Expand Up @@ -81,11 +82,15 @@ def __init__(
self._coder = coder
self._size = ACCOUNT_DISCRIMINATOR_SIZE + _account_size(idl, idl_account)

async def fetch(self, address: PublicKey) -> Container[Any]:
async def fetch(
self, address: PublicKey, commitment: Optional[Commitment] = None
) -> Container[Any]:
"""Return a deserialized account.

Args:
address: The address of the account to fetch.
commitment: Bank state to query.


Raises:
AccountDoesNotExistError: If the account doesn't exist.
Expand All @@ -94,6 +99,7 @@ async def fetch(self, address: PublicKey) -> Container[Any]:
account_info = await self._provider.connection.get_account_info(
address,
encoding="base64",
commitment=commitment,
)
if not account_info["result"]["value"]:
raise AccountDoesNotExistError(f"Account {address} does not exist")
Expand All @@ -105,7 +111,10 @@ async def fetch(self, address: PublicKey) -> Container[Any]:
return self._coder.accounts.decode(data)

async def fetch_multiple(
self, addresses: List[PublicKey], batch_size: int = 300
self,
addresses: List[PublicKey],
batch_size: int = 300,
commitment: Optional[Commitment] = None,
) -> list[Optional[Container[Any]]]:
"""Return multiple deserialized accounts.

Expand All @@ -115,11 +124,13 @@ async def fetch_multiple(
addresses: The addresses of the accounts to fetch.
batch_size: The number of `getMultipleAccounts` objects to send
in each HTTP request.
commitment: Bank state to query.
"""
accounts = await get_multiple_accounts(
self._provider.connection,
addresses,
batch_size=batch_size,
commitment=commitment,
)
discriminator = _account_discriminator(self._idl_account.name)
result: list[Optional[Container[Any]]] = []
Expand Down
20 changes: 20 additions & 0 deletions src/anchorpy/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ def local(
wallet = Wallet.local()
return cls(connection, wallet, opts)

@classmethod
def readonly(
cls, url: Optional[str] = None, opts: types.TxOpts = DEFAULT_OPTIONS
) -> Provider:
"""Create a `Provider` that can only fetch data, not send transactions.

Args:
url: The network cluster url.
opts: The default transaction confirmation options.
"""
connection = AsyncClient(url, opts.preflight_commitment)
wallet = Wallet.dummy()
return cls(connection, wallet, opts)

@classmethod
def env(cls) -> Provider:
"""Create a `Provider` using the `ANCHOR_PROVIDER_URL` environment variable."""
Expand Down Expand Up @@ -230,3 +244,9 @@ def local(cls) -> Wallet:
with path.open() as f:
keypair = json.load(f)
return cls(Keypair.from_secret_key(bytes(keypair)))

@classmethod
def dummy(cls) -> Wallet:
"""Create a dummy wallet instance that won't be used to sign transactions."""
keypair = Keypair.from_secret_key(bytes([0] * 64)) # noqa: WPS435
return cls(keypair)
14 changes: 10 additions & 4 deletions src/anchorpy/utils/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from toolz import partition_all, concat
from solana.publickey import PublicKey
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Commitment
from solana.transaction import (
AccountMeta,
Transaction,
Expand Down Expand Up @@ -76,7 +77,10 @@ class _MultipleAccountsItem:


async def get_multiple_accounts(
connection: AsyncClient, pubkeys: list[PublicKey], batch_size: int = 3
connection: AsyncClient,
pubkeys: list[PublicKey],
batch_size: int = 3,
commitment: Optional[Commitment] = None,
) -> list[Optional[_MultipleAccountsItem]]:
"""Fetch multiple account infos through batched `getMultipleAccount` RPC requests.

Expand All @@ -85,32 +89,34 @@ async def get_multiple_accounts(
pubkeys: Pubkeys to fetch.
batch_size: The number of `getMultipleAccount` objects to include in each
HTTP request.
commitment: Bank state to query.

Returns:
Account infos and pubkeys.
"""
pubkeys_per_network_request = _GET_MULTIPLE_ACCOUNTS_LIMIT * batch_size
chunks = partition_all(pubkeys_per_network_request, pubkeys)
awaitables = [
_get_multiple_accounts_core(connection, pubkeys_chunk)
_get_multiple_accounts_core(connection, pubkeys_chunk, commitment)
for pubkeys_chunk in chunks
]
results = await gather(*awaitables, return_exceptions=False)
return list(concat(results))


async def _get_multiple_accounts_core(
connection: AsyncClient, pubkeys: list[PublicKey]
connection: AsyncClient, pubkeys: list[PublicKey], commitment: Optional[Commitment]
) -> list[Optional[_MultipleAccountsItem]]:
pubkey_batches = partition_all(_GET_MULTIPLE_ACCOUNTS_LIMIT, pubkeys)
rpc_requests: list[dict[str, Any]] = []
commitment_to_use = connection._commitment if commitment is None else commitment
for pubkey_batch in pubkey_batches:
pubkeys_to_send = [str(pubkey) for pubkey in pubkey_batch]
rpc_request = jsonrpcclient.request(
"getMultipleAccounts",
params=[
pubkeys_to_send,
{"encoding": "base64+zstd", "commitment": connection._commitment},
{"encoding": "base64+zstd", "commitment": commitment_to_use},
],
)
rpc_requests.append(rpc_request)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ async def test_can_use_u128_and_i128(
assert data_account.idata == 22


@mark.asyncio
async def test_readonly_provider(
program: Program, initialized_keypair: Keypair
) -> None:
async with Provider.readonly() as provider:
readonly_program = Program(program.idl, program.program_id, provider=provider)
data_account = await readonly_program.account["Data"].fetch(
initialized_keypair.public_key
)
assert data_account.udata == 1234
assert data_account.idata == 22


@mark.asyncio
async def test_fetch_multiple(program: Program, initialized_keypair: Keypair) -> None:
batch_size = 2
Expand Down