diff --git a/src/miner.h b/src/miner.h --- a/src/miner.h +++ b/src/miner.h @@ -169,6 +169,9 @@ /** Add a tx to the block */ void AddToBlock(CTxMemPool::txiter iter); + /** Add current UTXO commitment to the coinbase if available */ + void AddUtxoCommitment(CMutableTransaction &coinbase); + // Methods for how to add transactions to a block. /** Add transactions based on tx "priority" */ void addPriorityTxs(); diff --git a/src/miner.cpp b/src/miner.cpp --- a/src/miner.cpp +++ b/src/miner.cpp @@ -35,6 +35,10 @@ static const int MAX_COINBASE_SCRIPTSIG_SIZE = 100; +// Magic for an UNVERFIED UTXO commitment. +// This should move to consensus when verifying +static const char MAGIC_UTXO_COMMITMENT[4] = {'U', 'T', 'X', '0'}; + ////////////////////////////////////////////////////////////////////////////// // // BitcoinMiner @@ -196,6 +200,9 @@ coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus()); coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; + + AddUtxoCommitment(coinbaseTx); + pblock->vtx[0] = MakeTransactionRef(coinbaseTx); pblocktemplate->vTxFees[0] = -1 * nFees; @@ -233,6 +240,30 @@ return std::move(pblocktemplate); } +void BlockAssembler::AddUtxoCommitment(CMutableTransaction &coinbase) { + CTxOut out; + uint256 hashCommit; + CUtxoCommit *commit = pcoinsTip->GetCommitment(); + if (commit == nullptr) { + return; + } + hashCommit = commit->GetHash(); + delete commit; + + out.nValue = Amount(0); + out.scriptPubKey.push_back(OP_RETURN); + out.scriptPubKey.push_back(0x24); // pushdata + // Add prefix + for (char c : MAGIC_UTXO_COMMITMENT) { + out.scriptPubKey.push_back((uint8_t)c); + } + // Add commitment + for (uint8_t c : hashCommit) { + out.scriptPubKey.push_back(c); + } + coinbase.vout.push_back(out); +} + bool BlockAssembler::isStillDependent(CTxMemPool::txiter iter) { for (CTxMemPool::txiter parent : mempool.GetMemPoolParents(iter)) { if (!inBlock.count(parent)) { diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -528,6 +528,16 @@ hashes = newhashes return uint256_from_str(hashes[0]) + # Finds a prefixed commitment in the coinbase outputs + def get_coinbase_commitment(self, prefix): + for txo in self.vtx[0].vout: + r = txo.scriptPubKey + if len(r) == 38 and r[0:6] == prefix: + h = r[6:] + return h[::-1] + + return None + def calc_merkle_root(self): hashes = [] for tx in self.vtx: diff --git a/test/functional/utxocommit.py b/test/functional/utxocommit.py --- a/test/functional/utxocommit.py +++ b/test/functional/utxocommit.py @@ -21,6 +21,9 @@ NO_COMMITMENT = '0000000000000000000000000000000000000000000000000000000000000000' +# UTXO version 0 magic +PREFIX = b"\x6a\x24UTX0" + class UtxoCommitTest(BitcoinTestFramework): def set_test_params(self): @@ -136,6 +139,12 @@ if self.is_maintaining: assert_equal(self.current_commitment, txoutsetinfo['commitment']) + def check_coinbase(self): + bhash = self.node.generate(1) + block = FromHex(CBlock(), self.node.getblock(bhash[0], False)) + expected = hex_str_to_bytes(self.current_commitment) + assert_equal(block.get_coinbase_commitment(PREFIX), expected) + def run_test(self): self.setup_blocks() @@ -162,6 +171,7 @@ self.send_blocks(self.blocks[1:21]) assert_equal(self.current_commitment, self.commitments[20]) assert(self.is_maintaining) + self.check_coinbase() # sending all headers should activate assumevalid self.send_headers(self.blocks[1:2001]) @@ -183,6 +193,7 @@ assert_equal(self.current_commitment, self.commitments[200]) assert(not (self.current_commitment == self.commitments[199])) assert(self.is_maintaining) + self.check_coinbase() # ** Node 3 self.log.info("Node 3: reorg after 175 blocks") @@ -195,6 +206,7 @@ self.send_blocks(self.blocks[150:180]) assert_equal(self.current_commitment, self.commitments[179]) assert(self.is_maintaining) + self.check_coinbase() if __name__ == '__main__': diff --git a/test/functional/utxocommit_vector.py b/test/functional/utxocommit_vector.py --- a/test/functional/utxocommit_vector.py +++ b/test/functional/utxocommit_vector.py @@ -6,7 +6,9 @@ # Test the UTXO commitment against fixed test vectors from test_framework.blocktools import (create_block, create_coinbase) -from test_framework.mininode import (COutPoint, +from test_framework.mininode import (FromHex, + CBlock, + COutPoint, CTxIn, NetworkThread, NodeConn, @@ -16,7 +18,6 @@ from test_framework.util import ( p2p_port, assert_equal, hex_str_to_bytes, bytes_to_hex_str) - # Test vectors coinbase 1,2,3 are taken from block 1,2,3 of the mainchain. # The resulting testvectors are calculated in the ECMH spec. # The txin_script is included to ensure the txid is correct @@ -38,6 +39,9 @@ } ] +# UTXO version 0 magic +PREFIX = b"\x6a\x24UTX0" + class UtxoCommitVectorTest(BitcoinTestFramework): def set_test_params(self): @@ -95,6 +99,12 @@ assert_equal(hex_str_to_bytes( txoutsetinfo['commitment']), expected) + # check coinbase of generated block + bhash = self.nodes[0].generate(1) + block = FromHex(CBlock(), self.nodes[0].getblock(bhash[0], False)) + expected = hex_str_to_bytes(coinbases[-1]['commitment_after'])[::-1] + assert_equal(block.get_coinbase_commitment(PREFIX), expected) + if __name__ == '__main__': UtxoCommitVectorTest().main()