Skip to content
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to this project will be documented in this file.

## [1.5.0] - 9999-99-99
### Added
- Added support for all queries in the chain 'tendermint' module
Comment on lines +5 to +7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The entry for version 1.5.0 uses a placeholder date 9999-99-99. It's important to replace this with the actual release date before the changelog is finalized.

- ## [1.5.0] - 9999-99-99
+ ## [1.5.0] - <actual release date>

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
## [1.5.0] - 9999-99-99
### Added
- Added support for all queries in the chain 'tendermint' module
## [1.5.0] - <actual release date>
### Added
- Added support for all queries in the chain 'tendermint' module


## [1.4.1] - 2024-03-12
### Changed
- Updates example scripts that were still using deprecated methods

## [1.4.0] - 2024-03-11
### Added
- Added support for all queries and messages in the chain 'distribution' module
Expand Down
2 changes: 1 addition & 1 deletion examples/chain_client/3_MessageBroadcaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async def main() -> None:
]

# prepare tx msg
msg = composer.MsgBatchUpdateOrders(
msg = composer.msg_batch_update_orders(
sender=address.to_acc_bech32(),
spot_orders_to_create=spot_orders_to_create,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async def main() -> None:
]

# prepare tx msg
msg = composer.MsgBatchUpdateOrders(
msg = composer.msg_batch_update_orders(
sender=address.to_acc_bech32(),
spot_orders_to_create=spot_orders_to_create,
)
Expand Down
2 changes: 1 addition & 1 deletion examples/chain_client/exchange/8_MsgCancelSpotOrder.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async def main() -> None:
order_hash = "0x52888d397d5ae821869c8acde5823dfd8018802d2ef642d3aa639e5308173fcf"

# prepare tx msg
msg = composer.MsgCancelSpotOrder(
msg = composer.msg_cancel_spot_order(
sender=address.to_acc_bech32(), market_id=market_id, subaccount_id=subaccount_id, order_hash=order_hash
)

Expand Down
2 changes: 1 addition & 1 deletion examples/chain_client/exchange/9_MsgBatchUpdateOrders.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ async def main() -> None:
]

# prepare tx msg
msg = composer.MsgBatchUpdateOrders(
msg = composer.msg_batch_update_orders(
sender=address.to_acc_bech32(),
derivative_orders_to_create=derivative_orders_to_create,
spot_orders_to_create=spot_orders_to_create,
Expand Down
16 changes: 16 additions & 0 deletions examples/chain_client/tendermint/query/1_GetNodeInfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import asyncio

from pyinjective.async_client import AsyncClient
from pyinjective.core.network import Network


async def main() -> None:
network = Network.testnet()
client = AsyncClient(network)

node_info = await client.fetch_node_info()
print(node_info)


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Comment on lines +1 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The script 1_GetNodeInfo.py is concise and follows asynchronous programming conventions. Like the previous script, adding error handling for the await client.fetch_node_info() call would improve the robustness of the script by managing exceptions that might occur during the API call.

16 changes: 16 additions & 0 deletions examples/chain_client/tendermint/query/2_GetSyncing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import asyncio

from pyinjective.async_client import AsyncClient
from pyinjective.core.network import Network


async def main() -> None:
network = Network.testnet()
client = AsyncClient(network)

syncing = await client.fetch_syncing()
print(syncing)


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Comment on lines +1 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The script 2_GetSyncing.py is well-structured and follows good practices for asynchronous programming in Python. It correctly initializes the AsyncClient and fetches the syncing status. However, consider adding error handling around the await client.fetch_syncing() call to gracefully handle potential exceptions that could arise from network issues or API changes.

16 changes: 16 additions & 0 deletions examples/chain_client/tendermint/query/3_GetLatestBlock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import asyncio

from pyinjective.async_client import AsyncClient
from pyinjective.core.network import Network


async def main() -> None:
network = Network.testnet()
client = AsyncClient(network)

latest_block = await client.fetch_latest_block()
print(latest_block)


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Comment on lines +1 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The script 3_GetLatestBlock.py effectively demonstrates how to retrieve the latest block information asynchronously. Consistent with previous scripts, incorporating error handling around the await client.fetch_latest_block() call would enhance the script's reliability in handling potential runtime exceptions.

16 changes: 16 additions & 0 deletions examples/chain_client/tendermint/query/4_GetBlockByHeight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import asyncio

from pyinjective.async_client import AsyncClient
from pyinjective.core.network import Network


async def main() -> None:
network = Network.testnet()
client = AsyncClient(network)

block = await client.fetch_block_by_height(height=15793860)
print(block)


if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Comment on lines +1 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The script 4_GetBlockByHeight.py is clear and demonstrates fetching a block by height. For improved usability, consider adding a comment to explain that the height value is a placeholder and should be replaced with the desired block height by the user. Additionally, implementing error handling for the await client.fetch_block_by_height(height=15793860) call would make the script more robust.

19 changes: 19 additions & 0 deletions examples/chain_client/tendermint/query/5_GetLatestValidatorSet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import asyncio

from google.protobuf import symbol_database

from pyinjective.async_client import AsyncClient
from pyinjective.core.network import Network


async def main() -> None:
network = Network.testnet()
client = AsyncClient(network)

validator_set = await client.fetch_latest_validator_set()
print(validator_set)


if __name__ == "__main__":
symbol_db = symbol_database.Default()
asyncio.get_event_loop().run_until_complete(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import asyncio

from google.protobuf import symbol_database

from pyinjective.async_client import AsyncClient
from pyinjective.client.model.pagination import PaginationOption
from pyinjective.core.network import Network


async def main() -> None:
network = Network.testnet()
client = AsyncClient(network)

pagination = PaginationOption(skip=2, limit=4)

validator_set = await client.fetch_validator_set_by_height(height=23040174, pagination=pagination)
print(validator_set)


if __name__ == "__main__":
symbol_db = symbol_database.Default()
asyncio.get_event_loop().run_until_complete(main())
57 changes: 50 additions & 7 deletions pyinjective/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from pyinjective.core.market import BinaryOptionMarket, DerivativeMarket, SpotMarket
from pyinjective.core.network import Network
from pyinjective.core.token import Token
from pyinjective.core.tx.grpc.tendermint_grpc_api import TendermintGrpcApi
from pyinjective.core.tx.grpc.tx_grpc_api import TxGrpcApi
from pyinjective.exceptions import NotFoundError
from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query, query_pb2_grpc as auth_query_grpc
Expand All @@ -49,6 +50,7 @@
query_pb2 as tendermint_query,
query_pb2_grpc as tendermint_query_grpc,
)
from pyinjective.proto.cosmos.crypto.ed25519 import keys_pb2 as ed25519_keys # noqa: F401 for validator set responses
from pyinjective.proto.cosmos.tx.v1beta1 import service_pb2 as tx_service, service_pb2_grpc as tx_service_grpc
from pyinjective.proto.exchange import (
injective_accounts_rpc_pb2 as exchange_accounts_rpc_pb,
Expand Down Expand Up @@ -192,6 +194,12 @@ def __init__(
metadata_query_provider=self._chain_cookie_metadata_requestor
),
)
self.tendermint_api = TendermintGrpcApi(
channel=self.chain_channel,
metadata_provider=lambda: self.network.chain_metadata(
metadata_query_provider=self._chain_cookie_metadata_requestor
),
)
self.token_factory_api = ChainGrpcTokenFactoryApi(
channel=self.chain_channel,
metadata_provider=lambda: self.network.chain_metadata(
Expand Down Expand Up @@ -379,18 +387,15 @@ async def close_chain_channel(self):

async def sync_timeout_height(self):
try:
block = await self.get_latest_block()
self.timeout_height = block.block.header.height + DEFAULT_TIMEOUTHEIGHT
block = await self.fetch_latest_block()
self.timeout_height = int(block["block"]["header"]["height"]) + DEFAULT_TIMEOUTHEIGHT
Comment on lines +390 to +391

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The method sync_timeout_height is not covered by tests. It's crucial to ensure that the functionality for syncing the timeout height is correctly implemented and does not introduce any unexpected behavior.

Would you like me to help generate tests for this method or open a GitHub issue to track this task?

except Exception as e:
LoggerProvider().logger_for_class(logging_class=self.__class__).debug(
f"error while fetching latest block, setting timeout height to 0: {e}"
)
self.timeout_height = 0

# default client methods
async def get_latest_block(self) -> tendermint_query.GetLatestBlockResponse:
req = tendermint_query.GetLatestBlockRequest()
return await self.stubCosmosTendermint.GetLatestBlock(req)

async def get_account(self, address: str) -> Optional[account_pb2.EthAccount]:
"""
Expand Down Expand Up @@ -500,8 +505,8 @@ async def send_tx_block_mode(self, tx_byte: bytes) -> abci_type.TxResponse:
return result.tx_response

async def get_chain_id(self) -> str:
latest_block = await self.get_latest_block()
return latest_block.block.header.chain_id
latest_block = await self.fetch_latest_block()
return latest_block["block"]["header"]["chainId"]
Comment on lines +508 to +509

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The method get_chain_id is not covered by tests. Considering this method fetches the chain ID from the latest block, it's essential to verify its correctness and reliability through testing.

Would you like assistance in creating tests for this method or should I open a GitHub issue to ensure it's tracked?


async def get_grants(self, granter: str, grantee: str, **kwargs):
"""
Expand Down Expand Up @@ -1225,6 +1230,44 @@ async def fetch_denoms_from_creator(
async def fetch_tokenfactory_module_state(self) -> Dict[str, Any]:
return await self.token_factory_api.fetch_tokenfactory_module_state()

# ------------------------------
# region Tendermint module
async def fetch_node_info(self) -> Dict[str, Any]:
return await self.tendermint_api.fetch_node_info()

async def fetch_syncing(self) -> Dict[str, Any]:
return await self.tendermint_api.fetch_syncing()

async def get_latest_block(self) -> tendermint_query.GetLatestBlockResponse:
"""
This method is deprecated and will be removed soon. Please use `fetch_latest_block` instead
"""
warn("This method is deprecated. Use fetch_latest_block instead", DeprecationWarning, stacklevel=2)
req = tendermint_query.GetLatestBlockRequest()
return await self.stubCosmosTendermint.GetLatestBlock(req)

async def fetch_latest_block(self) -> Dict[str, Any]:
return await self.tendermint_api.fetch_latest_block()

async def fetch_block_by_height(self, height: int) -> Dict[str, Any]:
return await self.tendermint_api.fetch_block_by_height(height=height)

async def fetch_latest_validator_set(self) -> Dict[str, Any]:
return await self.tendermint_api.fetch_latest_validator_set()

async def fetch_validator_set_by_height(
self, height: int, pagination: Optional[PaginationOption] = None
) -> Dict[str, Any]:
return await self.tendermint_api.fetch_validator_set_by_height(height=height, pagination=pagination)

async def abci_query(
self, path: str, data: Optional[bytes] = None, height: Optional[int] = None, prove: bool = False
) -> Dict[str, Any]:
return await self.tendermint_api.abci_query(path=path, data=data, height=height, prove=prove)

# endregion
Comment on lines +1234 to +1268

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The newly added methods in the Tendermint module (fetch_node_info, fetch_syncing, fetch_latest_block, fetch_block_by_height, fetch_latest_validator_set, fetch_validator_set_by_height, and abci_query) are crucial for interacting with the Tendermint gRPC API. However, several of these methods are not covered by tests, which is essential for ensuring their correct functionality and integration.

Would you like help in creating tests for these Tendermint module methods, or should I open a GitHub issue to track this task?


# ------------------------------
# Explorer RPC

async def get_tx_by_hash(self, tx_hash: str):
Expand Down
69 changes: 69 additions & 0 deletions pyinjective/core/tx/grpc/tendermint_grpc_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from typing import Any, Callable, Dict, Optional

from grpc.aio import Channel

from pyinjective.client.model.pagination import PaginationOption
from pyinjective.proto.cosmos.base.tendermint.v1beta1 import (
query_pb2 as tendermint_query,
query_pb2_grpc as tendermint_query_grpc,
)
from pyinjective.utils.grpc_api_request_assistant import GrpcApiRequestAssistant


class TendermintGrpcApi:
def __init__(self, channel: Channel, metadata_provider: Callable):
self._stub = tendermint_query_grpc.ServiceStub(channel)
self._assistant = GrpcApiRequestAssistant(metadata_provider=metadata_provider)

async def fetch_node_info(self) -> Dict[str, Any]:
request = tendermint_query.GetNodeInfoRequest()
response = await self._execute_call(call=self._stub.GetNodeInfo, request=request)

return response

async def fetch_syncing(self) -> Dict[str, Any]:
request = tendermint_query.GetSyncingRequest()
response = await self._execute_call(call=self._stub.GetSyncing, request=request)

return response

async def fetch_latest_block(self) -> Dict[str, Any]:
request = tendermint_query.GetLatestBlockRequest()
response = await self._execute_call(call=self._stub.GetLatestBlock, request=request)

return response

async def fetch_block_by_height(self, height: int) -> Dict[str, Any]:
request = tendermint_query.GetBlockByHeightRequest(height=height)
response = await self._execute_call(call=self._stub.GetBlockByHeight, request=request)

return response

async def fetch_latest_validator_set(self) -> Dict[str, Any]:
request = tendermint_query.GetLatestValidatorSetRequest()
response = await self._execute_call(call=self._stub.GetLatestValidatorSet, request=request)

return response

async def fetch_validator_set_by_height(
self, height: int, pagination: Optional[PaginationOption] = None
) -> Dict[str, Any]:
if pagination is None:
pagination = PaginationOption()
request = tendermint_query.GetValidatorSetByHeightRequest(
height=height, pagination=pagination.create_pagination_request()
)
response = await self._execute_call(call=self._stub.GetValidatorSetByHeight, request=request)

return response

async def abci_query(
self, path: str, data: Optional[bytes] = None, height: Optional[int] = None, prove: bool = False
) -> Dict[str, Any]:
request = tendermint_query.ABCIQueryRequest(path=path, data=data, height=height, prove=prove)
response = await self._execute_call(call=self._stub.ABCIQuery, request=request)

return response

async def _execute_call(self, call: Callable, request) -> Dict[str, Any]:
Comment on lines +13 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The TendermintGrpcApi class is well-structured and provides a comprehensive set of methods for interacting with the Tendermint gRPC API. Each method is designed to fetch specific information, such as node info, syncing status, latest block, block by height, latest validator set, validator set by height, and perform an ABCI query. The use of asynchronous programming ensures that these operations do not block the main thread, which is crucial for performance in I/O-bound tasks like network requests.

A few points to consider for further refinement:

  • Error Handling: It's important to ensure robust error handling around the gRPC calls. Consider catching exceptions specific to gRPC calls and handling them appropriately, possibly logging the errors or retrying the calls under certain conditions.
  • Type Annotations: The return type of each method is annotated as Dict[str, Any], which is quite generic. If possible, providing more specific type annotations would enhance code readability and maintainability by making the expected structure of the responses clearer.
  • Documentation: Adding docstrings to each method would greatly improve the understandability of the code. These should describe the purpose of the method, its parameters, its return type, and any exceptions it might raise.
  • Pagination Handling: For methods that support pagination, such as fetch_validator_set_by_height, consider documenting how pagination is handled and what the caller should expect in terms of response structure, especially how to retrieve subsequent pages.

Overall, the implementation aligns well with the objectives of enhancing the software by adding comprehensive support for Tendermint module queries. These suggestions aim to further improve the code's robustness, clarity, and maintainability.

return await self._assistant.execute_call(call=call, request=request)
34 changes: 30 additions & 4 deletions pyinjective/denoms_devnet.ini
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ min_display_quantity_tick_size = 0.00001
description = 'Devnet Spot PROJ/INJ'
base = 18
quote = 18
min_price_tick_size = 0.001
min_display_price_tick_size = 0.001
min_quantity_tick_size = 10000000000000
min_display_quantity_tick_size = 0.00001
min_price_tick_size = 0.00000001
min_display_price_tick_size = 0.00000001
min_quantity_tick_size = 1000000000000000000000
min_display_quantity_tick_size = 1000

[0x2d3b8d8833dda54a717adea9119134556848105fd6028e9a4a526e4e5a122a57]
description = 'Devnet Spot KIRA/INJ'
Expand All @@ -178,6 +178,24 @@ min_display_price_tick_size = 0.00000001
min_quantity_tick_size = 1000000000
min_display_quantity_tick_size = 1000

[0x42edf70cc37e155e9b9f178e04e18999bc8c404bd7b638cc4cbf41da8ef45a21]
description = 'Devnet Spot QUNT/INJ'
base = 6
quote = 18
min_price_tick_size = 10000
min_display_price_tick_size = 0.00000001
min_quantity_tick_size = 1000000000
min_display_quantity_tick_size = 1000

[0xc8fafa1fcab27e16da20e98b4dc9dda45320418c27db80663b21edac72f3b597]
description = 'Devnet Spot HDRO/INJ'
base = 6
quote = 18
min_price_tick_size = 1000000
min_display_price_tick_size = 0.000001
min_quantity_tick_size = 1000000
min_display_quantity_tick_size = 1

[0x1422a13427d5eabd4d8de7907c8340f7e58cb15553a9fd4ad5c90406561886f9]
description = 'Devnet Derivative COMP/USDT PERP'
base = 0
Expand Down Expand Up @@ -296,6 +314,10 @@ decimals = 18
peggy_denom = peggy0xAaEf88cEa01475125522e117BFe45cF32044E238
decimals = 18

[HDRO]
peggy_denom = factory/inj1etz0laas6h7vemg3qtd67jpr6lh8v7xz7gfzqw/hdro
decimals = 6

[INJ]
peggy_denom = inj
decimals = 18
Expand All @@ -316,6 +338,10 @@ decimals = 18
peggy_denom = proj
decimals = 18

[QUNT]
peggy_denom = factory/inj127l5a2wmkyvucxdlupqyac3y0v6wqfhq03ka64/qunt
decimals = 6

[SOMM]
peggy_denom = ibc/34346A60A95EB030D62D6F5BDD4B745BE18E8A693372A8A347D5D53DBBB1328B
decimals = 6
Expand Down
Loading