From efd04b920bbfe9914f869a301ff897b39f48c189 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 6 Jun 2020 09:46:45 +1200 Subject: [PATCH 01/36] Implement zip-207 and zip-214. Add funding streams to consensus parameters. Add funding stream payments to coinbase txns generated by the miner. * Reduce valueBalance for shielded outputs to funding streams. * Ensure we produce binding signatures in any case where shielded outputs go to either a funding stream or the miner. --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/coinbase_funding_streams.py | 106 +++++++++++++++++ qa/rpc-tests/mining_shielded_coinbase.py | 7 +- qa/rpc-tests/test_framework/mininode.py | 3 + src/Makefile.am | 2 + src/chainparams.cpp | 40 ++++++- src/chainparams.h | 5 + src/consensus/funding.cpp | 70 ++++++++++++ src/consensus/funding.h | 37 ++++++ src/consensus/params.cpp | 126 +++++++++++++++++++-- src/consensus/params.h | 99 +++++++++++++++- src/gtest/test_foundersreward.cpp | 40 +++++++ src/init.cpp | 41 +++++++ src/main.cpp | 67 +++++++++-- src/main.h | 7 +- src/miner.cpp | 138 +++++++++++++++++------ src/rpc/mining.cpp | 1 + src/transaction_builder.h | 6 +- 18 files changed, 729 insertions(+), 67 deletions(-) create mode 100755 qa/rpc-tests/coinbase_funding_streams.py create mode 100644 src/consensus/funding.cpp create mode 100644 src/consensus/funding.h diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index b6454a304..1a330c819 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -82,6 +82,7 @@ testScripts=( 'sprout_sapling_migration.py' 'turnstile.py' 'mining_shielded_coinbase.py' + 'coinbase_funding_streams.py' 'framework.py' 'sapling_rewind_check.py' 'feature_zip221.py' diff --git a/qa/rpc-tests/coinbase_funding_streams.py b/qa/rpc-tests/coinbase_funding_streams.py new file mode 100755 index 000000000..b5c3bf7be --- /dev/null +++ b/qa/rpc-tests/coinbase_funding_streams.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Zcash developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php . + +from decimal import Decimal +from test_framework.authproxy import JSONRPCException +from test_framework.test_framework import BitcoinTestFramework +from test_framework.mininode import ( + nuparams, + fundingstream, +) +from test_framework.util import ( + assert_equal, + assert_raises, + bitcoind_processes, + connect_nodes, + initialize_chain_clean, + start_node, + wait_and_assert_operationid_status, + check_node_log, + BLOSSOM_BRANCH_ID, + HEARTWOOD_BRANCH_ID, + CANOPY_BRANCH_ID, +) + +class CoinbaseFundingStreamsTest (BitcoinTestFramework): + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def start_node_with(self, index, extra_args=[]): + args = [ + nuparams(BLOSSOM_BRANCH_ID, 1), + nuparams(HEARTWOOD_BRANCH_ID, 2), + nuparams(CANOPY_BRANCH_ID, 5), + "-nurejectoldversions=false", + ] + return start_node(index, self.options.tmpdir, args + extra_args) + + def setup_network(self, split=False): + self.nodes = [] + self.nodes.append(self.start_node_with(0)) + self.nodes.append(self.start_node_with(1)) + connect_nodes(self.nodes[1], 0) + self.is_network_split=False + self.sync_all() + + def run_test (self): + # Generate a shielded address for node 1 for miner rewards, + miner_addr = self.nodes[1].z_getnewaddress('sapling') + + # Generate a shielded address (belonging to node 0) for funding stream + # rewards. + fs_addr = self.nodes[0].z_getnewaddress('sapling') + + # Generate past heartwood activation we won't need node 1 from this + # point onward except to check miner reward balances + self.nodes[1].generate(2) + self.sync_all() + + # Restart node 0 with funding streams. + self.nodes[0].stop() + bitcoind_processes[0].wait() + self.nodes[0] = self.start_node_with(0, [ + "-mineraddress=%s" % miner_addr, + "-minetolocalwallet=0", + fundingstream(0, 5, 9, [fs_addr, fs_addr, fs_addr]), + fundingstream(1, 5, 9, [fs_addr, fs_addr, fs_addr]), + fundingstream(2, 5, 9, [fs_addr, fs_addr, fs_addr]), + ]) + connect_nodes(self.nodes[1], 0) + self.sync_all() + + print("Generate to just prior to Canopy activation") + self.nodes[0].generate(2) + self.sync_all() + + # All miner addresses belong to node 1; check balances + walletinfo = self.nodes[1].getwalletinfo() + assert_equal(walletinfo['immature_balance'], 10) + assert_equal(walletinfo['balance'], 0) + assert_equal(self.nodes[1].z_getbalance(miner_addr, 0), 10) + assert_equal(self.nodes[1].z_getbalance(miner_addr), 10) + + print("Activating Canopy") + self.nodes[0].generate(4) + self.sync_all() + + # check that miner payments made it to node 1's wallet + walletinfo = self.nodes[1].getwalletinfo() + assert_equal(walletinfo['immature_balance'], 10) + assert_equal(walletinfo['balance'], 0) + assert_equal(self.nodes[1].z_getbalance(miner_addr, 0), 30) + assert_equal(self.nodes[1].z_getbalance(miner_addr), 30) + + # check that the node 0 private balance has been augmented by the + # funding stream payments + assert_equal(self.nodes[0].z_getbalance(fs_addr, 0), 5) + assert_equal(self.nodes[0].z_getbalance(fs_addr), 5) + assert_equal(self.nodes[0].z_gettotalbalance()['private'], '5.00') + assert_equal(self.nodes[0].z_gettotalbalance()['total'], '5.00') + +if __name__ == '__main__': + CoinbaseFundingStreamsTest().main() + diff --git a/qa/rpc-tests/mining_shielded_coinbase.py b/qa/rpc-tests/mining_shielded_coinbase.py index 64b73c772..bef3493be 100755 --- a/qa/rpc-tests/mining_shielded_coinbase.py +++ b/qa/rpc-tests/mining_shielded_coinbase.py @@ -6,7 +6,10 @@ from decimal import Decimal from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework +from test_framework.mininode import nuparams from test_framework.util import ( + BLOSSOM_BRANCH_ID, + HEARTWOOD_BRANCH_ID, assert_equal, assert_raises, bitcoind_processes, @@ -25,8 +28,8 @@ class ShieldCoinbaseTest (BitcoinTestFramework): def start_node_with(self, index, extra_args=[]): args = [ - "-nuparams=2bb40e60:1", # Blossom - "-nuparams=f5b9230b:10", # Heartwood + nuparams(BLOSSOM_BRANCH_ID, 1), + nuparams(HEARTWOOD_BRANCH_ID, 10), "-nurejectoldversions=false", ] return start_node(index, self.options.tmpdir, args + extra_args) diff --git a/qa/rpc-tests/test_framework/mininode.py b/qa/rpc-tests/test_framework/mininode.py index 32eceeb79..ca55f6d9b 100755 --- a/qa/rpc-tests/test_framework/mininode.py +++ b/qa/rpc-tests/test_framework/mininode.py @@ -80,6 +80,9 @@ def hash256(s): def nuparams(branch_id, height): return '-nuparams=%x:%d' % (branch_id, height) +def fundingstream(idx, start_height, end_height, addrs): + return '-fundingstream=%d:%d:%d:%s' % (idx, start_height, end_height, ",".join(addrs)) + def ser_compactsize(n): if n < 253: return struct.pack("B", n) diff --git a/src/Makefile.am b/src/Makefile.am index 3b911af92..a86943e80 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -181,6 +181,7 @@ BITCOIN_CORE_H = \ compat/sanity.h \ compressor.h \ consensus/consensus.h \ + consensus/funding.h \ consensus/params.h \ consensus/upgrades.h \ consensus/validation.h \ @@ -413,6 +414,7 @@ libbitcoin_common_a_SOURCES = \ chainparams.cpp \ coins.cpp \ compressor.cpp \ + consensus/funding.cpp \ consensus/params.cpp \ consensus/upgrades.cpp \ core_read.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 9ecb1eb52..fdc3cae5c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -87,7 +87,7 @@ public: consensus.fCoinbaseMustBeShielded = true; consensus.nSubsidySlowStartInterval = 20000; consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL; - consensus.nPostBlossomSubsidyHalvingInterval = Consensus::POST_BLOSSOM_HALVING_INTERVAL; + consensus.nPostBlossomSubsidyHalvingInterval = POST_BLOSSOM_HALVING_INTERVAL(Consensus::PRE_BLOSSOM_HALVING_INTERVAL); consensus.nMajorityEnforceBlockUpgrade = 750; consensus.nMajorityRejectBlockOutdated = 950; consensus.nMajorityWindow = 4000; @@ -125,6 +125,8 @@ public: consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.nFundingPeriodLength = consensus.nPostBlossomSubsidyHalvingInterval / 48; + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("000000000000000000000000000000000000000000000000017e73a331fae01c"); @@ -288,7 +290,7 @@ public: consensus.fCoinbaseMustBeShielded = true; consensus.nSubsidySlowStartInterval = 20000; consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_HALVING_INTERVAL; - consensus.nPostBlossomSubsidyHalvingInterval = Consensus::POST_BLOSSOM_HALVING_INTERVAL; + consensus.nPostBlossomSubsidyHalvingInterval = POST_BLOSSOM_HALVING_INTERVAL(Consensus::PRE_BLOSSOM_HALVING_INTERVAL); consensus.nMajorityEnforceBlockUpgrade = 51; consensus.nMajorityRejectBlockOutdated = 75; consensus.nMajorityWindow = 400; @@ -330,6 +332,24 @@ public: consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.nFundingPeriodLength = consensus.nPostBlossomSubsidyHalvingInterval / 48; + + // TODO: This `if` can be removed once canopy activation height is set. + if (consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight != Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT) { + consensus.AddZIP207FundingStream( + Consensus::FS_ZIP214_ECC, + consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight, 2726400, + {/*TODO*/}); + consensus.AddZIP207FundingStream( + Consensus::FS_ZIP214_ZF, + consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight, 2726400, + {/*TODO*/}); + consensus.AddZIP207FundingStream( + Consensus::FS_ZIP214_MG, + consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight, 2726400, + {/*TODO*/}); + } + // On testnet we activate this rule 6 blocks after Blossom activation. From block 299188 and // prior to Blossom activation, the testnet minimum-difficulty threshold was 15 minutes (i.e. // a minimum difficulty block can be mined if no block is mined normally within 15 minutes): @@ -452,7 +472,7 @@ public: consensus.fCoinbaseMustBeShielded = false; consensus.nSubsidySlowStartInterval = 0; consensus.nPreBlossomSubsidyHalvingInterval = Consensus::PRE_BLOSSOM_REGTEST_HALVING_INTERVAL; - consensus.nPostBlossomSubsidyHalvingInterval = Consensus::POST_BLOSSOM_REGTEST_HALVING_INTERVAL; + consensus.nPostBlossomSubsidyHalvingInterval = POST_BLOSSOM_HALVING_INTERVAL(Consensus::PRE_BLOSSOM_REGTEST_HALVING_INTERVAL); consensus.nMajorityEnforceBlockUpgrade = 750; consensus.nMajorityRejectBlockOutdated = 950; consensus.nMajorityWindow = 1000; @@ -490,6 +510,9 @@ public: consensus.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.nFundingPeriodLength = consensus.nPostBlossomSubsidyHalvingInterval / 48; + // Defined funding streams can be enabled with node config flags. + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); @@ -553,6 +576,12 @@ public: consensus.vUpgrades[idx].nActivationHeight = nActivationHeight; } + void UpdateFundingStreamParameters(Consensus::FundingStreamIndex idx, Consensus::FundingStream fs) + { + assert(idx >= Consensus::FIRST_FUNDING_STREAM && idx < Consensus::MAX_FUNDING_STREAMS); + consensus.vFundingStreams[idx] = fs; + } + void UpdateRegtestPow(int64_t nPowMaxAdjustDown, int64_t nPowMaxAdjustUp, uint256 powLimit) { consensus.nPowMaxAdjustDown = nPowMaxAdjustDown; @@ -644,6 +673,11 @@ void UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex idx, int nActivation regTestParams.UpdateNetworkUpgradeParameters(idx, nActivationHeight); } +void UpdateFundingStreamParameters(Consensus::FundingStreamIndex idx, Consensus::FundingStream fs) +{ + regTestParams.UpdateFundingStreamParameters(idx, fs); +} + void UpdateRegtestPow(int64_t nPowMaxAdjustDown, int64_t nPowMaxAdjustUp, uint256 powLimit) { regTestParams.UpdateRegtestPow(nPowMaxAdjustDown, nPowMaxAdjustUp, powLimit); } diff --git a/src/chainparams.h b/src/chainparams.h index 7bb38a6ae..11dbacd96 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -158,4 +158,9 @@ void UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex idx, int nActivation void UpdateRegtestPow(int64_t nPowMaxAdjustDown, int64_t nPowMaxAdjustUp, uint256 powLimit); +/** + * Allows modifying the regtest funding stream parameters. + */ +void UpdateFundingStreamParameters(Consensus::FundingStreamIndex idx, Consensus::FundingStream fs); + #endif // BITCOIN_CHAINPARAMS_H diff --git a/src/consensus/funding.cpp b/src/consensus/funding.cpp new file mode 100644 index 000000000..f0a9283ee --- /dev/null +++ b/src/consensus/funding.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2020 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#include + +namespace Consensus +{ +/** + * General information about each funding stream. + * Ordered by Consensus::FundingStreamIndex. + */ +const struct FSInfo FundingStreamInfo[Consensus::MAX_FUNDING_STREAMS] = { + { + /*.recipient =*/ "Electric Coin Company", + /*.specification =*/ "https://zips.z.cash/zip-0214", + /*.valueNumerator =*/ 7, + /*.valueDenominator =*/ 100, + }, + { + /*.recipient =*/ "Zcash Foundation", + /*.specification =*/ "https://zips.z.cash/zip-0214", + /*.valueNumerator =*/ 5, + /*.valueDenominator =*/ 100, + }, + { + /*.recipient =*/ "Major Grants", + /*.specification =*/ "https://zips.z.cash/zip-0214", + /*.valueNumerator =*/ 8, + /*.valueDenominator =*/ 100, + } +}; + +CAmount FSInfo::Value(CAmount blockSubsidy) const +{ + // Integer division is floor division for nonnegative integers in C++ + return CAmount((blockSubsidy * valueNumerator) / valueDenominator); +} + +std::set GetActiveFundingStreamShares( + int nHeight, + CAmount blockSubsidy, + const Consensus::Params& params) +{ + std::set> requiredShares; + for (int idx = Consensus::FIRST_FUNDING_STREAM; idx < Consensus::MAX_FUNDING_STREAMS; idx++) { + auto fs = params.vFundingStreams[idx]; + // Funding period is [startHeight, endHeight) + if (fs && nHeight >= fs.get().GetStartHeight() && nHeight < fs.get().GetEndHeight()) { + requiredShares.insert(std::make_pair( + fs.get().RecipientAddress(params, nHeight), + FundingStreamInfo[idx].Value(blockSubsidy))); + } + } + return requiredShares; +}; + +int GetMaxFundingStreamHeight(const Consensus::Params& params) { + int result = 0; + for (int idx = Consensus::FIRST_FUNDING_STREAM; idx < Consensus::MAX_FUNDING_STREAMS; idx++) { + auto fs = params.vFundingStreams[idx]; + if (fs && result < fs.get().GetEndHeight()) { + result = fs.get().GetEndHeight(); + } + } + + return result; +} + +} // namespace Consensus diff --git a/src/consensus/funding.h b/src/consensus/funding.h new file mode 100644 index 000000000..f789e2ec4 --- /dev/null +++ b/src/consensus/funding.h @@ -0,0 +1,37 @@ +// Copyright (c) 2020 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_CONSENSUS_FUNDING_H +#define ZCASH_CONSENSUS_FUNDING_H + +#include +#include + +#include + +namespace Consensus +{ + +struct FSInfo { + std::string recipient; + std::string specification; + uint64_t valueNumerator; + uint64_t valueDenominator; + + CAmount Value(CAmount blockSubsidy) const; +}; + +extern const struct FSInfo FundingStreamInfo[]; + +typedef std::pair FundingStreamShare; + +std::set GetActiveFundingStreamShares( + int nHeight, + CAmount blockSubsidy, + const Consensus::Params& params); + +int GetMaxFundingStreamHeight(const Consensus::Params& params); +} // namespace Consensus + +#endif // ZCASH_CONSENSUS_FUNDING_H diff --git a/src/consensus/params.cpp b/src/consensus/params.cpp index 076866add..7cce1e251 100644 --- a/src/consensus/params.cpp +++ b/src/consensus/params.cpp @@ -4,6 +4,9 @@ #include "params.h" +#include +#include +#include