Skip to content

Commit

Permalink
add recovery API and CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffwalmsley committed May 20, 2024
1 parent d4edcca commit 7acf624
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 15 deletions.
123 changes: 121 additions & 2 deletions chia/cmds/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import asyncio
from decimal import Decimal
from typing import Optional
from typing import Optional, Sequence

import click

Expand Down Expand Up @@ -66,6 +66,34 @@ def vault_cmd(ctx: click.Context) -> None:
callback=validate_fee,
)
@click.option("-n", "--name", help="Set the vault name", type=str)
@click.option(
"-ma",
"--min-coin-amount",
help="Ignore coins worth less then this much XCH or CAT units",
type=str,
required=False,
default="0",
)
@click.option(
"-l",
"--max-coin-amount",
help="Ignore coins worth more then this much XCH or CAT units",
type=str,
required=False,
default=None,
)
@click.option(
"--exclude-coin",
"coins_to_exclude",
multiple=True,
help="Exclude this coin from being spent.",
)
@click.option(
"--reuse",
help="Reuse existing address for the change.",
is_flag=True,
default=False,
)
def vault_create_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
Expand All @@ -75,6 +103,10 @@ def vault_create_cmd(
hidden_puzzle_index: Optional[int],
fee: str,
name: Optional[str],
min_coin_amount: str,
max_coin_amount: Optional[str],
coins_to_exclude: Sequence[str],
reuse: bool,
) -> None:
from .vault_funcs import create_vault

Expand All @@ -91,6 +123,10 @@ def vault_create_cmd(
hidden_puzzle_index,
Decimal(fee),
name,
min_coin_amount=min_coin_amount,
max_coin_amount=max_coin_amount,
excluded_coin_ids=coins_to_exclude,
reuse_puzhash=True if reuse else None,
)
)

