[CHIA-3251] Delete DID recovery endpoints (#19767)

* Port `did_set_wallet_name`

* Port `did_get_wallet_name`

* Port `did_update_recovery_ids`

* Port `did_message_spend`

* Port `did_get_info`

* Port `did_find_lost_did`

* fix test

* Port `did_update_metadata`

* Port `did_get_did`

* Port `did_get_recovery_list`

* Port `did_get_metadata`

* Port `did_get_pubkey`

* Port `did_get_information_needed_for_recovery`

* Port `did_get_current_coin_info`

* Port `did_create_backup_file`

* Port `did_transfer_did`

* Add extra_conditions uniformly in CLI tests

* test coverage

* Delete DID recovery endpoints

* Delete `create_exit_spend` as well

* Missed one
This commit is contained in:
Matt Hauff
2025-07-18 09:11:15 -07:00
committed by GitHub
parent 13917fcb89
commit 191ccde774
9 changed files with 46 additions and 1727 deletions
File diff suppressed because it is too large Load Diff
@@ -1284,7 +1284,7 @@ async def test_nft_transfer_nft_with_did(wallet_environments: WalletTestFramewor
async with did_wallet.wallet_state_manager.new_action_scope(
wallet_environments.tx_config, push=True
) as action_scope:
await did_wallet.transfer_did(wallet_1_ph, uint64(0), True, action_scope)
await did_wallet.transfer_did(wallet_1_ph, uint64(0), action_scope)
await wallet_environments.process_pending_states(
[
-16
View File
@@ -111,13 +111,11 @@ from chia.wallet.wallet_request_types import (
DIDGetDID,
DIDGetMetadata,
DIDGetPubkey,
DIDGetRecoveryList,
DIDGetWalletName,
DIDMessageSpend,
DIDSetWalletName,
DIDTransferDID,
DIDUpdateMetadata,
DIDUpdateRecoveryIDs,
FungibleAsset,
GetNotifications,
GetPrivateKey,
@@ -1538,20 +1536,6 @@ async def test_did_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment) -
# Create backup file
await wallet_1_rpc.create_did_backup_file(DIDCreateBackupFile(did_wallet_id_0))
await time_out_assert(5, check_mempool_spend_count, True, full_node_api, 1)
await farm_transaction_block(full_node_api, wallet_1_node)
# Update recovery list
update_res = await wallet_1_rpc.update_did_recovery_list(
DIDUpdateRecoveryIDs(
wallet_id=uint32(did_wallet_id_0), new_list=[did_id_0], num_verifications_required=uint64(1), push=True
),
DEFAULT_TX_CONFIG,
)
assert len(update_res.transactions) > 0
recovery_list_res = await wallet_1_rpc.get_did_recovery_list(DIDGetRecoveryList(did_wallet_id_0))
assert recovery_list_res.num_required == 1
assert recovery_list_res.recovery_list[0] == did_id_0
await time_out_assert(5, check_mempool_spend_count, True, full_node_api, 1)
await farm_transaction_block(full_node_api, wallet_1_node)
@@ -753,7 +753,7 @@ async def test_self_revoke(wallet_environments: WalletTestFramework) -> None:
async with did_wallet.wallet_state_manager.new_action_scope(
wallet_environments.tx_config, push=True
) as action_scope:
await did_wallet.transfer_did(bytes32.zeros, uint64(0), False, action_scope)
await did_wallet.transfer_did(bytes32.zeros, uint64(0), action_scope)
await wallet_environments.process_pending_states(
[
+6 -312
View File
@@ -78,8 +78,6 @@ class DIDWallet:
wallet: Wallet,
amount: uint64,
action_scope: WalletActionScope,
backups_ids: list[bytes32] = [],
num_of_backup_ids_needed: uint64 = None,
metadata: dict[str, str] = {},
name: Optional[str] = None,
fee: uint64 = uint64(0),
@@ -114,14 +112,10 @@ class DIDWallet:
if amount & 1 == 0:
raise ValueError("DID amount must be odd number")
if num_of_backup_ids_needed is None:
num_of_backup_ids_needed = uint64(len(backups_ids))
if num_of_backup_ids_needed > len(backups_ids):
raise ValueError("Cannot require more IDs than are known.")
self.did_info = DIDInfo(
origin_coin=None,
backup_ids=backups_ids,
num_of_backup_ids_needed=num_of_backup_ids_needed,
backup_ids=[],
num_of_backup_ids_needed=uint64(0),
parent_info=[],
current_inner=None,
temp_coin=None,
@@ -229,6 +223,7 @@ class DIDWallet:
recovery_list: list[bytes32] = []
backup_required: int = num_verification.as_int()
if not did_recovery_is_nil(recovery_list_hash):
self.log.warning(f"DID {launch_coin.name().hex()} has a recovery list hash which has been deprecated.")
try:
for did in inner_solution.rest().rest().rest().rest().rest().as_python():
recovery_list.append(bytes32(did[0]))
@@ -682,7 +677,6 @@ class DIDWallet:
self,
new_puzhash: bytes32,
fee: uint64,
with_recovery: bool,
action_scope: WalletActionScope,
extra_conditions: tuple[Condition, ...] = tuple(),
) -> None:
@@ -690,7 +684,6 @@ class DIDWallet:
Transfer the current DID to another owner
:param new_puzhash: New owner's p2_puzzle
:param fee: Transaction fee
:param with_recovery: A boolean indicates if the recovery info will be sent through the blockchain
:return: Spend bundle
"""
assert self.did_info.current_inner is not None
@@ -698,9 +691,8 @@ class DIDWallet:
coin = await self.get_coin()
backup_ids = []
backup_required = uint64(0)
if with_recovery:
backup_ids = self.did_info.backup_ids
backup_required = self.did_info.num_of_backup_ids_needed
backup_ids = self.did_info.backup_ids
backup_required = self.did_info.num_of_backup_ids_needed
new_did_puzhash = did_wallet_puzzles.get_inner_puzhash_by_p2(
p2_puzhash=new_puzhash,
recovery_list=backup_ids,
@@ -713,12 +705,7 @@ class DIDWallet:
primaries=[CreateCoin(new_did_puzhash, uint64(coin.amount), [new_puzhash])],
conditions=(*extra_conditions, CreateCoinAnnouncement(coin.name())),
)
# Need to include backup list reveal here, even we are don't recover
# innerpuz solution is
# (mode, p2_solution)
innersol: Program = Program.to([2, p2_solution])
if with_recovery:
innersol = Program.to([2, p2_solution, [], [], [], self.did_info.backup_ids])
innersol = Program.to([2, p2_solution, [], [], [], self.did_info.backup_ids])
# full solution is (corehash parent_info my_amount innerpuz_reveal solution)
full_puzzle: Program = create_singleton_puzzle(
@@ -850,280 +837,6 @@ class DIDWallet:
async with action_scope.use() as interface:
interface.side_effects.transactions.append(tx)
# This is used to cash out, or update the id_list
async def create_exit_spend(self, puzhash: bytes32, action_scope: WalletActionScope) -> None:
assert self.did_info.current_inner is not None
assert self.did_info.origin_coin is not None
coin = await self.get_coin()
message_puz = Program.to((1, [[51, puzhash, coin.amount - 1, [puzhash]], [51, 0x00, -113]]))
# innerpuz solution is (mode p2_solution)
innersol: Program = Program.to([1, [[], message_puz, []]])
# full solution is (corehash parent_info my_amount innerpuz_reveal solution)
innerpuz: Program = self.did_info.current_inner
full_puzzle: Program = create_singleton_puzzle(
innerpuz,
self.did_info.origin_coin.name(),
)
parent_info = self.get_parent_for_coin(coin)
assert parent_info is not None
fullsol = Program.to(
[
[
parent_info.parent_name,
parent_info.inner_puzzle_hash,
parent_info.amount,
],
coin.amount,
innersol,
]
)
list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)]
spend_bundle = WalletSpendBundle(list_of_coinspends, G2Element())
async with action_scope.use() as interface:
interface.side_effects.transactions.append(
TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=await action_scope.get_puzzle_hash(
self.wallet_state_manager, override_reuse_puzhash_with=True
),
amount=uint64(coin.amount),
fee_amount=uint64(0),
confirmed=False,
sent=uint32(0),
spend_bundle=spend_bundle,
additions=spend_bundle.additions(),
removals=spend_bundle.removals(),
wallet_id=self.wallet_info.id,
sent_to=[],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32.secret(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=ConditionValidTimes(),
)
)
# Pushes a spend bundle to create a message coin on the blockchain
# Returns a spend bundle for the recoverer to spend the message coin
async def create_attestment(
self,
recovering_coin_name: bytes32,
newpuz: bytes32,
pubkey: G1Element,
action_scope: WalletActionScope,
extra_conditions: tuple[Condition, ...] = tuple(),
) -> tuple[WalletSpendBundle, str]:
"""
Create an attestment
TODO:
1. We should use/respect `action_scope.config.tx_config` (reuse_puzhash and co)
2. We should take a fee as it's a requirement for every transaction function to do so
:param recovering_coin_name: Coin ID of the DID
:param newpuz: New puzzle hash
:param pubkey: New wallet pubkey
:return: (Spend bundle, attest string)
"""
assert self.did_info.current_inner is not None
assert self.did_info.origin_coin is not None
coin = await self.get_coin()
message = did_wallet_puzzles.create_recovery_message_puzzle(recovering_coin_name, newpuz, pubkey)
innermessage = message.get_tree_hash()
innerpuz: Program = self.did_info.current_inner
uncurried = did_wallet_puzzles.uncurry_innerpuz(innerpuz)
assert uncurried is not None
p2_puzzle = uncurried[0]
# innerpuz solution is (mode, p2_solution)
p2_solution = self.standard_wallet.make_solution(
primaries=[
CreateCoin(innerpuz.get_tree_hash(), uint64(coin.amount), [p2_puzzle.get_tree_hash()]),
CreateCoin(innermessage, uint64(0)),
],
conditions=extra_conditions,
)
innersol = Program.to([1, p2_solution])
# full solution is (corehash parent_info my_amount innerpuz_reveal solution)
full_puzzle: Program = create_singleton_puzzle(
innerpuz,
self.did_info.origin_coin.name(),
)
parent_info = self.get_parent_for_coin(coin)
assert parent_info is not None
fullsol = Program.to(
[
[
parent_info.parent_name,
parent_info.inner_puzzle_hash,
parent_info.amount,
],
coin.amount,
innersol,
]
)
list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)]
message_spend = did_wallet_puzzles.create_spend_for_message(coin.name(), recovering_coin_name, newpuz, pubkey)
message_spend_bundle = WalletSpendBundle([message_spend], AugSchemeMPL.aggregate([]))
spend_bundle = WalletSpendBundle(list_of_coinspends, G2Element())
did_record = TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=await action_scope.get_puzzle_hash(
self.wallet_state_manager, override_reuse_puzhash_with=True
),
amount=uint64(coin.amount),
fee_amount=uint64(0),
confirmed=False,
sent=uint32(0),
spend_bundle=spend_bundle,
additions=spend_bundle.additions(),
removals=spend_bundle.removals(),
wallet_id=self.wallet_info.id,
sent_to=[],
trade_id=None,
type=uint32(TransactionType.INCOMING_TX.value),
name=bytes32.secret(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
async with action_scope.use() as interface:
interface.side_effects.transactions.append(did_record)
attest_str: str = f"{self.get_my_DID()}:{bytes(message_spend_bundle).hex()}:{coin.parent_coin_info.hex()}:"
attest_str += f"{self.did_info.current_inner.get_tree_hash().hex()}:{coin.amount}"
return message_spend_bundle, attest_str
async def get_info_for_recovery(self) -> Optional[tuple[bytes32, bytes32, uint64]]:
assert self.did_info.current_inner is not None
assert self.did_info.origin_coin is not None
try:
coin = await self.get_coin()
except RuntimeError:
return None
parent = coin.parent_coin_info
innerpuzhash = self.did_info.current_inner.get_tree_hash()
amount = uint64(coin.amount)
return (parent, innerpuzhash, amount)
async def load_attest_files_for_recovery_spend(self, attest_data: list[str]) -> tuple[list, WalletSpendBundle]:
spend_bundle_list = []
info_dict = {}
for attest in attest_data:
info = attest.split(":")
info_dict[info[0]] = [
bytes.fromhex(info[2]),
bytes.fromhex(info[3]),
uint64(info[4]),
]
new_sb = WalletSpendBundle.from_bytes(bytes.fromhex(info[1]))
spend_bundle_list.append(new_sb)
# info_dict {0xidentity: "(0xparent_info 0xinnerpuz amount)"}
my_recovery_list: list[bytes32] = self.did_info.backup_ids
# convert info dict into recovery list - same order as wallet
info_list = []
for entry in my_recovery_list:
if entry.hex() in info_dict:
info_list.append(
[
info_dict[entry.hex()][0],
info_dict[entry.hex()][1],
info_dict[entry.hex()][2],
]
)
else:
info_list.append([])
message_spend_bundle = WalletSpendBundle.aggregate(spend_bundle_list)
return info_list, message_spend_bundle
async def recovery_spend(
self,
coin: Coin,
puzhash: bytes32,
parent_innerpuzhash_amounts_for_recovery_ids: list[tuple[bytes, bytes, int]],
pubkey: G1Element,
spend_bundle: WalletSpendBundle,
action_scope: WalletActionScope,
) -> None:
assert self.did_info.origin_coin is not None
# innersol is mode new_amount_or_p2_solution new_inner_puzhash parent_innerpuzhash_amounts_for_recovery_ids pubkey recovery_list_reveal my_id) # noqa
innersol: Program = Program.to(
[
0,
coin.amount,
puzhash,
parent_innerpuzhash_amounts_for_recovery_ids,
bytes(pubkey),
self.did_info.backup_ids,
coin.name(),
]
)
# full solution is (parent_info my_amount solution)
assert self.did_info.current_inner is not None
innerpuz: Program = self.did_info.current_inner
full_puzzle: Program = create_singleton_puzzle(
innerpuz,
self.did_info.origin_coin.name(),
)
parent_info = self.get_parent_for_coin(coin)
assert parent_info is not None
fullsol = Program.to(
[
[
parent_info.parent_name,
parent_info.inner_puzzle_hash,
parent_info.amount,
],
coin.amount,
innersol,
]
)
list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)]
spend_bundle = spend_bundle.aggregate([spend_bundle, WalletSpendBundle(list_of_coinspends, G2Element())])
async with action_scope.use() as interface:
interface.side_effects.transactions.append(
TransactionRecord(
confirmed_at_height=uint32(0),
created_at_time=uint64(int(time.time())),
to_puzzle_hash=await action_scope.get_puzzle_hash(
self.wallet_state_manager, override_reuse_puzhash_with=True
),
amount=uint64(coin.amount),
fee_amount=uint64(0),
confirmed=False,
sent=uint32(0),
spend_bundle=spend_bundle,
additions=spend_bundle.additions(),
removals=spend_bundle.removals(),
wallet_id=self.wallet_info.id,
sent_to=[],
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32.secret(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=ConditionValidTimes(),
)
)
new_did_info = DIDInfo(
origin_coin=self.did_info.origin_coin,
backup_ids=self.did_info.backup_ids,
num_of_backup_ids_needed=self.did_info.num_of_backup_ids_needed,
parent_info=self.did_info.parent_info,
current_inner=self.did_info.current_inner,
temp_coin=self.did_info.temp_coin,
temp_puzhash=self.did_info.temp_puzhash,
temp_pubkey=self.did_info.temp_pubkey,
sent_recovery_transaction=True,
metadata=self.did_info.metadata,
)
await self.save_info(new_did_info)
async def get_did_innerpuz(
self,
action_scope: WalletActionScope,
@@ -1395,25 +1108,6 @@ class DIDWallet:
)
await self.save_info(did_info)
async def update_recovery_list(self, recover_list: list[bytes32], num_of_backup_ids_needed: uint64) -> bool:
if num_of_backup_ids_needed > len(recover_list):
return False
did_info = DIDInfo(
origin_coin=self.did_info.origin_coin,
backup_ids=recover_list,
num_of_backup_ids_needed=num_of_backup_ids_needed,
parent_info=self.did_info.parent_info,
current_inner=self.did_info.current_inner,
temp_coin=self.did_info.temp_coin,
temp_puzhash=self.did_info.temp_puzhash,
temp_pubkey=self.did_info.temp_pubkey,
sent_recovery_transaction=self.did_info.sent_recovery_transaction,
metadata=self.did_info.metadata,
)
await self.save_info(did_info)
await self.wallet_state_manager.update_wallet_puzzle_hashes(self.wallet_info.id)
return True
async def update_metadata(self, metadata: dict[str, str]) -> bool:
# validate metadata
if not all(isinstance(k, str) and isinstance(v, str) for k, v in metadata.items()):
+4 -6
View File
@@ -400,11 +400,6 @@ class VCWallet:
)
return
recovery_info: Optional[tuple[bytes32, bytes32, uint64]] = await did_wallet.get_info_for_recovery()
if recovery_info is None:
raise RuntimeError("DID could not currently be accessed while trying to revoke VC") # pragma: no cover
_, provider_inner_puzhash, _ = recovery_info
# Generate spend specific nonce
coins = {await did_wallet.get_coin()}
coins.add(vc.coin)
@@ -421,7 +416,10 @@ class VCWallet:
)
# Assemble final bundle
expected_did_announcement, vc_spend = vc.activate_backdoor(provider_inner_puzhash, announcement_nonce=nonce)
assert did_wallet.did_info.current_inner is not None
expected_did_announcement, vc_spend = vc.activate_backdoor(
did_wallet.did_info.current_inner.get_tree_hash(), announcement_nonce=nonce
)
await did_wallet.create_message_spend(
action_scope,
extra_conditions=(*extra_conditions, expected_did_announcement, vc_announcement),
+5 -45
View File
@@ -389,23 +389,6 @@ class DIDGetPubkeyResponse(Streamable):
pubkey: G1Element
@streamable
@dataclass(frozen=True)
class DIDGetRecoveryInfo(Streamable):
wallet_id: uint32
@streamable
@dataclass(frozen=True)
class DIDGetRecoveryInfoResponse(Streamable):
wallet_id: uint32
my_did: str
coin_name: bytes32
newpuzhash: Optional[bytes32]
pubkey: Optional[G1Element]
backup_dids: list[bytes32]
@streamable
@dataclass(frozen=True)
class DIDGetCurrentCoinInfo(Streamable):
@@ -449,20 +432,6 @@ class DIDGetDIDResponse(Streamable):
coin_id: Optional[bytes32] = None
@streamable
@dataclass(frozen=True)
class DIDGetRecoveryList(Streamable):
wallet_id: uint32
@streamable
@dataclass(frozen=True)
class DIDGetRecoveryListResponse(Streamable):
wallet_id: uint32
recovery_list: list[str]
num_required: uint16
@streamable
@dataclass(frozen=True)
class DIDGetMetadata(Streamable):
@@ -999,20 +968,6 @@ class CombineCoinsResponse(TransactionEndpointResponse):
pass
@streamable
@kw_only_dataclass
class DIDUpdateRecoveryIDs(TransactionEndpointRequest):
wallet_id: uint32 = field(default_factory=default_raise)
new_list: list[str] = field(default_factory=default_raise)
num_verifications_required: Optional[uint64] = None
@streamable
@dataclass(frozen=True)
class DIDUpdateRecoveryIDsResponse(TransactionEndpointResponse):
pass
@streamable
@kw_only_dataclass
class DIDMessageSpend(TransactionEndpointRequest):
@@ -1048,6 +1003,11 @@ class DIDTransferDID(TransactionEndpointRequest):
inner_address: str = field(default_factory=default_raise)
with_recovery_info: bool = True
def __post_init__(self) -> None:
if self.with_recovery_info is False:
raise ValueError("Recovery related options are no longer supported. `with_recovery` must always be true.")
return super().__post_init__()
@streamable
@dataclass(frozen=True)
+11 -159
View File
@@ -132,10 +132,6 @@ from chia.wallet.wallet_request_types import (
DIDGetMetadataResponse,
DIDGetPubkey,
DIDGetPubkeyResponse,
DIDGetRecoveryInfo,
DIDGetRecoveryInfoResponse,
DIDGetRecoveryList,
DIDGetRecoveryListResponse,
DIDGetWalletName,
DIDGetWalletNameResponse,
DIDMessageSpend,
@@ -146,8 +142,6 @@ from chia.wallet.wallet_request_types import (
DIDTransferDIDResponse,
DIDUpdateMetadata,
DIDUpdateMetadataResponse,
DIDUpdateRecoveryIDs,
DIDUpdateRecoveryIDsResponse,
DLDeleteMirror,
DLDeleteMirrorResponse,
DLGetMirrors,
@@ -556,15 +550,10 @@ class WalletRpcApi:
# DID Wallet
"/did_set_wallet_name": self.did_set_wallet_name,
"/did_get_wallet_name": self.did_get_wallet_name,
"/did_update_recovery_ids": self.did_update_recovery_ids,
"/did_update_metadata": self.did_update_metadata,
"/did_get_pubkey": self.did_get_pubkey,
"/did_get_did": self.did_get_did,
"/did_recovery_spend": self.did_recovery_spend,
"/did_get_recovery_list": self.did_get_recovery_list,
"/did_get_metadata": self.did_get_metadata,
"/did_create_attest": self.did_create_attest,
"/did_get_information_needed_for_recovery": self.did_get_information_needed_for_recovery,
"/did_get_current_coin_info": self.did_get_current_coin_info,
"/did_create_backup_file": self.did_create_backup_file,
"/did_transfer_did": self.did_transfer_did,
@@ -1112,12 +1101,8 @@ class WalletRpcApi:
elif request["wallet_type"] == "did_wallet":
if request["did_type"] == "new":
backup_dids = []
num_needed = 0
for d in request["backup_dids"]:
backup_dids.append(decode_puzzle_hash(d))
if len(backup_dids) > 0:
num_needed = uint64(request["num_of_backup_ids_needed"])
if "backup_dids" in request and request["backup_dids"] != []:
raise ValueError("Recovery options are no longer supported. `backup_dids` cannot be set.")
metadata: dict[str, str] = {}
if "metadata" in request:
if type(request["metadata"]) is dict:
@@ -1132,8 +1117,6 @@ class WalletRpcApi:
main_wallet,
uint64(request["amount"]),
action_scope,
backup_dids,
uint64(num_needed),
metadata,
did_wallet_name,
uint64(request.get("fee", 0)),
@@ -2591,31 +2574,6 @@ class WalletRpcApi:
wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
return DIDGetWalletNameResponse(request.wallet_id, wallet.get_name())
@tx_endpoint(push=True)
@marshal
async def did_update_recovery_ids(
self,
request: DIDUpdateRecoveryIDs,
action_scope: WalletActionScope,
extra_conditions: tuple[Condition, ...] = tuple(),
) -> DIDUpdateRecoveryIDsResponse:
wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
recovery_list = [decode_puzzle_hash(puzzle_hash) for puzzle_hash in request.new_list]
new_amount_verifications_required = (
request.num_verifications_required
if request.num_verifications_required is not None
else uint64(len(recovery_list))
)
async with self.service.wallet_state_manager.lock:
update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required)
# Update coin with new ID info
if update_success:
await wallet.create_update_spend(action_scope, fee=request.fee, extra_conditions=extra_conditions)
# tx_endpoint will take care of default values here
return DIDUpdateRecoveryIDsResponse([], [])
else:
raise RuntimeError("updating recovery list failed")
@tx_endpoint(push=False)
@marshal
async def did_message_spend(
@@ -2910,68 +2868,14 @@ class WalletRpcApi:
except RuntimeError:
return DIDGetDIDResponse(wallet_id=request.wallet_id, my_did=my_did)
@marshal
async def did_get_recovery_list(self, request: DIDGetRecoveryList) -> DIDGetRecoveryListResponse:
wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
recovery_list = wallet.did_info.backup_ids
recovery_dids = []
for backup_id in recovery_list:
recovery_dids.append(encode_puzzle_hash(backup_id, AddressType.DID.hrp(self.service.config)))
return DIDGetRecoveryListResponse(
wallet_id=request.wallet_id,
recovery_list=recovery_dids,
num_required=uint16(wallet.did_info.num_of_backup_ids_needed),
)
@marshal
async def did_get_metadata(self, request: DIDGetMetadata) -> DIDGetMetadataResponse:
wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
metadata = json.loads(wallet.did_info.metadata)
return DIDGetMetadataResponse(wallet_id=request.wallet_id, metadata=metadata)
# TODO: this needs a test
# Don't need full @tx_endpoint decorator here, but "push" is still a valid option
async def did_recovery_spend(self, request: dict[str, Any]) -> EndpointResult: # pragma: no cover
wallet_id = uint32(request["wallet_id"])
wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed:
return {"success": False, "reason": "insufficient messages"}
async with self.service.wallet_state_manager.lock:
(
info_list,
message_spend_bundle,
) = await wallet.load_attest_files_for_recovery_spend(request["attest_data"])
if "pubkey" in request:
pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
else:
assert wallet.did_info.temp_pubkey is not None
pubkey = G1Element.from_bytes(wallet.did_info.temp_pubkey)
if "puzhash" in request:
puzhash = bytes32.from_hexstr(request["puzhash"])
else:
assert wallet.did_info.temp_puzhash is not None
puzhash = wallet.did_info.temp_puzhash
assert wallet.did_info.temp_coin is not None
async with self.service.wallet_state_manager.new_action_scope(
DEFAULT_TX_CONFIG, push=request.get("push", True)
) as action_scope:
await wallet.recovery_spend(
wallet.did_info.temp_coin,
puzhash,
info_list,
pubkey,
message_spend_bundle,
action_scope,
)
[tx] = action_scope.side_effects.transactions
return {
"success": True,
"spend_bundle": tx.spend_bundle,
"transactions": [tx.to_json_dict_convenience(self.service.config)],
}
return DIDGetMetadataResponse(
wallet_id=request.wallet_id,
metadata=metadata,
)
@marshal
async def did_get_pubkey(self, request: DIDGetPubkey) -> DIDGetPubkeyResponse:
@@ -2980,57 +2884,6 @@ class WalletRpcApi:
(await wallet.wallet_state_manager.get_unused_derivation_record(request.wallet_id)).pubkey
)
# TODO: this needs a test
@tx_endpoint(push=True)
async def did_create_attest(
self,
request: dict[str, Any],
action_scope: WalletActionScope,
extra_conditions: tuple[Condition, ...] = tuple(),
) -> EndpointResult: # pragma: no cover
wallet_id = uint32(request["wallet_id"])
wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet)
async with self.service.wallet_state_manager.lock:
info = await wallet.get_info_for_recovery()
coin = bytes32.from_hexstr(request["coin_name"])
pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"]))
message_spend_bundle, attest_data = await wallet.create_attestment(
coin,
bytes32.from_hexstr(request["puzhash"]),
pubkey,
action_scope,
extra_conditions=extra_conditions,
)
if info is not None:
return {
"success": True,
"message_spend_bundle": bytes(message_spend_bundle).hex(),
"info": [info[0].hex(), info[1].hex(), info[2]],
"attest_data": attest_data,
"transactions": None, # tx_endpoint wrapper will take care of this
}
else:
return {"success": False}
@marshal
async def did_get_information_needed_for_recovery(self, request: DIDGetRecoveryInfo) -> DIDGetRecoveryInfoResponse:
did_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
my_did = encode_puzzle_hash(
bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config)
)
assert did_wallet.did_info.temp_coin is not None
coin_name = did_wallet.did_info.temp_coin.name()
return DIDGetRecoveryInfoResponse(
wallet_id=request.wallet_id,
my_did=my_did,
coin_name=coin_name,
newpuzhash=did_wallet.did_info.temp_puzhash,
pubkey=G1Element.from_bytes(did_wallet.did_info.temp_pubkey)
if did_wallet.did_info.temp_pubkey is not None
else None,
backup_dids=did_wallet.did_info.backup_ids,
)
@marshal
async def did_get_current_coin_info(self, request: DIDGetCurrentCoinInfo) -> DIDGetCurrentCoinInfoResponse:
did_wallet = self.service.wallet_state_manager.get_wallet(id=request.wallet_id, required_type=DIDWallet)
@@ -3038,15 +2891,15 @@ class WalletRpcApi:
bytes32.from_hexstr(did_wallet.get_my_DID()), AddressType.DID.hrp(self.service.config)
)
did_coin_threeple = await did_wallet.get_info_for_recovery()
assert did_wallet.did_info.current_inner is not None
parent_coin = await did_wallet.get_coin()
assert my_did is not None
assert did_coin_threeple is not None
return DIDGetCurrentCoinInfoResponse(
wallet_id=request.wallet_id,
my_did=my_did,
did_parent=did_coin_threeple[0],
did_innerpuz=did_coin_threeple[1],
did_amount=did_coin_threeple[2],
did_parent=parent_coin.parent_coin_info,
did_innerpuz=did_wallet.did_info.current_inner.get_tree_hash(),
did_amount=parent_coin.amount,
)
@marshal
@@ -3070,7 +2923,6 @@ class WalletRpcApi:
await did_wallet.transfer_did(
puzzle_hash,
request.fee,
request.with_recovery_info,
action_scope,
extra_conditions=extra_conditions,
)
-57
View File
@@ -52,10 +52,6 @@ from chia.wallet.wallet_request_types import (
DIDGetMetadataResponse,
DIDGetPubkey,
DIDGetPubkeyResponse,
DIDGetRecoveryInfo,
DIDGetRecoveryInfoResponse,
DIDGetRecoveryList,
DIDGetRecoveryListResponse,
DIDGetWalletName,
DIDGetWalletNameResponse,
DIDMessageSpend,
@@ -66,8 +62,6 @@ from chia.wallet.wallet_request_types import (
DIDTransferDIDResponse,
DIDUpdateMetadata,
DIDUpdateMetadataResponse,
DIDUpdateRecoveryIDs,
DIDUpdateRecoveryIDsResponse,
DLDeleteMirror,
DLDeleteMirrorResponse,
DLGetMirrors,
@@ -543,25 +537,6 @@ class WalletRpcClient(RpcClient):
await self.fetch("did_create_backup_file", request.to_json_dict())
)
async def update_did_recovery_list(
self,
request: DIDUpdateRecoveryIDs,
tx_config: TXConfig,
extra_conditions: tuple[Condition, ...] = tuple(),
timelock_info: ConditionValidTimes = ConditionValidTimes(),
) -> DIDUpdateRecoveryIDsResponse:
return DIDUpdateRecoveryIDsResponse.from_json_dict(
await self.fetch(
"did_update_recovery_ids",
request.json_serialize_for_transport(tx_config, extra_conditions, timelock_info),
)
)
async def get_did_recovery_list(self, request: DIDGetRecoveryList) -> DIDGetRecoveryListResponse:
return DIDGetRecoveryListResponse.from_json_dict(
await self.fetch("did_get_recovery_list", request.to_json_dict())
)
async def did_message_spend(
self,
request: DIDMessageSpend,
@@ -604,43 +579,11 @@ class WalletRpcClient(RpcClient):
response = await self.fetch("create_new_wallet", request)
return response
async def did_create_attest(
self,
wallet_id: int,
coin_name: str,
pubkey: str,
puzhash: str,
file_name: str,
extra_conditions: tuple[Condition, ...] = tuple(),
timelock_info: ConditionValidTimes = ConditionValidTimes(),
) -> dict[str, Any]:
request = {
"wallet_id": wallet_id,
"coin_name": coin_name,
"pubkey": pubkey,
"puzhash": puzhash,
"filename": file_name,
"extra_conditions": conditions_to_json_dicts(extra_conditions),
**timelock_info.to_json_dict(),
}
response = await self.fetch("did_create_attest", request)
return response
async def did_get_recovery_info(self, request: DIDGetRecoveryInfo) -> DIDGetRecoveryInfoResponse:
return DIDGetRecoveryInfoResponse.from_json_dict(
await self.fetch("did_get_information_needed_for_recovery", request.to_json_dict())
)
async def did_get_current_coin_info(self, request: DIDGetCurrentCoinInfo) -> DIDGetCurrentCoinInfoResponse:
return DIDGetCurrentCoinInfoResponse.from_json_dict(
await self.fetch("did_get_current_coin_info", request.to_json_dict())
)
async def did_recovery_spend(self, wallet_id: int, attest_filenames: str) -> dict[str, Any]:
request = {"wallet_id": wallet_id, "attest_filenames": attest_filenames}
response = await self.fetch("did_recovery_spend", request)
return response
async def did_transfer_did(
self,
request: DIDTransferDID,