Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change founders' reward to development fund in coinbase tx after first halving #887

Merged
merged 3 commits into from
Aug 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 15 additions & 3 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ class CMainParams : public CChainParams {
consensus.nSubsidyHalvingInterval = 420000;
consensus.nSubsidyHalvingStopBlock = 3646849;

consensus.stage2DevelopmentFundShare = 15;
consensus.stage2ZnodeShare = 35;
consensus.stage2DevelopmentFundAddress = "aFrAVZFr8pva5mG8XKaUH8EXcFVVNxLiuB";

consensus.nMajorityEnforceBlockUpgrade = 750;
consensus.nMajorityRejectBlockOutdated = 950;
consensus.nMajorityWindow = 1000;
Expand Down Expand Up @@ -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 = "TUuKypsbbnHHmZ2auC2BBWfaP1oTEnxjK2";

consensus.nMajorityEnforceBlockUpgrade = 51;
consensus.nMajorityRejectBlockOutdated = 75;
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,8 +783,17 @@ void BlockAssembler::FillFoundersReward(CMutableTransaction &coinbaseTx, bool fM
auto &params = 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;
Expand Down
292 changes: 292 additions & 0 deletions src/test/firsthalving_tests.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/test/unit_test.hpp>

typedef std::map<COutPoint, std::pair<int, CAmount>> SimpleUTXOMap;

static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector<CTransaction>& 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<COutPoint> SelectUTXOs(SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet)
{
changeRet = 0;

std::vector<COutPoint> 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<Consensus::Params &>(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<Consensus::Params &>(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<const CBlock> shared_pblock = std::make_shared<const CBlock>(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<const CBlock> shared_pblock = std::make_shared<const CBlock>(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<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
BOOST_ASSERT(!ProcessNewBlock(Params(), shared_pblock, true, nullptr));
}

consensusParams = consensusParamsBackup;
}


BOOST_AUTO_TEST_SUITE_END()
Loading