chia-blockchain 2.5.1rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1042) hide show
  1. chia/__init__.py +10 -0
  2. chia/__main__.py +5 -0
  3. chia/_tests/README.md +53 -0
  4. chia/_tests/__init__.py +0 -0
  5. chia/_tests/blockchain/__init__.py +0 -0
  6. chia/_tests/blockchain/blockchain_test_utils.py +195 -0
  7. chia/_tests/blockchain/config.py +4 -0
  8. chia/_tests/blockchain/test_augmented_chain.py +145 -0
  9. chia/_tests/blockchain/test_blockchain.py +4202 -0
  10. chia/_tests/blockchain/test_blockchain_transactions.py +1031 -0
  11. chia/_tests/blockchain/test_build_chains.py +59 -0
  12. chia/_tests/blockchain/test_get_block_generator.py +72 -0
  13. chia/_tests/blockchain/test_lookup_fork_chain.py +194 -0
  14. chia/_tests/build-init-files.py +92 -0
  15. chia/_tests/build-job-matrix.py +204 -0
  16. chia/_tests/check_pytest_monitor_output.py +34 -0
  17. chia/_tests/check_sql_statements.py +72 -0
  18. chia/_tests/chia-start-sim +42 -0
  19. chia/_tests/clvm/__init__.py +0 -0
  20. chia/_tests/clvm/benchmark_costs.py +23 -0
  21. chia/_tests/clvm/coin_store.py +149 -0
  22. chia/_tests/clvm/test_chialisp_deserialization.py +101 -0
  23. chia/_tests/clvm/test_clvm_step.py +37 -0
  24. chia/_tests/clvm/test_condition_codes.py +13 -0
  25. chia/_tests/clvm/test_curry_and_treehash.py +55 -0
  26. chia/_tests/clvm/test_message_conditions.py +184 -0
  27. chia/_tests/clvm/test_program.py +150 -0
  28. chia/_tests/clvm/test_puzzle_compression.py +143 -0
  29. chia/_tests/clvm/test_puzzle_drivers.py +45 -0
  30. chia/_tests/clvm/test_puzzles.py +242 -0
  31. chia/_tests/clvm/test_singletons.py +540 -0
  32. chia/_tests/clvm/test_spend_sim.py +181 -0
  33. chia/_tests/cmds/__init__.py +0 -0
  34. chia/_tests/cmds/cmd_test_utils.py +469 -0
  35. chia/_tests/cmds/config.py +3 -0
  36. chia/_tests/cmds/conftest.py +23 -0
  37. chia/_tests/cmds/test_click_types.py +200 -0
  38. chia/_tests/cmds/test_cmd_framework.py +620 -0
  39. chia/_tests/cmds/test_cmds_util.py +97 -0
  40. chia/_tests/cmds/test_daemon.py +92 -0
  41. chia/_tests/cmds/test_dev_gh.py +131 -0
  42. chia/_tests/cmds/test_farm_cmd.py +66 -0
  43. chia/_tests/cmds/test_show.py +116 -0
  44. chia/_tests/cmds/test_sim.py +207 -0
  45. chia/_tests/cmds/test_timelock_args.py +75 -0
  46. chia/_tests/cmds/test_tx_config_args.py +154 -0
  47. chia/_tests/cmds/testing_classes.py +59 -0
  48. chia/_tests/cmds/wallet/__init__.py +0 -0
  49. chia/_tests/cmds/wallet/test_consts.py +47 -0
  50. chia/_tests/cmds/wallet/test_dao.py +565 -0
  51. chia/_tests/cmds/wallet/test_did.py +403 -0
  52. chia/_tests/cmds/wallet/test_nft.py +471 -0
  53. chia/_tests/cmds/wallet/test_notifications.py +124 -0
  54. chia/_tests/cmds/wallet/test_offer.toffer +1 -0
  55. chia/_tests/cmds/wallet/test_tx_decorators.py +27 -0
  56. chia/_tests/cmds/wallet/test_vcs.py +400 -0
  57. chia/_tests/cmds/wallet/test_wallet.py +1125 -0
  58. chia/_tests/cmds/wallet/test_wallet_check.py +109 -0
  59. chia/_tests/conftest.py +1419 -0
  60. chia/_tests/connection_utils.py +125 -0
  61. chia/_tests/core/__init__.py +0 -0
  62. chia/_tests/core/cmds/__init__.py +0 -0
  63. chia/_tests/core/cmds/test_beta.py +382 -0
  64. chia/_tests/core/cmds/test_keys.py +1734 -0
  65. chia/_tests/core/cmds/test_wallet.py +126 -0
  66. chia/_tests/core/config.py +3 -0
  67. chia/_tests/core/consensus/__init__.py +0 -0
  68. chia/_tests/core/consensus/test_block_creation.py +54 -0
  69. chia/_tests/core/consensus/test_pot_iterations.py +117 -0
  70. chia/_tests/core/custom_types/__init__.py +0 -0
  71. chia/_tests/core/custom_types/test_coin.py +107 -0
  72. chia/_tests/core/custom_types/test_proof_of_space.py +144 -0
  73. chia/_tests/core/custom_types/test_spend_bundle.py +70 -0
  74. chia/_tests/core/daemon/__init__.py +0 -0
  75. chia/_tests/core/daemon/config.py +4 -0
  76. chia/_tests/core/daemon/test_daemon.py +2128 -0
  77. chia/_tests/core/daemon/test_daemon_register.py +109 -0
  78. chia/_tests/core/daemon/test_keychain_proxy.py +101 -0
  79. chia/_tests/core/data_layer/__init__.py +0 -0
  80. chia/_tests/core/data_layer/config.py +5 -0
  81. chia/_tests/core/data_layer/conftest.py +106 -0
  82. chia/_tests/core/data_layer/test_data_cli.py +56 -0
  83. chia/_tests/core/data_layer/test_data_layer.py +83 -0
  84. chia/_tests/core/data_layer/test_data_layer_util.py +218 -0
  85. chia/_tests/core/data_layer/test_data_rpc.py +3847 -0
  86. chia/_tests/core/data_layer/test_data_store.py +2424 -0
  87. chia/_tests/core/data_layer/test_data_store_schema.py +381 -0
  88. chia/_tests/core/data_layer/test_plugin.py +91 -0
  89. chia/_tests/core/data_layer/util.py +233 -0
  90. chia/_tests/core/farmer/__init__.py +0 -0
  91. chia/_tests/core/farmer/config.py +3 -0
  92. chia/_tests/core/farmer/test_farmer_api.py +103 -0
  93. chia/_tests/core/full_node/__init__.py +0 -0
  94. chia/_tests/core/full_node/config.py +4 -0
  95. chia/_tests/core/full_node/dos/__init__.py +0 -0
  96. chia/_tests/core/full_node/dos/config.py +3 -0
  97. chia/_tests/core/full_node/full_sync/__init__.py +0 -0
  98. chia/_tests/core/full_node/full_sync/config.py +4 -0
  99. chia/_tests/core/full_node/full_sync/test_full_sync.py +443 -0
  100. chia/_tests/core/full_node/ram_db.py +27 -0
  101. chia/_tests/core/full_node/stores/__init__.py +0 -0
  102. chia/_tests/core/full_node/stores/config.py +4 -0
  103. chia/_tests/core/full_node/stores/test_block_store.py +590 -0
  104. chia/_tests/core/full_node/stores/test_coin_store.py +897 -0
  105. chia/_tests/core/full_node/stores/test_full_node_store.py +1219 -0
  106. chia/_tests/core/full_node/stores/test_hint_store.py +229 -0
  107. chia/_tests/core/full_node/stores/test_sync_store.py +135 -0
  108. chia/_tests/core/full_node/test_address_manager.py +588 -0
  109. chia/_tests/core/full_node/test_block_height_map.py +556 -0
  110. chia/_tests/core/full_node/test_conditions.py +556 -0
  111. chia/_tests/core/full_node/test_full_node.py +2700 -0
  112. chia/_tests/core/full_node/test_generator_tools.py +82 -0
  113. chia/_tests/core/full_node/test_hint_management.py +104 -0
  114. chia/_tests/core/full_node/test_node_load.py +34 -0
  115. chia/_tests/core/full_node/test_performance.py +179 -0
  116. chia/_tests/core/full_node/test_subscriptions.py +492 -0
  117. chia/_tests/core/full_node/test_transactions.py +203 -0
  118. chia/_tests/core/full_node/test_tx_processing_queue.py +155 -0
  119. chia/_tests/core/large_block.py +2388 -0
  120. chia/_tests/core/make_block_generator.py +70 -0
  121. chia/_tests/core/mempool/__init__.py +0 -0
  122. chia/_tests/core/mempool/config.py +4 -0
  123. chia/_tests/core/mempool/test_mempool.py +3255 -0
  124. chia/_tests/core/mempool/test_mempool_fee_estimator.py +104 -0
  125. chia/_tests/core/mempool/test_mempool_fee_protocol.py +55 -0
  126. chia/_tests/core/mempool/test_mempool_item_queries.py +190 -0
  127. chia/_tests/core/mempool/test_mempool_manager.py +2084 -0
  128. chia/_tests/core/mempool/test_mempool_performance.py +64 -0
  129. chia/_tests/core/mempool/test_singleton_fast_forward.py +567 -0
  130. chia/_tests/core/node_height.py +28 -0
  131. chia/_tests/core/server/__init__.py +0 -0
  132. chia/_tests/core/server/config.py +3 -0
  133. chia/_tests/core/server/flood.py +84 -0
  134. chia/_tests/core/server/serve.py +135 -0
  135. chia/_tests/core/server/test_api_protocol.py +21 -0
  136. chia/_tests/core/server/test_capabilities.py +66 -0
  137. chia/_tests/core/server/test_dos.py +319 -0
  138. chia/_tests/core/server/test_event_loop.py +109 -0
  139. chia/_tests/core/server/test_loop.py +294 -0
  140. chia/_tests/core/server/test_node_discovery.py +73 -0
  141. chia/_tests/core/server/test_rate_limits.py +482 -0
  142. chia/_tests/core/server/test_server.py +226 -0
  143. chia/_tests/core/server/test_upnp.py +8 -0
  144. chia/_tests/core/services/__init__.py +0 -0
  145. chia/_tests/core/services/config.py +3 -0
  146. chia/_tests/core/services/test_services.py +188 -0
  147. chia/_tests/core/ssl/__init__.py +0 -0
  148. chia/_tests/core/ssl/config.py +3 -0
  149. chia/_tests/core/ssl/test_ssl.py +202 -0
  150. chia/_tests/core/test_coins.py +33 -0
  151. chia/_tests/core/test_cost_calculation.py +313 -0
  152. chia/_tests/core/test_crawler.py +175 -0
  153. chia/_tests/core/test_crawler_rpc.py +53 -0
  154. chia/_tests/core/test_daemon_rpc.py +24 -0
  155. chia/_tests/core/test_db_conversion.py +130 -0
  156. chia/_tests/core/test_db_validation.py +162 -0
  157. chia/_tests/core/test_farmer_harvester_rpc.py +505 -0
  158. chia/_tests/core/test_filter.py +35 -0
  159. chia/_tests/core/test_full_node_rpc.py +768 -0
  160. chia/_tests/core/test_merkle_set.py +343 -0
  161. chia/_tests/core/test_program.py +47 -0
  162. chia/_tests/core/test_rpc_util.py +86 -0
  163. chia/_tests/core/test_seeder.py +420 -0
  164. chia/_tests/core/test_setproctitle.py +13 -0
  165. chia/_tests/core/util/__init__.py +0 -0
  166. chia/_tests/core/util/config.py +4 -0
  167. chia/_tests/core/util/test_block_cache.py +44 -0
  168. chia/_tests/core/util/test_cached_bls.py +57 -0
  169. chia/_tests/core/util/test_config.py +337 -0
  170. chia/_tests/core/util/test_file_keyring_synchronization.py +105 -0
  171. chia/_tests/core/util/test_files.py +391 -0
  172. chia/_tests/core/util/test_jsonify.py +146 -0
  173. chia/_tests/core/util/test_keychain.py +522 -0
  174. chia/_tests/core/util/test_keyring_wrapper.py +491 -0
  175. chia/_tests/core/util/test_lockfile.py +380 -0
  176. chia/_tests/core/util/test_log_exceptions.py +187 -0
  177. chia/_tests/core/util/test_lru_cache.py +56 -0
  178. chia/_tests/core/util/test_significant_bits.py +40 -0
  179. chia/_tests/core/util/test_streamable.py +883 -0
  180. chia/_tests/db/__init__.py +0 -0
  181. chia/_tests/db/test_db_wrapper.py +566 -0
  182. chia/_tests/environments/__init__.py +0 -0
  183. chia/_tests/environments/common.py +35 -0
  184. chia/_tests/environments/full_node.py +47 -0
  185. chia/_tests/environments/wallet.py +429 -0
  186. chia/_tests/ether.py +19 -0
  187. chia/_tests/farmer_harvester/__init__.py +0 -0
  188. chia/_tests/farmer_harvester/config.py +3 -0
  189. chia/_tests/farmer_harvester/test_farmer.py +1264 -0
  190. chia/_tests/farmer_harvester/test_farmer_harvester.py +292 -0
  191. chia/_tests/farmer_harvester/test_filter_prefix_bits.py +131 -0
  192. chia/_tests/farmer_harvester/test_third_party_harvesters.py +528 -0
  193. chia/_tests/farmer_harvester/test_third_party_harvesters_data.json +29 -0
  194. chia/_tests/fee_estimation/__init__.py +0 -0
  195. chia/_tests/fee_estimation/config.py +3 -0
  196. chia/_tests/fee_estimation/test_fee_estimation_integration.py +262 -0
  197. chia/_tests/fee_estimation/test_fee_estimation_rpc.py +287 -0
  198. chia/_tests/fee_estimation/test_fee_estimation_unit_tests.py +144 -0
  199. chia/_tests/fee_estimation/test_mempoolitem_height_added.py +146 -0
  200. chia/_tests/generator/__init__.py +0 -0
  201. chia/_tests/generator/puzzles/__init__.py +0 -0
  202. chia/_tests/generator/puzzles/test_generator_deserialize.clsp +3 -0
  203. chia/_tests/generator/puzzles/test_generator_deserialize.clsp.hex +1 -0
  204. chia/_tests/generator/puzzles/test_multiple_generator_input_arguments.clsp +19 -0
  205. chia/_tests/generator/puzzles/test_multiple_generator_input_arguments.clsp.hex +1 -0
  206. chia/_tests/generator/test_compression.py +201 -0
  207. chia/_tests/generator/test_generator_types.py +44 -0
  208. chia/_tests/generator/test_rom.py +180 -0
  209. chia/_tests/plot_sync/__init__.py +0 -0
  210. chia/_tests/plot_sync/config.py +3 -0
  211. chia/_tests/plot_sync/test_delta.py +101 -0
  212. chia/_tests/plot_sync/test_plot_sync.py +618 -0
  213. chia/_tests/plot_sync/test_receiver.py +451 -0
  214. chia/_tests/plot_sync/test_sender.py +116 -0
  215. chia/_tests/plot_sync/test_sync_simulated.py +451 -0
  216. chia/_tests/plot_sync/util.py +68 -0
  217. chia/_tests/plotting/__init__.py +0 -0
  218. chia/_tests/plotting/config.py +3 -0
  219. chia/_tests/plotting/test_plot_manager.py +781 -0
  220. chia/_tests/plotting/util.py +12 -0
  221. chia/_tests/pools/__init__.py +0 -0
  222. chia/_tests/pools/config.py +5 -0
  223. chia/_tests/pools/test_pool_cli_parsing.py +128 -0
  224. chia/_tests/pools/test_pool_cmdline.py +1001 -0
  225. chia/_tests/pools/test_pool_config.py +42 -0
  226. chia/_tests/pools/test_pool_puzzles_lifecycle.py +397 -0
  227. chia/_tests/pools/test_pool_rpc.py +1123 -0
  228. chia/_tests/pools/test_pool_wallet.py +205 -0
  229. chia/_tests/pools/test_wallet_pool_store.py +161 -0
  230. chia/_tests/process_junit.py +348 -0
  231. chia/_tests/rpc/__init__.py +0 -0
  232. chia/_tests/rpc/test_rpc_client.py +138 -0
  233. chia/_tests/rpc/test_rpc_server.py +183 -0
  234. chia/_tests/simulation/__init__.py +0 -0
  235. chia/_tests/simulation/config.py +6 -0
  236. chia/_tests/simulation/test_simulation.py +501 -0
  237. chia/_tests/simulation/test_simulator.py +232 -0
  238. chia/_tests/simulation/test_start_simulator.py +107 -0
  239. chia/_tests/testconfig.py +13 -0
  240. chia/_tests/timelord/__init__.py +0 -0
  241. chia/_tests/timelord/config.py +3 -0
  242. chia/_tests/timelord/test_new_peak.py +437 -0
  243. chia/_tests/timelord/test_timelord.py +11 -0
  244. chia/_tests/tools/1315537.json +170 -0
  245. chia/_tests/tools/1315544.json +160 -0
  246. chia/_tests/tools/1315630.json +150 -0
  247. chia/_tests/tools/300000.json +105 -0
  248. chia/_tests/tools/442734.json +140 -0
  249. chia/_tests/tools/466212.json +130 -0
  250. chia/_tests/tools/__init__.py +0 -0
  251. chia/_tests/tools/config.py +5 -0
  252. chia/_tests/tools/test-blockchain-db.sqlite +0 -0
  253. chia/_tests/tools/test_full_sync.py +30 -0
  254. chia/_tests/tools/test_legacy_keyring.py +82 -0
  255. chia/_tests/tools/test_run_block.py +128 -0
  256. chia/_tests/tools/test_virtual_project.py +591 -0
  257. chia/_tests/util/__init__.py +0 -0
  258. chia/_tests/util/benchmark_cost.py +170 -0
  259. chia/_tests/util/benchmarks.py +153 -0
  260. chia/_tests/util/bip39_test_vectors.json +148 -0
  261. chia/_tests/util/blockchain.py +134 -0
  262. chia/_tests/util/blockchain_mock.py +132 -0
  263. chia/_tests/util/build_network_protocol_files.py +302 -0
  264. chia/_tests/util/clvm_generator.bin +0 -0
  265. chia/_tests/util/config.py +3 -0
  266. chia/_tests/util/constants.py +20 -0
  267. chia/_tests/util/db_connection.py +37 -0
  268. chia/_tests/util/full_sync.py +253 -0
  269. chia/_tests/util/gen_ssl_certs.py +114 -0
  270. chia/_tests/util/generator_tools_testing.py +45 -0
  271. chia/_tests/util/get_name_puzzle_conditions.py +52 -0
  272. chia/_tests/util/key_tool.py +36 -0
  273. chia/_tests/util/misc.py +675 -0
  274. chia/_tests/util/network_protocol_data.py +1072 -0
  275. chia/_tests/util/protocol_messages_bytes-v1.0 +0 -0
  276. chia/_tests/util/protocol_messages_json.py +2701 -0
  277. chia/_tests/util/rpc.py +26 -0
  278. chia/_tests/util/run_block.py +163 -0
  279. chia/_tests/util/setup_nodes.py +481 -0
  280. chia/_tests/util/spend_sim.py +492 -0
  281. chia/_tests/util/split_managers.py +102 -0
  282. chia/_tests/util/temp_file.py +14 -0
  283. chia/_tests/util/test_action_scope.py +144 -0
  284. chia/_tests/util/test_async_pool.py +366 -0
  285. chia/_tests/util/test_build_job_matrix.py +42 -0
  286. chia/_tests/util/test_build_network_protocol_files.py +7 -0
  287. chia/_tests/util/test_chia_version.py +50 -0
  288. chia/_tests/util/test_collection.py +11 -0
  289. chia/_tests/util/test_condition_tools.py +229 -0
  290. chia/_tests/util/test_config.py +426 -0
  291. chia/_tests/util/test_dump_keyring.py +60 -0
  292. chia/_tests/util/test_errors.py +10 -0
  293. chia/_tests/util/test_full_block_utils.py +279 -0
  294. chia/_tests/util/test_installed.py +20 -0
  295. chia/_tests/util/test_limited_semaphore.py +53 -0
  296. chia/_tests/util/test_logging_filter.py +42 -0
  297. chia/_tests/util/test_misc.py +445 -0
  298. chia/_tests/util/test_network.py +73 -0
  299. chia/_tests/util/test_network_protocol_files.py +578 -0
  300. chia/_tests/util/test_network_protocol_json.py +267 -0
  301. chia/_tests/util/test_network_protocol_test.py +256 -0
  302. chia/_tests/util/test_paginator.py +71 -0
  303. chia/_tests/util/test_pprint.py +17 -0
  304. chia/_tests/util/test_priority_mutex.py +488 -0
  305. chia/_tests/util/test_recursive_replace.py +116 -0
  306. chia/_tests/util/test_replace_str_to_bytes.py +137 -0
  307. chia/_tests/util/test_service_groups.py +15 -0
  308. chia/_tests/util/test_ssl_check.py +31 -0
  309. chia/_tests/util/test_testnet_overrides.py +19 -0
  310. chia/_tests/util/test_tests_misc.py +38 -0
  311. chia/_tests/util/test_timing.py +37 -0
  312. chia/_tests/util/test_trusted_peer.py +51 -0
  313. chia/_tests/util/time_out_assert.py +191 -0
  314. chia/_tests/wallet/__init__.py +0 -0
  315. chia/_tests/wallet/cat_wallet/__init__.py +0 -0
  316. chia/_tests/wallet/cat_wallet/config.py +4 -0
  317. chia/_tests/wallet/cat_wallet/test_cat_lifecycle.py +468 -0
  318. chia/_tests/wallet/cat_wallet/test_cat_outer_puzzle.py +69 -0
  319. chia/_tests/wallet/cat_wallet/test_cat_wallet.py +1826 -0
  320. chia/_tests/wallet/cat_wallet/test_offer_lifecycle.py +291 -0
  321. chia/_tests/wallet/cat_wallet/test_trades.py +2600 -0
  322. chia/_tests/wallet/clawback/__init__.py +0 -0
  323. chia/_tests/wallet/clawback/config.py +3 -0
  324. chia/_tests/wallet/clawback/test_clawback_decorator.py +78 -0
  325. chia/_tests/wallet/clawback/test_clawback_lifecycle.py +292 -0
  326. chia/_tests/wallet/clawback/test_clawback_metadata.py +50 -0
  327. chia/_tests/wallet/config.py +4 -0
  328. chia/_tests/wallet/conftest.py +278 -0
  329. chia/_tests/wallet/dao_wallet/__init__.py +0 -0
  330. chia/_tests/wallet/dao_wallet/config.py +3 -0
  331. chia/_tests/wallet/dao_wallet/test_dao_clvm.py +1330 -0
  332. chia/_tests/wallet/dao_wallet/test_dao_wallets.py +3488 -0
  333. chia/_tests/wallet/db_wallet/__init__.py +0 -0
  334. chia/_tests/wallet/db_wallet/config.py +3 -0
  335. chia/_tests/wallet/db_wallet/test_db_graftroot.py +141 -0
  336. chia/_tests/wallet/db_wallet/test_dl_offers.py +491 -0
  337. chia/_tests/wallet/db_wallet/test_dl_wallet.py +823 -0
  338. chia/_tests/wallet/did_wallet/__init__.py +0 -0
  339. chia/_tests/wallet/did_wallet/config.py +4 -0
  340. chia/_tests/wallet/did_wallet/test_did.py +2284 -0
  341. chia/_tests/wallet/nft_wallet/__init__.py +0 -0
  342. chia/_tests/wallet/nft_wallet/config.py +4 -0
  343. chia/_tests/wallet/nft_wallet/test_nft_1_offers.py +1493 -0
  344. chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py +1024 -0
  345. chia/_tests/wallet/nft_wallet/test_nft_lifecycle.py +375 -0
  346. chia/_tests/wallet/nft_wallet/test_nft_offers.py +1209 -0
  347. chia/_tests/wallet/nft_wallet/test_nft_puzzles.py +172 -0
  348. chia/_tests/wallet/nft_wallet/test_nft_wallet.py +2584 -0
  349. chia/_tests/wallet/nft_wallet/test_ownership_outer_puzzle.py +70 -0
  350. chia/_tests/wallet/rpc/__init__.py +0 -0
  351. chia/_tests/wallet/rpc/config.py +4 -0
  352. chia/_tests/wallet/rpc/test_dl_wallet_rpc.py +285 -0
  353. chia/_tests/wallet/rpc/test_wallet_rpc.py +3153 -0
  354. chia/_tests/wallet/simple_sync/__init__.py +0 -0
  355. chia/_tests/wallet/simple_sync/config.py +3 -0
  356. chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py +718 -0
  357. chia/_tests/wallet/sync/__init__.py +0 -0
  358. chia/_tests/wallet/sync/config.py +4 -0
  359. chia/_tests/wallet/sync/test_wallet_sync.py +1692 -0
  360. chia/_tests/wallet/test_address_type.py +189 -0
  361. chia/_tests/wallet/test_bech32m.py +45 -0
  362. chia/_tests/wallet/test_clvm_streamable.py +244 -0
  363. chia/_tests/wallet/test_coin_management.py +354 -0
  364. chia/_tests/wallet/test_coin_selection.py +588 -0
  365. chia/_tests/wallet/test_conditions.py +400 -0
  366. chia/_tests/wallet/test_debug_spend_bundle.py +218 -0
  367. chia/_tests/wallet/test_new_wallet_protocol.py +1174 -0
  368. chia/_tests/wallet/test_nft_store.py +192 -0
  369. chia/_tests/wallet/test_notifications.py +196 -0
  370. chia/_tests/wallet/test_offer_parsing_performance.py +48 -0
  371. chia/_tests/wallet/test_puzzle_store.py +132 -0
  372. chia/_tests/wallet/test_sign_coin_spends.py +159 -0
  373. chia/_tests/wallet/test_signer_protocol.py +947 -0
  374. chia/_tests/wallet/test_singleton.py +122 -0
  375. chia/_tests/wallet/test_singleton_lifecycle_fast.py +772 -0
  376. chia/_tests/wallet/test_singleton_store.py +152 -0
  377. chia/_tests/wallet/test_taproot.py +19 -0
  378. chia/_tests/wallet/test_transaction_store.py +945 -0
  379. chia/_tests/wallet/test_util.py +185 -0
  380. chia/_tests/wallet/test_wallet.py +2139 -0
  381. chia/_tests/wallet/test_wallet_action_scope.py +85 -0
  382. chia/_tests/wallet/test_wallet_blockchain.py +111 -0
  383. chia/_tests/wallet/test_wallet_coin_store.py +1002 -0
  384. chia/_tests/wallet/test_wallet_interested_store.py +43 -0
  385. chia/_tests/wallet/test_wallet_key_val_store.py +40 -0
  386. chia/_tests/wallet/test_wallet_node.py +780 -0
  387. chia/_tests/wallet/test_wallet_retry.py +95 -0
  388. chia/_tests/wallet/test_wallet_state_manager.py +259 -0
  389. chia/_tests/wallet/test_wallet_test_framework.py +275 -0
  390. chia/_tests/wallet/test_wallet_trade_store.py +218 -0
  391. chia/_tests/wallet/test_wallet_user_store.py +34 -0
  392. chia/_tests/wallet/test_wallet_utils.py +156 -0
  393. chia/_tests/wallet/vc_wallet/__init__.py +0 -0
  394. chia/_tests/wallet/vc_wallet/config.py +3 -0
  395. chia/_tests/wallet/vc_wallet/test_cr_outer_puzzle.py +70 -0
  396. chia/_tests/wallet/vc_wallet/test_vc_lifecycle.py +883 -0
  397. chia/_tests/wallet/vc_wallet/test_vc_wallet.py +830 -0
  398. chia/_tests/wallet/wallet_block_tools.py +327 -0
  399. chia/_tests/weight_proof/__init__.py +0 -0
  400. chia/_tests/weight_proof/config.py +3 -0
  401. chia/_tests/weight_proof/test_weight_proof.py +528 -0
  402. chia/apis.py +19 -0
  403. chia/clvm/__init__.py +0 -0
  404. chia/cmds/__init__.py +0 -0
  405. chia/cmds/beta.py +184 -0
  406. chia/cmds/beta_funcs.py +137 -0
  407. chia/cmds/check_wallet_db.py +420 -0
  408. chia/cmds/chia.py +151 -0
  409. chia/cmds/cmd_classes.py +323 -0
  410. chia/cmds/cmd_helpers.py +242 -0
  411. chia/cmds/cmds_util.py +488 -0
  412. chia/cmds/coin_funcs.py +275 -0
  413. chia/cmds/coins.py +182 -0
  414. chia/cmds/completion.py +49 -0
  415. chia/cmds/configure.py +332 -0
  416. chia/cmds/dao.py +1064 -0
  417. chia/cmds/dao_funcs.py +598 -0
  418. chia/cmds/data.py +708 -0
  419. chia/cmds/data_funcs.py +385 -0
  420. chia/cmds/db.py +87 -0
  421. chia/cmds/db_backup_func.py +77 -0
  422. chia/cmds/db_upgrade_func.py +452 -0
  423. chia/cmds/db_validate_func.py +184 -0
  424. chia/cmds/dev.py +18 -0
  425. chia/cmds/farm.py +100 -0
  426. chia/cmds/farm_funcs.py +200 -0
  427. chia/cmds/gh.py +275 -0
  428. chia/cmds/init.py +63 -0
  429. chia/cmds/init_funcs.py +367 -0
  430. chia/cmds/installers.py +131 -0
  431. chia/cmds/keys.py +527 -0
  432. chia/cmds/keys_funcs.py +863 -0
  433. chia/cmds/netspace.py +50 -0
  434. chia/cmds/netspace_funcs.py +54 -0
  435. chia/cmds/options.py +32 -0
  436. chia/cmds/param_types.py +238 -0
  437. chia/cmds/passphrase.py +131 -0
  438. chia/cmds/passphrase_funcs.py +292 -0
  439. chia/cmds/peer.py +51 -0
  440. chia/cmds/peer_funcs.py +129 -0
  441. chia/cmds/plotnft.py +260 -0
  442. chia/cmds/plotnft_funcs.py +405 -0
  443. chia/cmds/plots.py +230 -0
  444. chia/cmds/plotters.py +18 -0
  445. chia/cmds/rpc.py +208 -0
  446. chia/cmds/show.py +72 -0
  447. chia/cmds/show_funcs.py +215 -0
  448. chia/cmds/signer.py +296 -0
  449. chia/cmds/sim.py +225 -0
  450. chia/cmds/sim_funcs.py +509 -0
  451. chia/cmds/start.py +24 -0
  452. chia/cmds/start_funcs.py +109 -0
  453. chia/cmds/stop.py +62 -0
  454. chia/cmds/units.py +9 -0
  455. chia/cmds/wallet.py +1901 -0
  456. chia/cmds/wallet_funcs.py +1874 -0
  457. chia/consensus/__init__.py +0 -0
  458. chia/consensus/block_body_validation.py +562 -0
  459. chia/consensus/block_creation.py +546 -0
  460. chia/consensus/block_header_validation.py +1059 -0
  461. chia/consensus/block_record.py +31 -0
  462. chia/consensus/block_rewards.py +53 -0
  463. chia/consensus/blockchain.py +1087 -0
  464. chia/consensus/blockchain_interface.py +56 -0
  465. chia/consensus/coinbase.py +30 -0
  466. chia/consensus/condition_costs.py +9 -0
  467. chia/consensus/constants.py +49 -0
  468. chia/consensus/cost_calculator.py +15 -0
  469. chia/consensus/default_constants.py +89 -0
  470. chia/consensus/deficit.py +55 -0
  471. chia/consensus/difficulty_adjustment.py +412 -0
  472. chia/consensus/find_fork_point.py +111 -0
  473. chia/consensus/full_block_to_block_record.py +167 -0
  474. chia/consensus/get_block_challenge.py +106 -0
  475. chia/consensus/get_block_generator.py +27 -0
  476. chia/consensus/make_sub_epoch_summary.py +210 -0
  477. chia/consensus/multiprocess_validation.py +268 -0
  478. chia/consensus/pos_quality.py +19 -0
  479. chia/consensus/pot_iterations.py +67 -0
  480. chia/consensus/puzzles/__init__.py +0 -0
  481. chia/consensus/puzzles/chialisp_deserialisation.clsp +69 -0
  482. chia/consensus/puzzles/chialisp_deserialisation.clsp.hex +1 -0
  483. chia/consensus/puzzles/rom_bootstrap_generator.clsp +37 -0
  484. chia/consensus/puzzles/rom_bootstrap_generator.clsp.hex +1 -0
  485. chia/consensus/vdf_info_computation.py +156 -0
  486. chia/daemon/__init__.py +0 -0
  487. chia/daemon/client.py +252 -0
  488. chia/daemon/keychain_proxy.py +502 -0
  489. chia/daemon/keychain_server.py +365 -0
  490. chia/daemon/server.py +1606 -0
  491. chia/daemon/windows_signal.py +56 -0
  492. chia/data_layer/__init__.py +0 -0
  493. chia/data_layer/data_layer.py +1291 -0
  494. chia/data_layer/data_layer_api.py +33 -0
  495. chia/data_layer/data_layer_errors.py +50 -0
  496. chia/data_layer/data_layer_server.py +170 -0
  497. chia/data_layer/data_layer_util.py +985 -0
  498. chia/data_layer/data_layer_wallet.py +1311 -0
  499. chia/data_layer/data_store.py +2267 -0
  500. chia/data_layer/dl_wallet_store.py +407 -0
  501. chia/data_layer/download_data.py +389 -0
  502. chia/data_layer/puzzles/__init__.py +0 -0
  503. chia/data_layer/puzzles/graftroot_dl_offers.clsp +100 -0
  504. chia/data_layer/puzzles/graftroot_dl_offers.clsp.hex +1 -0
  505. chia/data_layer/s3_plugin_config.yml +33 -0
  506. chia/data_layer/s3_plugin_service.py +468 -0
  507. chia/data_layer/util/__init__.py +0 -0
  508. chia/data_layer/util/benchmark.py +107 -0
  509. chia/data_layer/util/plugin.py +40 -0
  510. chia/farmer/__init__.py +0 -0
  511. chia/farmer/farmer.py +923 -0
  512. chia/farmer/farmer_api.py +820 -0
  513. chia/full_node/__init__.py +0 -0
  514. chia/full_node/bitcoin_fee_estimator.py +85 -0
  515. chia/full_node/block_height_map.py +271 -0
  516. chia/full_node/block_store.py +576 -0
  517. chia/full_node/bundle_tools.py +19 -0
  518. chia/full_node/coin_store.py +647 -0
  519. chia/full_node/fee_estimate.py +54 -0
  520. chia/full_node/fee_estimate_store.py +24 -0
  521. chia/full_node/fee_estimation.py +92 -0
  522. chia/full_node/fee_estimator.py +90 -0
  523. chia/full_node/fee_estimator_constants.py +38 -0
  524. chia/full_node/fee_estimator_interface.py +42 -0
  525. chia/full_node/fee_history.py +25 -0
  526. chia/full_node/fee_tracker.py +564 -0
  527. chia/full_node/full_node.py +3327 -0
  528. chia/full_node/full_node_api.py +2025 -0
  529. chia/full_node/full_node_store.py +1033 -0
  530. chia/full_node/hint_management.py +56 -0
  531. chia/full_node/hint_store.py +93 -0
  532. chia/full_node/mempool.py +589 -0
  533. chia/full_node/mempool_check_conditions.py +146 -0
  534. chia/full_node/mempool_manager.py +853 -0
  535. chia/full_node/pending_tx_cache.py +112 -0
  536. chia/full_node/puzzles/__init__.py +0 -0
  537. chia/full_node/puzzles/block_program_zero.clsp +14 -0
  538. chia/full_node/puzzles/block_program_zero.clsp.hex +1 -0
  539. chia/full_node/puzzles/decompress_coin_spend_entry.clsp +5 -0
  540. chia/full_node/puzzles/decompress_coin_spend_entry.clsp.hex +1 -0
  541. chia/full_node/puzzles/decompress_coin_spend_entry_with_prefix.clsp +7 -0
  542. chia/full_node/puzzles/decompress_coin_spend_entry_with_prefix.clsp.hex +1 -0
  543. chia/full_node/puzzles/decompress_puzzle.clsp +6 -0
  544. chia/full_node/puzzles/decompress_puzzle.clsp.hex +1 -0
  545. chia/full_node/signage_point.py +16 -0
  546. chia/full_node/subscriptions.py +247 -0
  547. chia/full_node/sync_store.py +146 -0
  548. chia/full_node/tx_processing_queue.py +78 -0
  549. chia/full_node/util/__init__.py +0 -0
  550. chia/full_node/weight_proof.py +1720 -0
  551. chia/harvester/__init__.py +0 -0
  552. chia/harvester/harvester.py +272 -0
  553. chia/harvester/harvester_api.py +380 -0
  554. chia/introducer/__init__.py +0 -0
  555. chia/introducer/introducer.py +122 -0
  556. chia/introducer/introducer_api.py +70 -0
  557. chia/legacy/__init__.py +0 -0
  558. chia/legacy/keyring.py +155 -0
  559. chia/plot_sync/__init__.py +0 -0
  560. chia/plot_sync/delta.py +61 -0
  561. chia/plot_sync/exceptions.py +56 -0
  562. chia/plot_sync/receiver.py +386 -0
  563. chia/plot_sync/sender.py +340 -0
  564. chia/plot_sync/util.py +43 -0
  565. chia/plotters/__init__.py +0 -0
  566. chia/plotters/bladebit.py +388 -0
  567. chia/plotters/chiapos.py +63 -0
  568. chia/plotters/madmax.py +224 -0
  569. chia/plotters/plotters.py +577 -0
  570. chia/plotters/plotters_util.py +133 -0
  571. chia/plotting/__init__.py +0 -0
  572. chia/plotting/cache.py +213 -0
  573. chia/plotting/check_plots.py +283 -0
  574. chia/plotting/create_plots.py +278 -0
  575. chia/plotting/manager.py +436 -0
  576. chia/plotting/util.py +336 -0
  577. chia/pools/__init__.py +0 -0
  578. chia/pools/pool_config.py +110 -0
  579. chia/pools/pool_puzzles.py +459 -0
  580. chia/pools/pool_wallet.py +933 -0
  581. chia/pools/pool_wallet_info.py +118 -0
  582. chia/pools/puzzles/__init__.py +0 -0
  583. chia/pools/puzzles/pool_member_innerpuz.clsp +70 -0
  584. chia/pools/puzzles/pool_member_innerpuz.clsp.hex +1 -0
  585. chia/pools/puzzles/pool_waitingroom_innerpuz.clsp +69 -0
  586. chia/pools/puzzles/pool_waitingroom_innerpuz.clsp.hex +1 -0
  587. chia/protocols/__init__.py +0 -0
  588. chia/protocols/farmer_protocol.py +102 -0
  589. chia/protocols/full_node_protocol.py +219 -0
  590. chia/protocols/harvester_protocol.py +216 -0
  591. chia/protocols/introducer_protocol.py +25 -0
  592. chia/protocols/pool_protocol.py +177 -0
  593. chia/protocols/protocol_message_types.py +139 -0
  594. chia/protocols/protocol_state_machine.py +87 -0
  595. chia/protocols/protocol_timing.py +8 -0
  596. chia/protocols/shared_protocol.py +86 -0
  597. chia/protocols/timelord_protocol.py +93 -0
  598. chia/protocols/wallet_protocol.py +401 -0
  599. chia/py.typed +0 -0
  600. chia/rpc/__init__.py +0 -0
  601. chia/rpc/crawler_rpc_api.py +80 -0
  602. chia/rpc/data_layer_rpc_api.py +644 -0
  603. chia/rpc/data_layer_rpc_client.py +188 -0
  604. chia/rpc/data_layer_rpc_util.py +58 -0
  605. chia/rpc/farmer_rpc_api.py +365 -0
  606. chia/rpc/farmer_rpc_client.py +86 -0
  607. chia/rpc/full_node_rpc_api.py +959 -0
  608. chia/rpc/full_node_rpc_client.py +292 -0
  609. chia/rpc/harvester_rpc_api.py +141 -0
  610. chia/rpc/harvester_rpc_client.py +54 -0
  611. chia/rpc/rpc_client.py +164 -0
  612. chia/rpc/rpc_server.py +521 -0
  613. chia/rpc/timelord_rpc_api.py +32 -0
  614. chia/rpc/util.py +93 -0
  615. chia/rpc/wallet_request_types.py +904 -0
  616. chia/rpc/wallet_rpc_api.py +4943 -0
  617. chia/rpc/wallet_rpc_client.py +1814 -0
  618. chia/seeder/__init__.py +0 -0
  619. chia/seeder/crawl_store.py +425 -0
  620. chia/seeder/crawler.py +410 -0
  621. chia/seeder/crawler_api.py +135 -0
  622. chia/seeder/dns_server.py +593 -0
  623. chia/seeder/peer_record.py +146 -0
  624. chia/seeder/start_crawler.py +92 -0
  625. chia/server/__init__.py +0 -0
  626. chia/server/address_manager.py +658 -0
  627. chia/server/address_manager_store.py +237 -0
  628. chia/server/api_protocol.py +116 -0
  629. chia/server/capabilities.py +24 -0
  630. chia/server/chia_policy.py +346 -0
  631. chia/server/introducer_peers.py +76 -0
  632. chia/server/node_discovery.py +714 -0
  633. chia/server/outbound_message.py +33 -0
  634. chia/server/rate_limit_numbers.py +214 -0
  635. chia/server/rate_limits.py +153 -0
  636. chia/server/server.py +741 -0
  637. chia/server/signal_handlers.py +120 -0
  638. chia/server/ssl_context.py +32 -0
  639. chia/server/start_data_layer.py +151 -0
  640. chia/server/start_farmer.py +98 -0
  641. chia/server/start_full_node.py +112 -0
  642. chia/server/start_harvester.py +93 -0
  643. chia/server/start_introducer.py +81 -0
  644. chia/server/start_service.py +316 -0
  645. chia/server/start_timelord.py +89 -0
  646. chia/server/start_wallet.py +113 -0
  647. chia/server/upnp.py +118 -0
  648. chia/server/ws_connection.py +766 -0
  649. chia/simulator/__init__.py +0 -0
  650. chia/simulator/add_blocks_in_batches.py +54 -0
  651. chia/simulator/block_tools.py +2054 -0
  652. chia/simulator/full_node_simulator.py +794 -0
  653. chia/simulator/keyring.py +128 -0
  654. chia/simulator/setup_services.py +506 -0
  655. chia/simulator/simulator_constants.py +13 -0
  656. chia/simulator/simulator_full_node_rpc_api.py +99 -0
  657. chia/simulator/simulator_full_node_rpc_client.py +60 -0
  658. chia/simulator/simulator_protocol.py +29 -0
  659. chia/simulator/simulator_test_tools.py +164 -0
  660. chia/simulator/socket.py +24 -0
  661. chia/simulator/ssl_certs.py +114 -0
  662. chia/simulator/ssl_certs_1.py +697 -0
  663. chia/simulator/ssl_certs_10.py +697 -0
  664. chia/simulator/ssl_certs_2.py +697 -0
  665. chia/simulator/ssl_certs_3.py +697 -0
  666. chia/simulator/ssl_certs_4.py +697 -0
  667. chia/simulator/ssl_certs_5.py +697 -0
  668. chia/simulator/ssl_certs_6.py +697 -0
  669. chia/simulator/ssl_certs_7.py +697 -0
  670. chia/simulator/ssl_certs_8.py +697 -0
  671. chia/simulator/ssl_certs_9.py +697 -0
  672. chia/simulator/start_simulator.py +143 -0
  673. chia/simulator/wallet_tools.py +246 -0
  674. chia/ssl/__init__.py +0 -0
  675. chia/ssl/chia_ca.crt +19 -0
  676. chia/ssl/chia_ca.key +28 -0
  677. chia/ssl/create_ssl.py +249 -0
  678. chia/ssl/dst_root_ca.pem +20 -0
  679. chia/timelord/__init__.py +0 -0
  680. chia/timelord/iters_from_block.py +50 -0
  681. chia/timelord/timelord.py +1226 -0
  682. chia/timelord/timelord_api.py +138 -0
  683. chia/timelord/timelord_launcher.py +190 -0
  684. chia/timelord/timelord_state.py +244 -0
  685. chia/timelord/types.py +22 -0
  686. chia/types/__init__.py +0 -0
  687. chia/types/aliases.py +35 -0
  688. chia/types/block_protocol.py +20 -0
  689. chia/types/blockchain_format/__init__.py +0 -0
  690. chia/types/blockchain_format/classgroup.py +5 -0
  691. chia/types/blockchain_format/coin.py +28 -0
  692. chia/types/blockchain_format/foliage.py +8 -0
  693. chia/types/blockchain_format/pool_target.py +5 -0
  694. chia/types/blockchain_format/program.py +269 -0
  695. chia/types/blockchain_format/proof_of_space.py +135 -0
  696. chia/types/blockchain_format/reward_chain_block.py +6 -0
  697. chia/types/blockchain_format/serialized_program.py +5 -0
  698. chia/types/blockchain_format/sized_bytes.py +11 -0
  699. chia/types/blockchain_format/slots.py +9 -0
  700. chia/types/blockchain_format/sub_epoch_summary.py +5 -0
  701. chia/types/blockchain_format/tree_hash.py +72 -0
  702. chia/types/blockchain_format/vdf.py +86 -0
  703. chia/types/clvm_cost.py +13 -0
  704. chia/types/coin_record.py +43 -0
  705. chia/types/coin_spend.py +115 -0
  706. chia/types/condition_opcodes.py +73 -0
  707. chia/types/condition_with_args.py +16 -0
  708. chia/types/eligible_coin_spends.py +365 -0
  709. chia/types/end_of_slot_bundle.py +5 -0
  710. chia/types/fee_rate.py +38 -0
  711. chia/types/full_block.py +5 -0
  712. chia/types/generator_types.py +13 -0
  713. chia/types/header_block.py +5 -0
  714. chia/types/internal_mempool_item.py +18 -0
  715. chia/types/mempool_inclusion_status.py +9 -0
  716. chia/types/mempool_item.py +85 -0
  717. chia/types/mempool_submission_status.py +30 -0
  718. chia/types/mojos.py +7 -0
  719. chia/types/peer_info.py +64 -0
  720. chia/types/signing_mode.py +29 -0
  721. chia/types/spend_bundle.py +30 -0
  722. chia/types/spend_bundle_conditions.py +7 -0
  723. chia/types/transaction_queue_entry.py +55 -0
  724. chia/types/unfinished_block.py +5 -0
  725. chia/types/unfinished_header_block.py +37 -0
  726. chia/types/validation_state.py +14 -0
  727. chia/types/weight_proof.py +49 -0
  728. chia/util/__init__.py +0 -0
  729. chia/util/action_scope.py +168 -0
  730. chia/util/async_pool.py +226 -0
  731. chia/util/augmented_chain.py +134 -0
  732. chia/util/batches.py +42 -0
  733. chia/util/bech32m.py +126 -0
  734. chia/util/beta_metrics.py +119 -0
  735. chia/util/block_cache.py +56 -0
  736. chia/util/byte_types.py +12 -0
  737. chia/util/check_fork_next_block.py +33 -0
  738. chia/util/chia_logging.py +144 -0
  739. chia/util/chia_version.py +33 -0
  740. chia/util/collection.py +17 -0
  741. chia/util/condition_tools.py +201 -0
  742. chia/util/config.py +367 -0
  743. chia/util/cpu.py +22 -0
  744. chia/util/db_synchronous.py +23 -0
  745. chia/util/db_version.py +32 -0
  746. chia/util/db_wrapper.py +430 -0
  747. chia/util/default_root.py +27 -0
  748. chia/util/dump_keyring.py +93 -0
  749. chia/util/english.txt +2048 -0
  750. chia/util/errors.py +353 -0
  751. chia/util/file_keyring.py +469 -0
  752. chia/util/files.py +97 -0
  753. chia/util/full_block_utils.py +345 -0
  754. chia/util/generator_tools.py +72 -0
  755. chia/util/hash.py +31 -0
  756. chia/util/initial-config.yaml +694 -0
  757. chia/util/inline_executor.py +26 -0
  758. chia/util/ints.py +19 -0
  759. chia/util/ip_address.py +39 -0
  760. chia/util/json_util.py +37 -0
  761. chia/util/keychain.py +676 -0
  762. chia/util/keyring_wrapper.py +327 -0
  763. chia/util/limited_semaphore.py +41 -0
  764. chia/util/lock.py +49 -0
  765. chia/util/log_exceptions.py +32 -0
  766. chia/util/logging.py +36 -0
  767. chia/util/lru_cache.py +31 -0
  768. chia/util/math.py +20 -0
  769. chia/util/network.py +182 -0
  770. chia/util/paginator.py +48 -0
  771. chia/util/path.py +31 -0
  772. chia/util/permissions.py +20 -0
  773. chia/util/prev_transaction_block.py +21 -0
  774. chia/util/priority_mutex.py +95 -0
  775. chia/util/profiler.py +197 -0
  776. chia/util/recursive_replace.py +24 -0
  777. chia/util/safe_cancel_task.py +16 -0
  778. chia/util/service_groups.py +47 -0
  779. chia/util/setproctitle.py +22 -0
  780. chia/util/significant_bits.py +32 -0
  781. chia/util/ssl_check.py +213 -0
  782. chia/util/streamable.py +642 -0
  783. chia/util/task_referencer.py +59 -0
  784. chia/util/task_timing.py +382 -0
  785. chia/util/timing.py +67 -0
  786. chia/util/vdf_prover.py +30 -0
  787. chia/util/virtual_project_analysis.py +540 -0
  788. chia/util/ws_message.py +66 -0
  789. chia/wallet/__init__.py +0 -0
  790. chia/wallet/cat_wallet/__init__.py +0 -0
  791. chia/wallet/cat_wallet/cat_constants.py +75 -0
  792. chia/wallet/cat_wallet/cat_info.py +47 -0
  793. chia/wallet/cat_wallet/cat_outer_puzzle.py +120 -0
  794. chia/wallet/cat_wallet/cat_utils.py +164 -0
  795. chia/wallet/cat_wallet/cat_wallet.py +855 -0
  796. chia/wallet/cat_wallet/dao_cat_info.py +28 -0
  797. chia/wallet/cat_wallet/dao_cat_wallet.py +669 -0
  798. chia/wallet/cat_wallet/lineage_store.py +74 -0
  799. chia/wallet/cat_wallet/puzzles/__init__.py +0 -0
  800. chia/wallet/cat_wallet/puzzles/cat_truths.clib +31 -0
  801. chia/wallet/cat_wallet/puzzles/cat_v2.clsp +397 -0
  802. chia/wallet/cat_wallet/puzzles/cat_v2.clsp.hex +1 -0
  803. chia/wallet/cat_wallet/puzzles/delegated_tail.clsp +25 -0
  804. chia/wallet/cat_wallet/puzzles/delegated_tail.clsp.hex +1 -0
  805. chia/wallet/cat_wallet/puzzles/everything_with_signature.clsp +15 -0
  806. chia/wallet/cat_wallet/puzzles/everything_with_signature.clsp.hex +1 -0
  807. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id.clsp +26 -0
  808. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id.clsp.hex +1 -0
  809. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id_or_singleton.clsp +42 -0
  810. chia/wallet/cat_wallet/puzzles/genesis_by_coin_id_or_singleton.clsp.hex +1 -0
  811. chia/wallet/cat_wallet/puzzles/genesis_by_puzzle_hash.clsp +24 -0
  812. chia/wallet/cat_wallet/puzzles/genesis_by_puzzle_hash.clsp.hex +1 -0
  813. chia/wallet/coin_selection.py +188 -0
  814. chia/wallet/conditions.py +1512 -0
  815. chia/wallet/dao_wallet/__init__.py +0 -0
  816. chia/wallet/dao_wallet/dao_info.py +61 -0
  817. chia/wallet/dao_wallet/dao_utils.py +811 -0
  818. chia/wallet/dao_wallet/dao_wallet.py +2119 -0
  819. chia/wallet/db_wallet/__init__.py +0 -0
  820. chia/wallet/db_wallet/db_wallet_puzzles.py +111 -0
  821. chia/wallet/derivation_record.py +30 -0
  822. chia/wallet/derive_keys.py +146 -0
  823. chia/wallet/did_wallet/__init__.py +0 -0
  824. chia/wallet/did_wallet/did_info.py +39 -0
  825. chia/wallet/did_wallet/did_wallet.py +1494 -0
  826. chia/wallet/did_wallet/did_wallet_puzzles.py +221 -0
  827. chia/wallet/did_wallet/puzzles/__init__.py +0 -0
  828. chia/wallet/did_wallet/puzzles/did_innerpuz.clsp +135 -0
  829. chia/wallet/did_wallet/puzzles/did_innerpuz.clsp.hex +1 -0
  830. chia/wallet/driver_protocol.py +26 -0
  831. chia/wallet/key_val_store.py +55 -0
  832. chia/wallet/lineage_proof.py +58 -0
  833. chia/wallet/nft_wallet/__init__.py +0 -0
  834. chia/wallet/nft_wallet/metadata_outer_puzzle.py +92 -0
  835. chia/wallet/nft_wallet/nft_info.py +120 -0
  836. chia/wallet/nft_wallet/nft_puzzles.py +305 -0
  837. chia/wallet/nft_wallet/nft_wallet.py +1687 -0
  838. chia/wallet/nft_wallet/ownership_outer_puzzle.py +101 -0
  839. chia/wallet/nft_wallet/puzzles/__init__.py +0 -0
  840. chia/wallet/nft_wallet/puzzles/create_nft_launcher_from_did.clsp +6 -0
  841. chia/wallet/nft_wallet/puzzles/create_nft_launcher_from_did.clsp.hex +1 -0
  842. chia/wallet/nft_wallet/puzzles/nft_intermediate_launcher.clsp +6 -0
  843. chia/wallet/nft_wallet/puzzles/nft_intermediate_launcher.clsp.hex +1 -0
  844. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_default.clsp +30 -0
  845. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_default.clsp.hex +1 -0
  846. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_updateable.clsp +28 -0
  847. chia/wallet/nft_wallet/puzzles/nft_metadata_updater_updateable.clsp.hex +1 -0
  848. chia/wallet/nft_wallet/puzzles/nft_ownership_layer.clsp +100 -0
  849. chia/wallet/nft_wallet/puzzles/nft_ownership_layer.clsp.hex +1 -0
  850. chia/wallet/nft_wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clsp +78 -0
  851. chia/wallet/nft_wallet/puzzles/nft_ownership_transfer_program_one_way_claim_with_royalties.clsp.hex +1 -0
  852. chia/wallet/nft_wallet/puzzles/nft_state_layer.clsp +74 -0
  853. chia/wallet/nft_wallet/puzzles/nft_state_layer.clsp.hex +1 -0
  854. chia/wallet/nft_wallet/singleton_outer_puzzle.py +101 -0
  855. chia/wallet/nft_wallet/transfer_program_puzzle.py +82 -0
  856. chia/wallet/nft_wallet/uncurry_nft.py +217 -0
  857. chia/wallet/notification_manager.py +117 -0
  858. chia/wallet/notification_store.py +178 -0
  859. chia/wallet/outer_puzzles.py +84 -0
  860. chia/wallet/payment.py +33 -0
  861. chia/wallet/puzzle_drivers.py +118 -0
  862. chia/wallet/puzzles/__init__.py +0 -0
  863. chia/wallet/puzzles/augmented_condition.clsp +13 -0
  864. chia/wallet/puzzles/augmented_condition.clsp.hex +1 -0
  865. chia/wallet/puzzles/clawback/__init__.py +0 -0
  866. chia/wallet/puzzles/clawback/drivers.py +188 -0
  867. chia/wallet/puzzles/clawback/metadata.py +38 -0
  868. chia/wallet/puzzles/clawback/puzzle_decorator.py +67 -0
  869. chia/wallet/puzzles/condition_codes.clib +77 -0
  870. chia/wallet/puzzles/curry-and-treehash.clib +102 -0
  871. chia/wallet/puzzles/curry.clib +135 -0
  872. chia/wallet/puzzles/curry_by_index.clib +16 -0
  873. chia/wallet/puzzles/dao_cat_eve.clsp +17 -0
  874. chia/wallet/puzzles/dao_cat_eve.clsp.hex +1 -0
  875. chia/wallet/puzzles/dao_cat_launcher.clsp +36 -0
  876. chia/wallet/puzzles/dao_cat_launcher.clsp.hex +1 -0
  877. chia/wallet/puzzles/dao_finished_state.clsp +35 -0
  878. chia/wallet/puzzles/dao_finished_state.clsp.hex +1 -0
  879. chia/wallet/puzzles/dao_finished_state.clsp.hex.sha256tree +1 -0
  880. chia/wallet/puzzles/dao_lockup.clsp +288 -0
  881. chia/wallet/puzzles/dao_lockup.clsp.hex +1 -0
  882. chia/wallet/puzzles/dao_lockup.clsp.hex.sha256tree +1 -0
  883. chia/wallet/puzzles/dao_proposal.clsp +377 -0
  884. chia/wallet/puzzles/dao_proposal.clsp.hex +1 -0
  885. chia/wallet/puzzles/dao_proposal.clsp.hex.sha256tree +1 -0
  886. chia/wallet/puzzles/dao_proposal_timer.clsp +78 -0
  887. chia/wallet/puzzles/dao_proposal_timer.clsp.hex +1 -0
  888. chia/wallet/puzzles/dao_proposal_timer.clsp.hex.sha256tree +1 -0
  889. chia/wallet/puzzles/dao_proposal_validator.clsp +87 -0
  890. chia/wallet/puzzles/dao_proposal_validator.clsp.hex +1 -0
  891. chia/wallet/puzzles/dao_proposal_validator.clsp.hex.sha256tree +1 -0
  892. chia/wallet/puzzles/dao_spend_p2_singleton_v2.clsp +240 -0
  893. chia/wallet/puzzles/dao_spend_p2_singleton_v2.clsp.hex +1 -0
  894. chia/wallet/puzzles/dao_spend_p2_singleton_v2.clsp.hex.sha256tree +1 -0
  895. chia/wallet/puzzles/dao_treasury.clsp +115 -0
  896. chia/wallet/puzzles/dao_treasury.clsp.hex +1 -0
  897. chia/wallet/puzzles/dao_update_proposal.clsp +44 -0
  898. chia/wallet/puzzles/dao_update_proposal.clsp.hex +1 -0
  899. chia/wallet/puzzles/deployed_puzzle_hashes.json +67 -0
  900. chia/wallet/puzzles/json.clib +25 -0
  901. chia/wallet/puzzles/load_clvm.py +161 -0
  902. chia/wallet/puzzles/merkle_utils.clib +18 -0
  903. chia/wallet/puzzles/notification.clsp +7 -0
  904. chia/wallet/puzzles/notification.clsp.hex +1 -0
  905. chia/wallet/puzzles/p2_1_of_n.clsp +22 -0
  906. chia/wallet/puzzles/p2_1_of_n.clsp.hex +1 -0
  907. chia/wallet/puzzles/p2_conditions.clsp +3 -0
  908. chia/wallet/puzzles/p2_conditions.clsp.hex +1 -0
  909. chia/wallet/puzzles/p2_conditions.py +26 -0
  910. chia/wallet/puzzles/p2_delegated_conditions.clsp +18 -0
  911. chia/wallet/puzzles/p2_delegated_conditions.clsp.hex +1 -0
  912. chia/wallet/puzzles/p2_delegated_conditions.py +21 -0
  913. chia/wallet/puzzles/p2_delegated_puzzle.clsp +19 -0
  914. chia/wallet/puzzles/p2_delegated_puzzle.clsp.hex +1 -0
  915. chia/wallet/puzzles/p2_delegated_puzzle.py +34 -0
  916. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clsp +91 -0
  917. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.clsp.hex +1 -0
  918. chia/wallet/puzzles/p2_delegated_puzzle_or_hidden_puzzle.py +160 -0
  919. chia/wallet/puzzles/p2_m_of_n_delegate_direct.clsp +108 -0
  920. chia/wallet/puzzles/p2_m_of_n_delegate_direct.clsp.hex +1 -0
  921. chia/wallet/puzzles/p2_m_of_n_delegate_direct.py +21 -0
  922. chia/wallet/puzzles/p2_parent.clsp +19 -0
  923. chia/wallet/puzzles/p2_parent.clsp.hex +1 -0
  924. chia/wallet/puzzles/p2_puzzle_hash.clsp +18 -0
  925. chia/wallet/puzzles/p2_puzzle_hash.clsp.hex +1 -0
  926. chia/wallet/puzzles/p2_puzzle_hash.py +27 -0
  927. chia/wallet/puzzles/p2_singleton.clsp +30 -0
  928. chia/wallet/puzzles/p2_singleton.clsp.hex +1 -0
  929. chia/wallet/puzzles/p2_singleton_aggregator.clsp +81 -0
  930. chia/wallet/puzzles/p2_singleton_aggregator.clsp.hex +1 -0
  931. chia/wallet/puzzles/p2_singleton_or_delayed_puzhash.clsp +50 -0
  932. chia/wallet/puzzles/p2_singleton_or_delayed_puzhash.clsp.hex +1 -0
  933. chia/wallet/puzzles/p2_singleton_via_delegated_puzzle.clsp +47 -0
  934. chia/wallet/puzzles/p2_singleton_via_delegated_puzzle.clsp.hex +1 -0
  935. chia/wallet/puzzles/puzzle_utils.py +34 -0
  936. chia/wallet/puzzles/settlement_payments.clsp +49 -0
  937. chia/wallet/puzzles/settlement_payments.clsp.hex +1 -0
  938. chia/wallet/puzzles/sha256tree.clib +11 -0
  939. chia/wallet/puzzles/singleton_launcher.clsp +16 -0
  940. chia/wallet/puzzles/singleton_launcher.clsp.hex +1 -0
  941. chia/wallet/puzzles/singleton_top_layer.clsp +177 -0
  942. chia/wallet/puzzles/singleton_top_layer.clsp.hex +1 -0
  943. chia/wallet/puzzles/singleton_top_layer.py +296 -0
  944. chia/wallet/puzzles/singleton_top_layer_v1_1.clsp +107 -0
  945. chia/wallet/puzzles/singleton_top_layer_v1_1.clsp.hex +1 -0
  946. chia/wallet/puzzles/singleton_top_layer_v1_1.py +345 -0
  947. chia/wallet/puzzles/singleton_truths.clib +21 -0
  948. chia/wallet/puzzles/tails.py +348 -0
  949. chia/wallet/puzzles/utility_macros.clib +48 -0
  950. chia/wallet/signer_protocol.py +125 -0
  951. chia/wallet/singleton.py +106 -0
  952. chia/wallet/singleton_record.py +30 -0
  953. chia/wallet/trade_manager.py +1102 -0
  954. chia/wallet/trade_record.py +67 -0
  955. chia/wallet/trading/__init__.py +0 -0
  956. chia/wallet/trading/offer.py +702 -0
  957. chia/wallet/trading/trade_status.py +13 -0
  958. chia/wallet/trading/trade_store.py +526 -0
  959. chia/wallet/transaction_record.py +158 -0
  960. chia/wallet/transaction_sorting.py +14 -0
  961. chia/wallet/uncurried_puzzle.py +17 -0
  962. chia/wallet/util/__init__.py +0 -0
  963. chia/wallet/util/address_type.py +55 -0
  964. chia/wallet/util/blind_signer_tl.py +164 -0
  965. chia/wallet/util/clvm_streamable.py +203 -0
  966. chia/wallet/util/compute_hints.py +66 -0
  967. chia/wallet/util/compute_memos.py +43 -0
  968. chia/wallet/util/curry_and_treehash.py +91 -0
  969. chia/wallet/util/debug_spend_bundle.py +232 -0
  970. chia/wallet/util/merkle_tree.py +100 -0
  971. chia/wallet/util/merkle_utils.py +102 -0
  972. chia/wallet/util/new_peak_queue.py +82 -0
  973. chia/wallet/util/notifications.py +12 -0
  974. chia/wallet/util/peer_request_cache.py +174 -0
  975. chia/wallet/util/pprint.py +39 -0
  976. chia/wallet/util/puzzle_compression.py +95 -0
  977. chia/wallet/util/puzzle_decorator.py +100 -0
  978. chia/wallet/util/puzzle_decorator_type.py +7 -0
  979. chia/wallet/util/query_filter.py +59 -0
  980. chia/wallet/util/transaction_type.py +23 -0
  981. chia/wallet/util/tx_config.py +158 -0
  982. chia/wallet/util/wallet_sync_utils.py +351 -0
  983. chia/wallet/util/wallet_types.py +72 -0
  984. chia/wallet/vc_wallet/__init__.py +0 -0
  985. chia/wallet/vc_wallet/cr_cat_drivers.py +664 -0
  986. chia/wallet/vc_wallet/cr_cat_wallet.py +877 -0
  987. chia/wallet/vc_wallet/cr_outer_puzzle.py +102 -0
  988. chia/wallet/vc_wallet/cr_puzzles/__init__.py +0 -0
  989. chia/wallet/vc_wallet/cr_puzzles/conditions_w_fee_announce.clsp +3 -0
  990. chia/wallet/vc_wallet/cr_puzzles/conditions_w_fee_announce.clsp.hex +1 -0
  991. chia/wallet/vc_wallet/cr_puzzles/credential_restriction.clsp +304 -0
  992. chia/wallet/vc_wallet/cr_puzzles/credential_restriction.clsp.hex +1 -0
  993. chia/wallet/vc_wallet/cr_puzzles/flag_proofs_checker.clsp +45 -0
  994. chia/wallet/vc_wallet/cr_puzzles/flag_proofs_checker.clsp.hex +1 -0
  995. chia/wallet/vc_wallet/vc_drivers.py +838 -0
  996. chia/wallet/vc_wallet/vc_puzzles/__init__.py +0 -0
  997. chia/wallet/vc_wallet/vc_puzzles/covenant_layer.clsp +30 -0
  998. chia/wallet/vc_wallet/vc_puzzles/covenant_layer.clsp.hex +1 -0
  999. chia/wallet/vc_wallet/vc_puzzles/eml_covenant_morpher.clsp +75 -0
  1000. chia/wallet/vc_wallet/vc_puzzles/eml_covenant_morpher.clsp.hex +1 -0
  1001. chia/wallet/vc_wallet/vc_puzzles/eml_transfer_program_covenant_adapter.clsp +32 -0
  1002. chia/wallet/vc_wallet/vc_puzzles/eml_transfer_program_covenant_adapter.clsp.hex +1 -0
  1003. chia/wallet/vc_wallet/vc_puzzles/eml_update_metadata_with_DID.clsp +80 -0
  1004. chia/wallet/vc_wallet/vc_puzzles/eml_update_metadata_with_DID.clsp.hex +1 -0
  1005. chia/wallet/vc_wallet/vc_puzzles/exigent_metadata_layer.clsp +163 -0
  1006. chia/wallet/vc_wallet/vc_puzzles/exigent_metadata_layer.clsp.hex +1 -0
  1007. chia/wallet/vc_wallet/vc_puzzles/p2_announced_delegated_puzzle.clsp +16 -0
  1008. chia/wallet/vc_wallet/vc_puzzles/p2_announced_delegated_puzzle.clsp.hex +1 -0
  1009. chia/wallet/vc_wallet/vc_puzzles/standard_vc_backdoor_puzzle.clsp +74 -0
  1010. chia/wallet/vc_wallet/vc_puzzles/standard_vc_backdoor_puzzle.clsp.hex +1 -0
  1011. chia/wallet/vc_wallet/vc_puzzles/std_parent_morpher.clsp +23 -0
  1012. chia/wallet/vc_wallet/vc_puzzles/std_parent_morpher.clsp.hex +1 -0
  1013. chia/wallet/vc_wallet/vc_puzzles/viral_backdoor.clsp +64 -0
  1014. chia/wallet/vc_wallet/vc_puzzles/viral_backdoor.clsp.hex +1 -0
  1015. chia/wallet/vc_wallet/vc_store.py +263 -0
  1016. chia/wallet/vc_wallet/vc_wallet.py +638 -0
  1017. chia/wallet/wallet.py +698 -0
  1018. chia/wallet/wallet_action_scope.py +96 -0
  1019. chia/wallet/wallet_blockchain.py +244 -0
  1020. chia/wallet/wallet_coin_record.py +72 -0
  1021. chia/wallet/wallet_coin_store.py +351 -0
  1022. chia/wallet/wallet_info.py +35 -0
  1023. chia/wallet/wallet_interested_store.py +188 -0
  1024. chia/wallet/wallet_nft_store.py +279 -0
  1025. chia/wallet/wallet_node.py +1765 -0
  1026. chia/wallet/wallet_node_api.py +207 -0
  1027. chia/wallet/wallet_pool_store.py +119 -0
  1028. chia/wallet/wallet_protocol.py +90 -0
  1029. chia/wallet/wallet_puzzle_store.py +396 -0
  1030. chia/wallet/wallet_retry_store.py +70 -0
  1031. chia/wallet/wallet_singleton_store.py +259 -0
  1032. chia/wallet/wallet_spend_bundle.py +25 -0
  1033. chia/wallet/wallet_state_manager.py +2819 -0
  1034. chia/wallet/wallet_transaction_store.py +496 -0
  1035. chia/wallet/wallet_user_store.py +110 -0
  1036. chia/wallet/wallet_weight_proof_handler.py +126 -0
  1037. chia_blockchain-2.5.1rc1.dist-info/LICENSE +201 -0
  1038. chia_blockchain-2.5.1rc1.dist-info/METADATA +156 -0
  1039. chia_blockchain-2.5.1rc1.dist-info/RECORD +1042 -0
  1040. chia_blockchain-2.5.1rc1.dist-info/WHEEL +4 -0
  1041. chia_blockchain-2.5.1rc1.dist-info/entry_points.txt +17 -0
  1042. mozilla-ca/cacert.pem +3611 -0
