diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -3,3 +3,5 @@ This release includes the following features and fixes: + - New `minerfund` subfield of `coinbasetxn` in `getblocktemplate` to enable + easy fetching of valid addresses for infrastructure funding. diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -5,15 +5,18 @@ #include #include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -424,9 +427,17 @@ " \"coinbasevalue\" : n, (numeric) maximum allowable " "input to coinbase transaction, including the generation award and " "transaction fees (in satoshis)\n" - " \"coinbasetxn\" : { ... }, (json object) information " + " \"coinbasetxn\" : { (json object) information " "for coinbase transaction\n" - " \"target\" : \"xxxx\", (string) The hash target\n" + " \"minerfund\" : { (json object) information " + "related to the coinbase miner fund\n" + " \"addresses\" : [ ... ], (array) List of valid " + "addresses for the miner fund output\n" + " \"minimumvalue\" : n, (numeric) The minimum " + "value the miner fund output must pay\n" + " },\n" + " },\n" + " \"target\" : \"xxxx\", (string) The hash target\n" " \"mintime\" : xxx, (numeric) The minimum " "timestamp appropriate for next block time in seconds since epoch " "(Jan 1 1970 GMT)\n" @@ -456,6 +467,7 @@ .Check(request); LOCK(cs_main); + const CChainParams &chainparams = config.GetChainParams(); std::string strMode = "template"; UniValue lpval = NullUniValue; @@ -503,7 +515,7 @@ return "inconclusive-not-best-prevblk"; } BlockValidationState state; - TestBlockValidity(state, config.GetChainParams(), block, pindexPrev, + TestBlockValidity(state, chainparams, block, pindexPrev, BlockValidationOptions(config) .withCheckPoW(false) .withCheckMerkleRoot(true)); @@ -614,7 +626,7 @@ CBlock *pblock = &pblocktemplate->block; // Update nTime - UpdateTime(pblock, config.GetChainParams(), pindexPrev); + UpdateTime(pblock, chainparams, pindexPrev); pblock->nNonce = 0; UniValue aCaps(UniValue::VARR); @@ -657,6 +669,26 @@ UniValue aux(UniValue::VOBJ); aux.pushKV("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end())); + UniValue minerFundList(UniValue::VARR); + const Consensus::Params &consensusParams = chainparams.GetConsensus(); + for (auto fundDestination : + GetMinerFundWhitelist(consensusParams, pindexPrev)) { + minerFundList.push_back(EncodeCashAddr(fundDestination, chainparams)); + } + + int64_t minerFundMinValue = 0; + if (IsAxionEnabled(consensusParams, pindexPrev)) { + minerFundMinValue = + int64_t(GetMinerFundAmount(coinbasevalue) / SATOSHI); + } + + UniValue minerFund(UniValue::VOBJ); + minerFund.pushKV("addresses", minerFundList); + minerFund.pushKV("minimumvalue", minerFundMinValue); + + UniValue coinbasetxn(UniValue::VOBJ); + coinbasetxn.pushKV("minerfund", minerFund); + arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits); UniValue aMutable(UniValue::VARR); @@ -672,6 +704,7 @@ result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex()); result.pushKV("transactions", transactions); result.pushKV("coinbaseaux", aux); + result.pushKV("coinbasetxn", coinbasetxn); result.pushKV("coinbasevalue", int64_t(coinbasevalue / SATOSHI)); result.pushKV("longpollid", ::ChainActive().Tip()->GetBlockHash().GetHex() + i64tostr(nTransactionsUpdatedLast)); diff --git a/test/functional/abc-magnetic-anomaly-mining.py b/test/functional/abc-magnetic-anomaly-mining.py --- a/test/functional/abc-magnetic-anomaly-mining.py +++ b/test/functional/abc-magnetic-anomaly-mining.py @@ -98,7 +98,6 @@ tmpl = mining_node.getblocktemplate() assert 'proposal' in tmpl['capabilities'] - assert 'coinbasetxn' not in tmpl # Check the template transaction metadata and ordering last_txid = 0 diff --git a/test/functional/abc_mining_basic.py b/test/functional/abc_mining_basic.py --- a/test/functional/abc_mining_basic.py +++ b/test/functional/abc_mining_basic.py @@ -22,6 +22,7 @@ from decimal import Decimal AXION_ACTIVATION_TIME = 2000000600 +MINER_FUND_ADDR = 'bchreg:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdgd35g0pkl' class AbcMiningRPCTest(BitcoinTestFramework): @@ -36,13 +37,6 @@ node = self.nodes[0] address = node.get_deterministic_priv_key().address - # Move MTP forward to axion activation - node.setmocktime(AXION_ACTIVATION_TIME) - node.generatetoaddress(6, address) - assert_equal( - node.getblockchaininfo()['mediantime'], - AXION_ACTIVATION_TIME) - # Assert the results of getblocktemplate have expected values. Keys not # in 'expected' are not checked. def assert_getblocktemplate(expected): @@ -55,6 +49,26 @@ for key, value in expected.items(): assert_equal(blockTemplate[key], value) + # Move block time to just before axion activation + node.setmocktime(AXION_ACTIVATION_TIME) + node.generatetoaddress(5, address) + + # Before axion activation, the miner fund list is empty + assert_getblocktemplate({ + 'coinbasetxn': { + 'minerfund': { + 'addresses': [], + 'minimumvalue': 0, + }, + }, + }) + + # Move MTP forward to axion activation + node.generatetoaddress(1, address) + assert_equal( + node.getblockchaininfo()['mediantime'], + AXION_ACTIVATION_TIME) + def get_best_coinbase(): return node.getblock(node.getbestblockhash(), 2)['tx'][0] @@ -66,6 +80,14 @@ # them are covered in mining_basic.py assert_equal(node.getmempoolinfo()['size'], 0) assert_getblocktemplate({ + 'coinbasetxn': { + # We expect to start seeing the miner fund addresses since the + # next block will start enforcing them. + 'minerfund': { + 'addresses': [MINER_FUND_ADDR], + 'minimumvalue': block_reward * 8 // 100 * COIN, + }, + }, # Although the coinbase value need not necessarily be the same as # the last block due to halvings and fees, we know this to be true # since we are not crossing a halving boundary and there are no @@ -86,6 +108,12 @@ assert_equal(total, block_reward) assert_getblocktemplate({ + 'coinbasetxn': { + 'minerfund': { + 'addresses': [MINER_FUND_ADDR], + 'minimumvalue': block_reward * 8 // 100 * COIN, + }, + }, # Again, we assume the coinbase value is the same as prior blocks. 'coinbasevalue': block_reward * COIN, 'mintime': AXION_ACTIVATION_TIME + 1, @@ -95,6 +123,12 @@ node.setmocktime(AXION_ACTIVATION_TIME + 1) node.generatetoaddress(6, address) assert_getblocktemplate({ + 'coinbasetxn': { + 'minerfund': { + 'addresses': [MINER_FUND_ADDR], + 'minimumvalue': block_reward * 8 // 100 * COIN, + }, + }, 'coinbasevalue': block_reward * COIN, 'mintime': AXION_ACTIVATION_TIME + 2, }) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -90,7 +90,6 @@ tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] - assert 'coinbasetxn' not in tmpl next_height = int(tmpl["height"]) coinbase_tx = create_coinbase(height=next_height)