diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5eeed42..e30738c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,10 +2,11 @@ name: black-action on: [push, pull_request] jobs: linter_name: - name: runner / black formatter + name: runner & black formatter runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: rickstaa/action-black@v1 + - uses: psf/black@stable with: black_args: ". --check" + src: "." diff --git a/README.md b/README.md index 96d87d1..d20f6e4 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ farmers, and points should be acknowledged and accumulated points returned in th ### Difficulty adjustment algorithm -This is a simple difficulty adjustment algorithm executed by the pool. The pool can also improve this or change it +This is a simple difficulty adjustment algorithm executed by the pool. The pool can also improve this or change it however they wish. The farmer can provide their own `suggested_difficulty`, and the pool can decide whether or not to update that farmer's difficulty. Be careful to only accept the latest authentication_public_key when setting difficulty or pool payout info. The initial reference client and pool do not use the `suggested_difficulty`. @@ -143,7 +143,7 @@ difficulty or pool payout info. The initial reference client and pool do not use - If < 45 minutes: - If have < 300 partials at this difficulty, maintain same difficulty - Else, multiply the difficulty by (24 * 3600 / (time taken for 300 partials)) - + The 6 hours is used to handle rare cases where a farmer's storage drops dramatically. The 45 minutes is similar, but for less extreme cases. Finally, the last case of < 45 minutes should properly handle users with increasing space, or slightly decreasing space. This targets 300 partials per day, but different numbers can be used based on @@ -158,8 +158,8 @@ latest seen authentication key for that launcher_id. ### Install and run (Testnet) To run a pool, you must use this along with the main branch of `chia-blockchain`. -1. Checkout the `main` branch of `chia-blockchain`, and install it. Checkout this repo in another directory next to (not inside) `chia-blockchain`. -If you want to connect to testnet, use `export CHIA_ROOT=~/.chia/testnet9`, or whichever testnet you want to join, and +1. Checkout the `main` branch of `chia-blockchain`, and install it. Checkout this repo in another directory next to (not inside) `chia-blockchain`. +If you want to connect to testnet, use `export CHIA_ROOT=~/.chia/testnet9`, or whichever testnet you want to join, and run `chia configure -t true`. You can also directly use the `pools.testnet9` branch, although this branch will be removed in the future (or past). @@ -168,14 +168,14 @@ If you want to connect to testnet, use `export CHIA_ROOT=~/.chia/testnet9`, or w 3. Change the `wallet_fingerprint` and `wallet_id` in the `config.yaml` config file, using the information from the first key you created in step 2. These can be obtained by doing `chia wallet show`. -4. Do `chia keys show` and get the first address for each of the keys created in step 2. Put these into the `config.yaml` +4. Do `chia keys show` and get the first address for each of the keys created in step 2. Put these into the `config.yaml` config file in `default_target_address` and `pool_fee_address` respectively. - -5. Change the `pool_url` in `config.yaml` to point to your external ip or hostname. + +5. Change the `pool_url` in `config.yaml` to point to your external ip or hostname. This must match exactly with what the user enters into their UI or CLI, and must start with https://. - -6. Start the node using `chia start farmer`, and log in to a different key (not the two keys created for the pool). -This will be referred to as the farmer's key here. Sync up your wallet on testnet for the farmer key. + +6. Start the node using `chia start farmer`, and log in to a different key (not the two keys created for the pool). +This will be referred to as the farmer's key here. Sync up your wallet on testnet for the farmer key. You can log in to a key by running `chia wallet show` and then choosing each wallet in turn, to make them start syncing. 7. Create a venv (different from chia-blockchain) and start the pool server using the following commands: @@ -195,19 +195,19 @@ INFO:root:Obtaining balance: {'confirmed_wallet_balance': 0, 'max_send_amount': ``` 8. Create a pool nft (on the farmer key) by doing `chia plotnft create -s pool -u https://127.0.0.1:80`, or whatever host:port you want -to use for your pool. Approve it and wait for transaction confirmation. This url must match *exactly* with what the +to use for your pool. Approve it and wait for transaction confirmation. This url must match *exactly* with what the pool uses. - + 9. Do `chia plotnft show` to ensure that your plotnft is created. Now start making some plots for this pool nft. -You can make plots by specifying the -c argument in `chia plots create`. Make sure to *not* use the `-p` argument. The +You can make plots by specifying the -c argument in `chia plots create`. Make sure to *not* use the `-p` argument. The value you should use for -c is the `P2 singleton address` from `chia plotnft show` output. You can start with small k25 plots and see if partials are submitted from the farmer to the pool server. The output will be the following in the pool if everything is working: ``` INFO:root:Returning {'new_difficulty': 1963211364}, time: 0.017535686492919922 singleton: 0x1f8dab79a614a82f9834c8f395f5fe195ae020807169b71a10218b9788a7a573 ``` - + Please send a message to @sorgente711 on keybase if you have questions about the 9 steps explained above. All other questions -should be sent to the #pools channel in keybase. +should be sent to the #pools channel in keybase. + - diff --git a/pool/pool.py b/pool/pool.py index 34fcaee..e5e290a 100644 --- a/pool/pool.py +++ b/pool/pool.py @@ -8,9 +8,9 @@ from math import floor from typing import Any, Callable, Dict, List, Optional, Set, Tuple -from blspy import AugSchemeMPL, G1Element from chia.consensus.block_rewards import calculate_pool_reward from chia.consensus.constants import ConsensusConstants +from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.consensus.pot_iterations import calculate_iterations_quality from chia.full_node.signage_point import SignagePoint from chia.pools.pool_puzzles import ( @@ -29,6 +29,7 @@ PutFarmerRequest, ) from chia.rpc.full_node_rpc_client import FullNodeRpcClient +from chia.rpc.wallet_request_types import SendTransactionMultiResponse from chia.rpc.wallet_rpc_client import WalletRpcClient from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.proof_of_space import verify_and_get_quality_string @@ -43,6 +44,8 @@ from chia.util.ints import uint8, uint16, uint32, uint64 from chia.util.lru_cache import LRUCache from chia.wallet.transaction_record import TransactionRecord +from chia.wallet.util.tx_config import TXConfig +from chia_rs import AugSchemeMPL, G1Element from .difficulty_adjustment import get_new_difficulty from .record import FarmerRecord @@ -317,9 +320,9 @@ async def collect_pool_rewards_loop(self) -> None: if singleton_tip is None: continue - singleton_coin_record: Optional[ - CoinRecord - ] = await self.node_rpc_client.get_coin_record_by_name(singleton_tip.name()) + singleton_coin_record: Optional[CoinRecord] = ( + await self.node_rpc_client.get_coin_record_by_name(singleton_tip.name()) + ) if singleton_coin_record is None: continue if singleton_coin_record.spent: @@ -406,9 +409,9 @@ async def create_payment_loop(self) -> None: async with self.store.lock: # Get the points of each farmer, as well as payout instructions. Here a chia address is used, # but other blockchain addresses can also be used. - points_and_ph: List[ - Tuple[uint64, bytes] - ] = await self.store.get_farmer_points_and_payout_instructions() + points_and_ph: List[Tuple[uint64, bytes]] = ( + await self.store.get_farmer_points_and_payout_instructions() + ) total_points = sum([pt for (pt, ph) in points_and_ph]) if total_points > 0: mojo_per_point = floor(amount_to_distribute / total_points) @@ -466,10 +469,18 @@ async def submit_payment_loop(self) -> None: # blockchain_fee = 0.00001 * (10 ** 12) * len(payment_targets) blockchain_fee: uint64 = uint64(0) try: + tx_config = TXConfig( + max_coin_amount=uint64(DEFAULT_CONSTANTS.MAX_COIN_AMOUNT), + min_coin_amount=uint64(0), + excluded_coin_amounts=[uint64(0)], + excluded_coin_ids=[], + reuse_puzhash=False, + ) assert self.wallet_rpc_client - transaction: TransactionRecord = await self.wallet_rpc_client.send_transaction_multi( - self.wallet_id, payment_targets, fee=blockchain_fee + response: SendTransactionMultiResponse = await self.wallet_rpc_client.send_transaction_multi( + self.wallet_id, payment_targets, tx_config, fee=blockchain_fee ) + transaction: TransactionRecord = response.transaction except ValueError as e: self.log.error(f"Error making payment: {e}") await asyncio.sleep(10) @@ -484,7 +495,7 @@ async def submit_payment_loop(self) -> None: or not (peak_height - transaction.confirmed_at_height) > self.confirmation_security_threshold ): assert self.wallet_rpc_client - transaction = await self.wallet_rpc_client.get_transaction(self.wallet_id, transaction.name) + transaction = await self.wallet_rpc_client.get_transaction(transaction.name) peak_height = self.blockchain_state["peak"].height self.log.info( f"Waiting for transaction to obtain {self.confirmation_security_threshold} confirmations" @@ -556,9 +567,9 @@ async def check_and_confirm_partial(self, partial: PostPartialRequest, points_re self.recent_points_added.put(pos_hash, uint64(1)) # Now we need to check to see that the singleton in the blockchain is still assigned to this pool - singleton_state_tuple: Optional[ - Tuple[CoinSpend, PoolState, bool] - ] = await self.get_and_validate_singleton_state(partial.payload.launcher_id) + singleton_state_tuple: Optional[Tuple[CoinSpend, PoolState, bool]] = ( + await self.get_and_validate_singleton_state(partial.payload.launcher_id) + ) if singleton_state_tuple is None: self.log.info(f"Invalid singleton {partial.payload.launcher_id}") @@ -616,9 +627,9 @@ async def add_farmer(self, request: PostFarmerRequest, metadata: RequestMetadata f"Farmer with launcher_id {request.payload.launcher_id} already known.", ) - singleton_state_tuple: Optional[ - Tuple[CoinSpend, PoolState, bool] - ] = await self.get_and_validate_singleton_state(request.payload.launcher_id) + singleton_state_tuple: Optional[Tuple[CoinSpend, PoolState, bool]] = ( + await self.get_and_validate_singleton_state(request.payload.launcher_id) + ) if singleton_state_tuple is None: return error_dict(PoolErrorCode.INVALID_SINGLETON, f"Invalid singleton {request.payload.launcher_id}") @@ -694,9 +705,9 @@ async def update_farmer(self, request: PutFarmerRequest, metadata: RequestMetada if farmer_record is None: return error_dict(PoolErrorCode.FARMER_NOT_KNOWN, f"Farmer with launcher_id {launcher_id} not known.") - singleton_state_tuple: Optional[ - Tuple[CoinSpend, PoolState, bool] - ] = await self.get_and_validate_singleton_state(launcher_id) + singleton_state_tuple: Optional[Tuple[CoinSpend, PoolState, bool]] = ( + await self.get_and_validate_singleton_state(launcher_id) + ) if singleton_state_tuple is None: return error_dict(PoolErrorCode.INVALID_SINGLETON, f"Invalid singleton {request.payload.launcher_id}") diff --git a/pool/pool_server.py b/pool/pool_server.py index 923c630..a4355cd 100644 --- a/pool/pool_server.py +++ b/pool/pool_server.py @@ -9,8 +9,7 @@ import aiohttp import yaml from aiohttp import web -from blspy import AugSchemeMPL, G2Element -from chia.consensus.constants import ConsensusConstants +from chia.consensus.constants import ConsensusConstants, replace_str_to_bytes from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.protocols.pool_protocol import ( POOL_PROTOCOL_VERSION, @@ -30,6 +29,7 @@ from chia.util.hash import std_hash from chia.util.ints import uint8, uint32, uint64 from chia.util.json_util import obj_to_response +from chia_rs import AugSchemeMPL, G2Element from .pool import Pool from .record import FarmerRecord @@ -291,7 +291,7 @@ async def start_pool_server(pool_store: Optional[AbstractPoolStore] = None): global runner config = load_config(DEFAULT_ROOT_PATH, "config.yaml") overrides = config["network_overrides"]["constants"][config["selected_network"]] - constants: ConsensusConstants = DEFAULT_CONSTANTS.replace_str_to_bytes(**overrides) + constants: ConsensusConstants = replace_str_to_bytes(DEFAULT_CONSTANTS, **overrides) server = PoolServer(config, constants, pool_store) await server.start() diff --git a/pool/record.py b/pool/record.py index 18d6581..45ad315 100644 --- a/pool/record.py +++ b/pool/record.py @@ -1,11 +1,11 @@ from dataclasses import dataclass -from blspy import G1Element from chia.pools.pool_wallet_info import PoolState from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.util.ints import uint64 from chia.util.streamable import Streamable, streamable +from chia_rs import G1Element @streamable diff --git a/pool/singleton.py b/pool/singleton.py index b5cb40a..75e8002 100644 --- a/pool/singleton.py +++ b/pool/singleton.py @@ -15,7 +15,6 @@ from chia.pools.pool_wallet_info import PoolState from chia.rpc.full_node_rpc_client import FullNodeRpcClient from chia.rpc.wallet_rpc_client import WalletRpcClient -from chia.types.announcement import Announcement from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 @@ -23,7 +22,9 @@ from chia.types.coin_spend import CoinSpend from chia.types.spend_bundle import SpendBundle from chia.util.ints import uint32, uint64 +from chia.wallet.conditions import AssertCoinAnnouncement, Condition from chia.wallet.transaction_record import TransactionRecord +from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG from .record import FarmerRecord @@ -173,7 +174,7 @@ async def create_absorb_transaction( farmer_record.launcher_id ) assert launcher_coin_record is not None - coin_announcements: List[Announcement] = [] + coin_announcements: Tuple[Condition, ...] = tuple() all_spends: List[CoinSpend] = [] for reward_coin_record in reward_coin_records: @@ -192,7 +193,10 @@ async def create_absorb_transaction( farmer_record.delay_puzzle_hash, ) if fee_amount > 0: - coin_announcements.append(Announcement(reward_coin_record.coin.name(), b"$")) + coin_announcements = ( + *coin_announcements, + AssertCoinAnnouncement(asserted_id=reward_coin_record.coin.name(), asserted_msg=b"$"), + ) last_spend = absorb_spend[0] all_spends += absorb_spend # TODO(pool): handle the case where the cost exceeds the size of the block @@ -200,11 +204,14 @@ async def create_absorb_transaction( if len(coin_announcements) > 0: # address can be anything assert wallet_rpc_client - signed_transaction: TransactionRecord = await wallet_rpc_client.create_signed_transaction( - additions=[{"amount": uint64(1), "puzzle_hash": fee_target_puzzle_hash}], - fee=uint64(fee_amount * len(coin_announcements)), - coin_announcements=coin_announcements, - ) + signed_transaction: TransactionRecord = ( + await wallet_rpc_client.create_signed_transactions( + additions=[{"amount": uint64(1), "puzzle_hash": fee_target_puzzle_hash}], + tx_config=DEFAULT_TX_CONFIG, + fee=uint64(fee_amount * len(coin_announcements)), + extra_conditions=(*coin_announcements,), + ) + ).signed_tx fee_spend_bundle: Optional[SpendBundle] = signed_transaction.spend_bundle else: fee_spend_bundle = None diff --git a/pool/store/mariadb_store.py b/pool/store/mariadb_store.py index 2553706..963cd32 100644 --- a/pool/store/mariadb_store.py +++ b/pool/store/mariadb_store.py @@ -5,19 +5,19 @@ import aiomysql import pymysql # type: ignore import yaml -from blspy import G1Element from chia.pools.pool_wallet_info import PoolState from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.util.ints import uint64 +from chia_rs import G1Element from ..record import FarmerRecord from ..util import RequestMetadata from .abstract import AbstractPoolStore pymysql.converters.encoders[uint64] = pymysql.converters.escape_int -pymysql.converters.conversions = pymysql.converters.encoders.copy() -pymysql.converters.conversions.update(pymysql.converters.decoders) +pymysql.converters.conversions = pymysql.converters.encoders.copy() # type:ignore +pymysql.converters.conversions.update(pymysql.converters.decoders) # type:ignore class MariadbPoolStore(AbstractPoolStore): diff --git a/pool/store/sqlite_store.py b/pool/store/sqlite_store.py index dedb27c..3544caa 100644 --- a/pool/store/sqlite_store.py +++ b/pool/store/sqlite_store.py @@ -4,11 +4,11 @@ from typing import Dict, List, Optional, Set, Tuple import aiosqlite -from blspy import G1Element from chia.pools.pool_wallet_info import PoolState from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.util.ints import uint64 +from chia_rs import G1Element from ..record import FarmerRecord from ..util import RequestMetadata diff --git a/setup.py b/setup.py index 6dbd89d..9a55f91 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,8 @@ def read(fname): dependencies = [ - "chia-blockchain==2.0.0", - "blspy==2.0.3", + "chia-blockchain==2.4.2", + "chia_rs>=0.5.2", "setuptools~=56.1.0", "aiosqlite==0.20.0", "aiohttp==3.9.5",