diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 607a122..d5f29a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,14 +7,57 @@ jobs: name: Pytest Tests runs-on: ubuntu-24.04 timeout-minutes: 10 + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive + - name: Install system dependencies (Rust + Python) + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + clang \ + lld \ + protobuf-compiler \ + make \ + wget + + - name: Install binaryen 120 + run: | + wget https://github.com/WebAssembly/binaryen/releases/download/version_120/binaryen-version_120-x86_64-linux.tar.gz + tar xvf binaryen-version_120-x86_64-linux.tar.gz + echo "$(pwd)/binaryen-version_120/bin" >> $GITHUB_PATH + - name: Install the latest version of uv uses: astral-sh/setup-uv@v5 + - name: Install Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + rustup install stable + rustup component add rust-src --toolchain stable + rustup target add wasm32-wasip1 + + - name: Set up Python environment and dependencies + run: | + uv venv .venv --python=3.12 + uv pip install -U rust-contracts-builder + echo "$(pwd)/.venv/bin" >> $GITHUB_PATH + + - name: Apply required modifications for rust-contracts-builder + run: | + sed -i "s/wasm32-wasi /wasm32-wasip1 /g" .venv/lib/python3.12/site-packages/rust_contracts_builder/__init__.py + sed -i "s/wasm32-wasi\//wasm32-wasip1\//g" .venv/lib/python3.12/site-packages/rust_contracts_builder/__init__.py + + - name: Build Rust project + run: rust-contract build + working-directory: tests/contracts/skygpu-contract + - uses: actions/cache@v3 name: Cache venv with: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..90b8d3e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/contracts/skygpu-contract"] + path = tests/contracts/skygpu-contract + url = https://github.com/skygpu/skygpu-contract.git diff --git a/skynet/_testing.py b/skynet/_testing.py index 44eefba..fc599c5 100644 --- a/skynet/_testing.py +++ b/skynet/_testing.py @@ -16,13 +16,14 @@ async def open_test_worker( cleos, ipfs_node, account: str = 'testworker', permission: str = 'active', + key: str = '5KRPFxF4RJebqPXqRzwStmCaEWeRfp3pR7XUNoA3zCHt5fnPu3s', hf_token: str = '', **kwargs ): config = override_dgpu_config( account=account, permission=permission, - key=cleos.private_keys[account], + key=key, node_url=cleos.endpoint, ipfs_url=ipfs_node[1].endpoint, hf_token=hf_token, diff --git a/skynet/constants.py b/skynet/constants.py index 4de9b23..b5d255c 100755 --- a/skynet/constants.py +++ b/skynet/constants.py @@ -271,221 +271,3 @@ TG_MAX_WIDTH = 1280 TG_MAX_HEIGHT = 1280 DEFAULT_SINGLE_CARD_MAP = 'cuda:0' - -GPU_CONTRACT_ABI = { - "version": "eosio::abi/1.2", - "types": [], - "structs": [ - { - "name": "account", - "base": "", - "fields": [ - {"name": "user", "type": "name"}, - {"name": "balance", "type": "asset"}, - {"name": "nonce", "type": "uint64"} - ] - }, - { - "name": "card", - "base": "", - "fields": [ - {"name": "id", "type": "uint64"}, - {"name": "owner", "type": "name"}, - {"name": "card_name", "type": "string"}, - {"name": "version", "type": "string"}, - {"name": "total_memory", "type": "uint64"}, - {"name": "mp_count", "type": "uint32"}, - {"name": "extra", "type": "string"} - ] - }, - { - "name": "clean", - "base": "", - "fields": [] - }, - { - "name": "config", - "base": "", - "fields": [ - {"name": "token_contract", "type": "name"}, - {"name": "token_symbol", "type": "symbol"} - ] - }, - { - "name": "dequeue", - "base": "", - "fields": [ - {"name": "user", "type": "name"}, - {"name": "request_id", "type": "uint64"} - ] - }, - { - "name": "enqueue", - "base": "", - "fields": [ - {"name": "user", "type": "name"}, - {"name": "request_body", "type": "string"}, - {"name": "binary_data", "type": "string"}, - {"name": "reward", "type": "asset"}, - {"name": "min_verification", "type": "uint32"} - ] - }, - { - "name": "gcfgstruct", - "base": "", - "fields": [ - {"name": "token_contract", "type": "name"}, - {"name": "token_symbol", "type": "symbol"} - ] - }, - { - "name": "submit", - "base": "", - "fields": [ - {"name": "worker", "type": "name"}, - {"name": "request_id", "type": "uint64"}, - {"name": "request_hash", "type": "checksum256"}, - {"name": "result_hash", "type": "checksum256"}, - {"name": "ipfs_hash", "type": "string"} - ] - }, - { - "name": "withdraw", - "base": "", - "fields": [ - {"name": "user", "type": "name"}, - {"name": "quantity", "type": "asset"} - ] - }, - { - "name": "work_request_struct", - "base": "", - "fields": [ - {"name": "id", "type": "uint64"}, - {"name": "user", "type": "name"}, - {"name": "reward", "type": "asset"}, - {"name": "min_verification", "type": "uint32"}, - {"name": "nonce", "type": "uint64"}, - {"name": "body", "type": "string"}, - {"name": "binary_data", "type": "string"}, - {"name": "timestamp", "type": "time_point_sec"} - ] - }, - { - "name": "work_result_struct", - "base": "", - "fields": [ - {"name": "id", "type": "uint64"}, - {"name": "request_id", "type": "uint64"}, - {"name": "user", "type": "name"}, - {"name": "worker", "type": "name"}, - {"name": "result_hash", "type": "checksum256"}, - {"name": "ipfs_hash", "type": "string"}, - {"name": "submited", "type": "time_point_sec"} - ] - }, - { - "name": "workbegin", - "base": "", - "fields": [ - {"name": "worker", "type": "name"}, - {"name": "request_id", "type": "uint64"}, - {"name": "max_workers", "type": "uint32"} - ] - }, - { - "name": "workcancel", - "base": "", - "fields": [ - {"name": "worker", "type": "name"}, - {"name": "request_id", "type": "uint64"}, - {"name": "reason", "type": "string"} - ] - }, - { - "name": "worker", - "base": "", - "fields": [ - {"name": "account", "type": "name"}, - {"name": "joined", "type": "time_point_sec"}, - {"name": "left", "type": "time_point_sec"}, - {"name": "url", "type": "string"} - ] - }, - { - "name": "worker_status_struct", - "base": "", - "fields": [ - {"name": "worker", "type": "name"}, - {"name": "status", "type": "string"}, - {"name": "started", "type": "time_point_sec"} - ] - } - ], - "actions": [ - {"name": "clean", "type": "clean", "ricardian_contract": ""}, - {"name": "config", "type": "config", "ricardian_contract": ""}, - {"name": "dequeue", "type": "dequeue", "ricardian_contract": ""}, - {"name": "enqueue", "type": "enqueue", "ricardian_contract": ""}, - {"name": "submit", "type": "submit", "ricardian_contract": ""}, - {"name": "withdraw", "type": "withdraw", "ricardian_contract": ""}, - {"name": "workbegin", "type": "workbegin", "ricardian_contract": ""}, - {"name": "workcancel", "type": "workcancel", "ricardian_contract": ""} - ], - "tables": [ - { - "name": "cards", - "index_type": "i64", - "key_names": [], - "key_types": [], - "type": "card" - }, - { - "name": "gcfgstruct", - "index_type": "i64", - "key_names": [], - "key_types": [], - "type": "gcfgstruct" - }, - { - "name": "queue", - "index_type": "i64", - "key_names": [], - "key_types": [], - "type": "work_request_struct" - }, - { - "name": "results", - "index_type": "i64", - "key_names": [], - "key_types": [], - "type": "work_result_struct" - }, - { - "name": "status", - "index_type": "i64", - "key_names": [], - "key_types": [], - "type": "worker_status_struct" - }, - { - "name": "users", - "index_type": "i64", - "key_names": [], - "key_types": [], - "type": "account" - }, - { - "name": "workers", - "index_type": "i64", - "key_names": [], - "key_types": [], - "type": "worker" - } - ], - "ricardian_clauses": [], - "error_messages": [], - "abi_extensions": [], - "variants": [], - "action_results": [] -} diff --git a/skynet/contract.py b/skynet/contract.py new file mode 100644 index 0000000..11e0412 --- /dev/null +++ b/skynet/contract.py @@ -0,0 +1,304 @@ +import time + +import msgspec +from leap import CLEOS +from leap.protocol import Name + +from skynet.types import ( + ConfigV1, + AccountV1, + WorkerV0, + RequestV1, + BodyV0, + WorkerStatusV0, + ResultV0 +) + + +class ConfigNotFound(BaseException): + ... + +class AccountNotFound(BaseException): + ... + +class WorkerNotFound(BaseException): + ... + +class RequestNotFound(BaseException): + ... + +class WorkerStatusNotFound(BaseException): + ... + + +class GPUContractAPI: + + def __init__(self, cleos: CLEOS): + self.receiver = 'gpu.scd' + self._cleos = cleos + + # views into data + + async def get_config(self) -> ConfigV1: + rows = await self._cleos.aget_table( + self.receiver, self.receiver, 'config', + resp_cls=ConfigV1 + ) + if len(rows) == 0: + raise ConfigNotFound() + + return rows[0] + + async def get_user(self, user: str) -> AccountV1: + rows = await self._cleos.aget_table( + self.receiver, self.receiver, 'users', + key_type='name', + lower_bound=user, + upper_bound=user, + resp_cls=AccountV1 + ) + if len(rows) == 0: + raise AccountNotFound(user) + + return rows[0] + + async def get_users(self) -> list[AccountV1]: + return await self._cleos.aget_table(self.receiver, self.receiver, 'users', resp_cls=AccountV1) + + async def get_worker(self, worker: str) -> WorkerV0: + rows = await self._cleos.aget_table( + self.receiver, self.receiver, 'workers', + key_type='name', + lower_bound=worker, + upper_bound=worker, + resp_cls=WorkerV0 + ) + if len(rows) == 0: + raise WorkerNotFound(worker) + + return rows[0] + + async def get_workers(self) -> list[AccountV1]: + return await self._cleos.aget_table(self.receiver, self.receiver, 'workers', resp_cls=WorkerV0) + + async def get_queue(self) -> RequestV1: + return await self._cleos.aget_table(self.receiver, self.receiver, 'queue', resp_cls=RequestV1) + + async def get_request(self, request_id: int) -> RequestV1: + rows = await self._cleos.aget_table( + self.receiver, self.receiver, 'queue', + lower_bound=request_id, + upper_bound=request_id, + resp_cls=RequestV1 + ) + if len(rows) == 0: + raise RequestNotFound(request_id) + + return rows[0] + + async def get_requests_since(self, seconds: int) -> list[RequestV1]: + return await self._cleos.aget_table( + self.receiver, self.receiver, 'queue', + index_position=2, + key_type='i64', + lower_bound=int(time.time()) - seconds, + resp_cls=RequestV1 + ) + + async def get_statuses_for_request(self, request_id: int) -> list[WorkerStatusV0]: + return await self._cleos.aget_table( + self.receiver, str(Name.from_int(request_id)), 'status', + resp_cls=WorkerStatusV0 + ) + + async def get_worker_status_for_request(self, request_id: int, worker: str) -> WorkerStatusV0: + rows = await self._cleos.aget_table( + self.receiver, str(Name.from_int(request_id)), 'status', + key_type='name', + lower_bound=worker, + upper_bound=worker, + resp_cls=WorkerStatusV0 + ) + if len(rows) == 0: + raise WorkerStatusNotFound(request_id) + + return rows[0] + + async def get_results(self, request_id: int) -> list[ResultV0]: + return await self._cleos.aget_table( + self.receiver, self.receiver, 'results', + index_position=2, + key_type='i64', + lower_bound=request_id, + upper_bound=request_id, + resp_cls=ResultV0 + ) + + async def get_worker_results(self, worker: str) -> list[ResultV0]: + return await self._cleos.aget_table( + self.receiver, self.receiver, 'results', + index_position=4, + key_type='name', + lower_bound=worker, + upper_bound=worker, + resp_cls=ResultV0 + ) + + # system actions + async def init_config(self, token_account: str, token_symbol: str): + return await self._cleos.a_push_action( + self.receiver, + 'config', + [token_account, token_symbol], + self.receiver + ) + + async def clean_tables(self, nuke: bool = False): + return await self._cleos.a_push_action( + self.receiver, + 'clean', + [nuke], + self.receiver + ) + + # balance actions + + async def deposit(self, user: str, quantity: str): + return await self._cleos.a_push_action( + 'eosio.token', + 'transfer', + [user, self.receiver, quantity, 'testing gpu deposit'], + user, + key=self._cleos.private_keys[user] + ) + + async def withdraw(self, user: str, quantity: str): + return await self._cleos.a_push_action( + self.receiver, + 'withdraw', + [user, quantity], + user, + key=self._cleos.private_keys[user] + ) + + # worker actions + + async def register_worker( + self, + worker: str, + url: str + ): + return await self._cleos.a_push_action( + self.receiver, + 'regworker', + [worker, url], + worker, + key=self._cleos.private_keys[worker] + ) + + async def unregister_worker( + self, + worker: str, + reason: str + ): + return await self._cleos.a_push_action( + self.receiver, + 'unregworker', + [worker, reason], + worker, + key=self._cleos.private_keys[worker] + ) + + async def accept_work( + self, + worker: str, + request_id: int, + max_workers: int = 10 + ): + return await self._cleos.a_push_action( + self.receiver, + 'workbegin', + [worker, request_id, max_workers], + worker, + key=self._cleos.private_keys[worker] + ) + + async def cancel_work( + self, + worker: str, + request_id: int, + reason: str + ): + return await self._cleos.a_push_action( + self.receiver, + 'workcancel', + [worker, request_id, reason], + worker, + key=self._cleos.private_keys[worker] + ) + + async def submit_work( + self, + worker: str, + request_id: int, + result_hash: str, + ipfs_hash: str + ): + return await self._cleos.a_push_action( + self.receiver, + 'submit', + [worker, request_id, result_hash, ipfs_hash], + worker, + key=self._cleos.private_keys[worker] + ) + + # user actions + + async def enqueue( + self, + account: str, + body: BodyV0, + binary_data: str = '', + reward: str = '1.0000 TLOS', + min_verification: int = 1 + ) -> int: + + body = msgspec.json.encode(body).decode('utf-8') + result = await self._cleos.a_push_action( + self.receiver, + 'enqueue', + [ + account, + body, + binary_data, + reward, + min_verification + ], + account, + key=self._cleos.private_keys[account] + ) + console = result['processed']['action_traces'][0]['console'] + nonce_index = -1 + timestamp_index = -2 + lines = console.rstrip().split('\n') + nonce = int(lines[nonce_index]) + timestamp = lines[timestamp_index] + + return RequestV1( + id=int(nonce), + user=account, + reward=reward, + min_verification=min_verification, + body=body, + binary_data=binary_data, + timestamp=timestamp + ) + + async def dequeue(self, user: str, request_id: int): + return await self._cleos.a_push_action( + self.receiver, + 'dequeue', + [user, request_id], + user, + key=self._cleos.private_keys[user] + ) diff --git a/skynet/dgpu/daemon.py b/skynet/dgpu/daemon.py index 8016e64..8dedc9b 100755 --- a/skynet/dgpu/daemon.py +++ b/skynet/dgpu/daemon.py @@ -1,6 +1,5 @@ import logging from functools import partial -from hashlib import sha256 import trio import msgspec @@ -22,7 +21,7 @@ from skynet.dgpu.network import ( async def maybe_update_tui_balance(conn: NetConnector): async def _fn(tui): # update balance - balance = await conn.get_worker_balance() + balance = await conn.contract.get_user(tui.config.account).balance tui.set_header_text(new_balance=f'balance: {balance}') await maybe_update_tui_async(_fn) @@ -101,24 +100,12 @@ async def maybe_serve_one( f'IPFS fetch input error !?! retries left {retry - r - 1}\n' ) - # compute unique request hash used on submit - hash_str = ( - str(req.nonce) - + - req.body - + - req.binary_data - ) - logging.debug(f'hashing: {hash_str}') - request_hash = sha256(hash_str.encode('utf-8')).hexdigest() - logging.info(f'calculated request hash: {request_hash}') - total_step = body.params.step mode = body.method # TODO: validate request - resp = await conn.begin_work(req.id) + resp = await conn.contract.accept_work(config.account, req.id) if not resp or 'code' in resp: logging.info('begin_work error, probably being worked on already... skip.') return @@ -157,7 +144,9 @@ async def maybe_serve_one( ipfs_hash = await conn.publish_on_ipfs(output, typ=output_type) - await conn.submit_work(req.id, request_hash, output_hash, ipfs_hash) + await conn.contract.submit_work(config.account, req.id, output_hash, ipfs_hash) + + await state_mngr.update_state() await maybe_update_tui_balance(conn) @@ -166,8 +155,10 @@ async def maybe_serve_one( if 'network cancel' not in str(err): logging.exception('Failed to serve model request !?\n') + await state_mngr.update_state() + if state_mngr.is_request_in_progress(req.id): - await conn.cancel_work(req.id, 'reason not provided') + await conn.contract.cancel_work(config.account, req.id, 'reason not provided') async def dgpu_serve_forever( diff --git a/skynet/dgpu/network.py b/skynet/dgpu/network.py index e5f1070..2e7b5e2 100755 --- a/skynet/dgpu/network.py +++ b/skynet/dgpu/network.py @@ -15,18 +15,15 @@ import outcome import msgspec from PIL import Image from leap.cleos import CLEOS -from leap.protocol import Asset from skynet.dgpu.tui import maybe_update_tui from skynet.config import DgpuConfig as Config, load_skynet_toml from skynet.types import ( - ConfigV0, - AccountV0, BodyV0, - RequestV0, + RequestV1, WorkerStatusV0, ResultV0 ) -from skynet.constants import GPU_CONTRACT_ABI +from skynet.contract import GPUContractAPI from skynet.ipfs import ( AsyncIPFSHTTP, @@ -70,178 +67,16 @@ class NetConnector: def __init__(self, config: Config): self.config = config self.cleos = CLEOS(endpoint=config.node_url) - self.cleos.load_abi('gpu.scd', GPU_CONTRACT_ABI) + self.cleos.import_key(config.account, config.key) + abi = self.cleos.get_abi('gpu.scd') + self.cleos.load_abi('gpu.scd', abi) + + self.contract = GPUContractAPI(self.cleos) self.ipfs_client = AsyncIPFSHTTP(config.ipfs_url) maybe_update_tui(lambda tui: tui.set_header_text(new_worker_name=self.config.account)) - - # blockchain helpers - - async def get_work_requests_last_hour(self) -> list[RequestV0]: - logging.info('get_work_requests_last_hour') - rows = await failable( - partial( - self.cleos.aget_table, - 'gpu.scd', 'gpu.scd', 'queue', - index_position=2, - key_type='i64', - lower_bound=int(time.time()) - 3600, - resp_cls=RequestV0 - ), ret_fail=[]) - - logging.info(f'found {len(rows)} requests on queue') - return rows - - async def get_status_by_request_id(self, request_id: int) -> list[WorkerStatusV0]: - logging.info('get_status_by_request_id') - rows = await failable( - partial( - self.cleos.aget_table, - 'gpu.scd', request_id, 'status', resp_cls=WorkerStatusV0), ret_fail=[]) - - logging.info(f'found status for workers: {[r.worker for r in rows]}') - return rows - - async def get_global_config(self) -> ConfigV0: - logging.info('get_global_config') - rows = await failable( - partial( - self.cleos.aget_table, - 'gpu.scd', 'gpu.scd', 'config', - resp_cls=ConfigV0)) - - if rows: - cfg = rows[0] - logging.info(f'config found: {cfg}') - return cfg - else: - logging.error('global config not found, is the contract initialized?') - return None - - async def get_worker_balance(self) -> str: - logging.info('get_worker_balance') - rows = await failable( - partial( - self.cleos.aget_table, - 'gpu.scd', 'gpu.scd', 'users', - index_position=1, - key_type='name', - lower_bound=self.config.account, - upper_bound=self.config.account, - resp_cls=AccountV0 - )) - - if rows: - b = rows[0].balance - logging.info(f'balance: {b}') - return b - else: - logging.info('no balance info found') - return None - - async def begin_work(self, request_id: int): - ''' - Publish to the bc that the worker is beginning a model-computation - step. - - ''' - logging.info(f'begin_work on #{request_id}') - return await failable( - partial( - self.cleos.a_push_action, - 'gpu.scd', - 'workbegin', - list({ - 'worker': self.config.account, - 'request_id': request_id, - 'max_workers': 2 - }.values()), - self.config.account, self.config.key, - permission=self.config.permission - ) - ) - - async def cancel_work(self, request_id: int, reason: str): - logging.info(f'cancel_work on #{request_id}') - return await failable( - partial( - self.cleos.a_push_action, - 'gpu.scd', - 'workcancel', - list({ - 'worker': self.config.account, - 'request_id': request_id, - 'reason': reason - }.values()), - self.config.account, self.config.key, - permission=self.config.permission - ) - ) - - async def maybe_withdraw_all(self): - logging.info('maybe_withdraw_all') - balance = await self.get_worker_balance() - if not balance: - return - - balance_amount = float(balance.split(' ')[0]) - if balance_amount > 0: - await failable( - partial( - self.cleos.a_push_action, - 'gpu.scd', - 'withdraw', - list({ - 'user': self.config.account, - 'quantity': Asset.from_str(balance) - }.values()), - self.config.account, self.config.key, - permission=self.config.permission - ) - ) - - async def find_results(self) -> list[ResultV0]: - logging.info('find_results') - rows = await failable( - partial( - self.cleos.aget_table, - 'gpu.scd', 'gpu.scd', 'results', - index_position=4, - key_type='name', - lower_bound=self.config.account, - upper_bound=self.config.account, - resp_cls=ResultV0 - ) - ) - return rows - - async def submit_work( - self, - request_id: int, - request_hash: str, - result_hash: str, - ipfs_hash: str - ): - logging.info(f'submit_work #{request_id}') - return await failable( - partial( - self.cleos.a_push_action, - 'gpu.scd', - 'submit', - list({ - 'worker': self.config.account, - 'request_id': request_id, - 'request_hash': request_hash, - 'result_hash': result_hash, - 'ipfs_hash': ipfs_hash - }.values()), - self.config.account, self.config.key, - permission=self.config.permission - ) - ) - # IPFS helpers async def publish_on_ipfs(self, raw, typ: str = 'png'): Path('ipfs-staging').mkdir(exist_ok=True) @@ -302,9 +137,10 @@ class ContractState: def __init__(self, conn: NetConnector): self._conn = conn + self._config = load_skynet_toml().dgpu self._poll_index = 0 - self._queue: list[RequestV0] = [] + self._queue: list[RequestV1] = [] self._status_by_rid: dict[int, list[WorkerStatusV0]] = {} self._results: list[ResultV0] = [] @@ -315,10 +151,10 @@ class ContractState: return self._poll_index async def _fetch_results(self): - self._results = await self._conn.find_results() + self._results = await self._conn.contract.get_worker_results(self._config.account) async def _fetch_statuses_for_id(self, rid: int): - self._status_by_rid[rid] = await self._conn.get_status_by_request_id(rid) + self._status_by_rid[rid] = await self._conn.contract.get_statuses_for_request(rid) async def update_state(self): ''' @@ -326,7 +162,7 @@ class ContractState: ''' # raw queue from chain - _queue = await self._conn.get_work_requests_last_hour() + _queue = await self._conn.contract.get_requests_since(3600) # filter out invalids self._queue = [] @@ -380,7 +216,7 @@ class ContractState: return len(self._queue) @property - def first(self) -> RequestV0 | None: + def first(self) -> RequestV1 | None: if len(self._queue) > 0: return self._queue[0] @@ -391,7 +227,7 @@ class ContractState: return set(( status.worker for status in self._status_by_rid[request_id] - if status.worker != self._conn.config.account + if status.worker != self._config.account )) # predicates @@ -406,7 +242,7 @@ class ContractState: def should_compete_for_id(self, request_id: int) -> bool: return bool( - self._conn.config.non_compete & + self._config.non_compete & self.competitors_for_id(request_id) ) diff --git a/skynet/types.py b/skynet/types.py index 4e7c3a0..4b44c1d 100644 --- a/skynet/types.py +++ b/skynet/types.py @@ -39,6 +39,25 @@ class ConfigV0: token_contract: str token_symbol: str +''' +ConfigV1 + +singleton containing global info about system, definition: +```rust +#[chain(table="config", singleton)] +pub struct Config { + token_account: Name, + token_symbol: Symbol, + global_nonce: u64 +} +``` +''' + +class ConfigV1(Struct): + token_account: str + token_symbol: str + global_nonce: int + ''' RequestV0 @@ -103,6 +122,38 @@ class RequestV0(Struct): binary_data: str timestamp: str +''' +RequestV1 + +a request placed on the queue, definition: +NEW: nonce field removed + +scope: self.receiver + +```rust +#[chain(table="queue")] +pub struct Request { + #[chain(primary)] + id: u64, + user: Name, + reward: Asset, + min_verification: u32, + body: String, + binary_data: String, + #[chain(secondary)] + timestamp: TimePointSec +} +``` +''' +class RequestV1(Struct): + id: int + user: str + reward: str + min_verification: int + body: str + binary_data: str + timestamp: str + ''' AccountV0 @@ -128,6 +179,27 @@ class AccountV0(Struct): balance: str nonce: int +''' +AccountV1 + +a user account, users must deposit tokens in order to enqueue requests, definition: + +scope: self.receiver + +```rust +#[chain(table="users")] +pub struct Account { + #[chain(primary)] + user: Name, + balance: Asset +} +``` +''' + +class AccountV1(Struct): + user: str + balance: str + ''' WorkerV0 diff --git a/tests/conftest.py b/tests/conftest.py index 7cb2fc0..888a94a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import pytest from skynet.ipfs import AsyncIPFSHTTP +from skynet.contract import GPUContractAPI from skynet._testing import override_dgpu_config @@ -24,9 +25,11 @@ def skynet_cleos(cleos_bs): # cleos.import_key('gpu.scd', priv) cleos.new_account('gpu.scd', ram=4200000) + contract_path = 'tests/contracts/skygpu-contract/target' cleos.deploy_contract_from_path( 'gpu.scd', - 'tests/contracts/gpu.scd', + contract_path, + contract_name='skygpu', create_account=False ) @@ -37,9 +40,13 @@ def skynet_cleos(cleos_bs): 'gpu.scd' ) - cleos.new_account('testworker') + testworker_key = '5KRPFxF4RJebqPXqRzwStmCaEWeRfp3pR7XUNoA3zCHt5fnPu3s' + pub_key = cleos.import_key('testworker', testworker_key) + cleos.new_account('testworker', key=pub_key) - yield cleos + cleos.wait_blocks(1) + + yield GPUContractAPI(cleos), cleos @pytest.fixture diff --git a/tests/contracts/gpu.scd/gpu.scd.abi b/tests/contracts/gpu.scd/gpu.scd.abi deleted file mode 100644 index f3708bf..0000000 --- a/tests/contracts/gpu.scd/gpu.scd.abi +++ /dev/null @@ -1,416 +0,0 @@ -{ - "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", - "version": "eosio::abi/1.2", - "types": [], - "structs": [ - { - "name": "account", - "base": "", - "fields": [ - { - "name": "user", - "type": "name" - }, - { - "name": "balance", - "type": "asset" - }, - { - "name": "nonce", - "type": "uint64" - } - ] - }, - { - "name": "card", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - }, - { - "name": "owner", - "type": "name" - }, - { - "name": "card_name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "total_memory", - "type": "uint64" - }, - { - "name": "mp_count", - "type": "uint32" - }, - { - "name": "extra", - "type": "string" - } - ] - }, - { - "name": "clean", - "base": "", - "fields": [] - }, - { - "name": "config", - "base": "", - "fields": [ - { - "name": "token_contract", - "type": "name" - }, - { - "name": "token_symbol", - "type": "symbol" - } - ] - }, - { - "name": "dequeue", - "base": "", - "fields": [ - { - "name": "user", - "type": "name" - }, - { - "name": "request_id", - "type": "uint64" - } - ] - }, - { - "name": "enqueue", - "base": "", - "fields": [ - { - "name": "user", - "type": "name" - }, - { - "name": "request_body", - "type": "string" - }, - { - "name": "binary_data", - "type": "string" - }, - { - "name": "reward", - "type": "asset" - }, - { - "name": "min_verification", - "type": "uint32" - } - ] - }, - { - "name": "global_configuration_struct", - "base": "", - "fields": [ - { - "name": "token_contract", - "type": "name" - }, - { - "name": "token_symbol", - "type": "symbol" - } - ] - }, - { - "name": "submit", - "base": "", - "fields": [ - { - "name": "worker", - "type": "name" - }, - { - "name": "request_id", - "type": "uint64" - }, - { - "name": "request_hash", - "type": "checksum256" - }, - { - "name": "result_hash", - "type": "checksum256" - }, - { - "name": "ipfs_hash", - "type": "string" - } - ] - }, - { - "name": "withdraw", - "base": "", - "fields": [ - { - "name": "user", - "type": "name" - }, - { - "name": "quantity", - "type": "asset" - } - ] - }, - { - "name": "work_request_struct", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - }, - { - "name": "user", - "type": "name" - }, - { - "name": "reward", - "type": "asset" - }, - { - "name": "min_verification", - "type": "uint32" - }, - { - "name": "nonce", - "type": "uint64" - }, - { - "name": "body", - "type": "string" - }, - { - "name": "binary_data", - "type": "string" - }, - { - "name": "timestamp", - "type": "time_point_sec" - } - ] - }, - { - "name": "work_result_struct", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - }, - { - "name": "request_id", - "type": "uint64" - }, - { - "name": "user", - "type": "name" - }, - { - "name": "worker", - "type": "name" - }, - { - "name": "result_hash", - "type": "checksum256" - }, - { - "name": "ipfs_hash", - "type": "string" - }, - { - "name": "submited", - "type": "time_point_sec" - } - ] - }, - { - "name": "workbegin", - "base": "", - "fields": [ - { - "name": "worker", - "type": "name" - }, - { - "name": "request_id", - "type": "uint64" - }, - { - "name": "max_workers", - "type": "uint32" - } - ] - }, - { - "name": "workcancel", - "base": "", - "fields": [ - { - "name": "worker", - "type": "name" - }, - { - "name": "request_id", - "type": "uint64" - }, - { - "name": "reason", - "type": "string" - } - ] - }, - { - "name": "worker", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "joined", - "type": "time_point_sec" - }, - { - "name": "left", - "type": "time_point_sec" - }, - { - "name": "url", - "type": "string" - } - ] - }, - { - "name": "worker_status_struct", - "base": "", - "fields": [ - { - "name": "worker", - "type": "name" - }, - { - "name": "status", - "type": "string" - }, - { - "name": "started", - "type": "time_point_sec" - } - ] - } - ], - "actions": [ - { - "name": "clean", - "type": "clean", - "ricardian_contract": "" - }, - { - "name": "config", - "type": "config", - "ricardian_contract": "" - }, - { - "name": "dequeue", - "type": "dequeue", - "ricardian_contract": "" - }, - { - "name": "enqueue", - "type": "enqueue", - "ricardian_contract": "" - }, - { - "name": "submit", - "type": "submit", - "ricardian_contract": "" - }, - { - "name": "withdraw", - "type": "withdraw", - "ricardian_contract": "" - }, - { - "name": "workbegin", - "type": "workbegin", - "ricardian_contract": "" - }, - { - "name": "workcancel", - "type": "workcancel", - "ricardian_contract": "" - } - ], - "tables": [ - { - "name": "cards", - "type": "card", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "config", - "type": "global_configuration_struct", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "queue", - "type": "work_request_struct", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "results", - "type": "work_result_struct", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "status", - "type": "worker_status_struct", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "users", - "type": "account", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, - { - "name": "workers", - "type": "worker", - "index_type": "i64", - "key_names": [], - "key_types": [] - } - ], - "ricardian_clauses": [], - "variants": [], - "action_results": [] -} \ No newline at end of file diff --git a/tests/contracts/gpu.scd/gpu.scd.wasm b/tests/contracts/gpu.scd/gpu.scd.wasm deleted file mode 100755 index 6881852..0000000 Binary files a/tests/contracts/gpu.scd/gpu.scd.wasm and /dev/null differ diff --git a/tests/contracts/skygpu-contract b/tests/contracts/skygpu-contract new file mode 160000 index 0000000..87794ff --- /dev/null +++ b/tests/contracts/skygpu-contract @@ -0,0 +1 @@ +Subproject commit 87794ffb45340103159450694c56f241615431b2 diff --git a/tests/test_chain.py b/tests/test_chain.py index 97adfe6..4f23848 100644 --- a/tests/test_chain.py +++ b/tests/test_chain.py @@ -1,13 +1,231 @@ import trio +import pytest +from leap.errors import TransactionPushError from msgspec import json +from skynet.contract import RequestNotFound, ConfigNotFound from skynet.types import BodyV0, BodyV0Params - from skynet._testing import open_test_worker +async def test_system(skynet_cleos): + gpu, cleos = skynet_cleos + + # assert config can only be init once (done by fixture) + with pytest.raises(TransactionPushError): + await gpu.init_config('eosio.token', '4,TLOS') + + # test clean function + + # fill tables with data + + # accounts + users = [] + quantity = '1.0000 TLOS' + usr_num = 3 + for _ in range(usr_num): + test_user = cleos.new_account() + cleos.transfer_token('eosio', test_user, quantity, 'clean testing') + await gpu.deposit(test_user, quantity) + # will throw if user not found + await gpu.get_user(test_user) + users.append(test_user) + + # queue + for user in users: + await gpu.enqueue( + user, + BodyV0( + method='txt2img', + params=BodyV0Params( + prompt='ayy lmao', + model='skygpu/mocker', + step=1, + seed=0, + guidance=10.0 + ) + ), + min_verification=2 # make reqs stay after 1 result + ) + + # check requests are in queue + queue = await gpu.get_queue() + assert len(queue) == usr_num + + # workers + workers = [] + quantity = '1.0000 TLOS' + wrk_num = 3 + for _ in range(wrk_num): + worker = cleos.new_account() + await gpu.register_worker(worker, 'http://localhost') + # will throw if worker not found + await gpu.get_worker(worker) + workers.append(worker) + + # status + for i in range(wrk_num): + req = queue[i] + worker = workers[i] + await gpu.accept_work(worker, req.id) + # will throw is status not found + await gpu.get_worker_status_for_request(req.id, worker) + + # results + # make one of the workers finish to populate result + await gpu.submit_work( + workers[0], + queue[0].id, + 'ff' * 32, + 'null hash' + ) + + results = await gpu.get_results(queue[0].id) + assert len(results) == 1 + + # all tables populated + + # run clean nuke == false + await gpu.clean_tables() + + # assert tables empty + assert len(await gpu.get_queue()) == 0 + for req in queue: + assert len(await gpu.get_statuses_for_request(req.id)) == 0 + assert len(await gpu.get_results(req.id)) == 0 + + # check config, accounts and workers still there + await gpu.get_config() # raises if not found + assert len(await gpu.get_users()) == usr_num + assert len(await gpu.get_workers()) == wrk_num + + # test nuke + await gpu.clean_tables(nuke=True) + with pytest.raises(ConfigNotFound): + await gpu.get_config() + + assert len(await gpu.get_users()) == 0 + assert len(await gpu.get_workers()) == 0 + + # re init config in case other tests run + await gpu.init_config('eosio.token', '4,TLOS') + + +async def test_balance(skynet_cleos): + gpu, cleos = skynet_cleos + + # create fresh account + account = cleos.new_account() + + # try call withdraw with no user account reg'd + with pytest.raises(TransactionPushError): + await gpu.withdraw(account, '1.0000 TLOS') + + # give tokens and deposit to gpu + quantity = '1000.0000 TLOS' + cleos.transfer_token('eosio', account, quantity) + await gpu.deposit(account, quantity) + + # check if balance increased + account_row = await gpu.get_user(account) + assert account_row.balance == quantity + + # try call withdraw with more than deposited + with pytest.raises(TransactionPushError): + await gpu.withdraw(account, '1000.0001 TLOS') + + # withdraw full correct amount + await gpu.withdraw(account, quantity) + + # check if balance decreased + account_row = await gpu.get_user(account) + assert account_row.balance == '0.0000 TLOS' + +async def test_worker_reg(skynet_cleos): + gpu, cleos = skynet_cleos + + # create fresh account + worker = cleos.new_account() + url = 'https://nvidia.com' + + await gpu.register_worker(worker, url) + + # find and check vals + worker_row = await gpu.get_worker(worker) + assert worker_row.account == worker + assert worker_row.url == url + assert worker_row.joined != '1970-01-01T00:00:00' + assert worker_row.left == '1970-01-01T00:00:00' + + # attempt to register twice + with pytest.raises(TransactionPushError): + await gpu.register_worker(worker, url) + + # unregister + reason = 'testing' + await gpu.unregister_worker(worker, reason) + + worker_row = await gpu.get_worker(worker) + assert worker_row.account == worker + assert worker_row.url == url + assert worker_row.left != '1970-01-01T00:00:00' + + # attempt to unreg twice + with pytest.raises(TransactionPushError): + await gpu.unregister_worker(worker, reason) + + +async def test_queue(skynet_cleos): + gpu, cleos = skynet_cleos + + body = BodyV0( + method='txt2img', + params=BodyV0Params( + prompt='cyberpunk hacker travis bickle dystopic alley graffiti', + model='skygpu/mocker', + step=4, + seed=0, + guidance=10.0 + ) + ) + + # create account + account = cleos.new_account() + quantity = '1000.0000 TLOS' + cleos.transfer_token('eosio', account, quantity) + + # attempt to create request without prev deposit + with pytest.raises(TransactionPushError): + await gpu.enqueue(account, body) + + # deposit tokens into gpu + await gpu.deposit(account, quantity) + + # finally enqueue + req = await gpu.enqueue(account, body) + + # search by id + req_found = await gpu.get_request(req.id) + assert req == req_found + + # search by timestamp + reqs = await gpu.get_requests_since(60 * 60) + assert len(reqs) == 1 + assert reqs[0] == req + + # attempt to dequeue wrong req + with pytest.raises(TransactionPushError): + await gpu.dequeue(account, 999999) + + # dequeue correctly + await gpu.dequeue(account, req.id) + + # check deletion + with pytest.raises(RequestNotFound): + await gpu.get_request(req.id) + async def test_full_flow(inject_mockers, skynet_cleos, ipfs_node): - cleos = skynet_cleos + gpu, cleos = skynet_cleos # create account and deposit tokens into gpu account = cleos.new_account() diff --git a/uv.lock b/uv.lock index 59b4d3d..4f33572 100644 --- a/uv.lock +++ b/uv.lock @@ -272,7 +272,7 @@ name = "cffi" version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "pycparser" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } wheels = [ @@ -443,14 +443,14 @@ wheels = [ [[package]] name = "discord-py" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/af/80cab4015722d3bee175509b7249a11d5adf77b5ff4c27f268558079d149/discord_py-2.4.0.tar.gz", hash = "sha256:d07cb2a223a185873a1d0ee78b9faa9597e45b3f6186df21a95cec1e9bcdc9a5", size = 1027707 } +sdist = { url = "https://files.pythonhosted.org/packages/59/7e/a778257411e86d834c94ea6d67abacca8f0efac62996882898d55e475748/discord_py-2.5.0.tar.gz", hash = "sha256:f6827909b87ea89bdb2cc49d475cb1fada2e73235a3e4568fc8b113660340c73", size = 1054589 } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/10/3c44e9331a5ec3bae8b2919d51f611a5b94e179563b1b89eb6423a8f43eb/discord.py-2.4.0-py3-none-any.whl", hash = "sha256:b8af6711c70f7e62160bfbecb55be699b5cb69d007426759ab8ab06b1bd77d1d", size = 1125988 }, + { url = "https://files.pythonhosted.org/packages/8a/d5/2f54c110f6707bf563957349f8ca9cdf883f420699cd61ec8b48018754bb/discord.py-2.5.0-py3-none-any.whl", hash = "sha256:8e1e3b3ff5a112a4ab3a615059a285238eea34edff6de8737db6e1f72ea05195", size = 1154805 }, ] [[package]] @@ -1272,7 +1272,7 @@ name = "nvidia-cudnn-cu12" version = "9.1.0.70" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cublas-cu12" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, @@ -1299,9 +1299,9 @@ name = "nvidia-cusolver-cu12" version = "11.4.5.107" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928 }, @@ -1312,7 +1312,7 @@ name = "nvidia-cusparse-cu12" version = "12.1.0.106" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278 }, @@ -1539,17 +1539,17 @@ wheels = [ [[package]] name = "psutil" -version = "6.1.1" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502 } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511 }, - { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985 }, - { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488 }, - { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477 }, - { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017 }, - { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602 }, - { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, ] [[package]] @@ -1599,7 +1599,7 @@ wheels = [ [[package]] name = "py-leap" version = "0.1a35" -source = { editable = "../py-leap" } +source = { git = "https://github.com/guilledk/py-leap.git?branch=struct_unwrap#20f2e1f74e98e3d75984e8e1eee13c3100c17652" } dependencies = [ { name = "base58" }, { name = "cryptos" }, @@ -1609,33 +1609,6 @@ dependencies = [ { name = "ripemd-hash" }, ] -[package.metadata] -requires-dist = [ - { name = "base58", specifier = ">=2.1.1,<3" }, - { name = "cryptos", specifier = ">=2.0.9,<3" }, - { name = "httpx", specifier = ">=0.28.1,<0.29" }, - { name = "msgspec", specifier = ">=0.19.0" }, - { name = "requests", specifier = "<2.32.0" }, - { name = "ripemd-hash", specifier = ">=1.0.1,<2" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "docker", specifier = ">=6.1.3,<7" }, - { name = "pdbpp", specifier = ">=0.10.3,<0.11" }, - { name = "pytest", specifier = ">=8.3.4,<9" }, - { name = "pytest-trio", specifier = ">=0.8.0,<0.9" }, -] -docs = [ - { name = "sphinx", specifier = "==7.1.2" }, - { name = "sphinx-rtd-theme", specifier = "==1.3.0" }, -] -snaps = [ - { name = "bs4", specifier = ">=0.0.2,<0.0.3" }, - { name = "tdqm", specifier = ">=0.0.1,<0.0.2" }, - { name = "zstandard", specifier = ">=0.21.0,<0.22" }, -] - [[package]] name = "pycparser" version = "2.22" @@ -1976,7 +1949,7 @@ wheels = [ [[package]] name = "scikit-image" -version = "0.25.1" +version = "0.25.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "imageio" }, @@ -1988,23 +1961,23 @@ dependencies = [ { name = "scipy" }, { name = "tifffile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/e5/496a74ccfc1206666b9c7164a16657febdfeb6df0e458cb61286b20102c9/scikit_image-0.25.1.tar.gz", hash = "sha256:d4ab30540d114d37c35fe5c837f89b94aaba2a7643afae8354aa353319e9bbbb", size = 22697578 } +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/16/f662cd3bdbe4ca8a20e2ffd47fdb758f164ac01ea48c4e69d2a09d8fae97/scikit_image-0.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40763a3a089617e6f00f92d46b3475368b9783588a165c2aa854da95b66bb4ff", size = 13985311 }, - { url = "https://files.pythonhosted.org/packages/76/ca/2912515df1e08a60d378d3572edf61248012747eeb593869289ecc47174d/scikit_image-0.25.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:7c6b69f33e5512ee7fc53361b064430f146583f08dc75317667e81d5f8fcd0c6", size = 13188177 }, - { url = "https://files.pythonhosted.org/packages/d0/90/42d55f46fd3d9c7d4495025367bcb10033904f65d512143fa39179fa2de2/scikit_image-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9187347d115776ff0ddba3e5d2a04638d291b1a62e3c315d17b71eea351cde8", size = 14153693 }, - { url = "https://files.pythonhosted.org/packages/04/53/2822fe04ae5fc69ea1eba65b8e30a691b7257f93c6ca5621d3d94747d83e/scikit_image-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdfca713979ad1873a4b55d94bb1eb4bc713f0c10165b261bf6f7e606f44a00c", size = 14768517 }, - { url = "https://files.pythonhosted.org/packages/86/9c/cf681f591bc17c0eed560d674223ef11c1d63561fd54b8c33ab0822e17fa/scikit_image-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:167fb146de80bb2a1493d1a760a9ac81644a8a5de254c3dd12a95d1b662d819c", size = 12809084 }, - { url = "https://files.pythonhosted.org/packages/1c/8a/698138616b782d368d24061339226089f29c42878a9b18046c6a2d9d6422/scikit_image-0.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1bde2d5f1dfb23b3c72ef9fcdb2dd5f42fa353e8bd606aea63590eba5e79565", size = 13999468 }, - { url = "https://files.pythonhosted.org/packages/64/dd/ff4d4123547a59bc156a192c8cd52ea9cfcf178b70d1f48afec9d26ab6f4/scikit_image-0.25.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5112d95cccaa45c434e57efc20c1f721ab439e516e2ed49709ddc2afb7c15c70", size = 13175810 }, - { url = "https://files.pythonhosted.org/packages/1e/28/4d76f333cd0c86ccf34ab74517877117914413d307f936eb8df74ca365aa/scikit_image-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f5e313b028f5d7a9f3888ad825ddf4fb78913d7762891abb267b99244b4dd31", size = 14145156 }, - { url = "https://files.pythonhosted.org/packages/27/05/265b62ace7626de13edb7e97f0429a4faae2a95bbc2adb15a28fd5680aba/scikit_image-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39ad76aeff754048dabaff83db752aa0655dee425f006678d14485471bdb459d", size = 14784715 }, - { url = "https://files.pythonhosted.org/packages/35/80/faf325a7aef1d07067dab5ff7a890da229b42a641d2e85c98f3675cd36a2/scikit_image-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:8dc8b06176c1a2316fa8bc539fd7e96155721628ae5cf51bc1a2c62cb9786581", size = 12788033 }, - { url = "https://files.pythonhosted.org/packages/c5/a8/7d56f4401c05a186a5e82aab53977029a3f88cc0f1bd6c1fb4f4dd524262/scikit_image-0.25.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ebf83699d60134909647395a0bf07db3859646de7192b088e656deda6bc15e95", size = 13982151 }, - { url = "https://files.pythonhosted.org/packages/80/0e/d78876faaf552cf575205160aa82849fc493977a5b0cdf093f6bbb1586fe/scikit_image-0.25.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:408086520eed036340e634ab7e4f648e00238f711bac61ab933efeb11464a238", size = 13231342 }, - { url = "https://files.pythonhosted.org/packages/e0/ae/78a8dba652cdaed8a5f5dd56cf8f11ed64e44151a4813e3312916a7dff46/scikit_image-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd709faa87795869ccd21f32490c37989ca5846571495822f4b9430fb42c34c", size = 14173769 }, - { url = "https://files.pythonhosted.org/packages/d7/77/6d1da74cb0b7ba07750d6ef7e48f87807b53df1cf4a090775115dd9cc5ea/scikit_image-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b15c0265c072a46ff4720784d756d8f8e5d63567639aa8451f6673994d6846", size = 15002945 }, - { url = "https://files.pythonhosted.org/packages/df/ad/cddec5c0bcde8936c15f07593419f6d94ed33b058737948a0d59fb1142a0/scikit_image-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:a689a0d091e0bd97d7767309abdeb27c43be210d075abb34e71657add920c22b", size = 12895262 }, + { url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922 }, + { url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698 }, + { url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634 }, + { url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545 }, + { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908 }, + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057 }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335 }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783 }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376 }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698 }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000 }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893 }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389 }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435 }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474 }, ] [[package]] @@ -2152,7 +2125,7 @@ requires-dist = [ { name = "outcome", specifier = ">=1.3.0.post0" }, { name = "pillow", specifier = ">=10.0.1,<11" }, { name = "protobuf", specifier = ">=5.29.3,<6" }, - { name = "py-leap", editable = "../py-leap" }, + { name = "py-leap", git = "https://github.com/guilledk/py-leap.git?branch=struct_unwrap" }, { name = "pytz", specifier = "~=2023.3.post1" }, { name = "toml", specifier = ">=0.10.2,<0.11" }, { name = "trio", specifier = ">=0.22.2,<0.23" }, @@ -2221,7 +2194,7 @@ wheels = [ [[package]] name = "tb-nightly" -version = "2.19.0a20250211" +version = "2.20.0a20250220" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "absl-py" }, @@ -2236,7 +2209,7 @@ dependencies = [ { name = "werkzeug" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/90/f0c8effd911d95e4eba1c10836fd446a9cac1f68e62b858b2177aaae963d/tb_nightly-2.19.0a20250211-py3-none-any.whl", hash = "sha256:d2ba2da592308980a4380da77777c0634e618ef2541155717a903986dc0d7adf", size = 5503304 }, + { url = "https://files.pythonhosted.org/packages/cf/b7/7302efa230db80eba197392c3ef0330ea5e75ce3b47821e7e72668deb8ff/tb_nightly-2.20.0a20250220-py3-none-any.whl", hash = "sha256:6a166cb9492f6635393a8f03b375b8fb6ca9d8fc27b4fe51f59b8acb50206c49", size = 5503542 }, ] [[package]] @@ -2251,14 +2224,14 @@ wheels = [ [[package]] name = "tifffile" -version = "2025.1.10" +version = "2025.2.18" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/fc/697d8dac6936a81eda88e7d4653d567fcb0d504efad3fd28f5272f96fcf9/tifffile-2025.1.10.tar.gz", hash = "sha256:baaf0a3b87bf7ec375fa1537503353f70497eabe1bdde590f2e41cc0346e612f", size = 365585 } +sdist = { url = "https://files.pythonhosted.org/packages/de/1f/96d743b3417425f958dfed2518ad271b346a072d27b6859bb158e601bc21/tifffile-2025.2.18.tar.gz", hash = "sha256:8d731789e691b468746c1615d989bc550ac93cf753e9210865222e90a5a95d11", size = 365412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/50/7bef6a1259a2c4b81823653a69d2d51074f7b8095db2abae5abee962ab87/tifffile-2025.1.10-py3-none-any.whl", hash = "sha256:ed24cf4c99fb13b4f5fb29f8a0d5605e60558c950bccbdca2a6470732a27cfb3", size = 227551 }, + { url = "https://files.pythonhosted.org/packages/63/70/6f363ab13f9903557a567a4471a28ee231b962e34af8e1dd8d1b0f17e64e/tifffile-2025.2.18-py3-none-any.whl", hash = "sha256:54b36c4d5e5b8d8920134413edfe5a7cfb1c7617bb50cddf7e2772edb7149043", size = 226358 }, ] [[package]] @@ -2462,7 +2435,7 @@ name = "triton" version = "3.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "filelock" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013 },