diff --git a/ptn_cli/ptncli.py b/ptn_cli/ptncli.py index 5e50757b0..00cc642d4 100644 --- a/ptn_cli/ptncli.py +++ b/ptn_cli/ptncli.py @@ -18,6 +18,9 @@ from ptn_cli.src.commands.asset import ( select as select_asset ) +from ptn_cli.src.commands.pop import ( + generate_tree as pop_generate_tree +) _epilog = "Made with [bold red]:heart:[/bold red] by The Proprieτary Trading Neτwork" @@ -38,6 +41,21 @@ class PTNOptions: "--prompt", help="Whether to prompt for confirmation", ) + data_file = typer.Option( + None, + "--path", + help="Path to the data.json file", + ) + hotkey_opt = typer.Option( + None, + "--hotkey", + help="Miner's hotkey", + ) + output_path = typer.Option( + None, + "--output", + help="Path where the tree.json file will be saved", + ) @@ -45,12 +63,14 @@ class PTNCLIManager(CLIManager): collateral_app: typer.Typer asset_app: typer.Typer + pop_app: typer.Typer def __init__(self): super().__init__() self.collateral_app = typer.Typer(epilog=_epilog) self.asset_app = typer.Typer(epilog=_epilog) + self.pop_app = typer.Typer(epilog=_epilog) self.app.add_typer( self.collateral_app, @@ -64,6 +84,12 @@ def __init__(self): short_help="Asset command for choosing asset", no_args_is_help=True ) + self.app.add_typer( + self.pop_app, + name="pop", + short_help="Proof-of-Portfolio commands", + no_args_is_help=True + ) self.collateral_app.command( "list", rich_help_panel="Collateral Management" @@ -79,6 +105,10 @@ def __init__(self): "select", rich_help_panel="Asset class selection" )(self.asset_select) + self.pop_app.command( + "generate-tree", rich_help_panel="Proof-of-Portfolio" + )(self.pop_generate_tree) + def collateral_list( self, wallet_name: Optional[str] = Options.wallet_name, @@ -240,6 +270,31 @@ def asset_select( ) ) + def pop_generate_tree( + self, + data_file: Optional[str] = PTNOptions.data_file, + hotkey: Optional[str] = PTNOptions.hotkey_opt, + output_path: Optional[str] = PTNOptions.output_path, + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + json_output: bool = Options.json_output, + ): + """ + Generate a merkle tree for a miner using proof-of-portfolio + """ + self.verbosity_handler(quiet, verbose, json_output) + + return self._run_command( + pop_generate_tree.generate_tree( + data_file, + hotkey, + output_path, + quiet, + verbose, + json_output + ) + ) + def main(): manager = PTNCLIManager() diff --git a/ptn_cli/src/commands/pop/__init__.py b/ptn_cli/src/commands/pop/__init__.py new file mode 100644 index 000000000..f7869873c --- /dev/null +++ b/ptn_cli/src/commands/pop/__init__.py @@ -0,0 +1 @@ +# Pop commands for proof-of-portfolio integration \ No newline at end of file diff --git a/ptn_cli/src/commands/pop/generate_tree.py b/ptn_cli/src/commands/pop/generate_tree.py new file mode 100644 index 000000000..ecb860079 --- /dev/null +++ b/ptn_cli/src/commands/pop/generate_tree.py @@ -0,0 +1,99 @@ +from typing import Optional +import sys +import json +from pathlib import Path +from proof_of_portfolio.signal_processor import ( + generate_validator_trees, + get_validator_tree_hashes, +) + + +async def generate_tree( + signals_dir: Optional[str] = None, + hotkey: Optional[str] = None, + output_path: Optional[str] = None, + quiet: bool = False, + verbose: bool = False, + json_output: bool = False, +): + """ + Generate merkle trees for miners using processed signals from mining directory + """ + + if not signals_dir: + current_dir = Path.cwd() + potential_dirs = [ + current_dir / "mining" / "processed_signals", + current_dir / "processed_signals", + Path("./mining/processed_signals"), + ] + + for dir_path in potential_dirs: + if dir_path.exists() and dir_path.is_dir(): + signals_dir = str(dir_path) + if not quiet: + print(f"Found signals directory: {signals_dir}") + break + + if not signals_dir: + if not quiet: + print("Error: No processed signals directory found.", file=sys.stderr) + print( + "Please provide --signals-dir path or place files in ./mining/processed_signals/", + file=sys.stderr, + ) + return False + + signals_path = Path(signals_dir) + if not signals_path.exists() or not signals_path.is_dir(): + if not quiet: + print( + f"Error: Signals directory not found at {signals_dir}", file=sys.stderr + ) + return False + + try: + validator_trees = generate_validator_trees( + signals_dir=signals_dir, hotkey=hotkey, output_dir=output_path, quiet=quiet + ) + + if not validator_trees: + return False + + validator_hashes = get_validator_tree_hashes(validator_trees) + + if json_output: + result = { + "validator_trees": len(validator_trees), + "validators": { + validator_key: { + "order_count": data["order_count"], + "output_path": data["output_path"], + "tree_hash": data["tree_hash"], + } + for validator_key, data in validator_trees.items() + }, + } + print(json.dumps(result, indent=2)) + else: + print("\nExpected Tree Hashes:") + print("-" * 80) + for validator_key, tree_hash in validator_hashes.items(): + data = validator_trees[validator_key] + print(f"Validator: {validator_key}") + print(f"Hash: {tree_hash}") + print(f"Orders: {data['order_count']}") + print(f"Output: {data['output_path']}") + print("-" * 80) + + return True + + except Exception as e: + if not quiet: + import traceback + + print(f"Error generating trees: {str(e)}", file=sys.stderr) + if verbose: + print(f"Full traceback:", file=sys.stderr) + traceback.print_exc() + return False diff --git a/pyproject.toml b/pyproject.toml index 854e47126..7a1e8e7e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,9 +7,7 @@ name = "ptn-cli" version = "1.0.3" description = "Proprietary Trading Network CLI" readme = "README.md" -authors = [ - {name = "taoshi.io" } -] +authors = [{ name = "taoshi.io" }] license = { file = "LICENSE" } scripts = { ptncli = "ptn_cli.ptncli:main" } requires-python = ">=3.9,<3.14" @@ -19,7 +17,7 @@ dependencies = [ "async-substrate-interface>=1.4.2", "aiohttp~=3.10.2", "backoff~=2.2.1", - "click<8.2.0", # typer.testing.CliRunner(mix_stderr=) is broken in click 8.2.0+ + "click<8.2.0", # typer.testing.CliRunner(mix_stderr=) is broken in click 8.2.0+ "GitPython>=3.0.0", "netaddr~=1.3.0", "numpy>=2.0.1,<3.0.0", @@ -32,17 +30,12 @@ dependencies = [ "bittensor-wallet>=3.0.7", "plotille>=5.0.0", "plotly>=6.0.0", + "proof-of-portfolio>=0.0.14", ] [project.optional-dependencies] -cuda = [ - "torch>=1.13.1,<3.0", -] -dev = [ - "pytest", - "pytest-asyncio", - "ruff==0.11.5", -] +cuda = ["torch>=1.13.1,<3.0"] +dev = ["pytest", "pytest-asyncio", "ruff==0.11.5"] [project.urls] # more details can be found here @@ -62,7 +55,7 @@ classifiers = [ "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Utilities" + "Topic :: Utilities", ] [tool.setuptools]