From 7142ea1c7f07b29837968a24eb9beff48960f5ae Mon Sep 17 00:00:00 2001 From: Louis-Vincent Date: Mon, 6 May 2024 13:35:39 -0400 Subject: [PATCH] examples: added python example --- .gitignore | 2 + examples/python/README.md | 72 ++++++++++++++++++++++++++++ examples/python/helloworld_geyser.py | 57 ++++++++++++++++++++++ examples/python/requirements.txt | 4 ++ 4 files changed, 135 insertions(+) create mode 100644 examples/python/README.md create mode 100644 examples/python/helloworld_geyser.py create mode 100644 examples/python/requirements.txt diff --git a/.gitignore b/.gitignore index c87472eb..0390b458 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ dump.rdb *.sw? .idea/ .vscode +venv +__pycache__/ \ No newline at end of file diff --git a/examples/python/README.md b/examples/python/README.md new file mode 100644 index 00000000..02904f52 --- /dev/null +++ b/examples/python/README.md @@ -0,0 +1,72 @@ +# Python example + +## Instruction + +This Python example uses [grpc.io](https://grpc.io/) library. +It assumes your CA trust store on your machine allows trust the CA from your RPC endpoint. + +## Installation + +Create a virtual environment and install its dependencies: +```bash +$ python -m venv venv +$ . venv/bin/activate +(venv) $ python -m pip install -U pip +(venv) $ python -m pip install -r requirements.txt +``` + +## Launch the helloworld_geyser + +Print the usage: + +```bash +(venv) $ python helloworld_geyser.py --help +Usage: helloworld_geyser.py [OPTIONS] + + Simple program to get the latest solana slot number + +Options: + --rpc-fqdn TEXT Fully Qualified domain name of your RPC endpoint + --x-token TEXT x-token to authenticate each gRPC call + --help Show this message and exit. +``` + +- `rpc-fqdn`: is the fully qualified domain name without the `https://`, such as `index.rpcpool.com`. +- `x-token` : is the x-token to authenticate yourself to the RPC node. + +Here is a full example: + +```bash +(venv) $ python helloworld_geyser.py --rpc-fqdn 'index.rpcpool.com' --x-token '2625ae71-0823-41b3-b3bc-4ff89d762d52' +slot: 264236514 + +``` + +**NOTE**: `2625ae71-0823-41b3-b3bc-4ff89d762d52` is a fake x-token, you need to provide your own token. + +## Generate gRPC service and request signatures + +The library `grpcio` generates the stubs for you. + +From the directory of `helloword_geyser.py` you can generate all the stubs and data types using the following command: + +```bash +(venv) $ python -m grpc_tools.protoc -I../../yellowstone-grpc-proto/proto/ --python_out=. --pyi_out=. --grpc_python_out=. ../../yellowstone-grpc-proto/proto/* +``` + +This will generate: +- geyser_pb2.py +- geyser_pb2.pyi +- geyser_pb2_grpc.py +- solana_storage_pb2.py +- solana_storage_pb2.pyi +- solana_storage_pb2_grpc.py + +Which you can then import into your code. + + +## Useful documentation for grpcio authentication process + +- [secure_channel](https://grpc.github.io/grpc/python/grpc.html#create-client-credentials) + +- [extend auth method via call credentials](https://grpc.io/docs/guides/auth/#extending-grpc-to-support-other-authentication-mechanisms) \ No newline at end of file diff --git a/examples/python/helloworld_geyser.py b/examples/python/helloworld_geyser.py new file mode 100644 index 00000000..24fd00d0 --- /dev/null +++ b/examples/python/helloworld_geyser.py @@ -0,0 +1,57 @@ +from typing import Optional +import grpc +import geyser_pb2 +import geyser_pb2_grpc +import logging +import click + + + +def _triton_sign_request( + callback: grpc.AuthMetadataPluginCallback, + x_token: Optional[str], + error: Optional[Exception], +): + # WARNING: metadata is a 1d-tuple (,), the last comma is necessary + metadata = (("x-token", x_token),) + return callback(metadata, error) + + +class TritonAuthMetadataPlugin(grpc.AuthMetadataPlugin): + """Metadata wrapper for raw access token credentials.""" + + def __init__(self, x_token: str): + self.x_token = x_token + + def __call__( + self, + context: grpc.AuthMetadataContext, + callback: grpc.AuthMetadataPluginCallback, + ): + return _triton_sign_request(callback, self.x_token, None) + + +@click.command() +@click.option('--rpc-fqdn', help='Fully Qualified domain name of your RPC endpoint') +@click.option('--x-token', help='x-token to authenticate each gRPC call') +def helloworld_geyser(rpc_fqdn, x_token): + """Simple program to get the latest solana slot number""" + auth = TritonAuthMetadataPlugin(x_token) + # ssl_creds allow you to use our https endpoint + # grpc.ssl_channel_credentials with no arguments will look through your CA trust store. + ssl_creds = grpc.ssl_channel_credentials() + + # call credentials will be sent on each request if setup with composite_channel_credentials. + call_creds: grpc.CallCredentials = grpc.metadata_call_credentials(auth) + + # Combined creds will store the channel creds aswell as the call credentials + combined_creds = grpc.composite_channel_credentials(ssl_creds, call_creds) + + with grpc.secure_channel(rpc_fqdn, credentials=combined_creds) as channel: + stub = geyser_pb2_grpc.GeyserStub(channel) + response = stub.GetSlot(geyser_pb2.GetSlotRequest()) + print(response) + +if __name__ == '__main__': + logging.basicConfig() + helloworld_geyser() \ No newline at end of file diff --git a/examples/python/requirements.txt b/examples/python/requirements.txt new file mode 100644 index 00000000..70efcfe0 --- /dev/null +++ b/examples/python/requirements.txt @@ -0,0 +1,4 @@ +click==8.1.7 +grpcio==1.63.0 +grpcio-tools==1.63.0 +protobuf==5.26.1