@@ -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
@@ -221,6 +220,23 @@ async def stop(self):
221
220
await self .node_rpc_client .await_closed ()
222
221
await self .store .connection .close ()
223
222
223
+ async def confirm_transaction (self , transaction ):
224
+ peak_height = self .blockchain_state ["peak" ].height
225
+ while (
226
+ not transaction .confirmed
227
+ or not (peak_height - transaction .confirmed_at_height ) > self .confirmation_security_threshold
228
+ ):
229
+ transaction = await self .wallet_rpc_client .get_transaction (self .wallet_id , transaction .name )
230
+ peak_height = self .blockchain_state ["peak" ].height
231
+ self .log .info (
232
+ f"Waiting for transaction to obtain { self .confirmation_security_threshold } confirmations"
233
+ )
234
+ if not transaction .confirmed :
235
+ self .log .info (f"Not confirmed. In mempool? { transaction .is_in_mempool ()} " )
236
+ else :
237
+ self .log .info (f"Confirmations: { peak_height - transaction .confirmed_at_height } " )
238
+ await asyncio .sleep (10 )
239
+
224
240
async def get_peak_loop (self ):
225
241
"""
226
242
Periodically contacts the full node to get the latest state of the blockchain
@@ -356,8 +372,8 @@ async def create_payment_loop(self):
356
372
await asyncio .sleep (60 )
357
373
continue
358
374
359
- if self .pending_payments . qsize ( ) != 0 :
360
- self .log .warning (f"Pending payments ({ self . pending_payments . qsize () } ), waiting" )
375
+ if ( pending_payment_count := await self .store . get_pending_payment_count () ) != 0 :
376
+ self .log .warning (f"Pending payments ({ pending_payment_count } ), waiting" )
361
377
await asyncio .sleep (60 )
362
378
continue
363
379
@@ -374,7 +390,7 @@ async def create_payment_loop(self):
374
390
await asyncio .sleep (120 )
375
391
continue
376
392
377
- total_amount_claimed = sum ([ c .coin .amount for c in coin_records ] )
393
+ total_amount_claimed = sum (c .coin .amount for c in coin_records )
378
394
pool_coin_amount = int (total_amount_claimed * self .pool_fee )
379
395
amount_to_distribute = total_amount_claimed - pool_coin_amount
380
396
@@ -387,34 +403,50 @@ async def create_payment_loop(self):
387
403
self .log .info (f"Total amount to distribute: { amount_to_distribute / (10 ** 12 )} " )
388
404
389
405
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 ])
406
+ # Get the launcher_id and points of each farmer, as well as payout instructions.
407
+ # Here a chia address is used, but other blockchain addresses can also be used.
408
+ launcher_id_and_points_and_ph : List [
409
+ Tuple [bytes32 , uint64 , bytes32 ]
410
+ ] = await self .store .get_farmer_launcher_id_and_points_and_payout_instructions ()
411
+ total_points = sum ([pt for (launcher_id , pt , ph ) in launcher_id_and_points_and_ph ])
396
412
if total_points > 0 :
397
413
mojo_per_point = floor (amount_to_distribute / total_points )
398
414
self .log .info (f"Paying out { mojo_per_point } mojo / point" )
399
415
416
+ # Pool fee payment record launcher_id is equal to puzzle_hash, points is 0.
400
417
additions_sub_list : List [Dict ] = [
401
- {"puzzle_hash" : self .pool_fee_puzzle_hash , "amount" : pool_coin_amount }
418
+ {
419
+ "launcher_id" : self .pool_fee_puzzle_hash ,
420
+ "puzzle_hash" : self .pool_fee_puzzle_hash ,
421
+ "amount" : pool_coin_amount ,
422
+ "points" : 0 ,
423
+ }
402
424
]
403
- for points , ph in points_and_ph :
425
+ for launcher_id , points , ph in launcher_id_and_points_and_ph :
404
426
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 = []
411
-
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 ())
415
-
416
- # Subtract the points from each farmer
417
- await self .store .clear_farmer_points ()
427
+ additions_sub_list .append ({
428
+ "launcher_id" : launcher_id ,
429
+ "puzzle_hash" : ph ,
430
+ "amount" : points * mojo_per_point ,
431
+ "points" : points ,
432
+ })
433
+
434
+ async with self .store .tx ():
435
+ for payment in additions_sub_list :
436
+ await self .store .add_payment (
437
+ payment ["launcher_id" ],
438
+ payment ["puzzle_hash" ],
439
+ uint64 (payment ["amount" ]),
440
+ payment ["points" ],
441
+ uint64 (int (time .time ())),
442
+ False ,
443
+ auto_commit = False ,
444
+ )
445
+
446
+ # Subtract the points from each farmer
447
+ await self .store .clear_farmer_points (auto_commit = False )
448
+
449
+ self .log .info (f"Will make payments: { additions_sub_list } " )
418
450
else :
419
451
self .log .info (f"No points for any farmer. Waiting { self .payment_interval } " )
420
452
@@ -430,60 +462,77 @@ async def create_payment_loop(self):
430
462
async def submit_payment_loop (self ):
431
463
while True :
432
464
try :
433
- peak_height = self .blockchain_state ["peak" ].height
434
465
await self .wallet_rpc_client .log_in_and_skip (fingerprint = self .wallet_fingerprint )
435
466
if not self .blockchain_state ["sync" ]["synced" ] or not self .wallet_synced :
436
467
self .log .warning ("Waiting for wallet sync" )
437
468
await asyncio .sleep (60 )
438
469
continue
439
470
440
- payment_targets = await self .pending_payments .get ()
441
- assert len (payment_targets ) > 0
471
+ pending_payments = await self .store .get_pending_payment_records (self .max_additions_per_transaction )
472
+ if len (pending_payments ) == 0 :
473
+ self .log .info ("No funds to pending payment records" )
474
+ await asyncio .sleep (60 )
475
+ continue
476
+ self .log .info (f"Submitting a payment: { pending_payments } " )
442
477
443
- self .log .info (f"Submitting a payment: { payment_targets } " )
478
+ payment_targets : List [Dict ] = []
479
+ payment_records : List [Tuple [bytes32 , uint64 ]] = []
480
+
481
+ for launcher_id , puzzle_hash , amount , _ , timestamp , _ , _ in pending_payments :
482
+ payment_targets .append ({"puzzle_hash" : puzzle_hash , "amount" : amount })
483
+ payment_records .append ((launcher_id , timestamp ))
444
484
445
485
# TODO(pool): make sure you have enough to pay the blockchain fee, this will be taken out of the pool
446
486
# fee itself. Alternatively you can set it to 0 and wait longer
447
487
# blockchain_fee = 0.00001 * (10 ** 12) * len(payment_targets)
448
488
blockchain_fee : uint64 = uint64 (0 )
449
489
try :
450
- transaction : TransactionRecord = await self .wallet_rpc_client .send_transaction_multi (
451
- self .wallet_id , payment_targets , fee = blockchain_fee
452
- )
490
+ async with self .store .tx ():
491
+ await self .store .update_is_payment (payment_records , auto_commit = False )
492
+
493
+ transaction : TransactionRecord = await self .wallet_rpc_client .send_transaction_multi (
494
+ self .wallet_id , payment_targets , fee = blockchain_fee
495
+ )
453
496
except ValueError as e :
454
497
self .log .error (f"Error making payment: { e } " )
455
498
await asyncio .sleep (10 )
456
- await self .pending_payments .put (payment_targets )
457
499
continue
458
500
501
+ await self .store .update_transaction_id (payment_records , transaction_id = transaction .name )
459
502
self .log .info (f"Transaction: { transaction } " )
460
503
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 )
466
- 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
-
476
- # TODO(pool): persist in DB
477
- self .log .info (f"Successfully confirmed payments { payment_targets } " )
478
-
504
+ await self .confirm_transaction (transaction )
479
505
except asyncio .CancelledError :
480
506
self .log .info ("Cancelled submit_payment_loop, closing" )
481
507
return
482
508
except Exception as e :
483
- # TODO(pool): retry transaction if failed
484
509
self .log .error (f"Unexpected error in submit_payment_loop: { e } " )
485
510
await asyncio .sleep (60 )
486
511
512
+ async def confirm_payment_loop (self ):
513
+ while True :
514
+ try :
515
+ confirming_payments = await self .store .get_confirming_payment_records ()
516
+ if len (confirming_payments ) == 0 :
517
+ self .log .info ("No funds to confirming payment records" )
518
+ await asyncio .sleep (60 )
519
+ continue
520
+ self .log .info (f"Confirming a payment: { confirming_payments } " )
521
+
522
+ for transaction_id in confirming_payments :
523
+ transaction = await self .wallet_rpc_client .get_transaction (self .wallet_id , transaction_id )
524
+ await self .confirm_transaction (transaction )
525
+
526
+ await self .store .update_is_confirmed (transaction_id )
527
+ self .log .info (f"Successfully confirmed payment { transaction_id } " )
528
+
529
+ except asyncio .CancelledError :
530
+ self .log .info ("Cancelled confirm_payment_loop, closing" )
531
+ return
532
+ except Exception as e :
533
+ self .log .error (f"Unexpected error in confirm_payment_loop: { e } " )
534
+ await asyncio .sleep (60 )
535
+
487
536
async def confirm_partials_loop (self ):
488
537
"""
489
538
Pulls things from the queue of partials one at a time, and adjusts balances.
0 commit comments