Skip to content

Commit 11ebd9e

Browse files
committed
Refresh rainbymagnitude
This fixes the magnitude rounding to agree with the same rounding method used in magnitude storage in the superblock. It also implements a dust suppressor by suppressing payments less than one CENT and renormalizing the remaining payments. It removes the ability to provide a custom message, because of prior abuse, and it also implements a minimum requirement of 1000 GRC.
1 parent 006aa49 commit 11ebd9e

File tree

2 files changed

+145
-53
lines changed

2 files changed

+145
-53
lines changed

src/rpc/blockchain.cpp

+143-53
Original file line numberDiff line numberDiff line change
@@ -545,31 +545,50 @@ UniValue backupprivatekeys(const UniValue& params, bool fHelp)
545545

546546
UniValue rainbymagnitude(const UniValue& params, bool fHelp)
547547
{
548-
if (fHelp || (params.size() < 2 || params.size() > 3))
548+
if (fHelp || (params.size() < 2 || params.size() > 4))
549549
throw runtime_error(
550-
"rainbymagnitude <whitelisted project> <amount> [message]\n"
550+
"rainbymagnitude project_id amount ( trial_run output_details )\n"
551551
"\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"
555560
"\n"
556561
"rain coins by magnitude on network");
557562
558563
UniValue res(UniValue::VOBJ);
564+
UniValue details(UniValue::VARR);
559565
560566
std::string sProject = params[0].get_str();
561567
562568
LogPrint(BCLog::LogFlags::VERBOSE, "rainbymagnitude: sProject = %s", sProject.c_str());
563569
564-
double dAmount = params[1].get_real();
570+
CAmount amount = AmountFromValue(params[1]);
565571
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+
}
568576
569-
std::string sMessage = "";
577+
std::string sMessage;
578+
579+
bool trial_run = false;
570580
571581
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+
}
573592
574593
// Make sure statistics are up to date. This will do nothing if a convergence has already been cached and is clean.
575594
bool bStatsAvail = ScraperGetSuperblockContract(false, false).WellFormed();
@@ -589,17 +608,17 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
589608
590609
LogPrint(BCLog::LogFlags::VERBOSE, "rainbymagnitude: mScraperConvergedStats size = %u", mScraperConvergedStats.size());
591610
592-
double dTotalAmount = 0;
593-
int64_t nTotalAmount = 0;
611+
CAmount total_amount_1st_pass = 0;
612+
CAmount total_amount_2nd_pass = 0;
594613
595614
double dTotalMagnitude = 0;
596615
597616
statsobjecttype rainbymagmode = (sProject == "*" ? statsobjecttype::byCPID : statsobjecttype::byCPIDbyProject);
598617
599618
const int64_t now = GetAdjustedTime(); // Time to calculate beacon expiration from
600619
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;
603622
604623
for (const auto& entry : mScraperConvergedStats)
605624
{
@@ -629,6 +648,8 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
629648
630649
CBitcoinAddress address;
631650
651+
// If the beacon is active get the address and insert an entry into the map for payment,
652+
// otherwise skip.
632653
if (const GRC::BeaconOption beacon = GRC::GetBeaconRegistry().TryActive(CPIDKey, now))
633654
{
634655
address = beacon->GetAddress();
@@ -638,60 +659,116 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
638659
continue;
639660
}
640661
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);
644664
645665
// Increment the accumulated mag. This will be equal to the total mag of the valid CPIDs entered
646666
// into the RAIN map, and will be used to normalize the payments.
647667
dTotalMagnitude += dCPIDMag;
648-
649-
LogPrint(BCLog::LogFlags::VERBOSE, "rainmagnitude: CPID = %s, address = %s, dCPIDMag = %f",
650-
CPIDKey.ToString(), address.ToString(), dCPIDMag);
651668
}
652669
}
653670
654671
if (mCPIDRain.empty() || !dTotalMagnitude)
655672
{
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.");
657675
}
658676
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);
660685
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)
663703
{
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);
665708
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);
667712
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);
669717
670-
CScript scriptPubKey;
671-
scriptPubKey.SetDestination(iter.second.first.Get());
718+
CScript scriptPubKey;
719+
scriptPubKey.SetDestination(address.Get());
672720
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);
675724
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);
677734
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.");
679749
}
680750
681751
LOCK2(cs_main, pwalletMain->cs_wallet);
682752
683753
CWalletTx wtx;
684754
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.
685758
wtx.vContracts.emplace_back(GRC::MakeContract<GRC::TxMessage>(
686759
GRC::ContractAction::ADD,
687-
"Rain By Magnitude: " + sMessage));
760+
"Rain By Magnitude"));
688761
689762
EnsureWalletIsUnlocked();
763+
690764
// 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+
}
692771
693-
if (dTotalAmount > dBalance)
694-
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
695772
// Send
696773
CReserveKey keyChange(pwalletMain);
697774
@@ -702,28 +779,41 @@ UniValue rainbymagnitude(const UniValue& params, bool fHelp)
702779
703780
if (!fCreated)
704781
{
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+
}
707786
708787
throw JSONRPCError(RPC_WALLET_ERROR, "Transaction creation failed");
709788
}
710789
711-
LogPrintf("Committing.");
712-
// Rain the recipients
713-
if (!pwalletMain->CommitTransaction(wtx, keyChange))
790+
if (!trial_run)
714791
{
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__);
716796
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+
}
727817
728818
return res;
729819
}

src/rpc/client.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
148148
{ "move" , 2 },
149149
{ "move" , 3 },
150150
{ "rainbymagnitude" , 1 },
151+
{ "rainbymagnitude" , 2 },
152+
{ "rainbymagnitude" , 3 },
151153
{ "reservebalance" , 0 },
152154
{ "reservebalance" , 1 },
153155
{ "scanforunspent" , 1 },

0 commit comments

Comments
 (0)