@@ -0,0 +1,2819 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import dataclasses
6
+ import logging
7
+ import multiprocessing.context
8
+ import time
9
+ import traceback
10
+ from collections.abc import AsyncIterator, Iterator
11
+ from contextlib import asynccontextmanager
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union
14
+
15
+ import aiosqlite
16
+ from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey
17
+
18
+ from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
19
+ from chia.consensus.coinbase import farmer_parent_id, pool_parent_id
20
+ from chia.consensus.constants import ConsensusConstants
21
+ from chia.data_layer.data_layer_wallet import DataLayerWallet
22
+ from chia.data_layer.dl_wallet_store import DataLayerStore
23
+ from chia.pools.pool_puzzles import (
24
+ SINGLETON_LAUNCHER_HASH,
25
+ get_most_recent_singleton_coin_from_coin_spend,
26
+ solution_to_pool_state,
27
+ )
28
+ from chia.pools.pool_wallet import PoolWallet
29
+ from chia.protocols.wallet_protocol import CoinState
30
+ from chia.rpc.rpc_server import StateChangedProtocol
31
+ from chia.server.outbound_message import NodeType
32
+ from chia.server.server import ChiaServer
33
+ from chia.server.ws_connection import WSChiaConnection
34
+ from chia.types.blockchain_format.coin import Coin
35
+ from chia.types.blockchain_format.program import Program
36
+ from chia.types.blockchain_format.sized_bytes import bytes32
37
+ from chia.types.coin_record import CoinRecord
38
+ from chia.types.coin_spend import CoinSpend, compute_additions
39
+ from chia.types.mempool_inclusion_status import MempoolInclusionStatus
40
+ from chia.util.bech32m import encode_puzzle_hash
41
+ from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
42
+ from chia.util.db_synchronous import db_synchronous_on
43
+ from chia.util.db_wrapper import DBWrapper2
44
+ from chia.util.errors import Err
45
+ from chia.util.hash import std_hash
46
+ from chia.util.ints import uint16, uint32, uint64, uint128
47
+ from chia.util.lru_cache import LRUCache
48
+ from chia.util.path import path_from_root
49
+ from chia.util.streamable import Streamable, UInt32Range, UInt64Range, VersionedBlob
50
+ from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
51
+ from chia.wallet.cat_wallet.cat_info import CATCoinData, CATInfo, CRCATInfo
52
+ from chia.wallet.cat_wallet.cat_utils import CAT_MOD, CAT_MOD_HASH, construct_cat_puzzle, match_cat_puzzle
53
+ from chia.wallet.cat_wallet.cat_wallet import CATWallet
54
+ from chia.wallet.cat_wallet.dao_cat_wallet import DAOCATWallet
55
+ from chia.wallet.conditions import (
56
+ AssertCoinAnnouncement,
57
+ Condition,
58
+ ConditionValidTimes,
59
+ CreateCoinAnnouncement,
60
+ parse_timelock_info,
61
+ )
62
+ from chia.wallet.dao_wallet.dao_utils import (
63
+ get_p2_singleton_puzhash,
64
+ match_dao_cat_puzzle,
65
+ match_finished_puzzle,
66
+ match_funding_puzzle,
67
+ match_proposal_puzzle,
68
+ match_treasury_puzzle,
69
+ )
70
+ from chia.wallet.dao_wallet.dao_wallet import DAOWallet
71
+ from chia.wallet.db_wallet.db_wallet_puzzles import MIRROR_PUZZLE_HASH
72
+ from chia.wallet.derivation_record import DerivationRecord
73
+ from chia.wallet.derive_keys import (
74
+ _derive_path,
75
+ _derive_pk_unhardened,
76
+ master_pk_to_wallet_pk_unhardened,
77
+ master_pk_to_wallet_pk_unhardened_intermediate,
78
+ master_sk_to_wallet_sk,
79
+ master_sk_to_wallet_sk_intermediate,
80
+ master_sk_to_wallet_sk_unhardened,
81
+ )
82
+ from chia.wallet.did_wallet.did_info import DIDCoinData
83
+ from chia.wallet.did_wallet.did_wallet import DIDWallet
84
+ from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, match_did_puzzle
85
+ from chia.wallet.key_val_store import KeyValStore
86
+ from chia.wallet.nft_wallet.nft_puzzles import get_metadata_and_phs, get_new_owner_did
87
+ from chia.wallet.nft_wallet.nft_wallet import NFTWallet
88
+ from chia.wallet.nft_wallet.uncurry_nft import NFTCoinData, UncurriedNFT
89
+ from chia.wallet.notification_manager import NotificationManager
90
+ from chia.wallet.outer_puzzles import AssetType
91
+ from chia.wallet.payment import Payment
92
+ from chia.wallet.puzzle_drivers import PuzzleInfo
93
+ from chia.wallet.puzzles.clawback.drivers import generate_clawback_spend_bundle, match_clawback_puzzle
94
+ from chia.wallet.puzzles.clawback.metadata import ClawbackMetadata, ClawbackVersion
95
+ from chia.wallet.signer_protocol import (
96
+ KeyHints,
97
+ PathHint,
98
+ SignedTransaction,
99
+ SigningInstructions,
100
+ SigningResponse,
101
+ SigningTarget,
102
+ Spend,
103
+ SumHint,
104
+ TransactionInfo,
105
+ UnsignedTransaction,
106
+ )
107
+ from chia.wallet.singleton import create_singleton_puzzle, get_inner_puzzle_from_singleton, get_singleton_id_from_puzzle
108
+ from chia.wallet.trade_manager import TradeManager
109
+ from chia.wallet.trading.offer import Offer
110
+ from chia.wallet.trading.trade_status import TradeStatus
111
+ from chia.wallet.transaction_record import LightTransactionRecord, TransactionRecord
112
+ from chia.wallet.uncurried_puzzle import uncurry_puzzle
113
+ from chia.wallet.util.address_type import AddressType
114
+ from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
115
+ from chia.wallet.util.compute_memos import compute_memos
116
+ from chia.wallet.util.curry_and_treehash import NIL_TREEHASH
117
+ from chia.wallet.util.puzzle_decorator import PuzzleDecoratorManager
118
+ from chia.wallet.util.query_filter import HashFilter
119
+ from chia.wallet.util.transaction_type import CLAWBACK_INCOMING_TRANSACTION_TYPES, TransactionType
120
+ from chia.wallet.util.tx_config import TXConfig, TXConfigLoader
121
+ from chia.wallet.util.wallet_sync_utils import (
122
+ PeerRequestException,
123
+ fetch_coin_spend_for_coin_state,
124
+ last_change_height_cs,
125
+ )
126
+ from chia.wallet.util.wallet_types import CoinType, WalletIdentifier, WalletType
127
+ from chia.wallet.vc_wallet.cr_cat_drivers import CRCAT, ProofsChecker, construct_pending_approval_state
128
+ from chia.wallet.vc_wallet.cr_cat_wallet import CRCATWallet
129
+ from chia.wallet.vc_wallet.vc_drivers import VerifiedCredential
130
+ from chia.wallet.vc_wallet.vc_store import VCStore
131
+ from chia.wallet.vc_wallet.vc_wallet import VCWallet
132
+ from chia.wallet.wallet import Wallet
133
+ from chia.wallet.wallet_action_scope import WalletActionScope, new_wallet_action_scope
134
+ from chia.wallet.wallet_blockchain import WalletBlockchain
135
+ from chia.wallet.wallet_coin_record import MetadataTypes, WalletCoinRecord
136
+ from chia.wallet.wallet_coin_store import WalletCoinStore
137
+ from chia.wallet.wallet_info import WalletInfo
138
+ from chia.wallet.wallet_interested_store import WalletInterestedStore
139
+ from chia.wallet.wallet_nft_store import WalletNftStore
140
+ from chia.wallet.wallet_pool_store import WalletPoolStore
141
+ from chia.wallet.wallet_protocol import WalletProtocol
142
+ from chia.wallet.wallet_puzzle_store import WalletPuzzleStore
143
+ from chia.wallet.wallet_retry_store import WalletRetryStore
144
+ from chia.wallet.wallet_spend_bundle import WalletSpendBundle
145
+ from chia.wallet.wallet_transaction_store import WalletTransactionStore
146
+ from chia.wallet.wallet_user_store import WalletUserStore
147
+
148
+ TWalletType = TypeVar("TWalletType", bound=WalletProtocol[Any])
149
+
150
+ if TYPE_CHECKING:
151
+ from chia.wallet.wallet_node import WalletNode
152
+
153
+
154
+ PendingTxCallback = Callable[[], None]
155
+
156
+
157
+ class WalletStateManager:
158
+ # Ruff thinks these are "mutable class attributes" that should be annotated with `ClassVar`
159
+ # When this is a dataclass, these errors should go away
160
+ interested_ph_cache: dict[bytes32, list[int]] = {} # noqa: RUF012
161
+ interested_coin_cache: dict[bytes32, list[int]] = {} # noqa: RUF012
162
+ constants: ConsensusConstants
163
+ config: dict[str, Any]
164
+ tx_store: WalletTransactionStore
165
+ puzzle_store: WalletPuzzleStore
166
+ user_store: WalletUserStore
167
+ nft_store: WalletNftStore
168
+ vc_store: VCStore
169
+ basic_store: KeyValStore
170
+
171
+ # Makes sure only one asyncio thread is changing the blockchain state at one time
172
+ lock: asyncio.Lock
173
+
174
+ log: logging.Logger
175
+
176
+ # TODO Don't allow user to send tx until wallet is synced
177
+ _sync_target: Optional[uint32]
178
+
179
+ state_changed_callback: Optional[StateChangedProtocol] = None
180
+ pending_tx_callback: Optional[PendingTxCallback]
181
+ db_path: Path
182
+ db_wrapper: DBWrapper2
183
+
184
+ main_wallet: Wallet
185
+ wallets: dict[uint32, WalletProtocol[Any]]
186
+ private_key: Optional[PrivateKey]
187
+ root_pubkey: G1Element
188
+
189
+ trade_manager: TradeManager
190
+ notification_manager: NotificationManager
191
+ blockchain: WalletBlockchain
192
+ coin_store: WalletCoinStore
193
+ interested_store: WalletInterestedStore
194
+ retry_store: WalletRetryStore
195
+ multiprocessing_context: multiprocessing.context.BaseContext
196
+ server: ChiaServer
197
+ root_path: Path
198
+ wallet_node: WalletNode
199
+ pool_store: WalletPoolStore
200
+ dl_store: DataLayerStore
201
+ default_cats: dict[str, Any]
202
+ asset_to_wallet_map: dict[AssetType, Any]
203
+ initial_num_public_keys: int
204
+ decorator_manager: PuzzleDecoratorManager
205
+
206
+ @staticmethod
207
+ async def create(
208
+ private_key: Optional[PrivateKey],
209
+ config: dict[str, Any],
210
+ db_path: Path,
211
+ constants: ConsensusConstants,
212
+ server: ChiaServer,
213
+ root_path: Path,
214
+ wallet_node: WalletNode,
215
+ root_pubkey: Optional[G1Element] = None,
216
+ ) -> WalletStateManager:
217
+ self = WalletStateManager()
218
+
219
+ self.config = config
220
+ self.constants = constants
221
+ self.server = server
222
+ self.root_path = root_path
223
+ self.log = logging.getLogger(__name__)
224
+ self.lock = asyncio.Lock()
225
+ self.log.debug(f"Starting in db path: {db_path}")
226
+ sql_log_path: Optional[Path] = None
227
+ if self.config.get("log_sqlite_cmds", False):
228
+ sql_log_path = path_from_root(self.root_path, "log/wallet_sql.log")
229
+ self.log.info(f"logging SQL commands to {sql_log_path}")
230
+
231
+ self.db_wrapper = await DBWrapper2.create(
232
+ database=db_path,
233
+ reader_count=self.config.get("db_readers", 4),
234
+ log_path=sql_log_path,
235
+ synchronous=db_synchronous_on(self.config.get("db_sync", "auto")),
236
+ )
237
+
238
+ self.initial_num_public_keys = config["initial_num_public_keys"]
239
+ min_num_public_keys = 425
240
+ if not config.get("testing", False) and self.initial_num_public_keys < min_num_public_keys:
241
+ self.initial_num_public_keys = min_num_public_keys
242
+
243
+ self.coin_store = await WalletCoinStore.create(self.db_wrapper)
244
+ self.tx_store = await WalletTransactionStore.create(self.db_wrapper)
245
+ self.puzzle_store = await WalletPuzzleStore.create(self.db_wrapper)
246
+ self.user_store = await WalletUserStore.create(self.db_wrapper)
247
+ self.nft_store = await WalletNftStore.create(self.db_wrapper)
248
+ self.vc_store = await VCStore.create(self.db_wrapper)
249
+ self.basic_store = await KeyValStore.create(self.db_wrapper)
250
+ self.trade_manager = await TradeManager.create(self, self.db_wrapper)
251
+ self.notification_manager = await NotificationManager.create(self, self.db_wrapper)
252
+ self.pool_store = await WalletPoolStore.create(self.db_wrapper)
253
+ self.dl_store = await DataLayerStore.create(self.db_wrapper)
254
+ self.interested_store = await WalletInterestedStore.create(self.db_wrapper)
255
+ self.retry_store = await WalletRetryStore.create(self.db_wrapper)
256
+ self.default_cats = DEFAULT_CATS
257
+
258
+ self.wallet_node = wallet_node
259
+ self._sync_target = None
260
+ self.blockchain = await WalletBlockchain.create(self.basic_store, self.constants)
261
+ self.state_changed_callback = None
262
+ self.pending_tx_callback = None
263
+ self.db_path = db_path
264
+
265
+ main_wallet_info = await self.user_store.get_wallet_by_id(1)
266
+ assert main_wallet_info is not None
267
+
268
+ self.private_key = private_key
269
+ if private_key is None: # pragma: no cover
270
+ if root_pubkey is None:
271
+ raise ValueError("WalletStateManager requires either a root private key or root public key")
272
+ else:
273
+ self.root_pubkey = root_pubkey
274
+ else:
275
+ calculated_root_public_key: G1Element = private_key.get_g1()
276
+ if root_pubkey is not None:
277
+ assert root_pubkey == calculated_root_public_key
278
+ self.root_pubkey = calculated_root_public_key
279
+
280
+ fingerprint = self.root_pubkey.get_fingerprint()
281
+ puzzle_decorators = self.config.get("puzzle_decorators", {}).get(fingerprint, [])
282
+ self.decorator_manager = PuzzleDecoratorManager.create(puzzle_decorators)
283
+
284
+ self.main_wallet = await Wallet.create(self, main_wallet_info)
285
+
286
+ self.wallets = {main_wallet_info.id: self.main_wallet}
287
+
288
+ self.asset_to_wallet_map = {
289
+ AssetType.CAT: CATWallet,
290
+ }
291
+
292
+ wallet: Optional[WalletProtocol[Any]] = None
293
+ for wallet_info in await self.get_all_wallet_info_entries():
294
+ wallet_type = WalletType(wallet_info.type)
295
+ if wallet_type == WalletType.STANDARD_WALLET:
296
+ if wallet_info.id == 1:
297
+ continue
298
+ wallet = await Wallet.create(self, wallet_info)
299
+ elif wallet_type == WalletType.CAT:
300
+ wallet = await CATWallet.create(
301
+ self,
302
+ self.main_wallet,
303
+ wallet_info,
304
+ )
305
+ elif wallet_type == WalletType.DECENTRALIZED_ID:
306
+ wallet = await DIDWallet.create(
307
+ self,
308
+ self.main_wallet,
309
+ wallet_info,
310
+ )
311
+ elif wallet_type == WalletType.NFT:
312
+ wallet = await NFTWallet.create(
313
+ self,
314
+ self.main_wallet,
315
+ wallet_info,
316
+ )
317
+ elif wallet_type == WalletType.POOLING_WALLET:
318
+ wallet = await PoolWallet.create_from_db(
319
+ self,
320
+ self.main_wallet,
321
+ wallet_info,
322
+ )
323
+ elif wallet_type == WalletType.DATA_LAYER: # pragma: no cover
324
+ wallet = await DataLayerWallet.create(
325
+ self,
326
+ wallet_info,
327
+ )
328
+ elif wallet_type == WalletType.DAO: # pragma: no cover
329
+ wallet = await DAOWallet.create(
330
+ self,
331
+ self.main_wallet,
332
+ wallet_info,
333
+ )
334
+ elif wallet_type == WalletType.DAO_CAT: # pragma: no cover
335
+ wallet = await DAOCATWallet.create(
336
+ self,
337
+ self.main_wallet,
338
+ wallet_info,
339
+ )
340
+ elif wallet_type == WalletType.VC: # pragma: no cover
341
+ wallet = await VCWallet.create(
342
+ self,
343
+ self.main_wallet,
344
+ wallet_info,
345
+ )
346
+ elif wallet_type == WalletType.CRCAT: # pragma: no cover
347
+ wallet = await CRCATWallet.create(
348
+ self,
349
+ self.main_wallet,
350
+ wallet_info,
351
+ )
352
+ if wallet is not None:
353
+ self.wallets[wallet_info.id] = wallet
354
+
355
+ return self
356
+
357
+ def get_public_key_unhardened(self, index: uint32) -> G1Element:
358
+ return master_pk_to_wallet_pk_unhardened(self.root_pubkey, index)
359
+
360
+ async def get_private_key(self, puzzle_hash: bytes32) -> PrivateKey:
361
+ record = await self.puzzle_store.record_for_puzzle_hash(puzzle_hash)
362
+ if record is None:
363
+ raise ValueError(f"No key for puzzle hash: {puzzle_hash.hex()}")
364
+ if record.hardened:
365
+ return master_sk_to_wallet_sk(self.get_master_private_key(), record.index)
366
+ return master_sk_to_wallet_sk_unhardened(self.get_master_private_key(), record.index)
367
+
368
+ async def get_public_key(self, puzzle_hash: bytes32) -> bytes:
369
+ record = await self.puzzle_store.record_for_puzzle_hash(puzzle_hash)
370
+ if record is None:
371
+ raise ValueError(f"No key for puzzle hash: {puzzle_hash.hex()}")
372
+ if isinstance(record._pubkey, bytes):
373
+ pk_bytes = record._pubkey
374
+ else:
375
+ pk_bytes = bytes(record._pubkey)
376
+ return pk_bytes
377
+
378
+ def get_master_private_key(self) -> PrivateKey:
379
+ if self.private_key is None: # pragma: no cover
380
+ raise ValueError("Wallet is currently in observer mode and access to private key is denied")
381
+
382
+ return self.private_key
383
+
384
+ def get_wallet(self, id: uint32, required_type: type[TWalletType]) -> TWalletType:
385
+ wallet = self.wallets[id]
386
+ if not isinstance(wallet, required_type):
387
+ raise Exception(
388
+ f"wallet id {id} is of type {type(wallet).__name__} but type {required_type.__name__} is required",
389
+ )
390
+
391
+ return wallet
392
+
393
+ async def create_more_puzzle_hashes(
394
+ self,
395
+ from_zero: bool = False,
396
+ mark_existing_as_used: bool = True,
397
+ up_to_index: Optional[uint32] = None,
398
+ num_additional_phs: Optional[int] = None,
399
+ ) -> None:
400
+ """
401
+ For all wallets in the user store, generates the first few puzzle hashes so
402
+ that we can restore the wallet from only the private keys.
403
+ """
404
+ targets = list(self.wallets.keys())
405
+ self.log.debug("Target wallets to generate puzzle hashes for: %s", repr(targets))
406
+ unused: Optional[uint32] = (
407
+ uint32(up_to_index + 1) if up_to_index is not None else await self.puzzle_store.get_unused_derivation_path()
408
+ )
409
+ if unused is None:
410
+ # This handles the case where the database has entries but they have all been used
411
+ unused = await self.puzzle_store.get_last_derivation_path()
412
+ self.log.debug("Tried finding unused: %s", unused)
413
+ if unused is None:
414
+ # This handles the case where the database is empty
415
+ unused = uint32(0)
416
+
417
+ self.log.debug(f"Requested to generate puzzle hashes to at least index {unused}")
418
+ start_t = time.time()
419
+ to_generate = num_additional_phs if num_additional_phs is not None else self.initial_num_public_keys
420
+
421
+ # iterate all wallets that need derived keys and establish the start
422
+ # index for all of them
423
+ start_index_by_wallet: dict[uint32, int] = {}
424
+ last_index = unused + to_generate
425
+ for wallet_id in targets:
426
+ target_wallet = self.wallets[wallet_id]
427
+ if not target_wallet.require_derivation_paths():
428
+ self.log.debug("Skipping wallet %s as no derivation paths required", wallet_id)
429
+ continue
430
+ if from_zero:
431
+ start_index_by_wallet[wallet_id] = 0
432
+ continue
433
+ last: Optional[uint32] = await self.puzzle_store.get_last_derivation_path_for_wallet(wallet_id)
434
+ if last is not None:
435
+ if last + 1 >= last_index:
436
+ self.log.debug(f"Nothing to create for for wallet_id: {wallet_id}, index: {last_index}")
437
+ continue
438
+ start_index_by_wallet[wallet_id] = last + 1
439
+ else:
440
+ start_index_by_wallet[wallet_id] = 0
441
+
442
+ if len(start_index_by_wallet) == 0:
443
+ return
444
+
445
+ lowest_start_index = min(start_index_by_wallet.values())
446
+
447
+ # now derive the keysfrom lowest_start_index to last_index
448
+ # these maps derivation index to public key
449
+ hardened_keys: dict[int, G1Element] = {}
450
+ unhardened_keys: dict[int, G1Element] = {}
451
+
452
+ if self.private_key is not None:
453
+ # Hardened
454
+ intermediate_sk = master_sk_to_wallet_sk_intermediate(self.private_key)
455
+ for index in range(lowest_start_index, last_index):
456
+ hardened_keys[index] = _derive_path(intermediate_sk, [index]).get_g1()
457
+
458
+ # Unhardened
459
+ intermediate_pk_un = master_pk_to_wallet_pk_unhardened_intermediate(self.root_pubkey)
460
+ for index in range(lowest_start_index, last_index):
461
+ unhardened_keys[index] = _derive_pk_unhardened(intermediate_pk_un, [index])
462
+
463
+ for wallet_id, start_index in start_index_by_wallet.items():
464
+ target_wallet = self.wallets[wallet_id]
465
+ assert target_wallet.type() != WalletType.POOLING_WALLET
466
+ assert start_index < last_index
467
+
468
+ derivation_paths: list[DerivationRecord] = []
469
+ creating_msg = f"Creating puzzle hashes from {start_index} to {last_index - 1} for wallet_id: {wallet_id}"
470
+ self.log.info(f"Start: {creating_msg}")
471
+ for index in range(start_index, last_index):
472
+ pubkey: Optional[G1Element] = hardened_keys.get(index)
473
+ if pubkey is not None:
474
+ # Hardened
475
+ puzzlehash: bytes32 = target_wallet.puzzle_hash_for_pk(pubkey)
476
+ self.log.debug(f"Puzzle at index {index} wallet ID {wallet_id} puzzle hash {puzzlehash.hex()}")
477
+ derivation_paths.append(
478
+ DerivationRecord(
479
+ uint32(index),
480
+ puzzlehash,
481
+ pubkey,
482
+ target_wallet.type(),
483
+ uint32(target_wallet.id()),
484
+ True,
485
+ )
486
+ )
487
+ # Unhardened
488
+ pubkey = unhardened_keys.get(index)
489
+ assert pubkey is not None
490
+ puzzlehash_unhardened: bytes32 = target_wallet.puzzle_hash_for_pk(pubkey)
491
+ self.log.debug(
492
+ f"Puzzle at index {index} wallet ID {wallet_id} puzzle hash {puzzlehash_unhardened.hex()}"
493
+ )
494
+ derivation_paths.append(
495
+ DerivationRecord(
496
+ uint32(index),
497
+ puzzlehash_unhardened,
498
+ pubkey,
499
+ target_wallet.type(),
500
+ uint32(target_wallet.id()),
501
+ False,
502
+ )
503
+ )
504
+ self.log.info(f"Done: {creating_msg} Time: {time.time() - start_t} seconds")
505
+ if len(derivation_paths) > 0:
506
+ await self.puzzle_store.add_derivation_paths(derivation_paths)
507
+ if wallet_id == self.main_wallet.id():
508
+ await self.wallet_node.new_peak_queue.subscribe_to_puzzle_hashes(
509
+ [record.puzzle_hash for record in derivation_paths]
510
+ )
511
+ if len(unhardened_keys) > 0:
512
+ self.state_changed("new_derivation_index", data_object={"index": last_index - 1})
513
+ # By default, we'll mark previously generated unused puzzle hashes as used if we have new paths
514
+ if mark_existing_as_used and unused > 0 and len(unhardened_keys) > 0:
515
+ self.log.info(f"Updating last used derivation index: {unused - 1}")
516
+ await self.puzzle_store.set_used_up_to(uint32(unused - 1))
517
+
518
+ async def update_wallet_puzzle_hashes(self, wallet_id: uint32) -> None:
519
+ derivation_paths: list[DerivationRecord] = []
520
+ target_wallet = self.wallets[wallet_id]
521
+ last: Optional[uint32] = await self.puzzle_store.get_last_derivation_path_for_wallet(wallet_id)
522
+ unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path_for_wallet(wallet_id)
523
+ if unused is None:
524
+ # This handles the case where the database has entries but they have all been used
525
+ unused = await self.puzzle_store.get_last_derivation_path()
526
+ if unused is None:
527
+ # This handles the case where the database is empty
528
+ unused = uint32(0)
529
+ if last is not None:
530
+ for index in range(unused, last):
531
+ # Since DID are not released yet we can assume they are only using unhardened keys derivation
532
+ pubkey: G1Element = self.get_public_key_unhardened(uint32(index))
533
+ puzzlehash = target_wallet.puzzle_hash_for_pk(pubkey)
534
+ self.log.info(f"Generating public key at index {index} puzzle hash {puzzlehash.hex()}")
535
+ derivation_paths.append(
536
+ DerivationRecord(
537
+ uint32(index),
538
+ puzzlehash,
539
+ pubkey,
540
+ WalletType(target_wallet.wallet_info.type),
541
+ uint32(target_wallet.wallet_info.id),
542
+ False,
543
+ )
544
+ )
545
+ await self.puzzle_store.add_derivation_paths(derivation_paths)
546
+
547
+ async def get_unused_derivation_record(self, wallet_id: uint32, *, hardened: bool = False) -> DerivationRecord:
548
+ """
549
+ Creates a puzzle hash for the given wallet, and then makes more puzzle hashes
550
+ for every wallet to ensure we always have more in the database. Never reusue the
551
+ same public key more than once (for privacy).
552
+ """
553
+ async with self.puzzle_store.lock:
554
+ # If we have no unused public keys, we will create new ones
555
+ unused: Optional[uint32] = await self.puzzle_store.get_unused_derivation_path()
556
+ if unused is None:
557
+ self.log.debug("No unused paths, generate more ")
558
+ await self.create_more_puzzle_hashes()
559
+ # Now we must have unused public keys
560
+ unused = await self.puzzle_store.get_unused_derivation_path()
561
+ assert unused is not None
562
+
563
+ self.log.debug("Fetching derivation record for: %s %s %s", unused, wallet_id, hardened)
564
+ record: Optional[DerivationRecord] = await self.puzzle_store.get_derivation_record(
565
+ unused, wallet_id, hardened
566
+ )
567
+ if record is None:
568
+ raise ValueError(f"Missing derivation '{unused}' for wallet id '{wallet_id}' (hardened={hardened})")
569
+
570
+ # Set this key to used so we never use it again
571
+ await self.puzzle_store.set_used_up_to(record.index)
572
+
573
+ # Create more puzzle hashes / keys
574
+ await self.create_more_puzzle_hashes()
575
+ return record
576
+
577
+ async def get_current_derivation_record_for_wallet(self, wallet_id: uint32) -> Optional[DerivationRecord]:
578
+ async with self.puzzle_store.lock:
579
+ # If we have no unused public keys, we will create new ones
580
+ current: Optional[DerivationRecord] = await self.puzzle_store.get_current_derivation_record_for_wallet(
581
+ wallet_id
582
+ )
583
+ return current
584
+
585
+ def set_callback(self, callback: StateChangedProtocol) -> None:
586
+ """
587
+ Callback to be called when the state of the wallet changes.
588
+ """
589
+ self.state_changed_callback = callback
590
+
591
+ def set_pending_callback(self, callback: PendingTxCallback) -> None:
592
+ """
593
+ Callback to be called when new pending transaction enters the store
594
+ """
595
+ self.pending_tx_callback = callback
596
+
597
+ def state_changed(
598
+ self, state: str, wallet_id: Optional[int] = None, data_object: Optional[dict[str, Any]] = None
599
+ ) -> None:
600
+ """
601
+ Calls the callback if it's present.
602
+ """
603
+ if self.state_changed_callback is None:
604
+ return None
605
+ change_data: dict[str, Any] = {"state": state}
606
+ if wallet_id is not None:
607
+ change_data["wallet_id"] = wallet_id
608
+ if data_object is not None:
609
+ change_data["additional_data"] = data_object
610
+ self.state_changed_callback(state, change_data)
611
+
612
+ def tx_pending_changed(self) -> None:
613
+ """
614
+ Notifies the wallet node that there's new tx pending
615
+ """
616
+ if self.pending_tx_callback is None:
617
+ return None
618
+
619
+ self.pending_tx_callback()
620
+
621
+ async def synced(self, block_is_current_at: Optional[int] = None) -> bool:
622
+ if block_is_current_at is None:
623
+ block_is_current_at = int(time.time() - 60 * 5)
624
+ if len(self.server.get_connections(NodeType.FULL_NODE)) == 0:
625
+ return False
626
+
627
+ latest = await self.blockchain.get_peak_block()
628
+ if latest is None:
629
+ return False
630
+
631
+ if "simulator" in self.config.get("selected_network", ""):
632
+ return True # sim is always synced if we have a genesis block.
633
+
634
+ if latest.height - await self.blockchain.get_finished_sync_up_to() > 1:
635
+ return False
636
+
637
+ latest_timestamp = self.blockchain.get_latest_timestamp()
638
+ has_pending_queue_items = self.wallet_node.new_peak_queue.has_pending_data_process_items()
639
+
640
+ if latest_timestamp > block_is_current_at and not has_pending_queue_items:
641
+ return True
642
+ return False
643
+
644
+ @property
645
+ def sync_mode(self) -> bool:
646
+ return self._sync_target is not None
647
+
648
+ @property
649
+ def sync_target(self) -> Optional[uint32]:
650
+ return self._sync_target
651
+
652
+ @asynccontextmanager
653
+ async def set_sync_mode(self, target_height: uint32) -> AsyncIterator[uint32]:
654
+ if self.log.level == logging.DEBUG:
655
+ self.log.debug(f"set_sync_mode enter {await self.blockchain.get_finished_sync_up_to()}-{target_height}")
656
+ async with self.lock:
657
+ self._sync_target = target_height
658
+ start_time = time.time()
659
+ start_height = await self.blockchain.get_finished_sync_up_to()
660
+ self.log.info(f"set_sync_mode syncing - range: {start_height}-{target_height}")
661
+ self.state_changed("sync_changed")
662
+ try:
663
+ yield start_height
664
+ except Exception:
665
+ self.log.exception(
666
+ f"set_sync_mode failed - range: {start_height}-{target_height}, seconds: {time.time() - start_time}"
667
+ )
668
+ finally:
669
+ self.state_changed("sync_changed")
670
+ if self.log.level == logging.DEBUG:
671
+ self.log.debug(
672
+ f"set_sync_mode exit - range: {start_height}-{target_height}, "
673
+ f"get_finished_sync_up_to: {await self.blockchain.get_finished_sync_up_to()}, "
674
+ f"seconds: {time.time() - start_time}"
675
+ )
676
+ self._sync_target = None
677
+
678
+ async def get_confirmed_spendable_balance_for_wallet(
679
+ self, wallet_id: int, unspent_records: Optional[set[WalletCoinRecord]] = None
680
+ ) -> uint128:
681
+ """
682
+ Returns the balance amount of all coins that are spendable.
683
+ """
684
+
685
+ spendable: set[WalletCoinRecord] = await self.get_spendable_coins_for_wallet(wallet_id, unspent_records)
686
+
687
+ spendable_amount: uint128 = uint128(0)
688
+ for record in spendable:
689
+ spendable_amount = uint128(spendable_amount + record.coin.amount)
690
+
691
+ return spendable_amount
692
+
693
+ async def does_coin_belong_to_wallet(
694
+ self, coin: Coin, wallet_id: int, hint_dict: dict[bytes32, bytes32] = {}
695
+ ) -> bool:
696
+ """
697
+ Returns true if we have the key for this coin.
698
+ """
699
+ wallet_identifier = await self.get_wallet_identifier_for_coin(coin, hint_dict)
700
+ return wallet_identifier is not None and wallet_identifier.id == wallet_id
701
+
702
+ async def get_confirmed_balance_for_wallet(
703
+ self,
704
+ wallet_id: int,
705
+ unspent_coin_records: Optional[set[WalletCoinRecord]] = None,
706
+ ) -> uint128:
707
+ """
708
+ Returns the confirmed balance, including coinbase rewards that are not spendable.
709
+ """
710
+ # lock only if unspent_coin_records is None
711
+ if unspent_coin_records is None:
712
+ if self.wallets[uint32(wallet_id)].type() == WalletType.CRCAT:
713
+ coin_type = CoinType.CRCAT
714
+ else:
715
+ coin_type = CoinType.NORMAL
716
+ unspent_coin_records = await self.coin_store.get_unspent_coins_for_wallet(wallet_id, coin_type)
717
+ return uint128(sum(cr.coin.amount for cr in unspent_coin_records))
718
+
719
+ async def get_unconfirmed_balance(
720
+ self, wallet_id: int, unspent_coin_records: Optional[set[WalletCoinRecord]] = None
721
+ ) -> uint128:
722
+ """
723
+ Returns the balance, including coinbase rewards that are not spendable, and unconfirmed
724
+ transactions.
725
+ """
726
+ # This API should change so that get_balance_from_coin_records is called for set[WalletCoinRecord]
727
+ # and this method is called only for the unspent_coin_records==None case.
728
+ if unspent_coin_records is None:
729
+ wallet_type: WalletType = self.wallets[uint32(wallet_id)].type()
730
+ if wallet_type == WalletType.CRCAT:
731
+ unspent_coin_records = await self.coin_store.get_unspent_coins_for_wallet(wallet_id, CoinType.CRCAT)
732
+ pending_crcat = await self.coin_store.get_unspent_coins_for_wallet(wallet_id, CoinType.CRCAT_PENDING)
733
+ unspent_coin_records = unspent_coin_records.union(pending_crcat)
734
+ else:
735
+ unspent_coin_records = await self.coin_store.get_unspent_coins_for_wallet(wallet_id)
736
+
737
+ unconfirmed_tx: list[TransactionRecord] = await self.tx_store.get_unconfirmed_for_wallet(wallet_id)
738
+ all_unspent_coins: set[Coin] = {cr.coin for cr in unspent_coin_records}
739
+
740
+ for record in unconfirmed_tx:
741
+ if record.type in CLAWBACK_INCOMING_TRANSACTION_TYPES:
742
+ # We do not wish to consider clawback-able funds as unconfirmed.
743
+ # That is reserved for when the action to actually claw a tx back or forward is initiated.
744
+ continue
745
+ for addition in record.additions:
746
+ # This change or a self transaction
747
+ if await self.does_coin_belong_to_wallet(addition, wallet_id, record.hint_dict()):
748
+ all_unspent_coins.add(addition)
749
+
750
+ for removal in record.removals:
751
+ if (
752
+ await self.does_coin_belong_to_wallet(removal, wallet_id, record.hint_dict())
753
+ and removal in all_unspent_coins
754
+ ):
755
+ all_unspent_coins.remove(removal)
756
+
757
+ return uint128(sum(coin.amount for coin in all_unspent_coins))
758
+
759
+ async def unconfirmed_removals_for_wallet(self, wallet_id: int) -> dict[bytes32, Coin]:
760
+ """
761
+ Returns new removals transactions that have not been confirmed yet.
762
+ """
763
+ removals: dict[bytes32, Coin] = {}
764
+ unconfirmed_tx = await self.tx_store.get_unconfirmed_for_wallet(wallet_id)
765
+ for record in unconfirmed_tx:
766
+ if record.type in CLAWBACK_INCOMING_TRANSACTION_TYPES:
767
+ # We do not wish to consider clawback-able funds as pending removal.
768
+ # That is reserved for when the action to actually claw a tx back or forward is initiated.
769
+ continue
770
+ for coin in record.removals:
771
+ if coin not in record.additions:
772
+ removals[coin.name()] = coin
773
+ trade_removals: dict[bytes32, WalletCoinRecord] = await self.trade_manager.get_locked_coins()
774
+ return {**removals, **{coin_id: cr.coin for coin_id, cr in trade_removals.items() if cr.wallet_id == wallet_id}}
775
+
776
+ async def determine_coin_type(
777
+ self, peer: WSChiaConnection, coin_state: CoinState, fork_height: Optional[uint32]
778
+ ) -> tuple[Optional[WalletIdentifier], Optional[Streamable]]:
779
+ if coin_state.created_height is not None and (
780
+ self.is_pool_reward(uint32(coin_state.created_height), coin_state.coin)
781
+ or self.is_farmer_reward(uint32(coin_state.created_height), coin_state.coin)
782
+ ):
783
+ return None, None
784
+
785
+ response: list[CoinState] = await self.wallet_node.get_coin_state(
786
+ [coin_state.coin.parent_coin_info], peer=peer, fork_height=fork_height
787
+ )
788
+ if len(response) == 0:
789
+ self.log.warning(f"Could not find a parent coin with ID: {coin_state.coin.parent_coin_info.hex()}")
790
+ return None, None
791
+ parent_coin_state = response[0]
792
+ assert parent_coin_state.spent_height == coin_state.created_height
793
+
794
+ coin_spend = await fetch_coin_spend_for_coin_state(parent_coin_state, peer)
795
+
796
+ uncurried = uncurry_puzzle(coin_spend.puzzle_reveal)
797
+
798
+ dao_ids = []
799
+ wallets = self.wallets.values()
800
+ for wallet in wallets:
801
+ if wallet.type() == WalletType.DAO.value:
802
+ assert isinstance(wallet, DAOWallet)
803
+ dao_ids.append(wallet.dao_info.treasury_id)
804
+ funding_puzzle_check = match_funding_puzzle(
805
+ uncurried, coin_spend.solution.to_program(), coin_state.coin, dao_ids
806
+ )
807
+ if funding_puzzle_check:
808
+ return await self.get_dao_wallet_from_coinspend_hint(coin_spend, coin_state), None
809
+
810
+ # Check if the coin is a DAO Treasury
811
+ dao_curried_args = match_treasury_puzzle(uncurried.mod, uncurried.args)
812
+ if dao_curried_args is not None:
813
+ return await self.handle_dao_treasury(dao_curried_args, parent_coin_state, coin_state, coin_spend), None
814
+ # Check if the coin is a Proposal and that it isn't the timer coin (amount == 0)
815
+ dao_curried_args = match_proposal_puzzle(uncurried.mod, uncurried.args)
816
+ if (dao_curried_args is not None) and (coin_state.coin.amount != 0):
817
+ return await self.handle_dao_proposal(dao_curried_args, parent_coin_state, coin_state, coin_spend), None
818
+
819
+ # Check if the coin is a finished proposal
820
+ dao_curried_args = match_finished_puzzle(uncurried.mod, uncurried.args)
821
+ if dao_curried_args is not None:
822
+ return (
823
+ await self.handle_dao_finished_proposals(dao_curried_args, parent_coin_state, coin_state, coin_spend),
824
+ None,
825
+ )
826
+
827
+ # Check if the coin is a DAO CAT
828
+ dao_cat_args = match_dao_cat_puzzle(uncurried)
829
+ if dao_cat_args:
830
+ return await self.handle_dao_cat(dao_cat_args, parent_coin_state, coin_state, coin_spend, fork_height), None
831
+
832
+ # Check if the coin is a CAT
833
+ cat_curried_args = match_cat_puzzle(uncurried)
834
+ if cat_curried_args is not None:
835
+ cat_mod_hash, tail_program_hash, cat_inner_puzzle = cat_curried_args
836
+ cat_data: CATCoinData = CATCoinData(
837
+ bytes32(cat_mod_hash.as_atom()),
838
+ bytes32(tail_program_hash.as_atom()),
839
+ cat_inner_puzzle,
840
+ parent_coin_state.coin.parent_coin_info,
841
+ uint64(parent_coin_state.coin.amount),
842
+ )
843
+ return (
844
+ await self.handle_cat(
845
+ cat_data,
846
+ parent_coin_state,
847
+ coin_state,
848
+ coin_spend,
849
+ peer,
850
+ fork_height,
851
+ ),
852
+ cat_data,
853
+ )
854
+
855
+ # Check if the coin is a NFT
856
+ # hint
857
+ # First spend where 1 mojo coin -> Singleton launcher -> NFT -> NFT
858
+ uncurried_nft = UncurriedNFT.uncurry(uncurried.mod, uncurried.args)
859
+ if uncurried_nft is not None and coin_state.coin.amount % 2 == 1:
860
+ nft_data = NFTCoinData(uncurried_nft, parent_coin_state, coin_spend)
861
+ return await self.handle_nft(nft_data), nft_data
862
+
863
+ # Check if the coin is a DID
864
+ did_curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
865
+ if did_curried_args is not None and coin_state.coin.amount % 2 == 1:
866
+ p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = did_curried_args
867
+ did_data: DIDCoinData = DIDCoinData(
868
+ p2_puzzle,
869
+ bytes32(recovery_list_hash.as_atom()),
870
+ uint16(num_verification.as_int()),
871
+ singleton_struct,
872
+ metadata,
873
+ get_inner_puzzle_from_singleton(coin_spend.puzzle_reveal),
874
+ parent_coin_state,
875
+ )
876
+ return await self.handle_did(did_data, parent_coin_state, coin_state, coin_spend, peer), did_data
877
+
878
+ # Check if the coin is clawback
879
+ clawback_coin_data = match_clawback_puzzle(uncurried, coin_spend.puzzle_reveal, coin_spend.solution)
880
+ if clawback_coin_data is not None:
881
+ return await self.handle_clawback(clawback_coin_data, coin_state, coin_spend, peer), clawback_coin_data
882
+
883
+ # Check if the coin is a VC
884
+ is_vc, _err_msg = VerifiedCredential.is_vc(uncurried)
885
+ if is_vc:
886
+ vc: VerifiedCredential = VerifiedCredential.get_next_from_coin_spend(coin_spend)
887
+ return await self.handle_vc(vc), vc
888
+
889
+ await self.notification_manager.potentially_add_new_notification(coin_state, coin_spend)
890
+
891
+ return None, None
892
+
893
+ async def auto_claim_coins(self) -> None:
894
+ # Get unspent clawback coin
895
+ current_timestamp = self.blockchain.get_latest_timestamp()
896
+ clawback_coins: dict[Coin, ClawbackMetadata] = {}
897
+ tx_fee = uint64(self.config.get("auto_claim", {}).get("tx_fee", 0))
898
+ assert self.wallet_node.logged_in_fingerprint is not None
899
+ tx_config_loader: TXConfigLoader = TXConfigLoader.from_json_dict(self.config.get("auto_claim", {}))
900
+ if tx_config_loader.min_coin_amount is None:
901
+ tx_config_loader = tx_config_loader.override(
902
+ min_coin_amount=self.config.get("auto_claim", {}).get("min_amount"),
903
+ )
904
+ tx_config: TXConfig = tx_config_loader.autofill(
905
+ constants=self.constants,
906
+ config=self.config,
907
+ logged_in_fingerprint=self.wallet_node.logged_in_fingerprint,
908
+ )
909
+ unspent_coins = await self.coin_store.get_coin_records(
910
+ coin_type=CoinType.CLAWBACK,
911
+ wallet_type=WalletType.STANDARD_WALLET,
912
+ spent_range=UInt32Range(stop=uint32(0)),
913
+ amount_range=UInt64Range(
914
+ start=tx_config.coin_selection_config.min_coin_amount,
915
+ stop=tx_config.coin_selection_config.max_coin_amount,
916
+ ),
917
+ )
918
+ async with self.new_action_scope(tx_config, push=True) as action_scope:
919
+ for coin in unspent_coins.records:
920
+ try:
921
+ metadata: MetadataTypes = coin.parsed_metadata()
922
+ assert isinstance(metadata, ClawbackMetadata)
923
+ if await metadata.is_recipient(self.puzzle_store):
924
+ coin_timestamp = await self.wallet_node.get_timestamp_for_height(coin.confirmed_block_height)
925
+ if current_timestamp - coin_timestamp >= metadata.time_lock:
926
+ clawback_coins[coin.coin] = metadata
927
+ if len(clawback_coins) >= self.config.get("auto_claim", {}).get("batch_size", 50):
928
+ await self.spend_clawback_coins(clawback_coins, tx_fee, action_scope)
929
+ clawback_coins = {}
930
+ except Exception as e:
931
+ self.log.error(f"Failed to claim clawback coin {coin.coin.name().hex()}: %s", e)
932
+ if len(clawback_coins) > 0:
933
+ await self.spend_clawback_coins(clawback_coins, tx_fee, action_scope)
934
+
935
+ async def spend_clawback_coins(
936
+ self,
937
+ clawback_coins: dict[Coin, ClawbackMetadata],
938
+ fee: uint64,
939
+ action_scope: WalletActionScope,
940
+ force: bool = False,
941
+ extra_conditions: tuple[Condition, ...] = tuple(),
942
+ ) -> None:
943
+ assert len(clawback_coins) > 0
944
+ coin_spends: list[CoinSpend] = []
945
+ message: bytes32 = std_hash(b"".join([c.name() for c in clawback_coins.keys()]))
946
+ now: uint64 = uint64(int(time.time()))
947
+ derivation_record: Optional[DerivationRecord] = None
948
+ amount: uint64 = uint64(0)
949
+ for coin, metadata in clawback_coins.items():
950
+ try:
951
+ self.log.info(f"Claiming clawback coin {coin.name().hex()}")
952
+ # Get incoming tx
953
+ incoming_tx = await self.tx_store.get_transaction_record(coin.name())
954
+ assert incoming_tx is not None, f"Cannot find incoming tx for clawback coin {coin.name().hex()}"
955
+ if incoming_tx.sent > 0 and not force:
956
+ self.log.error(
957
+ f"Clawback coin {coin.name().hex()} is already in a pending spend bundle. {incoming_tx}"
958
+ )
959
+ continue
960
+
961
+ recipient_puzhash: bytes32 = metadata.recipient_puzzle_hash
962
+ sender_puzhash: bytes32 = metadata.sender_puzzle_hash
963
+ is_recipient: bool = await metadata.is_recipient(self.puzzle_store)
964
+ if is_recipient:
965
+ derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(recipient_puzhash)
966
+ else:
967
+ derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(sender_puzhash)
968
+ assert derivation_record is not None
969
+ amount = uint64(amount + coin.amount)
970
+ # Remove the clawback hint since it is unnecessary for the XCH coin
971
+ memos: list[bytes] = [] if len(incoming_tx.memos) == 0 else incoming_tx.memos[0][1][1:]
972
+ inner_puzzle: Program = self.main_wallet.puzzle_for_pk(derivation_record.pubkey)
973
+ inner_solution: Program = self.main_wallet.make_solution(
974
+ primaries=[
975
+ Payment(
976
+ derivation_record.puzzle_hash,
977
+ uint64(coin.amount),
978
+ memos, # Forward memo of the first coin
979
+ )
980
+ ],
981
+ conditions=(
982
+ extra_conditions
983
+ if len(coin_spends) > 0 or fee == 0
984
+ else (*extra_conditions, CreateCoinAnnouncement(message))
985
+ ),
986
+ )
987
+ coin_spend: CoinSpend = generate_clawback_spend_bundle(coin, metadata, inner_puzzle, inner_solution)
988
+ coin_spends.append(coin_spend)
989
+ # Update incoming tx to prevent double spend and mark it is pending
990
+ await self.tx_store.increment_sent(incoming_tx.name, "", MempoolInclusionStatus.PENDING, None)
991
+ except Exception as e:
992
+ self.log.error(f"Failed to create clawback spend bundle for {coin.name().hex()}: {e}")
993
+ if len(coin_spends) == 0:
994
+ return
995
+ spend_bundle = WalletSpendBundle(coin_spends, G2Element())
996
+ if fee > 0:
997
+ async with self.new_action_scope(action_scope.config.tx_config, push=False) as inner_action_scope:
998
+ async with action_scope.use() as interface:
999
+ async with inner_action_scope.use() as inner_interface:
1000
+ inner_interface.side_effects.selected_coins = interface.side_effects.selected_coins
1001
+ await self.main_wallet.create_tandem_xch_tx(
1002
+ fee,
1003
+ inner_action_scope,
1004
+ extra_conditions=(
1005
+ AssertCoinAnnouncement(asserted_id=coin_spends[0].coin.name(), asserted_msg=message),
1006
+ ),
1007
+ )
1008
+ async with inner_action_scope.use() as inner_interface:
1009
+ # This should not be looked to for best practice.
1010
+ # Ideally, the two spend bundles can exist separately on each tx record until they are pushed.
1011
+ # This is not very supported behavior at the moment
1012
+ # so to avoid any potential backwards compatibility issues,
1013
+ # we're moving the spend bundle from this TX to the main
1014
+ interface.side_effects.transactions.extend(
1015
+ [
1016
+ dataclasses.replace(tx, spend_bundle=None)
1017
+ for tx in inner_interface.side_effects.transactions
1018
+ ]
1019
+ )
1020
+ interface.side_effects.selected_coins.extend(inner_interface.side_effects.selected_coins)
1021
+ spend_bundle = WalletSpendBundle.aggregate(
1022
+ [
1023
+ spend_bundle,
1024
+ *(
1025
+ tx.spend_bundle
1026
+ for tx in inner_action_scope.side_effects.transactions
1027
+ if tx.spend_bundle is not None
1028
+ ),
1029
+ ]
1030
+ )
1031
+ assert derivation_record is not None
1032
+ tx_record = TransactionRecord(
1033
+ confirmed_at_height=uint32(0),
1034
+ created_at_time=now,
1035
+ to_puzzle_hash=derivation_record.puzzle_hash,
1036
+ amount=amount,
1037
+ fee_amount=uint64(fee),
1038
+ confirmed=False,
1039
+ sent=uint32(0),
1040
+ spend_bundle=spend_bundle,
1041
+ additions=spend_bundle.additions(),
1042
+ removals=spend_bundle.removals(),
1043
+ wallet_id=uint32(1),
1044
+ sent_to=[],
1045
+ trade_id=None,
1046
+ type=uint32(TransactionType.OUTGOING_CLAWBACK),
1047
+ name=spend_bundle.name(),
1048
+ memos=list(compute_memos(spend_bundle).items()),
1049
+ valid_times=parse_timelock_info(extra_conditions),
1050
+ )
1051
+ async with action_scope.use() as interface:
1052
+ interface.side_effects.transactions.append(tx_record)
1053
+
1054
+ async def filter_spam(self, new_coin_state: list[CoinState]) -> list[CoinState]:
1055
+ xch_spam_amount = self.config.get("xch_spam_amount", 1000000)
1056
+
1057
+ # No need to filter anything if the filter is set to 1 or 0 mojos
1058
+ if xch_spam_amount <= 1:
1059
+ return new_coin_state
1060
+
1061
+ spam_filter_after_n_txs = self.config.get("spam_filter_after_n_txs", 200)
1062
+ small_unspent_count = await self.coin_store.count_small_unspent(xch_spam_amount)
1063
+
1064
+ # if small_unspent_count > spam_filter_after_n_txs:
1065
+ filtered_cs: list[CoinState] = []
1066
+ is_standard_wallet_phs: set[bytes32] = set()
1067
+
1068
+ for cs in new_coin_state:
1069
+ # Only apply filter to new coins being sent to our wallet, that are very small
1070
+ if (
1071
+ cs.created_height is not None
1072
+ and cs.spent_height is None
1073
+ and cs.coin.amount < xch_spam_amount
1074
+ and (cs.coin.puzzle_hash in is_standard_wallet_phs or await self.is_standard_wallet_tx(cs))
1075
+ ):
1076
+ is_standard_wallet_phs.add(cs.coin.puzzle_hash)
1077
+ if small_unspent_count < spam_filter_after_n_txs:
1078
+ filtered_cs.append(cs)
1079
+ small_unspent_count += 1
1080
+ else:
1081
+ filtered_cs.append(cs)
1082
+ return filtered_cs
1083
+
1084
+ async def is_standard_wallet_tx(self, coin_state: CoinState) -> bool:
1085
+ wallet_identifier = await self.get_wallet_identifier_for_puzzle_hash(coin_state.coin.puzzle_hash)
1086
+ return wallet_identifier is not None and wallet_identifier.type == WalletType.STANDARD_WALLET
1087
+
1088
+ async def handle_dao_cat(
1089
+ self,
1090
+ curried_args: Iterator[Program],
1091
+ parent_coin_state: CoinState,
1092
+ coin_state: CoinState,
1093
+ coin_spend: CoinSpend,
1094
+ fork_height: Optional[uint32],
1095
+ ) -> Optional[WalletIdentifier]:
1096
+ """
1097
+ Handle the new coin when it is a DAO CAT
1098
+ """
1099
+ _mod_hash, tail_hash, _inner_puzzle = curried_args
1100
+ asset_id: bytes32 = bytes32(bytes(tail_hash)[1:])
1101
+ for wallet in self.wallets.values():
1102
+ if wallet.type() == WalletType.DAO_CAT:
1103
+ assert isinstance(wallet, DAOCATWallet)
1104
+ if wallet.dao_cat_info.limitations_program_hash == asset_id:
1105
+ return WalletIdentifier.create(wallet)
1106
+ # Found a DAO_CAT, but we don't have a wallet for it. Add to unacknowledged
1107
+ await self.interested_store.add_unacknowledged_token(
1108
+ asset_id,
1109
+ CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()),
1110
+ None if parent_coin_state.spent_height is None else uint32(parent_coin_state.spent_height),
1111
+ parent_coin_state.coin.puzzle_hash,
1112
+ )
1113
+ await self.interested_store.add_unacknowledged_coin_state(
1114
+ asset_id,
1115
+ coin_state,
1116
+ fork_height,
1117
+ )
1118
+ self.state_changed("added_stray_cat")
1119
+ return None # pragma: no cover
1120
+
1121
+ async def handle_cat(
1122
+ self,
1123
+ parent_data: CATCoinData,
1124
+ parent_coin_state: CoinState,
1125
+ coin_state: CoinState,
1126
+ coin_spend: CoinSpend,
1127
+ peer: WSChiaConnection,
1128
+ fork_height: Optional[uint32],
1129
+ ) -> Optional[WalletIdentifier]:
1130
+ """
1131
+ Handle the new coin when it is a CAT
1132
+ :param parent_data: Parent CAT coin uncurried metadata
1133
+ :param parent_coin_state: Parent coin state
1134
+ :param coin_state: Current coin state
1135
+ :param coin_spend: New coin spend
1136
+ :param fork_height: Current block height
1137
+ :return: Wallet ID & Wallet Type
1138
+ """
1139
+ hinted_coin = compute_spend_hints_and_additions(coin_spend)[0][coin_state.coin.name()]
1140
+ assert hinted_coin.hint is not None, f"hint missing for coin {hinted_coin.coin}"
1141
+ derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(hinted_coin.hint)
1142
+
1143
+ if derivation_record is None:
1144
+ self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
1145
+ return None
1146
+ else:
1147
+ our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(derivation_record.pubkey)
1148
+ asset_id: bytes32 = parent_data.tail_program_hash
1149
+ cat_puzzle = construct_cat_puzzle(CAT_MOD, asset_id, our_inner_puzzle, CAT_MOD_HASH)
1150
+ is_crcat: bool = False
1151
+ if cat_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
1152
+ # Check if it is a CRCAT
1153
+ if CRCAT.is_cr_cat(uncurry_puzzle(coin_spend.puzzle_reveal)):
1154
+ is_crcat = True
1155
+ else:
1156
+ return None # pragma: no cover
1157
+ if is_crcat:
1158
+ # Since CRCAT wallet doesn't have derivation path, every CRCAT will go through this code path
1159
+ crcat: CRCAT = next(
1160
+ crc for crc in CRCAT.get_next_from_coin_spend(coin_spend) if crc.coin == coin_state.coin
1161
+ )
1162
+
1163
+ # Make sure we control the inner puzzle or we control it if it's wrapped in the pending state
1164
+ if (
1165
+ await self.puzzle_store.get_derivation_record_for_puzzle_hash(crcat.inner_puzzle_hash) is None
1166
+ and crcat.inner_puzzle_hash
1167
+ != construct_pending_approval_state(
1168
+ hinted_coin.hint,
1169
+ uint64(coin_state.coin.amount),
1170
+ ).get_tree_hash()
1171
+ ):
1172
+ self.log.error(f"Unknown CRCAT inner puzzle, coin ID:{crcat.coin.name().hex()}") # pragma: no cover
1173
+ return None # pragma: no cover
1174
+
1175
+ # Check if we already have a wallet
1176
+ for wallet_info in await self.get_all_wallet_info_entries(wallet_type=WalletType.CRCAT):
1177
+ crcat_info: CRCATInfo = CRCATInfo.from_bytes(bytes.fromhex(wallet_info.data))
1178
+ if crcat_info.limitations_program_hash == asset_id:
1179
+ return WalletIdentifier(wallet_info.id, WalletType(wallet_info.type))
1180
+
1181
+ # We didn't find a matching CR-CAT wallet, but maybe we have a matching CAT wallet that we can convert
1182
+ for wallet_info in await self.get_all_wallet_info_entries(wallet_type=WalletType.CAT):
1183
+ cat_info: CATInfo = CATInfo.from_bytes(bytes.fromhex(wallet_info.data))
1184
+ found_cat_wallet = self.wallets[wallet_info.id]
1185
+ assert isinstance(found_cat_wallet, CATWallet)
1186
+ if cat_info.limitations_program_hash == crcat.tail_hash:
1187
+ await CRCATWallet.convert_to_cr(
1188
+ found_cat_wallet,
1189
+ crcat.authorized_providers,
1190
+ ProofsChecker.from_program(uncurry_puzzle(crcat.proofs_checker)),
1191
+ )
1192
+ self.state_changed("converted cat wallet to cr", wallet_info.id)
1193
+ return WalletIdentifier(wallet_info.id, WalletType(WalletType.CRCAT))
1194
+ if parent_data.tail_program_hash.hex() in self.default_cats or self.config.get(
1195
+ "automatically_add_unknown_cats", False
1196
+ ):
1197
+ if is_crcat:
1198
+ cat_wallet: Union[CATWallet, CRCATWallet] = await CRCATWallet.get_or_create_wallet_for_cat(
1199
+ self,
1200
+ self.main_wallet,
1201
+ crcat.tail_hash.hex(),
1202
+ authorized_providers=crcat.authorized_providers,
1203
+ proofs_checker=ProofsChecker.from_program(uncurry_puzzle(crcat.proofs_checker)),
1204
+ )
1205
+ else:
1206
+ cat_wallet = await CATWallet.get_or_create_wallet_for_cat(
1207
+ self, self.main_wallet, parent_data.tail_program_hash.hex()
1208
+ )
1209
+ return WalletIdentifier.create(cat_wallet)
1210
+ else:
1211
+ # Found unacknowledged CAT, save it in the database.
1212
+ await self.interested_store.add_unacknowledged_token(
1213
+ asset_id,
1214
+ CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()),
1215
+ None if parent_coin_state.spent_height is None else uint32(parent_coin_state.spent_height),
1216
+ parent_coin_state.coin.puzzle_hash,
1217
+ )
1218
+ await self.interested_store.add_unacknowledged_coin_state(
1219
+ asset_id,
1220
+ coin_state,
1221
+ fork_height,
1222
+ )
1223
+ self.state_changed("added_stray_cat")
1224
+ return None
1225
+
1226
+ async def handle_did(
1227
+ self,
1228
+ parent_data: DIDCoinData,
1229
+ parent_coin_state: CoinState,
1230
+ coin_state: CoinState,
1231
+ coin_spend: CoinSpend,
1232
+ peer: WSChiaConnection,
1233
+ ) -> Optional[WalletIdentifier]:
1234
+ """
1235
+ Handle the new coin when it is a DID
1236
+ :param parent_data: Curried data of the DID coin
1237
+ :param parent_coin_state: Parent coin state
1238
+ :param coin_state: Current coin state
1239
+ :param coin_spend: New coin spend
1240
+ :return: Wallet ID & Wallet Type
1241
+ """
1242
+
1243
+ inner_puzzle_hash = parent_data.p2_puzzle.get_tree_hash()
1244
+ self.log.info(f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}")
1245
+
1246
+ hinted_coin = compute_spend_hints_and_additions(coin_spend)[0][coin_state.coin.name()]
1247
+ assert hinted_coin.hint is not None, f"hint missing for coin {hinted_coin.coin}"
1248
+ derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(hinted_coin.hint)
1249
+
1250
+ launch_id = bytes32(parent_data.singleton_struct.rest().first().as_atom())
1251
+ if derivation_record is None:
1252
+ self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
1253
+ # Check if it was owned by us
1254
+ # If the puzzle inside is no longer recognised then delete the wallet associated
1255
+ removed_wallet_ids = []
1256
+ for wallet in self.wallets.values():
1257
+ if not isinstance(wallet, DIDWallet):
1258
+ continue
1259
+ if (
1260
+ wallet.did_info.origin_coin is not None
1261
+ and launch_id == wallet.did_info.origin_coin.name()
1262
+ and not wallet.did_info.sent_recovery_transaction
1263
+ ):
1264
+ await self.delete_wallet(wallet.id())
1265
+ removed_wallet_ids.append(wallet.id())
1266
+ for remove_id in removed_wallet_ids:
1267
+ self.wallets.pop(remove_id)
1268
+ self.log.info(f"Removed DID wallet {remove_id}, Launch_ID: {launch_id.hex()}")
1269
+ self.state_changed("wallet_removed", remove_id)
1270
+ return None
1271
+ else:
1272
+ our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(derivation_record.pubkey)
1273
+
1274
+ self.log.info(f"Found DID, launch_id {launch_id}.")
1275
+ did_puzzle = DID_INNERPUZ_MOD.curry(
1276
+ our_inner_puzzle,
1277
+ parent_data.recovery_list_hash,
1278
+ parent_data.num_verification,
1279
+ parent_data.singleton_struct,
1280
+ parent_data.metadata,
1281
+ )
1282
+ full_puzzle = create_singleton_puzzle(did_puzzle, launch_id)
1283
+ did_puzzle_empty_recovery = DID_INNERPUZ_MOD.curry(
1284
+ our_inner_puzzle,
1285
+ NIL_TREEHASH,
1286
+ uint64(0),
1287
+ parent_data.singleton_struct,
1288
+ parent_data.metadata,
1289
+ )
1290
+ full_puzzle_empty_recovery = create_singleton_puzzle(did_puzzle_empty_recovery, launch_id)
1291
+ if full_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
1292
+ if full_puzzle_empty_recovery.get_tree_hash() == coin_state.coin.puzzle_hash:
1293
+ did_puzzle = did_puzzle_empty_recovery
1294
+ self.log.info("DID recovery list was reset by the previous owner.")
1295
+ else:
1296
+ self.log.error("DID puzzle hash doesn't match, please check curried parameters.")
1297
+ return None
1298
+ # Create DID wallet
1299
+ response: list[CoinState] = await self.wallet_node.get_coin_state([launch_id], peer=peer)
1300
+ if len(response) == 0:
1301
+ self.log.warning(f"Could not find the launch coin with ID: {launch_id}")
1302
+ return None
1303
+ launch_coin: CoinState = response[0]
1304
+ origin_coin = launch_coin.coin
1305
+
1306
+ did_wallet_count = 0
1307
+ for wallet in self.wallets.values():
1308
+ if wallet.type() == WalletType.DECENTRALIZED_ID:
1309
+ assert isinstance(wallet, DIDWallet)
1310
+ assert wallet.did_info.origin_coin is not None
1311
+ if origin_coin.name() == wallet.did_info.origin_coin.name():
1312
+ return WalletIdentifier.create(wallet)
1313
+ did_wallet_count += 1
1314
+ if coin_state.spent_height is not None:
1315
+ # The first coin we received for DID wallet is spent.
1316
+ # This means the wallet is in a resync process, skip the coin
1317
+ return None
1318
+ # check we aren't above the auto-add wallet limit
1319
+ limit = self.config.get("did_auto_add_limit", 10)
1320
+ if did_wallet_count < limit:
1321
+ did_wallet = await DIDWallet.create_new_did_wallet_from_coin_spend(
1322
+ self,
1323
+ self.main_wallet,
1324
+ launch_coin.coin,
1325
+ did_puzzle,
1326
+ coin_spend,
1327
+ f"DID {encode_puzzle_hash(launch_id, AddressType.DID.hrp(self.config))}",
1328
+ )
1329
+ wallet_identifier = WalletIdentifier.create(did_wallet)
1330
+ self.state_changed("wallet_created", wallet_identifier.id, {"did_id": did_wallet.get_my_DID()})
1331
+ return wallet_identifier
1332
+ # we are over the limit
1333
+ self.log.warning(
1334
+ f"You are at the max configured limit of {limit} DIDs. Ignoring received DID {launch_id.hex()}"
1335
+ )
1336
+ return None
1337
+
1338
+ async def get_minter_did(self, launcher_coin: Coin, peer: WSChiaConnection) -> Optional[bytes32]:
1339
+ # Get minter DID
1340
+ eve_coin = (await self.wallet_node.fetch_children(launcher_coin.name(), peer=peer))[0]
1341
+ eve_coin_spend = await fetch_coin_spend_for_coin_state(eve_coin, peer)
1342
+ eve_full_puzzle: Program = Program.from_bytes(bytes(eve_coin_spend.puzzle_reveal))
1343
+ eve_uncurried_nft: Optional[UncurriedNFT] = UncurriedNFT.uncurry(*eve_full_puzzle.uncurry())
1344
+ if eve_uncurried_nft is None:
1345
+ raise ValueError("Couldn't get minter DID for NFT")
1346
+ if not eve_uncurried_nft.supports_did:
1347
+ return None
1348
+ minter_did = get_new_owner_did(eve_uncurried_nft, eve_coin_spend.solution.to_program())
1349
+ if minter_did == b"":
1350
+ minter_did = None
1351
+ if minter_did is None:
1352
+ # Check if the NFT is a bulk minting
1353
+ launcher_parent: list[CoinState] = await self.wallet_node.get_coin_state(
1354
+ [launcher_coin.parent_coin_info], peer=peer
1355
+ )
1356
+ assert (
1357
+ launcher_parent is not None
1358
+ and len(launcher_parent) == 1
1359
+ and launcher_parent[0].spent_height is not None
1360
+ )
1361
+ # NFTs minted out of coinbase coins would not have minter DIDs
1362
+ if self.constants.GENESIS_CHALLENGE[:16] in bytes(
1363
+ launcher_parent[0].coin.parent_coin_info
1364
+ ) or self.constants.GENESIS_CHALLENGE[16:] in bytes(launcher_parent[0].coin.parent_coin_info):
1365
+ return None
1366
+ did_coin: list[CoinState] = await self.wallet_node.get_coin_state(
1367
+ [launcher_parent[0].coin.parent_coin_info], peer=peer
1368
+ )
1369
+ assert did_coin is not None and len(did_coin) == 1 and did_coin[0].spent_height is not None
1370
+ did_spend = await fetch_coin_spend_for_coin_state(did_coin[0], peer)
1371
+ uncurried = uncurry_puzzle(did_spend.puzzle_reveal)
1372
+ did_curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
1373
+ if did_curried_args is not None:
1374
+ _p2_puzzle, _recovery_list_hash, _num_verification, singleton_struct, _metadata = did_curried_args
1375
+ minter_did = bytes32(bytes(singleton_struct.rest().first())[1:])
1376
+ return minter_did
1377
+
1378
+ async def handle_dao_treasury(
1379
+ self,
1380
+ uncurried_args: Iterator[Program],
1381
+ parent_coin_state: CoinState,
1382
+ coin_state: CoinState,
1383
+ coin_spend: CoinSpend,
1384
+ ) -> Optional[WalletIdentifier]:
1385
+ self.log.info("Entering dao_treasury handling in WalletStateManager")
1386
+ singleton_id = get_singleton_id_from_puzzle(coin_spend.puzzle_reveal)
1387
+ for wallet in self.wallets.values():
1388
+ if wallet.type() == WalletType.DAO:
1389
+ assert isinstance(wallet, DAOWallet)
1390
+ if wallet.dao_info.treasury_id == singleton_id:
1391
+ return WalletIdentifier.create(wallet)
1392
+
1393
+ # TODO: If we can't find the wallet for this DAO but we've got here because we're subscribed,
1394
+ # then create the wallet. (see early in dao-wallet commits for how to do this)
1395
+ return None # pragma: no cover
1396
+
1397
+ async def handle_dao_proposal(
1398
+ self,
1399
+ uncurried_args: Iterator[Program],
1400
+ parent_coin_state: CoinState,
1401
+ coin_state: CoinState,
1402
+ coin_spend: CoinSpend,
1403
+ ) -> Optional[WalletIdentifier]:
1404
+ (
1405
+ # ; second hash
1406
+ _SELF_HASH,
1407
+ _PROPOSAL_ID,
1408
+ _PROPOSED_PUZ_HASH,
1409
+ _YES_VOTES,
1410
+ _TOTAL_VOTES,
1411
+ # ; first hash
1412
+ _PROPOSAL_TIMER_MOD_HASH,
1413
+ _SINGLETON_MOD_HASH,
1414
+ _SINGLETON_LAUNCHER_PUZHASH,
1415
+ _CAT_MOD_HASH,
1416
+ _DAO_FINISHED_STATE_MOD_HASH,
1417
+ _TREASURY_MOD_HASH,
1418
+ _LOCKUP_SELF_HASH,
1419
+ _CAT_TAIL_HASH,
1420
+ TREASURY_ID,
1421
+ ) = uncurried_args
1422
+ for wallet in self.wallets.values():
1423
+ if wallet.type() == WalletType.DAO:
1424
+ assert isinstance(wallet, DAOWallet)
1425
+ if wallet.dao_info.treasury_id == TREASURY_ID.as_atom():
1426
+ assert isinstance(coin_state.created_height, int)
1427
+ await wallet.add_or_update_proposal_info(coin_spend, uint32(coin_state.created_height))
1428
+ return WalletIdentifier.create(wallet)
1429
+ return None # pragma: no cover
1430
+
1431
+ async def handle_dao_finished_proposals(
1432
+ self,
1433
+ uncurried_args: Iterator[Program],
1434
+ parent_coin_state: CoinState,
1435
+ coin_state: CoinState,
1436
+ coin_spend: CoinSpend,
1437
+ ) -> Optional[WalletIdentifier]:
1438
+ if coin_state.created_height is None: # pragma: no cover
1439
+ raise ValueError("coin_state argument to handle_dao_finished_proposals cannot have created_height of None")
1440
+ (
1441
+ SINGLETON_STRUCT, # (SINGLETON_MOD_HASH, (SINGLETON_ID, LAUNCHER_PUZZLE_HASH))
1442
+ _FINISHED_STATE_MOD_HASH,
1443
+ ) = uncurried_args
1444
+ proposal_id = SINGLETON_STRUCT.rest().first().as_atom()
1445
+ for wallet in self.wallets.values():
1446
+ if wallet.type() == WalletType.DAO:
1447
+ assert isinstance(wallet, DAOWallet)
1448
+ for proposal_info in wallet.dao_info.proposals_list:
1449
+ if proposal_info.proposal_id == proposal_id:
1450
+ await wallet.add_or_update_proposal_info(coin_spend, uint32(coin_state.created_height))
1451
+ return WalletIdentifier.create(wallet)
1452
+ return None
1453
+
1454
+ async def get_dao_wallet_from_coinspend_hint(
1455
+ self, coin_spend: CoinSpend, coin_state: CoinState
1456
+ ) -> Optional[WalletIdentifier]:
1457
+ hinted_coin = compute_spend_hints_and_additions(coin_spend)[0][coin_state.coin.name()]
1458
+ if hinted_coin:
1459
+ for wallet in self.wallets.values():
1460
+ if wallet.type() == WalletType.DAO.value:
1461
+ assert isinstance(wallet, DAOWallet)
1462
+ if get_p2_singleton_puzhash(wallet.dao_info.treasury_id) == hinted_coin.hint:
1463
+ return WalletIdentifier.create(wallet)
1464
+ return None
1465
+
1466
+ async def handle_nft(
1467
+ self,
1468
+ nft_data: NFTCoinData,
1469
+ ) -> Optional[WalletIdentifier]:
1470
+ """
1471
+ Handle the new coin when it is a NFT
1472
+ :param nft_data: all necessary data to process a NFT coin
1473
+ :return: Wallet ID & Wallet Type
1474
+ """
1475
+ wallet_identifier = None
1476
+ # DID ID determines which NFT wallet should process the NFT
1477
+ new_did_id: Optional[bytes32] = None
1478
+ old_did_id = None
1479
+ # P2 puzzle hash determines if we should ignore the NFT
1480
+ uncurried_nft: UncurriedNFT = nft_data.uncurried_nft
1481
+ old_p2_puzhash = uncurried_nft.p2_puzzle.get_tree_hash()
1482
+ _metadata, new_p2_puzhash = get_metadata_and_phs(
1483
+ uncurried_nft,
1484
+ nft_data.parent_coin_spend.solution,
1485
+ )
1486
+ if uncurried_nft.supports_did:
1487
+ _new_did_id = get_new_owner_did(uncurried_nft, nft_data.parent_coin_spend.solution.to_program())
1488
+ old_did_id = uncurried_nft.owner_did
1489
+ if _new_did_id is None:
1490
+ new_did_id = old_did_id
1491
+ elif _new_did_id == b"":
1492
+ new_did_id = None
1493
+ else:
1494
+ new_did_id = _new_did_id
1495
+ self.log.debug(
1496
+ "Handling NFT: %s, old DID:%s, new DID:%s, old P2:%s, new P2:%s",
1497
+ nft_data.parent_coin_spend,
1498
+ old_did_id,
1499
+ new_did_id,
1500
+ old_p2_puzhash,
1501
+ new_p2_puzhash,
1502
+ )
1503
+ new_derivation_record: Optional[
1504
+ DerivationRecord
1505
+ ] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(new_p2_puzhash)
1506
+ old_derivation_record: Optional[
1507
+ DerivationRecord
1508
+ ] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(old_p2_puzhash)
1509
+ if new_derivation_record is None and old_derivation_record is None:
1510
+ self.log.debug(
1511
+ "Cannot find a P2 puzzle hash for NFT:%s, this NFT belongs to others.",
1512
+ uncurried_nft.singleton_launcher_id.hex(),
1513
+ )
1514
+ return wallet_identifier
1515
+ for nft_wallet in self.wallets.copy().values():
1516
+ if not isinstance(nft_wallet, NFTWallet):
1517
+ continue
1518
+ if nft_wallet.nft_wallet_info.did_id == old_did_id and old_derivation_record is not None:
1519
+ self.log.info(
1520
+ "Removing old NFT, NFT_ID:%s, DID_ID:%s",
1521
+ uncurried_nft.singleton_launcher_id.hex(),
1522
+ old_did_id,
1523
+ )
1524
+ if nft_data.parent_coin_state.spent_height is not None:
1525
+ await nft_wallet.remove_coin(
1526
+ nft_data.parent_coin_spend.coin, uint32(nft_data.parent_coin_state.spent_height)
1527
+ )
1528
+ is_empty = await nft_wallet.is_empty()
1529
+ has_did = False
1530
+ for did_wallet in self.wallets.values():
1531
+ if not isinstance(did_wallet, DIDWallet):
1532
+ continue
1533
+ assert did_wallet.did_info.origin_coin is not None
1534
+ if did_wallet.did_info.origin_coin.name() == old_did_id:
1535
+ has_did = True
1536
+ break
1537
+ if is_empty and nft_wallet.did_id is not None and not has_did:
1538
+ self.log.info(f"No NFT, deleting wallet {nft_wallet.did_id.hex()} ...")
1539
+ await self.delete_wallet(nft_wallet.wallet_info.id)
1540
+ self.wallets.pop(nft_wallet.wallet_info.id)
1541
+ if nft_wallet.nft_wallet_info.did_id == new_did_id and new_derivation_record is not None:
1542
+ self.log.info(
1543
+ "Adding new NFT, NFT_ID:%s, DID_ID:%s",
1544
+ uncurried_nft.singleton_launcher_id.hex(),
1545
+ new_did_id,
1546
+ )
1547
+ wallet_identifier = WalletIdentifier.create(nft_wallet)
1548
+
1549
+ if wallet_identifier is None and new_derivation_record is not None:
1550
+ # Cannot find an existed NFT wallet for the new NFT
1551
+ self.log.info(
1552
+ "Cannot find a NFT wallet for NFT_ID: %s DID_ID: %s, creating a new one.",
1553
+ uncurried_nft.singleton_launcher_id,
1554
+ new_did_id,
1555
+ )
1556
+ new_nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
1557
+ self, self.main_wallet, did_id=new_did_id, name="NFT Wallet"
1558
+ )
1559
+ wallet_identifier = WalletIdentifier.create(new_nft_wallet)
1560
+ return wallet_identifier
1561
+
1562
+ async def handle_clawback(
1563
+ self,
1564
+ metadata: ClawbackMetadata,
1565
+ coin_state: CoinState,
1566
+ coin_spend: CoinSpend,
1567
+ peer: WSChiaConnection,
1568
+ ) -> Optional[WalletIdentifier]:
1569
+ """
1570
+ Handle Clawback coins
1571
+ :param metadata: Clawback metadata for spending the merkle coin
1572
+ :param coin_state: Clawback merkle coin
1573
+ :param coin_spend: Parent coin spend
1574
+ :param peer: Fullnode peer
1575
+ :return:
1576
+ """
1577
+ # Record metadata
1578
+ assert coin_state.created_height is not None
1579
+ is_recipient: Optional[bool] = None
1580
+ # Check if the wallet is the sender
1581
+ sender_derivation_record: Optional[
1582
+ DerivationRecord
1583
+ ] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(metadata.sender_puzzle_hash)
1584
+ # Check if the wallet is the recipient
1585
+ recipient_derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(
1586
+ metadata.recipient_puzzle_hash
1587
+ )
1588
+ if sender_derivation_record is not None:
1589
+ self.log.info("Found Clawback merkle coin %s as the sender.", coin_state.coin.name().hex())
1590
+ is_recipient = False
1591
+ elif recipient_derivation_record is not None:
1592
+ self.log.info("Found Clawback merkle coin %s as the recipient.", coin_state.coin.name().hex())
1593
+ is_recipient = True
1594
+ # For the recipient we need to manually subscribe the merkle coin
1595
+ await self.add_interested_coin_ids([coin_state.coin.name()])
1596
+ if is_recipient is not None:
1597
+ spend_bundle = WalletSpendBundle([coin_spend], G2Element())
1598
+ memos = compute_memos(spend_bundle)
1599
+ spent_height: uint32 = uint32(0)
1600
+ if coin_state.spent_height is not None:
1601
+ self.log.debug("Resync clawback coin: %s", coin_state.coin.name().hex())
1602
+ # Resync case
1603
+ spent_height = uint32(coin_state.spent_height)
1604
+ # Create Clawback outgoing transaction
1605
+ created_timestamp = await self.wallet_node.get_timestamp_for_height(uint32(coin_state.spent_height))
1606
+ clawback_coin_spend: CoinSpend = await fetch_coin_spend_for_coin_state(coin_state, peer)
1607
+ clawback_spend_bundle = WalletSpendBundle([clawback_coin_spend], G2Element())
1608
+ if await self.puzzle_store.puzzle_hash_exists(clawback_spend_bundle.additions()[0].puzzle_hash):
1609
+ tx_record = TransactionRecord(
1610
+ confirmed_at_height=uint32(coin_state.spent_height),
1611
+ created_at_time=created_timestamp,
1612
+ to_puzzle_hash=(
1613
+ metadata.sender_puzzle_hash
1614
+ if clawback_spend_bundle.additions()[0].puzzle_hash == metadata.sender_puzzle_hash
1615
+ else metadata.recipient_puzzle_hash
1616
+ ),
1617
+ amount=uint64(coin_state.coin.amount),
1618
+ fee_amount=uint64(0),
1619
+ confirmed=True,
1620
+ sent=uint32(0),
1621
+ spend_bundle=clawback_spend_bundle,
1622
+ additions=clawback_spend_bundle.additions(),
1623
+ removals=clawback_spend_bundle.removals(),
1624
+ wallet_id=uint32(1),
1625
+ sent_to=[],
1626
+ trade_id=None,
1627
+ type=uint32(TransactionType.OUTGOING_CLAWBACK),
1628
+ name=clawback_spend_bundle.name(),
1629
+ memos=list(compute_memos(clawback_spend_bundle).items()),
1630
+ valid_times=ConditionValidTimes(),
1631
+ )
1632
+ await self.tx_store.add_transaction_record(tx_record)
1633
+ coin_record = WalletCoinRecord(
1634
+ coin_state.coin,
1635
+ uint32(coin_state.created_height),
1636
+ spent_height,
1637
+ spent_height != 0,
1638
+ False,
1639
+ WalletType.STANDARD_WALLET,
1640
+ 1,
1641
+ CoinType.CLAWBACK,
1642
+ VersionedBlob(ClawbackVersion.V1.value, bytes(metadata)),
1643
+ )
1644
+ # Add merkle coin
1645
+ await self.coin_store.add_coin_record(coin_record)
1646
+ # Add tx record
1647
+ # We use TransactionRecord.confirmed to indicate if a Clawback transaction is claimable
1648
+ # If the Clawback coin is unspent, confirmed should be false
1649
+ created_timestamp = await self.wallet_node.get_timestamp_for_height(uint32(coin_state.created_height))
1650
+ tx_record = TransactionRecord(
1651
+ confirmed_at_height=uint32(coin_state.created_height),
1652
+ created_at_time=uint64(created_timestamp),
1653
+ to_puzzle_hash=metadata.recipient_puzzle_hash,
1654
+ amount=uint64(coin_state.coin.amount),
1655
+ fee_amount=uint64(0),
1656
+ confirmed=spent_height != 0,
1657
+ sent=uint32(0),
1658
+ spend_bundle=None,
1659
+ additions=[coin_state.coin],
1660
+ removals=[coin_spend.coin],
1661
+ wallet_id=uint32(1),
1662
+ sent_to=[],
1663
+ trade_id=None,
1664
+ type=uint32(
1665
+ TransactionType.INCOMING_CLAWBACK_RECEIVE
1666
+ if is_recipient
1667
+ else TransactionType.INCOMING_CLAWBACK_SEND
1668
+ ),
1669
+ # Use coin ID as the TX ID to mapping with the coin table
1670
+ name=coin_record.coin.name(),
1671
+ memos=list(memos.items()),
1672
+ valid_times=ConditionValidTimes(),
1673
+ )
1674
+ await self.tx_store.add_transaction_record(tx_record)
1675
+ return None
1676
+
1677
+ async def handle_vc(self, vc: VerifiedCredential) -> Optional[WalletIdentifier]:
1678
+ # Check the ownership
1679
+ derivation_record: Optional[DerivationRecord] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(
1680
+ vc.inner_puzzle_hash
1681
+ )
1682
+ if derivation_record is None:
1683
+ self.log.warning(
1684
+ f"Verified credential {vc.launcher_id.hex()} is not belong to the current wallet."
1685
+ ) # pragma: no cover
1686
+ return None # pragma: no cover
1687
+ self.log.info(f"Found verified credential {vc.launcher_id.hex()}.")
1688
+ for wallet_info in await self.get_all_wallet_info_entries(wallet_type=WalletType.VC):
1689
+ return WalletIdentifier(wallet_info.id, WalletType.VC)
1690
+ # Create a new VC wallet
1691
+ vc_wallet = await VCWallet.create_new_vc_wallet(self, self.main_wallet) # pragma: no cover
1692
+ return WalletIdentifier(vc_wallet.id(), WalletType.VC) # pragma: no cover
1693
+
1694
+ async def _add_coin_states(
1695
+ self,
1696
+ coin_states: list[CoinState],
1697
+ peer: WSChiaConnection,
1698
+ fork_height: Optional[uint32],
1699
+ ) -> None:
1700
+ # TODO: add comment about what this method does
1701
+ # Input states should already be sorted by cs_height, with reorgs at the beginning
1702
+ curr_h = -1
1703
+ for c_state in coin_states:
1704
+ last_change_height = last_change_height_cs(c_state)
1705
+ if last_change_height < curr_h:
1706
+ raise ValueError("Input coin_states is not sorted properly")
1707
+ curr_h = last_change_height
1708
+
1709
+ trade_removals = await self.trade_manager.get_coins_of_interest()
1710
+ all_unconfirmed: list[LightTransactionRecord] = await self.tx_store.get_all_unconfirmed()
1711
+ used_up_to = -1
1712
+ ph_to_index_cache: LRUCache[bytes32, uint32] = LRUCache(100)
1713
+
1714
+ coin_names = [bytes32(coin_state.coin.name()) for coin_state in coin_states]
1715
+ local_records = await self.coin_store.get_coin_records(coin_id_filter=HashFilter.include(coin_names))
1716
+
1717
+ for coin_name, coin_state in zip(coin_names, coin_states):
1718
+ if peer.closed:
1719
+ raise ConnectionError("Connection closed")
1720
+ self.log.debug("Add coin state: %s: %s", coin_name, coin_state)
1721
+ local_record = local_records.coin_id_to_record.get(coin_name)
1722
+ rollback_wallets = None
1723
+ try:
1724
+ async with self.db_wrapper.writer():
1725
+ rollback_wallets = self.wallets.copy() # Shallow copy of wallets if writer rolls back the db
1726
+ # This only succeeds if we don't raise out of the transaction
1727
+ await self.retry_store.remove_state(coin_state)
1728
+
1729
+ wallet_identifier = await self.get_wallet_identifier_for_puzzle_hash(coin_state.coin.puzzle_hash)
1730
+ coin_data: Optional[Streamable] = None
1731
+ # If we already have this coin, & it was spent & confirmed at the same heights, then return (done)
1732
+ if local_record is not None:
1733
+ local_spent = None
1734
+ if local_record.spent_block_height != 0:
1735
+ local_spent = local_record.spent_block_height
1736
+ if (
1737
+ local_spent == coin_state.spent_height
1738
+ and local_record.confirmed_block_height == coin_state.created_height
1739
+ ):
1740
+ continue
1741
+
1742
+ if coin_state.spent_height is not None and coin_name in trade_removals:
1743
+ await self.trade_manager.coins_of_interest_farmed(coin_state, fork_height, peer)
1744
+ if wallet_identifier is not None:
1745
+ self.log.debug(f"Found existing wallet_identifier: {wallet_identifier}, coin: {coin_name}")
1746
+ elif local_record is not None:
1747
+ wallet_identifier = WalletIdentifier(uint32(local_record.wallet_id), local_record.wallet_type)
1748
+ elif coin_state.created_height is not None:
1749
+ wallet_identifier, coin_data = await self.determine_coin_type(peer, coin_state, fork_height)
1750
+ try:
1751
+ dl_wallet = self.get_dl_wallet()
1752
+ except ValueError:
1753
+ pass
1754
+ else:
1755
+ if (
1756
+ await dl_wallet.get_singleton_record(coin_name) is not None
1757
+ or coin_state.coin.puzzle_hash == MIRROR_PUZZLE_HASH
1758
+ ):
1759
+ wallet_identifier = WalletIdentifier.create(dl_wallet)
1760
+
1761
+ if wallet_identifier is None:
1762
+ # Confirm tx records for txs which we submitted for coins which aren't in our wallet
1763
+ if coin_state.created_height is not None and coin_state.spent_height is not None:
1764
+ all_unconfirmed = await self.tx_store.get_all_unconfirmed()
1765
+ tx_records_to_confirm: list[LightTransactionRecord] = []
1766
+ for out_tx_record in all_unconfirmed:
1767
+ if coin_state.coin in out_tx_record.removals:
1768
+ tx_records_to_confirm.append(out_tx_record)
1769
+
1770
+ if len(tx_records_to_confirm) > 0:
1771
+ for light_tx_record in tx_records_to_confirm:
1772
+ await self.tx_store.set_confirmed(
1773
+ light_tx_record.name, uint32(coin_state.spent_height)
1774
+ )
1775
+ self.log.debug(f"No wallet for coin state: {coin_state}")
1776
+ continue
1777
+
1778
+ # Update the DB to signal that we used puzzle hashes up to this one
1779
+ derivation_index = ph_to_index_cache.get(coin_state.coin.puzzle_hash)
1780
+ if derivation_index is None:
1781
+ derivation_index = await self.puzzle_store.index_for_puzzle_hash(coin_state.coin.puzzle_hash)
1782
+ if derivation_index is not None:
1783
+ ph_to_index_cache.put(coin_state.coin.puzzle_hash, derivation_index)
1784
+ if derivation_index > used_up_to:
1785
+ await self.puzzle_store.set_used_up_to(derivation_index)
1786
+ used_up_to = derivation_index
1787
+
1788
+ if coin_state.created_height is None:
1789
+ # TODO implements this coin got reorged
1790
+ # TODO: we need to potentially roll back the pool wallet here
1791
+ pass
1792
+ # if the new coin has not been spent (i.e not ephemeral)
1793
+ elif coin_state.created_height is not None and coin_state.spent_height is None:
1794
+ if local_record is None:
1795
+ await self.coin_added(
1796
+ coin_state.coin,
1797
+ uint32(coin_state.created_height),
1798
+ all_unconfirmed,
1799
+ wallet_identifier.id,
1800
+ wallet_identifier.type,
1801
+ peer,
1802
+ coin_name,
1803
+ coin_data,
1804
+ )
1805
+ await self.add_interested_coin_ids([coin_name])
1806
+
1807
+ # if the coin has been spent
1808
+ elif coin_state.created_height is not None and coin_state.spent_height is not None:
1809
+ self.log.debug("Coin spent: %s", coin_state)
1810
+ children = await self.wallet_node.fetch_children(coin_name, peer=peer, fork_height=fork_height)
1811
+ record = local_record
1812
+ if record is None:
1813
+ farmer_reward = False
1814
+ pool_reward = False
1815
+ tx_type: int
1816
+ if self.is_farmer_reward(uint32(coin_state.created_height), coin_state.coin):
1817
+ farmer_reward = True
1818
+ tx_type = TransactionType.FEE_REWARD.value
1819
+ elif self.is_pool_reward(uint32(coin_state.created_height), coin_state.coin):
1820
+ pool_reward = True
1821
+ tx_type = TransactionType.COINBASE_REWARD.value
1822
+ else:
1823
+ tx_type = TransactionType.INCOMING_TX.value
1824
+ record = WalletCoinRecord(
1825
+ coin_state.coin,
1826
+ uint32(coin_state.created_height),
1827
+ uint32(coin_state.spent_height),
1828
+ True,
1829
+ farmer_reward or pool_reward,
1830
+ wallet_identifier.type,
1831
+ wallet_identifier.id,
1832
+ )
1833
+ await self.coin_store.add_coin_record(record)
1834
+ # Coin first received
1835
+ parent_coin_record: Optional[WalletCoinRecord] = await self.coin_store.get_coin_record(
1836
+ coin_state.coin.parent_coin_info
1837
+ )
1838
+ if (
1839
+ parent_coin_record is not None
1840
+ and wallet_identifier.type == parent_coin_record.wallet_type
1841
+ ):
1842
+ change = True
1843
+ else:
1844
+ change = False
1845
+
1846
+ if not change:
1847
+ created_timestamp = await self.wallet_node.get_timestamp_for_height(
1848
+ uint32(coin_state.created_height)
1849
+ )
1850
+ tx_record = TransactionRecord(
1851
+ confirmed_at_height=uint32(coin_state.created_height),
1852
+ created_at_time=uint64(created_timestamp),
1853
+ to_puzzle_hash=(
1854
+ await self.convert_puzzle_hash(
1855
+ wallet_identifier.id, coin_state.coin.puzzle_hash
1856
+ )
1857
+ ),
1858
+ amount=uint64(coin_state.coin.amount),
1859
+ fee_amount=uint64(0),
1860
+ confirmed=True,
1861
+ sent=uint32(0),
1862
+ spend_bundle=None,
1863
+ additions=[coin_state.coin],
1864
+ removals=[],
1865
+ wallet_id=wallet_identifier.id,
1866
+ sent_to=[],
1867
+ trade_id=None,
1868
+ type=uint32(tx_type),
1869
+ name=bytes32.secret(),
1870
+ memos=[],
1871
+ valid_times=ConditionValidTimes(),
1872
+ )
1873
+ await self.tx_store.add_transaction_record(tx_record)
1874
+
1875
+ additions = [state.coin for state in children]
1876
+ if len(children) > 0:
1877
+ fee = 0
1878
+
1879
+ to_puzzle_hash = None
1880
+ coin_spend: Optional[CoinSpend] = None
1881
+ clawback_metadata: Optional[ClawbackMetadata] = None
1882
+ # Find coin that doesn't belong to us
1883
+ amount = 0
1884
+ for coin in additions:
1885
+ derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(
1886
+ coin.puzzle_hash
1887
+ )
1888
+ if derivation_record is None: # not change
1889
+ to_puzzle_hash = coin.puzzle_hash
1890
+ amount += coin.amount
1891
+ if coin_spend is None:
1892
+ # To prevent unnecessary fetch, we only fetch once,
1893
+ # if there is a child coin that is not owned by the wallet.
1894
+ coin_spend = await fetch_coin_spend_for_coin_state(coin_state, peer)
1895
+ # Check if the parent coin is a Clawback coin
1896
+ uncurried = uncurry_puzzle(coin_spend.puzzle_reveal)
1897
+ clawback_metadata = match_clawback_puzzle(
1898
+ uncurried, coin_spend.puzzle_reveal, coin_spend.solution
1899
+ )
1900
+ if clawback_metadata is not None:
1901
+ # Add the Clawback coin as the interested coin for the sender
1902
+ await self.add_interested_coin_ids([coin.name()])
1903
+ elif wallet_identifier.type == WalletType.CAT:
1904
+ # We subscribe to change for CATs since they didn't hint previously
1905
+ await self.add_interested_coin_ids([coin.name()])
1906
+
1907
+ if to_puzzle_hash is None:
1908
+ to_puzzle_hash = additions[0].puzzle_hash
1909
+
1910
+ spent_timestamp = await self.wallet_node.get_timestamp_for_height(
1911
+ uint32(coin_state.spent_height)
1912
+ )
1913
+
1914
+ # Reorg rollback adds reorged transactions so it's possible there is tx_record already
1915
+ # Even though we are just adding coin record to the db (after reorg)
1916
+ tx_records: list[LightTransactionRecord] = []
1917
+ for out_tx_record in all_unconfirmed:
1918
+ for rem_coin in out_tx_record.removals:
1919
+ if rem_coin == coin_state.coin:
1920
+ tx_records.append(out_tx_record)
1921
+
1922
+ if len(tx_records) > 0:
1923
+ for light_record in tx_records:
1924
+ await self.tx_store.set_confirmed(
1925
+ light_record.name, uint32(coin_state.spent_height)
1926
+ )
1927
+ else:
1928
+ tx_name = bytes(coin_state.coin.name())
1929
+ for added_coin in additions:
1930
+ tx_name += bytes(added_coin.name())
1931
+ tx_name = std_hash(tx_name)
1932
+ tx_record = TransactionRecord(
1933
+ confirmed_at_height=uint32(coin_state.spent_height),
1934
+ created_at_time=uint64(spent_timestamp),
1935
+ to_puzzle_hash=(
1936
+ await self.convert_puzzle_hash(wallet_identifier.id, to_puzzle_hash)
1937
+ ),
1938
+ amount=uint64(int(amount)),
1939
+ fee_amount=uint64(fee),
1940
+ confirmed=True,
1941
+ sent=uint32(0),
1942
+ spend_bundle=None,
1943
+ additions=additions,
1944
+ removals=[coin_state.coin],
1945
+ wallet_id=wallet_identifier.id,
1946
+ sent_to=[],
1947
+ trade_id=None,
1948
+ type=uint32(TransactionType.OUTGOING_TX.value),
1949
+ name=tx_name,
1950
+ memos=[],
1951
+ valid_times=ConditionValidTimes(),
1952
+ )
1953
+
1954
+ await self.tx_store.add_transaction_record(tx_record)
1955
+ else:
1956
+ await self.coin_store.set_spent(coin_name, uint32(coin_state.spent_height))
1957
+ if record.coin_type == CoinType.CLAWBACK:
1958
+ await self.interested_store.remove_interested_coin_id(coin_state.coin.name())
1959
+ confirmed_tx_records: list[LightTransactionRecord] = []
1960
+
1961
+ for light_record in all_unconfirmed:
1962
+ if light_record.type in CLAWBACK_INCOMING_TRANSACTION_TYPES:
1963
+ for add_coin in light_record.additions:
1964
+ if add_coin == coin_state.coin:
1965
+ confirmed_tx_records.append(light_record)
1966
+ else:
1967
+ for rem_coin in light_record.removals:
1968
+ if rem_coin == coin_state.coin:
1969
+ confirmed_tx_records.append(light_record)
1970
+
1971
+ for light_record in confirmed_tx_records:
1972
+ await self.tx_store.set_confirmed(light_record.name, uint32(coin_state.spent_height))
1973
+ for unconfirmed_record in all_unconfirmed:
1974
+ for rem_coin in unconfirmed_record.removals:
1975
+ if rem_coin == coin_state.coin:
1976
+ self.log.info(f"Setting tx_id: {unconfirmed_record.name} to confirmed")
1977
+ await self.tx_store.set_confirmed(
1978
+ unconfirmed_record.name, uint32(coin_state.spent_height)
1979
+ )
1980
+
1981
+ if record.wallet_type in {WalletType.POOLING_WALLET, WalletType.DAO}:
1982
+ wallet_type_to_class = {WalletType.POOLING_WALLET: PoolWallet, WalletType.DAO: DAOWallet}
1983
+ if coin_state.spent_height is not None and coin_state.coin.amount == uint64(1):
1984
+ singleton_wallet: Union[PoolWallet, DAOWallet] = self.get_wallet(
1985
+ id=uint32(record.wallet_id), required_type=wallet_type_to_class[record.wallet_type]
1986
+ )
1987
+ curr_coin_state: CoinState = coin_state
1988
+
1989
+ while curr_coin_state.spent_height is not None:
1990
+ cs: CoinSpend = await fetch_coin_spend_for_coin_state(curr_coin_state, peer)
1991
+ success = await singleton_wallet.apply_state_transition(
1992
+ cs, uint32(curr_coin_state.spent_height)
1993
+ )
1994
+ if not success:
1995
+ break
1996
+ new_singleton_coin = get_most_recent_singleton_coin_from_coin_spend(cs)
1997
+ if new_singleton_coin is None:
1998
+ # No more singleton (maybe destroyed?)
1999
+ break
2000
+
2001
+ coin_name = new_singleton_coin.name()
2002
+ existing = await self.coin_store.get_coin_record(coin_name)
2003
+ if existing is None:
2004
+ await self.coin_added(
2005
+ new_singleton_coin,
2006
+ uint32(curr_coin_state.spent_height),
2007
+ [],
2008
+ uint32(record.wallet_id),
2009
+ record.wallet_type,
2010
+ peer,
2011
+ coin_name,
2012
+ coin_data,
2013
+ )
2014
+ await self.coin_store.set_spent(
2015
+ curr_coin_state.coin.name(), uint32(curr_coin_state.spent_height)
2016
+ )
2017
+ await self.add_interested_coin_ids([new_singleton_coin.name()])
2018
+ new_coin_state: list[CoinState] = await self.wallet_node.get_coin_state(
2019
+ [coin_name], peer=peer, fork_height=fork_height
2020
+ )
2021
+ assert len(new_coin_state) == 1
2022
+ curr_coin_state = new_coin_state[0]
2023
+ if record.wallet_type == WalletType.DATA_LAYER:
2024
+ singleton_spend = await fetch_coin_spend_for_coin_state(coin_state, peer)
2025
+ dl_wallet = self.get_wallet(id=uint32(record.wallet_id), required_type=DataLayerWallet)
2026
+ await dl_wallet.singleton_removed(
2027
+ singleton_spend,
2028
+ uint32(coin_state.spent_height),
2029
+ )
2030
+
2031
+ elif record.wallet_type == WalletType.NFT:
2032
+ if coin_state.spent_height is not None:
2033
+ nft_wallet = self.get_wallet(id=uint32(record.wallet_id), required_type=NFTWallet)
2034
+ await nft_wallet.remove_coin(coin_state.coin, uint32(coin_state.spent_height))
2035
+ elif record.wallet_type == WalletType.VC:
2036
+ if coin_state.spent_height is not None:
2037
+ vc_wallet = self.get_wallet(id=uint32(record.wallet_id), required_type=VCWallet)
2038
+ await vc_wallet.remove_coin(coin_state.coin, uint32(coin_state.spent_height))
2039
+
2040
+ # Check if a child is a singleton launcher
2041
+ for child in children:
2042
+ if child.coin.puzzle_hash != SINGLETON_LAUNCHER_HASH:
2043
+ continue
2044
+ if await self.have_a_pool_wallet_with_launched_id(child.coin.name()):
2045
+ continue
2046
+ if child.spent_height is None:
2047
+ # TODO handle spending launcher later block
2048
+ continue
2049
+ launcher_spend = await fetch_coin_spend_for_coin_state(child, peer)
2050
+ if launcher_spend is None:
2051
+ continue
2052
+ try:
2053
+ pool_state = solution_to_pool_state(launcher_spend)
2054
+ assert pool_state is not None
2055
+ except (AssertionError, ValueError) as e:
2056
+ self.log.debug(f"Not a pool wallet launcher {e}, child: {child}")
2057
+ matched, inner_puzhash = await DataLayerWallet.match_dl_launcher(launcher_spend)
2058
+ if (
2059
+ matched
2060
+ and inner_puzhash is not None
2061
+ and (await self.puzzle_store.puzzle_hash_exists(inner_puzhash))
2062
+ ):
2063
+ try:
2064
+ dl_wallet = self.get_dl_wallet()
2065
+ except ValueError:
2066
+ dl_wallet = await DataLayerWallet.create_new_dl_wallet(
2067
+ self,
2068
+ )
2069
+ await dl_wallet.track_new_launcher_id(
2070
+ child.coin.name(),
2071
+ peer,
2072
+ spend=launcher_spend,
2073
+ height=uint32(child.spent_height),
2074
+ )
2075
+ continue
2076
+
2077
+ # solution_to_pool_state may return None but this may not be an error
2078
+ if pool_state is None:
2079
+ self.log.debug("solution_to_pool_state returned None, ignore and continue")
2080
+ continue
2081
+
2082
+ pool_wallet = await PoolWallet.create(
2083
+ self,
2084
+ self.main_wallet,
2085
+ child.coin.name(),
2086
+ [launcher_spend],
2087
+ uint32(child.spent_height),
2088
+ name="pool_wallet",
2089
+ )
2090
+ launcher_spend_additions = compute_additions(launcher_spend)
2091
+ assert len(launcher_spend_additions) == 1
2092
+ coin_added = launcher_spend_additions[0]
2093
+ coin_name = coin_added.name()
2094
+ existing = await self.coin_store.get_coin_record(coin_name)
2095
+ if existing is None:
2096
+ await self.coin_added(
2097
+ coin_added,
2098
+ uint32(coin_state.spent_height),
2099
+ [],
2100
+ pool_wallet.id(),
2101
+ pool_wallet.type(),
2102
+ peer,
2103
+ coin_name,
2104
+ coin_data,
2105
+ )
2106
+ await self.add_interested_coin_ids([coin_name])
2107
+
2108
+ else:
2109
+ raise RuntimeError("All cases already handled") # Logic error, all cases handled
2110
+ except Exception as e:
2111
+ self.log.exception(f"Failed to add coin_state: {coin_state}, error: {e}")
2112
+ if rollback_wallets is not None:
2113
+ self.wallets = rollback_wallets # Restore since DB will be rolled back by writer
2114
+ if isinstance(e, (PeerRequestException, aiosqlite.Error)):
2115
+ await self.retry_store.add_state(coin_state, peer.peer_node_id, fork_height)
2116
+ else:
2117
+ await self.retry_store.remove_state(coin_state)
2118
+ continue
2119
+
2120
+ async def add_coin_states(
2121
+ self,
2122
+ coin_states: list[CoinState],
2123
+ peer: WSChiaConnection,
2124
+ fork_height: Optional[uint32],
2125
+ ) -> bool:
2126
+ try:
2127
+ await self._add_coin_states(coin_states, peer, fork_height)
2128
+ except Exception as e:
2129
+ log_level = logging.DEBUG if peer.closed else logging.ERROR
2130
+ self.log.log(log_level, f"add_coin_states failed - exception {e}, traceback: {traceback.format_exc()}")
2131
+ return False
2132
+
2133
+ await self.blockchain.clean_block_records()
2134
+
2135
+ return True
2136
+
2137
+ async def have_a_pool_wallet_with_launched_id(self, launcher_id: bytes32) -> bool:
2138
+ for wallet_id, wallet in self.wallets.items():
2139
+ if wallet.type() == WalletType.POOLING_WALLET:
2140
+ assert isinstance(wallet, PoolWallet)
2141
+ if (await wallet.get_current_state()).launcher_id == launcher_id:
2142
+ self.log.warning("Already have, not recreating")
2143
+ return True
2144
+ return False
2145
+
2146
+ def is_pool_reward(self, created_height: uint32, coin: Coin) -> bool:
2147
+ if coin.amount != calculate_pool_reward(created_height) and coin.amount != calculate_pool_reward(
2148
+ uint32(max(0, created_height - 128))
2149
+ ):
2150
+ # Optimization to avoid the computation below. Any coin that has a different amount is not a pool reward
2151
+ return False
2152
+ for i in range(0, 30):
2153
+ try_height = created_height - i
2154
+ if try_height < 0:
2155
+ break
2156
+ calculated = pool_parent_id(uint32(try_height), self.constants.GENESIS_CHALLENGE)
2157
+ if calculated == coin.parent_coin_info:
2158
+ return True
2159
+ return False
2160
+
2161
+ def is_farmer_reward(self, created_height: uint32, coin: Coin) -> bool:
2162
+ if coin.amount < calculate_base_farmer_reward(created_height):
2163
+ # Optimization to avoid the computation below. Any coin less than this base amount cannot be farmer reward
2164
+ return False
2165
+ for i in range(0, 30):
2166
+ try_height = created_height - i
2167
+ if try_height < 0:
2168
+ break
2169
+ calculated = farmer_parent_id(uint32(try_height), self.constants.GENESIS_CHALLENGE)
2170
+ if calculated == coin.parent_coin_info:
2171
+ return True
2172
+ return False
2173
+
2174
+ async def get_wallet_identifier_for_puzzle_hash(self, puzzle_hash: bytes32) -> Optional[WalletIdentifier]:
2175
+ wallet_identifier = await self.puzzle_store.get_wallet_identifier_for_puzzle_hash(puzzle_hash)
2176
+ if wallet_identifier is not None:
2177
+ return wallet_identifier
2178
+
2179
+ interested_wallet_id = await self.interested_store.get_interested_puzzle_hash_wallet_id(puzzle_hash=puzzle_hash)
2180
+ if interested_wallet_id is not None:
2181
+ wallet_id = uint32(interested_wallet_id)
2182
+ if wallet_id not in self.wallets.keys():
2183
+ self.log.warning(f"Do not have wallet {wallet_id} for puzzle_hash {puzzle_hash}")
2184
+ return None
2185
+ return WalletIdentifier(uint32(wallet_id), self.wallets[uint32(wallet_id)].type())
2186
+ return None
2187
+
2188
+ async def get_wallet_identifier_for_coin(
2189
+ self, coin: Coin, hint_dict: dict[bytes32, bytes32] = {}
2190
+ ) -> Optional[WalletIdentifier]:
2191
+ wallet_identifier = await self.puzzle_store.get_wallet_identifier_for_puzzle_hash(coin.puzzle_hash)
2192
+ if (
2193
+ wallet_identifier is None
2194
+ and coin.name() in hint_dict
2195
+ and await self.puzzle_store.puzzle_hash_exists(hint_dict[coin.name()])
2196
+ ):
2197
+ wallet_identifier = await self.get_wallet_identifier_for_hinted_coin(coin, hint_dict[coin.name()])
2198
+ if wallet_identifier is None:
2199
+ coin_record = await self.coin_store.get_coin_record(coin.name())
2200
+ if coin_record is not None:
2201
+ wallet_identifier = WalletIdentifier(uint32(coin_record.wallet_id), coin_record.wallet_type)
2202
+
2203
+ return wallet_identifier
2204
+
2205
+ async def get_wallet_identifier_for_hinted_coin(self, coin: Coin, hint: bytes32) -> Optional[WalletIdentifier]:
2206
+ for wallet in self.wallets.values():
2207
+ if await wallet.match_hinted_coin(coin, hint):
2208
+ return WalletIdentifier(wallet.id(), wallet.type())
2209
+ return None
2210
+
2211
+ async def coin_added(
2212
+ self,
2213
+ coin: Coin,
2214
+ height: uint32,
2215
+ all_unconfirmed_transaction_records: list[LightTransactionRecord],
2216
+ wallet_id: uint32,
2217
+ wallet_type: WalletType,
2218
+ peer: WSChiaConnection,
2219
+ coin_name: bytes32,
2220
+ coin_data: Optional[Streamable],
2221
+ ) -> None:
2222
+ """
2223
+ Adding coin to DB
2224
+ """
2225
+
2226
+ self.log.debug(
2227
+ "Adding record to state manager coin: %s at %s wallet_id: %s and type: %s",
2228
+ coin,
2229
+ height,
2230
+ wallet_id,
2231
+ wallet_type,
2232
+ )
2233
+
2234
+ if self.is_pool_reward(height, coin):
2235
+ tx_type = TransactionType.COINBASE_REWARD
2236
+ elif self.is_farmer_reward(height, coin):
2237
+ tx_type = TransactionType.FEE_REWARD
2238
+ else:
2239
+ tx_type = TransactionType.INCOMING_TX
2240
+
2241
+ coinbase = tx_type in {TransactionType.FEE_REWARD, TransactionType.COINBASE_REWARD}
2242
+ coin_confirmed_transaction = False
2243
+ if not coinbase:
2244
+ for record in all_unconfirmed_transaction_records:
2245
+ if coin in record.additions:
2246
+ await self.tx_store.set_confirmed(record.name, height)
2247
+ coin_confirmed_transaction = True
2248
+ break
2249
+
2250
+ parent_coin_record: Optional[WalletCoinRecord] = await self.coin_store.get_coin_record(coin.parent_coin_info)
2251
+ change = parent_coin_record is not None and wallet_type.value == parent_coin_record.wallet_type
2252
+ # If the coin is from a Clawback spent, we want to add the INCOMING_TX,
2253
+ # no matter if there is another TX updated.
2254
+ clawback = parent_coin_record is not None and parent_coin_record.coin_type == CoinType.CLAWBACK
2255
+
2256
+ if coinbase or clawback or (not coin_confirmed_transaction and not change):
2257
+ tx_record = TransactionRecord(
2258
+ confirmed_at_height=uint32(height),
2259
+ created_at_time=await self.wallet_node.get_timestamp_for_height(height),
2260
+ to_puzzle_hash=await self.convert_puzzle_hash(wallet_id, coin.puzzle_hash),
2261
+ amount=uint64(coin.amount),
2262
+ fee_amount=uint64(0),
2263
+ confirmed=True,
2264
+ sent=uint32(0),
2265
+ spend_bundle=None,
2266
+ additions=[coin],
2267
+ removals=[],
2268
+ wallet_id=wallet_id,
2269
+ sent_to=[],
2270
+ trade_id=None,
2271
+ type=uint32(tx_type),
2272
+ name=coin_name,
2273
+ memos=[],
2274
+ valid_times=ConditionValidTimes(),
2275
+ )
2276
+ if tx_record.amount > 0:
2277
+ await self.tx_store.add_transaction_record(tx_record)
2278
+
2279
+ # We only add normal coins here
2280
+ coin_record: WalletCoinRecord = WalletCoinRecord(
2281
+ coin, height, uint32(0), False, coinbase, wallet_type, wallet_id
2282
+ )
2283
+
2284
+ await self.coin_store.add_coin_record(coin_record, coin_name)
2285
+
2286
+ await self.wallets[wallet_id].coin_added(coin, height, peer, coin_data)
2287
+
2288
+ if wallet_type == WalletType.DAO:
2289
+ return
2290
+
2291
+ await self.create_more_puzzle_hashes()
2292
+
2293
+ async def add_pending_transactions(
2294
+ self,
2295
+ tx_records: list[TransactionRecord],
2296
+ push: bool = True,
2297
+ merge_spends: bool = True,
2298
+ sign: Optional[bool] = None,
2299
+ additional_signing_responses: Optional[list[SigningResponse]] = None,
2300
+ extra_spends: Optional[list[WalletSpendBundle]] = None,
2301
+ ) -> list[TransactionRecord]:
2302
+ """
2303
+ Add a list of transactions to be submitted to the full node.
2304
+ Aggregates the `spend_bundle` property for each transaction onto the first transaction in the list.
2305
+ """
2306
+ if sign is None:
2307
+ sign = self.config.get("auto_sign_txs", True)
2308
+ agg_spend = WalletSpendBundle.aggregate([tx.spend_bundle for tx in tx_records if tx.spend_bundle is not None])
2309
+ if extra_spends is not None:
2310
+ agg_spend = WalletSpendBundle.aggregate([agg_spend, *extra_spends])
2311
+ actual_spend_involved: bool = agg_spend != WalletSpendBundle([], G2Element())
2312
+ if merge_spends and actual_spend_involved:
2313
+ tx_records = [
2314
+ dataclasses.replace(
2315
+ tx,
2316
+ spend_bundle=agg_spend if i == 0 else None,
2317
+ name=agg_spend.name() if i == 0 else bytes32.secret(),
2318
+ )
2319
+ for i, tx in enumerate(tx_records)
2320
+ ]
2321
+ elif extra_spends is not None and extra_spends != []:
2322
+ extra_spends.extend([] if tx_records[0].spend_bundle is None else [tx_records[0].spend_bundle])
2323
+ extra_spend_bundle = WalletSpendBundle.aggregate(extra_spends)
2324
+ tx_records = [
2325
+ dataclasses.replace(
2326
+ tx,
2327
+ spend_bundle=extra_spend_bundle if i == 0 else tx.spend_bundle,
2328
+ name=extra_spend_bundle.name() if i == 0 else bytes32.secret(),
2329
+ )
2330
+ for i, tx in enumerate(tx_records)
2331
+ ]
2332
+ if sign:
2333
+ tx_records, _ = await self.sign_transactions(
2334
+ tx_records,
2335
+ [] if additional_signing_responses is None else additional_signing_responses,
2336
+ additional_signing_responses != [] and additional_signing_responses is not None,
2337
+ )
2338
+ if push:
2339
+ all_coins_names = []
2340
+ async with self.db_wrapper.writer_maybe_transaction():
2341
+ for tx_record in tx_records:
2342
+ # Wallet node will use this queue to retry sending this transaction until full nodes receives it
2343
+ await self.tx_store.add_transaction_record(tx_record)
2344
+ all_coins_names.extend([coin.name() for coin in tx_record.additions])
2345
+ all_coins_names.extend([coin.name() for coin in tx_record.removals])
2346
+
2347
+ await self.add_interested_coin_ids(all_coins_names)
2348
+
2349
+ if actual_spend_involved:
2350
+ self.tx_pending_changed()
2351
+ for wallet_id in {tx.wallet_id for tx in tx_records}:
2352
+ self.state_changed("pending_transaction", wallet_id)
2353
+ await self.wallet_node.update_ui()
2354
+
2355
+ return tx_records
2356
+
2357
+ async def add_transaction(self, tx_record: TransactionRecord) -> None:
2358
+ """
2359
+ Called from wallet to add transaction that is not being set to full_node
2360
+ """
2361
+ await self.tx_store.add_transaction_record(tx_record)
2362
+ self.state_changed("pending_transaction", tx_record.wallet_id)
2363
+
2364
+ async def remove_from_queue(
2365
+ self,
2366
+ spendbundle_id: bytes32,
2367
+ name: str,
2368
+ send_status: MempoolInclusionStatus,
2369
+ error: Optional[Err],
2370
+ ) -> None:
2371
+ """
2372
+ Full node received our transaction, no need to keep it in queue anymore, unless there was an error
2373
+ """
2374
+
2375
+ updated = await self.tx_store.increment_sent(spendbundle_id, name, send_status, error)
2376
+ if updated:
2377
+ tx: Optional[TransactionRecord] = await self.get_transaction(spendbundle_id)
2378
+ if tx is not None and tx.spend_bundle is not None:
2379
+ self.log.info("Checking if we need to cancel trade for tx: %s", tx.name)
2380
+ # we're only interested in errors that are not temporary
2381
+ if (
2382
+ send_status != MempoolInclusionStatus.SUCCESS
2383
+ and error
2384
+ and error not in {Err.INVALID_FEE_LOW_FEE, Err.INVALID_FEE_TOO_CLOSE_TO_ZERO}
2385
+ ):
2386
+ coins_removed = tx.spend_bundle.removals()
2387
+ trade_coins_removed = set()
2388
+ trades = []
2389
+ for removed_coin in coins_removed:
2390
+ trades_by_coin = await self.trade_manager.get_trades_by_coin(removed_coin)
2391
+ for trade in trades_by_coin:
2392
+ if trade is not None and trade.status in {
2393
+ TradeStatus.PENDING_CONFIRM.value,
2394
+ TradeStatus.PENDING_ACCEPT.value,
2395
+ TradeStatus.PENDING_CANCEL.value,
2396
+ }:
2397
+ if trade not in trades:
2398
+ trades.append(trade)
2399
+ # offer was tied to these coins, lets subscribe to them to get a confirmation to
2400
+ # cancel it if it's confirmed
2401
+ # we send transactions to multiple peers, and in cases when mempool gets
2402
+ # fragmented, it's safest to wait for confirmation from blockchain before setting
2403
+ # offer to failed
2404
+ trade_coins_removed.add(removed_coin.name())
2405
+ if trades != [] and trade_coins_removed != set():
2406
+ if not tx.is_valid():
2407
+ # we've tried to send this transaction to a full node multiple times
2408
+ # but failed, it's safe to assume that it's not going to be accepted
2409
+ # we can mark this offer as failed
2410
+ self.log.info("This offer can't be posted, removing it from pending offers")
2411
+ for trade in trades:
2412
+ await self.trade_manager.fail_pending_offer(trade.trade_id)
2413
+ else:
2414
+ self.log.info(
2415
+ "Subscribing to unspendable offer coins: %s",
2416
+ [x.hex() for x in trade_coins_removed],
2417
+ )
2418
+ await self.add_interested_coin_ids(list(trade_coins_removed))
2419
+
2420
+ self.state_changed(
2421
+ "tx_update", tx.wallet_id, {"transaction": tx, "error": error.name, "status": send_status.value}
2422
+ )
2423
+ else:
2424
+ self.state_changed("tx_update", tx.wallet_id, {"transaction": tx})
2425
+
2426
+ async def get_all_transactions(self, wallet_id: int) -> list[TransactionRecord]:
2427
+ """
2428
+ Retrieves all confirmed and pending transactions
2429
+ """
2430
+ records = await self.tx_store.get_all_transactions_for_wallet(wallet_id)
2431
+ return records
2432
+
2433
+ async def get_transaction(self, tx_id: bytes32) -> Optional[TransactionRecord]:
2434
+ return await self.tx_store.get_transaction_record(tx_id)
2435
+
2436
+ async def get_coin_record_by_wallet_record(self, wr: WalletCoinRecord) -> CoinRecord:
2437
+ timestamp: uint64 = await self.wallet_node.get_timestamp_for_height(wr.confirmed_block_height)
2438
+ return wr.to_coin_record(timestamp)
2439
+
2440
+ async def get_coin_records_by_coin_ids(self, **kwargs: Any) -> list[CoinRecord]:
2441
+ result = await self.coin_store.get_coin_records(**kwargs)
2442
+ return [await self.get_coin_record_by_wallet_record(record) for record in result.records]
2443
+
2444
+ async def get_wallet_for_coin(self, coin_id: bytes32) -> Optional[WalletProtocol[Any]]:
2445
+ coin_record = await self.coin_store.get_coin_record(coin_id)
2446
+ if coin_record is None:
2447
+ return None
2448
+ wallet_id = uint32(coin_record.wallet_id)
2449
+ wallet = self.wallets[wallet_id]
2450
+ return wallet
2451
+
2452
+ async def reorg_rollback(self, height: int) -> list[uint32]:
2453
+ """
2454
+ Rolls back and updates the coin_store and transaction store. It's possible this height
2455
+ is the tip, or even beyond the tip.
2456
+ """
2457
+ await self.retry_store.rollback_to_block(height)
2458
+ await self.nft_store.rollback_to_block(height)
2459
+ await self.coin_store.rollback_to_block(height)
2460
+ await self.interested_store.rollback_to_block(height)
2461
+ await self.dl_store.rollback_to_block(height)
2462
+ reorged: list[TransactionRecord] = await self.tx_store.get_transaction_above(height)
2463
+ await self.tx_store.rollback_to_block(height)
2464
+ for record in reorged:
2465
+ if TransactionType(record.type) in {
2466
+ TransactionType.OUTGOING_TX,
2467
+ TransactionType.OUTGOING_TRADE,
2468
+ TransactionType.INCOMING_TRADE,
2469
+ TransactionType.OUTGOING_CLAWBACK,
2470
+ TransactionType.INCOMING_CLAWBACK_SEND,
2471
+ TransactionType.INCOMING_CLAWBACK_RECEIVE,
2472
+ }:
2473
+ await self.tx_store.tx_reorged(record)
2474
+
2475
+ # Removes wallets that were created from a blockchain transaction which got reorged.
2476
+ remove_ids: list[uint32] = []
2477
+ for wallet_id, wallet in self.wallets.items():
2478
+ if wallet.type() == WalletType.POOLING_WALLET.value:
2479
+ assert isinstance(wallet, PoolWallet)
2480
+ remove: bool = await wallet.rewind(height)
2481
+ if remove:
2482
+ remove_ids.append(wallet_id)
2483
+ for wallet_id in remove_ids:
2484
+ await self.delete_wallet(wallet_id)
2485
+ self.state_changed("wallet_removed", wallet_id)
2486
+
2487
+ return remove_ids
2488
+
2489
+ async def _await_closed(self) -> None:
2490
+ await self.db_wrapper.close()
2491
+
2492
+ def unlink_db(self) -> None:
2493
+ Path(self.db_path).unlink()
2494
+
2495
+ async def get_all_wallet_info_entries(self, wallet_type: Optional[WalletType] = None) -> list[WalletInfo]:
2496
+ return await self.user_store.get_all_wallet_info_entries(wallet_type)
2497
+
2498
+ async def get_wallet_for_asset_id(self, asset_id: str) -> Optional[WalletProtocol[Any]]:
2499
+ for wallet_id, wallet in self.wallets.items():
2500
+ if wallet.type() in {WalletType.CAT, WalletType.CRCAT}:
2501
+ assert isinstance(wallet, CATWallet)
2502
+ if wallet.get_asset_id() == asset_id:
2503
+ return wallet
2504
+ elif wallet.type() == WalletType.DATA_LAYER:
2505
+ assert isinstance(wallet, DataLayerWallet)
2506
+ if await wallet.get_latest_singleton(bytes32.from_hexstr(asset_id)) is not None:
2507
+ return wallet
2508
+ elif wallet.type() == WalletType.NFT:
2509
+ assert isinstance(wallet, NFTWallet)
2510
+ nft_coin = await self.nft_store.get_nft_by_id(bytes32.from_hexstr(asset_id), wallet_id)
2511
+ if nft_coin:
2512
+ return wallet
2513
+ return None
2514
+
2515
+ async def get_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo) -> Optional[WalletProtocol[Any]]:
2516
+ for wallet in self.wallets.values():
2517
+ match_function = getattr(wallet, "match_puzzle_info", None)
2518
+ if match_function is not None and callable(match_function):
2519
+ if await match_function(puzzle_driver):
2520
+ return wallet
2521
+ return None
2522
+
2523
+ async def create_wallet_for_puzzle_info(self, puzzle_driver: PuzzleInfo, name: Optional[str] = None) -> None:
2524
+ if AssetType(puzzle_driver.type()) in self.asset_to_wallet_map:
2525
+ await self.asset_to_wallet_map[AssetType(puzzle_driver.type())].create_from_puzzle_info(
2526
+ self,
2527
+ self.main_wallet,
2528
+ puzzle_driver,
2529
+ name,
2530
+ potential_subclasses={
2531
+ AssetType.CR: CRCATWallet,
2532
+ },
2533
+ )
2534
+
2535
+ async def add_new_wallet(self, wallet: WalletProtocol[Any]) -> None:
2536
+ self.wallets[wallet.id()] = wallet
2537
+ await self.create_more_puzzle_hashes()
2538
+ self.state_changed("wallet_created")
2539
+
2540
+ async def get_spendable_coins_for_wallet(
2541
+ self, wallet_id: int, records: Optional[set[WalletCoinRecord]] = None
2542
+ ) -> set[WalletCoinRecord]:
2543
+ wallet_type = self.wallets[uint32(wallet_id)].type()
2544
+ if records is None:
2545
+ if wallet_type == WalletType.CRCAT:
2546
+ records = await self.coin_store.get_unspent_coins_for_wallet(wallet_id, CoinType.CRCAT)
2547
+ else:
2548
+ records = await self.coin_store.get_unspent_coins_for_wallet(wallet_id)
2549
+
2550
+ # Coins that are currently part of a transaction
2551
+ unconfirmed_tx: list[TransactionRecord] = await self.tx_store.get_unconfirmed_for_wallet(wallet_id)
2552
+ removal_dict: dict[bytes32, Coin] = {}
2553
+ for tx in unconfirmed_tx:
2554
+ for coin in tx.removals:
2555
+ # TODO, "if" might not be necessary once unconfirmed tx doesn't contain coins for other wallets
2556
+ if await self.does_coin_belong_to_wallet(coin, wallet_id, tx.hint_dict()):
2557
+ removal_dict[coin.name()] = coin
2558
+
2559
+ # Coins that are part of the trade
2560
+ offer_locked_coins: dict[bytes32, WalletCoinRecord] = await self.trade_manager.get_locked_coins()
2561
+
2562
+ filtered = set()
2563
+ for record in records:
2564
+ if record.coin.name() in offer_locked_coins:
2565
+ continue
2566
+ if record.coin.name() in removal_dict:
2567
+ continue
2568
+ filtered.add(record)
2569
+
2570
+ return filtered
2571
+
2572
+ async def new_peak(self, height: uint32) -> None:
2573
+ for wallet_id, wallet in self.wallets.items():
2574
+ if wallet.type() == WalletType.POOLING_WALLET:
2575
+ assert isinstance(wallet, PoolWallet)
2576
+ await wallet.new_peak(height)
2577
+ current_time = int(time.time())
2578
+
2579
+ if self.wallet_node.last_wallet_tx_resend_time < current_time - self.wallet_node.wallet_tx_resend_timeout_secs:
2580
+ self.tx_pending_changed()
2581
+
2582
+ async def add_interested_puzzle_hashes(self, puzzle_hashes: list[bytes32], wallet_ids: list[int]) -> None:
2583
+ # TODO: It's unclear if the intended use for this is that each puzzle hash should store all
2584
+ # the elements of wallet_ids. It only stores one wallet_id per puzzle hash in the interested_store
2585
+ # but the coin_cache keeps all wallet_ids for each puzzle hash
2586
+ for puzzle_hash in puzzle_hashes:
2587
+ if puzzle_hash in self.interested_coin_cache:
2588
+ wallet_ids_to_add = list({w for w in wallet_ids if w not in self.interested_coin_cache[puzzle_hash]})
2589
+ self.interested_coin_cache[puzzle_hash].extend(wallet_ids_to_add)
2590
+ else:
2591
+ self.interested_coin_cache[puzzle_hash] = list(set(wallet_ids))
2592
+ for puzzle_hash, wallet_id in zip(puzzle_hashes, wallet_ids):
2593
+ await self.interested_store.add_interested_puzzle_hash(puzzle_hash, wallet_id)
2594
+ if len(puzzle_hashes) > 0:
2595
+ await self.wallet_node.new_peak_queue.subscribe_to_puzzle_hashes(puzzle_hashes)
2596
+
2597
+ async def add_interested_coin_ids(self, coin_ids: list[bytes32], wallet_ids: list[int] = []) -> None:
2598
+ # TODO: FIX: wallet_ids is sometimes populated unexpectedly when called from add_pending_transaction
2599
+ for coin_id in coin_ids:
2600
+ if coin_id in self.interested_coin_cache:
2601
+ # prevent repeated wallet_ids from appearing in the coin cache
2602
+ wallet_ids_to_add = list({w for w in wallet_ids if w not in self.interested_coin_cache[coin_id]})
2603
+ self.interested_coin_cache[coin_id].extend(wallet_ids_to_add)
2604
+ else:
2605
+ self.interested_coin_cache[coin_id] = list(set(wallet_ids))
2606
+ for coin_id in coin_ids:
2607
+ await self.interested_store.add_interested_coin_id(coin_id)
2608
+ if len(coin_ids) > 0:
2609
+ await self.wallet_node.new_peak_queue.subscribe_to_coin_ids(coin_ids)
2610
+
2611
+ async def delete_trade_transactions(self, trade_id: bytes32) -> None:
2612
+ txs: list[TransactionRecord] = await self.tx_store.get_transactions_by_trade_id(trade_id)
2613
+ for tx in txs:
2614
+ await self.tx_store.delete_transaction_record(tx.name)
2615
+
2616
+ async def convert_puzzle_hash(self, wallet_id: uint32, puzzle_hash: bytes32) -> bytes32:
2617
+ wallet = self.wallets[wallet_id]
2618
+ # This should be general to wallets but for right now this is just for CATs so we'll add this if
2619
+ if wallet.type() in {WalletType.CAT.value, WalletType.CRCAT.value}:
2620
+ assert isinstance(wallet, CATWallet)
2621
+ return await wallet.convert_puzzle_hash(puzzle_hash)
2622
+
2623
+ return puzzle_hash
2624
+
2625
+ def get_dl_wallet(self) -> DataLayerWallet:
2626
+ for wallet in self.wallets.values():
2627
+ if wallet.type() == WalletType.DATA_LAYER.value:
2628
+ assert isinstance(
2629
+ wallet, DataLayerWallet
2630
+ ), f"WalletType.DATA_LAYER should be a DataLayerWallet instance got: {type(wallet).__name__}"
2631
+ return wallet
2632
+ raise ValueError("DataLayerWallet not available")
2633
+
2634
+ async def get_or_create_vc_wallet(self) -> VCWallet:
2635
+ for _, wallet in self.wallets.items():
2636
+ if WalletType(wallet.type()) == WalletType.VC:
2637
+ assert isinstance(wallet, VCWallet)
2638
+ vc_wallet: VCWallet = wallet
2639
+ break
2640
+ else:
2641
+ # Create a new VC wallet
2642
+ vc_wallet = await VCWallet.create_new_vc_wallet(self, self.main_wallet)
2643
+
2644
+ return vc_wallet
2645
+
2646
+ async def sum_hint_for_pubkey(self, pk: bytes) -> Optional[SumHint]:
2647
+ return await self.main_wallet.sum_hint_for_pubkey(pk)
2648
+
2649
+ async def path_hint_for_pubkey(self, pk: bytes) -> Optional[PathHint]:
2650
+ return await self.main_wallet.path_hint_for_pubkey(pk)
2651
+
2652
+ async def key_hints_for_pubkeys(self, pks: list[bytes]) -> KeyHints:
2653
+ return KeyHints(
2654
+ [sum_hint for pk in pks for sum_hint in (await self.sum_hint_for_pubkey(pk),) if sum_hint is not None],
2655
+ [path_hint for pk in pks for path_hint in (await self.path_hint_for_pubkey(pk),) if path_hint is not None],
2656
+ )
2657
+
2658
+ async def gather_signing_info(self, coin_spends: list[Spend]) -> SigningInstructions:
2659
+ pks: list[bytes] = []
2660
+ signing_targets: list[SigningTarget] = []
2661
+ for coin_spend in coin_spends:
2662
+ _coin_spend = coin_spend.as_coin_spend()
2663
+ # Get AGG_SIG conditions
2664
+ conditions_dict = conditions_dict_for_solution(
2665
+ _coin_spend.puzzle_reveal.to_program(),
2666
+ _coin_spend.solution.to_program(),
2667
+ self.constants.MAX_BLOCK_COST_CLVM,
2668
+ )
2669
+ # Create signature
2670
+ for pk, msg in pkm_pairs_for_conditions_dict(
2671
+ conditions_dict, _coin_spend.coin, self.constants.AGG_SIG_ME_ADDITIONAL_DATA
2672
+ ):
2673
+ pk_bytes = bytes(pk)
2674
+ pks.append(pk_bytes)
2675
+ fingerprint: bytes = pk.get_fingerprint().to_bytes(4, "big")
2676
+ signing_targets.append(SigningTarget(fingerprint, msg, std_hash(pk_bytes + msg)))
2677
+
2678
+ return SigningInstructions(
2679
+ await self.key_hints_for_pubkeys(pks),
2680
+ signing_targets,
2681
+ )
2682
+
2683
+ async def gather_signing_info_for_bundles(self, bundles: list[WalletSpendBundle]) -> list[UnsignedTransaction]:
2684
+ utxs: list[UnsignedTransaction] = []
2685
+ for bundle in bundles:
2686
+ signer_protocol_spends: list[Spend] = [Spend.from_coin_spend(spend) for spend in bundle.coin_spends]
2687
+ utxs.append(
2688
+ UnsignedTransaction(
2689
+ TransactionInfo(signer_protocol_spends), await self.gather_signing_info(signer_protocol_spends)
2690
+ )
2691
+ )
2692
+
2693
+ return utxs
2694
+
2695
+ async def gather_signing_info_for_txs(self, txs: list[TransactionRecord]) -> list[UnsignedTransaction]:
2696
+ return await self.gather_signing_info_for_bundles(
2697
+ [tx.spend_bundle for tx in txs if tx.spend_bundle is not None]
2698
+ )
2699
+
2700
+ async def gather_signing_info_for_trades(self, offers: list[Offer]) -> list[UnsignedTransaction]:
2701
+ return await self.gather_signing_info_for_bundles([offer._bundle for offer in offers])
2702
+
2703
+ async def execute_signing_instructions(
2704
+ self, signing_instructions: SigningInstructions, partial_allowed: bool = False
2705
+ ) -> list[SigningResponse]:
2706
+ return await self.main_wallet.execute_signing_instructions(signing_instructions, partial_allowed)
2707
+
2708
+ async def apply_signatures(
2709
+ self, spends: list[Spend], signing_responses: list[SigningResponse]
2710
+ ) -> SignedTransaction:
2711
+ return await self.main_wallet.apply_signatures(spends, signing_responses)
2712
+
2713
+ def signed_tx_to_spendbundle(self, signed_tx: SignedTransaction) -> WalletSpendBundle:
2714
+ if len([_ for _ in signed_tx.signatures if _.type != "bls_12381_aug_scheme"]) > 0:
2715
+ raise ValueError("Unable to handle signatures that are not bls_12381_aug_scheme") # pragma: no cover
2716
+ return WalletSpendBundle(
2717
+ [spend.as_coin_spend() for spend in signed_tx.transaction_info.spends],
2718
+ AugSchemeMPL.aggregate([G2Element.from_bytes(sig.signature) for sig in signed_tx.signatures]),
2719
+ )
2720
+
2721
+ async def sign_transactions(
2722
+ self,
2723
+ tx_records: list[TransactionRecord],
2724
+ additional_signing_responses: list[SigningResponse] = [],
2725
+ partial_allowed: bool = False,
2726
+ ) -> tuple[list[TransactionRecord], list[SigningResponse]]:
2727
+ unsigned_txs: list[UnsignedTransaction] = await self.gather_signing_info_for_txs(tx_records)
2728
+ new_txs: list[TransactionRecord] = []
2729
+ all_signing_responses = additional_signing_responses.copy()
2730
+ for unsigned_tx, tx in zip(
2731
+ unsigned_txs, [tx_record for tx_record in tx_records if tx_record.spend_bundle is not None]
2732
+ ):
2733
+ signing_responses: list[SigningResponse] = await self.execute_signing_instructions(
2734
+ unsigned_tx.signing_instructions, partial_allowed=partial_allowed
2735
+ )
2736
+ all_signing_responses.extend(signing_responses)
2737
+ new_bundle = self.signed_tx_to_spendbundle(
2738
+ await self.apply_signatures(
2739
+ unsigned_tx.transaction_info.spends,
2740
+ [*additional_signing_responses, *signing_responses],
2741
+ )
2742
+ )
2743
+ new_txs.append(dataclasses.replace(tx, spend_bundle=new_bundle, name=new_bundle.name()))
2744
+ new_txs.extend([tx_record for tx_record in tx_records if tx_record.spend_bundle is None])
2745
+ return new_txs, all_signing_responses
2746
+
2747
+ async def sign_offers(
2748
+ self,
2749
+ offers: list[Offer],
2750
+ additional_signing_responses: list[SigningResponse] = [],
2751
+ partial_allowed: bool = False,
2752
+ ) -> tuple[list[Offer], list[SigningResponse]]:
2753
+ unsigned_txs: list[UnsignedTransaction] = await self.gather_signing_info_for_trades(offers)
2754
+ new_offers: list[Offer] = []
2755
+ all_signing_responses = additional_signing_responses.copy()
2756
+ for unsigned_tx, offer in zip(unsigned_txs, [offer for offer in offers]):
2757
+ signing_responses: list[SigningResponse] = await self.execute_signing_instructions(
2758
+ unsigned_tx.signing_instructions, partial_allowed=partial_allowed
2759
+ )
2760
+ all_signing_responses.extend(signing_responses)
2761
+ new_bundle = self.signed_tx_to_spendbundle(
2762
+ await self.apply_signatures(
2763
+ unsigned_tx.transaction_info.spends,
2764
+ [*additional_signing_responses, *signing_responses],
2765
+ )
2766
+ )
2767
+ new_offers.append(Offer(offer.requested_payments, new_bundle, offer.driver_dict))
2768
+ return new_offers, all_signing_responses
2769
+
2770
+ async def sign_bundle(
2771
+ self,
2772
+ coin_spends: list[CoinSpend],
2773
+ additional_signing_responses: list[SigningResponse] = [],
2774
+ partial_allowed: bool = False,
2775
+ ) -> tuple[WalletSpendBundle, list[SigningResponse]]:
2776
+ [unsigned_tx] = await self.gather_signing_info_for_bundles([WalletSpendBundle(coin_spends, G2Element())])
2777
+ signing_responses: list[SigningResponse] = await self.execute_signing_instructions(
2778
+ unsigned_tx.signing_instructions, partial_allowed=partial_allowed
2779
+ )
2780
+ return (
2781
+ self.signed_tx_to_spendbundle(
2782
+ await self.apply_signatures(
2783
+ unsigned_tx.transaction_info.spends,
2784
+ [*additional_signing_responses, *signing_responses],
2785
+ )
2786
+ ),
2787
+ signing_responses,
2788
+ )
2789
+
2790
+ async def submit_transactions(self, signed_txs: list[SignedTransaction]) -> list[bytes32]:
2791
+ bundles: list[WalletSpendBundle] = [self.signed_tx_to_spendbundle(tx) for tx in signed_txs]
2792
+ for bundle in bundles:
2793
+ await self.wallet_node.push_tx(bundle)
2794
+ return [bundle.name() for bundle in bundles]
2795
+
2796
+ @contextlib.asynccontextmanager
2797
+ async def new_action_scope(
2798
+ self,
2799
+ tx_config: TXConfig,
2800
+ push: bool = False,
2801
+ merge_spends: bool = True,
2802
+ sign: Optional[bool] = None,
2803
+ additional_signing_responses: list[SigningResponse] = [],
2804
+ extra_spends: list[WalletSpendBundle] = [],
2805
+ ) -> AsyncIterator[WalletActionScope]:
2806
+ async with new_wallet_action_scope(
2807
+ self,
2808
+ tx_config,
2809
+ push=push,
2810
+ merge_spends=merge_spends,
2811
+ sign=sign,
2812
+ additional_signing_responses=additional_signing_responses,
2813
+ extra_spends=extra_spends,
2814
+ ) as action_scope:
2815
+ yield action_scope
2816
+
2817
+ async def delete_wallet(self, wallet_id: uint32) -> None:
2818
+ await self.user_store.delete_wallet(wallet_id)
2819
+ await self.puzzle_store.delete_wallet(wallet_id)