searcher-cli.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import time
  2. from typing import List
  3. import click
  4. from solana.rpc.api import Client
  5. from solana.rpc.commitment import Processed
  6. from solders.keypair import Keypair
  7. from solders.pubkey import Pubkey
  8. from solders.system_program import TransferParams, transfer
  9. from solders.transaction import Transaction, VersionedTransaction
  10. from spl.memo.instructions import MemoParams, create_memo
  11. from jito_searcher_client.convert import tx_to_protobuf_packet
  12. from jito_searcher_client.generated.bundle_pb2 import Bundle
  13. from jito_searcher_client.generated.searcher_pb2 import (
  14. ConnectedLeadersRequest,
  15. NextScheduledLeaderRequest,
  16. NextScheduledLeaderResponse,
  17. PendingTxSubscriptionRequest,
  18. SendBundleRequest,
  19. MempoolSubscription,
  20. WriteLockedAccountSubscriptionV0,
  21. ProgramSubscriptionV0,
  22. )
  23. from jito_searcher_client.generated.searcher_pb2_grpc import SearcherServiceStub
  24. from jito_searcher_client.searcher import get_searcher_client
  25. @click.group("cli")
  26. @click.pass_context
  27. @click.option(
  28. "--keypair-path",
  29. help="Path to a keypair that is authenticated with the block engine.",
  30. required=True,
  31. )
  32. @click.option(
  33. "--block-engine-url",
  34. help="Block Engine URL",
  35. required=True,
  36. )
  37. def cli(
  38. ctx,
  39. keypair_path: str,
  40. block_engine_url: str,
  41. ):
  42. """
  43. This script can be used to interface with the block engine as a jito_searcher_client.
  44. """
  45. with open(keypair_path) as kp_path:
  46. kp = Keypair.from_json(kp_path.read())
  47. ctx.obj = get_searcher_client(block_engine_url, kp)
  48. @click.command("mempool-accounts")
  49. @click.pass_obj
  50. @click.argument("accounts", required=True, nargs=-1)
  51. def mempool_accounts(client: SearcherServiceStub, accounts: List[str]):
  52. """
  53. Stream transactions from the mempool if they write-lock one of the provided accounts
  54. """
  55. leader: NextScheduledLeaderResponse = client.GetNextScheduledLeader(NextScheduledLeaderRequest())
  56. print(
  57. f"next scheduled leader is {leader.next_leader_identity} in {leader.next_leader_slot - leader.current_slot} slots"
  58. )
  59. for notification in client.SubscribeMempool(
  60. MempoolSubscription(wla_v0_sub=WriteLockedAccountSubscriptionV0(accounts=accounts))
  61. ):
  62. for packet in notification.transactions:
  63. print(VersionedTransaction.from_bytes(packet.data))
  64. @click.command("mempool-programs")
  65. @click.pass_obj
  66. @click.argument("programs", required=True, nargs=-1)
  67. def mempool_programs(client: SearcherServiceStub, programs: List[str]):
  68. """
  69. Stream transactions from the mempool if they mention one of the provided programs
  70. """
  71. leader: NextScheduledLeaderResponse = client.GetNextScheduledLeader(NextScheduledLeaderRequest())
  72. print(
  73. f"next scheduled leader is {leader.next_leader_identity} in {leader.next_leader_slot - leader.current_slot} slots"
  74. )
  75. for notification in client.SubscribeMempool(
  76. MempoolSubscription(program_v0_sub=ProgramSubscriptionV0(programs=programs))
  77. ):
  78. for packet in notification.transactions:
  79. print(VersionedTransaction.from_bytes(packet.data))
  80. @click.command("next-scheduled-leader")
  81. @click.pass_obj
  82. def next_scheduled_leader(client: SearcherServiceStub):
  83. """
  84. Find information on the next scheduled leader.
  85. """
  86. next_leader = client.GetNextScheduledLeader(NextScheduledLeaderRequest())
  87. print(f"{next_leader=}")
  88. @click.command("connected-leaders")
  89. @click.pass_obj
  90. def connected_leaders(client: SearcherServiceStub):
  91. """
  92. Get leaders connected to this block engine.
  93. """
  94. leaders = client.GetConnectedLeaders(ConnectedLeadersRequest())
  95. print(f"{leaders=}")
  96. @click.command("tip-accounts")
  97. @click.pass_obj
  98. def tip_accounts(client: SearcherServiceStub):
  99. """
  100. Get the tip accounts from the block engine.
  101. """
  102. accounts = client.GetNextScheduledLeader(NextScheduledLeaderRequest())
  103. print(f"{accounts=}")
  104. @click.command("send-bundle")
  105. @click.pass_obj
  106. @click.option(
  107. "--rpc-url",
  108. help="RPC URL path",
  109. type=str,
  110. required=True,
  111. )
  112. @click.option(
  113. "--payer",
  114. help="Path to payer keypair",
  115. type=str,
  116. required=True,
  117. )
  118. @click.option(
  119. "--message",
  120. help="Message in the bundle",
  121. type=str,
  122. required=True,
  123. )
  124. @click.option(
  125. "--num_txs",
  126. help="Number of transactions in the bundle (max is 5)",
  127. type=int,
  128. required=True,
  129. )
  130. @click.option(
  131. "--lamports",
  132. help="Number of lamports to tip in each transaction",
  133. type=int,
  134. required=True,
  135. )
  136. @click.option(
  137. "--tip_account",
  138. help="Tip account to tip",
  139. type=str,
  140. required=True,
  141. )
  142. def send_bundle(
  143. client: SearcherServiceStub,
  144. rpc_url: str,
  145. payer: str,
  146. message: str,
  147. num_txs: int,
  148. lamports: int,
  149. tip_account: str,
  150. ):
  151. """
  152. Send a bundle!
  153. """
  154. with open(payer) as kp_path:
  155. payer_kp = Keypair.from_json(kp_path.read())
  156. tip_account = Pubkey.from_string(tip_account)
  157. rpc_client = Client(rpc_url)
  158. balance = rpc_client.get_balance(payer_kp.pubkey()).value
  159. print(f"payer public key: {payer_kp.pubkey()} {balance=}")
  160. is_leader_slot = False
  161. print("waiting for jito leader...")
  162. while not is_leader_slot:
  163. time.sleep(0.5)
  164. next_leader: NextScheduledLeaderResponse = client.GetNextScheduledLeader(NextScheduledLeaderRequest())
  165. num_slots_to_leader = next_leader.next_leader_slot - next_leader.current_slot
  166. print(f"waiting {num_slots_to_leader} slots to jito leader")
  167. is_leader_slot = num_slots_to_leader <= 2
  168. blockhash = rpc_client.get_latest_blockhash().value.blockhash
  169. block_height = rpc_client.get_block_height(Processed).value
  170. # Build bundle
  171. txs: List[Transaction] = []
  172. for idx in range(num_txs):
  173. ixs = [
  174. create_memo(
  175. MemoParams(
  176. program_id=Pubkey.from_string("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
  177. signer=payer_kp.pubkey(),
  178. message=bytes(f"jito bundle {idx}: {message}", "utf-8"),
  179. )
  180. )
  181. ]
  182. if idx == num_txs - 1:
  183. # Adds searcher tip on last tx
  184. ixs.append(
  185. transfer(TransferParams(from_pubkey=payer_kp.pubkey(), to_pubkey=tip_account, lamports=lamports))
  186. )
  187. tx = Transaction.new_signed_with_payer(
  188. instructions=ixs, payer=payer_kp.pubkey(), signing_keypairs=[payer_kp], recent_blockhash=blockhash
  189. )
  190. print(f"{idx=} signature={tx.signatures[0]}")
  191. txs.append(tx)
  192. # Note: setting meta.size here is important so the block engine can deserialize the packet
  193. packets = [tx_to_protobuf_packet(tx) for tx in txs]
  194. uuid_response = client.SendBundle(SendBundleRequest(bundle=Bundle(header=None, packets=packets)))
  195. print(f"bundle uuid: {uuid_response.uuid}")
  196. for tx in txs:
  197. print(
  198. rpc_client.confirm_transaction(
  199. tx.signatures[0], Processed, sleep_seconds=0.5, last_valid_block_height=block_height + 10
  200. )
  201. )
  202. if __name__ == "__main__":
  203. cli.add_command(mempool_accounts)
  204. cli.add_command(mempool_programs)
  205. cli.add_command(next_scheduled_leader)
  206. cli.add_command(connected_leaders)
  207. cli.add_command(tip_accounts)
  208. cli.add_command(send_bundle)
  209. cli()