hippius 0.2.48__py3-none-any.whl → 0.2.50__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hippius-0.2.48.dist-info → hippius-0.2.50.dist-info}/METADATA +1 -1
- {hippius-0.2.48.dist-info → hippius-0.2.50.dist-info}/RECORD +9 -8
- hippius_sdk/__init__.py +1 -1
- hippius_sdk/cli.py +47 -0
- hippius_sdk/cli_handlers.py +429 -0
- hippius_sdk/cli_parser.py +147 -0
- hippius_sdk/incentives.py +487 -0
- {hippius-0.2.48.dist-info → hippius-0.2.50.dist-info}/WHEEL +0 -0
- {hippius-0.2.48.dist-info → hippius-0.2.50.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,8 @@
|
|
1
|
-
hippius_sdk/__init__.py,sha256=
|
2
|
-
hippius_sdk/cli.py,sha256=
|
1
|
+
hippius_sdk/__init__.py,sha256=W3hzhHCetgr_Eoa1Q8xbgW1pYYYhJUi3vZ9FrNRm5_A,1474
|
2
|
+
hippius_sdk/cli.py,sha256=eGxStdSNtO0dR-zgDajJZUCGqEzeo2xaQQfdFrvPmBQ,20546
|
3
3
|
hippius_sdk/cli_assets.py,sha256=rjH3Z5A1CQr2d5CIAAAb0WMCjoZZlMWcdo0f93KqluE,635
|
4
|
-
hippius_sdk/cli_handlers.py,sha256=
|
5
|
-
hippius_sdk/cli_parser.py,sha256=
|
4
|
+
hippius_sdk/cli_handlers.py,sha256=iDDLswdNmeBVp09h7JWCXt3XyFlRqJ9efVdGWXSMC2E,143422
|
5
|
+
hippius_sdk/cli_parser.py,sha256=uYd_k6wBN3UyFJyJ-rRcvAzQNFoHaW28AkiBq8f6aco,26025
|
6
6
|
hippius_sdk/cli_rich.py,sha256=_jTBYMdHi2--fIVwoeNi-EtkdOb6Zy_O2TUiGvU3O7s,7324
|
7
7
|
hippius_sdk/client.py,sha256=6W50r7-WcMyZNI1j3NOKMpcSMlB819AMKM9w06YqMx0,24302
|
8
8
|
hippius_sdk/config.py,sha256=Hf_aUYzG9ylzqauA_ABUSSB5mBTYbp-VtB36VQt2XDw,21981
|
@@ -13,12 +13,13 @@ hippius_sdk/db/migrations/20241202000001_switch_to_subaccount_encryption.sql,sha
|
|
13
13
|
hippius_sdk/db/setup_database.sh,sha256=STp03qxkp2RmIVr6YZIcvQQm-_LLUOb6Jobh-52HWmg,3115
|
14
14
|
hippius_sdk/db_utils.py,sha256=-x0rbN0as7Tn3PJPZBYCgreZe52FLH40ppA1TLxsg90,1851
|
15
15
|
hippius_sdk/errors.py,sha256=LScJJmawVAx7aRzqqQguYSkf9iazSjEQEBNlD_GXZ6Y,1589
|
16
|
+
hippius_sdk/incentives.py,sha256=0_xpaxNBmbK-0mvTm87vwBaZF-daM7hxm0dKYgkuXh4,16908
|
16
17
|
hippius_sdk/ipfs.py,sha256=c4tfS5VymZYph9yXuFr40teefyVi7APq9oIpFiIXlDI,104651
|
17
18
|
hippius_sdk/ipfs_core.py,sha256=xsY0Ox6anmrkbrxkRr2RXzEukB-EEaW_oMvO0Va3vjQ,13148
|
18
19
|
hippius_sdk/key_storage.py,sha256=HWV9mM5Zkq_xxn7A72L7gvlYBtcmMwOyeFdtl8ExlmE,8315
|
19
20
|
hippius_sdk/substrate.py,sha256=4a7UIE4UqGcDW7luKTBgSDqfb2OIZusB39G1UiRs_YU,50158
|
20
21
|
hippius_sdk/utils.py,sha256=rJ611yvwKSyiBpYU3w-SuyQxoghMGU-ePuslrPv5H5g,7388
|
21
|
-
hippius-0.2.
|
22
|
-
hippius-0.2.
|
23
|
-
hippius-0.2.
|
24
|
-
hippius-0.2.
|
22
|
+
hippius-0.2.50.dist-info/METADATA,sha256=KHFUTYzy_C2mHhS0hWs-GW5CCddxce7tM-N1jfZDPDc,30088
|
23
|
+
hippius-0.2.50.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
24
|
+
hippius-0.2.50.dist-info/entry_points.txt,sha256=bFAZjW3vndretf9-8s587jA2ebMVI7puhn_lVs8jPc8,149
|
25
|
+
hippius-0.2.50.dist-info/RECORD,,
|
hippius_sdk/__init__.py
CHANGED
@@ -26,7 +26,7 @@ from hippius_sdk.config import (
|
|
26
26
|
from hippius_sdk.ipfs import IPFSClient, S3PublishResult, S3DownloadResult
|
27
27
|
from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
|
28
28
|
|
29
|
-
__version__ = "0.2.
|
29
|
+
__version__ = "0.2.50"
|
30
30
|
__all__ = [
|
31
31
|
"HippiusClient",
|
32
32
|
"IPFSClient",
|
hippius_sdk/cli.py
CHANGED
@@ -409,6 +409,53 @@ def main():
|
|
409
409
|
print_help_text(address_parser)
|
410
410
|
return 1
|
411
411
|
|
412
|
+
# Handle miner commands
|
413
|
+
elif args.command == "miner":
|
414
|
+
if args.miner_action == "register-coldkey":
|
415
|
+
return run_async_handler(
|
416
|
+
cli_handlers.handle_register_coldkey,
|
417
|
+
client,
|
418
|
+
args.node_id,
|
419
|
+
args.node_priv_hex,
|
420
|
+
args.node_type,
|
421
|
+
ipfs_config=getattr(args, "ipfs_config", None),
|
422
|
+
ipfs_priv_b64=getattr(args, "ipfs_priv_b64", None),
|
423
|
+
ipfs_peer_id=getattr(args, "ipfs_peer_id", None),
|
424
|
+
pay_in_credits=getattr(args, "pay_in_credits", False),
|
425
|
+
expires_in=getattr(args, "expires_in", 10),
|
426
|
+
block_width=getattr(args, "block_width", "u32"),
|
427
|
+
domain=getattr(args, "domain", "HIPPIUS::REGISTER::v1"),
|
428
|
+
nonce_hex=getattr(args, "nonce_hex", None),
|
429
|
+
dry_run=getattr(args, "dry_run", False),
|
430
|
+
)
|
431
|
+
elif args.miner_action == "register-hotkey":
|
432
|
+
return run_async_handler(
|
433
|
+
cli_handlers.handle_register_hotkey,
|
434
|
+
client,
|
435
|
+
args.coldkey,
|
436
|
+
args.node_id,
|
437
|
+
args.node_priv_hex,
|
438
|
+
args.node_type,
|
439
|
+
ipfs_config=getattr(args, "ipfs_config", None),
|
440
|
+
ipfs_priv_b64=getattr(args, "ipfs_priv_b64", None),
|
441
|
+
ipfs_peer_id=getattr(args, "ipfs_peer_id", None),
|
442
|
+
pay_in_credits=getattr(args, "pay_in_credits", False),
|
443
|
+
expires_in=getattr(args, "expires_in", 10),
|
444
|
+
block_width=getattr(args, "block_width", "u32"),
|
445
|
+
domain=getattr(args, "domain", "HIPPIUS::REGISTER::v1"),
|
446
|
+
nonce_hex=getattr(args, "nonce_hex", None),
|
447
|
+
dry_run=getattr(args, "dry_run", False),
|
448
|
+
)
|
449
|
+
else:
|
450
|
+
# Display the Hippius logo banner with Rich formatting
|
451
|
+
console.print(HERO_TITLE, style="bold cyan")
|
452
|
+
|
453
|
+
miner_parser = get_subparser("miner")
|
454
|
+
from hippius_sdk.cli_rich import print_help_text
|
455
|
+
|
456
|
+
print_help_text(miner_parser)
|
457
|
+
return 1
|
458
|
+
|
412
459
|
else:
|
413
460
|
# Command not recognized
|
414
461
|
error(f"Unknown command: [bold]{args.command}[/bold]")
|
hippius_sdk/cli_handlers.py
CHANGED
@@ -3291,3 +3291,432 @@ def handle_default_address_clear() -> int:
|
|
3291
3291
|
except Exception as e:
|
3292
3292
|
error(f"Error clearing default address: {e}")
|
3293
3293
|
return 1
|
3294
|
+
|
3295
|
+
|
3296
|
+
def handle_register_coldkey(
|
3297
|
+
client: HippiusClient,
|
3298
|
+
node_id: str,
|
3299
|
+
node_priv_hex: str,
|
3300
|
+
node_type: str,
|
3301
|
+
ipfs_config: str = None,
|
3302
|
+
ipfs_priv_b64: str = None,
|
3303
|
+
ipfs_peer_id: str = None,
|
3304
|
+
pay_in_credits: bool = False,
|
3305
|
+
expires_in: int = 10,
|
3306
|
+
block_width: str = "u32",
|
3307
|
+
domain: str = "HIPPIUS::REGISTER::v1",
|
3308
|
+
nonce_hex: str = None,
|
3309
|
+
dry_run: bool = False,
|
3310
|
+
) -> int:
|
3311
|
+
"""Handle miner register-coldkey command"""
|
3312
|
+
try:
|
3313
|
+
# Get current account info
|
3314
|
+
account = get_active_account()
|
3315
|
+
if not account:
|
3316
|
+
error(
|
3317
|
+
"No active account. Please set up an account first with 'hippius account create' or 'hippius seed set'"
|
3318
|
+
)
|
3319
|
+
return 1
|
3320
|
+
|
3321
|
+
account_address = get_account_address(account)
|
3322
|
+
if not account_address:
|
3323
|
+
error(f"Could not get address for account '{account}'")
|
3324
|
+
return 1
|
3325
|
+
|
3326
|
+
info(
|
3327
|
+
f"Registering node with coldkey using account: [bold cyan]{account}[/bold cyan] ({account_address})"
|
3328
|
+
)
|
3329
|
+
|
3330
|
+
# Import and use incentives.py functionality
|
3331
|
+
from hippius_sdk.incentives import (
|
3332
|
+
load_ipfs_seed,
|
3333
|
+
load_main_seed,
|
3334
|
+
encode_account_id,
|
3335
|
+
verify_peer_id,
|
3336
|
+
blake2_256,
|
3337
|
+
manual_encode_challenge,
|
3338
|
+
get_peer_id_from_public_key,
|
3339
|
+
get_public_key_from_peer_id,
|
3340
|
+
)
|
3341
|
+
from substrateinterface import SubstrateInterface, Keypair
|
3342
|
+
from nacl.signing import SigningKey
|
3343
|
+
import base58
|
3344
|
+
import secrets
|
3345
|
+
from binascii import hexlify
|
3346
|
+
|
3347
|
+
# Initialize SubstrateInterface
|
3348
|
+
substrate = SubstrateInterface(url=client.substrate_client.url)
|
3349
|
+
|
3350
|
+
# Get genesis hash and current block
|
3351
|
+
genesis_hash_hex = substrate.get_block_hash(0)
|
3352
|
+
genesis_hash = bytes.fromhex(genesis_hash_hex[2:])
|
3353
|
+
current_block_number = substrate.get_block_number(None)
|
3354
|
+
|
3355
|
+
log(f"Current block number: {current_block_number}")
|
3356
|
+
|
3357
|
+
# Process node_id
|
3358
|
+
if node_id.startswith("0x"):
|
3359
|
+
node_id_bytes = bytes.fromhex(node_id[2:])
|
3360
|
+
else:
|
3361
|
+
node_id_bytes = base58.b58decode(node_id)
|
3362
|
+
|
3363
|
+
# Load IPFS and main seeds
|
3364
|
+
ipfs_seed, peerid_from_config = load_ipfs_seed(ipfs_config, ipfs_priv_b64)
|
3365
|
+
main_seed = load_main_seed(node_priv_hex)
|
3366
|
+
|
3367
|
+
# Create signing keys
|
3368
|
+
main_sk = SigningKey(main_seed)
|
3369
|
+
main_pk = bytes(main_sk.verify_key)
|
3370
|
+
ipfs_sk = SigningKey(ipfs_seed)
|
3371
|
+
ipfs_pk = bytes(ipfs_sk.verify_key)
|
3372
|
+
|
3373
|
+
# Handle IPFS peer ID
|
3374
|
+
if ipfs_peer_id:
|
3375
|
+
ipfs_peer_id_bytes = base58.b58decode(ipfs_peer_id)
|
3376
|
+
elif peerid_from_config:
|
3377
|
+
ipfs_peer_id_bytes = base58.b58decode(peerid_from_config)
|
3378
|
+
else:
|
3379
|
+
if node_type in ["StorageMiner", "Validator"]:
|
3380
|
+
error(
|
3381
|
+
"IPFS PeerID is required for StorageMiner and Validator node types"
|
3382
|
+
)
|
3383
|
+
return 1
|
3384
|
+
ipfs_peer_id_bytes = b""
|
3385
|
+
|
3386
|
+
# Verify keys match node IDs
|
3387
|
+
if not verify_peer_id(main_pk, node_id_bytes, "Ed25519"):
|
3388
|
+
error("Main public key does not match node ID")
|
3389
|
+
return 1
|
3390
|
+
|
3391
|
+
if ipfs_peer_id_bytes and not verify_peer_id(
|
3392
|
+
ipfs_pk, ipfs_peer_id_bytes, "Ed25519"
|
3393
|
+
):
|
3394
|
+
error("IPFS public key does not match peer ID")
|
3395
|
+
return 1
|
3396
|
+
|
3397
|
+
# Create challenge data
|
3398
|
+
domain_bytes = domain.encode()
|
3399
|
+
domain24 = b"HIPPIUS::REGISTER::v1" + b"\x00" * 3
|
3400
|
+
node_id_hash = blake2_256(node_id_bytes)
|
3401
|
+
ipfs_peer_id_hash = blake2_256(ipfs_peer_id_bytes)
|
3402
|
+
|
3403
|
+
nonce = (
|
3404
|
+
bytes.fromhex(nonce_hex[2:])
|
3405
|
+
if nonce_hex and nonce_hex.startswith("0x")
|
3406
|
+
else bytes.fromhex(nonce_hex)
|
3407
|
+
if nonce_hex
|
3408
|
+
else secrets.token_bytes(32)
|
3409
|
+
)
|
3410
|
+
|
3411
|
+
expires_at_block = current_block_number + expires_in
|
3412
|
+
account_bytes = encode_account_id(account_address)
|
3413
|
+
|
3414
|
+
challenge_data = {
|
3415
|
+
"domain": domain24,
|
3416
|
+
"genesis_hash": genesis_hash,
|
3417
|
+
"account": account_bytes,
|
3418
|
+
"node_id_hash": node_id_hash,
|
3419
|
+
"ipfs_peer_id_hash": ipfs_peer_id_hash,
|
3420
|
+
"block_number": current_block_number,
|
3421
|
+
"nonce": nonce,
|
3422
|
+
"expires_at": expires_at_block,
|
3423
|
+
}
|
3424
|
+
|
3425
|
+
challenge_bytes = manual_encode_challenge(challenge_data, block_width)
|
3426
|
+
|
3427
|
+
# Sign challenge
|
3428
|
+
main_sig = main_sk.sign(challenge_bytes).signature
|
3429
|
+
ipfs_sig = ipfs_sk.sign(challenge_bytes).signature
|
3430
|
+
|
3431
|
+
# Build call parameters
|
3432
|
+
call_params = {
|
3433
|
+
"node_type": node_type,
|
3434
|
+
"node_id": node_id,
|
3435
|
+
"node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
|
3436
|
+
"pay_in_credits": pay_in_credits,
|
3437
|
+
"ipfs_node_id": ipfs_peer_id,
|
3438
|
+
"owner": account_address,
|
3439
|
+
"ipfs_peer_id": ipfs_peer_id,
|
3440
|
+
"ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
|
3441
|
+
"main_key_type": "Ed25519",
|
3442
|
+
"main_public_key": "0x" + main_pk.hex(),
|
3443
|
+
"main_sig": "0x" + main_sig.hex(),
|
3444
|
+
"ipfs_key_type": "Ed25519",
|
3445
|
+
"ipfs_public_key": "0x" + ipfs_pk.hex(),
|
3446
|
+
"ipfs_sig": "0x" + ipfs_sig.hex(),
|
3447
|
+
"challenge_bytes": "0x" + challenge_bytes.hex(),
|
3448
|
+
}
|
3449
|
+
|
3450
|
+
if dry_run:
|
3451
|
+
log("Dry run mode - printing payload without submitting")
|
3452
|
+
payload = {
|
3453
|
+
"genesis_hash_hex": "0x" + genesis_hash.hex(),
|
3454
|
+
"current_block_number": current_block_number,
|
3455
|
+
"challenge_bytes_hex": "0x" + challenge_bytes.hex(),
|
3456
|
+
"call_module": "Registration",
|
3457
|
+
"call_function": "register_node_with_coldkey",
|
3458
|
+
"call_params": call_params,
|
3459
|
+
}
|
3460
|
+
console.print(json.dumps(payload, indent=2))
|
3461
|
+
return 0
|
3462
|
+
|
3463
|
+
# Get keypair for signing
|
3464
|
+
from hippius_sdk.config import get_seed_phrase
|
3465
|
+
seed_phrase = get_seed_phrase()
|
3466
|
+
if not seed_phrase:
|
3467
|
+
error("No seed phrase available for signing transaction")
|
3468
|
+
return 1
|
3469
|
+
|
3470
|
+
kp = Keypair.create_from_uri(seed_phrase)
|
3471
|
+
|
3472
|
+
# Submit transaction
|
3473
|
+
log("Submitting registration transaction...")
|
3474
|
+
call = substrate.compose_call(
|
3475
|
+
call_module="Registration",
|
3476
|
+
call_function="register_node_with_coldkey",
|
3477
|
+
call_params=call_params,
|
3478
|
+
)
|
3479
|
+
extrinsic = substrate.create_signed_extrinsic(call=call, keypair=kp)
|
3480
|
+
receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
|
3481
|
+
|
3482
|
+
result = {
|
3483
|
+
"extrinsic_hash": receipt.extrinsic_hash,
|
3484
|
+
"is_success": receipt.is_success,
|
3485
|
+
"error_message": receipt.error_message,
|
3486
|
+
"triggered_events": [str(event) for event in receipt.triggered_events],
|
3487
|
+
}
|
3488
|
+
|
3489
|
+
if receipt.is_success:
|
3490
|
+
success(f"Node registered successfully with coldkey!")
|
3491
|
+
success(f"Transaction hash: {receipt.extrinsic_hash}")
|
3492
|
+
else:
|
3493
|
+
error(f"Registration failed: {receipt.error_message}")
|
3494
|
+
|
3495
|
+
log("Full result:")
|
3496
|
+
console.print(json.dumps(result, indent=2))
|
3497
|
+
|
3498
|
+
return 0 if receipt.is_success else 1
|
3499
|
+
|
3500
|
+
except Exception as e:
|
3501
|
+
error(f"Error registering node with coldkey: {e}")
|
3502
|
+
if hasattr(e, "__traceback__"):
|
3503
|
+
import traceback
|
3504
|
+
|
3505
|
+
traceback.print_exc()
|
3506
|
+
return 1
|
3507
|
+
|
3508
|
+
|
3509
|
+
def handle_register_hotkey(
|
3510
|
+
client: HippiusClient,
|
3511
|
+
coldkey: str,
|
3512
|
+
node_id: str,
|
3513
|
+
node_priv_hex: str,
|
3514
|
+
node_type: str,
|
3515
|
+
ipfs_config: str = None,
|
3516
|
+
ipfs_priv_b64: str = None,
|
3517
|
+
ipfs_peer_id: str = None,
|
3518
|
+
pay_in_credits: bool = False,
|
3519
|
+
expires_in: int = 10,
|
3520
|
+
block_width: str = "u32",
|
3521
|
+
domain: str = "HIPPIUS::REGISTER::v1",
|
3522
|
+
nonce_hex: str = None,
|
3523
|
+
dry_run: bool = False,
|
3524
|
+
) -> int:
|
3525
|
+
"""Handle miner register-hotkey command"""
|
3526
|
+
try:
|
3527
|
+
# Get current account info
|
3528
|
+
account = get_active_account()
|
3529
|
+
if not account:
|
3530
|
+
error(
|
3531
|
+
"No active account. Please set up an account first with 'hippius account create' or 'hippius seed set'"
|
3532
|
+
)
|
3533
|
+
return 1
|
3534
|
+
|
3535
|
+
account_address = get_account_address(account)
|
3536
|
+
if not account_address:
|
3537
|
+
error(f"Could not get address for account '{account}'")
|
3538
|
+
return 1
|
3539
|
+
|
3540
|
+
info(
|
3541
|
+
f"Registering node with hotkey using account: [bold cyan]{account}[/bold cyan] ({account_address})"
|
3542
|
+
)
|
3543
|
+
info(f"Coldkey: [bold yellow]{coldkey}[/bold yellow]")
|
3544
|
+
|
3545
|
+
# Import and use incentives.py functionality
|
3546
|
+
from hippius_sdk.incentives import (
|
3547
|
+
load_ipfs_seed,
|
3548
|
+
load_main_seed,
|
3549
|
+
encode_account_id,
|
3550
|
+
verify_peer_id,
|
3551
|
+
blake2_256,
|
3552
|
+
manual_encode_challenge,
|
3553
|
+
get_peer_id_from_public_key,
|
3554
|
+
get_public_key_from_peer_id,
|
3555
|
+
)
|
3556
|
+
from substrateinterface import SubstrateInterface, Keypair
|
3557
|
+
from nacl.signing import SigningKey
|
3558
|
+
import base58
|
3559
|
+
import secrets
|
3560
|
+
from binascii import hexlify
|
3561
|
+
|
3562
|
+
# Initialize SubstrateInterface
|
3563
|
+
substrate = SubstrateInterface(url=client.substrate_client.url)
|
3564
|
+
|
3565
|
+
# Get genesis hash and current block
|
3566
|
+
genesis_hash_hex = substrate.get_block_hash(0)
|
3567
|
+
genesis_hash = bytes.fromhex(genesis_hash_hex[2:])
|
3568
|
+
current_block_number = substrate.get_block_number(None)
|
3569
|
+
|
3570
|
+
log(f"Current block number: {current_block_number}")
|
3571
|
+
|
3572
|
+
# Process node_id
|
3573
|
+
if node_id.startswith("0x"):
|
3574
|
+
node_id_bytes = bytes.fromhex(node_id[2:])
|
3575
|
+
else:
|
3576
|
+
node_id_bytes = base58.b58decode(node_id)
|
3577
|
+
|
3578
|
+
# Load IPFS and main seeds
|
3579
|
+
ipfs_seed, peerid_from_config = load_ipfs_seed(ipfs_config, ipfs_priv_b64)
|
3580
|
+
main_seed = load_main_seed(node_priv_hex)
|
3581
|
+
|
3582
|
+
# Create signing keys
|
3583
|
+
main_sk = SigningKey(main_seed)
|
3584
|
+
main_pk = bytes(main_sk.verify_key)
|
3585
|
+
ipfs_sk = SigningKey(ipfs_seed)
|
3586
|
+
ipfs_pk = bytes(ipfs_sk.verify_key)
|
3587
|
+
|
3588
|
+
# Handle IPFS peer ID
|
3589
|
+
if ipfs_peer_id:
|
3590
|
+
ipfs_peer_id_bytes = base58.b58decode(ipfs_peer_id)
|
3591
|
+
elif peerid_from_config:
|
3592
|
+
ipfs_peer_id_bytes = base58.b58decode(peerid_from_config)
|
3593
|
+
else:
|
3594
|
+
if node_type in ["StorageMiner", "Validator"]:
|
3595
|
+
error(
|
3596
|
+
"IPFS PeerID is required for StorageMiner and Validator node types"
|
3597
|
+
)
|
3598
|
+
return 1
|
3599
|
+
ipfs_peer_id_bytes = b""
|
3600
|
+
|
3601
|
+
# Verify keys match node IDs
|
3602
|
+
if not verify_peer_id(main_pk, node_id_bytes, "Ed25519"):
|
3603
|
+
error("Main public key does not match node ID")
|
3604
|
+
return 1
|
3605
|
+
|
3606
|
+
if ipfs_peer_id_bytes and not verify_peer_id(
|
3607
|
+
ipfs_pk, ipfs_peer_id_bytes, "Ed25519"
|
3608
|
+
):
|
3609
|
+
error("IPFS public key does not match peer ID")
|
3610
|
+
return 1
|
3611
|
+
|
3612
|
+
# Create challenge data
|
3613
|
+
domain_bytes = domain.encode()
|
3614
|
+
domain24 = b"HIPPIUS::REGISTER::v1" + b"\x00" * 3
|
3615
|
+
node_id_hash = blake2_256(node_id_bytes)
|
3616
|
+
ipfs_peer_id_hash = blake2_256(ipfs_peer_id_bytes)
|
3617
|
+
|
3618
|
+
nonce = (
|
3619
|
+
bytes.fromhex(nonce_hex[2:])
|
3620
|
+
if nonce_hex and nonce_hex.startswith("0x")
|
3621
|
+
else bytes.fromhex(nonce_hex)
|
3622
|
+
if nonce_hex
|
3623
|
+
else secrets.token_bytes(32)
|
3624
|
+
)
|
3625
|
+
|
3626
|
+
expires_at_block = current_block_number + expires_in
|
3627
|
+
account_bytes = encode_account_id(account_address)
|
3628
|
+
|
3629
|
+
challenge_data = {
|
3630
|
+
"domain": domain24,
|
3631
|
+
"genesis_hash": genesis_hash,
|
3632
|
+
"account": account_bytes,
|
3633
|
+
"node_id_hash": node_id_hash,
|
3634
|
+
"ipfs_peer_id_hash": ipfs_peer_id_hash,
|
3635
|
+
"block_number": current_block_number,
|
3636
|
+
"nonce": nonce,
|
3637
|
+
"expires_at": expires_at_block,
|
3638
|
+
}
|
3639
|
+
|
3640
|
+
challenge_bytes = manual_encode_challenge(challenge_data, block_width)
|
3641
|
+
|
3642
|
+
# Sign challenge
|
3643
|
+
main_sig = main_sk.sign(challenge_bytes).signature
|
3644
|
+
ipfs_sig = ipfs_sk.sign(challenge_bytes).signature
|
3645
|
+
|
3646
|
+
# Build call parameters
|
3647
|
+
call_params = {
|
3648
|
+
"coldkey": coldkey,
|
3649
|
+
"node_type": node_type,
|
3650
|
+
"node_id": node_id,
|
3651
|
+
"node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
|
3652
|
+
"pay_in_credits": pay_in_credits,
|
3653
|
+
"ipfs_node_id": ipfs_peer_id,
|
3654
|
+
"ipfs_peer_id": ipfs_peer_id,
|
3655
|
+
"ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
|
3656
|
+
"owner": account_address,
|
3657
|
+
"main_key_type": "Ed25519",
|
3658
|
+
"main_public_key": "0x" + main_pk.hex(),
|
3659
|
+
"main_sig": "0x" + main_sig.hex(),
|
3660
|
+
"ipfs_key_type": "Ed25519",
|
3661
|
+
"ipfs_public_key": "0x" + ipfs_pk.hex(),
|
3662
|
+
"ipfs_sig": "0x" + ipfs_sig.hex(),
|
3663
|
+
"challenge_bytes": "0x" + challenge_bytes.hex(),
|
3664
|
+
}
|
3665
|
+
|
3666
|
+
if dry_run:
|
3667
|
+
log("Dry run mode - printing payload without submitting")
|
3668
|
+
payload = {
|
3669
|
+
"genesis_hash_hex": "0x" + genesis_hash.hex(),
|
3670
|
+
"current_block_number": current_block_number,
|
3671
|
+
"challenge_bytes_hex": "0x" + challenge_bytes.hex(),
|
3672
|
+
"call_module": "Registration",
|
3673
|
+
"call_function": "register_node_with_hotkey",
|
3674
|
+
"call_params": call_params,
|
3675
|
+
}
|
3676
|
+
console.print(json.dumps(payload, indent=2))
|
3677
|
+
return 0
|
3678
|
+
|
3679
|
+
# Get keypair for signing
|
3680
|
+
from hippius_sdk.config import get_seed_phrase
|
3681
|
+
seed_phrase = get_seed_phrase()
|
3682
|
+
if not seed_phrase:
|
3683
|
+
error("No seed phrase available for signing transaction")
|
3684
|
+
return 1
|
3685
|
+
|
3686
|
+
kp = Keypair.create_from_uri(seed_phrase)
|
3687
|
+
|
3688
|
+
# Submit transaction
|
3689
|
+
log("Submitting registration transaction...")
|
3690
|
+
call = substrate.compose_call(
|
3691
|
+
call_module="Registration",
|
3692
|
+
call_function="register_node_with_hotkey",
|
3693
|
+
call_params=call_params,
|
3694
|
+
)
|
3695
|
+
extrinsic = substrate.create_signed_extrinsic(call=call, keypair=kp)
|
3696
|
+
receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
|
3697
|
+
|
3698
|
+
result = {
|
3699
|
+
"extrinsic_hash": receipt.extrinsic_hash,
|
3700
|
+
"is_success": receipt.is_success,
|
3701
|
+
"error_message": receipt.error_message,
|
3702
|
+
"triggered_events": [str(event) for event in receipt.triggered_events],
|
3703
|
+
}
|
3704
|
+
|
3705
|
+
if receipt.is_success:
|
3706
|
+
success(f"Node registered successfully with hotkey!")
|
3707
|
+
success(f"Transaction hash: {receipt.extrinsic_hash}")
|
3708
|
+
else:
|
3709
|
+
error(f"Registration failed: {receipt.error_message}")
|
3710
|
+
|
3711
|
+
log("Full result:")
|
3712
|
+
console.print(json.dumps(result, indent=2))
|
3713
|
+
|
3714
|
+
return 0 if receipt.is_success else 1
|
3715
|
+
|
3716
|
+
except Exception as e:
|
3717
|
+
error(f"Error registering node with hotkey: {e}")
|
3718
|
+
if hasattr(e, "__traceback__"):
|
3719
|
+
import traceback
|
3720
|
+
|
3721
|
+
traceback.print_exc()
|
3722
|
+
return 1
|
hippius_sdk/cli_parser.py
CHANGED
@@ -171,6 +171,7 @@ examples:
|
|
171
171
|
add_seed_commands(subparsers)
|
172
172
|
add_account_commands(subparsers)
|
173
173
|
add_address_commands(subparsers)
|
174
|
+
add_miner_commands(subparsers)
|
174
175
|
|
175
176
|
return parser
|
176
177
|
|
@@ -654,6 +655,152 @@ def add_address_commands(subparsers):
|
|
654
655
|
)
|
655
656
|
|
656
657
|
|
658
|
+
def add_miner_commands(subparsers):
|
659
|
+
"""Add miner registration commands to the parser."""
|
660
|
+
# Miner command
|
661
|
+
miner_parser = subparsers.add_parser(
|
662
|
+
"miner", help="Miner node registration commands"
|
663
|
+
)
|
664
|
+
miner_subparsers = miner_parser.add_subparsers(
|
665
|
+
dest="miner_action", help="Miner action"
|
666
|
+
)
|
667
|
+
|
668
|
+
# Register coldkey command
|
669
|
+
register_coldkey_parser = miner_subparsers.add_parser(
|
670
|
+
"register-coldkey",
|
671
|
+
help="Register a node with coldkey (current account becomes coldkey)",
|
672
|
+
)
|
673
|
+
|
674
|
+
# Required arguments for register-coldkey
|
675
|
+
register_coldkey_parser.add_argument(
|
676
|
+
"--node-id", required=True, help="Your main node_id (libp2p peer ID)"
|
677
|
+
)
|
678
|
+
register_coldkey_parser.add_argument(
|
679
|
+
"--node-priv-hex",
|
680
|
+
required=True,
|
681
|
+
help="Main libp2p ed25519 private key hex (32/64B)",
|
682
|
+
)
|
683
|
+
register_coldkey_parser.add_argument(
|
684
|
+
"--node-type",
|
685
|
+
required=True,
|
686
|
+
choices=["StorageMiner", "Validator", "ComputeMiner", "StorageS3", "GpuMiner"],
|
687
|
+
help="Type of miner node",
|
688
|
+
)
|
689
|
+
|
690
|
+
# IPFS configuration (one required)
|
691
|
+
ipfs_group = register_coldkey_parser.add_mutually_exclusive_group(required=True)
|
692
|
+
ipfs_group.add_argument(
|
693
|
+
"--ipfs-config", help="Path to IPFS config file (e.g. ~/.ipfs/config)"
|
694
|
+
)
|
695
|
+
ipfs_group.add_argument(
|
696
|
+
"--ipfs-priv-b64",
|
697
|
+
help="IPFS Identity.PrivKey base64 if not using --ipfs-config",
|
698
|
+
)
|
699
|
+
|
700
|
+
# Optional arguments
|
701
|
+
register_coldkey_parser.add_argument(
|
702
|
+
"--ipfs-peer-id", help="Optional override IPFS PeerID"
|
703
|
+
)
|
704
|
+
register_coldkey_parser.add_argument(
|
705
|
+
"--pay-in-credits", action="store_true", help="Pay in credits for registration"
|
706
|
+
)
|
707
|
+
register_coldkey_parser.add_argument(
|
708
|
+
"--expires-in",
|
709
|
+
type=int,
|
710
|
+
default=10,
|
711
|
+
help="Challenge expiration in blocks (default: 10)",
|
712
|
+
)
|
713
|
+
register_coldkey_parser.add_argument(
|
714
|
+
"--block-width",
|
715
|
+
choices=["u32", "u64"],
|
716
|
+
default="u32",
|
717
|
+
help="Block number width (default: u32)",
|
718
|
+
)
|
719
|
+
register_coldkey_parser.add_argument(
|
720
|
+
"--domain",
|
721
|
+
default="HIPPIUS::REGISTER::v1",
|
722
|
+
help="Domain for challenge (default: HIPPIUS::REGISTER::v1)",
|
723
|
+
)
|
724
|
+
register_coldkey_parser.add_argument(
|
725
|
+
"--nonce-hex", help="32-byte hex nonce (optional, random if not provided)"
|
726
|
+
)
|
727
|
+
register_coldkey_parser.add_argument(
|
728
|
+
"--dry-run",
|
729
|
+
action="store_true",
|
730
|
+
help="Do not submit extrinsic; just print payload",
|
731
|
+
)
|
732
|
+
|
733
|
+
# Register hotkey command
|
734
|
+
register_hotkey_parser = miner_subparsers.add_parser(
|
735
|
+
"register-hotkey",
|
736
|
+
help="Register a node with hotkey (current account becomes hotkey)",
|
737
|
+
)
|
738
|
+
|
739
|
+
# Required arguments for register-hotkey
|
740
|
+
register_hotkey_parser.add_argument(
|
741
|
+
"--coldkey", required=True, help="Coldkey SS58 address"
|
742
|
+
)
|
743
|
+
register_hotkey_parser.add_argument(
|
744
|
+
"--node-id", required=True, help="Your main node_id (libp2p peer ID)"
|
745
|
+
)
|
746
|
+
register_hotkey_parser.add_argument(
|
747
|
+
"--node-priv-hex",
|
748
|
+
required=True,
|
749
|
+
help="Main libp2p ed25519 private key hex (32/64B)",
|
750
|
+
)
|
751
|
+
register_hotkey_parser.add_argument(
|
752
|
+
"--node-type",
|
753
|
+
required=True,
|
754
|
+
choices=["StorageMiner", "Validator", "ComputeMiner", "StorageS3", "GpuMiner"],
|
755
|
+
help="Type of miner node",
|
756
|
+
)
|
757
|
+
|
758
|
+
# IPFS configuration (one required)
|
759
|
+
ipfs_group_hotkey = register_hotkey_parser.add_mutually_exclusive_group(
|
760
|
+
required=True
|
761
|
+
)
|
762
|
+
ipfs_group_hotkey.add_argument(
|
763
|
+
"--ipfs-config", help="Path to IPFS config file (e.g. ~/.ipfs/config)"
|
764
|
+
)
|
765
|
+
ipfs_group_hotkey.add_argument(
|
766
|
+
"--ipfs-priv-b64",
|
767
|
+
help="IPFS Identity.PrivKey base64 if not using --ipfs-config",
|
768
|
+
)
|
769
|
+
|
770
|
+
# Optional arguments
|
771
|
+
register_hotkey_parser.add_argument(
|
772
|
+
"--ipfs-peer-id", help="Optional override IPFS PeerID"
|
773
|
+
)
|
774
|
+
register_hotkey_parser.add_argument(
|
775
|
+
"--pay-in-credits", action="store_true", help="Pay in credits for registration"
|
776
|
+
)
|
777
|
+
register_hotkey_parser.add_argument(
|
778
|
+
"--expires-in",
|
779
|
+
type=int,
|
780
|
+
default=10,
|
781
|
+
help="Challenge expiration in blocks (default: 10)",
|
782
|
+
)
|
783
|
+
register_hotkey_parser.add_argument(
|
784
|
+
"--block-width",
|
785
|
+
choices=["u32", "u64"],
|
786
|
+
default="u32",
|
787
|
+
help="Block number width (default: u32)",
|
788
|
+
)
|
789
|
+
register_hotkey_parser.add_argument(
|
790
|
+
"--domain",
|
791
|
+
default="HIPPIUS::REGISTER::v1",
|
792
|
+
help="Domain for challenge (default: HIPPIUS::REGISTER::v1)",
|
793
|
+
)
|
794
|
+
register_hotkey_parser.add_argument(
|
795
|
+
"--nonce-hex", help="32-byte hex nonce (optional, random if not provided)"
|
796
|
+
)
|
797
|
+
register_hotkey_parser.add_argument(
|
798
|
+
"--dry-run",
|
799
|
+
action="store_true",
|
800
|
+
help="Do not submit extrinsic; just print payload",
|
801
|
+
)
|
802
|
+
|
803
|
+
|
657
804
|
def get_subparser(command: str) -> argparse.ArgumentParser:
|
658
805
|
"""Get a subparser for a specific command.
|
659
806
|
|
@@ -0,0 +1,487 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
import argparse
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import base64
|
6
|
+
import secrets
|
7
|
+
from typing import Tuple
|
8
|
+
from nacl.signing import SigningKey
|
9
|
+
from binascii import unhexlify, hexlify
|
10
|
+
from hashlib import blake2b
|
11
|
+
import base58
|
12
|
+
|
13
|
+
from substrateinterface import SubstrateInterface, Keypair
|
14
|
+
from scalecodec.utils.ss58 import ss58_decode
|
15
|
+
|
16
|
+
|
17
|
+
# ---------- Helpers ----------
|
18
|
+
def blake2_256(b: bytes) -> bytes:
|
19
|
+
return blake2b(b, digest_size=32).digest()
|
20
|
+
|
21
|
+
|
22
|
+
def _read_varint(b: bytes, i: int) -> Tuple[int, int]:
|
23
|
+
x, s = 0, 0
|
24
|
+
while True:
|
25
|
+
if i >= len(b):
|
26
|
+
raise ValueError("truncated varint")
|
27
|
+
c = b[i]
|
28
|
+
i += 1
|
29
|
+
x |= (c & 0x7F) << s
|
30
|
+
if not (c & 0x80):
|
31
|
+
return x, i
|
32
|
+
s += 7
|
33
|
+
|
34
|
+
|
35
|
+
def decode_libp2p_privkey_b64(privkey_b64: str) -> bytes:
|
36
|
+
"""Decode go-libp2p PrivateKey proto; return ed25519 32B seed."""
|
37
|
+
raw = base64.b64decode(privkey_b64)
|
38
|
+
i = 0
|
39
|
+
key_type = None
|
40
|
+
data = None
|
41
|
+
while i < len(raw):
|
42
|
+
key, i = _read_varint(raw, i)
|
43
|
+
fld, wtype = key >> 3, key & 0x7
|
44
|
+
if fld == 1:
|
45
|
+
if wtype != 0:
|
46
|
+
raise ValueError("bad wiretype for Type")
|
47
|
+
key_type, i = _read_varint(raw, i)
|
48
|
+
elif fld == 2:
|
49
|
+
if wtype != 2:
|
50
|
+
raise ValueError("bad wiretype for Data")
|
51
|
+
ln, j = _read_varint(raw, i)
|
52
|
+
data = raw[j : j + ln]
|
53
|
+
i = j + ln
|
54
|
+
else:
|
55
|
+
if wtype == 0:
|
56
|
+
_, i = _read_varint(raw, i)
|
57
|
+
elif wtype == 2:
|
58
|
+
ln, j = _read_varint(raw, i)
|
59
|
+
i = j + ln
|
60
|
+
else:
|
61
|
+
raise ValueError("unsupported wiretype")
|
62
|
+
if key_type != 1: # 1 = Ed25519
|
63
|
+
raise ValueError(f"Only Ed25519 supported (KeyType={key_type})")
|
64
|
+
if data is None:
|
65
|
+
raise ValueError("no Data")
|
66
|
+
if len(data) == 64:
|
67
|
+
return data[:32]
|
68
|
+
if len(data) == 32:
|
69
|
+
return data
|
70
|
+
raise ValueError(f"unexpected ed25519 Data length {len(data)}")
|
71
|
+
|
72
|
+
|
73
|
+
def get_peer_id_from_public_key(public_key: bytes) -> str:
|
74
|
+
"""Convert a public key to a libp2p peer ID"""
|
75
|
+
prefix = bytes([0x00, 0x24, 0x08, 0x01, 0x12, 0x20])
|
76
|
+
peer_id_bytes = prefix + public_key
|
77
|
+
return base58.b58encode(peer_id_bytes).decode()
|
78
|
+
|
79
|
+
|
80
|
+
def get_public_key_from_peer_id(peer_id: str) -> bytes:
|
81
|
+
"""Extract public key from a libp2p peer ID"""
|
82
|
+
peer_id_bytes = base58.b58decode(peer_id)
|
83
|
+
|
84
|
+
if len(peer_id_bytes) != 38:
|
85
|
+
raise ValueError(f"Invalid peer ID length: {len(peer_id_bytes)}")
|
86
|
+
|
87
|
+
prefix = bytes([0x00, 0x24, 0x08, 0x01, 0x12, 0x20])
|
88
|
+
if peer_id_bytes[:6] != prefix:
|
89
|
+
raise ValueError("Invalid peer ID prefix")
|
90
|
+
|
91
|
+
return peer_id_bytes[6:38]
|
92
|
+
|
93
|
+
|
94
|
+
def load_ipfs_seed(
|
95
|
+
ipfs_config: str = None, ipfs_priv_b64: str = None
|
96
|
+
) -> Tuple[bytes, str]:
|
97
|
+
if ipfs_priv_b64:
|
98
|
+
try:
|
99
|
+
seed = decode_libp2p_privkey_b64(ipfs_priv_b64)
|
100
|
+
return seed, ""
|
101
|
+
except ValueError as e:
|
102
|
+
raise SystemExit(f"Error decoding IPFS private key: {e}")
|
103
|
+
|
104
|
+
if not ipfs_config:
|
105
|
+
raise SystemExit(
|
106
|
+
"Either --ipfs-config or --ipfs-priv-b64 is required for IPFS setup."
|
107
|
+
)
|
108
|
+
|
109
|
+
cfg_path = os.path.expanduser(ipfs_config)
|
110
|
+
if not os.path.exists(cfg_path):
|
111
|
+
raise SystemExit(f"IPFS config file not found at {cfg_path}")
|
112
|
+
|
113
|
+
try:
|
114
|
+
cfg = json.load(open(cfg_path))
|
115
|
+
except json.JSONDecodeError:
|
116
|
+
raise SystemExit(f"Invalid JSON in IPFS config file: {cfg_path}")
|
117
|
+
|
118
|
+
ident = cfg.get("Identity") or {}
|
119
|
+
if "PrivKey" not in ident:
|
120
|
+
raise SystemExit("IPFS config missing Identity.PrivKey")
|
121
|
+
|
122
|
+
seed = decode_libp2p_privkey_b64(ident["PrivKey"])
|
123
|
+
return seed, ident.get("PeerID", "")
|
124
|
+
|
125
|
+
|
126
|
+
def load_main_seed(node_priv_hex: str) -> bytes:
|
127
|
+
h = node_priv_hex.removeprefix("0x")
|
128
|
+
try:
|
129
|
+
b = unhexlify(h)
|
130
|
+
except Exception:
|
131
|
+
raise SystemExit("Invalid hex for node_priv_hex")
|
132
|
+
if len(b) == 32:
|
133
|
+
return b
|
134
|
+
if len(b) == 64:
|
135
|
+
return b[:32]
|
136
|
+
raise SystemExit("node_priv_hex must be 32 or 64 bytes")
|
137
|
+
|
138
|
+
|
139
|
+
def encode_account_id(ss58_address: str) -> bytes:
|
140
|
+
"""Convert SS58 address to 32-byte public key (AccountId)"""
|
141
|
+
decoded = ss58_decode(ss58_address)
|
142
|
+
if isinstance(decoded, str):
|
143
|
+
decoded = bytes.fromhex(decoded)
|
144
|
+
elif isinstance(decoded, list):
|
145
|
+
decoded = bytes(decoded)
|
146
|
+
if len(decoded) != 32:
|
147
|
+
raise ValueError(f"Decoded AccountId must be 32 bytes, got {len(decoded)}")
|
148
|
+
return decoded
|
149
|
+
|
150
|
+
|
151
|
+
def verify_peer_id(public_key: bytes, peer_id: bytes, key_type: str) -> bool:
|
152
|
+
"""Verify that a public key corresponds to a libp2p peer ID"""
|
153
|
+
if key_type != "Ed25519":
|
154
|
+
return False
|
155
|
+
|
156
|
+
if len(peer_id) != 38:
|
157
|
+
return False
|
158
|
+
|
159
|
+
expected_prefix = bytes([0x00, 0x24, 0x08, 0x01, 0x12, 0x20])
|
160
|
+
if peer_id[:6] != expected_prefix:
|
161
|
+
return False
|
162
|
+
|
163
|
+
return peer_id[6:38] == public_key
|
164
|
+
|
165
|
+
|
166
|
+
def manual_encode_challenge(challenge_data, block_width):
|
167
|
+
"""Manually encode the challenge data using basic Scale encoding"""
|
168
|
+
encoded = b""
|
169
|
+
|
170
|
+
encoded += challenge_data["domain"]
|
171
|
+
encoded += challenge_data["genesis_hash"]
|
172
|
+
encoded += challenge_data["account"]
|
173
|
+
encoded += challenge_data["node_id_hash"]
|
174
|
+
encoded += challenge_data["ipfs_peer_id_hash"]
|
175
|
+
|
176
|
+
if block_width == "u32":
|
177
|
+
encoded += challenge_data["block_number"].to_bytes(4, "little")
|
178
|
+
else:
|
179
|
+
encoded += challenge_data["block_number"].to_bytes(8, "little")
|
180
|
+
|
181
|
+
encoded += challenge_data["nonce"]
|
182
|
+
|
183
|
+
if block_width == "u32":
|
184
|
+
encoded += challenge_data["expires_at"].to_bytes(4, "little")
|
185
|
+
else:
|
186
|
+
encoded += challenge_data["expires_at"].to_bytes(8, "little")
|
187
|
+
|
188
|
+
return encoded
|
189
|
+
|
190
|
+
|
191
|
+
# ---------- CLI ----------
|
192
|
+
|
193
|
+
|
194
|
+
def main():
|
195
|
+
ap = argparse.ArgumentParser(
|
196
|
+
description="Hippius v2 register: dual libp2p sigs + one-shot challenge"
|
197
|
+
)
|
198
|
+
ap.add_argument(
|
199
|
+
"--ws", required=True, help="Substrate WS endpoint (e.g. ws://127.0.0.1:9944)"
|
200
|
+
)
|
201
|
+
ap.add_argument("--module", default="Registration", help="Pallet name in metadata")
|
202
|
+
ap.add_argument(
|
203
|
+
"--function",
|
204
|
+
default="register_node_with_coldkey",
|
205
|
+
choices=[
|
206
|
+
"register_node_with_coldkey",
|
207
|
+
"register_node_with_hotkey",
|
208
|
+
"verify_existing_node",
|
209
|
+
"verify_existing_coldkey_node",
|
210
|
+
],
|
211
|
+
help="Call function",
|
212
|
+
)
|
213
|
+
|
214
|
+
ap.add_argument(
|
215
|
+
"--owner-uri", required=True, help="Owner secret URI //path or mnemonic"
|
216
|
+
)
|
217
|
+
ap.add_argument("--owner-ss58", required=True, help="Owner SS58 address")
|
218
|
+
ap.add_argument("--owner-crypto", choices=["sr25519", "ed25519"], default="sr25519")
|
219
|
+
|
220
|
+
ap.add_argument("--node-id", required=True, help="Your main node_id (hex or utf8)")
|
221
|
+
ap.add_argument(
|
222
|
+
"--node-priv-hex",
|
223
|
+
required=True,
|
224
|
+
help="Main libp2p ed25519 private key hex (32/64B)",
|
225
|
+
)
|
226
|
+
|
227
|
+
ap.add_argument("--ipfs-config", help="~/.ipfs/config (reads PeerID & PrivKey)")
|
228
|
+
ap.add_argument(
|
229
|
+
"--ipfs-priv-b64", help="Identity.PrivKey base64 if not using --ipfs-config"
|
230
|
+
)
|
231
|
+
ap.add_argument("--ipfs-peer-id", help="Optional override PeerID (utf8)")
|
232
|
+
ap.add_argument("--expires-in", type=int, default=10)
|
233
|
+
ap.add_argument("--block-width", choices=["u32", "u64"], default="u32")
|
234
|
+
ap.add_argument("--domain", default="HIPPIUS::REGISTER::v1")
|
235
|
+
ap.add_argument("--nonce-hex", help="32-byte hex (optional)")
|
236
|
+
ap.add_argument(
|
237
|
+
"--node-type",
|
238
|
+
choices=["StorageMiner", "Validator", "ComputeMiner", "StorageS3", "GpuMiner"],
|
239
|
+
help="Required for registration functions",
|
240
|
+
)
|
241
|
+
ap.add_argument(
|
242
|
+
"--pay-in-credits", action="store_true", help="Pay in credits for registration"
|
243
|
+
)
|
244
|
+
|
245
|
+
ap.add_argument(
|
246
|
+
"--coldkey",
|
247
|
+
help="Coldkey SS58 address (required for register_node_with_hotkey)",
|
248
|
+
)
|
249
|
+
|
250
|
+
ap.add_argument(
|
251
|
+
"--dry-run",
|
252
|
+
action="store_true",
|
253
|
+
help="Do not submit extrinsic; just print payload",
|
254
|
+
)
|
255
|
+
args = ap.parse_args()
|
256
|
+
|
257
|
+
# Validate function-specific requirements
|
258
|
+
if args.function == "register_node_with_hotkey" and not args.coldkey:
|
259
|
+
raise SystemExit("--coldkey is required for register_node_with_hotkey function")
|
260
|
+
if (
|
261
|
+
args.function in ["register_node_with_coldkey", "register_node_with_hotkey"]
|
262
|
+
and not args.node_type
|
263
|
+
):
|
264
|
+
raise SystemExit("--node-type is required for registration functions")
|
265
|
+
|
266
|
+
# Initialize SubstrateInterface
|
267
|
+
substrate = SubstrateInterface(url=args.ws)
|
268
|
+
|
269
|
+
# Connect & fetch genesis hash
|
270
|
+
genesis_hash_hex = substrate.get_block_hash(0)
|
271
|
+
genesis_hash = bytes.fromhex(genesis_hash_hex[2:])
|
272
|
+
|
273
|
+
# Get current block number from chain
|
274
|
+
try:
|
275
|
+
current_block_number = substrate.get_block_number(None)
|
276
|
+
print(f"Current block number: {current_block_number}")
|
277
|
+
except Exception as e:
|
278
|
+
raise SystemExit(f"Error fetching current block number: {e}")
|
279
|
+
|
280
|
+
# Ensure node_id_bytes is correctly formed
|
281
|
+
if args.node_id.startswith("0x"):
|
282
|
+
node_id_bytes = bytes.fromhex(args.node_id[2:])
|
283
|
+
else:
|
284
|
+
node_id_bytes = base58.b58decode(args.node_id)
|
285
|
+
|
286
|
+
ipfs_seed, peerid_from_config = load_ipfs_seed(args.ipfs_config, args.ipfs_priv_b64)
|
287
|
+
if args.ipfs_peer_id:
|
288
|
+
ipfs_peer_id_bytes = base58.b58decode(args.ipfs_peer_id)
|
289
|
+
elif peerid_from_config:
|
290
|
+
ipfs_peer_id_bytes = base58.b58decode(peerid_from_config)
|
291
|
+
else:
|
292
|
+
if args.node_type in ["StorageMiner", "Validator"] and args.function in [
|
293
|
+
"register_node_with_coldkey",
|
294
|
+
"register_node_with_hotkey",
|
295
|
+
]:
|
296
|
+
raise SystemExit(
|
297
|
+
"IPFS PeerID is required for StorageMiner and Validator node WARRANTtypes."
|
298
|
+
)
|
299
|
+
ipfs_peer_id_bytes = b""
|
300
|
+
|
301
|
+
main_seed = load_main_seed(args.node_priv_hex)
|
302
|
+
|
303
|
+
# Keys & pubkeys
|
304
|
+
main_sk = SigningKey(main_seed)
|
305
|
+
main_pk = bytes(main_sk.verify_key)
|
306
|
+
ipfs_sk = SigningKey(ipfs_seed)
|
307
|
+
ipfs_pk = bytes(ipfs_sk.verify_key)
|
308
|
+
|
309
|
+
# DEBUG: Print what we're working with
|
310
|
+
print(f"Main public key: 0x{main_pk.hex()}")
|
311
|
+
print(f"IPFS public key: 0x{ipfs_pk.hex()}")
|
312
|
+
|
313
|
+
expected_main_peer_id = get_peer_id_from_public_key(main_pk)
|
314
|
+
expected_ipfs_peer_id = get_peer_id_from_public_key(ipfs_pk)
|
315
|
+
print(f"Expected main node ID from private key: {expected_main_peer_id}")
|
316
|
+
print(f"Expected IPFS peer ID from private key: {expected_ipfs_peer_id}")
|
317
|
+
print(f"Provided main node ID: {args.node_id}")
|
318
|
+
print(
|
319
|
+
f"Provided IPFS peer ID: {args.ipfs_peer_id if args.ipfs_peer_id else peerid_from_config}"
|
320
|
+
)
|
321
|
+
|
322
|
+
try:
|
323
|
+
expected_main_pk = get_public_key_from_peer_id(args.node_id)
|
324
|
+
print(f"Expected main public key from node ID: 0x{expected_main_pk.hex()}")
|
325
|
+
except Exception as e:
|
326
|
+
print(f"Error extracting public key from main node ID: {e}")
|
327
|
+
|
328
|
+
try:
|
329
|
+
provided_ipfs_peer_id = (
|
330
|
+
args.ipfs_peer_id if args.ipfs_peer_id else peerid_from_config
|
331
|
+
)
|
332
|
+
expected_ipfs_pk = get_public_key_from_peer_id(provided_ipfs_peer_id)
|
333
|
+
print(f"Expected IPFS public key from peer ID: 0x{expected_ipfs_pk.hex()}")
|
334
|
+
except Exception as e:
|
335
|
+
print(f"Error extracting public key from IPFS peer ID: {e}")
|
336
|
+
|
337
|
+
if not verify_peer_id(main_pk, node_id_bytes, "Ed25519"):
|
338
|
+
raise SystemExit("Main public key does not match node ID")
|
339
|
+
|
340
|
+
if ipfs_peer_id_bytes and not verify_peer_id(
|
341
|
+
ipfs_pk, ipfs_peer_id_bytes, "Ed25519"
|
342
|
+
):
|
343
|
+
raise SystemExit("IPFS public key does not match peer ID")
|
344
|
+
|
345
|
+
# Challenge bytes
|
346
|
+
domain = args.domain.encode()
|
347
|
+
domain24 = b"HIPPIUS::REGISTER::v1" + b"\x00" * 3
|
348
|
+
|
349
|
+
node_id_hash = blake2_256(node_id_bytes)
|
350
|
+
ipfs_peer_id_hash = blake2_256(ipfs_peer_id_bytes)
|
351
|
+
|
352
|
+
nonce = (
|
353
|
+
bytes.fromhex(args.nonce_hex[2:])
|
354
|
+
if args.nonce_hex and args.nonce_hex.startswith("0x")
|
355
|
+
else bytes.fromhex(args.nonce_hex)
|
356
|
+
if args.nonce_hex
|
357
|
+
else secrets.token_bytes(32)
|
358
|
+
)
|
359
|
+
|
360
|
+
expires_at_block = current_block_number + args.expires_in
|
361
|
+
|
362
|
+
account_bytes = encode_account_id(args.owner_ss58)
|
363
|
+
|
364
|
+
challenge_data = {
|
365
|
+
"domain": domain24,
|
366
|
+
"genesis_hash": genesis_hash,
|
367
|
+
"account": account_bytes,
|
368
|
+
"node_id_hash": node_id_hash,
|
369
|
+
"ipfs_peer_id_hash": ipfs_peer_id_hash,
|
370
|
+
"block_number": current_block_number,
|
371
|
+
"nonce": nonce,
|
372
|
+
"expires_at": expires_at_block,
|
373
|
+
}
|
374
|
+
|
375
|
+
try:
|
376
|
+
challenge_bytes = manual_encode_challenge(challenge_data, args.block_width)
|
377
|
+
print(f"Successfully encoded challenge bytes: 0x{challenge_bytes.hex()}")
|
378
|
+
except Exception as e:
|
379
|
+
print(f"Error encoding challenge bytes: {e}")
|
380
|
+
raise
|
381
|
+
|
382
|
+
main_sig = main_sk.sign(challenge_bytes).signature
|
383
|
+
ipfs_sig = ipfs_sk.sign(challenge_bytes).signature
|
384
|
+
|
385
|
+
# Compose call params based on function
|
386
|
+
if args.function == "register_node_with_coldkey":
|
387
|
+
call_params = {
|
388
|
+
"node_type": args.node_type,
|
389
|
+
"node_id": args.node_id,
|
390
|
+
"node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
|
391
|
+
"pay_in_credits": bool(args.pay_in_credits),
|
392
|
+
"ipfs_node_id": args.ipfs_peer_id,
|
393
|
+
"owner": args.owner_ss58,
|
394
|
+
"ipfs_peer_id": args.ipfs_peer_id,
|
395
|
+
"ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
|
396
|
+
"main_key_type": "Ed25519",
|
397
|
+
"main_public_key": "0x" + main_pk.hex(),
|
398
|
+
"main_sig": "0x" + main_sig.hex(),
|
399
|
+
"ipfs_key_type": "Ed25519",
|
400
|
+
"ipfs_public_key": "0x" + ipfs_pk.hex(),
|
401
|
+
"ipfs_sig": "0x" + ipfs_sig.hex(),
|
402
|
+
"challenge_bytes": "0x" + challenge_bytes.hex(),
|
403
|
+
}
|
404
|
+
elif args.function == "register_node_with_hotkey":
|
405
|
+
call_params = {
|
406
|
+
"coldkey": args.coldkey,
|
407
|
+
"node_type": args.node_type,
|
408
|
+
"node_id": args.node_id,
|
409
|
+
"node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
|
410
|
+
"pay_in_credits": bool(args.pay_in_credits),
|
411
|
+
"ipfs_node_id": args.ipfs_peer_id,
|
412
|
+
"ipfs_peer_id": args.ipfs_peer_id,
|
413
|
+
"ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
|
414
|
+
"owner": args.owner_ss58,
|
415
|
+
"main_key_type": "Ed25519",
|
416
|
+
"main_public_key": "0x" + main_pk.hex(),
|
417
|
+
"main_sig": "0x" + main_sig.hex(),
|
418
|
+
"ipfs_key_type": "Ed25519",
|
419
|
+
"ipfs_public_key": "0x" + ipfs_pk.hex(),
|
420
|
+
"ipfs_sig": "0x" + ipfs_sig.hex(),
|
421
|
+
"challenge_bytes": "0x" + challenge_bytes.hex(),
|
422
|
+
}
|
423
|
+
elif args.function in ["verify_existing_node", "verify_existing_coldkey_node"]:
|
424
|
+
call_params = {
|
425
|
+
"node_id": args.node_id,
|
426
|
+
"node_id_hex": "0x" + hexlify(node_id_bytes).decode(),
|
427
|
+
"ipfs_id_hex": "0x" + hexlify(ipfs_peer_id_bytes).decode(),
|
428
|
+
"main_key_type": "Ed25519",
|
429
|
+
"main_public_key": "0x" + main_pk.hex(),
|
430
|
+
"main_sig": "0x" + main_sig.hex(),
|
431
|
+
"ipfs_key_type": "Ed25519",
|
432
|
+
"ipfs_public_key": "0x" + ipfs_pk.hex(),
|
433
|
+
"ipfs_sig": "0x" + ipfs_sig.hex(),
|
434
|
+
"challenge_bytes": "0x" + challenge_bytes.hex(),
|
435
|
+
}
|
436
|
+
|
437
|
+
if args.dry_run:
|
438
|
+
print(
|
439
|
+
json.dumps(
|
440
|
+
{
|
441
|
+
"genesis_hash_hex": "0x" + genesis_hash.hex(),
|
442
|
+
"current_block_number": current_block_number,
|
443
|
+
"challenge_bytes_hex": "0x" + challenge_bytes.hex(),
|
444
|
+
"call_module": args.module,
|
445
|
+
"call_function": args.function,
|
446
|
+
"call_params": call_params,
|
447
|
+
},
|
448
|
+
indent=2,
|
449
|
+
)
|
450
|
+
)
|
451
|
+
return
|
452
|
+
|
453
|
+
try:
|
454
|
+
from substrateinterface import KeypairType
|
455
|
+
|
456
|
+
crypto_arg = (
|
457
|
+
KeypairType.SR25519
|
458
|
+
if args.owner_crypto == "sr25519"
|
459
|
+
else KeypairType.ED25519
|
460
|
+
)
|
461
|
+
kp = Keypair.create_from_uri(args.owner_uri, crypto_type=crypto_arg)
|
462
|
+
except Exception as e:
|
463
|
+
print(
|
464
|
+
f"Error creating keypair with specified type: {e}. Falling back to default sr25519."
|
465
|
+
)
|
466
|
+
kp = Keypair.create_from_uri(args.owner_uri)
|
467
|
+
|
468
|
+
call = substrate.compose_call(
|
469
|
+
call_module=args.module, call_function=args.function, call_params=call_params
|
470
|
+
)
|
471
|
+
extrinsic = substrate.create_signed_extrinsic(call=call, keypair=kp)
|
472
|
+
receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
|
473
|
+
print(
|
474
|
+
json.dumps(
|
475
|
+
{
|
476
|
+
"extrinsic_hash": receipt.extrinsic_hash,
|
477
|
+
"is_success": receipt.is_success,
|
478
|
+
"error_message": receipt.error_message,
|
479
|
+
"triggered_events": [str(event) for event in receipt.triggered_events],
|
480
|
+
},
|
481
|
+
indent=2,
|
482
|
+
)
|
483
|
+
)
|
484
|
+
|
485
|
+
|
486
|
+
if __name__ == "__main__":
|
487
|
+
main()
|
File without changes
|
File without changes
|