Skip to content

Commit cd3fa27

Browse files
author
abel
committed
(feat) Added a component to calculate gas limit for the Transaction Broadcaster based on the messages (without running the transaction simulation)
1 parent 2b87432 commit cd3fa27

File tree

5 files changed

+503
-5
lines changed

5 files changed

+503
-5
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import asyncio
2+
3+
from pyinjective.composer import Composer as ProtoMsgComposer
4+
from pyinjective.core.broadcaster import MsgBroadcasterWithPk
5+
from pyinjective.constant import Network
6+
from pyinjective.wallet import PrivateKey
7+
8+
9+
async def main() -> None:
10+
# select network: local, testnet, mainnet
11+
network = Network.testnet()
12+
composer = ProtoMsgComposer(network=network.string())
13+
private_key_in_hexa = "f9db9bf330e23cb7839039e944adef6e9df447b90b503d5b4464c90bea9022f3"
14+
15+
message_broadcaster = MsgBroadcasterWithPk.new_without_simulation(
16+
network=network,
17+
private_key=private_key_in_hexa,
18+
use_secure_connection=True
19+
)
20+
21+
priv_key = PrivateKey.from_hex(private_key_in_hexa)
22+
pub_key = priv_key.to_public_key()
23+
address = pub_key.to_address()
24+
subaccount_id = address.get_subaccount_id(index=0)
25+
26+
# prepare trade info
27+
fee_recipient = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r"
28+
29+
spot_market_id_create = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
30+
31+
spot_orders_to_create = [
32+
composer.SpotOrder(
33+
market_id=spot_market_id_create,
34+
subaccount_id=subaccount_id,
35+
fee_recipient=fee_recipient,
36+
price=3,
37+
quantity=55,
38+
is_buy=True,
39+
is_po=False
40+
),
41+
composer.SpotOrder(
42+
market_id=spot_market_id_create,
43+
subaccount_id=subaccount_id,
44+
fee_recipient=fee_recipient,
45+
price=300,
46+
quantity=55,
47+
is_buy=False,
48+
is_po=False
49+
),
50+
]
51+
52+
# prepare tx msg
53+
msg = composer.MsgBatchUpdateOrders(
54+
sender=address.to_acc_bech32(),
55+
spot_orders_to_create=spot_orders_to_create,
56+
)
57+
58+
# broadcast the transaction
59+
result = await message_broadcaster.broadcast([msg])
60+
print("---Transaction Response---")
61+
print(result)
62+
63+
if __name__ == "__main__":
64+
asyncio.get_event_loop().run_until_complete(main())
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import asyncio
2+
3+
from pyinjective.composer import Composer as ProtoMsgComposer
4+
from pyinjective.async_client import AsyncClient
5+
from pyinjective.core.broadcaster import MsgBroadcasterWithPk
6+
from pyinjective.constant import Network
7+
from pyinjective.wallet import PrivateKey, Address
8+
9+
10+
async def main() -> None:
11+
# select network: local, testnet, mainnet
12+
network = Network.testnet()
13+
composer = ProtoMsgComposer(network=network.string())
14+
15+
# initialize grpc client
16+
client = AsyncClient(network, insecure=False)
17+
await client.sync_timeout_height()
18+
19+
# load account
20+
private_key_in_hexa = "5d386fbdbf11f1141010f81a46b40f94887367562bd33b452bbaa6ce1cd1381e"
21+
priv_key = PrivateKey.from_hex(private_key_in_hexa)
22+
pub_key = priv_key.to_public_key()
23+
address = pub_key.to_address()
24+
25+
message_broadcaster = MsgBroadcasterWithPk.new_for_grantee_account_without_simulation(
26+
network=network,
27+
grantee_private_key=private_key_in_hexa,
28+
use_secure_connection=True
29+
)
30+
31+
# prepare tx msg
32+
market_id = "0x0611780ba69656949525013d947713300f56c37b6175e02f26bffa495c3208fe"
33+
granter_inj_address = "inj1hkhdaj2a2clmq5jq6mspsggqs32vynpk228q3r"
34+
granter_address = Address.from_acc_bech32(granter_inj_address)
35+
granter_subaccount_id = granter_address.get_subaccount_id(index=0)
36+
37+
msg = composer.MsgCreateSpotLimitOrder(
38+
sender=granter_inj_address,
39+
market_id=market_id,
40+
subaccount_id=granter_subaccount_id,
41+
fee_recipient=address.to_acc_bech32(),
42+
price=7.523,
43+
quantity=0.01,
44+
is_buy=True,
45+
is_po=False
46+
)
47+
48+
# broadcast the transaction
49+
result = await message_broadcaster.broadcast([msg])
50+
print("---Transaction Response---")
51+
print(result)
52+
53+
if __name__ == "__main__":
54+
asyncio.get_event_loop().run_until_complete(main())