Expand All @@ -105,6 +141,37 @@ def vault_create_cmd(
)
@options.create_fingerprint()
@click.option("-i", "--wallet-id", help="Vault Wallet ID", type=int, required=True, default=1)
@click.option(
"-pk",
"--public-key",
help="SECP public key",
type=str,
required=True,
)
@click.option(
"-i",
"--hidden-puzzle-index",
help="Starting index for hidden puzzle",
type=int,
required=False,
default=0,
)
@click.option(
"-rk",
"--recovery-public-key",
help="BLS public key for vault recovery",
type=str,
required=False,
default=None,
)
@click.option(
"-rt",
"--recovery-timelock",
help="Timelock for vault recovery (in seconds)",
type=int,
required=False,
default=None,
)
@click.option(
"-ri",
"--recovery-initiate-file",
Expand All @@ -121,13 +188,65 @@ def vault_create_cmd(
required=True,
default="finish_recovery.json",
)
@click.option(
"-ma",
"--min-coin-amount",
help="Ignore coins worth less then this much XCH or CAT units",
type=str,
required=False,
default="0",
)
@click.option(
"-l",
"--max-coin-amount",
help="Ignore coins worth more then this much XCH or CAT units",
type=str,
required=False,
default=None,
)
@click.option(
"--exclude-coin",
"coins_to_exclude",
multiple=True,
help="Exclude this coin from being spent.",
)
@click.option(
"--reuse",
help="Reuse existing address for the change.",
is_flag=True,
default=False,
)
def vault_recover_cmd(
wallet_rpc_port: Optional[int],
fingerprint: int,
wallet_id: int,
public_key: str,
hidden_puzzle_index: int,
recovery_public_key: Optional[str],
recovery_timelock: Optional[int],
recovery_initiate_file: str,
recovery_finish_file: str,
min_coin_amount: str,
max_coin_amount: Optional[str],
coins_to_exclude: Sequence[str],
reuse: bool,
) -> None:
from .vault_funcs import recover_vault

asyncio.run(recover_vault(wallet_rpc_port, fingerprint, wallet_id, recovery_initiate_file, recovery_finish_file))
asyncio.run(
recover_vault(
wallet_rpc_port,
fingerprint,
wallet_id,
public_key,
hidden_puzzle_index,
recovery_public_key,
recovery_timelock,
recovery_initiate_file,
recovery_finish_file,
min_coin_amount=min_coin_amount,
max_coin_amount=max_coin_amount,
excluded_coin_ids=coins_to_exclude,
reuse_puzhash=True if reuse else None,
)
)
48 changes: 43 additions & 5 deletions chia/cmds/vault_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import json
from decimal import Decimal
from typing import Optional
from typing import Optional, Sequence

from chia.cmds.cmds_util import get_wallet_client
from chia.cmds.cmds_util import CMDTXConfigLoader, get_wallet_client
from chia.cmds.units import units
from chia.util.ints import uint32, uint64

Expand All @@ -18,17 +18,27 @@ async def create_vault(
hidden_puzzle_index: int,
d_fee: Decimal,
name: Optional[str],
min_coin_amount: Optional[str],
max_coin_amount: Optional[str],
excluded_coin_ids: Sequence[str],
reuse_puzhash: Optional[bool],
) -> None:
async with get_wallet_client(wallet_rpc_port, fingerprint) as (wallet_client, fingerprint, config):
fee: int = int(d_fee * units["chia"])
assert hidden_puzzle_index >= 0
tx_config = CMDTXConfigLoader(
min_coin_amount=min_coin_amount,
max_coin_amount=max_coin_amount,
excluded_coin_ids=list(excluded_coin_ids),
reuse_puzhash=reuse_puzhash,
).to_tx_config(units["chia"], config, fingerprint)
if timelock is not None:
assert timelock > 0
try:
await wallet_client.vault_create(
bytes.fromhex(public_key),
uint32(hidden_puzzle_index),
config,
tx_config,
bytes.fromhex(recovery_public_key) if recovery_public_key else None,
uint64(timelock) if timelock else None,
uint64(fee),
Expand All @@ -40,11 +50,39 @@ async def create_vault(


async def recover_vault(
wallet_rpc_port: Optional[int], fingerprint: Optional[int], wallet_id: int, initiate_file: str, finish_file: str
wallet_rpc_port: Optional[int],
fingerprint: Optional[int],
wallet_id: int,
public_key: str,
hidden_puzzle_index: int,
recovery_public_key: Optional[str],
timelock: Optional[int],
initiate_file: str,
finish_file: str,
min_coin_amount: Optional[str],
max_coin_amount: Optional[str],
excluded_coin_ids: Sequence[str],
reuse_puzhash: Optional[bool],
) -> None:
async with get_wallet_client(wallet_rpc_port, fingerprint) as (wallet_client, fingerprint, config):
assert hidden_puzzle_index >= 0
if timelock is not None:
assert timelock > 0
tx_config = CMDTXConfigLoader(
min_coin_amount=min_coin_amount,
max_coin_amount=max_coin_amount,
excluded_coin_ids=list(excluded_coin_ids),
reuse_puzhash=reuse_puzhash,
).to_tx_config(units["chia"], config, fingerprint)
try:
response = await wallet_client.vault_recovery(uint32(wallet_id))
response = await wallet_client.vault_recovery(
uint32(wallet_id),
bytes.fromhex(public_key),
uint32(hidden_puzzle_index),
tx_config,
bytes.fromhex(recovery_public_key) if recovery_public_key else None,
uint64(timelock) if timelock else None,
)
with open(initiate_file, "w") as f:
json.dump(response[0].to_json_dict(), f, indent=4)
print(f"Initiate Recovery transaction written to: {initiate_file}")
Expand Down
14 changes: 12 additions & 2 deletions chia/rpc/wallet_rpc_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4620,11 +4620,21 @@ async def vault_create(
"transactions": [vault_record.to_json_dict_convenience(self.service.config)],
}

async def vault_recovery(self, request: Dict[str, Any]) -> EndpointResult:
async def vault_recovery(self, request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG) -> EndpointResult:
"""
Initiate Vault Recovery
"""
wallet_id = uint32(request["wallet_id"])
wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=Vault)
recovery_txs = await wallet.create_recovery_spends()
secp_pk = bytes.fromhex(str(request.get("secp_pk")))
hp_index = request.get("hp_index", 0)
hidden_puzzle_hash = get_vault_hidden_puzzle_with_index(hp_index).get_tree_hash()
bls_str = request.get("bls_pk")
bls_pk = G1Element.from_bytes(bytes.fromhex(str(bls_str))) if bls_str else None
timelock_int = request.get("timelock")
timelock = uint64(timelock_int) if timelock_int else None
genesis_challenge = DEFAULT_CONSTANTS.GENESIS_CHALLENGE
recovery_txs = await wallet.create_recovery_spends(
secp_pk, hidden_puzzle_hash, genesis_challenge, tx_config, bls_pk=bls_pk, timelock=timelock
)
return {"transactions": [tx.to_json_dict_convenience(self.service.config) for tx in recovery_txs]}
18 changes: 16 additions & 2 deletions chia/rpc/wallet_rpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1674,9 +1674,23 @@ async def vault_create(
)
return [TransactionRecord.from_json_dict_convenience(tx) for tx in response["transactions"]]

async def vault_recovery(self, wallet_id: uint32) -> List[TransactionRecord]:
async def vault_recovery(
self,
wallet_id: uint32,
secp_pk: bytes,
hp_index: uint32,
tx_config: TXConfig,
bls_pk: Optional[bytes] = None,
timelock: Optional[uint64] = None,
) -> List[TransactionRecord]:
response = await self.fetch(
"vault_recovery",
{"wallet_id": wallet_id},
{
"wallet_id": wallet_id,
"secp_pk": secp_pk.hex(),
"hp_index": hp_index,
"bls_pk": bls_pk.hex() if bls_pk else None,
"timelock": timelock,
},
)
return [TransactionRecord.from_json_dict_convenience(tx) for tx in response["transactions"]]
25 changes: 21 additions & 4 deletions tests/cmds/wallet/test_vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from typing import List, Optional, Tuple

from chia_rs import Coin, G2Element
from chia_rs import Coin, G1Element, G2Element

from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint8, uint32, uint64
Expand Down Expand Up @@ -55,9 +55,9 @@ async def vault_create(
test_rpc_clients.wallet_rpc_client = inst_rpc_client
pk = get_bytes32(0).hex()
recovery_pk = get_bytes32(1).hex()
timelock = 100
hidden_puzzle_index = 10
fee = 0.1
timelock = "100"
hidden_puzzle_index = "10"
fee = "0.1"
command_args = [
"vault",
"create",
Expand All @@ -84,6 +84,11 @@ class CreateVaultRpcClient(TestWalletRpcClient):
async def vault_recovery(
self,
wallet_id: uint32,
secp_pk: bytes,
hp_index: uint32,
tx_config: TXConfig,
bls_pk: Optional[G1Element] = None,
timelock: Optional[uint64] = None,
) -> List[TransactionRecord]:
tx_rec = TransactionRecord(
confirmed_at_height=uint32(1),
Expand All @@ -108,9 +113,21 @@ async def vault_recovery(

inst_rpc_client = CreateVaultRpcClient() # pylint: disable=no-value-for-parameter
test_rpc_clients.wallet_rpc_client = inst_rpc_client
pk = get_bytes32(0).hex()
recovery_pk = get_bytes32(1).hex()
timelock = "100"
hidden_puzzle_index = "10"
command_args = [
"vault",
"recover",
"-pk",
pk,
"-rk",
recovery_pk,
"-rt",
timelock,
"-i",
hidden_puzzle_index,
"-ri",
"recovery_init.json",
"-rf",
Expand Down

0 comments on commit 7acf624

Please sign in to comment.