Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions services/nillion-interactor/.env
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
NILLION_BOOTNODE_MULTIADDRESS=/ip4/127.0.0.1/tcp/37939/p2p/12D3KooWMvw1hEqm7EWSDEyqTb6pNetUVkepahKY6hixuAuMZfJS
NILLION_CLUSTER_ID=9e68173f-9c23-4acc-ba81-4f079b639964
NILLION_USERKEY_PATH_PARTY_1=/Users/vishakh/dev/MonadicDNA/services/nillion-interactor/user.key
NILLION_USERKEY_PATH_PARTY_2=
NILLION_USERKEY_PATH_PARTY_3=
NILLION_USERKEY_PATH_PARTY_4=
NILLION_USERKEY_PATH_PARTY_5=
NILLION_NODEKEY_PATH_PARTY_1=/Users/vishakh/dev/MonadicDNA/services/nillion-interactor/node.key
NILLION_NODEKEY_PATH_PARTY_2=
NILLION_NODEKEY_PATH_PARTY_3=
NILLION_NODEKEY_PATH_PARTY_4=
NILLION_NODEKEY_PATH_PARTY_5=
NILLION_BLOCKCHAIN_RPC_ENDPOINT=http://localhost:61391
NILLION_CHAIN_ID=31337
NILLION_PAYMENTS_SC_ADDRESS=5fc8d32690cc91d4c39d9d3abcbd16989f875707
NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS=a513e6e4b8f2a923d98304ec87f64353c4d5c853
NILLION_WALLET_PRIVATE_KEY=df57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e
# NILLION_BOOTNODE_MULTIADDRESS=/ip4/127.0.0.1/tcp/37939/p2p/12D3KooWMvw1hEqm7EWSDEyqTb6pNetUVkepahKY6hixuAuMZfJS
# NILLION_CLUSTER_ID=9e68173f-9c23-4acc-ba81-4f079b639964
# NILLION_USERKEY_PATH_PARTY_1=/Users/vishakh/dev/MonadicDNA/services/nillion-interactor/user.key
# NILLION_USERKEY_PATH_PARTY_2=
# NILLION_USERKEY_PATH_PARTY_3=
# NILLION_USERKEY_PATH_PARTY_4=
# NILLION_USERKEY_PATH_PARTY_5=
# NILLION_NODEKEY_PATH_PARTY_1=/Users/vishakh/dev/MonadicDNA/services/nillion-interactor/node.key
# NILLION_NODEKEY_PATH_PARTY_2=
# NILLION_NODEKEY_PATH_PARTY_3=
# NILLION_NODEKEY_PATH_PARTY_4=
# NILLION_NODEKEY_PATH_PARTY_5=
# NILLION_BLOCKCHAIN_RPC_ENDPOINT=http://localhost:61391
# NILLION_CHAIN_ID=31337
# NILLION_PAYMENTS_SC_ADDRESS=5fc8d32690cc91d4c39d9d3abcbd16989f875707
# NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS=a513e6e4b8f2a923d98304ec87f64353c4d5c853
# NILLION_WALLET_PRIVATE_KEY=df57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e

# Nucleus 2 testet setup
MONADIC_PRIVKEY=11112217e1ba614ee927fdc38a31b686c7b56976aa92451e89f769b518416112
SNIPPER_BOT_PRIVKEY=c653318e9a92e3d8a5fd536f6c3324047b85d294c48b2fdaa8e861b8bd53a3d1
NILLION_BLOCKCHAIN_RPC_ENDPOINT=https://testnet-nillion-grpc.lavenderfive.com
NILLION_BOOTNODE_MULTIADDRESS=https://node-1.nucleus2.nillion-network.nilogy.xyz:14311
NILLION_CHAIN_ID=nillion-chain-testnet-1
THROMBOSIS_PROGRAM_ID=8557268d73e95608764ed372665ce53ec16c5c2b/thrombosis/sha256/1b40f8cf92763b3a19c55a272203e367d2204ad0d2f78c8f1d046afc3147b6b5
677 changes: 342 additions & 335 deletions services/nillion-interactor/E2E_Nillion_Test.ipynb

Large diffs are not rendered by default.

178 changes: 113 additions & 65 deletions services/nillion-interactor/app.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import asyncio
from flask import Flask, request, jsonify
from flask_cors import CORS
from quart import Quart, request, jsonify
from quart_cors import cors
import os
import py_nillion_client as nillion
import sys
import werkzeug

import socket
import random
import string
from uuid import UUID

from dotenv import load_dotenv

from nillion_client import (VmClient, UserId, InputPartyBinding, OutputPartyBinding, SecretInteger, Permissions)

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from helpers.nillion_client_helper import create_nillion_client
from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile

load_dotenv()

app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}})
app = Quart(__name__)
app = cors(app, allow_origin="*")

def get_random_node_key():
# hostname = socket.gethostname()
Expand All @@ -30,9 +28,35 @@ def get_random_node_key():
print("Node key: ", result)
return result

_userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1"))
_nodekey = nillion.NodeKey.from_seed(get_random_node_key())
_client = create_nillion_client(_userkey, _nodekey)
_keys = {
"monadic" : bytes.fromhex(os.getenv("MONADIC_PRIVKEY")),
"snipperBot" : bytes.fromhex(os.getenv("SNIPPER_BOT_PRIVKEY"))
}
_clients:dict[str, VmClient] = None

_default_client = "monadic"

_program_id = os.getenv("THROMBOSIS_PROGRAM_ID")

async def initialize_client():
global _clients, _program_id
if _clients is None:
_clients = {}
print("initializing clients")
for key in _keys :
_clients[key] = await create_nillion_client(_keys[key])
print("done initializing clients")
if _program_id == '' or _program_id is None:
print("uploading thrombosis program")
_program_id = await upload_thrombosis_program()
print("thrombosis program uploaded, id "+_program_id)

async def upload_thrombosis_program():
program_name = "thrombosis"
program_mir_path = f"binaries/thrombosis.nada.bin"
program = open(program_mir_path, "rb").read()
return await _clients[_default_client].store_program(program_name, program,).invoke()


def read_and_filter_23andme(file_storage):
# Define the SNPs of interest and their deterministic integer values
Expand Down Expand Up @@ -70,97 +94,121 @@ def read_and_filter_23andme(file_storage):
return results

async def store_on_nillion(gene_data):
cluster_id = os.getenv("NILLION_CLUSTER_ID")
party_id = _client.party_id()
user_id = _client.user_id()
party_name = "Party1"

program_name = "thrombosis"
program_mir_path = f"binaries/thrombosis.nada.bin"
program_id = f"{user_id}/{program_name}"

secret_bindings = nillion.ProgramBindings(program_id)
secret_bindings.add_input_party(party_name, party_id)
# Set permissions for the client to compute on the program
permissions = Permissions.defaults_for_user(_clients[_default_client].user_id).allow_compute(
_clients[_default_client].user_id, _program_id
)

store_ids = {}

for row in gene_data:
rsid = row['rsid']
rsid_int = row['rsid_int']
genotype_int = row['genotype_int']
stored_secret = nillion.Secrets({
"snp": nillion.SecretInteger(6),
"genotype": nillion.SecretInteger(9),
})
store_id = await _client.store_secrets(
cluster_id, secret_bindings, stored_secret, None
)
stored_secret = {
"snp": SecretInteger(rsid_int),
"genotype": SecretInteger(genotype_int),
}
store_id = await _clients[_default_client].store_values(
values=stored_secret, ttl_days=5, permissions=permissions
).invoke()

store_ids[rsid] = store_id

return store_ids

async def compute_on_nillion(store_id):
cluster_id = os.getenv("NILLION_CLUSTER_ID")
party_id = _client.party_id()
user_id = _client.user_id()
party_name = "Party1"
program_name = "thrombosis"
program_mir_path = f"binaries/thrombosis.nada.bin"
async def compute_on_nillion(client_id, store_id):

program_id = f"{user_id}/{program_name}"
party_name = "Party1"

compute_bindings = nillion.ProgramBindings(program_id)
compute_bindings.add_input_party(party_name, party_id)
compute_bindings.add_output_party(party_name, party_id)
input_bindings = [InputPartyBinding(party_name, _clients[client_id].user_id)]
output_bindings = [OutputPartyBinding(party_name, [_clients[client_id].user_id])]

computation_time_secrets = nillion.Secrets({})
computation_time_secrets = {}

# Compute on the secret
compute_id = await _client.compute(
cluster_id,
compute_bindings,
[store_id],
computation_time_secrets,
nillion.PublicVariables({}),
)
compute_id = await _clients[client_id].compute(
_program_id,
input_bindings,
output_bindings,
values=computation_time_secrets,
value_ids=[UUID(f"urn:uuid:{store_id}")]
).invoke()

# Print compute result
print(f"The computation was sent to the network. compute_id: {compute_id}")
while True:
compute_event = await _client.next_compute_event()
if isinstance(compute_event, nillion.ComputeFinishedEvent):
print(f"✅ Compute complete for compute_id {compute_event.uuid}")
print(f"🖥️ The result is {compute_event.result.value}")
return compute_event.result.value

result = await _clients[client_id].retrieve_compute_results(compute_id).invoke()
return result['Result'].value