examples/exchange_client/explorer_rpc/1_GetTxByHash.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
async def main() -> None:
99
# select network: local, testnet, mainnet
10-
network = Network.testnet()
10+
network = Network.mainnet()
1111
client = AsyncClient(network, insecure=False)
1212
composer = Composer(network=network.string())
13-
tx_hash = "0F3EBEC1882E1EEAC5B7BDD836E976250F1CD072B79485877CEACCB92ACDDF52"
13+
tx_hash = "50DD80270052D85835939A393127B5626917007E71A7ABD5205F1A094B976C1B"
1414
transaction_response = await client.get_tx_by_hash(tx_hash=tx_hash)
1515
print(transaction_response)
1616

pyinjective/core/broadcaster.py

Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from abc import ABC, abstractmethod
22
from decimal import Decimal
3-
from typing import List, Optional
3+
from typing import Callable, List, Optional, Tuple
44

55
import math
66
from google.protobuf import any_pb2
@@ -9,6 +9,7 @@
99
from pyinjective.async_client import AsyncClient
1010
from pyinjective.composer import Composer
1111
from pyinjective.constant import Network
12+
from pyinjective.proto.cosmos.authz.v1beta1 import tx_pb2 as cosmos_authz_tx_pb
1213

1314

1415
class BroadcasterAccountConfig(ABC):
@@ -86,6 +87,31 @@ def new_using_simulation(
8687
)
8788
return instance
8889

