Skip to content

Commit

Permalink
docs: Simple python examples (#9)
Browse files Browse the repository at this point in the history
3 simple python examples using the json, web and sui apis.
  • Loading branch information
gdanezis authored Jun 7, 2024
1 parent 7134758 commit 5a120d2
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,17 @@
# mdBook
build/

# Python
__pycache__/
.venv/
*.pyc

# Misc
*.key
.env
config.yml
working_dir
*.log

# Walrus binary
examples/CONFIG/bin/walrus
3 changes: 3 additions & 0 deletions examples/CONFIG/bin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Configuration

Place the 'walrus' client binary for your system in this directory.
38 changes: 38 additions & 0 deletions examples/CONFIG/config_dir/client_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
system_pkg: 0x17108fb344dbea0d315e6f44cdd3af12a028cd568bafcbfb7f7a7b152719d15d
system_object: 0x3fb18e675ad41158f59acbdb7f574136a198cbd83be9b968c0fdeaa139c312f9

# You can define a custom path to your Sui wallet configuration here. If this is unset or `null`,
# the wallet is configured from `./client.yaml`, `./sui_config.yaml` (both relative to your current
# working directory), or `~/.sui/sui_config/client.yaml` in this order.
wallet_config: null

# Default values for the client are commented out.
#
# There is no risk in playing around with these values.
# Worst case, you may not be able to store/read from Walrus.

# communication_config:
# max_concurrent_writes: null
# max_concurrent_sliver_reads: null
# max_concurrent_metadata_reads: 3
# reqwest_config:
# total_timeout:
# secs: 180
# nanos: 0
# pool_idle_timeout: null
# http2_keep_alive_timeout:
# secs: 5
# nanos: 0
# http2_keep_alive_interval:
# secs: 30
# nanos: 0
# http2_keep_alive_while_idle: true
# request_rate_config:
# max_node_connections: 10
# max_retries: 5
# min_backoff:
# secs: 2
# nanos: 0
# max_backoff:
# secs: 60
# nanos: 0
13 changes: 13 additions & 0 deletions examples/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Walrus Python Examples

## Prerequisites

- [Configure Sui Client](https://docs.sui.io/guides/developer/getting-started/connect) to connect
to testnet, and some testnet Sui tokens.
- Configure Walrus TODO(#12).
- Update the paths PATH_TO_WALRUS and PATH_TO_WALRUS_CONFIG and other
constant in `utils.py`.

## Index of examples

- ...
97 changes: 97 additions & 0 deletions examples/python/hello_walrus_jsonapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Example of uploading and downloading a file to / from the Walrus service
# Using the walrus client json input & output facilities.

# Std lib imports
import os
import subprocess
import json
import tempfile
import base64

import requests

from utils import num_to_blob_id, PATH_TO_WALRUS, PATH_TO_WALRUS_CONFIG, FULL_NODE_URL

try:
# Create a 1MB file of random data
random_data = os.urandom(1024 * 1024)
tmp = tempfile.NamedTemporaryFile(delete=False)
tmp.write(random_data)
tmp.close()

# Part 1. Upload the file to the Walrus service
store_json_command = f"""{{ "config" : "{PATH_TO_WALRUS_CONFIG}",
"command" : {{ "store" :
{{ "file" : "{tmp.name}", "epochs" : 2 }}}}
}}"""
result = subprocess.run(
[PATH_TO_WALRUS, "json"],
text=True,
capture_output=True,
input=store_json_command,
)
assert result.returncode == 0

# Parse the response and display key information
json_result_dict = json.loads(result.stdout.strip())
print(
f"Upload Blob ID: {json_result_dict['blob_id']} Size {len(random_data)} bytes"
)
sui_object_id = json_result_dict["sui_object_id"]
blob_id = json_result_dict["blob_id"]
print(f"Certificate in Object ID: {sui_object_id}")

# Part 2. Download the file from the Walrus service
read_json_command = f"""{{ "config" : "{PATH_TO_WALRUS_CONFIG}",
"command" : {{ "read" :
{{ "blob_id" : "{json_result_dict['blob_id']}" }}}}
}}"""
result = subprocess.run(
[PATH_TO_WALRUS, "json"],
text=True,
capture_output=True,
input=read_json_command,
)
assert result.returncode == 0

# Parse the response and display key information
json_result_dict = json.loads(result.stdout.strip())
downloaded_data = base64.b64decode(json_result_dict["blob"])
assert downloaded_data == random_data

print(
f"Download Blob ID: {json_result_dict['blob_id']} Size {len(downloaded_data)} bytes"
)

# Part 3. Check the availability of the blob
request = {
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getObject",
"params": [
sui_object_id,
{
"showType": True,
"showOwner": False,
"showPreviousTransaction": True,
"showDisplay": False,
"showContent": True,
"showBcs": False,
"showStorageRebate": False,
},
],
}
response = requests.post(FULL_NODE_URL, json=request)
object_content = response.json()["result"]["data"]["content"]
print("Object content:")
print(json.dumps(object_content, indent=4))

# Check that the blob ID matches the one we uploaded
blob_id_downloaded = int(object_content["fields"]["blob_id"])
if num_to_blob_id(blob_id_downloaded) == blob_id:
print("Blob ID matches certificate!")
else:
print("Blob ID does not match")

finally:
os.unlink(tmp.name)
48 changes: 48 additions & 0 deletions examples/python/hello_walrus_sui_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Example of querying the Walrus system object on Sui

# Std lib imports
import requests
import re

from utils import PATH_TO_WALRUS_CONFIG

system_object_id = re.findall(
r"system_object:[ ]*(.*)", open(PATH_TO_WALRUS_CONFIG).read()
)[0]
print(f"System object ID: {system_object_id}")

# Query the Walrus system object on Sui
request = {
"jsonrpc": "2.0",
"id": 1,
"method": "sui_getObject",
"params": [
system_object_id,
{
"showType": True,
"showOwner": False,
"showPreviousTransaction": True,
"showDisplay": False,
"showContent": True,
"showBcs": False,
"showStorageRebate": False,
},
],
}
response = requests.post("https://fullnode.testnet.sui.io:443", json=request)
assert response.status_code == 200

system_object_content = response.json()["result"]["data"]["content"]["fields"]
committee = system_object_content["current_committee"]["fields"]["bls_committee"][
"fields"
]

print(
f'Current walrus epoch: {system_object_content["current_committee"]["fields"]["epoch"]}'
)
print(
f'Number of members: {len(committee["members"])} Number of shards: {committee["n_shards"]}'
)
print(f'Price per unit size: {system_object_content["price_per_unit_size"]} MIST')
print(f'Total capacity size: {system_object_content["total_capacity_size"]} bytes')
print(f'Used capacity size: {system_object_content["used_capacity_size"]} bytes')
63 changes: 63 additions & 0 deletions examples/python/hello_walrus_webapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Example of uploading and downloading a file to / from the Walrus service
# Using the walrus client web API facilities.
#
# Prerequisites:
#
# - Run the Walrus client in daemon mode:
# $ ../CONFIG/bin/walrus --config ../CONFIG/config_dir/client_config.yaml daemon -b 127.0.0.1:8899
#

# Std lib imports
import os
import time

# External requests HTTP library
import requests

ADDRESS = "127.0.0.1:8899"
EPOCHS = "5"


# Helper functions to upload a blob
def upload_blob(ADDRESS, EPOCHS, data):
# Upload the data to the Walrus service using a PUT request
store_url = f"http://{ADDRESS}/v1/store?epochs={EPOCHS}"
response = requests.put(store_url, data=data)

# Assert the response status code
assert response.status_code == 200
blob_id = response.text
return blob_id


# Helper functions to download a blob
def download_blob(ADDRESS, blob_id):
# Now read the same resource using the blob-id
read_url = f"http://{ADDRESS}/v1/{blob_id}"
response = requests.get(read_url)

# Assert the response status code
assert response.status_code == 200
return response.content


# Upload a random 1MB string then download it, and check it matches
if __name__ == "__main__":
# Generate a 1MB blob of random data
random_data = os.urandom(1024 * 1024)

# Upload the blob to the Walrus service
start_time = time.time()
blob_id = upload_blob(ADDRESS, EPOCHS, random_data)
upload_time = time.time()

# Now download the same blob using the blob-id
data = download_blob(ADDRESS, blob_id)
assert data == random_data
download_time = time.time()

# Print some information about the blob
print(f"Blob ID: {blob_id}")
print(f"Size {len(random_data)} bytes")
print(f"Upload time: {upload_time - start_time:.2f}s")
print(f"Download time: {download_time - upload_time:.2f}s")
1 change: 1 addition & 0 deletions examples/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests>=2.22.0
27 changes: 27 additions & 0 deletions examples/python/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import base64

# Configure these paths to match your system
FULL_NODE_URL = "https://fullnode.testnet.sui.io:443"
PATH_TO_WALRUS = "../CONFIG/bin/walrus"
PATH_TO_WALRUS_CONFIG = "../CONFIG/config_dir/client_config.yaml"


# Convert a numeric (u256) blob_id to a base64 encoded Blob ID
def num_to_blob_id(blob_id_num):
extracted_bytes = []
for i in range(32):
extracted_bytes += [blob_id_num & 0xFF]
blob_id_num = blob_id_num >> 8
assert blob_id_num == 0
blob_id_bytes = bytes(extracted_bytes)
encoded = base64.urlsafe_b64encode(blob_id_bytes)
return encoded.decode("ascii").strip("=")


if __name__ == "__main__":
# A test case for the num_to_blob_id function
blob_id_num = (
46269954626831698189342469164469112511517843773769981308926739591706762839432
)
blob_id_base64 = "iIWkkUTzPZx-d1E_A7LqUynnYFD-ztk39_tP8MLdS2Y"
assert num_to_blob_id(blob_id_num) == blob_id_base64

0 comments on commit 5a120d2

Please sign in to comment.