async def fund_user(user_id):
try:
uid = UserId.parse(user_id)
except Exception:
return "Invalid UserID"
try:
await _clients[_default_client].add_funds(500000, target_user=uid)
except Exception:
return "Failed to add funds"

@app.route('/dataset', methods=['PUT'])
async def handle_dataset():
if 'file' not in request.files:
files = await request.files
if 'file' not in files :
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
file = files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
if file:
filtered_data = read_and_filter_23andme(file)
results = await asyncio.wait_for(store_on_nillion(filtered_data), timeout=120)
return jsonify(results)
try:
filtered_data = read_and_filter_23andme(file)
results = await store_on_nillion(filtered_data)
return jsonify(results)
except Exception as e:
print(e)
return jsonify({'error': 'internal error'}), 500
return jsonify({'error': 'File processing failed'}), 500

@app.route('/computations/thrombosis', methods=['POST'])
async def thrombosis():
store_id = request.json.get('store_id')

store_id = (await request.json).get('store_id')
if store_id is None:
return jsonify({'error': 'Missing store_id'}), 400

result = await asyncio.wait_for(compute_on_nillion(store_id), timeout=120)
client_id = (await request.json).get('client_id')
if client_id is None:
client_id = _default_client
elif client_id not in _clients:
return jsonify({'error': 'Invalid client_id'}), 400
try:
result = await compute_on_nillion(client_id, store_id)
except Exception as e:
print(e)
return jsonify({'error': 'internal error'}), 500
return jsonify(result)

@app.route('/fund', methods=['POST'])
async def fund():
user_id = (await request.json).get('user_id')
if user_id is None:
return jsonify({'error': 'Missing user_id'}), 400
try:
result = await fund_user(user_id)
if result.__contains__("Invalid UserID"):
return jsonify({'error': result}), 400
elif result.__ne__(""):
return jsonify({'error': result}), 500
except Exception as e:
print(e)
return jsonify({'error': 'internal error'}), 500
return jsonify({'message': f"funded {user_id}"})


@app.route('/')
def hello_world():
return "Hello, world!"

@app.before_serving
async def startup():
await initialize_client()

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=8732)
Binary file modified services/nillion-interactor/binaries/double.nada.bin
Binary file not shown.
2 changes: 1 addition & 1 deletion services/nillion-interactor/binaries/double.nada.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"functions":[],"parties":[{"name":"Party1","source_ref_index":3}],"inputs":[{"type":"SecretInteger","party":"Party1","name":"foo","doc":"","source_ref_index":1}],"literals":[],"outputs":[{"name":"my_output","operation_id":4347741072,"party":"Party1","type":"SecretInteger","source_ref_index":2}],"operations":{"4347741072":{"Addition":{"id":4347741072,"left":4347690704,"right":4347690704,"type":"SecretInteger","source_ref_index":0}},"4347690704":{"InputReference":{"id":4347690704,"refers_to":"foo","type":"SecretInteger","source_ref_index":1}}},"source_files":{"double.py":"from nada_dsl import *\n\ndef nada_main():\n party1 = Party(name=\"Party1\")\n a = SecretInteger(Input(name=\"foo\", party=party1))\n\n result = a + a\n\n return [Output(result, \"my_output\", party1)]\n"},"source_refs":[{"file":"double.py","lineno":7,"offset":131,"length":18},{"file":"double.py","lineno":5,"offset":75,"length":54},{"file":"double.py","lineno":9,"offset":0,"length":0},{"file":"double.py","lineno":4,"offset":41,"length":33}]}
{"functions":[],"parties":[{"name":"Party1","source_ref_index":3}],"inputs":[{"type":"SecretInteger","party":"Party1","name":"foo","doc":"","source_ref_index":1}],"literals":[],"outputs":[{"name":"my_output","operation_id":2,"party":"Party1","type":"SecretInteger","source_ref_index":2}],"operations":{"1":{"InputReference":{"id":1,"refers_to":"foo","type":"SecretInteger","source_ref_index":1}},"2":{"Addition":{"id":2,"left":1,"right":1,"type":"SecretInteger","source_ref_index":0}}},"source_files":{"double.py":"from nada_dsl import *\n\ndef nada_main():\n party1 = Party(name=\"Party1\")\n a = SecretInteger(Input(name=\"foo\", party=party1))\n\n result = a + a\n\n return [Output(result, \"my_output\", party1)]\n"},"source_refs":[{"file":"scalar_types.py","lineno":136,"offset":0,"length":0},{"file":"double.py","lineno":5,"offset":75,"length":54},{"file":"double.py","lineno":9,"offset":0,"length":0},{"file":"double.py","lineno":4,"offset":41,"length":33}]}
Binary file modified services/nillion-interactor/binaries/muscle-perform.nada.bin
Binary file not shown.
Loading