diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index bd6bae83d..ded51685d 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -5,6 +5,7 @@ bin_PROGRAMS += zcash-gtest zcash_gtest_SOURCES = \ gtest/main.cpp \ gtest/json_test_vectors.cpp \ + gtest/test_foundersreward.cpp \ gtest/test_jsonspirit.cpp \ gtest/test_tautology.cpp \ gtest/test_checktransaction.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 04497b6b2..94813a0c7 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -13,6 +13,8 @@ #include +#include "base58.h" + using namespace std; #include "chainparamsseeds.h" @@ -138,6 +140,23 @@ public: // (the tx=... number in the SetBestChain debug.log lines) 0 // * estimated number of transactions per day after checkpoint }; + + // Founders reward script expects a vector of 2-of-3 multisig addresses + vFoundersRewardAddress = { + "ADDRESS1", "ADDRESS2", "ADDRESS3", "ADDRESS4", + "ADDRESS5", "ADDRESS6", "ADDRESS7", "ADDRESS8", + "ADDRESS9", "ADDRESS10", "ADDRESS11", "ADDRESS12", + "ADDRESS13", "ADDRESS14", "ADDRESS15", "ADDRESS16", + "ADDRESS17", "ADDRESS18", "ADDRESS19", "ADDRESS20", + "ADDRESS21", "ADDRESS22", "ADDRESS23", "ADDRESS24", + "ADDRESS25", "ADDRESS26", "ADDRESS27", "ADDRESS28", + "ADDRESS29", "ADDRESS30", "ADDRESS31", "ADDRESS32", + "ADDRESS33", "ADDRESS34", "ADDRESS35", "ADDRESS36", + "ADDRESS37", "ADDRESS38", "ADDRESS39", "ADDRESS40", + "ADDRESS41", "ADDRESS42", "ADDRESS43", "ADDRESS44", + "ADDRESS45", "ADDRESS46", "ADDRESS47", "ADDRESS48", + }; + assert(vFoundersRewardAddress.size() < consensus.nSubsidyHalvingInterval + consensus.SubsidySlowStartShift()); } }; static CMainParams mainParams; @@ -202,6 +221,22 @@ public: 0 }; + // Founders reward script expects a vector of 2-of-3 multisig addresses + vFoundersRewardAddress = { + "2N2e2FRfP9D1dRN1oRWkH7pbFM69eGNAuQ4", "2NBW8WsA2jUussoJbRv82UXH1BYopkjYqcd", "2N1MudZmwDFTcYiLCZfrcsnhHwaSTTigbcN", "2MxfUJXWKz9D8X3mcMpVcdEJKdJ6zFukca9", + "2N8iUwMCpU16VYpKQ1HRM6xfut5FZwGwieM", "2N9hyafTvJVrykBvZDw79j1brozwZNySwPP", "2NFx7tRozsp3kT1M4w4tL9FfnEj8RovzbzN", "2NAqoH96V1RtmK72LEZpJNX1uxhJ5yejRiK", + "2MyV7hoV28KS8Uam2Z8nzY3xeo7R3T3TLUr", "2N8Tn19hMoCD4EmCwpg1V8qupVkQLVVPhav", "2NA5UeJU9zAQkSMyy3xpDcjfp4CEyKfzXKp", "2NBERNyXy46CfM9yewGeof4yzC3vkwYnhgS", + "2N7fnpAswHb4mnPm2ZjWX3eKkF8hABAYBtQ", "2N9MXGsz7uYaY5ciax6tSMDG7sjZUoLhJTC", "2N5PwzPQFFmLut2XWGQWAmpwKsF8VzUoPtr", "2MvZdDpNP8hWyEqg6zKW9B62YTJqcUwjHr5", + "2Mx4KfKJ37EDc3A43Frzof1iEjSe91JUX7d", "2NBMSdXjZ7YqREmwxEtgGryY59KBpqMSs1d", "2N9RbfE4ZCJ3Nx68vPfmvH2M6Q3qicJhagb", "2N4xwfFkFj4DR4NWNbynzP2aJmVcEFnA2DB", + "2Mx4TyAwedmsRuDkvMNYGqrcCZfQTfCvxAp", "2Mx4HSVsxEqXjLxn8igJzmCrFdG9XhnNvtf", "2MtLM4SP7LJbBZ5rA5ZG8kAVz9UNrNKuoFB", "2N7SPq83Cbmwuwv5rjNBzVd9QtJKAxxKj8M", + "2MwYkbE4U4p9XBsCrupDDkdcDH9L9xvc9Bn", "2MyaeCHpVmckokUi67YP1QK9L3Dkx3Pt86F", "2N7URNgBPXGjqnuPHiynCa6qMMhKm6YEaHr", "2N2eNwGVwj4WwbEdJg7YZDgrnYvDv1ZSNbB", + "2MuWAG6BqLM1mtZc67Fv1aKgGwkNQ2akDGt", "2N7XH82MbGwpzbc7PM2aK5CU14bSJvK7Etz", "2MuPX8Ke5TvDDQ1nkqpaPMgYWPyWbFp18Jn", "2NFBST7oK9yw9PaXaq5QhdyYwp5HpHz9m81", + "2MuSeMBUrttbjvDZAeQjTrrDeoP197qj2kG", "2N6JU8JNGGAUFknTCuLSuDEEhZJqMfFsH88", "2N4P2MrwtwbiHymQm1RASoVoiH3sFrBpmXa", "2MyhFiVXvVVxUNc8Qh9ppV7jG4NsKpnxige", + "2N5dLXUho2GtjuHMWuqixLrHLCwUMcYxd7s", "2N9NhfSiYBt3fhETFR6mQc3uxreEy7simSg", "2NBEEWPY3v38uuC7n1tMtviEY7ND2XzfgSG", "2NCWWj6oREJiMmfJ2bV5sbm1xchMwQfAZ5r", + "2N4ACsVCKMvJmtEb3Pd3xkqhJ3rLT4mYx1r", "2MtmMdabcwRJmenswaYtWA675df854KhUxD", "2N2h27Dd87eiGcm7ajvu4hJpXjTm9GkzvLZ", "2NGE19agRXU1EAK3PCLZWXERkpqyUexhk9r", + "2N63112wMnBsXTaBFjbCTjW9LuyTXQmvEdw", "2NBkHxgkYZbU56zsoLNsP5WZVfMtBK6X8WK", "2N5pK7NfKo6d9qBmsKggpwuvQeMxGf65SLH", "2N5jHzgCg9a9uAcLaT2jij8WKTZzWbVNC5c", + }; + assert(vFoundersRewardAddress.size() < consensus.nSubsidyHalvingInterval + consensus.SubsidySlowStartShift()); } }; static CTestNetParams testNetParams; @@ -259,6 +294,10 @@ public: 0, 0 }; + + // Founders reward script expects a vector of 2-of-3 multisig addresses + vFoundersRewardAddress = { "2N2e2FRfP9D1dRN1oRWkH7pbFM69eGNAuQ4" }; + assert(vFoundersRewardAddress.size() < consensus.nSubsidyHalvingInterval + consensus.SubsidySlowStartShift()); } }; static CRegTestParams regTestParams; @@ -298,3 +337,29 @@ bool SelectParamsFromCommandLine() SelectParams(network); return true; } + + +// Block height must be >0 and <=last founders reward block height +// Index variable i ranges from 0 - (vFoundersRewardAddress.size()-1) +std::string CChainParams::GetFoundersRewardAddress(int nHeight) const +{ + int maxHeight = consensus.GetLastFoundersRewardBlockHeight(); + assert(nHeight>0 && nHeight<=maxHeight); + + size_t i = (size_t)floor((double(nHeight-1)/maxHeight)*vFoundersRewardAddress.size()); + return vFoundersRewardAddress[i]; +} + +// Block height must be >0 and <=last founders reward block height +// The founders reward address is expected to be a multisig (P2SH) address +CScript CChainParams::GetFoundersRewardScript(int nHeight) const +{ + assert(nHeight > 0 && nHeight <= consensus.GetLastFoundersRewardBlockHeight()); + + CBitcoinAddress address(GetFoundersRewardAddress(nHeight).c_str()); + assert(address.IsValid()); + assert(address.IsScript()); + CScriptID scriptID = get(address.Get()); // Get() returns a boost variant + CScript script = CScript() << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL; + return script; +} diff --git a/src/chainparams.h b/src/chainparams.h index de89fa0ef..28d08a141 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -77,6 +77,9 @@ public: const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::vector& FixedSeeds() const { return vFixedSeeds; } const Checkpoints::CCheckpointData& Checkpoints() const { return checkpointData; } + /** Return the founder's reward address and script for a given block height */ + std::string GetFoundersRewardAddress(int height) const; + CScript GetFoundersRewardScript(int height) const; protected: CChainParams() {} @@ -102,6 +105,7 @@ protected: bool fMineBlocksOnDemand; bool fTestnetToBeDeprecatedFieldRPC; Checkpoints::CCheckpointData checkpointData; + std::vector vFoundersRewardAddress; }; /** diff --git a/src/consensus/params.h b/src/consensus/params.h index 5d951eef1..d3e6462b8 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -32,6 +32,9 @@ struct Params { */ int SubsidySlowStartShift() const { return nSubsidySlowStartInterval / 2; } int nSubsidyHalvingInterval; + int GetLastFoundersRewardBlockHeight() const { + return nSubsidyHalvingInterval + SubsidySlowStartShift() - 1; + } /** Used to check majorities for block version upgrade */ int nMajorityEnforceBlockUpgrade; int nMajorityRejectBlockOutdated; diff --git a/src/gtest/test_foundersreward.cpp b/src/gtest/test_foundersreward.cpp new file mode 100644 index 000000000..01a72d846 --- /dev/null +++ b/src/gtest/test_foundersreward.cpp @@ -0,0 +1,148 @@ +#include + +#include "main.h" +#include "utilmoneystr.h" +#include "chainparams.h" +#include "utilstrencodings.h" +#include "zcash/Address.hpp" +#include "wallet/wallet.h" +#include "amount.h" +#include +#include +#include +#include +#include + +// To run tests: +// ./zcash-gtest --gtest_filter="founders_reward_test.*" + +// +// Enable this test to generate and print 48 testnet 2-of-3 multisig addresses. +// The output can be copied into chainparams.cpp. +// +#if 0 +TEST(founders_reward_test, create_testnet_2of3multisig) { + ECC_Start(); + SelectParams(CBaseChainParams::TESTNET); + boost::filesystem::path temp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + const std::string path = temp.native(); + bool fFirstRun; + auto pWallet = std::make_shared(path); + ASSERT_EQ(DB_LOAD_OK, pWallet->LoadWallet(fFirstRun)); + pWallet->TopUpKeyPool(); + + int numKeys = 48; + std::vector pubkeys; + pubkeys.resize(3); + CPubKey newKey; + std::vector addresses; + for (int i=0; iGetKeyFromPool(newKey)); + pubkeys[0] = newKey; + ASSERT_TRUE(pWallet->GetKeyFromPool(newKey)); + pubkeys[1] = newKey; + ASSERT_TRUE(pWallet->GetKeyFromPool(newKey)); + pubkeys[2] = newKey; + CScript result = GetScriptForMultisig(2, pubkeys); + ASSERT_FALSE(result.size() > MAX_SCRIPT_ELEMENT_SIZE); + CScriptID innerID(result); + std::string address = CBitcoinAddress(innerID).ToString(); + addresses.push_back(address); + } + + // Print out the addresses, 4 on each line. + std::string s = "vFoundersRewardAddress = {\n"; + int i=0; + int colsPerRow = 4; + ASSERT_TRUE(numKeys % colsPerRow == 0); + int numRows = numKeys/colsPerRow; + for (int row=0; row addresses; + for (int i=1; i<=maxHeight; i++) { + addresses.insert(params.GetFoundersRewardAddress(i)); + } + ASSERT_TRUE(addresses.size()==NUM_TESTNET_FOUNDER_ADDRESSES); + +} + + +#define NUM_MAINNET_FOUNDER_ADDRESSES 48 + +TEST(founders_reward_test, mainnet) { + SelectParams(CBaseChainParams::MAIN); + CChainParams params = Params(); + + int maxHeight = params.GetConsensus().GetLastFoundersRewardBlockHeight(); + std::set addresses; + for (int i=1; i<=maxHeight; i++) { + addresses.insert(params.GetFoundersRewardAddress(i)); + } + ASSERT_TRUE(addresses.size()==NUM_MAINNET_FOUNDER_ADDRESSES); +} + + +#define NUM_REGTEST_FOUNDER_ADDRESSES 1 + +TEST(founders_reward_test, regtest) { + SelectParams(CBaseChainParams::REGTEST); + CChainParams params = Params(); + + int maxHeight = params.GetConsensus().GetLastFoundersRewardBlockHeight(); + std::set addresses; + for (int i=1; i<=maxHeight; i++) { + addresses.insert(params.GetFoundersRewardAddress(i)); + } + ASSERT_TRUE(addresses.size()==NUM_REGTEST_FOUNDER_ADDRESSES); +} + + + +// Test that 10% founders reward is fully rewarded after the first halving and slow start shift. +// On Mainnet, this would be 2,100,000 ZEC after 850,000 blocks (840,000 + 10,000). +TEST(founders_reward_test, slow_start_subsidy) { + SelectParams(CBaseChainParams::MAIN); + CChainParams params = Params(); + + int maxHeight = params.GetConsensus().GetLastFoundersRewardBlockHeight(); + CAmount totalSubsidy = 0; + for (int nHeight=1; nHeight<=maxHeight; nHeight++) { + CAmount nSubsidy = GetBlockSubsidy(nHeight, params.GetConsensus())/ 5; + totalSubsidy += nSubsidy; + } + + ASSERT_TRUE(totalSubsidy == MAX_MONEY/10.0); +} diff --git a/src/main.cpp b/src/main.cpp index d0321ff62..c6b1f1fcb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3086,11 +3086,11 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn // Coinbase transaction must include an output sending 20% of // the block reward to `FOUNDERS_REWARD_SCRIPT` until the first // subsidy halving block, with exception to the genesis block. - if ((nHeight > 0) && (nHeight < consensusParams.nSubsidyHalvingInterval)) { + if ((nHeight > 0) && (nHeight <= consensusParams.GetLastFoundersRewardBlockHeight())) { bool found = false; BOOST_FOREACH(const CTxOut& output, block.vtx[0].vout) { - if (output.scriptPubKey == ParseHex(FOUNDERS_REWARD_SCRIPT)) { + if (output.scriptPubKey == Params().GetFoundersRewardScript(nHeight)) { if (output.nValue == (GetBlockSubsidy(nHeight, consensusParams) / 5)) { found = true; break; diff --git a/src/main.h b/src/main.h index 9c875d8c3..0cb71407f 100644 --- a/src/main.h +++ b/src/main.h @@ -47,9 +47,6 @@ class CValidationState; struct CNodeStateStats; -// This is a 2-of-3 multisig P2SH -static const char *FOUNDERS_REWARD_SCRIPT = "a9146708e6670db0b950dac68031025cc5b63213a49187"; - /** Default for -blockmaxsize and -blockminsize, which control the range of sizes the mining code will create **/ static const unsigned int DEFAULT_BLOCK_MAX_SIZE = 750000; static const unsigned int DEFAULT_BLOCK_MIN_SIZE = 0; diff --git a/src/miner.cpp b/src/miner.cpp index a27210b69..0d655249c 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -335,17 +335,14 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) txNew.vout[0].scriptPubKey = scriptPubKeyIn; txNew.vout[0].nValue = GetBlockSubsidy(nHeight, chainparams.GetConsensus()); - if ((nHeight > 0) && (nHeight < chainparams.GetConsensus().nSubsidyHalvingInterval)) { + if ((nHeight > 0) && (nHeight < chainparams.GetConsensus().GetLastFoundersRewardBlockHeight())) { // Founders reward is 20% of the block subsidy auto vFoundersReward = txNew.vout[0].nValue / 5; // Take some reward away from us txNew.vout[0].nValue -= vFoundersReward; - auto rewardScript = ParseHex(FOUNDERS_REWARD_SCRIPT); - // And give it to the founders - txNew.vout.push_back(CTxOut(vFoundersReward, CScript(rewardScript.begin(), - rewardScript.end()))); + txNew.vout.push_back(CTxOut(vFoundersReward, chainparams.GetFoundersRewardScript(nHeight))); } // Add fees diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index fb6b0a565..11230acd9 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -786,7 +786,7 @@ Value getblocksubsidy(const Array& params, bool fHelp) CAmount nReward = GetBlockSubsidy(nHeight, Params().GetConsensus()); CAmount nFoundersReward = 0; - if ((nHeight > 0) && (nHeight < Params().GetConsensus().nSubsidyHalvingInterval)) { + if ((nHeight > 0) && (nHeight <= Params().GetConsensus().GetLastFoundersRewardBlockHeight())) { nFoundersReward = nReward/5; nReward -= nFoundersReward; }