Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Used python block formatting and added block claim fees #258

Merged
merged 17 commits into from
Jan 18, 2022
1 change: 1 addition & 0 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pool_info:
description: (example) The Reference Pool allows you to pool with low fees, paying out daily using Chia.
welcome_message: "Welcome to the reference pool!"
pool_fee: 0.01
block_claim_fee: 0
pool_url: http://10.0.0.45
min_difficulty: 10
default_difficulty: 10
Expand Down
2 changes: 1 addition & 1 deletion pool/difficulty_adjustment.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get_new_difficulty(

# If we don't have enough partials at this difficulty and time between last and
# 1st partials is below target time, don't update yet
if len(recent_partials) < number_of_partials_target and time_taken < time_target :
if len(recent_partials) < number_of_partials_target and time_taken < time_target:
return current_difficulty

# Adjust time_taken if number of partials didn't reach number_of_partials_target
Expand Down
20 changes: 14 additions & 6 deletions pool/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from chia.types.blockchain_format.coin import Coin
from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import decode_puzzle_hash
from chia.consensus.constants import ConsensusConstants
from chia.util.ints import uint8, uint16, uint32, uint64
Expand Down Expand Up @@ -77,8 +78,9 @@ def __init__(
self.config = config
self.constants = constants

if pool_config.get('store') == "MariadbPoolStore":
if pool_config.get("store") == "MariadbPoolStore":
from .store.mariadb_store import MariadbPoolStore

self.store: AbstractPoolStore = pool_store or MariadbPoolStore()
else:
self.store: AbstractPoolStore = pool_store or SqlitePoolStore()
Expand Down Expand Up @@ -162,6 +164,9 @@ def __init__(
# Whether or not the wallet is synced (required to make payments)
self.wallet_synced = False

# The fee to pay ( In mojo ) when claiming a block reward
self.claim_fee: uint64 = uint64(pool_config.get("block_claim_fee", 0))

# We target these many partials for this number of seconds. We adjust after receiving this many partials.
self.number_of_partials_target: int = pool_config["number_of_partials_target"]
self.time_target: int = pool_config["time_target"]
Expand Down Expand Up @@ -298,9 +303,9 @@ async def collect_pool_rewards_loop(self):
not_claimable_amounts += ph_to_amounts[rec.p2_singleton_puzzle_hash]

if len(coin_records) > 0:
self.log.info(f"Claimable amount: {claimable_amounts / (10**12)}")
self.log.info(f"Not claimable amount: {not_claimable_amounts / (10**12)}")
self.log.info(f"Not buried amounts: {not_buried_amounts / (10**12)}")
self.log.info(f"Claimable amount: {claimable_amounts / (10 ** 12)}")
self.log.info(f"Not claimable amount: {not_claimable_amounts / (10 ** 12)}")
self.log.info(f"Not buried amounts: {not_buried_amounts / (10 ** 12)}")

for rec in farmer_records:
if rec.is_pool_member:
Expand Down Expand Up @@ -328,6 +333,9 @@ async def collect_pool_rewards_loop(self):
self.blockchain_state["peak"].height,
ph_to_coins[rec.p2_singleton_puzzle_hash],
self.constants.GENESIS_CHALLENGE,
self.claim_fee,
self.wallet_rpc_client,
self.default_target_puzzle_hash,
)

if spend_bundle is None:
Expand Down Expand Up @@ -388,8 +396,8 @@ async def create_payment_loop(self):
continue

self.log.info(f"Total amount claimed: {total_amount_claimed / (10 ** 12)}")
self.log.info(f"Pool coin amount (includes blockchain fee) {pool_coin_amount / (10 ** 12)}")
self.log.info(f"Total amount to distribute: {amount_to_distribute / (10 ** 12)}")
self.log.info(f"Pool coin amount (includes blockchain fee) {pool_coin_amount / (10 ** 12)}")
self.log.info(f"Total amount to distribute: {amount_to_distribute / (10 ** 12)}")

async with self.store.lock:
# Get the points of each farmer, as well as payout instructions. Here a chia address is used,
Expand Down
8 changes: 5 additions & 3 deletions pool/pool_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ async def post_farmer(self, request_obj) -> web.Response:
return authentication_token_error

post_farmer_response = await self.pool.add_farmer(
post_farmer_request, self.post_metadata_from_request(request_obj))
post_farmer_request, self.post_metadata_from_request(request_obj)
)

self.pool.log.info(
f"post_farmer response {post_farmer_response}, "
Expand All @@ -197,8 +198,9 @@ async def put_farmer(self, request_obj) -> web.Response:
return authentication_token_error

# Process the request
put_farmer_response = await self.pool.update_farmer(put_farmer_request,
self.post_metadata_from_request(request_obj))
put_farmer_response = await self.pool.update_farmer(
put_farmer_request, self.post_metadata_from_request(request_obj)
)

self.pool.log.info(
f"put_farmer response {put_farmer_response}, "
Expand Down
30 changes: 29 additions & 1 deletion pool/singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
from chia.pools.pool_wallet import PoolSingletonState
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, SerializedProgram
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint32, uint64
from chia.wallet.transaction_record import TransactionRecord


from .record import FarmerRecord

Expand Down Expand Up @@ -141,6 +145,9 @@ async def create_absorb_transaction(
peak_height: uint32,
reward_coin_records: List[CoinRecord],
genesis_challenge: bytes32,
fee_amount: Optional[uint64] = None,
wallet_rpc_client: Optional[WalletRpcClient] = None,
fee_target_puzzle_hash: Optional[bytes32] = None,
) -> Optional[SpendBundle]:
singleton_state_tuple: Optional[Tuple[CoinSpend, PoolState, PoolState]] = await get_singleton_state(
node_rpc_client, farmer_record.launcher_id, farmer_record, peak_height, 0, genesis_challenge
Expand All @@ -160,6 +167,7 @@ async def create_absorb_transaction(
farmer_record.launcher_id
)
assert launcher_coin_record is not None
coinbase_coin_records: List[CoinRecord] = []

all_spends: List[CoinSpend] = []
for reward_coin_record in reward_coin_records:
Expand All @@ -168,6 +176,8 @@ async def create_absorb_transaction(
# The puzzle does not allow spending coins that are not a coinbase reward
log.info(f"Received reward {reward_coin_record.coin} that is not a pool reward.")
continue
else:
coinbase_coin_records.append(reward_coin_record)
absorb_spend: List[CoinSpend] = create_absorb_spend(
last_spend,
last_state,
Expand All @@ -186,6 +196,24 @@ async def create_absorb_transaction(
# - create an output with slightly less XCH, to yourself. for example, 1.7499 XCH
# - The remaining value will automatically be used as a fee

if fee_amount > 0:
# address can be anything
signed_transaction: TransactionRecord = await wallet_rpc_client.create_signed_transaction(
additions=[{"amount": uint64(1), "puzzle_hash": fee_target_puzzle_hash}],
fee=fee_amount,
jack60612 marked this conversation as resolved.
Show resolved Hide resolved
puzzle_announcements=[
Announcement(coin_record.coin.puzzle_hash, coin_record.coin.name())
for coin_record in coinbase_coin_records
],
)
fee_spend_bundle: Optional[SpendBundle] = signed_transaction.spend_bundle
else:
fee_spend_bundle = None

if len(all_spends) == 0:
return None
return SpendBundle(all_spends, G2Element())
spend_bundle: SpendBundle = SpendBundle(all_spends, G2Element())
if fee_spend_bundle is not None:
spend_bundle = SpendBundle.aggregate([spend_bundle, fee_spend_bundle])

return spend_bundle
51 changes: 27 additions & 24 deletions pool/store/mariadb_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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_solution import CoinSolution
from chia.types.coin_solution import CoinSpend
from chia.util.ints import uint64

from .abstract import AbstractPoolStore
Expand All @@ -21,29 +21,31 @@
pymysql.converters.conversions = pymysql.converters.encoders.copy()
pymysql.converters.conversions.update(pymysql.converters.decoders)


class MariadbPoolStore(AbstractPoolStore):
"""
Pool store based on MariaDB.
"""

async def connect(self):
try:
#initialize logging
# initialize logging
self.log = logging
#load config
# load config
with open(os.getcwd() + "/config.yaml") as f:
config: Dict = yaml.safe_load(f)
self.pool = await aiomysql.create_pool(
minsize=1,
maxsize=12,
host=config["db_host"],
port=config["db_port"],
user=config["db_user"],
password=config["db_password"],
db=config["db_name"],
minsize=1,
maxsize=12,
host=config["db_host"],
port=config["db_port"],
user=config["db_user"],
password=config["db_password"],
db=config["db_name"],
)
except pymysql.err.OperationalError as e:
self.log.error("Error In Database Config. Check your config file! %s", e)
raise ConnectionError('Unable to Connect to SQL Database.')
self.log.error("Error In Database Config. Check your config file! %s", e)
raise ConnectionError("Unable to Connect to SQL Database.")
self.connection = await self.pool.acquire()
self.cursor = await self.connection.cursor()
await self.cursor.execute(
Expand Down Expand Up @@ -74,7 +76,6 @@ async def connect(self):
await self.cursor.execute("CREATE INDEX IF NOT EXISTS launcher_id_index on partial(launcher_id)")
await self.connection.commit()
self.pool.release(self.connection)


@staticmethod
def _row_to_farmer_record(row) -> FarmerRecord:
Expand All @@ -84,7 +85,7 @@ def _row_to_farmer_record(row) -> FarmerRecord:
row[2],
bytes.fromhex(row[3]),
G1Element.from_bytes(bytes.fromhex(row[4])),
CoinSolution.from_bytes(row[5]),
CoinSpend.from_bytes(row[5]),
PoolState.from_bytes(row[6]),
row[7],
row[8],
Expand All @@ -96,9 +97,9 @@ async def add_farmer_record(self, farmer_record: FarmerRecord, metadata: Request
with (await self.pool) as connection:
cursor = await connection.cursor()
await cursor.execute(
f"INSERT INTO farmer VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) "
f"INSERT INTO farmer VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) "
f"ON DUPLICATE KEY UPDATE p2_singleton_puzzle_hash=%s, delay_time=%s, delay_puzzle_hash=%s,"
f"authentication_public_key=%s, singleton_tip=%s, singleton_tip_state=%s, payout_instructions=%s, "
f"authentication_public_key=%s, singleton_tip=%s, singleton_tip_state=%s, payout_instructions=%s, "
f"is_pool_member=%s",
(
farmer_record.launcher_id.hex(),
Expand Down Expand Up @@ -129,7 +130,8 @@ async def get_farmer_record(self, launcher_id: bytes32) -> Optional[FarmerRecord
with (await self.pool) as connection:
cursor = await connection.cursor()
await cursor.execute(
f"SELECT * FROM farmer WHERE launcher_id=%s",(launcher_id.hex(),),
f"SELECT * FROM farmer WHERE launcher_id=%s",
(launcher_id.hex(),),
)
row = await cursor.fetchone()
if row is None:
Expand All @@ -144,12 +146,11 @@ async def update_difficulty(self, launcher_id: bytes32, difficulty: uint64):
f"UPDATE farmer SET difficulty=%s WHERE launcher_id=%s", (difficulty, launcher_id.hex())
)
await connection.commit()


async def update_singleton(
self,
launcher_id: bytes32,
singleton_tip: CoinSolution,
singleton_tip: CoinSpend,
singleton_tip_state: PoolState,
is_pool_member: bool,
):
Expand All @@ -171,7 +172,7 @@ async def get_pay_to_singleton_phs(self) -> Set[bytes32]:

all_phs: Set[bytes32] = set()
for row in rows:
all_phs.add(bytes32(bytes.fromhex(row[0])))
all_phs.add(bytes32(bytes.fromhex(row[0])))
return all_phs

async def get_farmer_records_for_p2_singleton_phs(self, puzzle_hashes: Set[bytes32]) -> List[FarmerRecord]:
Expand Down Expand Up @@ -215,11 +216,12 @@ async def clear_farmer_points(self) -> None:
await cursor.close()
await connection.commit()


async def add_partial(self, launcher_id: bytes32, timestamp: uint64, difficulty: uint64):
with (await self.pool) as connection:
cursor = await connection.cursor()
await cursor.execute("INSERT INTO partial VALUES(%s, %s, %s)",(launcher_id.hex(), timestamp, difficulty),
await cursor.execute(
"INSERT INTO partial VALUES(%s, %s, %s)",
(launcher_id.hex(), timestamp, difficulty),
)
await connection.commit()
with (await self.pool) as connection:
Expand All @@ -238,6 +240,7 @@ async def get_recent_partials(self, launcher_id: bytes32, count: int) -> List[Tu
(launcher_id.hex(), count),
)
rows = await cursor.fetchall()
ret: List[Tuple[uint64, uint64]] = [(uint64(timestamp), uint64(difficulty)) for timestamp, difficulty in rows]
ret: List[Tuple[uint64, uint64]] = [
(uint64(timestamp), uint64(difficulty)) for timestamp, difficulty in rows
]
return ret

1 change: 1 addition & 0 deletions pool/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class RequestMetadata:
"""
HTTP-related metadata passed with HTTP requests
"""

url: str # original request url, as used by the client
scheme: str # for example https
headers: Mapping[str, str] # header names are all lower case
Expand Down
1 change: 1 addition & 0 deletions tests/test_difficulty.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,6 @@ def test_partials_low_24h_decreases_diff(self):

assert get_new_difficulty(partials, num_partials * 2, time_target, 20, current_time, 1) == 9


if __name__ == "__main__":
unittest.main()