@@ -545,31 +545,50 @@ UniValue backupprivatekeys(const UniValue& params, bool fHelp)
545
545
546
546
UniValue rainbymagnitude (const UniValue& params, bool fHelp )
547
547
{
548
- if (fHelp || (params.size () < 2 || params.size () > 3 ))
548
+ if (fHelp || (params.size () < 2 || params.size () > 4 ))
549
549
throw runtime_error (
550
- " rainbymagnitude <whitelisted project> < amount> [message] \n "
550
+ " rainbymagnitude project_id amount ( trial_run output_details ) \n "
551
551
" \n "
552
- " <whitelisted project> --> Required: If a project is specified, rain will be limited to that project. Use * for network-wide.\n "
553
- " <amount> --> Required: Specify amount of coins to be rained in double precision float\n "
554
- " [message] -> Optional: Provide a message rained to all rainees\n "
552
+ " project_id -> Required: Limits rain to a specific project. Use \" *\" for\n "
553
+ " network-wide. Call \" listprojects\" for the IDs of eligible\n "
554
+ " projects.
555
+ " amount -> Required: Amount to rain (1000 GRC minimum).\n"
556
+ " trial_run -> Optional: Boolean to specify a trial run instead of an actual\n"
557
+ " transaction (default : false ).\n"
558
+ " output_details -> Optional: Boolean to output recipient details (default : false \n"
559
+ " if not trial run, true if trial run).\n"
555
560
" \n"
556
561
" rain coins by magnitude on network" );
557
562
558
563
UniValue res(UniValue::VOBJ);
564
+ UniValue details(UniValue::VARR);
559
565
560
566
std::string sProject = params[0].get_str();
561
567
562
568
LogPrint(BCLog::LogFlags::VERBOSE, " rainbymagnitude: sProject = %s" , sProject.c_str());
563
569
564
- double dAmount = params[1 ]. get_real ( );
570
+ CAmount amount = AmountFromValue( params[1]);
565
571
566
- if (dAmount <= 0 )
567
- throw runtime_error (" Amount must be greater then 0" );
572
+ if (amount < 1000 * COIN)
573
+ {
574
+ throw runtime_error(" Minimum amount to rain is 1000 GRC." );
575
+ }
568
576
569
- std::string sMessage = " " ;
577
+ std::string sMessage;
578
+
579
+ bool trial_run = false;
570
580
571
581
if (params.size() > 2)
572
- sMessage = params[2 ].get_str ();
582
+ {
583
+ trial_run = params[2].get_bool();
584
+ }
585
+
586
+ bool output_details = trial_run;
587
+
588
+ if (params.size() > 3)
589
+ {
590
+ output_details = params[3].get_bool();
591
+ }
573
592
574
593
// Make sure statistics are up to date. This will do nothing if a convergence has already been cached and is clean.
575
594
bool bStatsAvail = ScraperGetSuperblockContract(false, false).WellFormed();
@@ -589,17 +608,17 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
589
608
590
609
LogPrint(BCLog::LogFlags::VERBOSE, " rainbymagnitude: mScraperConvergedStats size = %u" , mScraperConvergedStats.size());
591
610
592
- double dTotalAmount = 0 ;
593
- int64_t nTotalAmount = 0 ;
611
+ CAmount total_amount_1st_pass = 0;
612
+ CAmount total_amount_2nd_pass = 0;
594
613
595
614
double dTotalMagnitude = 0;
596
615
597
616
statsobjecttype rainbymagmode = (sProject == " *" ? statsobjecttype::byCPID : statsobjecttype::byCPIDbyProject);
598
617
599
618
const int64_t now = GetAdjustedTime(); // Time to calculate beacon expiration from
600
619
601
- // ------- CPID ------------- beacon address -- Mag
602
- std::map<GRC::Cpid, std::pair <CBitcoinAddress, double >> mCPIDRain ;
620
+ //------- CPID -------------- beacon address -- Mag --- payment - suppressed
621
+ std::map<GRC::Cpid, std::tuple <CBitcoinAddress, double, CAmount, bool >> mCPIDRain;
603
622
604
623
for (const auto& entry : mScraperConvergedStats)
605
624
{
@@ -629,6 +648,8 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
629
648
630
649
CBitcoinAddress address;
631
650
651
+ // If the beacon is active get the address and insert an entry into the map for payment,
652
+ // otherwise skip.
632
653
if (const GRC::BeaconOption beacon = GRC::GetBeaconRegistry().TryActive(CPIDKey, now))
633
654
{
634
655
address = beacon->GetAddress();
@@ -638,60 +659,116 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
638
659
continue;
639
660
}
640
661
641
- LogPrint (BCLog::LogFlags::VERBOSE, " INFO: rainbymagnitude: address = %s." , address.ToString ());
642
-
643
- mCPIDRain [CPIDKey] = std::make_pair (address, dCPIDMag);
662
+ // The last two elements of the tuple will be filled out when doing the passes for payment.
663
+ mCPIDRain[CPIDKey] = std::make_tuple(address, dCPIDMag, CAmount {0}, false);
644
664
645
665
// Increment the accumulated mag. This will be equal to the total mag of the valid CPIDs entered
646
666
// into the RAIN map, and will be used to normalize the payments.
647
667
dTotalMagnitude += dCPIDMag;
648
-
649
- LogPrint (BCLog::LogFlags::VERBOSE, " rainmagnitude: CPID = %s, address = %s, dCPIDMag = %f" ,
650
- CPIDKey.ToString (), address.ToString (), dCPIDMag);
651
668
}
652
669
}
653
670
654
671
if (mCPIDRain.empty() || !dTotalMagnitude)
655
672
{
656
- throw JSONRPCError (RPC_MISC_ERROR, " No CPIDs to pay and/or total CPID magnitude is zero. This could be caused by an incorrect project specified." );
673
+ throw JSONRPCError(RPC_MISC_ERROR, " No CPIDs to pay and /or total CPID magnitude is zero. This could be caused by an "
674
+ " incorrect project specified." );
657
675
}
658
676
659
- std::vector<std::pair<CScript, int64_t > > vecSend;
677
+ std::vector<std::pair<CScript, CAmount> > vecSend;
678
+ unsigned int subcent_suppression_count = 0;
679
+
680
+ for (auto& iter : mCPIDRain)
681
+ {
682
+ double dCPIDMag = std::get<1>(iter.second);
683
+
684
+ CAmount payment = roundint64((double) amount * dCPIDMag / dTotalMagnitude);
660
685
661
- // Setup the payment vector now that the CPID entries and mags have been validated and the total mag is computed.
662
- for (const auto & iter : mCPIDRain )
686
+ std::get<2>(iter.second) = payment;
687
+
688
+ // Do not allow payments less than one cent to prevent dust in the network.
689
+ if (payment < CENT)
690
+ {
691
+ std::get<3>(iter.second) = true;
692
+
693
+ ++subcent_suppression_count;
694
+ continue;
695
+ }
696
+
697
+ total_amount_1st_pass += payment;
698
+ }
699
+
700
+ // Because we are suppressing payments of less than one cent to CPIDs, we need to renormalize the payments to ensure
701
+ // the full amount is disbursed to the surviving CPID payees. This is going to be a small amount, but is worth doing.
702
+ for (const auto& iter : mCPIDRain)
663
703
{
664
- double dCPIDMag = iter.second .second ;
704
+ // Make it easier to read.
705
+ const GRC::Cpid& cpid = iter.first;
706
+ const CBitcoinAddress& address = std::get<0>(iter.second);
707
+ const double& magnitude = std::get<1>(iter.second);
665
708
666
- double dPayout = (dCPIDMag / dTotalMagnitude) * dAmount;
709
+ // This is not a const reference on purpose because it has to be renormalized.
710
+ CAmount payment = std::get<2>(iter.second);
711
+ const bool& suppressed = std::get<3>(iter.second);
667
712
668
- dTotalAmount += dPayout;
713
+ // Note the cast to double ensures that the calculations are reasonable over a wide dynamic range, without risk of
714
+ // overflow on a large iter.second and amount without using bignum math. The slight loss of precision here is not
715
+ // important. As above, with the renormalization here, we will round to the nearest Halford rather than truncating.
716
+ payment = roundint64((double) payment * (double) amount / (double) total_amount_1st_pass);
669
717
670
- CScript scriptPubKey;
671
- scriptPubKey.SetDestination (iter. second . first .Get ());
718
+ CScript scriptPubKey;
719
+ scriptPubKey.SetDestination(address .Get());
672
720
673
- int64_t nAmount = roundint64 (dPayout * COIN);
674
- nTotalAmount += nAmount;
721
+ LogPrint(BCLog::LogFlags::VERBOSE, " INFO: %s: cpid = %s. address = %s, magnitude = %f, "
722
+ " payment = %s, dust_suppressed = %u" ,
723
+ __func__, cpid.ToString(), address.ToString(), magnitude, (double) payment / COIN, suppressed);
675
724
676
- vecSend.push_back (std::make_pair (scriptPubKey, nAmount));
725
+ if (output_details)
726
+ {
727
+ UniValue detail_entry(UniValue::VOBJ);
728
+
729
+ detail_entry.pushKV(" cpid" , cpid.ToString());
730
+ detail_entry.pushKV(" address" , address.ToString());
731
+ detail_entry.pushKV(" magnitude" , magnitude);
732
+ detail_entry.pushKV(" amount" , ValueFromAmount(payment));
733
+ detail_entry.pushKV(" suppressed" , suppressed);
677
734
678
- LogPrint (BCLog::LogFlags::VERBOSE, " rainmagnitude: address = %s, amount = %f" , iter.second .first .ToString (), CoinToDouble (nAmount));
735
+ details.push_back(detail_entry);
736
+ }
737
+
738
+ // If dust suppression flag is false, add to payment vector for sending.
739
+ if (!suppressed)
740
+ {
741
+ vecSend.push_back(std::make_pair(scriptPubKey, payment));
742
+ total_amount_2nd_pass += payment;
743
+ }
744
+ }
745
+
746
+ if (total_amount_2nd_pass <= 0)
747
+ {
748
+ throw JSONRPCError(RPC_MISC_ERROR, " No payments above 0.01 GRC qualify. Please recheck your specified amount." );
679
749
}
680
750
681
751
LOCK2(cs_main, pwalletMain->cs_wallet);
682
752
683
753
CWalletTx wtx;
684
754
wtx.mapValue[" comment" ] = " Rain By Magnitude" ;
755
+
756
+ // Custom messages are no longer supported. The static " rain by magnitude" message will be replaced by an actual
757
+ // rain contract at the next mandatory.
685
758
wtx.vContracts.emplace_back(GRC::MakeContract<GRC::TxMessage>(
686
759
GRC::ContractAction::ADD,
687
- " Rain By Magnitude: " + sMessage ));
760
+ " Rain By Magnitude" ));
688
761
689
762
EnsureWalletIsUnlocked();
763
+
690
764
// Check funds
691
- double dBalance = pwalletMain->GetBalance ();
765
+ CAmount balance = pwalletMain->GetBalance();
766
+
767
+ if (total_amount_2nd_pass > balance)
768
+ {
769
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, " Wallet has insufficient funds for specified rain." );
770
+ }
692
771
693
- if (dTotalAmount > dBalance)
694
- throw JSONRPCError (RPC_WALLET_INSUFFICIENT_FUNDS, " Account has insufficient funds" );
695
772
// Send
696
773
CReserveKey keyChange(pwalletMain);
697
774
@@ -702,28 +779,41 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
702
779
703
780
if (!fCreated)
704
781
{
705
- if (nTotalAmount + nFeeRequired > pwalletMain->GetBalance ())
706
- throw JSONRPCError (RPC_WALLET_INSUFFICIENT_FUNDS, " Insufficient funds" );
782
+ if (total_amount_2nd_pass + nFeeRequired > balance)
783
+ {
784
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, " Wallet has insufficient funds for specified rain." );
785
+ }
707
786
708
787
throw JSONRPCError(RPC_WALLET_ERROR, " Transaction creation failed" );
709
788
}
710
789
711
- LogPrintf (" Committing." );
712
- // Rain the recipients
713
- if (!pwalletMain->CommitTransaction (wtx, keyChange))
790
+ if (!trial_run)
714
791
{
715
- LogPrintf (" Rain By Magnitude Commit failed." );
792
+ // Rain the recipients
793
+ if (!pwalletMain->CommitTransaction(wtx, keyChange))
794
+ {
795
+ error(" %s: Rain by magnitude transaction commit failed." , __func__);
716
796
717
- throw JSONRPCError (RPC_WALLET_ERROR, " Transaction commit failed" );
718
- }
719
- res.pushKV (" Rain By Magnitude" , " Sent" );
720
- res.pushKV (" TXID" , wtx.GetHash ().GetHex ());
721
- res.pushKV (" Rain Amount Sent" , dTotalAmount);
722
- res.pushKV (" TX Fee" , ValueFromAmount (nFeeRequired));
723
- res.pushKV (" # of Recipients" , (uint64_t )vecSend.size ());
724
-
725
- if (!sMessage .empty ())
726
- res.pushKV (" Message" , sMessage );
797
+ throw JSONRPCError(RPC_WALLET_ERROR, " Rain by magnitude transaction commit failed." );
798
+ }
799
+
800
+ res.pushKV(" status" , " transaction sent" );
801
+ res.pushKV(" txid" , wtx.GetHash().GetHex());
802
+ }
803
+ else
804
+ {
805
+ res.pushKV(" status" , " trial run - nothing sent" );
806
+ }
807
+
808
+ res.pushKV(" amount" , ValueFromAmount(total_amount_2nd_pass));
809
+ res.pushKV(" fee" , ValueFromAmount(nFeeRequired));
810
+ res.pushKV(" recipients" , (uint64_t) vecSend.size());
811
+ res.pushKV(" suppressed_subcent_recipients" , (uint64_t) subcent_suppression_count);
812
+
813
+ if (output_details)
814
+ {
815
+ res.pushKV(" recipient_details" , details);
816
+ }
727
817
728
818
return res;
729
819
}
0 commit comments