Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Simple python example #9

Merged
merged 15 commits into from
Jun 7, 2024
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

gdanezis marked this conversation as resolved.
Show resolved Hide resolved
# 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
gdanezis marked this conversation as resolved.
Show resolved Hide resolved

# 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
gdanezis marked this conversation as resolved.
Show resolved Hide resolved
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