@@ -149,9 +149,6 @@ def __init__(
149
149
# faster.
150
150
self .max_additions_per_transaction = pool_config ["max_additions_per_transaction" ]
151
151
152
- # This is the list of payments that we have not sent yet, to farmers
153
- self .pending_payments : Optional [asyncio .Queue ] = None
154
-
155
152
# Keeps track of the latest state of our node
156
153
self .blockchain_state = {"peak" : None }
157
154
@@ -167,6 +164,7 @@ def __init__(
167
164
self .collect_pool_rewards_loop_task : Optional [asyncio .Task ] = None
168
165
self .create_payment_loop_task : Optional [asyncio .Task ] = None
169
166
self .submit_payment_loop_task : Optional [asyncio .Task ] = None
167
+ self .confirm_payment_loop_task : Optional [asyncio .Task ] = None
170
168
self .get_peak_loop_task : Optional [asyncio .Task ] = None
171
169
172
170
self .node_rpc_client : Optional [FullNodeRpcClient ] = None
@@ -199,10 +197,9 @@ async def start(self):
199
197
self .collect_pool_rewards_loop_task = asyncio .create_task (self .collect_pool_rewards_loop ())
200
198
self .create_payment_loop_task = asyncio .create_task (self .create_payment_loop ())
201
199
self .submit_payment_loop_task = asyncio .create_task (self .submit_payment_loop ())
200
+ self .confirm_payment_loop_task = asyncio .create_task (self .confirm_payment_loop ())
202
201
self .get_peak_loop_task = asyncio .create_task (self .get_peak_loop ())
203
202
204
- self .pending_payments = asyncio .Queue ()
205
-
206
203
async def stop (self ):
207
204
if self .confirm_partials_loop_task is not None :
208
205
self .confirm_partials_loop_task .cancel ()
@@ -212,6 +209,8 @@ async def stop(self):
212
209
self .create_payment_loop_task .cancel ()
213
210
if self .submit_payment_loop_task is not None :
214
211
self .submit_payment_loop_task .cancel ()
212
+ if self .confirm_payment_loop_task is not None :
213
+ self .confirm_payment_loop_task .cancel ()
215
214
if self .get_peak_loop_task is not None :
216
215
self .get_peak_loop_task .cancel ()
217
216
@@ -356,8 +355,8 @@ async def create_payment_loop(self):
356
355
await asyncio .sleep (60 )
357
356
continue
358
357
359
- if self .pending_payments . qsize ( ) != 0 :
360
- self .log .warning (f"Pending payments ({ self . pending_payments . qsize () } ), waiting" )
358
+ if ( pending_payment_count := await self .store . get_pending_payment_count () ) != 0 :
359
+ self .log .warning (f"Pending payments ({ pending_payment_count } ), waiting" )
361
360
await asyncio .sleep (60 )
362
361
continue
363
362
@@ -387,31 +386,45 @@ async def create_payment_loop(self):
387
386
self .log .info (f"Total amount to distribute: { amount_to_distribute / (10 ** 12 )} " )
388
387
389
388
async with self .store .lock :
390
- # Get the points of each farmer, as well as payout instructions. Here a chia address is used,
391
- # but other blockchain addresses can also be used.
392
- points_and_ph : List [
393
- Tuple [uint64 , bytes ]
394
- ] = await self .store .get_farmer_points_and_payout_instructions ()
395
- total_points = sum ([pt for (pt , ph ) in points_and_ph ])
389
+ # Get the launcher_id and points of each farmer, as well as payout instructions.
390
+ # Here a chia address is used, but other blockchain addresses can also be used.
391
+ launcher_id_and_points_and_ph : List [
392
+ Tuple [bytes32 , uint64 , bytes32 ]
393
+ ] = await self .store .get_farmer_launcher_id_and_points_and_payout_instructions ()
394
+ total_points = sum ([pt for (launcher_id , pt , ph ) in launcher_id_and_points_and_ph ])
396
395
if total_points > 0 :
397
396
mojo_per_point = floor (amount_to_distribute / total_points )
398
397
self .log .info (f"Paying out { mojo_per_point } mojo / point" )
399
398
399
+ # Pool fee payment record launcher_id is equal to puzzle_hash, points is 0.
400
400
additions_sub_list : List [Dict ] = [
401
- {"puzzle_hash" : self .pool_fee_puzzle_hash , "amount" : pool_coin_amount }
401
+ {
402
+ "launcher_id" : self .pool_fee_puzzle_hash ,
403
+ "puzzle_hash" : self .pool_fee_puzzle_hash ,
404
+ "amount" : pool_coin_amount ,
405
+ "points" : 0 ,
406
+ }
402
407
]
403
- for points , ph in points_and_ph :
408
+ for launcher_id , points , ph in launcher_id_and_points_and_ph :
404
409
if points > 0 :
405
- additions_sub_list .append ({"puzzle_hash" : ph , "amount" : points * mojo_per_point })
406
-
407
- if len (additions_sub_list ) == self .max_additions_per_transaction :
408
- await self .pending_payments .put (additions_sub_list .copy ())
409
- self .log .info (f"Will make payments: { additions_sub_list } " )
410
- additions_sub_list = []
410
+ additions_sub_list .append ({
411
+ "launcher_id" : launcher_id ,
412
+ "puzzle_hash" : ph ,
413
+ "amount" : points * mojo_per_point ,
414
+ "points" : points ,
415
+ })
416
+
417
+ for payment in additions_sub_list :
418
+ await self .store .add_payment (
419
+ payment ["launcher_id" ],
420
+ payment ["puzzle_hash" ],
421
+ uint64 (payment ["amount" ]),
422
+ payment ["points" ],
423
+ uint64 (int (time .time ())),
424
+ False ,
425
+ )
411
426
412
- if len (additions_sub_list ) > 0 :
413
- self .log .info (f"Will make payments: { additions_sub_list } " )
414
- await self .pending_payments .put (additions_sub_list .copy ())
427
+ self .log .info (f"Will make payments: { additions_sub_list } " )
415
428
416
429
# Subtract the points from each farmer
417
430
await self .store .clear_farmer_points ()
@@ -430,58 +443,89 @@ async def create_payment_loop(self):
430
443
async def submit_payment_loop (self ):
431
444
while True :
432
445
try :
433
- peak_height = self .blockchain_state ["peak" ].height
434
446
await self .wallet_rpc_client .log_in_and_skip (fingerprint = self .wallet_fingerprint )
435
447
if not self .blockchain_state ["sync" ]["synced" ] or not self .wallet_synced :
436
448
self .log .warning ("Waiting for wallet sync" )
437
449
await asyncio .sleep (60 )
438
450
continue
439
451
440
- payment_targets = await self .pending_payments .get ()
441
- assert len (payment_targets ) > 0
452
+ pending_payments = await self .store .get_pending_payment_records (self .max_additions_per_transaction )
453
+ if len (pending_payments ) == 0 :
454
+ self .log .info ("No funds to pending payment records" )
455
+ await asyncio .sleep (60 )
456
+ continue
457
+ self .log .info (f"Submitting a payment: { pending_payments } " )
458
+
459
+ payment_targets : List [Dict ] = []
460
+ payment_records : List [Tuple [bytes32 , uint64 ]] = []
442
461
443
- self .log .info (f"Submitting a payment: { payment_targets } " )
462
+ for launcher_id , puzzle_hash , amount , _ , timestamp , _ , _ , _ in pending_payments :
463
+ payment_targets .append ({"puzzle_hash" : puzzle_hash , "amount" : amount })
464
+ payment_records .append ((launcher_id , timestamp ))
444
465
445
466
# TODO(pool): make sure you have enough to pay the blockchain fee, this will be taken out of the pool
446
467
# fee itself. Alternatively you can set it to 0 and wait longer
447
468
# blockchain_fee = 0.00001 * (10 ** 12) * len(payment_targets)
448
469
blockchain_fee : uint64 = uint64 (0 )
449
470
try :
450
- transaction : TransactionRecord = await self .wallet_rpc_client .send_transaction_multi (
451
- self .wallet_id , payment_targets , fee = blockchain_fee
452
- )
471
+ async with self .store .tx ():
472
+ await self .store .update_is_payment (payment_records , auto_commit = False )
473
+
474
+ transaction : TransactionRecord = await self .wallet_rpc_client .send_transaction_multi (
475
+ self .wallet_id , payment_targets , fee = blockchain_fee
476
+ )
453
477
except ValueError as e :
454
478
self .log .error (f"Error making payment: { e } " )
455
479
await asyncio .sleep (10 )
456
- await self .pending_payments .put (payment_targets )
457
480
continue
458
481
482
+ await self .store .update_transaction_id (payment_records , transaction_id = transaction .name )
459
483
self .log .info (f"Transaction: { transaction } " )
460
484
461
- while (
462
- not transaction .confirmed
463
- or not (peak_height - transaction .confirmed_at_height ) > self .confirmation_security_threshold
464
- ):
465
- transaction = await self .wallet_rpc_client .get_transaction (self .wallet_id , transaction .name )
485
+ except asyncio .CancelledError :
486
+ self .log .info ("Cancelled submit_payment_loop, closing" )
487
+ return
488
+ except Exception as e :
489
+ self .log .error (f"Unexpected error in submit_payment_loop: { e } " )
490
+ await asyncio .sleep (60 )
491
+
492
+ async def confirm_payment_loop (self ):
493
+ while True :
494
+ try :
495
+ confirming_payments = await self .store .get_confirming_payment_records ()
496
+ if len (confirming_payments ) == 0 :
497
+ self .log .info ("No funds to confirming payment records" )
498
+ await asyncio .sleep (60 )
499
+ continue
500
+ self .log .info (f"Confirming a payment: { confirming_payments } " )
501
+
502
+ for transaction_id in confirming_payments :
503
+ transaction = await self .wallet_rpc_client .get_transaction (self .wallet_id , transaction_id )
466
504
peak_height = self .blockchain_state ["peak" ].height
467
- self .log .info (
468
- f"Waiting for transaction to obtain { self .confirmation_security_threshold } confirmations"
469
- )
470
- if not transaction .confirmed :
471
- self .log .info (f"Not confirmed. In mempool? { transaction .is_in_mempool ()} " )
472
- else :
473
- self .log .info (f"Confirmations: { peak_height - transaction .confirmed_at_height } " )
474
- await asyncio .sleep (10 )
475
505
476
- # TODO(pool): persist in DB
477
- self .log .info (f"Successfully confirmed payments { payment_targets } " )
506
+ while (
507
+ not transaction .confirmed
508
+ or not (peak_height - transaction .confirmed_at_height ) > self .confirmation_security_threshold
509
+ ):
510
+ transaction = await self .wallet_rpc_client .get_transaction (self .wallet_id , transaction .name )
511
+ peak_height = self .blockchain_state ["peak" ].height
512
+ self .log .info (
513
+ f"Waiting for transaction to obtain { self .confirmation_security_threshold } confirmations"
514
+ )
515
+ if not transaction .confirmed :
516
+ self .log .info (f"Not confirmed. In mempool? { transaction .is_in_mempool ()} " )
517
+ else :
518
+ self .log .info (f"Confirmations: { peak_height - transaction .confirmed_at_height } " )
519
+ await asyncio .sleep (10 )
520
+
521
+ await self .store .update_is_confirmed (transaction_id )
522
+ self .log .info (f"Successfully confirmed payment { transaction_id } " )
478
523
479
524
except asyncio .CancelledError :
480
- self .log .info ("Cancelled submit_payment_loop , closing" )
525
+ self .log .info ("Cancelled confirm_payment_loop , closing" )
481
526
return
482
527
except Exception as e :
483
- # TODO(pool): retry transaction if failed
484
- self .log .error (f"Unexpected error in submit_payment_loop: { e } " )
528
+ self .log .error (f"Unexpected error in confirm_payment_loop: { e } " )
485
529
await asyncio .sleep (60 )
486
530
487
531
async def confirm_partials_loop (self ):
0 commit comments