From 41cc6c4c76e87ca3245b81e23d84ffc516db2b8e Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Sat, 1 Aug 2020 20:55:20 +0300 Subject: [PATCH 1/3] Change to distribution of rewards after first halving --- src/Makefile.test.include | 3 +- src/chainparams.cpp | 18 +- src/consensus/params.h | 9 + src/miner.cpp | 13 +- src/test/firsthalving_tests.cpp | 292 ++++++++++++++++++++++++++++++++ src/validation.cpp | 30 +++- src/validation.h | 1 + 7 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 src/test/firsthalving_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 63ffe98dae..35ac91c9bb 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -162,7 +162,8 @@ BITCOIN_TESTS =\ test/uint256_tests.cpp \ test/univalue_tests.cpp \ test/util_tests.cpp \ - test/multiexponentation_test.cpp + test/multiexponentation_test.cpp \ + test/firsthalving_tests.cpp # test/evo_deterministicmns_tests.cpp \ # test/evo_simplifiedmns_tests.cpp \ # test/bls_tests.cpp diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 269128c734..2097f51508 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -180,6 +180,10 @@ class CMainParams : public CChainParams { consensus.nSubsidyHalvingInterval = 420000; consensus.nSubsidyHalvingStopBlock = 3646849; + consensus.stage2DevelopmentFundShare = 15; + consensus.stage2ZnodeShare = 35; + consensus.stage2DevelopmentFundAddress = ""; // TODO: specify the address + consensus.nMajorityEnforceBlockUpgrade = 750; consensus.nMajorityRejectBlockOutdated = 950; consensus.nMajorityWindow = 1000; @@ -397,9 +401,13 @@ class CTestNetParams : public CChainParams { consensus.chainType = Consensus::chainTestnet; - consensus.nSubsidyHalvingFirst = 302438; - consensus.nSubsidyHalvingInterval = 420000; - consensus.nSubsidyHalvingStopBlock = 3646849; + consensus.nSubsidyHalvingFirst = 12000; + consensus.nSubsidyHalvingInterval = 100000; + consensus.nSubsidyHalvingStopBlock = 1000000; + + consensus.stage2DevelopmentFundShare = 15; + consensus.stage2ZnodeShare = 35; + consensus.stage2DevelopmentFundAddress = ""; // TODO: specify the address consensus.nMajorityEnforceBlockUpgrade = 51; consensus.nMajorityRejectBlockOutdated = 75; @@ -599,10 +607,14 @@ class CRegTestParams : public CChainParams { consensus.chainType = Consensus::chainRegtest; + // To be changed for specific tests consensus.nSubsidyHalvingFirst = 302438; consensus.nSubsidyHalvingInterval = 420000; consensus.nSubsidyHalvingStopBlock = 3646849; + consensus.stage2DevelopmentFundShare = 15; + consensus.stage2ZnodeShare = 35; + consensus.nMajorityEnforceBlockUpgrade = 750; consensus.nMajorityRejectBlockOutdated = 950; consensus.nMajorityWindow = 1000; diff --git a/src/consensus/params.h b/src/consensus/params.h index 920fce150d..0d51acb66e 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -132,6 +132,15 @@ struct Params { int nSubsidyHalvingInterval; /** Stop subsidy at this block number */ int nSubsidyHalvingStopBlock; + + /** parameters for coinbase payment distribution between first and second halvings (aka stage 2) */ + /** P2PKH or P2SH address for developer funds */ + std::string stage2DevelopmentFundAddress; + /** percentage of block subsidy going to developer fund */ + int stage2DevelopmentFundShare; + /** percentage of block subsidy going to znode */ + int stage2ZnodeShare; + /** Used to check majorities for block version upgrade */ int nMajorityEnforceBlockUpgrade; int nMajorityRejectBlockOutdated; diff --git a/src/miner.cpp b/src/miner.cpp index f9a33aeccf..c52b10d385 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -783,8 +783,17 @@ void BlockAssembler::FillFoundersReward(CMutableTransaction &coinbaseTx, bool fM auto ¶ms = chainparams.GetConsensus(); CAmount coin = COIN / (fMTP ? params.nMTPRewardReduction : 1); - // To founders and investors - if ((nHeight + 1 > 0) && (nHeight + 1 < params.nSubsidyHalvingFirst)) { + if (nHeight >= params.nSubsidyHalvingFirst && nHeight < params.nSubsidyHalvingFirst + params.nSubsidyHalvingInterval) { + // Stage 2 + CScript devPayoutScript = GetScriptForDestination(CBitcoinAddress(params.stage2DevelopmentFundAddress).Get()); + CAmount devPayoutValue = (GetBlockSubsidyWithMTPFlag(nHeight, params, fMTP) * params.stage2DevelopmentFundShare) / 100; + + coinbaseTx.vout[0].nValue -= devPayoutValue; + coinbaseTx.vout.push_back(CTxOut(devPayoutValue, devPayoutScript)); + } + + else if ((nHeight > 0) && (nHeight < params.nSubsidyHalvingFirst)) { + // Stage 1: To founders and investors CScript FOUNDER_1_SCRIPT; CScript FOUNDER_2_SCRIPT; CScript FOUNDER_3_SCRIPT; diff --git a/src/test/firsthalving_tests.cpp b/src/test/firsthalving_tests.cpp new file mode 100644 index 0000000000..e4528e56c9 --- /dev/null +++ b/src/test/firsthalving_tests.cpp @@ -0,0 +1,292 @@ +#include "test/test_bitcoin.h" + +#include "script/interpreter.h" +#include "script/standard.h" +#include "script/sign.h" +#include "validation.h" +#include "zerocoin.h" +#include "netbase.h" +#include "keystore.h" +#include "base58.h" +#include "evo/specialtx.h" + +#include + +typedef std::map> SimpleUTXOMap; + +static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs) +{ + SimpleUTXOMap utxos; + CAmount balance = 0; + for (size_t i = 0; i < txs.size(); i++) { + auto& tx = txs[i]; + size_t const znode_output = tx.vout.size() > 6 ? FindZnodeOutput(tx) : 0; + for (size_t j = 0; j < tx.vout.size(); j++) { + if(j == 0 || j == znode_output) { + balance += tx.vout[j].nValue; + utxos.emplace(COutPoint(tx.GetHash(), j), std::make_pair((int)i + 1, tx.vout[j].nValue)); + } + } + } + return utxos; +} + +static std::vector SelectUTXOs(SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet) +{ + changeRet = 0; + + std::vector selectedUtxos; + CAmount selectedAmount = 0; + while (!utoxs.empty()) { + bool found = false; + for (auto it = utoxs.begin(); it != utoxs.end(); ++it) { + if (chainActive.Height() - it->second.first < 101) { + continue; + } + + found = true; + selectedAmount += it->second.second; + selectedUtxos.emplace_back(it->first); + utoxs.erase(it); + break; + } + BOOST_ASSERT(found); + if (selectedAmount >= amount) { + changeRet = selectedAmount - amount; + break; + } + } + + return selectedUtxos; +} + +static void FundTransaction(CMutableTransaction& tx, SimpleUTXOMap& utoxs, const CScript& scriptPayout, CAmount amount, const CKey& coinbaseKey) +{ + CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; + + CAmount change; + auto inputs = SelectUTXOs(utoxs, amount, change); + for (size_t i = 0; i < inputs.size(); i++) { + tx.vin.emplace_back(CTxIn(inputs[i])); + } + tx.vout.emplace_back(CTxOut(amount, scriptPayout)); + if (change != 0) { + tx.vout.emplace_back(CTxOut(change, scriptPayout)); + } +} + +static void SignTransaction(CMutableTransaction& tx, const CKey& coinbaseKey) +{ + CBasicKeyStore tempKeystore; + tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + + for (size_t i = 0; i < tx.vin.size(); i++) { + CTransactionRef txFrom; + uint256 hashBlock; + BOOST_ASSERT(GetTransaction(tx.vin[i].prevout.hash, txFrom, Params().GetConsensus(), hashBlock)); + bool result = SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL); + if(!result) + std::cerr << i << std::endl; + } +} + +static CMutableTransaction CreateProRegTx(SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, CKey& ownerKeyRet, CBLSSecretKey& operatorKeyRet) +{ + ownerKeyRet.MakeNewKey(true); + operatorKeyRet.MakeNewKey(); + + CAmount change; + auto inputs = SelectUTXOs(utxos, 1000 * COIN, change); + + CProRegTx proTx; + proTx.collateralOutpoint.n = 0; + proTx.addr = LookupNumeric("1.1.1.1", port); + proTx.keyIDOwner = ownerKeyRet.GetPubKey().GetID(); + proTx.pubKeyOperator = operatorKeyRet.GetPublicKey(); + proTx.keyIDVoting = ownerKeyRet.GetPubKey().GetID(); + proTx.scriptPayout = scriptPayout; + + CMutableTransaction tx; + tx.nVersion = 3; + tx.nType = TRANSACTION_PROVIDER_REGISTER; + FundTransaction(tx, utxos, scriptPayout, 1000 * COIN, coinbaseKey); + proTx.inputsHash = CalcTxInputsHash(tx); + SetTxPayload(tx, proTx); + SignTransaction(tx, coinbaseKey); + + return tx; +} + +static CScript GenerateRandomAddress() +{ + CKey key; + key.MakeNewKey(false); + return GetScriptForDestination(key.GetPubKey().GetID()); +} + +static CDeterministicMNCPtr FindPayoutDmn(const CBlock& block, CAmount &nValue) +{ + auto dmnList = deterministicMNManager->GetListAtChainTip(); + + for (const auto& txout : block.vtx[0]->vout) { + CDeterministicMNCPtr found; + dmnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + if (found == nullptr && txout.scriptPubKey == dmn->pdmnState->scriptPayout) { + found = dmn; + nValue = txout.nValue; + } + }); + if (found != nullptr) { + return found; + } + } + return nullptr; +} + +BOOST_AUTO_TEST_SUITE(firsthalving) + +BOOST_FIXTURE_TEST_CASE(devpayout, TestChainDIP3BeforeActivationSetup) +{ + Consensus::Params &consensusParams = const_cast(Params().GetConsensus()); + Consensus::Params consensusParamsBackup = consensusParams; + + // Simulate testnet (and its founders' reward) + consensusParams.chainType = Consensus::chainTestnet; + + consensusParams.nSubsidyHalvingFirst = 600; + consensusParams.nSubsidyHalvingInterval = 10; + consensusParams.nSubsidyHalvingStopBlock = 1000; + + CScript devPayoutScript = GenerateRandomAddress(); + CTxDestination devPayoutDest{CScriptID(devPayoutScript)}; + consensusParams.stage2DevelopmentFundAddress = CBitcoinAddress(devPayoutDest).ToString(); + + auto utxos = BuildSimpleUtxoMap(coinbaseTxns); + + // we're at block 498, skip to block 499 + for (int i=498; i<499; i++) + CreateAndProcessBlock({}, coinbaseKey); + + CKey ownerKey; + CBLSSecretKey operatorSecretKey; + CScript znodePayoutScript = GenerateRandomAddress(); + + auto tx = CreateProRegTx(utxos, 4444, znodePayoutScript, coinbaseKey, ownerKey, operatorSecretKey); + CreateAndProcessBlock({tx}, coinbaseKey); + deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); + + // we're at block 500, skip to 549 + for (int i=500; i<549; i++) { + CreateAndProcessBlock({}, coinbaseKey); + deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); + } + + // blocks 550 through 599 + for (int i=550; i<600; i++) { + CBlock block = CreateAndProcessBlock({}, coinbaseKey); + deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); + + CAmount nValue; + auto dmnPayout = FindPayoutDmn(block, nValue); + auto dmnExpectedPayee = deterministicMNManager->GetListAtChainTip().GetMNPayee(); + + BOOST_ASSERT(dmnPayout != nullptr); + BOOST_CHECK_EQUAL(dmnPayout->proTxHash.ToString(), dmnExpectedPayee->proTxHash.ToString()); + + CValidationState state; + BOOST_ASSERT(CheckZerocoinFoundersInputs(*block.vtx[0], state, consensusParams, chainActive.Height(), false)); + + BOOST_ASSERT(nValue == 15*COIN); // znode reward before the first halving + } + + // halving occurs at block 600 + // devs fund is valid until second block halving at block 610 + for (int i=600; i<610; i++) { + CBlock block = CreateAndProcessBlock({}, coinbaseKey); + deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); + + CAmount nValue; + auto dmnPayout = FindPayoutDmn(block, nValue); + + BOOST_ASSERT(dmnPayout != nullptr && nValue == 875*COIN/100); // 8.75 after halving (25*0.35) + + bool paymentToDevFound = false; + for (const CTxOut &txout: block.vtx[0]->vout) { + if (txout.scriptPubKey == GetScriptForDestination(CBitcoinAddress(consensusParams.stage2DevelopmentFundAddress).Get())) { + BOOST_ASSERT(txout.nValue == 375*COIN/100); // 25*0.15 + paymentToDevFound = true; + } + } + BOOST_ASSERT(paymentToDevFound); + } + + CBlock block = CreateAndProcessBlock({}, coinbaseKey); + deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); + + CAmount nValue; + auto dmnPayout = FindPayoutDmn(block, nValue); + + BOOST_ASSERT(dmnPayout != nullptr && nValue == 4375*COIN/1000); // 4.375 (12.5*0.35) + + // there should be no more payment to devs fund + for (const CTxOut &txout: block.vtx[0]->vout) { + BOOST_ASSERT(txout.scriptPubKey != GetScriptForDestination(CBitcoinAddress(consensusParams.stage2DevelopmentFundAddress).Get())); + } + + // miner's reward should be 12.5-4.375 = 8.125 + BOOST_ASSERT(block.vtx[0]->vout[0].nValue == 8125*COIN/1000); + // should be only 2 vouts in coinbase + BOOST_ASSERT(block.vtx[0]->vout.size() == 2); + + consensusParams = consensusParamsBackup; +} + +BOOST_FIXTURE_TEST_CASE(devpayoutverification, TestChainDIP3BeforeActivationSetup) +{ + Consensus::Params &consensusParams = const_cast(Params().GetConsensus()); + Consensus::Params consensusParamsBackup = consensusParams; + + consensusParams.nSubsidyHalvingFirst = 600; + consensusParams.nSubsidyHalvingInterval = 10; + consensusParams.nSubsidyHalvingStopBlock = 1000; + + // skip to block 600 + for (int i=498; i<600; i++) + CreateAndProcessBlock({}, coinbaseKey); + + // try to send dev payout to different destination + CKey key; + key.MakeNewKey(false); + consensusParams.stage2DevelopmentFundAddress = CBitcoinAddress(CTxDestination(key.GetPubKey().GetID())).ToString(); + + { + CBlock block = CreateBlock({}, coinbaseKey); + consensusParams.stage2DevelopmentFundAddress = consensusParamsBackup.stage2DevelopmentFundAddress; + + std::shared_ptr shared_pblock = std::make_shared(block); + BOOST_ASSERT(!ProcessNewBlock(Params(), shared_pblock, true, nullptr)); + } + + // now try to alter payment value + { + consensusParams.stage2DevelopmentFundShare /= 2; + CBlock block = CreateBlock({}, coinbaseKey); + consensusParams.stage2DevelopmentFundShare = consensusParamsBackup.stage2DevelopmentFundShare; + std::shared_ptr shared_pblock = std::make_shared(block); + BOOST_ASSERT(!ProcessNewBlock(Params(), shared_pblock, true, nullptr)); + } + + // now try to alter payment value + { + consensusParams.stage2DevelopmentFundShare *= 2; + CBlock block = CreateBlock({}, coinbaseKey); + consensusParams.stage2DevelopmentFundShare = consensusParamsBackup.stage2DevelopmentFundShare; + std::shared_ptr shared_pblock = std::make_shared(block); + BOOST_ASSERT(!ProcessNewBlock(Params(), shared_pblock, true, nullptr)); + } + + consensusParams = consensusParamsBackup; +} + + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/validation.cpp b/src/validation.cpp index 5bf564cf98..09a1f78d37 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1571,7 +1571,7 @@ bool ReadBlockHeaderFromDisk(CBlock &block, const CDiskBlockPos &pos) { return true; } -CAmount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams, int nTime) { +CAmount GetBlockSubsidyWithMTPFlag(int nHeight, const Consensus::Params &consensusParams, bool fMTP) { // Genesis block is 0 coin if (nHeight == 0) return 0; @@ -1588,15 +1588,23 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams, i CAmount nSubsidy = 50 * COIN; nSubsidy >>= halvings; - if (nHeight > 0 && nTime >= (int)consensusParams.nMTPSwitchTime) + if (nHeight > 0 && fMTP) nSubsidy /= consensusParams.nMTPRewardReduction; return nSubsidy; } +CAmount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams, int nTime) { + return GetBlockSubsidyWithMTPFlag(nHeight, consensusParams, nTime >= (int)consensusParams.nMTPSwitchTime); +} + CAmount GetMasternodePayment(int nHeight, CAmount blockValue) { - return blockValue*3/10; // 30% + const Consensus::Params ¶ms = Params().GetConsensus(); + if (nHeight >= params.nSubsidyHalvingFirst) + return blockValue*params.stage2ZnodeShare/100; + else + return blockValue*3/10; // 30% } bool IsInitialBlockDownload() { @@ -4061,7 +4069,21 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, const Co } } - if (!CheckZerocoinFoundersInputs(*block.vtx[0], state, consensusParams, nHeight, block.IsMTP())) { + if (nHeight >= consensusParams.nSubsidyHalvingFirst) { + if (nHeight < consensusParams.nSubsidyHalvingFirst + consensusParams.nSubsidyHalvingInterval) { + // "stage 2" interval between first and second halvings + CScript devPayoutScript = GetScriptForDestination(CBitcoinAddress(consensusParams.stage2DevelopmentFundAddress).Get()); + CAmount devPayoutValue = (GetBlockSubsidy(nHeight, consensusParams, block.nTime) * consensusParams.stage2DevelopmentFundShare) / 100; + bool found = false; + for (const CTxOut &txout: block.vtx[0]->vout) { + if ((found = txout.scriptPubKey == devPayoutScript && txout.nValue == devPayoutValue) == true) + break; + } + if (!found) + return state.Invalid(false, state.GetRejectCode(), state.GetRejectReason(), "Stage 2 developer reward check failed"); + } + } + else if (!CheckZerocoinFoundersInputs(*block.vtx[0], state, consensusParams, nHeight, block.IsMTP())) { return state.Invalid(false, state.GetRejectCode(), state.GetRejectReason(), "Founders' reward check failed"); } diff --git a/src/validation.h b/src/validation.h index 3277445f1b..151b339fc7 100644 --- a/src/validation.h +++ b/src/validation.h @@ -311,6 +311,7 @@ std::string GetWarnings(const std::string& strFor); bool GetTransaction(const uint256 &hash, CTransactionRef &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false); /** Find the best known block, and make it the tip of the block chain */ bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, std::shared_ptr pblock = std::shared_ptr()); +CAmount GetBlockSubsidyWithMTPFlag(int nHeight, const Consensus::Params& consensusParams, bool fMTP); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams, int nTime = 1475020800); CAmount GetMasternodePayment(int nHeight, CAmount blockValue); From f827f3c6dff9717a84bbefc64d753b550f4b27f0 Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Mon, 3 Aug 2020 21:27:05 +0300 Subject: [PATCH 2/3] Set address for dev fund on testnet --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 2097f51508..e5a8098ae9 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -407,7 +407,7 @@ class CTestNetParams : public CChainParams { consensus.stage2DevelopmentFundShare = 15; consensus.stage2ZnodeShare = 35; - consensus.stage2DevelopmentFundAddress = ""; // TODO: specify the address + consensus.stage2DevelopmentFundAddress = "TUuKypsbbnHHmZ2auC2BBWfaP1oTEnxjK2"; consensus.nMajorityEnforceBlockUpgrade = 51; consensus.nMajorityRejectBlockOutdated = 75; From 58b8ebf497d412c93e9da804f1d6b0be0fb11a87 Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Thu, 6 Aug 2020 19:49:57 +0300 Subject: [PATCH 3/3] Set up an address for mainnet stage 2 dev funds --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e5a8098ae9..5ab645b860 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -182,7 +182,7 @@ class CMainParams : public CChainParams { consensus.stage2DevelopmentFundShare = 15; consensus.stage2ZnodeShare = 35; - consensus.stage2DevelopmentFundAddress = ""; // TODO: specify the address + consensus.stage2DevelopmentFundAddress = "aFrAVZFr8pva5mG8XKaUH8EXcFVVNxLiuB"; consensus.nMajorityEnforceBlockUpgrade = 750; consensus.nMajorityRejectBlockOutdated = 950;