diff --git a/src/miner.h b/src/miner.h --- a/src/miner.h +++ b/src/miner.h @@ -24,10 +24,19 @@ static const bool DEFAULT_PRINTPRIORITY = false; +struct CBlockTemplateEntry { + CTransactionRef tx; + Amount fees; + int64_t sigOpCount; + + CBlockTemplateEntry(CTransactionRef tx, Amount fees, int64_t sigOpCount) + : tx(tx), fees(fees), sigOpCount(sigOpCount){}; +}; + struct CBlockTemplate { CBlock block; - std::vector vTxFees; - std::vector vTxSigOpsCount; + + std::vector entries; }; // Container for tracking updates to ancestor feerate as we include (parent) diff --git a/src/miner.cpp b/src/miner.cpp --- a/src/miner.cpp +++ b/src/miner.cpp @@ -32,9 +32,6 @@ #include #include -#include -#include - ////////////////////////////////////////////////////////////////////////////// // // BitcoinMiner @@ -142,12 +139,8 @@ // Pointer for convenience. pblock = &pblocktemplate->block; - // Add dummy coinbase tx as first transaction. - pblock->vtx.emplace_back(); - // updated at end - pblocktemplate->vTxFees.push_back(-SATOSHI); - // updated at end - pblocktemplate->vTxSigOpsCount.push_back(-1); + // Add dummy coinbase tx as first transaction. It is updated at the end. + pblocktemplate->entries.emplace_back(CTransactionRef(), -SATOSHI, -1); LOCK2(cs_main, mpool->cs); CBlockIndex *pindexPrev = chainActive.Tip(); @@ -179,11 +172,10 @@ if (IsMagneticAnomalyEnabled(*config, pindexPrev)) { // If magnetic anomaly is enabled, we make sure transaction are // canonically ordered. - std::sort(std::begin(pblock->vtx) + 1, std::end(pblock->vtx), - [](const std::shared_ptr &a, - const std::shared_ptr &b) -> bool { - return a->GetId() < b->GetId(); - }); + std::sort(std::begin(pblocktemplate->entries) + 1, + std::end(pblocktemplate->entries), + [](const CBlockTemplateEntry &a, const CBlockTemplateEntry &b) + -> bool { return a.tx->GetId() < b.tx->GetId(); }); } int64_t nTime1 = GetTimeMicros(); @@ -209,8 +201,8 @@ << std::vector(MIN_TX_SIZE - coinbaseSize - 1); } - pblock->vtx[0] = MakeTransactionRef(coinbaseTx); - pblocktemplate->vTxFees[0] = -1 * nFees; + pblocktemplate->entries[0].tx = MakeTransactionRef(coinbaseTx); + pblocktemplate->entries[0].fees = -1 * nFees; uint64_t nSerializeSize = GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION); @@ -223,8 +215,13 @@ UpdateTime(pblock, *config, pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, *config); pblock->nNonce = 0; - pblocktemplate->vTxSigOpsCount[0] = GetSigOpCountWithoutP2SH( - *pblock->vtx[0], STANDARD_CHECKDATASIG_VERIFY_FLAGS); + pblocktemplate->entries[0].sigOpCount = GetSigOpCountWithoutP2SH( + *pblocktemplate->entries[0].tx, STANDARD_CHECKDATASIG_VERIFY_FLAGS); + + // Copy all the transactions into the block + for (const CBlockTemplateEntry &tx : pblocktemplate->entries) { + pblock->vtx.push_back(tx.tx); + } CValidationState state; BlockValidationOptions validationOptions(false, false); @@ -351,9 +348,8 @@ } void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) { - pblock->vtx.emplace_back(iter->GetSharedTx()); - pblocktemplate->vTxFees.push_back(iter->GetFee()); - pblocktemplate->vTxSigOpsCount.push_back(iter->GetSigOpCount()); + pblocktemplate->entries.push_back(CBlockTemplateEntry( + iter->GetSharedTx(), iter->GetFee(), iter->GetSigOpCount())); nBlockSize += iter->GetTxSize(); ++nBlockTx; nBlockSigOps += iter->GetSigOpCount(); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -633,9 +633,10 @@ entry.pushKV("depends", deps); int index_in_template = i - 1; - entry.push_back( - Pair("fee", pblocktemplate->vTxFees[index_in_template] / SATOSHI)); - int64_t nTxSigOps = pblocktemplate->vTxSigOpsCount[index_in_template]; + entry.push_back(Pair( + "fee", pblocktemplate->entries[index_in_template].fees / SATOSHI)); + int64_t nTxSigOps = + pblocktemplate->entries[index_in_template].sigOpCount; entry.pushKV("sigops", nTxSigOps); transactions.push_back(entry); diff --git a/test/functional/abc-magnetic-anomaly-mining.py b/test/functional/abc-magnetic-anomaly-mining.py new file mode 100755 --- /dev/null +++ b/test/functional/abc-magnetic-anomaly-mining.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test mining RPCs + +- getblocktemplate proposal mode +- submitblock""" + +import time +import random +import decimal +from binascii import b2a_hex +import copy + +from test_framework.blocktools import create_coinbase +from test_framework.test_framework import BitcoinTestFramework +from test_framework.mininode import CBlock, CTransaction, FromHex, uint256_from_str +from test_framework.util import * + +RPC_VERIFY_REJECTED = -26 + + +def b2x(b): + return b2a_hex(b).decode('ascii') + + +def assert_template(node, block, expect, rehash=True): + if rehash: + block.hashMerkleRoot = block.calc_merkle_root() + rsp = node.getblocktemplate( + {'data': b2x(block.serialize()), 'mode': 'proposal'}) + assert_equal(rsp, expect) + + +class CTORMiningTest(BitcoinTestFramework): + def set_test_params(self): + # Setup two nodes so we can getblocktemplate + # it errors out if it is not connected to other nodes + self.num_nodes = 2 + self.setup_clean_chain = True + self.block_heights = {} + self.tip = None + self.blocks = {} + self.mocktime = int(time.time()) - 600 * 100 + + extra_arg = ['-spendzeroconfchange=0', '-whitelist=127.0.0.1', + "-magneticanomalyactivationtime=%d" % self.mocktime, + "-replayprotectionactivationtime=%d" % (10 * self.mocktime)] + self.extra_args = [extra_arg, extra_arg] + + def run_test(self): + mnode = self.nodes[0] + + # Helper for updating the times + def update_time(): + mnode.setmocktime(self.mocktime) + self.mocktime = self.mocktime + 600 + + mnode.getnewaddress() + + # Generate some unspent utxos and also + # activate magnetic anomaly + for x in range(150): + update_time() + mnode.generate(1) + + update_time() + unspent = mnode.listunspent() + + transactions = {} + # Spend all our coinbases + while len(unspent): + inputs = [] + # Grab a random number of inputs + for _ in range(random.randrange(1, 5)): + txin = unspent.pop() + inputs.append({ + 'txid': txin['txid'], + 'vout': 0 # This is a coinbase + }) + if len(unspent) == 0: + break + + outputs = {} + # Calculate a unique fee for this transaction + fee = decimal.Decimal(random.randint( + 1000, 2000)) / decimal.Decimal(1e8) + # Spend to the same number of outputs as inputs, so we can leave + # the amounts unchanged and avoid rounding errors. + addr = "" + for _ in range(len(inputs)): + addr = mnode.getnewaddress() + output = { + # 50 BCH per coinbase + addr: decimal.Decimal(50) + } + outputs.update(output) + + # Take the fee off the last output to avoid rounding errors we + # need the exact fee later for assertions + outputs[addr] -= fee + + rawtx = mnode.createrawtransaction(inputs, outputs) + signedtx = mnode.signrawtransaction(rawtx) + txid = mnode.sendrawtransaction(signedtx['hex']) + # number of inputs is the same as the number of sigops in this + # case + transactions.update({txid: {'fee': fee, 'sigops': len(outputs)}}) + + tmpl = mnode.getblocktemplate() + assert 'proposal' in tmpl['capabilities'] + assert 'coinbasetxn' not in tmpl + + # Check the template transaction metadata and ordering + last_txid = 0 + for txn in tmpl['transactions'][1:]: + txid = txn['txid'] + txnMetadata = transactions[txid] + expectedFeeSats = int(txnMetadata['fee'] * 10**8) + expectedSigOps = txnMetadata['sigops'] + + txid_decoded = int(txid, 16) + + # Assert we got the expected metadata + assert(expectedFeeSats == txn['fee']) + assert(expectedSigOps == txn['sigops']) + # Asser transaciton ids are in order + assert(last_txid == 0 or last_txid < txid_decoded) + last_txid = txid_decoded + + +if __name__ == '__main__': + CTORMiningTest().main()