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 get_multiple_accounts and clean up docs #103

Merged
merged 5 commits into from
Sep 21, 2021
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.13.0
current_version = 0.14.0
commit = True
tag = True

Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ types-requests = "*"
notebook = "*"
pytest-asyncio = "*"
pytest-cov = "*"
sphinx-rtd-theme = "*"

[packages]
pynacl = "*"
Expand Down
23 changes: 16 additions & 7 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
project = "solana.py"
copyright = "2020, Michael Huang"
# *IMPORTANT*: Don't manually change the version here. Use the 'bumpversion' utility.4
version = "0.13.0"
version = "0.14.0"
author = "Michael Huang"


Expand Down Expand Up @@ -52,7 +52,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "alabaster"
html_theme = "sphinx_rtd_theme"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
setup(
name="solana",
# *IMPORTANT*: Don't manually change the version here. Use the 'bumpversion' utility.
version="0.13.0",
version="0.14.0",
author="Michael Huang",
author_mail="michaelhly@gmail.com",
description="""Solana.py""",
Expand Down
30 changes: 14 additions & 16 deletions solana/blockhash.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,37 @@


class BlockhashCache:
"""A recent blockhash cache that expires after a given number of seconds."""
"""A recent blockhash cache that expires after a given number of seconds.

def __init__(self, ttl: int = 60) -> None:
"""Instantiate the cache (you only need to do this once).

Args:
----
ttl (int): Seconds until cached blockhash expires.
:param ttl: Seconds until cached blockhash expires.
"""

"""
def __init__(self, ttl: int = 60) -> None:
"""Instantiate the cache (you only need to do this once)."""
maxsize = 300
self.unused_blockhashes: TTLCache = TTLCache(maxsize=maxsize, ttl=ttl)
self.used_blockhashes: TTLCache = TTLCache(maxsize=maxsize, ttl=ttl)

def set(self, blockhash: Blockhash, slot: int) -> None:
def set(self, blockhash: Blockhash, slot: int, used_immediately: bool = False) -> None:
"""Update the cache.

Args:
----
blockhash (Blockhash): new Blockhash value.
slot (int): the slot which the blockhash came from
:param blockhash: new Blockhash value.
:param slot: the slot which the blockhash came from.
:param used_immediately: whether the client used the blockhash immediately after fetching it.

"""
if used_immediately:
if slot not in self.used_blockhashes:
self.used_blockhashes[slot] = blockhash
return
if slot in self.used_blockhashes or slot in self.unused_blockhashes:
return
self.unused_blockhashes[slot] = blockhash

def get(self) -> Blockhash:
"""Get the cached Blockhash. Raises KeyError if cache has expired.

Returns
-------
Blockhash: cached Blockhash.
:return: cached Blockhash.

"""
try:
Expand Down
112 changes: 84 additions & 28 deletions solana/rpc/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,36 +32,36 @@ def MemcmpOpt(*args, **kwargs) -> types.MemcmpOpts: # pylint: disable=invalid-n


class Client(_ClientCore): # pylint: disable=too-many-public-methods
"""Client class."""
"""Client class.

:param endpoint: URL of the RPC endpoint.
:param commitment: Default bank state to query. It can be either "finalized", "confirmed" or "processed".
:param blockhash_cache: (Experimental) If True, keep a cache of recent blockhashes to make
``send_transaction`` calls faster.
You can also pass your own BlockhashCache object to customize its parameters.

The cache works as follows:

1. Retrieve the oldest unused cached blockhash that is younger than ``ttl`` seconds,
where ``ttl`` is defined in the BlockhashCache (we prefer unused blockhashes because
reusing blockhashes can cause errors in some edge cases, and we prefer slightly
older blockhashes because they're more likely to be accepted by every validator).
2. If there are no unused blockhashes in the cache, take the oldest used
blockhash that is younger than ``ttl`` seconds.
3. Fetch a new recent blockhash *after* sending the transaction. This is to keep the cache up-to-date.

If you want something tailored to your use case, run your own loop that fetches the recent blockhash,
and pass that value in your ``.send_transaction`` calls.

"""

def __init__(
self,
endpoint: Optional[str] = None,
commitment: Optional[Commitment] = None,
blockhash_cache: Union[BlockhashCache, bool] = False,
):
"""Init API client.

:param endpoint: URL of the RPC endpoint.
:param commitment: Default bank state to query. It can be either "finalized", "confirmed" or "processed".
:param blockhash_cache: (Experimental) If True, keep a cache of recent blockhashes to make
`send_transaction` calls faster.
You can also pass your own BlockhashCache object to customize its parameters.

The cache works as follows:

1. Retrieve the oldest unused cached blockhash that is younger than `ttl` seconds,
where `ttl` is defined in the BlockhashCache
(we prefer unused blockhashes because reusing blockhashes can cause errors in some edge cases,
and we prefer slightly older blockhashes because they're more likely to be accepted by every validator).
2. If there are no unused blockhashes in the cache, take the oldest used
blockhash that is younger than `ttl` seconds.
3. Fetch a new recent blockhash *after* sending the transaction. This is to keep the cache up-to-date.

If you want something tailored to your use case, run your own loop that fetches the recent blockhash,
and pass that value in your `.send_transaction` calls.

"""
"""Init API client."""
super().__init__(commitment, blockhash_cache)
self._provider = http.HTTPProvider(endpoint)

Expand Down Expand Up @@ -104,7 +104,7 @@ def get_account_info(

- "base58" is limited to Account data of less than 128 bytes.
- "base64" will return base64 encoded data for Account data of any size.
- "jsonPrased" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data.
- "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data.

If jsonParsed is requested but a parser cannot be found, the field falls back to base64 encoding,
detectable when the data field is type. (jsonParsed encoding is UNSTABLE).
Expand Down Expand Up @@ -598,6 +598,62 @@ def get_minimum_balance_for_rent_exemption(
args = self._get_minimum_balance_for_rent_exemption_args(usize, commitment)
return self._provider.make_request(*args)

def get_multiple_accounts(
self,
pubkeys: List[Union[PublicKey, str]],
commitment: Optional[Commitment] = None,
encoding: str = "base64",
data_slice: Optional[types.DataSliceOpts] = None,
) -> types.RPCResponse:
"""Returns all the account info for a list of public keys.

:param pubkeys: list of Pubkeys to query, as base-58 encoded string or PublicKey object.
:param commitment: Bank state to query. It can be either "finalized", "confirmed" or "processed".
:param encoding: (optional) Encoding for Account data, either "base58" (slow), "base64", or
"jsonParsed". Default is "base64".

- "base58" is limited to Account data of less than 128 bytes.
- "base64" will return base64 encoded data for Account data of any size.
- "jsonParsed" encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data.

If jsonParsed is requested but a parser cannot be found, the field falls back to base64 encoding,
detectable when the data field is type. (jsonParsed encoding is UNSTABLE).
:param data_slice: (optional) Option to limit the returned account data using the provided `offset`: <usize> and
`length`: <usize> fields; only available for "base58" or "base64" encoding.

>>> from solana.publickey import PublicKey
>>> solana_client = Client("http://localhost:8899")
>>> pubkeys = [PublicKey("6ZWcsUiWJ63awprYmbZgBQSreqYZ4s6opowP4b7boUdh"), PublicKey("HkcE9sqQAnjJtECiFsqGMNmUho3ptXkapUPAqgZQbBSY")]
>>> solana_client.get_multiple_accounts(pubkeys) # doctest: +SKIP
{
"jsonrpc": "2.0",
"result": {
"context": {"slot": 97531946},
"value": [
{
"data": ["", "base64"],
"executable": False,
"lamports": 1,
"owner": "11111111111111111111111111111111",
"rentEpoch": 225,
},
{
"data": ["", "base64"],
"executable": False,
"lamports": 809441127,
"owner": "11111111111111111111111111111111",
"rentEpoch": 225,
},
],
},
"id": 1,
}
""" # noqa: E501 # pylint: disable=line-too-long
args = self._get_multiple_accounts_args(
pubkeys=pubkeys, commitment=commitment, encoding=encoding, data_slice=data_slice
)
return self._provider.make_request(*args)

def get_program_accounts( # pylint: disable=too-many-arguments
self,
pubkey: Union[str, PublicKey],
Expand Down Expand Up @@ -668,9 +724,9 @@ def get_signature_statuses(
) -> types.RPCResponse:
"""Returns the statuses of a list of signatures.

Unless the `searchTransactionHistory` configuration parameter is included, this method only
Unless the ``search_transaction_history`` configuration parameter is included, this method only
searches the recent status cache of signatures, which retains statuses for all active slots plus
`MAX_RECENT_BLOCKHASHES` rooted slots.
``MAX_RECENT_BLOCKHASHES`` rooted slots.

:param signatures: An array of transaction signatures to confirm.
:param search_transaction_history: If true, a Solana node will search its ledger cache for
Expand Down Expand Up @@ -988,7 +1044,7 @@ def send_transaction(
recent_blockhash = self.blockhash_cache.get()
except ValueError:
blockhash_resp = self.get_recent_blockhash()
recent_blockhash = self._process_blockhash_resp(blockhash_resp)
recent_blockhash = self._process_blockhash_resp(blockhash_resp, used_immediately=True)
else:
blockhash_resp = self.get_recent_blockhash()
recent_blockhash = self.parse_recent_blockhash(blockhash_resp)
Expand All @@ -998,7 +1054,7 @@ def send_transaction(
txn_resp = self.send_raw_transaction(txn.serialize(), opts=opts)
if self.blockhash_cache:
blockhash_resp = self.get_recent_blockhash()
self._process_blockhash_resp(blockhash_resp)
self._process_blockhash_resp(blockhash_resp, used_immediately=False)
return txn_resp

def simulate_transaction(
Expand Down
Loading