90+
@classmethod
91+
def new_without_simulation(
92+
cls,
93+
network: Network,
94+
private_key: str,
95+
use_secure_connection: bool = True,
96+
client: Optional[AsyncClient] = None,
97+
composer: Optional[Composer] = None,
98+
):
99+
client = client or AsyncClient(network=network, insecure=not (use_secure_connection))
100+
composer = composer or Composer(network=client.network.string())
101+
account_config = StandardAccountBroadcasterConfig(private_key=private_key)
102+
fee_calculator = MessageBasedTransactionFeeCalculator(
103+
client=client,
104+
composer=composer
105+
)
106+
instance = cls(
107+
network=network,
108+
account_config=account_config,
109+
client=client,
110+
composer=composer,
111+
fee_calculator=fee_calculator,
112+
)
113+
return instance
114+
89115
@classmethod
90116
def new_for_grantee_account_using_simulation(
91117
cls,
@@ -111,6 +137,31 @@ def new_for_grantee_account_using_simulation(
111137
)
112138
return instance
113139

140+
@classmethod
141+
def new_for_grantee_account_without_simulation(
142+
cls,
143+
network: Network,
144+
grantee_private_key: str,
145+
use_secure_connection: bool = True,
146+
client: Optional[AsyncClient] = None,
147+
composer: Optional[Composer] = None,
148+
):
149+
client = client or AsyncClient(network=network, insecure=not (use_secure_connection))
150+
composer = composer or Composer(network=client.network.string())
151+
account_config = GranteeAccountBroadcasterConfig(grantee_private_key=grantee_private_key, composer=composer)
152+
fee_calculator = MessageBasedTransactionFeeCalculator(
153+
client=client,
154+
composer=composer
155+
)
156+
instance = cls(
157+
network=network,
158+
account_config=account_config,
159+
client=client,
160+
composer=composer,
161+
fee_calculator=fee_calculator,
162+
)
163+
return instance
164+
114165
async def broadcast(self, messages: List[any_pb2.Any]):
115166
await self._client.sync_timeout_height()
116167
await self._client.get_account(self._account_config.trading_injective_address)
@@ -196,10 +247,16 @@ def messages_prepared_for_transaction(self, messages: List[any_pb2.Any]) -> List
196247

197248
class SimulatedTransactionFeeCalculator(TransactionFeeCalculator):
198249

199-
def __init__(self, client: AsyncClient, composer: Composer, gas_price: Optional[int] = None):
250+
def __init__(
251+
self,
252+
client: AsyncClient,
253+
composer: Composer,
254+
gas_price: Optional[int] = None,
255+
gas_limit_adjustment_multiplier: Optional[Decimal] = None):
200256
self._client = client
201257
self._composer = composer
202258
self._gas_price = gas_price or self.DEFAULT_GAS_PRICE
259+
self._gas_limit_adjustment_multiplier = gas_limit_adjustment_multiplier or Decimal("1.3")
203260

204261
async def configure_gas_fee_for_transaction(
205262
self,
@@ -216,7 +273,7 @@ async def configure_gas_fee_for_transaction(
216273
if not success:
217274
raise RuntimeError(f"Transaction simulation error: {sim_res}")
218275

219-
gas_limit = math.ceil(Decimal(str(sim_res.gas_info.gas_used)) * Decimal("1.1"))
276+
gas_limit = math.ceil(Decimal(str(sim_res.gas_info.gas_used)) * self._gas_limit_adjustment_multiplier)
220277

221278
fee = [
222279
self._composer.Coin(
@@ -227,3 +284,95 @@ async def configure_gas_fee_for_transaction(
227284

228285
transaction.with_gas(gas=gas_limit)
229286
transaction.with_fee(fee=fee)
287+
288+
289+
class MessageBasedTransactionFeeCalculator(TransactionFeeCalculator):
290+
DEFAULT_GAS_LIMIT = 400_000
291+
DEFAULT_EXCHANGE_GAS_LIMIT = 200_000
292+
293+
def __init__(
294+
self,
295+
client: AsyncClient,
296+
composer: Composer,
297+
gas_price: Optional[int] = None,
298+
base_gas_limit: Optional[int] = None,
299+
base_exchange_gas_limit: Optional[int] = None):
300+
self._client = client
301+
self._composer = composer
302+
self._gas_price = gas_price or self.DEFAULT_GAS_PRICE
303+
self._base_gas_limit = base_gas_limit or self.DEFAULT_GAS_LIMIT
304+
self._base_exchange_gas_limit = base_exchange_gas_limit or self.DEFAULT_EXCHANGE_GAS_LIMIT
305+
306+
self._gas_limit_per_message_type_rules: List[Tuple[Callable, Decimal]] = [
307+
(
308+
lambda message: "MsgPrivilegedExecuteContract" in self._message_type(message=message),
309+
Decimal("6") * self._base_gas_limit
310+
),
311+
(
312+
lambda message: "MsgExecuteContract" in self._message_type(message=message),
313+
Decimal("2.5") * self._base_gas_limit
314+
),
315+
(
316+
lambda message: "wasm" in self._message_type(message=message),
317+
Decimal("1.5") * self._base_gas_limit
318+
),
319+
(
320+
lambda message: "exchange" in self._message_type(message=message),
321+
Decimal("1") * self._base_exchange_gas_limit
322+
),
323+
(
324+
lambda message: self._is_governance_message(message=message),
325+
Decimal("15") * self._base_gas_limit
326+
),
327+
]
328+
329+
async def configure_gas_fee_for_transaction(
330+
self,
331+
transaction: Transaction,
332+
private_key: PrivateKey,
333+
public_key: PublicKey,
334+
):
335+
transaction_gas_limit = math.ceil(self._calculate_gas_limit(messages=transaction.msgs))
336+
337+
fee = [
338+
self._composer.Coin(
339+
amount=math.ceil(self._gas_price * transaction_gas_limit),
340+
denom=self._client.network.fee_denom,
341+
)
342+
]
343+
344+
transaction.with_gas(gas=transaction_gas_limit)
345+
transaction.with_fee(fee=fee)
346+
347+
def _message_type(self, message: any_pb2.Any) -> str:
348+
if isinstance(message, any_pb2.Any):
349+
message_type = message.type_url
350+
else:
351+
message_type = message.DESCRIPTOR.full_name
352+
return message_type
353+
354+
def _is_governance_message(self, message: any_pb2.Any) -> bool:
355+
message_type = self._message_type(message=message)
356+
return "gov" in message_type and ("MsgDeposit" in message_type or "MsgSubmitProposal" in message_type)
357+
358+
def _calculate_gas_limit(self, messages: List[any_pb2.Any]) -> int:
359+
total_gas_limit = Decimal("0")
360+
361+
for message in messages:
362+
applying_rule = next(
363+
(rule_tuple for rule_tuple in self._gas_limit_per_message_type_rules
364+
if rule_tuple[0](message)),
365+
None
366+
)
367+
368+
if applying_rule is None:
369+
total_gas_limit += self._base_gas_limit
370+
else:
371+
total_gas_limit += applying_rule[1]
372+
373+
if self._message_type(message=message).endswith("MsgExec"):
374+
exec_message = cosmos_authz_tx_pb.MsgExec.FromString(message.value)
375+
sub_messages_limit = self._calculate_gas_limit(messages=exec_message.msgs)
376+
total_gas_limit += sub_messages_limit
377+
378+
return math.ceil(total_gas_limit)

0 commit comments

Comments
 (0)