Merge branch 'main' into retain_chiabip

This commit is contained in:
Kyle Altendorf
2025-07-21 14:39:19 -04:00
36 changed files with 246 additions and 2029 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ from chia.util.files import write_file_async
def generate_random_ip(rand: random.Random) -> str:
return str(IPv4Address(rand.getrandbits(32)))
return str(IPv4Address(rand.randbytes(4)))
def populate_address_manager(num_new: int = 500000, num_tried: int = 200000) -> AddressManager:
+1 -2
View File
@@ -25,7 +25,6 @@ from chia_rs.sized_ints import uint8, uint32, uint64, uint128
from benchmarks.utils import setup_db
from chia._tests.util.benchmarks import (
clvm_generator,
rand_bytes,
rand_class_group_element,
rand_g1,
rand_g2,
@@ -110,7 +109,7 @@ async def run_add_block_benchmark(version: int) -> None:
rand_hash() if not has_pool_pk else None,
rand_g1(), # plot_public_key
uint8(32),
rand_bytes(8 * 32),
random.randbytes(8 * 32),
)
reward_chain_block = RewardChainBlock(
+4 -3
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
import random
import sys
from dataclasses import dataclass
from enum import Enum
@@ -14,7 +15,7 @@ from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint64
from benchmarks.utils import EnumType, get_commit_hash
from chia._tests.util.benchmarks import rand_bytes, rand_full_block, rand_hash
from chia._tests.util.benchmarks import rand_full_block, rand_hash
from chia.util.streamable import Streamable, streamable
# to run this benchmark:
@@ -50,13 +51,13 @@ class BenchmarkClass(Streamable):
def get_random_inner() -> BenchmarkInner:
return BenchmarkInner(rand_bytes(20).hex())
return BenchmarkInner(random.randbytes(20).hex())
def get_random_middle() -> BenchmarkMiddle:
a: uint64 = uint64(10)
b: list[bytes32] = [rand_hash() for _ in range(a)]
c: tuple[str, bool, uint8, list[bytes]] = ("benchmark", False, uint8(1), [rand_bytes(a) for _ in range(a)])
c: tuple[str, bool, uint8, list[bytes]] = ("benchmark", False, uint8(1), [random.randbytes(a) for _ in range(a)])
d: tuple[BenchmarkInner, BenchmarkInner] = (get_random_inner(), get_random_inner())
e: BenchmarkInner = get_random_inner()
return BenchmarkMiddle(a, b, c, d, e)
+2 -1
View File
@@ -13,6 +13,7 @@ excepted_packages = {
"chialisp_loader",
"chialisp_puzzles",
"chia_base",
"keyrings.cryptfile",
}
@@ -36,7 +37,7 @@ def main() -> int:
artifact_directory_path = directory_path.joinpath("artifacts")
artifact_directory_path.mkdir()
extras = ["upnp"]
extras = ["dev", "legacy-keyring", "upnp"]
print("Downloading packages for Python version:")
lines = [
@@ -7,7 +7,6 @@ from pytest import raises
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.consensus.pos_quality import _expected_plot_size
from chia.consensus.pot_iterations import (
PHASE_OUT_PERIOD,
calculate_ip_iters,
calculate_iterations_quality,
calculate_phase_out,
@@ -135,14 +134,16 @@ class TestPotIterations:
# after HARD_FORK2_HEIGHT, should return value = delta/phase_out_period * sp_interval
assert (
calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + 1)
== sp_interval // PHASE_OUT_PERIOD
== sp_interval // constants.PLOT_V1_PHASE_OUT
)
assert (
calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + PHASE_OUT_PERIOD // 2)
calculate_phase_out(
constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT // 2
)
== sp_interval // 2
)
assert (
calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + PHASE_OUT_PERIOD)
calculate_phase_out(constants, sub_slot_iters, constants.HARD_FORK2_HEIGHT + constants.PLOT_V1_PHASE_OUT)
== sp_interval
)
@@ -28,10 +28,7 @@ class TestStructStream(unittest.TestCase):
def rand_hash(rng: random.Random) -> bytes32:
ret = bytearray(32)
for i in range(32):
ret[i] = rng.getrandbits(8)
return bytes32(ret)
return bytes32.random(r=rng)
def create_spends(num: int) -> tuple[list[CoinSpend], list[Coin]]:
@@ -158,13 +158,6 @@ def test_internal_hash(seeded_random: Random) -> None:
assert definition(left_hash=left_hash, right_hash=right_hash) == reference
def get_random_bytes(length: int, r: Random) -> bytes:
if length == 0:
return b""
return r.getrandbits(length * 8).to_bytes(length, "big")
def test_leaf_hash(seeded_random: Random) -> None:
def definition(key: bytes, value: bytes) -> bytes32:
return SerializedProgram.to((key, value)).get_tree_hash()
@@ -176,13 +169,13 @@ def test_leaf_hash(seeded_random: Random) -> None:
else:
length = seeded_random.randrange(100)
key = get_random_bytes(length=length, r=seeded_random)
key = seeded_random.randbytes(length)
if cycle in {1, 2}:
length = 0
else:
length = seeded_random.randrange(100)
value = get_random_bytes(length=length, r=seeded_random)
value = seeded_random.randbytes(length)
reference = definition(key=key, value=value)
data.append((key, value, reference))
@@ -205,7 +198,7 @@ def test_key_hash(seeded_random: Random) -> None:
length = 0
else:
length = seeded_random.randrange(100)
key = get_random_bytes(length=length, r=seeded_random)
key = seeded_random.randbytes(length)
reference = definition(key=key)
data.append((key, reference))
@@ -1589,11 +1589,7 @@ async def test_benchmark_batch_insert_speed(
r.seed("shadowlands", version=2)
changelist = [
{
"action": "insert",
"key": x.to_bytes(32, byteorder="big", signed=False),
"value": bytes(r.getrandbits(8) for _ in range(1200)),
}
{"action": "insert", "key": x.to_bytes(32, byteorder="big", signed=False), "value": r.randbytes(1200)}
for x in range(case.pre + case.count)
]
@@ -1637,7 +1633,7 @@ async def test_benchmark_batch_insert_speed_multiple_batches(
{
"action": "insert",
"key": x.to_bytes(32, byteorder="big", signed=False),
"value": bytes(r.getrandbits(8) for _ in range(10000)),
"value": r.randbytes(10000),
}
for x in range(batch * case.count, (batch + 1) * case.count)
]
@@ -369,16 +369,10 @@ async def test_count_uncompactified_blocks(bt: BlockTools, tmp_dir: Path, db_ver
async def test_replace_proof(bt: BlockTools, tmp_dir: Path, db_version: int, use_cache: bool) -> None:
blocks = bt.get_consecutive_blocks(10)
def rand_bytes(num: int) -> bytes:
ret = bytearray(num)
for i in range(num):
ret[i] = random.getrandbits(8)
return bytes(ret)
def rand_vdf_proof() -> VDFProof:
return VDFProof(
uint8(1), # witness_type
rand_bytes(32), # witness
random.randbytes(32), # witness
bool(random.randint(0, 1)), # normalized_to_identity
)
+2 -5
View File
@@ -2955,11 +2955,8 @@ def test_timeout(old: bool) -> None:
def rand_hash() -> bytes32:
rng = random.Random()
ret = bytearray(32)
for i in range(32):
ret[i] = rng.getrandbits(8)
return bytes32(ret)
# TODO: does this need to be creating a new rng?
return bytes32.random(r=random.Random())
def item_cost(cost: int, fee_rate: float) -> MempoolItem:
+6 -13
View File
@@ -20,13 +20,6 @@ from chia.simulator.block_tools import test_constants
from chia.util.db_wrapper import DBWrapper2
def rand_bytes(num) -> bytes:
ret = bytearray(num)
for i in range(num):
ret[i] = random.getrandbits(8)
return bytes(ret)
@pytest.mark.anyio
@pytest.mark.parametrize("with_hints", [True, False])
@pytest.mark.skip("we no longer support DB v1")
@@ -35,21 +28,21 @@ async def test_blocks(default_1000_blocks, with_hints: bool):
hints: list[tuple[bytes32, bytes]] = []
for i in range(351):
hints.append((bytes32(rand_bytes(32)), rand_bytes(20)))
hints.append((bytes32.random(), random.randbytes(20)))
# the v1 schema allows duplicates in the hints table
for i in range(10):
coin_id = bytes32(rand_bytes(32))
hint = rand_bytes(20)
coin_id = bytes32.random()
hint = random.randbytes(20)
hints.append((coin_id, hint))
hints.append((coin_id, hint))
for i in range(2000):
hints.append((bytes32(rand_bytes(32)), rand_bytes(20)))
hints.append((bytes32.random(), random.randbytes(20)))
for i in range(5):
coin_id = bytes32(rand_bytes(32))
hint = rand_bytes(20)
coin_id = bytes32.random()
hint = random.randbytes(20)
hints.append((coin_id, hint))
hints.append((coin_id, hint))
+1 -5
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import random
import sqlite3
from contextlib import closing
from pathlib import Path
@@ -25,10 +24,7 @@ from chia.util.db_wrapper import DBWrapper2
def rand_hash() -> bytes32:
ret = bytearray(32)
for i in range(32):
ret[i] = random.getrandbits(8)
return bytes32(ret)
return bytes32.random()
def make_version(conn: sqlite3.Connection, version: int) -> None:
+22 -22
View File
@@ -88,8 +88,8 @@ async def test1(
peak_block = await client.get_block(state["peak"].header_hash)
assert peak_block == blocks[-1]
assert (await client.get_block(bytes32([1] * 32))) is None
with pytest.raises(ValueError, match="not found"):
await client.get_block(bytes32([1] * 32))
block_record = await client.get_block_record_by_height(2)
assert block_record is not None
assert block_record.header_hash == blocks[2].header_hash
@@ -150,8 +150,10 @@ async def test1(
assert len(await client.get_all_mempool_items()) == 0
assert len(await client.get_all_mempool_tx_ids()) == 0
assert (await client.get_mempool_item_by_tx_id(spend_bundle.name())) is None
assert (await client.get_mempool_item_by_tx_id(spend_bundle.name(), False)) is None
with pytest.raises(ValueError, match="not in the mempool"):
await client.get_mempool_item_by_tx_id(spend_bundle.name())
with pytest.raises(ValueError, match="not in the mempool"):
await client.get_mempool_item_by_tx_id(spend_bundle.name(), False)
await client.push_tx(spend_bundle)
coin = spend_bundle.additions()[0]
@@ -168,7 +170,8 @@ async def test1(
mempool_item = await client.get_mempool_item_by_tx_id(spend_bundle.name())
assert mempool_item is not None
assert WalletSpendBundle.from_json_dict(mempool_item["spend_bundle"]) == spend_bundle
assert (await client.get_coin_record_by_name(coin.name())) is None
with pytest.raises(ValueError, match="not found"):
await client.get_coin_record_by_name(coin.name())
# Verify that the include_pending arg to get_mempool_item_by_tx_id works
coin_to_spend_pending = included_reward_coins[1]
@@ -181,11 +184,11 @@ async def test1(
condition_dic=condition_dic,
)
await client.push_tx(spend_bundle_pending)
with pytest.raises(ValueError, match="not in the mempool"):
# not strictly in the mempool
assert (await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), False)) is None
await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), False)
# pending entry into mempool, so include_pending fetches
mempool_item = await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), True)
assert mempool_item is not None
assert WalletSpendBundle.from_json_dict(mempool_item["spend_bundle"]) == spend_bundle_pending
await full_node_api_1.farm_new_transaction_block(FarmNewBlockProtocol(ph_2))
@@ -454,17 +457,15 @@ async def test_signage_points(
full_node_service_1.config,
) as client:
# Only provide one
res = await client.get_recent_signage_point_or_eos(None, None)
assert res is None
res = await client.get_recent_signage_point_or_eos(std_hash(b"0"), std_hash(b"1"))
assert res is None
with pytest.raises(ValueError, match="sp_hash or challenge_hash must be provided."):
await client.get_recent_signage_point_or_eos(None, None)
with pytest.raises(ValueError, match="Either sp_hash or challenge_hash must be provided, not both."):
await client.get_recent_signage_point_or_eos(std_hash(b"0"), std_hash(b"1"))
# Not found
res = await client.get_recent_signage_point_or_eos(std_hash(b"0"), None)
assert res is None
res = await client.get_recent_signage_point_or_eos(None, std_hash(b"0"))
assert res is None
with pytest.raises(ValueError, match="in cache"):
await client.get_recent_signage_point_or_eos(std_hash(b"0"), None)
with pytest.raises(ValueError, match="in cache"):
await client.get_recent_signage_point_or_eos(None, std_hash(b"0"))
blocks = bt.get_consecutive_blocks(5)
for block in blocks:
await full_node_api_1.full_node.add_block(block)
@@ -494,8 +495,8 @@ async def test_signage_points(
assert sp.rc_proof is not None
assert sp.rc_vdf is not None
# Don't have SP yet
res = await client.get_recent_signage_point_or_eos(sp.cc_vdf.output.get_hash(), None)
assert res is None
with pytest.raises(ValueError, match="Did not find sp"):
await client.get_recent_signage_point_or_eos(sp.cc_vdf.output.get_hash(), None)
# Add the last block
await full_node_api_1.full_node.add_block(blocks[-1])
@@ -517,9 +518,8 @@ async def test_signage_points(
selected_eos = blocks[-1].finished_sub_slots[0]
# Don't have EOS yet
res = await client.get_recent_signage_point_or_eos(None, selected_eos.challenge_chain.get_hash())
assert res is None
with pytest.raises(ValueError, match="Did not find eos"):
await client.get_recent_signage_point_or_eos(None, selected_eos.challenge_chain.get_hash())
# Properly fetch an EOS
for eos in blocks[-1].finished_sub_slots:
await full_node_api_1.full_node.add_end_of_sub_slot(eos, peer)
+1 -4
View File
@@ -279,10 +279,7 @@ async def test_merkle_right_edge() -> None:
def rand_hash(rng: random.Random) -> bytes32:
ret = bytearray(32)
for i in range(32):
ret[i] = rng.getrandbits(8)
return bytes32(ret)
return bytes32.random(r=rng)
@pytest.mark.anyio
+3 -3
View File
@@ -372,7 +372,7 @@ def test_not_lists() -> None:
def test_basic_optional() -> None:
assert is_type_SpecificOptional(Optional[int])
assert is_type_SpecificOptional(Optional[Optional[int]])
assert is_type_SpecificOptional(Optional[int])
assert not is_type_SpecificOptional(list[int])
@@ -398,8 +398,8 @@ class PostInitTestClassBad(Streamable):
class PostInitTestClassOptional(Streamable):
a: Optional[uint8]
b: Optional[uint8]
c: Optional[Optional[uint8]]
d: Optional[Optional[uint8]]
c: Optional[uint8]
d: Optional[uint8]
@streamable
+5 -12
View File
@@ -40,29 +40,22 @@ def rewards(height: uint32) -> tuple[Coin, Coin]:
return farmer_coin, pool_coin
def rand_bytes(num: int) -> bytes:
ret = bytearray(num)
for i in range(num):
ret[i] = random.getrandbits(8)
return bytes(ret)
def rand_hash() -> bytes32:
return bytes32(rand_bytes(32))
return bytes32.random()
def rand_g1() -> G1Element:
sk = AugSchemeMPL.key_gen(rand_bytes(96))
sk = AugSchemeMPL.key_gen(random.randbytes(96))
return sk.get_g1()
def rand_g2() -> G2Element:
sk = AugSchemeMPL.key_gen(rand_bytes(96))
sk = AugSchemeMPL.key_gen(random.randbytes(96))
return AugSchemeMPL.sign(sk, b"foobar")
def rand_class_group_element() -> ClassgroupElement:
return ClassgroupElement(bytes100(rand_bytes(100)))
return ClassgroupElement(bytes100.random())
def rand_vdf() -> VDFInfo:
@@ -84,7 +77,7 @@ def rand_full_block() -> FullBlock:
None,
rand_g1(),
uint8(0),
rand_bytes(8 * 32),
random.randbytes(8 * 32),
)
reward_chain_block = RewardChainBlock(
+2 -2
View File
@@ -26,7 +26,7 @@ from chia_rs import (
from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint8, uint32, uint64, uint128
from chia._tests.util.benchmarks import rand_bytes, rand_g1, rand_g2, rand_hash, rand_vdf, rand_vdf_proof, rewards
from chia._tests.util.benchmarks import rand_g1, rand_g2, rand_hash, rand_vdf, rand_vdf_proof, rewards
from chia.consensus.generator_tools import get_block_header
from chia.full_node.full_block_utils import (
block_info_from_block,
@@ -73,7 +73,7 @@ def get_proof_of_space() -> Generator[ProofOfSpace, None, None]:
plot_hash,
g1(), # plot_public_key
uint8(32),
rand_bytes(8 * 32),
random.randbytes(8 * 32),
)
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)
@@ -25,6 +25,7 @@ from chia_rs.sized_ints import uint32, uint64, uint128
from chiabip158 import PyBIP158
from colorlog import getLogger
from chia._tests.conftest import ConsensusMode
from chia._tests.connection_utils import connect_and_get_peer, disconnect_all, disconnect_all_and_reconnect
from chia._tests.util.blockchain_mock import BlockchainMock
from chia._tests.util.misc import patch_request_handler, wallet_height_at_least
@@ -1702,6 +1703,11 @@ async def test_long_sync_untrusted_break(
@pytest.mark.anyio
@pytest.mark.parametrize("chain_length", [0, 100])
@pytest.mark.parametrize("fork_point", [500, 1500])
# TODO: todo_v2_plots once we have new test chains, we can probably re-enable this
@pytest.mark.limit_consensus_modes(
allows=[ConsensusMode.PLAIN, ConsensusMode.HARD_FORK_2_0],
reason="after plot-v1 phase-out, the chains aren't valid anymore",
)
async def test_long_reorg_nodes_and_wallet(
chain_length: int,
fork_point: int,
@@ -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(
[
+2 -5
View File
@@ -10,9 +10,6 @@ from chia.consensus.pos_quality import _expected_plot_size
from chia.types.blockchain_format.proof_of_space import verify_and_get_quality_string
from chia.util.hash import std_hash
# TODO: todo_v2_plots add to chia_rs and get from constants
PHASE_OUT_PERIOD = uint32(10000000)
def is_overflow_block(constants: ConsensusConstants, signage_point_index: uint8) -> bool:
if signage_point_index >= constants.NUM_SPS_SUB_SLOT:
@@ -38,7 +35,7 @@ def calculate_phase_out(
) -> uint64:
if prev_transaction_block_height <= constants.HARD_FORK2_HEIGHT:
return uint64(0)
elif uint32(prev_transaction_block_height - constants.HARD_FORK2_HEIGHT) >= PHASE_OUT_PERIOD:
elif uint32(prev_transaction_block_height - constants.HARD_FORK2_HEIGHT) >= constants.PLOT_V1_PHASE_OUT:
return uint64(calculate_sp_interval_iters(constants, sub_slot_iters))
return uint64(
@@ -46,7 +43,7 @@ def calculate_phase_out(
uint32(prev_transaction_block_height - constants.HARD_FORK2_HEIGHT)
* calculate_sp_interval_iters(constants, sub_slot_iters)
)
// PHASE_OUT_PERIOD
// constants.PLOT_V1_PHASE_OUT
)
+2 -2
View File
@@ -21,7 +21,7 @@ class FarmerRpcClient(RpcClient):
async def get_signage_point(self, sp_hash: bytes32) -> Optional[dict[str, Any]]:
try:
return await self.fetch("get_signage_point", {"sp_hash": sp_hash.hex()})
except ValueError:
except ValueError: # not synced
return None
async def get_signage_points(self) -> list[dict[str, Any]]:
@@ -83,5 +83,5 @@ class FarmerRpcClient(RpcClient):
try:
result = await self.fetch("get_pool_login_link", {"launcher_id": launcher_id.hex()})
return cast(Optional[str], result["login_link"])
except ValueError:
except ValueError: # not connected to pool.
return None
+1
View File
@@ -1623,6 +1623,7 @@ class FullNode:
for i, block in enumerate(blocks_to_validate):
header_hash = block.header_hash
assert vs.prev_ses_block is None or vs.prev_ses_block.height < block.height
assert pre_validation_results[i].error is None
assert pre_validation_results[i].required_iters is not None
state_change_summary: Optional[StateChangeSummary]
# when adding blocks in batches, we won't have any overlapping
+21 -44
View File
@@ -7,7 +7,7 @@ from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32
from chia.consensus.signage_point import SignagePoint
from chia.rpc.rpc_client import RpcClient
from chia.rpc.rpc_client import ResponseFailureError, RpcClient
from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpendWithConditions
from chia.types.condition_opcodes import ConditionOpcode
@@ -36,11 +36,8 @@ class FullNodeRpcClient(RpcClient):
response["blockchain_state"]["peak"] = BlockRecord.from_json_dict(response["blockchain_state"]["peak"])
return cast(dict[str, Any], response["blockchain_state"])
async def get_block(self, header_hash: bytes32) -> Optional[FullBlock]:
try:
async def get_block(self, header_hash: bytes32) -> FullBlock:
response = await self.fetch("get_block", {"header_hash": header_hash.hex()})
except Exception:
return None
return FullBlock.from_json_dict(response["block"])
async def get_blocks(self, start: int, end: int, exclude_reorged: bool = False) -> list[FullBlock]:
@@ -52,17 +49,16 @@ class FullNodeRpcClient(RpcClient):
async def get_block_record_by_height(self, height: int) -> Optional[BlockRecord]:
try:
response = await self.fetch("get_block_record_by_height", {"height": height})
except Exception:
except ResponseFailureError as e: # Block Height not found
if e.response["error"] == f"Block height {height} not found in chain":
return None
raise e
return BlockRecord.from_json_dict(response["block_record"])
async def get_block_record(self, header_hash: bytes32) -> Optional[BlockRecord]:
try:
response = await self.fetch("get_block_record", {"header_hash": header_hash.hex()})
if response["block_record"] is None:
return None
except Exception:
return None
return BlockRecord.from_json_dict(response["block_record"])
async def get_unfinished_block_headers(self) -> list[UnfinishedHeaderBlock]:
@@ -84,12 +80,8 @@ class FullNodeRpcClient(RpcClient):
return cast(int, network_space_bytes_estimate["space"])
async def get_coin_record_by_name(self, coin_id: bytes32) -> Optional[CoinRecord]:
try:
async def get_coin_record_by_name(self, coin_id: bytes32) -> CoinRecord:
response = await self.fetch("get_coin_record_by_name", {"name": coin_id.hex()})
except Exception:
return None
return CoinRecord.from_json_dict(coin_record_dict_backwards_compat(response["coin_record"]))
async def get_coin_records_by_names(
@@ -180,10 +172,7 @@ class FullNodeRpcClient(RpcClient):
return [CoinRecord.from_json_dict(coin_record_dict_backwards_compat(coin)) for coin in response["coin_records"]]
async def get_additions_and_removals(self, header_hash: bytes32) -> tuple[list[CoinRecord], list[CoinRecord]]:
try:
response = await self.fetch("get_additions_and_removals", {"header_hash": header_hash.hex()})
except Exception:
return [], []
removals = []
additions = []
for coin_record in response["removals"]:
@@ -195,25 +184,23 @@ class FullNodeRpcClient(RpcClient):
async def get_block_records(self, start: int, end: int) -> list[dict[str, Any]]:
try:
response = await self.fetch("get_block_records", {"start": start, "end": end})
if response["block_records"] is None:
except ResponseFailureError as e: # No Peak Yet
if e.response["error"] == "Peak is None":
return []
except Exception:
raise e
if response["block_records"] is None:
return []
# TODO: return block records
return cast(list[dict[str, Any]], response["block_records"])
async def get_block_spends(self, header_hash: bytes32) -> Optional[list[CoinSpend]]:
try:
async def get_block_spends(self, header_hash: bytes32) -> list[CoinSpend]:
response = await self.fetch("get_block_spends", {"header_hash": header_hash.hex()})
output = []
for block_spend in response["block_spends"]:
output.append(CoinSpend.from_json_dict(block_spend))
return output
except Exception:
return None
async def get_block_spends_with_conditions(self, header_hash: bytes32) -> Optional[list[CoinSpendWithConditions]]:
try:
async def get_block_spends_with_conditions(self, header_hash: bytes32) -> list[CoinSpendWithConditions]:
response = await self.fetch("get_block_spends_with_conditions", {"header_hash": header_hash.hex()})
block_spends: list[CoinSpendWithConditions] = []
for block_spend in response["block_spends_with_conditions"]:
@@ -228,18 +215,12 @@ class FullNodeRpcClient(RpcClient):
block_spends.append(CoinSpendWithConditions(coin_spend=coin_spend, conditions=conditions))
return block_spends
except Exception:
return None
async def push_tx(self, spend_bundle: SpendBundle) -> dict[str, Any]:
return await self.fetch("push_tx", {"spend_bundle": spend_bundle.to_json_dict()})
async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> Optional[CoinSpend]:
try:
async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> CoinSpend:
response = await self.fetch("get_puzzle_and_solution", {"coin_id": coin_id.hex(), "height": height})
return CoinSpend.from_json_dict(response["coin_solution"])
except Exception:
return None
async def get_all_mempool_tx_ids(self) -> list[bytes32]:
response = await self.fetch("get_all_mempool_tx_ids", {})
@@ -256,14 +237,11 @@ class FullNodeRpcClient(RpcClient):
self,
tx_id: bytes32,
include_pending: bool = False,
) -> Optional[dict[str, Any]]:
try:
) -> dict[str, Any]:
response = await self.fetch(
"get_mempool_item_by_tx_id", {"tx_id": tx_id.hex(), "include_pending": include_pending}
)
return cast(dict[str, Any], response["mempool_item"])
except Exception:
return None
async def get_mempool_items_by_coin_name(self, coin_name: bytes32) -> dict[str, Any]:
response = await self.fetch("get_mempool_items_by_coin_name", {"coin_name": coin_name.hex()})
@@ -275,26 +253,25 @@ class FullNodeRpcClient(RpcClient):
async def get_recent_signage_point_or_eos(
self, sp_hash: Optional[bytes32], challenge_hash: Optional[bytes32]
) -> Optional[Any]:
try:
if sp_hash is not None:
assert challenge_hash is None
) -> dict[str, Any]:
if sp_hash is not None and challenge_hash is not None:
raise ValueError("Either sp_hash or challenge_hash must be provided, not both.")
elif sp_hash is not None:
response = await self.fetch("get_recent_signage_point_or_eos", {"sp_hash": sp_hash.hex()})
return {
"signage_point": SignagePoint.from_json_dict(response["signage_point"]),
"time_received": response["time_received"],
"reverted": response["reverted"],
}
else:
assert challenge_hash is not None
elif challenge_hash is not None:
response = await self.fetch("get_recent_signage_point_or_eos", {"challenge_hash": challenge_hash.hex()})
return {
"eos": EndOfSubSlotBundle.from_json_dict(response["eos"]),
"time_received": response["time_received"],
"reverted": response["reverted"],
}
except Exception:
return None
else:
raise ValueError("sp_hash or challenge_hash must be provided.")
async def get_fee_estimate(
self,
+1 -1
View File
@@ -22,7 +22,7 @@ class MempoolSubmissionStatus(Streamable):
inclusion_status: uint8 # MempoolInclusionStatus
error_msg: Optional[str]
def to_json_dict_convenience(self) -> dict[str, Union[str, MempoolInclusionStatus, Optional[str]]]:
def to_json_dict_convenience(self) -> dict[str, Union[str, MempoolInclusionStatus, None]]:
formatted = self.to_json_dict()
formatted["inclusion_status"] = MempoolInclusionStatus(self.inclusion_status).name
return formatted
+3 -309
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,7 +691,6 @@ 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
new_did_puzhash = did_wallet_puzzles.get_inner_puzhash_by_p2(
@@ -713,11 +705,6 @@ 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])
# full solution is (corehash parent_info my_amount innerpuz_reveal solution)
@@ -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)
+10 -158
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,
return DIDGetMetadataResponse(
wallet_id=request.wallet_id,
metadata=metadata,
)
[tx] = action_scope.side_effects.transactions
return {
"success": True,
"spend_bundle": tx.spend_bundle,
"transactions": [tx.to_json_dict_convenience(self.service.config)],
}
@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,
)
+1 -58
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,
@@ -762,7 +705,7 @@ class WalletRpcClient(RpcClient):
request = {"asset_id": asset_id.hex()}
try:
res = await self.fetch("cat_asset_id_to_name", request)
except ValueError:
except ValueError: # This happens if the asset_id is unknown
return None
wallet_id: Optional[uint32] = None if res["wallet_id"] is None else uint32(int(res["wallet_id"]))
+1
View File
@@ -9,6 +9,7 @@ def main() -> int:
[
"poetry",
"check",
"--strict",
],
check=True,
)
+47 -95
View File
@@ -140,7 +140,6 @@ description = "CORS support for aiohttp"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "aiohttp_cors-0.8.1-py3-none-any.whl", hash = "sha256:3180cf304c5c712d626b9162b195b1db7ddf976a2a25172b35bb2448b890a80d"},
{file = "aiohttp_cors-0.8.1.tar.gz", hash = "sha256:ccacf9cb84b64939ea15f859a146af1f662a6b1d68175754a07315e305fb1403"},
@@ -190,7 +189,7 @@ description = "Python graph (network) package"
optional = true
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.12\" and extra == \"dev\""
markers = "extra == \"dev\" and python_version <= \"3.12\""
files = [
{file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
@@ -226,7 +225,6 @@ description = "Argon2 for Python"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"legacy-keyring\""
files = [
{file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"},
{file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"},
@@ -248,7 +246,6 @@ description = "Low-level CFFI bindings for Argon2"
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "extra == \"legacy-keyring\""
files = [
{file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"},
{file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"},
@@ -336,7 +333,6 @@ description = "Simple bencode parser (for Python 2, Python 3 and PyPy)"
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "bencode.py-4.0.0-py2.py3-none-any.whl", hash = "sha256:99c06a55764e85ffe81622fdf9ee78bd737bad3ea61d119784a54bb28860d962"},
{file = "bencode.py-4.0.0.tar.gz", hash = "sha256:2a24ccda1725a51a650893d0b63260138359eaa299bb6e7a09961350a2a6e05c"},
@@ -506,18 +502,18 @@ bitarray = ">=3.0.0,<4.0"
[[package]]
name = "boto3"
version = "1.39.1"
version = "1.39.4"
description = "The AWS SDK for Python"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "boto3-1.39.1-py3-none-any.whl", hash = "sha256:beb945ec1ab4bb48d39640ce7d3054b7e4ab2020ef3259034c039195ae04b2f7"},
{file = "boto3-1.39.1.tar.gz", hash = "sha256:3f89d6f05ab7d3a6f6807b45e9456a1a0db53bb47b1758cf5e0a3479cdd6d734"},
{file = "boto3-1.39.4-py3-none-any.whl", hash = "sha256:f8e9534b429121aa5c5b7c685c6a94dd33edf14f87926e9a182d5b50220ba284"},
{file = "boto3-1.39.4.tar.gz", hash = "sha256:6c955729a1d70181bc8368e02a7d3f350884290def63815ebca8408ee6d47571"},
]
[package.dependencies]
botocore = ">=1.39.1,<1.40.0"
botocore = ">=1.39.4,<1.40.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.13.0,<0.14.0"
@@ -526,14 +522,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.39.1"
version = "1.39.4"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "botocore-1.39.1-py3-none-any.whl", hash = "sha256:912d518ac3096460e54d2cf80899943a521a586fd5c075568e807f3c35623318"},
{file = "botocore-1.39.1.tar.gz", hash = "sha256:ac829b46b30bd837392c9792cdf63b44d290a4691c028b5e66ab471ced1b8305"},
{file = "botocore-1.39.4-py3-none-any.whl", hash = "sha256:c41e167ce01cfd1973c3fa9856ef5244a51ddf9c82cb131120d8617913b6812a"},
{file = "botocore-1.39.4.tar.gz", hash = "sha256:e662ac35c681f7942a93f2ec7b4cde8f8b56dd399da47a79fa3e370338521a56"},
]
[package.dependencies]
@@ -554,7 +550,6 @@ description = "A simple, correct Python build frontend"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"},
{file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"},
@@ -581,7 +576,7 @@ description = "Python package for providing Mozilla's CA Bundle."
optional = true
python-versions = ">=3.6"
groups = ["main"]
markers = "sys_platform == \"linux\" and extra == \"dev\""
markers = "extra == \"dev\" and sys_platform == \"linux\""
files = [
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
@@ -594,7 +589,6 @@ description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "platform_python_implementation != \"PyPy\" or extra == \"legacy-keyring\""
files = [
{file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
{file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
@@ -660,7 +654,6 @@ description = "Validate configuration and produce human readable error messages.
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
@@ -673,7 +666,6 @@ description = "Universal encoding detector for Python 3"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
{file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
@@ -686,7 +678,7 @@ description = "The Real First Universal Charset Detector. Open, modern and activ
optional = true
python-versions = ">=3.7.0"
groups = ["main"]
markers = "sys_platform == \"linux\" and extra == \"dev\""
markers = "extra == \"dev\" and sys_platform == \"linux\""
files = [
{file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"},
{file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"},
@@ -877,6 +869,7 @@ files = [
{file = "chiabip158-1.5.2-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b36a529ee5685294fe55cedfa0788cb1baac03c310b1533cd23481357efd10"},
{file = "chiabip158-1.5.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad40df68317d39f33272e25fd9651f05a27b85d524e9ed694ac7549cde44918c"},
{file = "chiabip158-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:07b298cfb0621dba1027c710e9669970f4e089c118db8732bd456101c727db65"},
{file = "chiabip158-1.5.2.tar.gz", hash = "sha256:86c225f5a566cca3199607f6ea646799da9e406df6fb0ae7323d57e5ac8e2f2c"},
]
[[package]]
@@ -1118,7 +1111,6 @@ description = "Code coverage measurement for Python"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912"},
{file = "coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f"},
@@ -1252,7 +1244,6 @@ description = "Run coverage and linting reports on diffs"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "diff_cover-9.4.1-py3-none-any.whl", hash = "sha256:84d5bd402f566d04212126988a2c352b8ec801fa7e43b8856bd8dc146baec5a9"},
{file = "diff_cover-9.4.1.tar.gz", hash = "sha256:7ded89e5fb3a61161be9b98d025f2ad4f5aa95de593c3fbeb65419ddb6667610"},
@@ -1274,7 +1265,6 @@ description = "Distribution utilities"
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"},
{file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"},
@@ -1336,7 +1326,6 @@ description = "execnet: rapid multi-Python deployment"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
@@ -1456,7 +1445,6 @@ description = "Git Object Database"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"},
{file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"},
@@ -1472,7 +1460,6 @@ description = "GitPython is a Python library used to interact with Git repositor
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"},
{file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"},
@@ -1512,7 +1499,6 @@ description = "File identification library for Python"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"},
{file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"},
@@ -1669,7 +1655,6 @@ description = "A very fast and expressive template engine."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
@@ -1730,7 +1715,6 @@ description = "Encrypted file keyring backend"
optional = true
python-versions = ">=3.5"
groups = ["main"]
markers = "extra == \"legacy-keyring\""
files = [
{file = "keyrings.cryptfile-1.3.9.tar.gz", hash = "sha256:7c2a453cab9985426b8c21f7ad54a57e49ff8e819ba18e08340bd8801acf0091"},
]
@@ -1748,7 +1732,6 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "lxml-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35bc626eec405f745199200ccb5c6b36f202675d204aa29bb52e27ba2b71dea8"},
{file = "lxml-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:246b40f8a4aec341cbbf52617cad8ab7c888d944bfe12a6abd2b1f6cfb6f6082"},
@@ -1875,7 +1858,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
@@ -1901,7 +1883,6 @@ description = "Safely add untrusted strings to HTML/XML markup."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
@@ -1972,7 +1953,6 @@ description = "Markdown URL utilities"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
@@ -1985,7 +1965,7 @@ description = "A module for monitoring memory usage of a python program"
optional = true
python-versions = ">=3.5"
groups = ["main"]
markers = "sys_platform == \"linux\" and extra == \"dev\""
markers = "extra == \"dev\" and sys_platform == \"linux\""
files = [
{file = "memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84"},
{file = "memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0"},
@@ -2001,7 +1981,6 @@ description = "MiniUPnP IGD client"
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"upnp\""
files = [
{file = "miniupnpc-2.3.3-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:0424940059620f7b2a753876d40719324f32d2d2d6684a8851ae512b22b978ec"},
{file = "miniupnpc-2.3.3-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:6a86e1d7387954f5a1ab43ef1ddbf35cd7a0cdcf7f1d02b632fd79e473fedd64"},
@@ -2149,7 +2128,6 @@ description = "Optional static typing for Python"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
{file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
@@ -2204,7 +2182,6 @@ description = "Type system extensions for programs checked with the mypy type ch
optional = true
python-versions = ">=3.5"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@@ -2217,7 +2194,7 @@ description = "Python package for creating and manipulating graphs and networks"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version < \"3.10\" and extra == \"dev\""
markers = "python_version < \"3.10\""
files = [
{file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"},
{file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"},
@@ -2237,7 +2214,7 @@ description = "Python package for creating and manipulating graphs and networks"
optional = true
python-versions = ">=3.10"
groups = ["main"]
markers = "python_version >= \"3.10\" and extra == \"dev\" and python_version < \"3.12\""
markers = "python_version < \"3.12\" and python_version >= \"3.10\""
files = [
{file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"},
{file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"},
@@ -2258,7 +2235,7 @@ description = "Python package for creating and manipulating graphs and networks"
optional = true
python-versions = ">=3.11"
groups = ["main"]
markers = "python_version >= \"3.12\" and extra == \"dev\""
markers = "python_version >= \"3.12\""
files = [
{file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"},
{file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"},
@@ -2280,7 +2257,6 @@ description = "Node.js virtual environment builder"
optional = true
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
{file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
@@ -2333,7 +2309,6 @@ description = "A small Python package for determining appropriate platform-speci
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
{file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
@@ -2386,7 +2361,6 @@ description = "A framework for managing and maintaining multi-language pre-commi
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"},
{file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"},
@@ -2406,7 +2380,6 @@ description = "Library for building powerful interactive command lines in Python
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"},
{file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"},
@@ -2538,7 +2511,6 @@ description = "Create torrents via command line!"
optional = true
python-versions = "<4,>=3.5"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "py3createtorrent-1.2.1-py3-none-any.whl", hash = "sha256:dede7e87d869d2b013a633486f5f1fcedd6f057ff9f12d9ba9a370acfc496311"},
{file = "py3createtorrent-1.2.1.tar.gz", hash = "sha256:04d801adbbe8beb37547104935bd1fb81e02459341b524f85852629fa7dd326d"},
@@ -2554,7 +2526,6 @@ description = "C parser in Python"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
markers = "platform_python_implementation != \"PyPy\" or extra == \"legacy-keyring\""
files = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
@@ -2567,7 +2538,6 @@ description = "Cryptographic library for Python"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
groups = ["main"]
markers = "extra == \"legacy-keyring\""
files = [
{file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"},
{file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"},
@@ -2610,7 +2580,6 @@ description = "Python interface to Graphviz's Dot"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "pydot-3.0.4-py3-none-any.whl", hash = "sha256:bfa9c3fc0c44ba1d132adce131802d7df00429d1a79cc0346b0a5cd374dbe9c6"},
{file = "pydot-3.0.4.tar.gz", hash = "sha256:3ce88b2558f3808b0376f22bfa6c263909e1c3981e2a7b629b65b451eee4a25d"},
@@ -2646,7 +2615,7 @@ description = "PyInstaller bundles a Python application and all its dependencies
optional = true
python-versions = "<3.14,>=3.8"
groups = ["main"]
markers = "python_version <= \"3.12\" and extra == \"dev\""
markers = "extra == \"dev\" and python_version <= \"3.12\""
files = [
{file = "pyinstaller-6.14.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:da559cfe4f7a20a7ebdafdf12ea2a03ea94d3caa49736ef53ee2c155d78422c9"},
{file = "pyinstaller-6.14.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:f040d1e3d42af3730104078d10d4a8ca3350bd1c78de48f12e1b26f761e0cbc3"},
@@ -2683,7 +2652,7 @@ description = "Community maintained hooks for PyInstaller"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.12\" and extra == \"dev\""
markers = "extra == \"dev\" and python_version <= \"3.12\""
files = [
{file = "pyinstaller_hooks_contrib-2025.5-py3-none-any.whl", hash = "sha256:ebfae1ba341cb0002fb2770fad0edf2b3e913c2728d92df7ad562260988ca373"},
{file = "pyinstaller_hooks_contrib-2025.5.tar.gz", hash = "sha256:707386770b8fe066c04aad18a71bc483c7b25e18b4750a756999f7da2ab31982"},
@@ -2701,7 +2670,6 @@ description = "pyparsing module - Classes and methods to define and execute pars
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"},
{file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"},
@@ -2717,7 +2685,6 @@ description = "Wrappers to call pyproject.toml-based build backend hooks."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"},
{file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"},
@@ -2757,7 +2724,6 @@ description = "Pytest plugin for measuring coverage."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"},
{file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"},
@@ -2778,7 +2744,6 @@ description = "Thin-wrapper around the mock package for easier use with pytest"
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"},
{file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"},
@@ -2797,7 +2762,7 @@ description = "Pytest plugin for analyzing resource usage."
optional = true
python-versions = ">=3.5"
groups = ["main"]
markers = "sys_platform == \"linux\" and extra == \"dev\""
markers = "extra == \"dev\" and sys_platform == \"linux\""
files = [
{file = "pytest-monitor-1.6.6.tar.gz", hash = "sha256:b0c44dc44a2d6cdd19f84caa18fafeb1227e2b33bcbd11a2071dacd3763e1b6f"},
{file = "pytest_monitor-1.6.6-py3-none-any.whl", hash = "sha256:5be37d14aa423fe97af94bd44e3a47a551bd5d94d64921974580bbaadc1c1c94"},
@@ -2817,7 +2782,6 @@ description = "pytest xdist plugin for distributed testing, most importantly acr
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"},
{file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"},
@@ -2955,7 +2919,7 @@ description = "Python HTTP for Humans."
optional = true
python-versions = ">=3.8"
groups = ["main"]
markers = "sys_platform == \"linux\" and extra == \"dev\""
markers = "extra == \"dev\" and sys_platform == \"linux\""
files = [
{file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
{file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
@@ -2978,7 +2942,6 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar
optional = true
python-versions = ">=3.8.0"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
{file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
@@ -2994,31 +2957,30 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.11.11"
version = "0.12.4"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092"},
{file = "ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4"},
{file = "ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd"},
{file = "ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6"},
{file = "ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4"},
{file = "ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac"},
{file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709"},
{file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8"},
{file = "ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b"},
{file = "ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875"},
{file = "ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1"},
{file = "ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81"},
{file = "ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639"},
{file = "ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345"},
{file = "ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112"},
{file = "ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f"},
{file = "ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b"},
{file = "ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d"},
{file = "ruff-0.12.4-py3-none-linux_armv6l.whl", hash = "sha256:cb0d261dac457ab939aeb247e804125a5d521b21adf27e721895b0d3f83a0d0a"},
{file = "ruff-0.12.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:55c0f4ca9769408d9b9bac530c30d3e66490bd2beb2d3dae3e4128a1f05c7442"},
{file = "ruff-0.12.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a8224cc3722c9ad9044da7f89c4c1ec452aef2cfe3904365025dd2f51daeae0e"},
{file = "ruff-0.12.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9949d01d64fa3672449a51ddb5d7548b33e130240ad418884ee6efa7a229586"},
{file = "ruff-0.12.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:be0593c69df9ad1465e8a2d10e3defd111fdb62dcd5be23ae2c06da77e8fcffb"},
{file = "ruff-0.12.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7dea966bcb55d4ecc4cc3270bccb6f87a337326c9dcd3c07d5b97000dbff41c"},
{file = "ruff-0.12.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afcfa3ab5ab5dd0e1c39bf286d829e042a15e966b3726eea79528e2e24d8371a"},
{file = "ruff-0.12.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c057ce464b1413c926cdb203a0f858cd52f3e73dcb3270a3318d1630f6395bb3"},
{file = "ruff-0.12.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64b90d1122dc2713330350626b10d60818930819623abbb56535c6466cce045"},
{file = "ruff-0.12.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2abc48f3d9667fdc74022380b5c745873499ff827393a636f7a59da1515e7c57"},
{file = "ruff-0.12.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2b2449dc0c138d877d629bea151bee8c0ae3b8e9c43f5fcaafcd0c0d0726b184"},
{file = "ruff-0.12.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:56e45bb11f625db55f9b70477062e6a1a04d53628eda7784dce6e0f55fd549eb"},
{file = "ruff-0.12.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:478fccdb82ca148a98a9ff43658944f7ab5ec41c3c49d77cd99d44da019371a1"},
{file = "ruff-0.12.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0fc426bec2e4e5f4c4f182b9d2ce6a75c85ba9bcdbe5c6f2a74fcb8df437df4b"},
{file = "ruff-0.12.4-py3-none-win32.whl", hash = "sha256:4de27977827893cdfb1211d42d84bc180fceb7b72471104671c59be37041cf93"},
{file = "ruff-0.12.4-py3-none-win_amd64.whl", hash = "sha256:fe0b9e9eb23736b453143d72d2ceca5db323963330d5b7859d60d101147d461a"},
{file = "ruff-0.12.4-py3-none-win_arm64.whl", hash = "sha256:0618ec4442a83ab545e5b71202a5c0ed7791e8471435b94e655b570a5031a98e"},
{file = "ruff-0.12.4.tar.gz", hash = "sha256:13efa16df6c6eeb7d0f091abae50f58e9522f3843edb40d56ad52a5a4a4b6873"},
]
[[package]]
@@ -3180,14 +3142,14 @@ test = ["pytest"]
[[package]]
name = "setuptools"
version = "80.8.0"
version = "80.9.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0"},
{file = "setuptools-80.8.0.tar.gz", hash = "sha256:49f7af965996f26d43c8ae34539c8d99c5042fbff34302ea151eaa9c207cd257"},
{file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"},
{file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"},
]
[package.extras]
@@ -3218,7 +3180,6 @@ description = "A pure Python implementation of a sliding window memory map manag
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"},
{file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"},
@@ -3255,7 +3216,7 @@ description = "A list of Python Standard Libraries (2.7 through 3.13)."
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\" and python_version < \"3.10\""
markers = "python_version < \"3.10\""
files = [
{file = "stdlib_list-0.11.1-py3-none-any.whl", hash = "sha256:9029ea5e3dfde8cd4294cfd4d1797be56a67fc4693c606181730148c3fd1da29"},
{file = "stdlib_list-0.11.1.tar.gz", hash = "sha256:95ebd1d73da9333bba03ccc097f5bac05e3aa03e6822a0c0290f87e1047f1857"},
@@ -3275,7 +3236,6 @@ description = "A Python tool to maintain a modular package architecture."
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "tach-0.29.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:517f33d18d381326a775d101650e576c6922db53b2c336192db7db88b9a3521d"},
{file = "tach-0.29.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:d984f54bebba0e4c981d2a08c3e4cdf76c3b5f3126e2f593a0faaed9d218552a"},
@@ -3311,7 +3271,6 @@ description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\" or python_version < \"3.11\""
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
@@ -3324,7 +3283,6 @@ description = "A lil' TOML writer"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"},
{file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"},
@@ -3332,15 +3290,14 @@ files = [
[[package]]
name = "types-aiofiles"
version = "24.1.0.20250606"
version = "24.1.0.20250708"
description = "Typing stubs for aiofiles"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "types_aiofiles-24.1.0.20250606-py3-none-any.whl", hash = "sha256:e568c53fb9017c80897a9aa15c74bf43b7ee90e412286ec1e0912b6e79301aee"},
{file = "types_aiofiles-24.1.0.20250606.tar.gz", hash = "sha256:48f9e26d2738a21e0b0f19381f713dcdb852a36727da8414b1ada145d40a18fe"},
{file = "types_aiofiles-24.1.0.20250708-py3-none-any.whl", hash = "sha256:07f8f06465fd415d9293467d1c66cd074b2c3b62b679e26e353e560a8cf63720"},
{file = "types_aiofiles-24.1.0.20250708.tar.gz", hash = "sha256:c8207ed7385491ce5ba94da02658164ebd66b69a44e892288c9f20cbbf5284ff"},
]
[[package]]
@@ -3350,7 +3307,6 @@ description = "Typing stubs for cryptography"
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "types-cryptography-3.3.23.2.tar.gz", hash = "sha256:09cc53f273dd4d8c29fa7ad11fefd9b734126d467960162397bc5e3e604dea75"},
{file = "types_cryptography-3.3.23.2-py3-none-any.whl", hash = "sha256:b965d548f148f8e87f353ccf2b7bd92719fdf6c845ff7cedf2abb393a0643e4f"},
@@ -3363,7 +3319,6 @@ description = "Typing stubs for PyYAML"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"},
{file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"},
@@ -3376,7 +3331,6 @@ description = "Typing stubs for setuptools"
optional = true
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "types_setuptools-80.9.0.20250529-py3-none-any.whl", hash = "sha256:00dfcedd73e333a430e10db096e4d46af93faf9314f832f13b6bbe3d6757e95f"},
{file = "types_setuptools-80.9.0.20250529.tar.gz", hash = "sha256:79e088ba0cba2186c8d6499cbd3e143abb142d28a44b042c28d3148b1e353c91"},
@@ -3438,7 +3392,6 @@ description = "Virtual Python Environment builder"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"},
{file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"},
@@ -3503,7 +3456,6 @@ description = "Measures the displayed width of unicode strings in a terminal"
optional = true
python-versions = "*"
groups = ["main"]
markers = "extra == \"dev\""
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
@@ -3516,7 +3468,7 @@ description = "A built-package format for Python"
optional = true
python-versions = ">=3.7"
groups = ["main"]
markers = "sys_platform == \"linux\" and extra == \"dev\""
markers = "extra == \"dev\" and sys_platform == \"linux\""
files = [
{file = "wheel-0.41.2-py3-none-any.whl", hash = "sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"},
{file = "wheel-0.41.2.tar.gz", hash = "sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985"},
@@ -3785,4 +3737,4 @@ upnp = ["miniupnpc"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.9, <4"
content-hash = "a0b086bb169964bc3c677ffeceb8d2d8a3e5c13d0eb245f685ea49538cab48a5"
content-hash = "1ae7dc9d5aaaee0970c3ef5752cd29841350d65b788486a107dde51a801b2f8b"
+1 -1
View File
@@ -109,7 +109,7 @@ miniupnpc = {version = ">=2.3.2, <3", source = "chia", optional = true}
# {version=">=1.26.4", python = ">=3.9", optional = true}]
ruff = { version = ">=0.8.1", optional = true }
[tool.poetry.extras]
[project.optional-dependencies]
dev = ["aiohttp_cors", "build", "coverage", "diff-cover", "mypy", "pre-commit", "py3createtorrent", "pyinstaller", "pytest", "pytest-cov", "pytest-mock", "pytest-monitor", "pytest-xdist", "ruff", "tach", "types-aiofiles", "types-cryptography", "types-pyyaml", "types-setuptools", "lxml"]
upnp = ["miniupnpc"]
legacy_keyring = ["keyrings.cryptfile"]
+12 -6
View File
@@ -153,9 +153,11 @@ async def node_spends_with_conditions(
block_hash: bytes32,
height: int,
) -> None:
result = await node_client.get_block_spends_with_conditions(block_hash)
if result is None:
try:
await node_client.get_block_spends_with_conditions(block_hash)
except Exception as e:
print(f"ERROR: [{height}] get_block_spends_with_conditions returned invalid result")
raise e
async def node_block_spends(
@@ -163,9 +165,11 @@ async def node_block_spends(
block_hash: bytes32,
height: int,
) -> None:
result = await node_client.get_block_spends(block_hash)
if result is None:
try:
await node_client.get_block_spends(block_hash)
except Exception as e:
print(f"ERROR: [{height}] get_block_spends returned invalid result")
raise e
async def node_additions_removals(
@@ -173,9 +177,11 @@ async def node_additions_removals(
block_hash: bytes32,
height: int,
) -> None:
response = await node_client.get_additions_and_removals(block_hash)
if response is None:
try:
await node_client.get_additions_and_removals(block_hash)
except Exception as e:
print(f"ERROR: [{height}] get_additions_and_removals returned invalid result")
raise e
async def cli_async(