diff --git a/configure.ac b/configure.ac index 7a0f72be6..34d62956f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) -define(_CLIENT_VERSION_MINOR, 6) -define(_CLIENT_VERSION_REVISION, 5) +define(_CLIENT_VERSION_MINOR, 7) +define(_CLIENT_VERSION_REVISION, 0) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2018) diff --git a/src/Makefile.am b/src/Makefile.am index 05d645819..c80557450 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -137,6 +137,9 @@ MERIT_CORE_H = \ pog2/cgs.h \ pog2/select.h \ pog2/reward.h \ + pog3/cgs.h \ + pog3/select.h \ + pog3/reward.h \ protocol.h \ random.h \ refdb.h \ @@ -234,6 +237,9 @@ libmerit_server_a_SOURCES = \ pog2/cgs.cpp \ pog2/reward.cpp \ pog2/select.cpp \ + pog3/cgs.cpp \ + pog3/reward.cpp \ + pog3/select.cpp \ policy/fees.cpp \ policy/policy.cpp \ policy/rbf.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 002afca41..12d3bf329 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -193,6 +193,18 @@ class CMainParams : public CChainParams consensus.pog2_convex_b = 0.2; consensus.pog2_convex_s = 0.05; + // PoG v3 settings. + consensus.pog3_blockheight = 441700; //October 31th 2018 + consensus.pog3_total_winning_ambassadors = 20; + consensus.pog3_ambassador_percent_cut = 75; //75% + consensus.pog3_pow_target_timespan = 5 * 60 * 60; // every 5 hours + consensus.pog3_initial_ambassador_stake = 20_merit; + consensus.pog3_coin_maturity = blocks_per_minute * 60 * 24 * 30; //Month for coin maturity + consensus.pog3_new_coin_maturity = blocks_per_minute * 60 * 24 * 30; //Month for coin maturity + consensus.pog3_max_outstanding_invites_per_address = 20; + consensus.pog3_convex_b = 0.2; + consensus.pog3_convex_s = 0.05; + // Fix invite lottery distribution amount. consensus.imp_fix_invites_blockheight = 348400; @@ -358,6 +370,18 @@ class CTestNetParams : public CChainParams consensus.pog2_convex_b = 0.2; consensus.pog2_convex_s = 0.05; + // PoG v3 settings. + consensus.pog3_blockheight = 291500; //October 20th 2018 + consensus.pog3_total_winning_ambassadors = 20; + consensus.pog3_ambassador_percent_cut = 75; //75% + consensus.pog3_pow_target_timespan = 5 * 60 * 60; // every 5 hours + consensus.pog3_initial_ambassador_stake = 20_merit; + consensus.pog3_coin_maturity = blocks_per_minute * 60 * 24 * 30; //Month for coin maturity + consensus.pog3_new_coin_maturity = blocks_per_minute * 60 * 24 * 30; //Month for coin maturity + consensus.pog3_max_outstanding_invites_per_address = 20; + consensus.pog3_convex_b = 0.2; + consensus.pog3_convex_s = 0.05; + // Fix invite lottery distribution amount. consensus.imp_fix_invites_blockheight = 217450; @@ -502,6 +526,18 @@ class CRegTestParams : public CChainParams consensus.pog2_convex_b = 0.2; consensus.pog2_convex_s = 0.05; + // PoG v3 settings. + consensus.pog3_blockheight = 12; + consensus.pog3_total_winning_ambassadors = 20; + consensus.pog3_ambassador_percent_cut = 75; //75% + consensus.pog3_pow_target_timespan = 30 * 60; // every 30 min + consensus.pog3_initial_ambassador_stake = 20_merit; + consensus.pog3_coin_maturity = blocks_per_minute * 20; //20 minutes for coin maturity + consensus.pog3_new_coin_maturity = blocks_per_minute * 10; //10 minutes for coin maturity + consensus.pog3_max_outstanding_invites_per_address = 20; + consensus.pog3_convex_b = 0.2; + consensus.pog3_convex_s = 0.05; + consensus.imp_fix_invites_blockheight = 20; pchMessageStart[0] = 0xfa; diff --git a/src/consensus/params.h b/src/consensus/params.h index 3841b9469..81f2ed13d 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -118,6 +118,18 @@ struct Params { double pog2_convex_b; double pog2_convex_s; + /** PoG version 3 */ + int pog3_blockheight; + int64_t pog3_total_winning_ambassadors; + int64_t pog3_ambassador_percent_cut; + int64_t pog3_pow_target_timespan; + CAmount pog3_initial_ambassador_stake; + int pog3_coin_maturity; + int pog3_new_coin_maturity; + int pog3_max_outstanding_invites_per_address; + double pog3_convex_b; + double pog3_convex_s; + }; } // namespace Consensus diff --git a/src/init.cpp b/src/init.cpp index fc48faf5c..25df91311 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1232,11 +1232,6 @@ bool AppInitLockDataDirectory() return true; } -namespace pog2 -{ - extern void SetupCgsThreadPool(size_t threads); -} - bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) { const CChainParams& chainparams = Params(); @@ -1260,7 +1255,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", MERIT_CONF_FILENAME)).string()); LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD); - pog2::SetupCgsThreadPool(boost::thread::hardware_concurrency()); + pog3::SetupCgsThreadPool(boost::thread::hardware_concurrency()); InitSignatureCache(); InitScriptExecutionCache(); diff --git a/src/miner.cpp b/src/miner.cpp index a5f4a2063..847e4b99c 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -245,21 +245,24 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc previousBlockHash, subsidy.ambassador, chain_params); - assert(lottery.first.remainder >= 0); - auto cgs_selector = lottery.second; + const auto& ambassador_lottery = std::get<0>(lottery); + assert(ambassador_lottery.remainder >= 0); + + auto pog2_cgs_selector = std::get<1>(lottery); + auto pog3_cgs_selector = std::get<2>(lottery); /** * Update the coinbase transaction vout with rewards. */ - PayAmbassadors(lottery.first, coinbaseTx); + PayAmbassadors(ambassador_lottery, coinbaseTx); /** * The miner recieves their subsidy and any remaining subsidy that was left * over from paying the ambassadors. The reason there is a remaining subsidy * is because we use integer math. */ - const auto miner_subsidy = subsidy.miner + lottery.first.remainder; + const auto miner_subsidy = subsidy.miner + ambassador_lottery.remainder; assert(miner_subsidy > 0); coinbaseTx.vout[0].nValue = nFees + miner_subsidy; @@ -303,7 +306,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc referral::ConfirmedAddresses dummy_selected_new_pool_addresses; RewardInvites( - cgs_selector, + pog2_cgs_selector, + pog3_cgs_selector, nHeight, pindexPrev, previousBlockHash, diff --git a/src/pog2/cgs.cpp b/src/pog2/cgs.cpp index 68e205844..373ad02cb 100644 --- a/src/pog2/cgs.cpp +++ b/src/pog2/cgs.cpp @@ -23,7 +23,6 @@ namespace pog2 { const size_t BATCH_SIZE = 100; const int NO_GENESIS = 13500; - ctpl::thread_pool cgs_pool; } CAmount GetAmbassadorMinumumStake(int height, const Consensus::Params& consensus_params) @@ -33,11 +32,6 @@ namespace pog2 consensus_params.pog2_initial_ambassador_stake >> halvings : 0; } - void SetupCgsThreadPool(size_t threads) - { - cgs_pool.resize(threads); - } - using UnspentPair = std::pair; using BigInt = boost::multiprecision::cpp_int; @@ -470,11 +464,13 @@ namespace pog2 } void ComputeAges(CGSContext& context) { + assert(context.cgs_pool != nullptr); + std::vector> jobs; jobs.reserve(context.entrants.size() / BATCH_SIZE); for(size_t b = 0; b < context.entrants.size(); b+=BATCH_SIZE) { jobs.push_back( - cgs_pool.push([b, &context](int id) { + context.cgs_pool->push([b, &context](int id) { const auto end = std::min(context.entrants.size(), b + BATCH_SIZE); for(size_t i = b; i < end; i++) { auto& e = context.entrants[i]; @@ -526,12 +522,13 @@ namespace pog2 void ComputeAllContributions( CGSContext& context, referral::ReferralsViewCache& db) { + assert(context.cgs_pool != nullptr); std::vector> jobs; jobs.reserve(context.entrants.size() / BATCH_SIZE); for(size_t b = 0; b < context.entrants.size(); b+=BATCH_SIZE) { jobs.push_back( - cgs_pool.push([b, &context, &db](int id) { + context.cgs_pool->push([b, &context, &db](int id) { const auto end = std::min(context.entrants.size(), b + BATCH_SIZE); for(size_t i = b; i < end; i++) { auto& e = context.entrants[i]; @@ -550,6 +547,7 @@ namespace pog2 const Consensus::Params& params, Entrants& entrants) { + assert(context.cgs_pool != nullptr); const auto minimum_stake = GetAmbassadorMinumumStake(context.tip_height, params); std::vector> jobs; @@ -561,7 +559,7 @@ namespace pog2 //Important, 1 here to skip the genesis address for(size_t b = 1; b < context.entrants.size(); b+=BATCH_SIZE) { jobs.push_back( - cgs_pool.push([b, minimum_stake , &context, &db](int id) { + context.cgs_pool->push([b, minimum_stake , &context, &db](int id) { const auto end = std::min(context.entrants.size(), b + BATCH_SIZE); Entrants es; es.reserve(end - b); diff --git a/src/pog2/cgs.h b/src/pog2/cgs.h index 66dfa2e39..5006cff3f 100644 --- a/src/pog2/cgs.h +++ b/src/pog2/cgs.h @@ -7,6 +7,7 @@ #include "primitives/referral.h" #include "consensus/params.h" +#include "ctpl/ctpl.h" #include "referrals.h" #include "pog/wrs.h" #include "coins.h" @@ -101,6 +102,7 @@ namespace pog2 CachedEntrant& GetEntrant(const referral::Address&); const CachedEntrant& GetEntrant(const referral::Address&) const; + ctpl::thread_pool* cgs_pool = nullptr; }; using Entrants = std::vector; @@ -119,7 +121,6 @@ namespace pog2 referral::ReferralsViewCache& db); void TestChain(); - void SetupCgsThreadPool(size_t threads); CAmount GetAmbassadorMinumumStake(int height, const Consensus::Params& consensus_params); diff --git a/src/pog3/cgs.cpp b/src/pog3/cgs.cpp new file mode 100644 index 000000000..ac2a118ba --- /dev/null +++ b/src/pog3/cgs.cpp @@ -0,0 +1,623 @@ +// Copyright (c) 2018 The Merit Foundation developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "pog3/cgs.h" +#include "addressindex.h" +#include "validation.h" +#include "referrals.h" +#include "sync.h" + +#include +#include +#include +#include + +#include +#include + +namespace pog3 +{ + namespace + { + const size_t BATCH_SIZE = 100; + const int NO_GENESIS = 13500; + ctpl::thread_pool g_cgs_pool; + } + + CAmount GetAmbassadorMinumumStake(int height, const Consensus::Params& consensus_params) + { + const int halvings = height / consensus_params.nSubsidyHalvingInterval; + return halvings < 64 ? + consensus_params.pog3_initial_ambassador_stake >> halvings : 0; + } + + void SetupCgsThreadPool(size_t threads) + { + g_cgs_pool.resize(threads); + } + + ctpl::thread_pool* GetCgsThreadPool() + { + return &g_cgs_pool; + } + + using UnspentPair = std::pair; + + using BigInt = boost::multiprecision::cpp_int; + + double Age(int height, int tip_height, double maturity) + { + assert(tip_height >= 0); + assert(height <= tip_height); + assert(maturity > 0); + + const double maturity_scale = maturity / 4.0; //matures to about 97% at 4 + const double age = (tip_height - height) / maturity_scale; + + assert(age >= 0); + return age; + } + + double AgeScale(int height, int tip_height, double maturity) + { + assert(tip_height >= 0); + assert(height <= tip_height); + assert(maturity > 0); + + const auto age = Age(height, tip_height, maturity); + const auto age_scale = 1.0 - (1.0 / (std::pow(age, 2) + 1.0)); + + assert(age_scale >= 0); + assert(age_scale <= 1.001); + return age_scale; + } + + double AgeScale(const Coin& c, int tip_height, int maturity) { + assert(tip_height >= 0); + assert(c.height <= tip_height); + assert(maturity > 0); + return AgeScale(c.height, tip_height, maturity); + } + + int GetReferralHeight( + referral::ReferralsViewCache& db, + const referral::Address& a + ) + { + auto height = db.GetReferralHeight(a); + if (height < 0) { + const auto beacon = db.GetReferral(a); + assert(beacon); + + uint256 hashBlock; + CBlockIndex* pindex = nullptr; + + referral::ReferralRef beacon_out; + if (GetReferral(beacon->GetHash(), beacon_out, hashBlock, pindex)) { + assert(pindex); + height = pindex->nHeight; + if (height > 0) { + db.SetReferralHeight(height, a); + } + } + } + return height; + } + + Coins GetCoins( + int height, + char address_type, + const referral::Address& address) { + Coins cs; + std::vector unspent; + if (!GetAddressUnspent(address, address_type, false, unspent)) { + return cs; + } + + cs.reserve(unspent.size()); + for (const auto& p : unspent) { + if (p.first.type == 0 || p.first.isInvite) { + continue; + } + assert(p.second.satoshis >= 0); + + cs.push_back({std::min(p.second.blockHeight, height), p.second.satoshis}); + } + + return cs; + } + + bool GetAllCoins(CGSContext& context, int tip_height) { + std::vector unspent; + if (!GetAllUnspent(false, [&context, tip_height](const CAddressUnspentKey& key, const CAddressUnspentValue& value) { + if (key.type == 0 || key.isInvite || value.satoshis == 0 || value.blockHeight > tip_height) { + return; + } + + assert(!key.isInvite); + assert(value.satoshis > 0); + + auto& entrant = context.GetEntrant(key.hashBytes); + entrant.coins.emplace_back(value.blockHeight, value.satoshis); + })) { + return false; + } + return true; + } + + BalancePair BalanceDecay(int tip_height, const Coin& c, int maturity) { + assert(tip_height >= 0); + assert(c.height <= tip_height); + assert(c.amount >= 0); + assert(maturity > 0); + + const auto age_scale = AgeScale(c, tip_height, maturity); + const auto aged_balance = age_scale * c.amount; + + assert(aged_balance <= std::numeric_limits::max()); + + CAmount amount = aged_balance; + + assert(amount >= 0); + assert(amount <= c.amount); + return BalancePair{amount, c.amount}; + } + + template + BalancePair AgedBalance(int tip_height, const Coins& cs, int maturity, AgeFunc AgedBalanceFunc) { + assert(tip_height >= 0); + + BalancePairs balances(cs.size()); + std::transform(cs.begin(), cs.end(), balances.begin(), + [tip_height, maturity, &AgedBalanceFunc](const Coin& c) { + return AgedBalanceFunc(tip_height, c, maturity); + }); + + const auto aged_balance = + std::accumulate(balances.begin(), balances.end(), CAmount{0}, + [](CAmount amount, const BalancePair& b) { + return amount + b.first; + }); + + const auto balance = + std::accumulate(balances.begin(), balances.end(), CAmount{0}, + [](CAmount amount, const BalancePair& b) { + return amount + b.second; + }); + + assert(aged_balance <= balance); + return BalancePair{aged_balance, balance}; + } + + //Convex function with property that if C0 > C1 and you have A within [0, 1] then + //ConvexF(C0 + A) - ConvexF(C0) > ConvexF(C1 + A) - ConvexF(C1); + //See: Lottery Trees: Motivational Deployment of Networked Systems + //These properties are important to allow for some kind of growth incentive + //without compromising the system's integrity against sybil attacks. + template + V ConvexF(V c, V B, V S) { + assert(c >= V{0}); + assert(c <= V{1.01}); + assert(B >= V{0}); + assert(B <= V{1.01}); + assert(S >= V{0}); + assert(S <= V{1.01}); + + const V v = (B*c) + ((V{1} - B)*boost::multiprecision::pow(c, V{1} + S)); + assert(v >= 0); + return v; + } + + Contribution ContributionNode( + CGSContext& context, + CachedEntrant& entrant, + referral::ReferralsViewCache& db) + { + assert(context.tip_height > 0); + assert(context.new_coin_maturity > 0); + + const auto aged_balance = entrant.balances; + + const auto beacon_height = + std::min(entrant.height, context.tip_height); + + if (beacon_height < 0 ) { + return Contribution{}; + } + + assert(beacon_height <= context.tip_height); + + const auto beacon_age_scale = + 1.0 - AgeScale(beacon_height, context.tip_height, context.new_coin_maturity); + + assert(beacon_age_scale >= 0); + assert(beacon_age_scale <= 1.01); + + Contribution c; + + //We compute both the linear and sublinear versions of the contribution. + //This is done because there are two pools of selections evenly split + //between stake oriented and growth oriented engagements. + c.value = (beacon_age_scale * aged_balance.second) + aged_balance.first; + + assert(c.value >= 0); + assert(c.value <= aged_balance.second); + + return c; + } + + struct Node + { + char address_type; + referral::Address address; + Children children; + SubtreeContribution contribution; + }; + + using NodeStack = std::stack; + + using AddressQueue = std::deque; + + /** + * Computes the subtree contribution rooted at the address specified. + * This algorithm computes the subtree contribution by doing a post order + * traversal of the ambassador tree. + * + * TODO: Implement parallel version, maybe Coffman–Graham algorithm. + */ + SubtreeContribution ContributionSubtreeIter( + CGSContext& context, + char address_type, + const referral::Address& address, + referral::ReferralsViewCache& db) + { + const auto c = context.subtree_contribution.find(address); + + if (c != context.subtree_contribution.end()) { + return c->second; + } + + const auto& root_entrant = context.GetEntrant(address); + + SubtreeContribution contribution; + + NodeStack ns; + ns.push({ + root_entrant.address_type, + address, + root_entrant.children, + {}}); + + while (!ns.empty()) { + auto& n = ns.top(); + n.contribution.value += contribution.value; + n.contribution.tree_size += contribution.tree_size; + + if (n.children.empty()) { + const auto& c = context.GetEntrant(n.address).contribution; + n.contribution.value += c.value; + n.contribution.tree_size++; + + assert(n.contribution.value >= 0); + + contribution = n.contribution; + context.subtree_contribution[n.address] = n.contribution; + + ns.pop(); + + } else { + const auto child_address = n.children.back(); + n.children.pop_back(); + + contribution.value = 0; + contribution.tree_size = 0; + + const auto& child_entrant = context.GetEntrant(child_address); + + ns.push({ + child_entrant.address_type, + child_address, + child_entrant.children, + {}}); + } + } + + return context.subtree_contribution[address]; + } + + ContributionAmount GetValue(const SubtreeContribution& t) + { + return t.value; + } + + struct WeightedScores + { + ContributionAmount value; + size_t tree_size; + }; + + WeightedScores WeightedScore( + CGSContext& context, + const char address_type, + const referral::Address& address, + referral::ReferralsViewCache& db) + { + assert(context.tree_contribution.value > 0); + + const auto subtree_contribution = + ContributionSubtreeIter( + context, + address_type, + address, + db); + assert(subtree_contribution.value >= 0); + assert(subtree_contribution.value <= context.tree_contribution.value); + + WeightedScores score; + score.value = ConvexF( + subtree_contribution.value / context.tree_contribution.value, + context.B, + context.S); + + score.tree_size = subtree_contribution.tree_size; + + assert(score.value >= 0); + assert(score.tree_size > 0); + return score; + } + + struct ExpectedValues + { + ContributionAmount value; + size_t tree_size; + }; + + ExpectedValues ExpectedValue( + CGSContext& context, + const CachedEntrant& entrant, + referral::ReferralsViewCache& db) + { + //this case can occur on regtest if there is not enough data. + if (context.tree_contribution.value == 0) { + return {0, 0}; + } + + assert(context.tree_contribution.value > 0); + + auto expected_value = WeightedScore( + context, + entrant.address_type, + entrant.address, + db); + + assert(expected_value.value >= 0); + + for (const auto& c: entrant.children) { + const auto& child_entrant = context.GetEntrant(c); + auto child_score = WeightedScore( + context, + child_entrant.address_type, + c, + db); + + assert(child_score.value >= 0); + expected_value.value -= child_score.value; + } + + assert(expected_value.value >= 0); + return { expected_value.value, expected_value.tree_size }; + } + + Entrant ComputeCGS( + CGSContext& context, + const CachedEntrant& entrant, + referral::ReferralsViewCache& db) + { + auto expected_value = ExpectedValue( + context, + entrant, + db); + + const ContributionAmount cgs = context.tree_contribution.value * expected_value.value; + + assert(cgs >= 0); + + auto floored_cgs = cgs.convert_to(); + + const auto& balance = entrant.balances; + + return Entrant{ + entrant.address_type, + entrant.address, + balance.second, + balance.first, + floored_cgs, + entrant.height, + entrant.children.size(), + expected_value.tree_size + }; + } + + void ComputeAges(CGSContext& context) { + assert(context.cgs_pool != nullptr); + + std::vector> jobs; + jobs.reserve(context.entrants.size() / BATCH_SIZE); + + for(size_t b = 0; b < context.entrants.size(); b+=BATCH_SIZE) { + jobs.push_back( + context.cgs_pool->push([b, &context](int id) { + const auto end = std::min(context.entrants.size(), b + BATCH_SIZE); + for(size_t i = b; i < end; i++) { + auto& e = context.entrants[i]; + e.balances = AgedBalance( + context.tip_height, + e.coins, + context.coin_maturity, + BalanceDecay); + } + })); + } + for(auto& j : jobs) { + j.wait(); + } + } + + void PrefillContributionsAndHeights( + CGSContext& context, + const char address_type, + const referral::Address& address, + referral::ReferralsViewCache& db) { + + AddressQueue q; + q.push_back(std::make_pair(address_type, address)); + while(!q.empty()) { + const auto p = q.front(); + q.pop_front(); + + const auto height = GetReferralHeight(db, p.second); + + const auto& entrant = context.AddEntrant( + p.first, + p.second, + height, + db.GetChildren(p.second)); + + for(const auto& c : entrant.children) { + const auto maybe_ref = db.GetReferral(c); + if (!maybe_ref) { + continue; + } + + q.push_back(std::make_pair(maybe_ref->addressType, maybe_ref->GetAddress())); + } + + } + } + + void ComputeAllContributions( + CGSContext& context, + referral::ReferralsViewCache& db) { + assert(context.cgs_pool != nullptr); + + std::vector> jobs; + jobs.reserve(context.entrants.size() / BATCH_SIZE); + for(size_t b = 0; b < context.entrants.size(); b+=BATCH_SIZE) { + jobs.push_back( + context.cgs_pool->push([b, &context, &db](int id) { + const auto end = std::min(context.entrants.size(), b + BATCH_SIZE); + for(size_t i = b; i < end; i++) { + auto& e = context.entrants[i]; + e.contribution = ContributionNode(context, e, db); + } + })); + } + for(auto& j : jobs) { + j.wait(); + } + } + + void ComputeAllScores( + CGSContext& context, + referral::ReferralsViewCache& db, + const Consensus::Params& params, + Entrants& entrants) + { + assert(context.cgs_pool != nullptr); + + const auto minimum_stake = GetAmbassadorMinumumStake(context.tip_height, params); + + std::vector> jobs; + jobs.reserve(context.entrants.size() / BATCH_SIZE); + + assert(!context.entrants.empty()); + assert(context.entrants[0].address == params.genesis_address); + + //Important, 1 here to skip the genesis address + for(size_t b = 1; b < context.entrants.size(); b+=BATCH_SIZE) { + jobs.push_back( + context.cgs_pool->push([b, minimum_stake , &context, &db](int id) { + const auto end = std::min(context.entrants.size(), b + BATCH_SIZE); + Entrants es; + es.reserve(end - b); + for(size_t i = b; i < end; i++) { + const auto& e = context.entrants[i]; + if(e.balances.second >= minimum_stake) { + es.emplace_back(ComputeCGS(context, e, db)); + } + } + return es; + })); + } + + entrants.reserve(context.entrants.size()*BATCH_SIZE); + + for(auto& j : jobs) { + auto es = j.get(); + entrants.insert(entrants.end(), es.begin(), es.end()); + } + } + + void GetAllRewardableEntrants( + CGSContext& context, + referral::ReferralsViewCache& db, + const Consensus::Params& params, + int height, + Entrants& entrants) + { + assert(height >= 0); + + context.tip_height = height; + context.coin_maturity = params.pog3_coin_maturity; + context.new_coin_maturity = params.pog3_new_coin_maturity; + context.B = params.pog3_convex_b; + context.S = params.pog3_convex_s; + PrefillContributionsAndHeights( + context, + 2, + params.genesis_address, + db); + GetAllCoins(context, height); + ComputeAges(context); + + ComputeAllContributions(context, db); + context.tree_contribution = ContributionSubtreeIter(context, 2, params.genesis_address, db); + + ComputeAllScores(context, db, params, entrants); + } + + CachedEntrant& CGSContext::AddEntrant( + char address_type, + const referral::Address& address, + int height, + const Children& children) + { + + CachedEntrant e; + e.address = address; + e.address_type = address_type; + e.height = height; + e.children = children; + + entrants.emplace_back(e); + auto ei = entrant_idx.insert(std::make_pair(address, entrants.size() - 1)); + assert(ei.second); + return entrants.back(); + } + + CachedEntrant& CGSContext::GetEntrant(const referral::Address& a) + { + auto p = entrant_idx.find(a); + assert(p != entrant_idx.end()); + return entrants[p->second]; + } + + const CachedEntrant& CGSContext::GetEntrant(const referral::Address& a) const + { + const auto p = entrant_idx.find(a); + assert(p != entrant_idx.end()); + return entrants[p->second]; + } + +} // namespace pog3 diff --git a/src/pog3/cgs.h b/src/pog3/cgs.h new file mode 100644 index 000000000..53fdb4bea --- /dev/null +++ b/src/pog3/cgs.h @@ -0,0 +1,128 @@ +// Copyright (c) 2018 The Merit Foundation developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MERIT_POG3_CGS_H +#define MERIT_POG3_CGS_H + +#include "primitives/referral.h" +#include "consensus/params.h" +#include "ctpl/ctpl.h" +#include "referrals.h" +#include "pog/wrs.h" +#include "coins.h" + +#include +#include + +namespace pog3 +{ + + struct Entrant + { + char address_type; + referral::Address address; + CAmount balance; + CAmount aged_balance; + CAmount cgs; + int beacon_height; + size_t children; + size_t network_size; + }; + + using MaybeEntrant = boost::optional; + + using ContributionAmount = pog::BigFloat; + struct Contribution + { + ContributionAmount value = 0.0; + }; + + struct SubtreeContribution + { + ContributionAmount value = 0.0; + size_t tree_size = 0; + }; + + //Aged and non-aged balance. + using BalancePair = std::pair; + using BalancePairs = std::vector; + + struct Coin + { + Coin(int h, CAmount a) : height{h}, amount{a} {} + int height; + CAmount amount; + }; + + using Coins = std::vector; + struct AddressBalance { + }; + + using AddressBalances = std::map; + + using AddressPair = std::pair; + using Addresses = std::vector; + using Children = Addresses; + + struct CachedEntrant + { + referral::Address address; + char address_type; + Coins coins; + BalancePair balances; + Contribution contribution; + int height; + Children children; + }; + + struct CGSContext + { + int tip_height; + int coin_maturity; + int new_coin_maturity; + SubtreeContribution tree_contribution; + + std::vector entrants; + std::map entrant_idx; + + std::map subtree_contribution; + double B; + double S; + + CachedEntrant& AddEntrant( + char address_type, + const referral::Address& address, + int height, + const Children& children); + + CachedEntrant& GetEntrant(const referral::Address&); + const CachedEntrant& GetEntrant(const referral::Address&) const; + + ctpl::thread_pool* cgs_pool = nullptr; + }; + + using Entrants = std::vector; + + void GetAllRewardableEntrants( + CGSContext& context, + referral::ReferralsViewCache&, + const Consensus::Params&, + int height, + Entrants&); + + + Entrant ComputeCGS( + CGSContext& context, + const CachedEntrant& entrant, + referral::ReferralsViewCache& db); + + void TestChain(); + void SetupCgsThreadPool(size_t threads); + ctpl::thread_pool* GetCgsThreadPool(); + + CAmount GetAmbassadorMinumumStake(int height, const Consensus::Params& consensus_params); + +} // namespace pog3 + +#endif //MERIT_POG2_ANV_H diff --git a/src/pog3/reward.cpp b/src/pog3/reward.cpp new file mode 100644 index 000000000..591c2679c --- /dev/null +++ b/src/pog3/reward.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2018 The Merit Foundation developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "pog3/reward.h" + +#include "pog/wrs.h" + +#include +#include + +namespace pog3 +{ + namespace + { + const int INVITES_PER_WINNER = 1; + } + + double ScaledCGS(const Entrant& e) + { + const auto scaled = std::log1p(e.cgs / 1_merit); + assert(scaled >= 0); + return scaled; + } + + double TotalCgs(const Entrants& winners) + { + return std::accumulate(std::begin(winners), std::end(winners), double{0.0}, + [](double acc, const Entrant& e) + { + return acc + ScaledCGS(e); + }); + } + + CAmount ProportionalRewards(pog::Rewards& rewards, CAmount total_reward_0, const Entrants& winners) { + auto total_cgs = TotalCgs(winners); + double total_reward = total_reward_0; + + pog::Rewards unfiltered_rewards; + unfiltered_rewards.resize(winners.size()); + std::transform(std::begin(winners), std::end(winners), std::back_inserter(unfiltered_rewards), + [total_reward, total_cgs](const Entrant& v) + { + double percent = ScaledCGS(v) / total_cgs; + double reward = total_reward * percent; + CAmount floor_reward = std::floor(reward); + + return pog::AmbassadorReward{ + v.address_type, + v.address, + floor_reward}; + }); + + rewards.reserve(unfiltered_rewards.size()); + std::copy_if(std::begin(unfiltered_rewards), std::end(unfiltered_rewards), + std::back_inserter(rewards), + [](const pog::AmbassadorReward& reward) { + return reward.amount > 0; + }); + + return + std::accumulate(std::begin(rewards), std::end(rewards), CAmount{0}, + [](CAmount acc, const pog::AmbassadorReward& reward) + { + return acc + reward.amount; + }); + } + + + pog::AmbassadorLottery RewardAmbassadors( + int height, + const Entrants& winners, + CAmount total_reward) + { + pog::Rewards rewards; + auto total_rewarded = ProportionalRewards(rewards, total_reward, winners); + + assert(total_rewarded <= total_reward); + + auto remainder = total_reward - total_rewarded; + + assert(remainder >= 0); + assert(remainder <= total_reward); + + return {rewards, remainder}; + } + + int ComputeTotalInviteLotteryWinners( + const pog::InviteLotteryParamsVec& lottery_points, + const Consensus::Params& params) + { + assert(lottery_points.size() == 2); + + const auto& block1 = lottery_points[0]; + const auto& block2 = lottery_points[1]; + + int min_total_winners = 0; + + //block1 is a weighted sum based on the imp_weights array. block1.blocks + //is divided by the number of weights so we multiply that back here to + //get the correct number of blocks + const auto min_miner_invites = block1.blocks / params.imp_miner_reward_for_every_x_blocks; + const auto min_lottery_invites = block1.blocks / params.imp_min_one_invite_for_every_x_blocks; + const auto min_invites = min_miner_invites + min_lottery_invites; + + LogPrint(BCLog::POG, "Invites used: %d created: %d period: %d used per block: %d min %d\n", + block1.invites_used_fixed, + block1.invites_created, + block1.blocks, + block1.mean_used_fixed, + min_invites); + + + if(block1.invites_created < min_invites) { + min_total_winners = block1.invites_used_fixed + min_lottery_invites; + } + + const double mean_diff = block1.mean_used_fixed - block2.mean_used_fixed; + + //Assume we need more or less than what was used before. + //This allows invites to grow or shrink exponentially. + const int change = mean_diff >= 0 ? + std::ceil(mean_diff) : + std::floor(mean_diff); + + LogPrint( + BCLog::POG, + "Mean Diff: %d change: %d b2: %d b1: %d min_total_winners: %d\n", + mean_diff, + change, + block1.mean_used_fixed, + block2.mean_used_fixed, + min_total_winners); + + const int total_winners = std::max( + min_total_winners, + static_cast(std::floor(block1.mean_used_fixed) + change)); + + assert(total_winners >= 0); + return total_winners; + } + + pog::InviteRewards RewardInvites(const referral::ConfirmedAddresses& winners) + { + assert(winners.size() >= 0); + + pog::InviteRewards rewards(winners.size()); + std::transform(winners.begin(), winners.end(), rewards.begin(), + [](const referral::ConfirmedAddress& winner) { + return pog::InviteReward { + winner.address_type, + winner.address, + INVITES_PER_WINNER + }; + }); + + assert(rewards.size() == winners.size()); + return rewards; + } + +} // namespace pog3 diff --git a/src/pog3/reward.h b/src/pog3/reward.h new file mode 100644 index 000000000..8c62b8b3c --- /dev/null +++ b/src/pog3/reward.h @@ -0,0 +1,30 @@ +// Copyright (c) 2018 The Merit Foundation developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MERIT_POG3_REWARD_H +#define MERIT_POG3_REWARD_H + +#include "refdb.h" +#include "pog/reward.h" +#include "pog3/cgs.h" +#include "consensus/params.h" + +namespace pog3 +{ + pog::AmbassadorLottery RewardAmbassadors( + int height, + const Entrants& winners, + CAmount total); + + double ComputeUsedInviteMean(const pog::InviteLotteryParams& lottery); + + int ComputeTotalInviteLotteryWinners( + const pog::InviteLotteryParamsVec&, + const Consensus::Params& params); + + pog::InviteRewards RewardInvites(const referral::ConfirmedAddresses& winners); + +} // namespace pog3 + +#endif //MERIT_POG2_REWARD_H diff --git a/src/pog3/select.cpp b/src/pog3/select.cpp new file mode 100644 index 000000000..616d00f87 --- /dev/null +++ b/src/pog3/select.cpp @@ -0,0 +1,437 @@ +// Copyright (c) 2018 The Merit Foundation developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "pog3/select.h" +#include "pog/select.h" +#include "base58.h" +#include "clientversion.h" +#include "util.h" + +#include +#include +#include "referrals.h" + +namespace pog3 +{ + namespace + { + enum PoolType { + CGS, + NEW, + ANY, + }; + + struct InvitePool + { + PoolType type; + double probability; + }; + + using InvitePoolDitributions = std::vector; + + const InvitePoolDitributions INVITE_POOLS = { + {CGS, 0.5}, + {NEW, 0.4}, + {ANY, 0.1} + }; + } + + bool IsValidAmbassadorDestinationForInvites(char type) + { + //KeyID + return type == 1; + } + + /** + * CgsDistribution uses Inverse Transform Sampling. Computing the + * CDF is trivial for the CGS discrete distribution by simply sorting and + * adding up all the CGSs of the addresss provided. + * + * Scaling to probabilities is unnecessary because we will use a hash function + * to sample into the range between 0-MaxCgs. Since the hash is already + * a uniform distribution then it provides a good way to sample into + * the distribution of CGSs where those with a bigger CGS are sampled more often. + * + * The most expensive part of creating the distribution is sorting the CGSs. + * However, since the number of CGSs is fixed no matter how large the + * blockchain gets, then there should be no issue handling growth. + */ + CgsDistribution::CgsDistribution(pog3::Entrants cgses) : + m_inverted(cgses.size()) + { + //index cgses by address id for convenience. + std::transform( + std::begin(cgses), + std::end(cgses), + std::inserter(m_cgses, std::end(m_cgses)), + + [](const pog3::Entrant& v) { + assert(v.cgs >= 0); + return std::make_pair(v.address, v); + }); + + assert(m_cgses.size() == cgses.size()); + + std::sort(std::begin(cgses), std::end(cgses), + [](const pog3::Entrant& a, const pog3::Entrant& b) { + return a.cgs == b.cgs ? a.address < b.address : a.cgs < b.cgs; + }); + + assert(m_inverted.size() == cgses.size()); + + //compute CDF by adding up all the CGSs + CAmount previous_cgs = 0; + std::transform(std::begin(cgses), std::end(cgses), std::begin(m_inverted), + [&previous_cgs](pog3::Entrant w) { + w.cgs += previous_cgs; + previous_cgs = w.cgs; + return w; + }); + + //back will always return because we assume m_cgses is non-empty + if(!m_inverted.empty()) m_max_cgs = m_inverted.back().cgs; + + assert(m_max_cgs >= 0); + } + + pog3::MaybeEntrant CgsDistribution::Sample(const uint256& hash) const + { + //It doesn't make sense to sample from an empty distribution. + assert(m_inverted.empty() == false); + if(m_max_cgs == 0) { + return {}; + } + + const auto selected_cgs = SipHashUint256(0, 0, hash) % m_max_cgs; + + auto pos = std::lower_bound(std::begin(m_inverted), std::end(m_inverted), + selected_cgs, + [](const pog3::Entrant& a, CAmount selected) { + return a.cgs < selected; + }); + + assert(m_max_cgs >= 0); + assert(selected_cgs < static_cast(m_max_cgs)); + assert(pos != std::end(m_inverted)); //it should be impossible to not find an cgs + //because selected_cgs must be less than max + auto selected_address = m_cgses.find(pos->address); + + assert(selected_address != std::end(m_cgses)); //all cgses in m_inverted must be in + //our index + return selected_address->second; + } + + size_t CgsDistribution::Size() const { + return m_inverted.size(); + } + + const pog3::Entrants& CgsDistribution::Entrants() const + { + return m_entrants; + } + + AddressSelector::AddressSelector( + int height, + const pog3::Entrants& entrants, + const Consensus::Params& params) : + m_entrants{entrants}, + m_stake_minumum{GetAmbassadorMinumumStake(height, params)} + { + m_cgs_distribution.reset(new CgsDistribution{entrants}); + + assert(m_cgs_distribution); + } + + const pog3::Entrants& AddressSelector::Entrants() const + { + return m_entrants; + } + + /** + * Selecting winners from the distribution is deterministic and will return the same + * N samples given the same input hash. + */ + pog3::Entrants AddressSelector::Select( + const referral::ReferralsViewCache& referrals, + uint256 hash, + size_t n, + const CgsDistribution& distribution) + { + assert(n <= Size()); + pog3::Entrants samples; + + auto max_tries = distribution.Size(); + n = std::min(n, max_tries); + + LogPrint(BCLog::POG, "%s: Selecting Ambassadors: %d Max: %d Out of: %d\n", __func__, n, max_tries, distribution.Size()); + + while(n-- && max_tries--) { + const auto sampled = distribution.Sample(hash); + //combine hashes and hash to get next sampling value + CHashWriter hasher{SER_DISK, CLIENT_VERSION}; + if(sampled) { + hasher << hash << sampled->address; + hash = hasher.GetHash(); + } else { + //combine hashes and hash to get next sampling value + CHashWriter hasher{SER_DISK, CLIENT_VERSION}; + hasher << hash << hash; + hash = hasher.GetHash(); + continue; + } + + const bool not_sampled = m_sampled.count(sampled->address) == 0; + const bool meets_stake_minumum = sampled->balance >= m_stake_minumum; + + if (not_sampled && + meets_stake_minumum && + referrals.IsConfirmed(sampled->address) && + pog::IsValidAmbassadorDestination(sampled->address_type)) { + + LogPrint(BCLog::POG, "%s: \tSelected %d: addr: %s cgs: %d abal: %d\n", __func__, + n, + CMeritAddress{sampled->address_type, sampled->address}.ToString(), + sampled->cgs, + sampled->aged_balance); + m_sampled.insert(sampled->address); + samples.push_back(*sampled); + } else { + LogPrint(BCLog::POG, "%s: \tSkipped %d: addr: %s sampled: %d, meetsstake: %d, cgs: %d bal: %d abal: %d\n", __func__, + n, + CMeritAddress{sampled->address_type, sampled->address}.ToString(), + !not_sampled, + meets_stake_minumum, + sampled->cgs, + sampled->balance, + sampled->aged_balance); + n++; + } + } + + LogPrint(BCLog::POG, "%s: Selected Ambassadors: %d\n", __func__, samples.size()); + return samples; + } + + pog3::Entrants AddressSelector::SelectByCgs( + const referral::ReferralsViewCache& referrals, + uint256 hash, + size_t n) + { + assert(m_cgs_distribution); + return Select(referrals, hash, n, *m_cgs_distribution); + } + + const pog3::Entrants& AddressSelector::CgsEntrants() const + { + assert(m_cgs_distribution); + return m_cgs_distribution->Entrants(); + } + + size_t AddressSelector::Size() const + { + assert(m_cgs_distribution); + return m_cgs_distribution->Size(); + } + + void GetConfirmedAddressesForNewPool( + uint64_t total_beacons, + const referral::ReferralsViewCache& db, + referral::ConfirmedAddresses& cas) + { + for(uint64_t i = 0; i < total_beacons; i++) { + const auto c = db.GetConfirmation(i); + if (!c || c->invites > 1 || !IsValidAmbassadorDestinationForInvites(c->address_type)) { + continue; + } + + const int height = db.GetNewInviteRewardedHeight(c->address); + if (height > 0) { + continue; + } + + cas.emplace_back(*c); + } + } + + referral::MaybeConfirmedAddress SelectInviteAddressFromNewPool( + const referral::ReferralsViewCache& db, + referral::ConfirmedAddresses& new_pool, + referral::ConfirmedAddresses& selected_new, + uint256 hash) + { + if (new_pool.empty()) { + return {}; + } + + const auto selected_idx = SipHashUint256(0, 0, hash) % new_pool.size(); + const auto selected = new_pool[selected_idx]; + + //remove entry by swapping with the last entry and removing from end. + const auto last_idx = new_pool.size() - 1; + std::swap(new_pool[selected_idx], new_pool[last_idx]); + new_pool.erase(new_pool.begin()+last_idx); + + //insert into our selected set. + selected_new.emplace_back(selected); + return selected; + } + + referral::MaybeConfirmedAddress SelectInviteAddressFromCgsPool( + const referral::ReferralsViewCache& db, + AddressSelector& selector, + uint256 hash) + { + const auto sampled = selector.SelectByCgs(db, hash, 1); + if(sampled.empty()) { + return {}; + } + assert(sampled.size() == 1); + + const auto& entrant = sampled[0]; + + return db.GetConfirmation(entrant.address_type, entrant.address); + } + + referral::MaybeConfirmedAddress SelectInviteAddressFromAnyPool( + const referral::ReferralsViewCache& db, + uint64_t total_beacons, + uint256 hash) + { + const auto selected_idx = SipHashUint256(0, 0, hash) % total_beacons; + return db.GetConfirmation(selected_idx); + } + + referral::ConfirmedAddresses SelectInviteAddresses( + AddressSelector& selector, + int height, + const referral::ReferralsViewCache& db, + uint256 hash, + const uint160& genesis_address, + size_t n, + const std::set &unconfirmed_invites, + int max_outstanding_invites, + referral::ConfirmedAddresses& selected_new_pool_addresses) + { + assert(n > 0); + assert(max_outstanding_invites > 0); + + auto requested = n; + + const auto total_beacons = db.GetTotalConfirmations(); + auto max_tries = std::min( + std::max(static_cast(n), total_beacons / 10), + total_beacons); + + referral::ConfirmedAddresses addresses; + referral::ConfirmedAddresses new_pool_addresses; + GetConfirmedAddressesForNewPool(total_beacons, db, new_pool_addresses); + + LogPrint(BCLog::POG, "%s: Selecting %d: Max: %d Out of: %d\n", __func__, n, max_tries, total_beacons); + + while(n-- && max_tries--) { + const auto selected_idx = SipHashUint256(0, 0, hash) % total_beacons; + const double rand_val = static_cast(selected_idx) / static_cast(total_beacons); + + CHashWriter hasher_a{SER_DISK, CLIENT_VERSION}; + hasher_a << hash << hash; + hash = hasher_a.GetHash(); + + const auto& selected_pool = INVITE_POOLS[SipHashUint256(0, 0, hash) % INVITE_POOLS.size()]; + + LogPrint(BCLog::POG, "%s: \tsampling pool: %d randval: %d poolprob: %d n: %d maxtries: %d\n", + __func__, + static_cast(selected_pool.type), + rand_val, + selected_pool.probability, + n, + max_tries); + + if(rand_val < selected_pool.probability) { + referral::MaybeConfirmedAddress maybe_address; + switch(selected_pool.type) { + case PoolType::CGS: + maybe_address = SelectInviteAddressFromCgsPool( + db, + selector, + hash); + break; + case PoolType::NEW: + maybe_address = + SelectInviteAddressFromNewPool( + db, + new_pool_addresses, + selected_new_pool_addresses, + hash); + break; + case PoolType::ANY: + maybe_address = SelectInviteAddressFromAnyPool( + db, + total_beacons, + hash); + break; + } + + if(maybe_address) { + LogPrint(BCLog::POG, "%s: \t%d %s invites: %d\n", + __func__, + static_cast(selected_pool.type), + CMeritAddress{maybe_address->address_type, maybe_address->address}.ToString(), + maybe_address->invites); + + } + + if (!maybe_address) { + LogPrint(BCLog::POG, "%s: \tskipping no address: %d\n", + __func__, + static_cast(selected_pool.type)); + n++; + } else if (!IsValidAmbassadorDestinationForInvites(maybe_address->address_type)) { + LogPrint(BCLog::POG, "%s: \tskipping invalid address: %d %s invites: %d\n", + __func__, + static_cast(selected_pool.type), + CMeritAddress{maybe_address->address_type, maybe_address->address}.ToString(), + maybe_address->invites); + n++; + } else if (maybe_address->invites > max_outstanding_invites) { + LogPrint(BCLog::POG, "%s: \tskipping max invites: %d %s invites: %d\n", + __func__, + static_cast(selected_pool.type), + CMeritAddress{maybe_address->address_type, maybe_address->address}.ToString(), + maybe_address->invites); + n++; + } else if (maybe_address->address == genesis_address) { + LogPrint(BCLog::POG, "%s: \tskipping genesis: %d %s invites: %d\n", + __func__, + static_cast(selected_pool.type), + CMeritAddress{maybe_address->address_type, maybe_address->address}.ToString(), + maybe_address->invites); + n++; + } else if ( unconfirmed_invites.count(maybe_address->address)) { + LogPrint(BCLog::POG, "%s: \tskipping unconfirmed: %d %s invites: %d\n", + __func__, + static_cast(selected_pool.type), + CMeritAddress{maybe_address->address_type, maybe_address->address}.ToString(), + maybe_address->invites); + n++; + } else { + addresses.push_back(*maybe_address); + } + } else { + n++; + max_tries++; + } + + } + + LogPrint(BCLog::POG, "%s: Selected %d:\n", __func__, addresses.size()); + + if(requested > 0) { + LogPrintf("Selected %d addresses (requested %d) for the invite lottery from a pool of %d\n", addresses.size(), requested, total_beacons); + } + + assert(addresses.size() <= requested); + return addresses; + } + +} // namespace pog3 diff --git a/src/pog3/select.h b/src/pog3/select.h new file mode 100644 index 000000000..fe834e849 --- /dev/null +++ b/src/pog3/select.h @@ -0,0 +1,93 @@ +// Copyright (c) 2018 The Merit Foundation developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MERIT_POG3_SELECT_H +#define MERIT_POG3_SELECT_H + +#include "hash.h" +#include "amount.h" +#include "pog3/cgs.h" +#include "consensus/params.h" + +#include + +#include +#include + +namespace referral +{ + class ReferralsViewCache; +} + +namespace pog3 +{ + using InvertedEntrants = pog3::Entrants; + using AddressToEntrant = std::map; + using SampledAddresses = std::set; + + class CgsDistribution + { + public: + CgsDistribution(pog3::Entrants entrants); + pog3::MaybeEntrant Sample(const uint256& hash) const; + size_t Size() const; + const pog3::Entrants& Entrants() const; + + private: + + const pog3::Entrants m_entrants; + InvertedEntrants m_inverted; + AddressToEntrant m_cgses; + CAmount m_max_cgs = 0; + }; + + using CgsDistributionPtr = std::unique_ptr; + + class AddressSelector + { + public: + AddressSelector( + int height, + const pog3::Entrants& entrants, + const Consensus::Params&); + + const pog3::Entrants& Entrants() const; + + pog3::Entrants SelectByCgs( + const referral::ReferralsViewCache& referrals, + uint256 hash, + size_t n); + + const pog3::Entrants& CgsEntrants() const; + + size_t Size() const; + private: + pog3::Entrants Select( + const referral::ReferralsViewCache& referrals, + uint256 hash, + size_t n, + const CgsDistribution& distribution); + + const pog3::Entrants m_entrants; + CgsDistributionPtr m_cgs_distribution; + SampledAddresses m_sampled; + const CAmount m_stake_minumum; + }; + + using AddressSelectorPtr = std::shared_ptr; + + referral::ConfirmedAddresses SelectInviteAddresses( + AddressSelector&, + int height, + const referral::ReferralsViewCache& db, + uint256 hash, + const uint160& genesis_address, + size_t n, + const std::set &unconfirmed_invites, + int max_outstanding_invites, + referral::ConfirmedAddresses& confirmed_new_pool); + +} // namespace pog3 + +#endif //MERIT_POG2_SELECT_H diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index e70bff001..e696de605 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -24,7 +24,7 @@ #include "policy/rbf.h" #include "pog/anv.h" -#include "pog2/cgs.h" +#include "pog3/cgs.h" #include "pog/select.h" #include "rpc/safemode.h" @@ -1316,7 +1316,7 @@ UniValue getaddressbalance(const JSONRPCRequest& request) return result; } -UniValue RanksToUniValue(CAmount lottery_cgs, const Pog2Ranks& ranks, size_t total, bool sub) { +UniValue RanksToUniValue(CAmount lottery_cgs, const Pog3Ranks& ranks, size_t total, bool sub) { UniValue rankarr(UniValue::VARR); for(const auto& r : ranks) { @@ -1326,7 +1326,7 @@ UniValue RanksToUniValue(CAmount lottery_cgs, const Pog2Ranks& ranks, size_t tot double percentile = (static_cast(r.second) / static_cast(total)) * 100.0; - const auto cgs = sub ? r.first.sub_cgs : r.first.cgs; + const auto cgs = r.first.cgs; auto alias = FindAliasForAddress(r.first.address); auto beacon_age = chainActive.Height() - r.first.beacon_height; @@ -1404,17 +1404,17 @@ UniValue getaddressrank(const JSONRPCRequest& request) const auto params = Params().GetConsensus(); - pog2::Entrants all_entrants; - pog2::CGSContext context; - pog2::GetAllRewardableEntrants(context, *prefviewcache, params, chainActive.Height(), all_entrants); + pog3::Entrants all_entrants; + pog3::CGSContext context; + context.cgs_pool = pog3::GetCgsThreadPool(); - bool sub_linear = true; + pog3::GetAllRewardableEntrants(context, *prefviewcache, params, chainActive.Height(), all_entrants); std::vector cgs; for (const auto& a : validAddresses) { const auto& e = context.GetEntrant(a.first); - auto node = pog2::ComputeCGS(context, e, *prefviewcache); - cgs.push_back(sub_linear ? node.sub_cgs : node.cgs); + auto node = pog3::ComputeCGS(context, e, *prefviewcache); + cgs.push_back(node.cgs); } CAmount lottery_cgs = 0; @@ -1422,8 +1422,7 @@ UniValue getaddressrank(const JSONRPCRequest& request) cgs, chainActive.Height(), Params().GetConsensus(), - lottery_cgs, - sub_linear); + lottery_cgs); //Hack to keep ANVRanks (2nlog(n)) vs (nlogn + n) we rewrite the address //because among addresses of equal rank, ANVRAnks may return an entry with a different address. @@ -1546,15 +1545,15 @@ UniValue simulatelottery(const JSONRPCRequest& request) const auto& params = Params().GetConsensus(); - const auto subsidy = GetSplitSubsidy(params.pog2_blockheight, params); - const bool FORCE_POG2 = true; + const auto subsidy = GetSplitSubsidy(params.pog3_blockheight, params); + const bool FORCE_POG3 = true; - auto rewards = Pog2RewardAmbassadors( + auto rewards = Pog3RewardAmbassadors( height, seed, subsidy.ambassador, params, - FORCE_POG2); + true); CCoinsViewCache view(pcoinsTip); DebitsAndCredits dummy_debits_and_credits; @@ -1563,6 +1562,7 @@ UniValue simulatelottery(const JSONRPCRequest& request) referral::ConfirmedAddresses selected_new_pool_addresses; if(!RewardInvites( + nullptr, rewards.second, height, chainActive[height], @@ -1573,7 +1573,7 @@ UniValue simulatelottery(const JSONRPCRequest& request) dummy_state, invite_rewards, selected_new_pool_addresses, - FORCE_POG2)) { + FORCE_POG3)) { throw JSONRPCError(RPC_MISC_ERROR, "error running invite lottery"); } diff --git a/src/validation.cpp b/src/validation.cpp index c17ff9482..acaaf4adb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -23,6 +23,8 @@ #include "pog/select.h" #include "pog2/reward.h" #include "pog2/select.h" +#include "pog3/reward.h" +#include "pog3/select.h" #include "pog/invitebuffer.h" #include "policy/fees.h" #include "policy/policy.h" @@ -1811,18 +1813,34 @@ CAmount GetBlockSubsidy(int height, const Consensus::Params& consensus_params) return nSubsidy; } +int64_t AmbassadorPercentCut(int height, const Consensus::Params& params) +{ + assert(height >= 0); + + int64_t percent_cut = 0; + if (height >= params.pog3_blockheight) { + percent_cut = params.pog3_ambassador_percent_cut; + } else if (height >= params.pog2_blockheight) { + percent_cut = params.pog2_ambassador_percent_cut; + } else { + percent_cut = params.ambassador_percent_cut; + } + + assert(percent_cut > 0); + return percent_cut; +} + SplitSubsidy GetSplitSubsidy(int height, const Consensus::Params& params) { - const auto percent_cut = height >= params.pog2_blockheight ? - params.pog2_ambassador_percent_cut : params.ambassador_percent_cut; + const auto percent_cut = AmbassadorPercentCut(height, params); assert(percent_cut >= 0 && percent_cut <= 100); - auto block_subsidy = GetBlockSubsidy(height, params); + const auto block_subsidy = GetBlockSubsidy(height, params); - auto ambassador_subsidy = (block_subsidy * percent_cut) / 100; - auto miner_subsidy = block_subsidy - ambassador_subsidy; + const auto ambassador_subsidy = (block_subsidy * percent_cut) / 100; + const auto miner_subsidy = block_subsidy - ambassador_subsidy; - assert(ambassador_subsidy <= miner_subsidy); + assert(miner_subsidy > 0); assert(miner_subsidy + ambassador_subsidy == block_subsidy); return {miner_subsidy, ambassador_subsidy}; } @@ -1903,6 +1921,18 @@ void LogWinners(const pog2::Entrants& es) } } +void LogWinners(const pog3::Entrants& es) +{ + for(const auto& e : es) { + LogPrint(BCLog::POG, "%s: \t%s: cgs: %d children: %d netsize: %d\n", + __func__, + CMeritAddress{e.address_type, e.address}.ToString(), + e.cgs, + e.children, + e.network_size); + } +} + void LogRewards(const pog::Rewards& rewards) { for(const auto& r : rewards) { @@ -1945,6 +1975,8 @@ std::pair Pog2RewardAmbassador entrants.reserve(reserve_size); pog2::CGSContext context; + context.cgs_pool = pog3::GetCgsThreadPool(); + pog2::GetAllRewardableEntrants(context, *prefviewcache, params, height, entrants); max_ambassador_lottery = std::max(max_ambassador_lottery, entrants.size()); @@ -2024,19 +2056,105 @@ std::pair Pog2RewardAmbassador return std::make_pair(rewards, selector); } -std::pair RewardAmbassadors( +std::pair Pog3RewardAmbassadors( int height, const uint256& previous_block_hash, CAmount total, - const Consensus::Params& params) + const Consensus::Params& params, + bool force_pog3) { - if (height >= params.pog2_blockheight) { - return Pog2RewardAmbassadors(height, previous_block_hash, total, params); + if (!force_pog3) { + assert(height >= params.pog3_blockheight); + } + + assert(prefviewdb != nullptr); + + static size_t max_ambassador_lottery = 0; + pog3::Entrants entrants; + + // unlikely that the candidates grew over 50% since last time. + auto reserve_size = max_ambassador_lottery * 1.5; + entrants.reserve(reserve_size); + + pog3::CGSContext context; + context.cgs_pool = pog3::GetCgsThreadPool(); + + pog3::GetAllRewardableEntrants(context, *prefviewcache, params, height, entrants); + + max_ambassador_lottery = std::max(max_ambassador_lottery, entrants.size()); + + // Wallet selector will create a distribution from all the keys + auto selector = std::make_shared(height, entrants, params); + + // We may have fewer keys in the distribution than the expected winners, + // so just pick smallest of the two. + auto desired_winners = std::min( + params.pog3_total_winning_ambassadors, + static_cast(selector->Size())); + + LogPrint(BCLog::POG, "%s: Desired winners: %d\n", __func__, desired_winners); + + // If we have an empty distribution, for example in some of the unit + // tests, we return the whole ambassador amount back to the miner + if (desired_winners == 0) { + return std::make_pair(pog::AmbassadorLottery{{}, total}, selector); } - return std::make_pair( + // validate sane winner amount + assert(desired_winners < 100); + + LogPrint(BCLog::POG, "%s: Desired winners: %d\n", __func__, desired_winners); + + // Select the N winners using the previous block hash as the seed + // from the CGS distribution. This CGS distribution is weighted towards stake. + const auto winners = selector->SelectByCgs( + *prefviewcache, + previous_block_hash, + desired_winners); + + LogPrint(BCLog::POG, "%s: Cgs winners: %d\n", __func__, winners.size()); + LogWinners(winners); + + assert(winners.size() <= desired_winners); + + // Compute reward for all the winners + const auto rewards = pog3::RewardAmbassadors(height, winners, total); + LogPrint(BCLog::POG, "%s: Rewarding %d winners\n", __func__, rewards.winners.size()); + LogRewards(rewards.winners); + + // Return the remainder which will be given to the miner; + assert(rewards.remainder <= total); + assert(rewards.remainder >= 0); + + return std::make_pair(rewards, selector); +} + +std::tuple RewardAmbassadors( + int height, + const uint256& previous_block_hash, + CAmount total, + const Consensus::Params& params) +{ + if (height >= params.pog3_blockheight) { + const auto pog3_rewards = + Pog3RewardAmbassadors(height, previous_block_hash, total, params); + return std::make_tuple( + pog3_rewards.first, + pog2::AddressSelectorPtr{}, + pog3_rewards.second); + } else if (height >= params.pog2_blockheight) { + const auto pog2_rewards = + Pog2RewardAmbassadors(height, previous_block_hash, total, params); + return std::make_tuple( + pog2_rewards.first, + pog2_rewards.second, + pog3::AddressSelectorPtr{}); + } + + return std::make_tuple( Pog1RewardAmbassadors(height, previous_block_hash, total, params), - pog2::AddressSelectorPtr{}); + pog2::AddressSelectorPtr{}, + pog3::AddressSelectorPtr{}); } bool OldComputeInviteLotteryParams( @@ -2224,7 +2342,8 @@ bool ComputeInviteLotteryParams( } bool RewardInvites( - pog2::AddressSelectorPtr cgs_selector, + pog2::AddressSelectorPtr pog2_cgs_selector, + pog3::AddressSelectorPtr pog3_cgs_selector, int height, CBlockIndex* pindexPrev, const uint256& previous_block_hash, @@ -2234,7 +2353,7 @@ bool RewardInvites( CValidationState& state, pog::InviteRewards& rewards, referral::ConfirmedAddresses& selected_new_pool_addresses, - bool force_pog2) + bool force_pog3) { assert(height >= 0); assert(prefviewdb != nullptr); @@ -2254,7 +2373,11 @@ bool RewardInvites( size_t total_winners = 0; const bool pog2 = height >= params.pog2_blockheight; - if(force_pog2 || height >= params.imp_fix_invites_blockheight) { + const bool pog3 = height >= params.pog3_blockheight; + + if(force_pog3 || height >= params.pog3_blockheight) { + total_winners = pog3::ComputeTotalInviteLotteryWinners(lottery_params, params); + } else if(height >= params.imp_fix_invites_blockheight) { total_winners = pog2::ComputeTotalInviteLotteryWinnersWithAmountFix(lottery_params, params); } else if(pog2) { total_winners = pog2::ComputeTotalInviteLotteryWinners(lottery_params, params); @@ -2292,9 +2415,22 @@ bool RewardInvites( } } - const auto winners = pog2 ? - pog2::SelectInviteAddresses( - *cgs_selector, + referral::ConfirmedAddresses winners; + + if (pog3) { + winners = pog3::SelectInviteAddresses( + *pog3_cgs_selector, + height, + *prefviewcache, + previous_block_hash, + params.genesis_address, + total_winners, + unconfirmed_invites, + params.pog3_max_outstanding_invites_per_address, + selected_new_pool_addresses); + } else if (pog2) { + winners = pog2::SelectInviteAddresses( + *pog2_cgs_selector, height, *prefviewcache, previous_block_hash, @@ -2302,14 +2438,16 @@ bool RewardInvites( total_winners, unconfirmed_invites, params.pog2_max_outstanding_invites_per_address, - selected_new_pool_addresses) : - pog::SelectConfirmedAddresses( + selected_new_pool_addresses); + } else { + winners = pog::SelectConfirmedAddresses( *prefviewdb, previous_block_hash, params.genesis_address, total_winners, unconfirmed_invites, params.daedalus_max_outstanding_invites_per_address); + } assert(winners.size() <= static_cast(total_winners)); @@ -4407,11 +4545,14 @@ static bool ConnectBlock( hashPrevBlock, subsidy.ambassador, chainparams.GetConsensus()); - assert(lottery.first.remainder >= 0); - auto cgs_selector = lottery.second; + const auto& ambassador_lottery = std::get<0>(lottery); + assert(ambassador_lottery.remainder >= 0); + + auto pog2_cgs_selector = std::get<1>(lottery); + auto pog3_cgs_selector = std::get<2>(lottery); - if (validate && !AreExpectedLotteryWinnersPaid(lottery.first, coinbase_tx)) { + if (validate && !AreExpectedLotteryWinnersPaid(ambassador_lottery, coinbase_tx)) { return state.DoS(100, error("ConnectBlock(): coinbase did not pay the expected ambassadors."), REJECT_INVALID, "bad-cb-bad-ambassadors"); @@ -4429,7 +4570,8 @@ static bool ConnectBlock( if (block.IsDaedalus()) { pog::InviteRewards invite_rewards; if (!RewardInvites( - cgs_selector, + pog2_cgs_selector, + pog3_cgs_selector, pindex->nHeight, pindex->pprev, hashPrevBlock, @@ -7671,50 +7813,47 @@ std::pair TopANVRanks( return {ranks, entrants.size()}; } -std::pair CGSRanks( +std::pair CGSRanks( const std::vector& cgs, int height, const Consensus::Params& params, - CAmount& lottery_cgs, - bool sub) + CAmount& lottery_cgs) { assert(height >= 0); assert(prefviewcache); - auto value_f = sub ? - [](const pog2::Entrant& e) { return e.sub_cgs;} : - [](const pog2::Entrant& e) { return e.cgs;}; - static size_t max_ambassador_lottery = 0; - pog2::Entrants entrants; + pog3::Entrants entrants; // unlikely that the candidates grew over 50% since last time. auto reserve_size = max_ambassador_lottery * 1.5; entrants.reserve(reserve_size); - pog2::CGSContext context; - pog2::GetAllRewardableEntrants(context, *prefviewcache, params, height, entrants); + pog3::CGSContext context; + context.cgs_pool = pog3::GetCgsThreadPool(); + + pog3::GetAllRewardableEntrants(context, *prefviewcache, params, height, entrants); max_ambassador_lottery = std::max(max_ambassador_lottery, entrants.size()); lottery_cgs = std::accumulate(entrants.begin(), entrants.end(), CAmount{0}, - [value_f](CAmount acc, const pog2::Entrant& e) { - return acc + value_f(e); + [](CAmount acc, const pog3::Entrant& e) { + return acc + e.cgs; }); std::sort(entrants.begin(), entrants.end(), - [value_f](const pog2::Entrant& a, const pog2::Entrant& b) { - return value_f(a) < value_f(b); + [](const pog3::Entrant& a, const pog3::Entrant& b) { + return a.cgs < b.cgs; }); - Pog2Ranks ranks; + Pog3Ranks ranks; ranks.resize(cgs.size()); std::transform(cgs.begin(), cgs.end(), ranks.begin(), - [&entrants, value_f](CAmount cgs) { + [&entrants](CAmount cgs) { auto pos = std::lower_bound(entrants.begin(), entrants.end(), cgs, - [value_f](const pog2::Entrant& a, CAmount cgs) { - return value_f(a) < cgs; + [](const pog3::Entrant& a, CAmount cgs) { + return a.cgs < cgs; }); return std::make_pair(*pos, std::distance(entrants.begin(), pos)); }); @@ -7723,49 +7862,46 @@ std::pair CGSRanks( return {ranks, total}; } -std::pair TopCGSRanks( +std::pair TopCGSRanks( size_t total, int height, const Consensus::Params& params, - CAmount& lottery_cgs, - bool sub) + CAmount& lottery_cgs) { assert(height >= 0); assert(prefviewcache); - auto value_f = sub ? - [](const pog2::Entrant& e) { return e.sub_cgs;} : - [](const pog2::Entrant& e) { return e.cgs;}; - static size_t max_ambassador_lottery = 0; - pog2::Entrants entrants; + pog3::Entrants entrants; // unlikely that the candidates grew over 50% since last time. auto reserve_size = max_ambassador_lottery * 1.5; entrants.reserve(reserve_size); - pog2::CGSContext context; - pog2::GetAllRewardableEntrants(context, *prefviewcache, params, height, entrants); + pog3::CGSContext context; + context.cgs_pool = pog3::GetCgsThreadPool(); + + pog3::GetAllRewardableEntrants(context, *prefviewcache, params, height, entrants); lottery_cgs = std::accumulate(entrants.begin(), entrants.end(), CAmount{0}, - [value_f](CAmount acc, const pog2::Entrant& e) { - return acc + value_f(e); + [](CAmount acc, const pog3::Entrant& e) { + return acc + e.cgs; }); max_ambassador_lottery = std::max(max_ambassador_lottery, entrants.size()); total = std::min(total, entrants.size()); std::partial_sort(entrants.begin(), entrants.begin() + total, entrants.end(), - [value_f](const pog2::Entrant& a, const pog2::Entrant& b) { - return value_f(a) > value_f(b); + [](const pog3::Entrant& a, const pog3::Entrant& b) { + return a.cgs > b.cgs; }); - Pog2Ranks ranks; + Pog3Ranks ranks; ranks.resize(total); int pos = 1; std::transform(entrants.begin(), entrants.begin() + total, ranks.begin(), - [&pos,&entrants](const pog2::Entrant& e) { + [&pos,&entrants](const pog3::Entrant& e) { return std::make_pair(e, entrants.size() - pos++); }); diff --git a/src/validation.h b/src/validation.h index 1e837a8d7..258b5d81f 100644 --- a/src/validation.h +++ b/src/validation.h @@ -26,20 +26,22 @@ #include "pog/reward.h" #include "pog2/cgs.h" #include "pog2/select.h" +#include "pog3/cgs.h" +#include "pog3/select.h" #include "txdb.h" #include "script/standard.h" #include +#include #include #include #include #include #include +#include #include #include -#include - class CBlockIndex; class CBlockPolicyEstimator; class CBlockTreeDB; @@ -356,7 +358,7 @@ SplitSubsidy GetSplitSubsidy(int height, const Consensus::Params& consensus_para * with part of the block subsidy based on a deterministic lottery. RewardAmbassadors * returns a vector of key -> reward pairs. Any remainder not allocated is returned. */ -std::pair RewardAmbassadors( +std::tuple RewardAmbassadors( int height, const uint256& previous_block_hash, CAmount total, @@ -369,8 +371,16 @@ std::pair Pog2RewardAmbassador const Consensus::Params& params, bool force_pog2 = false); +std::pair Pog3RewardAmbassadors( + int height, + const uint256& previous_block_hash, + CAmount total, + const Consensus::Params& params, + bool force_pog3 = false); + bool RewardInvites( pog2::AddressSelectorPtr, + pog3::AddressSelectorPtr, int height, CBlockIndex* pindexPrev, const uint256& previous_block_hash, @@ -740,6 +750,9 @@ using Ranks = std::vector; using Pog2Rank = std::pair; using Pog2Ranks = std::vector; +using Pog3Rank = std::pair; +using Pog3Ranks = std::vector; + std::pair ANVRanks( const std::vector& anv, int height, @@ -750,19 +763,17 @@ std::pair TopANVRanks( int height, const Consensus::Params& params); -std::pair CGSRanks( +std::pair CGSRanks( const std::vector& cgs, int height, const Consensus::Params& params, - CAmount& lottery_cgs, - bool sub = true); + CAmount& lottery_cgs); -std::pair TopCGSRanks( +std::pair TopCGSRanks( size_t total, int height, const Consensus::Params& params, - CAmount& lottery_cgs, - bool sub = true); + CAmount& lottery_cgs); template bool GetAllUnspent(