-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
456 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Miscellaneous | ||
*.class | ||
*.log | ||
*.pyc | ||
*.swp | ||
.DS_Store | ||
.atom/ | ||
.buildlog/ | ||
.history | ||
.svn/ | ||
__pycache__ | ||
|
||
# IntelliJ related | ||
*.iml | ||
*.ipr | ||
*.iws | ||
.idea/ | ||
|
||
*~ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import json | ||
|
||
disconnect_threshold = -0.002 | ||
|
||
def invoice(amt): | ||
return json.dumps({'type': 'invoice', 'amount': amt}) | ||
|
||
class Billing: | ||
def __init__(self, prices): | ||
self.ledger = {} | ||
self.prices = prices | ||
|
||
def credit(self, id, type=None, amount=0): | ||
self.adjust(id, type, amount, 1) | ||
|
||
def debit(self, id, type=None, amount=0): | ||
self.adjust(id, type, amount, -1) | ||
|
||
def adjust(self, id, type, amount, sign): | ||
amount_ = self.prices[type] if type is not None else amount | ||
if id in self.ledger: | ||
self.ledger[id] = self.ledger[id] + sign * amount_ | ||
else: | ||
self.ledger[id] = sign * amount_ | ||
|
||
def min_balance(self): | ||
return 2 * (self.prices['invoice'] + self.prices['payment']) | ||
|
||
def balance(self, id): | ||
if id in self.ledger: | ||
return self.ledger[id] | ||
else: | ||
return 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import asyncio | ||
import random | ||
import datetime | ||
import json | ||
import requests | ||
|
||
import websockets | ||
|
||
class jobs: | ||
def __init__(self, model, url): | ||
self.queue = asyncio.PriorityQueue() | ||
self.sessions = {} | ||
self.model = model | ||
self.url = url | ||
|
||
def get_queues(self, id): | ||
squeue = asyncio.Queue(maxsize=10) | ||
rqueue = asyncio.Queue(maxsize=10) | ||
self.sessions[id] = {'send': squeue, 'recv': rqueue} | ||
return [rqueue, squeue] | ||
|
||
async def add_job(self, id, bid, job): | ||
print(f'add_job({id}, {bid}, {job})') | ||
priority = 0.1 | ||
await self.queue.put((priority, [id, bid, job])) | ||
|
||
async def process_jobs(self): | ||
while True: | ||
priority, job_params = await self.queue.get() | ||
print(f"process_jobs: got job: {job_params}") | ||
id, bid, job = job_params | ||
await self.sessions[id]['send'].put(json.dumps({'type': 'started'})) | ||
result = "" | ||
data = {'model': self.model, "temperature": 0.7, "top_p": 1, | ||
"max_tokens": 4000, "stream": False, "safe_prompt": False, "random_seed": None, | ||
'messages': [{'role': 'user', 'content': job['prompt']}]} | ||
r = requests.post(self.url, data=json.dumps(data)) | ||
result = r.json() | ||
print(result) | ||
if result['object'] != 'chat.completion': | ||
print('*** Error from llm') | ||
continue | ||
response = result['choices'][0]['message']['content'] | ||
model = result['model'] | ||
reason = result['choices'][0]['finish_reason'] | ||
usage = result['usage'] | ||
await self.sessions[id]['send'].put(json.dumps({'type': 'complete', 'response': response, | ||
'model': model, 'reason': reason, | ||
'usage': usage})) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import json | ||
import web3 | ||
|
||
from ticket import Ticket | ||
|
||
# Gnosis - default | ||
rpc_url_default = 'https://rpc.gnosischain.com/' | ||
chain_id_default = 100 | ||
|
||
# Polygon - default | ||
# rpc_url_default = 'https://polygon-rpc.com' | ||
# chain_id_default = 137 | ||
|
||
# Gas default | ||
gas_amount_default = 100000 | ||
|
||
uint64 = pow(2, 64) - 1 # 18446744073709551615 | ||
uint128 = pow(2, 128) - 1 # 340282366920938463463374607431768211455 | ||
|
||
def to_32byte_hex(val): | ||
return web3.Web3.to_hex(web3.Web3.to_bytes(hexstr=val).rjust(32, b'\0')) | ||
|
||
|
||
class Lottery: | ||
addr_type = pow(2, 20 * 8) - 1 | ||
contract_addr = '0x6dB8381b2B41b74E17F5D4eB82E8d5b04ddA0a82' | ||
token = '0x' + '0' * 40 | ||
contract_abi_str = "[ { \"inputs\": [ { \"internalType\": \"uint64\", \"name\": \"day\", \"type\": \"uint64\" } ], \"stateMutability\": \"nonpayable\", \"type\": \"constructor\" }, { \"anonymous\": false, \"inputs\": [ { \"indexed\": true, \"internalType\": \"contract IERC20\", \"name\": \"token\", \"type\": \"address\" }, { \"indexed\": true, \"internalType\": \"address\", \"name\": \"funder\", \"type\": \"address\" }, { \"indexed\": true, \"internalType\": \"address\", \"name\": \"signer\", \"type\": \"address\" } ], \"name\": \"Create\", \"type\": \"event\" }, { \"anonymous\": false, \"inputs\": [ { \"indexed\": true, \"internalType\": \"bytes32\", \"name\": \"key\", \"type\": \"bytes32\" }, { \"indexed\": false, \"internalType\": \"uint256\", \"name\": \"unlock_warned\", \"type\": \"uint256\" } ], \"name\": \"Delete\", \"type\": \"event\" }, { \"anonymous\": false, \"inputs\": [ { \"indexed\": true, \"internalType\": \"address\", \"name\": \"funder\", \"type\": \"address\" }, { \"indexed\": true, \"internalType\": \"address\", \"name\": \"recipient\", \"type\": \"address\" } ], \"name\": \"Enroll\", \"type\": \"event\" }, { \"anonymous\": false, \"inputs\": [ { \"indexed\": true, \"internalType\": \"bytes32\", \"name\": \"key\", \"type\": \"bytes32\" }, { \"indexed\": false, \"internalType\": \"uint256\", \"name\": \"escrow_amount\", \"type\": \"uint256\" } ], \"name\": \"Update\", \"type\": \"event\" }, { \"inputs\": [ { \"internalType\": \"contract IERC20\", \"name\": \"token\", \"type\": \"address\" }, { \"internalType\": \"address\", \"name\": \"recipient\", \"type\": \"address\" }, { \"components\": [ { \"internalType\": \"bytes32\", \"name\": \"data\", \"type\": \"bytes32\" }, { \"internalType\": \"bytes32\", \"name\": \"reveal\", \"type\": \"bytes32\" }, { \"internalType\": \"uint256\", \"name\": \"packed0\", \"type\": \"uint256\" }, { \"internalType\": \"uint256\", \"name\": \"packed1\", \"type\": \"uint256\" }, { \"internalType\": \"bytes32\", \"name\": \"r\", \"type\": \"bytes32\" }, { \"internalType\": \"bytes32\", \"name\": \"s\", \"type\": \"bytes32\" } ], \"internalType\": \"struct OrchidLottery1.Ticket[]\", \"name\": \"tickets\", \"type\": \"tuple[]\" }, { \"internalType\": \"bytes32[]\", \"name\": \"refunds\", \"type\": \"bytes32[]\" } ], \"name\": \"claim\", \"outputs\": [], \"stateMutability\": \"nonpayable\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"contract IERC20\", \"name\": \"token\", \"type\": \"address\" }, { \"internalType\": \"uint256\", \"name\": \"amount\", \"type\": \"uint256\" }, { \"internalType\": \"address\", \"name\": \"signer\", \"type\": \"address\" }, { \"internalType\": \"int256\", \"name\": \"adjust\", \"type\": \"int256\" }, { \"internalType\": \"int256\", \"name\": \"warn\", \"type\": \"int256\" }, { \"internalType\": \"uint256\", \"name\": \"retrieve\", \"type\": \"uint256\" } ], \"name\": \"edit\", \"outputs\": [], \"stateMutability\": \"nonpayable\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"address\", \"name\": \"signer\", \"type\": \"address\" }, { \"internalType\": \"int256\", \"name\": \"adjust\", \"type\": \"int256\" }, { \"internalType\": \"int256\", \"name\": \"warn\", \"type\": \"int256\" }, { \"internalType\": \"uint256\", \"name\": \"retrieve\", \"type\": \"uint256\" } ], \"name\": \"edit\", \"outputs\": [], \"stateMutability\": \"payable\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"bool\", \"name\": \"cancel\", \"type\": \"bool\" }, { \"internalType\": \"address[]\", \"name\": \"recipients\", \"type\": \"address[]\" } ], \"name\": \"enroll\", \"outputs\": [], \"stateMutability\": \"nonpayable\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"address\", \"name\": \"funder\", \"type\": \"address\" }, { \"internalType\": \"address\", \"name\": \"recipient\", \"type\": \"address\" } ], \"name\": \"enrolled\", \"outputs\": [ { \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" } ], \"stateMutability\": \"view\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"contract IERC20\", \"name\": \"token\", \"type\": \"address\" }, { \"internalType\": \"address\", \"name\": \"signer\", \"type\": \"address\" }, { \"internalType\": \"uint64\", \"name\": \"marked\", \"type\": \"uint64\" } ], \"name\": \"mark\", \"outputs\": [], \"stateMutability\": \"nonpayable\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"address\", \"name\": \"sender\", \"type\": \"address\" }, { \"internalType\": \"uint256\", \"name\": \"amount\", \"type\": \"uint256\" }, { \"internalType\": \"bytes\", \"name\": \"data\", \"type\": \"bytes\" } ], \"name\": \"onTokenTransfer\", \"outputs\": [ { \"internalType\": \"bool\", \"name\": \"\", \"type\": \"bool\" } ], \"stateMutability\": \"nonpayable\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"contract IERC20\", \"name\": \"token\", \"type\": \"address\" }, { \"internalType\": \"address\", \"name\": \"funder\", \"type\": \"address\" }, { \"internalType\": \"address\", \"name\": \"signer\", \"type\": \"address\" } ], \"name\": \"read\", \"outputs\": [ { \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }, { \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" } ], \"stateMutability\": \"view\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"uint256\", \"name\": \"count\", \"type\": \"uint256\" }, { \"internalType\": \"bytes32\", \"name\": \"seed\", \"type\": \"bytes32\" } ], \"name\": \"save\", \"outputs\": [], \"stateMutability\": \"nonpayable\", \"type\": \"function\" }, { \"inputs\": [ { \"internalType\": \"address\", \"name\": \"sender\", \"type\": \"address\" }, { \"internalType\": \"uint256\", \"name\": \"amount\", \"type\": \"uint256\" }, { \"internalType\": \"bytes\", \"name\": \"data\", \"type\": \"bytes\" } ], \"name\": \"tokenFallback\", \"outputs\": [], \"stateMutability\": \"nonpayable\", \"type\": \"function\" }]" | ||
contract_abi= None | ||
|
||
contract= None | ||
rpc_url= None | ||
chain_id = None | ||
gas_amount = None | ||
|
||
def __init__(self, rpc_url=rpc_url_default, chain_id=chain_id_default, gas_amount=gas_amount_default): | ||
self.rpc_url = rpc_url | ||
self.chain_id = chain_id | ||
self.gas_amount = gas_amount | ||
|
||
self.contract_abi = json.loads(self.contract_abi_str) | ||
|
||
def init_contract(self, web3): | ||
self.web3 = web3 | ||
self.contract = self.web3.eth.contract(address=self.contract_addr, abi=self.contract_abi) | ||
|
||
|
||
@staticmethod | ||
def prepareTicket(tk:Ticket, reveal): | ||
return [tk.data, to_32byte_hex(reveal), tk.packed0, tk.packed1, to_32byte_hex(tk.sig_r), to_32byte_hex(tk.sig_s)] | ||
return [tk.data.hex(), reveal, tk.packed0, tk.packed1, tk.sig_r, tk.sig_s] | ||
|
||
# Ticket object, L1 address & key | ||
def claim_ticket(self, ticket, recipient, executor_key, reveal): | ||
tk = Lottery.prepareTicket(ticket, reveal) | ||
executor_address = self.web3.eth.account.from_key(executor_key).address | ||
l1nonce = self.web3.eth.get_transaction_count(executor_address) | ||
func = self.contract.functions.claim(self.token, recipient, [tk], []) | ||
|
||
tx = func.build_transaction({ | ||
'chainId': self.chain_id, | ||
'gas': self.gas_amount, | ||
'maxFeePerGas': self.web3.to_wei('100', 'gwei'), | ||
'maxPriorityFeePerGas': self.web3.to_wei('40', 'gwei'), | ||
'nonce': l1nonce | ||
}) | ||
|
||
# Polygon Estimates | ||
# if (self.chain_id == 137): | ||
# gas_estimate = self.web3.eth.estimate_gas(tx) | ||
# print("gas ", gas_estimate) | ||
# tx.update({'gas': gas_estimate}) | ||
|
||
signed = self.web3.eth.account.sign_transaction(tx, private_key=executor_key) | ||
txhash = self.web3.eth.send_raw_transaction(signed.rawTransaction) | ||
return txhash.hex() | ||
|
||
def check_balance(self, addressL1, addressL2): | ||
escrow_amount = self.contract.functions.read(self.token, addressL1, addressL2).call(block_identifier='latest')[0] | ||
balance = float(escrow_amount & uint128) / pow(10,18) | ||
escrow = float(escrow_amount >> 128) / pow(10,18) | ||
return balance, escrow |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import asyncio | ||
import websockets | ||
import functools | ||
import json | ||
import hashlib | ||
import random | ||
|
||
import web3 | ||
import ethereum | ||
|
||
import billing | ||
import jobs | ||
import ticket | ||
import lottery | ||
import os | ||
import traceback | ||
import sys | ||
|
||
prices = { | ||
'invoice': 0.0001, | ||
'payment': 0.0001, | ||
'connection': 0.0001, | ||
'error': 0.0001, | ||
'job': 0.01, | ||
'complete': 0.001, | ||
'started': 0.0001 | ||
} | ||
|
||
internal_messages = ['charge'] | ||
disconnect_threshold = -25 | ||
|
||
def invoice(amt, commit, recipient): | ||
return json.dumps({'type': 'invoice', 'amount': amt, 'commit': '0x' + str(commit), 'recipient': recipient}) | ||
|
||
def process_tickets(tix, recip, reveal, commit, lotto, key): | ||
try: | ||
# print(f'Got ticket: {tix[0]}') | ||
tk = ticket.Ticket.deserialize_ticket(tix[0], reveal, commit, recip, lotaddr=lottery_address) | ||
# tk.print_ticket() | ||
if tk.is_winner(reveal): | ||
hash = lotto.claim_ticket(tk, recip, key, reveal) | ||
print(f"Claim tx: {hash}") | ||
reveal, commit = new_reveal() | ||
return tk.face_value() / pow(10,18), reveal, commit | ||
except Exception: | ||
print('process_ticket() failed') | ||
exc_type, exc_value, exc_traceback = sys.exc_info() | ||
traceback.print_exception(exc_type, exc_value, exc_traceback, limit=20, file=sys.stdout) | ||
return 0, reveal, commit | ||
|
||
async def send_error(ws, code): | ||
await ws.send(json.dumps({'type': 'error', 'code': code})) | ||
|
||
def new_reveal(): | ||
num = hex(random.randrange(pow(2,256)))[2:] | ||
reveal = '0x' + num[2:].zfill(64) | ||
# print(f'new_reveal: {reveal}') | ||
try: | ||
commit = ethereum.utils.sha3(bytes.fromhex(reveal[2:])).hex() | ||
except: | ||
exc_type, exc_value, exc_traceback = sys.exc_info() | ||
traceback.print_exception(exc_type, exc_value, exc_traceback, limit=20, file=sys.stdout) | ||
return reveal, commit | ||
|
||
async def session(websocket, bills=None, job=None, recipient='0x0', key=''): | ||
print("New client connection") | ||
reserve_price = 0.00006 | ||
lotto = lottery.Lottery() | ||
w3 = web3.Web3(web3.Web3.HTTPProvider('https://rpc.gnosischain.com/')) | ||
lotto.init_contract(w3) | ||
id = websocket.id | ||
bills.debit(id, type='invoice') | ||
send_queue, recv_queue = job.get_queues(id) | ||
reveal, commit = new_reveal() | ||
await websocket.send(invoice(2 * bills.min_balance(), commit, recipient)) | ||
sources = [websocket.recv, recv_queue.get] | ||
tasks = [None, None] | ||
while True: | ||
if bills.balance(id) < disconnect_threshold: | ||
await websocket.close(reason='Balance too low') | ||
break | ||
try: | ||
for i in range(2): | ||
if tasks[i] is None: | ||
tasks[i] = asyncio.create_task(sources[i]()) | ||
done, pending = await asyncio.wait(tasks, return_when = asyncio.FIRST_COMPLETED) | ||
for i, task in enumerate(tasks): | ||
if task in done: | ||
tasks[i] = None | ||
for task in done: | ||
message_ = task.result() | ||
message = json.loads(message_) | ||
if message['type'] == 'payment': | ||
try: | ||
amt, reveal, commit = process_tickets(message['tickets'], recipient, reveal, commit, lotto, key) | ||
bills.credit(id, amount=amt) | ||
except: | ||
print('outer failure in processing payment') | ||
exc_type, exc_value, exc_traceback = sys.exc_info() | ||
traceback.print_tb(exc_traceback, limit=1, file=sys.stdout) | ||
bills.debit(id, type='error') | ||
await send_error(websocket, -6001) | ||
if bills.balance(id) < bills.min_balance(): | ||
bills.debit(id, type='invoice') | ||
await websocket.send(invoice(2 * bills.min_balance() - bills.balance(id), commit, recipient)) | ||
if message['type'] not in internal_messages: | ||
bills.debit(id, type=message['type']) | ||
if message['type'] == 'job': | ||
jid = hashlib.sha256(bytes(message['prompt'], 'utf-8')).hexdigest() | ||
if reserve_price != 0 and float(message['bid']) < reserve_price: | ||
await websocket.send(json.dumps({'type': 'bid_low'})) | ||
continue | ||
await job.add_job(id, message['bid'], | ||
{'id': jid, 'prompt': message['prompt']}) | ||
if message['type'] == 'charge': | ||
try: | ||
bills.debit(id, amount=message['amount']) | ||
await send_queue.put(True) | ||
except: | ||
print('exception in charge handler') | ||
if message['type'] == 'complete': | ||
await websocket.send(json.dumps({'type': 'job_complete', "output": message['response'], | ||
'model': message['model'], 'reason': message['reason'], | ||
'usage': message['usage']})) | ||
if message['type'] == 'started': | ||
await websocket.send(json.dumps({'type': 'job_started'})) | ||
except (websockets.exceptions.ConnectionClosedOK, websockets.exceptions.ConnectionClosedError): | ||
print('connection closed') | ||
break | ||
|
||
|
||
async def main(model, url, bind_addr, bind_port, recipient_key): | ||
recipient_addr = web3.Account.from_key(recipient_key).address | ||
bills = billing.Billing(prices) | ||
job = jobs.jobs('', '') | ||
print("\n*****") | ||
print(f"* Server starting up at {bind_addr} {bind_port}") | ||
print(f"* Connecting to back end at {url}") | ||
print(f"* With model {model}") | ||
print(f"* Using wallet at {recipient_addr}") | ||
print("******\n\n") | ||
async with websockets.serve(functools.partial(session, bills=bills, job=job, recipient=recipient_addr, key=recipient_key), bind_addr, bind_port): | ||
await asyncio.wait([asyncio.create_task(job.process_jobs())]) | ||
|
||
if __name__ == "__main__": | ||
bind_addr = os.environ['ORCHID_GENAI_ADDR'] | ||
bind_port = os.environ['ORCHID_GENAI_PORT'] | ||
recipient_key = os.environ['ORCHID_GENAI_RECIPIENT_KEY'] | ||
url = sys.argv[1] | ||
model = sys.argv[2] | ||
asyncio.run(main(model, url, bind_addr, bind_port, recipient_key)) |
Oops, something went wrong.