From 8c8c12398e6a6777641aea8eb5f24dd01e4b3516 Mon Sep 17 00:00:00 2001 From: Brannon King Date: Tue, 2 Mar 2021 23:44:11 -0500 Subject: [PATCH] in progress on auxpow support --- .travis.yml | 15 +- configure.ac | 4 +- src/Makefile.am | 4 + src/Makefile.test.include | 1 + src/auxpow.cpp | 237 +++++++++++++++ src/auxpow.h | 204 +++++++++++++ src/chain.cpp | 68 +++++ src/chain.h | 22 +- src/chainparams.cpp | 6 + src/consensus/params.h | 2 + src/miner.cpp | 4 +- src/primitives/block.cpp | 44 ++- src/primitives/block.h | 91 +++--- src/primitives/pureheader.cpp | 22 ++ src/primitives/pureheader.h | 65 ++++ src/rpc/blockchain.cpp | 44 ++- src/rpc/mining.cpp | 185 ++++++++++++ src/rpc/rawtransaction.cpp | 2 +- src/rpc/rawtransaction.h | 2 + src/test/auxpow_tests.cpp | 445 ++++++++++++++++++++++++++++ src/test/validation_block_tests.cpp | 3 +- src/validation.cpp | 36 ++- 22 files changed, 1428 insertions(+), 78 deletions(-) create mode 100644 src/auxpow.cpp create mode 100644 src/auxpow.h create mode 100644 src/primitives/pureheader.cpp create mode 100644 src/primitives/pureheader.h create mode 100644 src/test/auxpow_tests.cpp diff --git a/.travis.yml b/.travis.yml index e46781e58..e35cbf8f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ jobs: - &build-template stage: build name: linux - env: NAME=linux EXT= + env: NAME=linux DOCKER_IMAGE=lbry/build_lbrycrd_gcc EXT= os: linux dist: xenial language: minimal @@ -22,8 +22,9 @@ jobs: - docker install: - mkdir -p ${HOME}/ccache - - docker pull $DOCKER_BUILD_IMAGE + - docker pull $DOCKER_IMAGE script: + - echo "build..." - docker run -v "$(pwd):/lbrycrd" -v "${HOME}/ccache:/ccache" -w /lbrycrd -e CCACHE_DIR=/ccache ${DOCKER_IMAGE} packaging/build_${NAME}_64bit.sh before_deploy: - mkdir -p dist @@ -47,11 +48,11 @@ jobs: - <<: *build-template name: windows - env: NAME=windows EXT=.exe + env: NAME=windows DOCKER_IMAGE=lbry/build_lbrycrd EXT=.exe - <<: *build-template name: osx - env: NAME=darwin EXT= + env: NAME=darwin DOCKER_IMAGE=lbry/build_lbrycrd EXT= before_install: - mkdir -p ./depends/SDKs && pushd depends/SDKs && curl -C - ${MAC_OS_SDK} | tar --skip-old-files -xJ && popd @@ -62,7 +63,7 @@ jobs: dist: xenial language: minimal git: - depth: 3 + clone: false install: - mkdir -p testrun && cd testrun - curl http://build.lbry.io/lbrycrd/${TRAVIS_BRANCH}/lbrycrd-${NAME}-test.zip -o temp.zip @@ -77,8 +78,8 @@ jobs: services: - docker script: - - docker pull $DOCKER_WINE_IMAGE - - docker run -v "$(pwd):/test" -e "WINEDEBUG=-all" -e "TRIEHASH_FUZZER_BLOCKS=1000" -it $DOCKER_WINE_IMAGE wine "/test/test_lbrycrd.exe" + - docker pull lbry/wine + - docker run -v "$(pwd):/test" -e "WINEDEBUG=-all" -e "TRIEHASH_FUZZER_BLOCKS=1000" -it lbry/wine wine "/test/test_lbrycrd.exe" - <<: *test-template os: osx diff --git a/configure.ac b/configure.ac index 255c7e9d2..1daad77fc 100644 --- a/configure.ac +++ b/configure.ac @@ -3,9 +3,9 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 17) define(_CLIENT_VERSION_REVISION, 3) -define(_CLIENT_VERSION_BUILD, 2) +define(_CLIENT_VERSION_BUILD, 3) define(_CLIENT_VERSION_IS_RELEASE, true) -define(_COPYRIGHT_YEAR, 2019) +define(_COPYRIGHT_YEAR, 2021) define(_COPYRIGHT_HOLDERS,[The %s developers]) define(_COPYRIGHT_HOLDERS_SUBSTITUTION,[[LBRYcrd Core]]) AC_INIT([LBRYcrd Core],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_REVISION],[https://github.com/lbryio/lbrycrd/issues],[lbrycrd],[https://lbry.com/]) diff --git a/src/Makefile.am b/src/Makefile.am index a030b6dc4..843afa7cb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -92,6 +92,7 @@ endif BITCOIN_CORE_H = \ addrdb.h \ addrman.h \ + auxpow.h \ base58.h \ bech32.h \ bloom.h \ @@ -225,6 +226,7 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrdb.cpp \ addrman.cpp \ + auxpow.cpp \ bloom.cpp \ blockencodings.cpp \ chain.cpp \ @@ -366,6 +368,8 @@ libbitcoin_consensus_a_SOURCES = \ prevector.h \ primitives/block.cpp \ primitives/block.h \ + primitives/pureheader.cpp \ + primitives/pureheader.h \ primitives/transaction.cpp \ primitives/transaction.h \ pubkey.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 88ed87510..44e12416a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -33,6 +33,7 @@ BITCOIN_TESTS =\ test/addrman_tests.cpp \ test/amount_tests.cpp \ test/allocator_tests.cpp \ + test/auxpow_tests.cpp \ test/base32_tests.cpp \ test/base58_tests.cpp \ test/base64_tests.cpp \ diff --git a/src/auxpow.cpp b/src/auxpow.cpp new file mode 100644 index 000000000..486d2ce44 --- /dev/null +++ b/src/auxpow.cpp @@ -0,0 +1,237 @@ +// Copyright(c) 2009-2010 Satoshi Nakamoto +// Copyright(c) 2011 Vince Durham +// Copyright(c) 2009-2014 The Bitcoin developers +// Copyright(c) 2014-2016 Daniel Kraft +// Distributed under the MIT/X11 software license, see the accompanying +// file license.txt or http://www.opensource.org/licenses/mit-license.php. + +#include "auxpow.h" + +#include "chainparams.h" +#include "compat/endian.h" +#include "consensus/consensus.h" +#include "consensus/merkle.h" +#include "consensus/validation.h" +#include "hash.h" +#include "script/script.h" +#include "txmempool.h" +#include "util.h" +#include "utilstrencodings.h" +#include "validation.h" + +#include + +/* Moved from wallet.cpp. CMerkleTx is necessary for auxpow, independent + of an enabled(or disabled) wallet. Always include the code. */ + +const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); + +void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) +{ + // Update the tx's hashBlock + hashBlock = pindex->GetBlockHash(); + + // set the position of the transaction in the block + nIndex = posInBlock; +} + +static std::vector BlockMerkleBranch(const CBlock& block, uint32_t position); +void CMerkleTx::InitMerkleBranch(const CBlock& block, int posInBlock) +{ + hashBlock = block.GetHash(); + nIndex = posInBlock; + vMerkleBranch = BlockMerkleBranch(block, nIndex); +} + +int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const +{ + if(hashUnset()) + return 0; + + AssertLockHeld(cs_main); + + // Find the block it claims to be in + BlockMap::iterator mi = mapBlockIndex.find(hashBlock); + if(mi == mapBlockIndex.end()) + return 0; + CBlockIndex* pindex =(*mi).second; + if(!pindex || !chainActive.Contains(pindex)) + return 0; + + pindexRet = pindex; + return((nIndex == -1) ?(-1) : 1) *(chainActive.Height() - pindex->nHeight + 1); +} + +int CMerkleTx::GetBlocksToMaturity() const +{ + if(!IsCoinBase()) + return 0; + return std::max(0,(COINBASE_MATURITY + 1) - GetDepthInMainChain()); +} + + +bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) +{ + bool missing = true; + return ::AcceptToMemoryPool(mempool, state, tx, &missing, NULL, NULL, false, nAbsurdFee); +} + +/* ************************************************************************** */ + +bool +CAuxPow::check(const uint256& hashAuxBlock, const Consensus::Params& params) const +{ + if(nIndex != 0) + return error("AuxPow is not a generate"); + + if(nNonce != params.nAuxPoWChainID) + return error("Aux POW parent has our chain ID"); + + if(vChainMerkleBranch.size() > 30) + return error("Aux POW chain merkle branch too long"); + + // Check that the chain merkle root is in the coinbase + const uint256 nRootHash + = CheckMerkleBranch(hashAuxBlock, vChainMerkleBranch, nChainIndex); + std::vector vchRootHash(nRootHash.begin(), nRootHash.end()); + std::reverse(vchRootHash.begin(), vchRootHash.end()); // correct endian + + // Check that we are in the parent block merkle tree + if(CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != parentBlock.hashMerkleRoot) + return error("Aux POW merkle root incorrect"); + + const CScript script = tx->vin[0].scriptSig; + + // Check that the same work is not submitted twice to our chain. + + CScript::const_iterator pcHead = + std::search(script.begin(), script.end(), UBEGIN(pchMergedMiningHeader), UEND(pchMergedMiningHeader)); + + CScript::const_iterator pc = + std::search(script.begin(), script.end(), vchRootHash.begin(), vchRootHash.end()); + + if(pc == script.end()) + return error("Aux POW missing chain merkle root in parent coinbase"); + + if(pcHead != script.end()) + { + // Enforce only one chain merkle root by checking that a single instance of the merged + // mining header exists just before. + if(script.end() != std::search(pcHead + 1, script.end(), UBEGIN(pchMergedMiningHeader), UEND(pchMergedMiningHeader))) + return error("Multiple merged mining headers in coinbase"); + if(pcHead + sizeof(pchMergedMiningHeader) != pc) + return error("Merged mining header is not just before chain merkle root"); + } + else + { + // For backward compatibility. + // Enforce only one chain merkle root by checking that it starts early in the coinbase. + // 8-12 bytes are enough to encode extraNonce and nBits. + if(pc - script.begin() > 20) + return error("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase"); + } + + + // Ensure we are at a deterministic point in the merkle leaves by hashing + // a nonce and our chain ID and comparing to the index. + pc += vchRootHash.size(); + if(script.end() - pc < 8) + return error("Aux POW missing chain merkle tree size and nonce in parent coinbase"); + + uint32_t nSize; + memcpy(&nSize, &pc[0], 4); + nSize = le32toh(nSize); + const unsigned merkleHeight = vChainMerkleBranch.size(); + if(nSize !=(1u << merkleHeight)) + return error("Aux POW merkle branch size does not match parent coinbase"); + + uint32_t nNonce; + memcpy(&nNonce, &pc[4], 4); + nNonce = le32toh(nNonce); + if(nChainIndex != getExpectedIndex(nNonce, nChainId, merkleHeight)) + return error("Aux POW wrong index"); + + return true; +} + +int +CAuxPow::getExpectedIndex(uint32_t nNonce, int nChainId, unsigned h) +{ + // Choose a pseudo-random slot in the chain merkle tree + // but have it be fixed for a size/nonce/chain combination. + // + // This prevents the same work from being used twice for the + // same chain while reducing the chance that two chains clash + // for the same slot. + + /* This computation can overflow the uint32 used. This is not an issue, + though, since we take the mod against a power-of-two in the end anyway. + This also ensures that the computation is, actually, consistent + even if done in 64 bits as it was in the past on some systems. + Note that h is always <= 30(enforced by the maximum allowed chain + merkle branch length), so that 32 bits are enough for the computation. */ + + uint32_t rand = nNonce; + rand = rand * 1103515245 + 12345; + rand += nChainId; + rand = rand * 1103515245 + 12345; + + return rand %(1 << h); +} + +uint256 +CAuxPow::CheckMerkleBranch(uint256 hash, + const std::vector& vMerkleBranch, + int nIndex) +{ + if(nIndex == -1) + return uint256(); + for(std::vector::const_iterator it(vMerkleBranch.begin()); + it != vMerkleBranch.end(); ++it) + { + if(nIndex & 1) + hash = Hash(BEGIN(*it), END(*it), BEGIN(hash), END(hash)); + else + hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it)); + nIndex >>= 1; + } + return hash; +} + +void +CAuxPow::initAuxPow(CBlockHeader& header) +{ + /* Set auxpow flag right now, since we take the block hash below. */ + header.SetAuxpowFlag(true); + + /* Build a minimal coinbase script input for merge-mining. */ + const uint256 blockHash = header.GetHash(); + std::vector inputData(blockHash.begin(), blockHash.end()); + std::reverse(inputData.begin(), inputData.end()); + inputData.push_back(1); + inputData.insert(inputData.end(), 7, 0); + + /* Fake a parent-block coinbase with just the required input + script and no outputs. */ + CMutableTransaction coinbase; + coinbase.vin.resize(1); + coinbase.vin[0].prevout.SetNull(); + coinbase.vin[0].scriptSig =(CScript() << inputData); + assert(coinbase.vout.empty()); + CTransactionRef coinbaseRef = MakeTransactionRef(coinbase); + + /* Build a fake parent block with the coinbase. */ + CBlock parent; + parent.nVersion = 1; + parent.vtx.resize(1); + parent.vtx[0] = coinbaseRef; + parent.hashMerkleRoot = BlockMerkleRoot(parent); + + /* Construct the auxpow object. */ + header.SetAuxpow(new CAuxPow(coinbaseRef)); + assert(header.auxpow->vChainMerkleBranch.empty()); + header.auxpow->nChainIndex = 0; + assert(header.auxpow->vMerkleBranch.empty()); + header.auxpow->nIndex = 0; + header.auxpow->parentBlock = parent; +} \ No newline at end of file diff --git a/src/auxpow.h b/src/auxpow.h new file mode 100644 index 000000000..6a8a802cf --- /dev/null +++ b/src/auxpow.h @@ -0,0 +1,204 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2014 The Bitcoin developers +// Copyright (c) 2014-2016 Daniel Kraft +// Distributed under the MIT/X11 software license, see the accompanying +// file license.txt or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_AUXPOW_H +#define BITCOIN_AUXPOW_H + +#include "consensus/params.h" +#include "consensus/validation.h" +#include "primitives/pureheader.h" +#include "primitives/transaction.h" +#include "serialize.h" +#include "uint256.h" + +#include + +class CBlock; +class CBlockHeader; +class CBlockIndex; + +/** Header for merge-mining data in the coinbase. */ +static const unsigned char pchMergedMiningHeader[] = { 0xfa, 0xbe, 'm', 'm' }; + +/* Because it is needed for auxpow, the definition of CMerkleTx is moved + here from wallet.h. */ + +/** A transaction with a merkle branch linking it to the block chain. */ +class CMerkleTx +{ +private: + /** Constant used in hashBlock to indicate tx has been abandoned */ + static const uint256 ABANDON_HASH; + +public: + CTransactionRef tx; + uint256 hashBlock; + std::vector vMerkleBranch; + + /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest + * block in the chain we know this or any in-wallet dependency conflicts + * with. Older clients interpret nIndex == -1 as unconfirmed for backward + * compatibility. + */ + int nIndex = -1; + + CMerkleTx() + { + SetTx(MakeTransactionRef()); + } + + CMerkleTx(CTransactionRef arg) + { + SetTx(std::move(arg)); + } + + /** Helper conversion operator to allow passing CMerkleTx where CTransaction is expected. + * TODO: adapt callers and remove this operator. */ + operator const CTransaction&() const { return *tx; } + + void SetTx(CTransactionRef arg) + { + tx = std::move(arg); + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(tx); + READWRITE(hashBlock); + READWRITE(vMerkleBranch); + READWRITE(nIndex); + } + + /** + * Actually compute the Merkle branch. This is used for unit tests when + * constructing an auxpow. It is not needed for actual production, since + * we do not care in the lbrycrd client how the auxpow is constructed + * by a miner. + */ + void InitMerkleBranch(const CBlock& block, int posInBlock); + + void SetMerkleBranch(const CBlockIndex* pindex, int posInBlock); + + /** + * Return depth of transaction in blockchain: + * <0 : conflicts with a transaction this deep in the blockchain + * 0 : in memory pool, waiting to be included in a block + * >=1 : this many blocks deep in the main chain + */ + int GetDepthInMainChain(const CBlockIndex* &pindexRet) const; + int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } + bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; } + int GetBlocksToMaturity() const; + /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ + bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state); + bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } + bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } + void setAbandoned() { hashBlock = ABANDON_HASH; } + + const uint256& GetHash() const { return tx->GetHash(); } + bool IsCoinBase() const { return tx->IsCoinBase(); } +}; + +/** + * Data for the merge-mining auxpow. This is a merkle tx (the parent block's + * coinbase tx) that can be verified to be in the parent block, and this + * transaction's input (the coinbase script) contains the reference + * to the actual merge-mined block. + */ +class CAuxPow : public CMerkleTx +{ + +/* Public for the unit tests. */ +public: + + /** The merkle branch connecting the aux block to our coinbase. */ + std::vector vChainMerkleBranch; + + /** Merkle tree index of the aux block header in the coinbase. */ + int nChainIndex; + + /** Parent block header (on which the real PoW is done). */ + CPureBlockHeader parentBlock; + +public: + + /* Prevent accidental conversion. */ + inline explicit CAuxPow(CTransactionRef txIn) + : CMerkleTx(txIn) + { + } + + inline CAuxPow() + : CMerkleTx() + { + } + + ADD_SERIALIZE_METHODS; + + template + inline void + SerializationOp (Stream& s, Operation ser_action) + { + READWRITE (*static_cast (this)); + READWRITE (vChainMerkleBranch); + READWRITE (nChainIndex); + READWRITE (parentBlock); + } + + /** + * Check the auxpow, given the merge-mined block's hash and our chain ID. + * Note that this does not verify the actual PoW on the parent block! It + * just confirms that all the merkle branches are valid. + * @param hashAuxBlock Hash of the merge-mined block. + * @param nChainId The auxpow chain ID of the block to check. + * @param params Consensus parameters. + * @return True if the auxpow is valid. + */ + bool check(const uint256& hashAuxBlock, const Consensus::Params& params) const; + + /** + * Get the parent block's hash. This is used to verify that it + * satisfies the PoW requirement. + * @return The parent block hash. + */ + inline uint256 + getParentBlockPoWHash() const + { + return parentBlock.GetPoWHash(); + } + + /** + * Calculate the expected index in the merkle tree. This is also used + * for the test-suite. + * @param nNonce The coinbase's nonce value. + * @param nChainId The chain ID. + * @param h The merkle block height. + * @return The expected index for the aux hash. + */ + static int getExpectedIndex(uint32_t nNonce, int nChainId, unsigned h); + + /** + * Check a merkle branch. This used to be in CBlock, but was removed + * upstream. Thus include it here now. + */ + static uint256 CheckMerkleBranch(uint256 hash, + const std::vector& vMerkleBranch, + int nIndex); + + /** + * Initialise the auxpow of the given block header. This constructs + * a minimal CAuxPow object with a minimal parent block and sets + * it on the block header. The auxpow is not necessarily valid, but + * can be "mined" to make it valid. + * @param header The header to set the auxpow on. + */ + static void initAuxPow(CBlockHeader& header); + +}; + +#endif // BITCOIN_AUXPOW_H \ No newline at end of file diff --git a/src/chain.cpp b/src/chain.cpp index d462f94ab..175862a27 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include /** * CChain implementation @@ -118,6 +119,73 @@ void CBlockIndex::BuildSkip() pskip = pprev->GetAncestor(GetSkipHeight(nHeight)); } +/* Generic implementation of block reading that can handle + both a block and its header. */ + +template +static bool ReadBlockOrHeader(T& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams, bool fCheckPOW) +{ + block.SetNull(); + + // Open history file to read + CAutoFile filein( OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION); + if (filein.IsNull()) + return error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString()); + + // Read block + try { + filein >> block; + } + catch (const std::exception& e) { + return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString()); + } + + // Check the header + if (fCheckPOW && !CheckAuxPowProofOfWork(block, consensusParams)) + return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); + + return true; +} + +template +static bool ReadBlockOrHeader(T& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams, bool fCheckPOW) +{ + if (!ReadBlockOrHeader(block, pindex->GetBlockPos(), consensusParams, fCheckPOW)) + return false; + if (block.GetHash() != pindex->GetBlockHash()) + return error("ReadBlockOrHeader(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s", + pindex->ToString(), pindex->GetBlockPos().ToString()); + return true; +} + +bool ReadBlockHeaderFromDisk(CBlockHeader& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams, bool fCheckPOW=false) +{ + return ReadBlockOrHeader(block, pindex, consensusParams, fCheckPOW); +} + +CBlockHeader CBlockIndex::GetBlockHeader(const Consensus::Params& consensusParams) const { + CBlockHeader block; + block.nVersion = nVersion; + if (pprev) + block.hashPrevBlock = pprev->GetBlockHash(); + + /* The CBlockIndex object's block header is missing the auxpow. + So if this is an auxpow block, read it from disk instead. We only + have to read the actual *header*, not the full block. */ + if (block.IsAuxpow()) + { + ReadBlockHeaderFromDisk(block, this, consensusParams); + return block; + } + + block.hashMerkleRoot = hashMerkleRoot; + block.hashClaimTrie = hashClaimTrie; + block.nTime = nTime; + block.nBits = nBits; + block.nNonce = nNonce; + return block; +} + arith_uint256 GetBlockProof(const CBlockIndex& block) { arith_uint256 bnTarget; diff --git a/src/chain.h b/src/chain.h index 9aa305444..8a927dd4a 100644 --- a/src/chain.h +++ b/src/chain.h @@ -7,6 +7,7 @@ #define BITCOIN_CHAIN_H #include +#include #include #include #include @@ -279,18 +280,15 @@ public: return ret; } - CBlockHeader GetBlockHeader() const + CBlockHeader GetBlockHeader(const Consensus::Params& consensusParams) const; + + /** + * Check if the auxpow flag is set in the version. + * @return True if this block version is marked as auxpow. + */ + inline bool IsAuxpow() const { - CBlockHeader block; - block.nVersion = nVersion; - if (pprev) - block.hashPrevBlock = pprev->GetBlockHash(); - block.hashMerkleRoot = hashMerkleRoot; - block.hashClaimTrie = hashClaimTrie; - block.nTime = nTime; - block.nBits = nBits; - block.nNonce = nNonce; - return block; + return nVersion & CPureBlockHeader::VERSION_AUXPOW; } uint256 GetBlockHash() const @@ -300,7 +298,7 @@ public: uint256 GetBlockPoWHash() const { - return GetBlockHeader().GetPoWHash(); + return GetBlockHeader(Params().GetConsensus()).GetPoWHash(); } int64_t GetBlockTime() const diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 672ee7d29..06fe4bb0f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -147,6 +147,8 @@ public: consensus.nMaxTakeoverWorkaroundHeight = 658300; // targeting 30 Oct 2019 consensus.nWitnessForkHeight = 680770; // targeting 11 Dec 2019 consensus.nAllClaimsInMerkleForkHeight = 658310; // targeting 30 Oct 2019 + consensus.nAuxPoWStartHeight = 1000000; + consensus.nAuxPoWChainID = 0x4C425259; // LBRY consensus.fPowAllowMinDifficultyBlocks = false; consensus.fPowNoRetargeting = false; consensus.nRuleChangeActivationThreshold = 1916; // 95% of a half week @@ -265,6 +267,8 @@ public: consensus.nMaxTakeoverWorkaroundHeight = 1198550; // targeting 30 Sep 2019 consensus.nWitnessForkHeight = 1198600; consensus.nAllClaimsInMerkleForkHeight = 1198560; // targeting 30 Sep 2019 + consensus.nAuxPoWStartHeight = 2000000; + consensus.nAuxPoWChainID = 0x4C424374; // LBCt consensus.fPowAllowMinDifficultyBlocks = true; consensus.fPowNoRetargeting = false; consensus.nRuleChangeActivationThreshold = 1512; // 75% for testchains @@ -372,6 +376,8 @@ public: consensus.nMaxTakeoverWorkaroundHeight = -1; consensus.nWitnessForkHeight = 150; consensus.nAllClaimsInMerkleForkHeight = 350; + consensus.nAuxPoWStartHeight = 450; + consensus.nAuxPoWChainID = 0x4C424372; // LBCr consensus.fPowAllowMinDifficultyBlocks = false; consensus.fPowNoRetargeting = false; consensus.nRuleChangeActivationThreshold = 108; // 75% for testchains diff --git a/src/consensus/params.h b/src/consensus/params.h index 9534d8834..e463b6ec6 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -82,6 +82,8 @@ struct Params { int nMaxTakeoverWorkaroundHeight; int nWitnessForkHeight; + int nAuxPoWStartHeight; + uint32_t nAuxPoWChainID; int64_t nPowTargetSpacing; int64_t nPowTargetTimespan; diff --git a/src/miner.cpp b/src/miner.cpp index 8ba1e8fd6..ef0e024cd 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -147,12 +147,14 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc CBlockIndex* pindexPrev = chainActive.Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; - pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); + auto& params = chainparams.GetConsensus(); + pblock->nVersion = ComputeBlockVersion(pindexPrev, params); // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios if (chainparams.MineBlocksOnDemand()) pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion); + pblock->nNonce = params.nChainID; pblock->nTime = GetAdjustedTime(); const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index 1b32021b8..4dea207f0 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -5,19 +5,27 @@ #include +#include #include #include #include #include #include -uint256 CBlockHeader::GetHash() const +void CBlockHeader::SetAuxpow(CAuxPow* apow) { - return SerializeHash(*this); + if (apow) + { + auxpow.reset(apow); + SetAuxpowFlag(true); + } else + { + auxpow.reset(); + SetAuxpowFlag(false); + } } -uint256 CBlockHeader::GetPoWHash() const -{ +uint256 CBlockHeader::GetPoWHash() const { CDataStream ds(SER_GETHASH, PROTOCOL_VERSION); ds << *this; std::vector input(ds.begin(), ds.end()); @@ -28,15 +36,25 @@ std::string CBlock::ToString() const { std::stringstream s; s << strprintf("CBlock(hash=%s, ver=0x%08x, hashPrevBlock=%s, hashMerkleRoot=%s, hashClaimTrie=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%u)\n", - GetHash().ToString(), - nVersion, - hashPrevBlock.ToString(), - hashMerkleRoot.ToString(), - hashClaimTrie.ToString(), - nTime, nBits, nNonce, - vtx.size()); - for (const auto& tx : vtx) { - s << " " << tx->ToString() << "\n"; + GetHash().ToString(), + nVersion, + hashPrevBlock.ToString(), + hashMerkleRoot.ToString(), + hashClaimTrie.ToString(), + nTime, nBits, nNonce, + vtx.size()); + for (unsigned int i = 0; i < vtx.size(); i++) + { + s << " " << vtx[i]->ToString() << "\n"; } return s.str(); } + +int64_t GetBlockWeight(const CBlock& block) +{ + // This implements the weight = (stripped_size * 4) + witness_size formula, + // using only serialization with and without witness data. As witness_size + // is equal to total_size - stripped_size, this formula is identical to: + // weight = (stripped_size * 3) + total_size. + return ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); +} \ No newline at end of file diff --git a/src/primitives/block.h b/src/primitives/block.h index 23fce79e8..d693e4482 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -6,6 +6,8 @@ #ifndef BITCOIN_PRIMITIVES_BLOCK_H #define BITCOIN_PRIMITIVES_BLOCK_H +#include +#include #include #include #include @@ -17,63 +19,74 @@ * in the block is a special one that creates a new coin owned by the creator * of the block. */ -class CBlockHeader +class CBlockHeader : public CPureBlockHeader { public: - // header - int32_t nVersion; - uint256 hashPrevBlock; - uint256 hashMerkleRoot; + /* Modifiers to the version. */ + static const int32_t VERSION_AUXPOW = (1 << 8); + uint256 hashClaimTrie; - uint32_t nTime; - uint32_t nBits; - uint32_t nNonce; - - CBlockHeader() - { - SetNull(); - } + std::shared_ptr auxpow; // (if this is a merge-minded block) + CBlockHeader() = default; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(this->nVersion); + READWRITE(nVersion); READWRITE(hashPrevBlock); READWRITE(hashMerkleRoot); READWRITE(hashClaimTrie); READWRITE(nTime); READWRITE(nBits); READWRITE(nNonce); + + if (IsAuxpow()) + { + if (ser_action.ForRead()) + auxpow.reset(new CAuxPow()); + assert(auxpow); + READWRITE(*auxpow); + } else if (ser_action.ForRead()) + auxpow.reset(); } void SetNull() { - nVersion = 0; - hashPrevBlock.SetNull(); - hashMerkleRoot.SetNull(); + CPureBlockHeader::SetNull(); hashClaimTrie.SetNull(); - nTime = 0; - nBits = 0; - nNonce = 0; + auxpow.reset(); } - bool IsNull() const - { - return (nBits == 0); - } - - uint256 GetHash() const; - uint256 GetPoWHash() const; - int64_t GetBlockTime() const - { - return (int64_t)nTime; + /** + * Set the block's auxpow (or unset it). This takes care of updating + * the version accordingly. + * @param apow Pointer to the auxpow to use or NULL. + */ + void SetAuxpow(CAuxPow* apow); + + /** + * Check if the auxpow flag is set in the version. + * @return True iff this block version is marked as auxpow. + */ + inline bool IsAuxpow() const { + return nVersion & VERSION_AUXPOW; + } + + /** + * Set the auxpow flag. This is used for testing. + * @param auxpow Whether to mark auxpow as true. + */ + inline void SetAuxpowFlag(bool auxpow) { + if (auxpow) + nVersion |= VERSION_AUXPOW; + else + nVersion &= ~VERSION_AUXPOW; } }; - class CBlock : public CBlockHeader { public: @@ -83,22 +96,20 @@ public: // memory only mutable bool fChecked; - CBlock() + CBlock() : fChecked(false) { - SetNull(); } - CBlock(const CBlockHeader &header) + CBlock(const CBlockHeader &header) : fChecked(false) { - SetNull(); - *(static_cast(this)) = header; + *((CBlockHeader*)this) = header; } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITEAS(CBlockHeader, *this); + READWRITE(*(CBlockHeader*)this); READWRITE(vtx); } @@ -119,6 +130,7 @@ public: block.nTime = nTime; block.nBits = nBits; block.nNonce = nNonce; + block.auxpow = auxpow; return block; } @@ -158,4 +170,7 @@ struct CBlockLocator } }; -#endif // BITCOIN_PRIMITIVES_BLOCK_H +/** Compute the consensus-critical block weight (see BIP 141). */ +int64_t GetBlockWeight(const CBlock& tx); + +#endif // BITCOIN_PRIMITIVES_BLOCK_H \ No newline at end of file diff --git a/src/primitives/pureheader.cpp b/src/primitives/pureheader.cpp new file mode 100644 index 000000000..c887a84c7 --- /dev/null +++ b/src/primitives/pureheader.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +uint256 CPureBlockHeader::GetHash() const +{ + return SerializeHash(*this); +} + +uint256 CPureBlockHeader::GetPoWHash() const +{ + CDataStream ds(SER_GETHASH, PROTOCOL_VERSION); + ds << *this; + auto once = Hash(ds.begin(), ds.end()); + return Hash(once.begin(), once.end()); +} \ No newline at end of file diff --git a/src/primitives/pureheader.h b/src/primitives/pureheader.h new file mode 100644 index 000000000..68a572c4b --- /dev/null +++ b/src/primitives/pureheader.h @@ -0,0 +1,65 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_PRIMITIVES_PUREHEADER_H +#define BITCOIN_PRIMITIVES_PUREHEADER_H + +#include +#include + +/** + * A block header without auxpow information. This "intermediate step" + * in constructing the full header is useful, because it breaks the cyclic + * dependency between auxpow (referencing a parent block header) and + * the block header (referencing an auxpow). The parent block header + * does not have auxpow itself, so it is a pure header. + */ +class CPureBlockHeader { // by "pure" we mean "bitcoin" +public: + // header + uint256 hashPrevBlock; + uint256 hashMerkleRoot; + int32_t nVersion; + uint32_t nTime; + uint32_t nBits; + uint32_t nNonce; + + CPureBlockHeader() : nVersion(0), nTime(0), nBits(0), nNonce(0) { + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream &s, Operation ser_action) { + READWRITE(nVersion); + READWRITE(hashPrevBlock); + READWRITE(hashMerkleRoot); + READWRITE(nTime); + READWRITE(nBits); + READWRITE(nNonce); + } + + void SetNull() { + nVersion = 0; + hashPrevBlock.SetNull(); + hashMerkleRoot.SetNull(); + nTime = 0; + nBits = 0; + nNonce = 0; + } + + bool IsNull() const { + return (nBits == 0); + } + + uint256 GetHash() const; + + uint256 GetPoWHash() const; + + int64_t GetBlockTime() const { + return (int64_t) nTime; + } +}; +#endif // BITCOIN_PRIMITIVES_PUREHEADER_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index d217dc3b6..eb3a93495 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -44,6 +44,9 @@ #include #include +#include "rawtransaction.h" + + struct CUpdatedBlock { uint256 hash; @@ -81,6 +84,42 @@ double GetDifficulty(const CBlockIndex* blockindex) return dDiff; } +UniValue AuxpowToJSON(const CAuxPow& auxpow) +{ + UniValue result(UniValue::VOBJ); + + { + UniValue tx(UniValue::VOBJ); + tx.push_back(Pair("hex", EncodeHexTx(auxpow))); + TxToJSON(auxpow, auxpow.parentBlock.GetHash(), tx); + result.push_back(Pair("tx", tx)); + } + + result.push_back(Pair("index", auxpow.nIndex)); + result.push_back(Pair("chainindex", auxpow.nChainIndex)); + + { + UniValue branch(UniValue::VARR); + for (const uint256& node : auxpow.vMerkleBranch) + branch.push_back(node.GetHex()); + result.push_back(Pair("merklebranch", branch)); + } + + { + UniValue branch(UniValue::VARR); + for (const uint256& node : auxpow.vChainMerkleBranch) + branch.push_back(node.GetHex()); + result.push_back(Pair("chainmerklebranch", branch)); + } + + CDataStream ssParent(SER_NETWORK, PROTOCOL_VERSION); + ssParent << auxpow.parentBlock; + const std::string strHex = HexStr(ssParent.begin(), ssParent.end()); + result.push_back(Pair("parentblock", strHex)); + + return result; +} + UniValue blockheaderToJSON(const CBlockIndex* blockindex) { AssertLockHeld(cs_main); @@ -151,6 +190,9 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx result.pushKV("chainwork", blockindex->nChainWork.GetHex()); result.pushKV("nTx", (uint64_t)blockindex->nTx); + if (block.auxpow) + result.push_back(Pair("auxpow", AuxpowToJSON(*block.auxpow))); + if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); CBlockIndex *pnext = chainActive.Next(blockindex); @@ -733,7 +775,7 @@ static UniValue getblockheader(const JSONRPCRequest& request) if (!fVerbose) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); - ssBlock << pblockindex->GetBlockHeader(); + ssBlock << pblockindex->GetBlockHeader(Params().GetConsensus()); std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); return strHex; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 7875f05e4..6bd45af19 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -972,6 +972,190 @@ static UniValue estimaterawfee(const JSONRPCRequest& request) return result; } +/* ************************************************************************** */ +/* Merge mining. */ + +UniValue getauxblockbip22(const JSONRPCRequest& request) +{ + if (request.fHelp + || (request.params.size() != 0 && request.params.size() != 2)) + throw std::runtime_error( + "getauxblock (hash auxpow)\n" + "\nCreate or submit a merge-mined block.\n" + "\nWithout arguments, create a new block and return information\n" + "required to merge-mine it. With arguments, submit a solved\n" + "auxpow for a previously returned block.\n" + "\nArguments:\n" + "1. hash (string, optional) hash of the block to submit\n" + "2. auxpow (string, optional) serialised auxpow found\n" + "\nResult (without arguments):\n" + "{\n" + " \"hash\" (string) hash of the created block\n" + " \"chainid\" (numeric) chain ID for this block\n" + " \"previousblockhash\" (string) hash of the previous block\n" + " \"coinbasevalue\" (numeric) value of the block's coinbase\n" + " \"bits\" (string) compressed target of the block\n" + " \"height\" (numeric) height of the block\n" + " \"target\" (string) target in reversed byte order\n" + "}\n" + "\nResult (with arguments):\n" + "xxxxx (boolean) whether the submitted block was correct\n" + "\nExamples:\n" + + HelpExampleCli("getauxblock", "") + + HelpExampleCli("getauxblock", "\"hash\" \"serialised auxpow\"") + + HelpExampleRpc("getauxblock", "") + ); + + boost::shared_ptr coinbaseScript; + GetMainSignals().ScriptForMining(coinbaseScript); + + // If the keypool is exhausted, no script is returned at all. Catch this. + if (!coinbaseScript) + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + + //throw an error if no script was provided + if (!coinbaseScript->reserveScript.size()) + throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet)"); + + if(!g_connman) + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + + if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0 && !Params().MineBlocksOnDemand()) + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Dogecoin is not connected!"); + + if (IsInitialBlockDownload() && !Params().MineBlocksOnDemand()) + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, + "Dogecoin is downloading blocks..."); + + /* This should never fail, since the chain is already + past the point of merge-mining start. Check nevertheless. */ + { + LOCK(cs_main); + if (chainActive.Height() < Params().GetConsensus().nAuxPoWStartHeight) + throw std::runtime_error("getauxblock method is not yet available"); + } + + /* The variables below are used to keep track of created and not yet + submitted auxpow blocks. Lock them to be sure even for multiple + RPC threads running in parallel. */ + static CCriticalSection cs_auxblockCache; + LOCK(cs_auxblockCache); + static std::map mapNewBlock; + static std::vector> vNewBlockTemplate; + + /* Create a new block? */ + if (request.params.size() == 0) + { + static unsigned nTransactionsUpdatedLast; + static const CBlockIndex* pindexPrev = nullptr; + static uint64_t nStart; + static CBlock* pblock = nullptr; + static unsigned nExtraNonce = 0; + + // Update block + // Dogecoin: Never mine witness tx + const bool fMineWitnessTx = false; + { + LOCK(cs_main); + if (pindexPrev != chainActive.Tip() + || (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast + && GetTime() - nStart > 60)) + { + if (pindexPrev != chainActive.Tip()) + { + // Clear old blocks since they're obsolete now. + mapNewBlock.clear(); + vNewBlockTemplate.clear(); + pblock = nullptr; + } + + // Create new block with nonce = 0 and extraNonce = 1 + std::unique_ptr newBlock(BlockAssembler(Params()).CreateNewBlock(coinbaseScript->reserveScript, fMineWitnessTx)); + if (!newBlock) + throw JSONRPCError(RPC_OUT_OF_MEMORY, "out of memory"); + + // Update state only when CreateNewBlock succeeded + nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); + pindexPrev = chainActive.Tip(); + nStart = GetTime(); + + // Finalise it by setting the version and building the merkle root + IncrementExtraNonce(&newBlock->block, pindexPrev, nExtraNonce); + newBlock->block.SetAuxpowFlag(true); + + // Save + pblock = &newBlock->block; + mapNewBlock[pblock->GetHash()] = pblock; + vNewBlockTemplate.push_back(std::move(newBlock)); + } + } + + arith_uint256 target; + bool fNegative, fOverflow; + target.SetCompact(pblock->nBits, &fNegative, &fOverflow); + if (fNegative || fOverflow || target == 0) + throw std::runtime_error("invalid difficulty bits in block"); + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("hash", pblock->GetHash().GetHex())); + result.push_back(Pair("chainid", pblock->GetChainId())); + result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex())); + result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue)); + result.push_back(Pair("bits", strprintf("%08x", pblock->nBits))); + result.push_back(Pair("height", static_cast (pindexPrev->nHeight + 1))); + result.push_back(Pair("target", HexStr(BEGIN(target), END(target)))); + + return result; + } + + /* Submit a block instead. Note that this need not lock cs_main, + since ProcessNewBlock below locks it instead. */ + + assert(request.params.size() == 2); + uint256 hash; + hash.SetHex(request.params[0].get_str()); + + const std::map::iterator mit = mapNewBlock.find(hash); + if (mit == mapNewBlock.end()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "block hash unknown"); + CBlock& block = *mit->second; + + const std::vector vchAuxPow + = ParseHex(request.params[1].get_str()); + CDataStream ss(vchAuxPow, SER_GETHASH, PROTOCOL_VERSION); + CAuxPow pow; + ss >> pow; + block.SetAuxpow(new CAuxPow(pow)); + assert(block.GetHash() == hash); + + submitblock_StateCatcher sc(block.GetHash()); + RegisterValidationInterface(&sc); + std::shared_ptr shared_block + = std::make_shared(block); + bool fAccepted = ProcessNewBlock(Params(), shared_block, true, nullptr); + UnregisterValidationInterface(&sc); + + if (fAccepted) + coinbaseScript->KeepScript(); + + return BIP22ValidationResult(sc.state); +} + +UniValue getauxblock(const JSONRPCRequest& request) +{ + const UniValue response = getauxblockbip22(request); + + // this is a request for a new blocktemplate: return response + if (request.params.size() == 0) + return response; + + // this is a new block submission: return bool + return response.isNull(); +} + +/* ************************************************************************** */ + + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- @@ -980,6 +1164,7 @@ static const CRPCCommand commands[] = { "mining", "prioritisetransaction", &prioritisetransaction, {"txid","dummy","fee_delta"} }, { "mining", "getblocktemplate", &getblocktemplate, {"template_request"} }, { "mining", "submitblock", &submitblock, {"hexdata","dummy"} }, + { "mining", "getauxblock", &getauxblock, {"hash", "auxpow"} }, { "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 68c87e2d9..4facd33cb 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -37,7 +37,7 @@ #include -static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) +void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. // diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index 924611ed5..908504677 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -15,4 +15,6 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBas /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); +void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); + #endif // BITCOIN_RPC_RAWTRANSACTION_H diff --git a/src/test/auxpow_tests.cpp b/src/test/auxpow_tests.cpp new file mode 100644 index 000000000..d514c0f9a --- /dev/null +++ b/src/test/auxpow_tests.cpp @@ -0,0 +1,445 @@ +// Copyright (c) 2014-2015 Daniel Kraft +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "auxpow.h" +#include "chainparams.h" +#include "coins.h" +#include "consensus/merkle.h" +#include "primitives/block.h" +#include "script/script.h" +#include "uint256.h" +#include "utilstrencodings.h" +#include "validation.h" + +#include "test/test_bitcoin.h" + +#include + +#include +#include + +BOOST_FIXTURE_TEST_SUITE(auxpow_tests, BasicTestingSetup) + +/* ************************************************************************** */ + +/** + * Tamper with a uint256 (modify it). + * @param num The number to modify. + */ + static void + tamperWith(uint256& num) + { + arith_uint256 modifiable = UintToArith256(num); + modifiable += 1; + num = ArithToUint256(modifiable); + } + +/** + * Utility class to construct auxpow's and manipulate them. This is used + * to simulate various scenarios. + */ + class CAuxpowBuilder + { + public: + /** The parent block (with coinbase, not just header). */ + CBlock parentBlock; + + /** The auxpow's merkle branch (connecting it to the coinbase). */ + std::vector auxpowChainMerkleBranch; + /** The auxpow's merkle tree index. */ + int auxpowChainIndex; + + /** + * Initialise everything. + * @param baseVersion The parent block's base version to use. + * @param chainId The parent block's chain ID to use. + */ + CAuxpowBuilder(int baseVersion, int chainId); + + /** + * Set the coinbase's script. + * @param scr Set it to this script. + */ + void setCoinbase(const CScript& scr); + + /** + * Build the auxpow merkle branch. The member variables will be + * set accordingly. This has to be done before constructing the coinbase + * itself (which must contain the root merkle hash). When we have the + * coinbase afterwards, the member variables can be used to initialise + * the CAuxPow object from it. + * @param hashAux The merge-mined chain's block hash. + * @param h Height of the merkle tree to build. + * @param index Index to use in the merkle tree. + * @return The root hash, with reversed endian. + */ + std::vector buildAuxpowChain(const uint256& hashAux, unsigned h, int index); + + /** + * Build the finished CAuxPow object. We assume that the auxpowChain + * member variables are already set. We use the passed in transaction + * as the base. It should (probably) be the parent block's coinbase. + * @param tx The base tx to use. + * @return The constructed CAuxPow object. + */ + CAuxPow get(const CTransactionRef tx) const; + + /** + * Build the finished CAuxPow object from the parent block's coinbase. + * @return The constructed CAuxPow object. + */ + inline CAuxPow + get() const + { + assert(!parentBlock.vtx.empty()); + return get(parentBlock.vtx[0]); + } + + /** + * Build a data vector to be included in the coinbase. It consists + * of the aux hash, the merkle tree size and the nonce. Optionally, + * the header can be added as well. + * @param header Add the header? + * @param hashAux The aux merkle root hash. + * @param h Height of the merkle tree. + * @param nonce The nonce value to use. + * @return The constructed data. + */ + static std::vector buildCoinbaseData(bool header, const std::vector& auxRoot, unsigned h, int nonce); + }; + + CAuxpowBuilder::CAuxpowBuilder(int baseVersion, int chainId) + : auxpowChainIndex(-1) + { + parentBlock.SetBaseVersion(baseVersion, chainId); + } + + void CAuxpowBuilder::setCoinbase(const CScript& scr) + { + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].prevout.SetNull(); + mtx.vin[0].scriptSig = scr; + + parentBlock.vtx.clear(); + parentBlock.vtx.push_back(MakeTransactionRef(std::move(mtx))); + parentBlock.hashMerkleRoot = BlockMerkleRoot(parentBlock); + } + + std::vector + CAuxpowBuilder::buildAuxpowChain(const uint256& hashAux, unsigned h, int index) + { + auxpowChainIndex = index; + + /* Just use "something" for the branch. Doesn't really matter. */ + auxpowChainMerkleBranch.clear(); + for (unsigned i = 0; i < h; ++i) + auxpowChainMerkleBranch.push_back(ArithToUint256(arith_uint256(i))); + + const uint256 hash = CAuxPow::CheckMerkleBranch(hashAux, auxpowChainMerkleBranch, index); + + std::vector res = ToByteVector(hash); + std::reverse(res.begin(), res.end()); + + return res; + } + + CAuxPow + CAuxpowBuilder::get(const CTransactionRef tx) const + { + LOCK(cs_main); + CAuxPow res(tx); + res.InitMerkleBranch(parentBlock, 0); + + res.vChainMerkleBranch = auxpowChainMerkleBranch; + res.nChainIndex = auxpowChainIndex; + res.parentBlock = parentBlock; + + return res; + } + + std::vector + CAuxpowBuilder::buildCoinbaseData(bool header, const std::vector& auxRoot, unsigned h, int nonce) + { + std::vector res; + + if (header) + res.insert(res.end(), UBEGIN(pchMergedMiningHeader), + UEND(pchMergedMiningHeader)); + res.insert(res.end(), auxRoot.begin(), auxRoot.end()); + + const int size = (1 << h); + res.insert(res.end(), UBEGIN(size), UEND(size)); + res.insert(res.end(), UBEGIN(nonce), UEND(nonce)); + + return res; + } + +/* ************************************************************************** */ + + BOOST_AUTO_TEST_CASE(check_auxpow) + { + const Consensus::Params& params = Params().GetConsensus(); + CAuxpowBuilder builder(5, 42); + CAuxPow auxpow; + + const uint256 hashAux = ArithToUint256(arith_uint256(12345)); + const int32_t ourChainId = params.nAuxPoWChainID; + const unsigned height = 30; + const int nonce = 7; + int index; + + std::vector auxRoot, data; + CScript scr; + + /* Build a correct auxpow. The height is the maximally allowed one. */ + index = CAuxPow::getExpectedIndex(nonce, ourChainId, height); + auxRoot = builder.buildAuxpowChain(hashAux, height, index); + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + scr = (CScript() << 2809 << 2013) + COINBASE_FLAGS; + scr = (scr << OP_2 << data); + builder.setCoinbase(scr); + BOOST_CHECK(builder.get().check(hashAux, ourChainId, params)); + + /* Check that the auxpow is invalid if we change either the aux block's + hash or the chain ID. */ + uint256 modifiedAux(hashAux); + tamperWith(modifiedAux); + BOOST_CHECK(!builder.get().check(modifiedAux, ourChainId, params)); + BOOST_CHECK(!builder.get().check(hashAux, ourChainId + 1, params)); + + /* Non-coinbase parent tx should fail. Note that we can't just copy + the coinbase literally, as we have to get a tx with different hash. */ + const CTransactionRef oldCoinbase = builder.parentBlock.vtx[0]; + builder.setCoinbase(scr << 5); + builder.parentBlock.vtx.push_back(oldCoinbase); + builder.parentBlock.hashMerkleRoot = BlockMerkleRoot(builder.parentBlock); + auxpow = builder.get(builder.parentBlock.vtx[0]); + BOOST_CHECK(auxpow.check(hashAux, ourChainId, params)); + auxpow = builder.get(builder.parentBlock.vtx[1]); + BOOST_CHECK(!auxpow.check(hashAux, ourChainId, params)); + + /* The parent chain can't have the same chain ID. */ + CAuxpowBuilder builder2(builder); + builder2.parentBlock.SetChainId(100); + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + builder2.parentBlock.SetChainId(ourChainId); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + /* Disallow too long merkle branches. */ + builder2 = builder; + index = CAuxPow::getExpectedIndex(nonce, ourChainId, height + 1); + auxRoot = builder2.buildAuxpowChain(hashAux, height + 1, index); + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height + 1, nonce); + scr = (CScript() << 2809 << 2013) + COINBASE_FLAGS; + scr = (scr << OP_2 << data); + builder2.setCoinbase(scr); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + /* Verify that we compare correctly to the parent block's merkle root. */ + builder2 = builder; + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + tamperWith(builder2.parentBlock.hashMerkleRoot); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + /* Build a non-header legacy version and check that it is also accepted. */ + builder2 = builder; + index = CAuxPow::getExpectedIndex(nonce, ourChainId, height); + auxRoot = builder2.buildAuxpowChain(hashAux, height, index); + data = CAuxpowBuilder::buildCoinbaseData(false, auxRoot, height, nonce); + scr = (CScript() << 2809 << 2013) + COINBASE_FLAGS; + scr = (scr << OP_2 << data); + builder2.setCoinbase(scr); + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + + /* However, various attempts at smuggling two roots in should be detected. */ + + const std::vector wrongAuxRoot = builder2.buildAuxpowChain(modifiedAux, height, index); + std::vector data2 = CAuxpowBuilder::buildCoinbaseData(false, wrongAuxRoot, height, nonce); + builder2.setCoinbase(CScript() << data << data2); + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + builder2.setCoinbase(CScript() << data2 << data); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + data2 = CAuxpowBuilder::buildCoinbaseData(true, wrongAuxRoot, height, nonce); + builder2.setCoinbase(CScript() << data << data2); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + builder2.setCoinbase(CScript() << data2 << data); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + builder2.setCoinbase(CScript() << data << data2); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + builder2.setCoinbase(CScript() << data2 << data); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + data2 = CAuxpowBuilder::buildCoinbaseData(false, wrongAuxRoot, + height, nonce); + builder2.setCoinbase(CScript() << data << data2); + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + builder2.setCoinbase(CScript() << data2 << data); + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + + /* Verify that the appended nonce/size values are checked correctly. */ + + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + builder2.setCoinbase(CScript() << data); + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + + data.pop_back(); + builder2.setCoinbase(CScript() << data); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height - 1, nonce); + builder2.setCoinbase(CScript() << data); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce + 3); + builder2.setCoinbase(CScript() << data); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + /* Put the aux hash in an invalid merkle tree position. */ + + auxRoot = builder.buildAuxpowChain(hashAux, height, index + 1); + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + builder2.setCoinbase(CScript() << data); + BOOST_CHECK(!builder2.get().check(hashAux, ourChainId, params)); + + auxRoot = builder.buildAuxpowChain(hashAux, height, index); + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + builder2.setCoinbase(CScript() << data); + BOOST_CHECK(builder2.get().check(hashAux, ourChainId, params)); + } + +/* ************************************************************************** */ + +/** + * Mine a block (assuming minimal difficulty) that either matches + * or doesn't match the difficulty target specified in the block header. + * @param block The block to mine (by updating nonce). + * @param ok Whether the block should be ok for PoW. + * @param nBits Use this as difficulty if specified. + */ + static void + mineBlock(CBlockHeader& block, bool ok, int nBits = -1) + { + if (nBits == -1) + nBits = block.nBits; + + arith_uint256 target; + target.SetCompact(nBits); + + block.nNonce = 0; + while (true) { + const bool nowOk = (UintToArith256(block.GetPoWHash()) <= target); + if ((ok && nowOk) || (!ok && !nowOk)) + break; + + ++block.nNonce; + } + + if (ok) + BOOST_CHECK(CheckProofOfWork(block.GetPoWHash(), nBits, Params().GetConsensus(0))); + else + BOOST_CHECK(!CheckProofOfWork(block.GetPoWHash(), nBits, Params().GetConsensus(0))); + } + + BOOST_AUTO_TEST_CASE(auxpow_pow) + { + /* Use regtest parameters to allow mining with easy difficulty. */ + SelectParams(CBaseChainParams::REGTEST); + const Consensus::Params& params = Params().GetConsensus(371337); + + const arith_uint256 target = (~arith_uint256(0) >> 1); + CBlockHeader block; + block.nBits = target.GetCompact(); + + /* Verify the block version checks. */ + + block.nVersion = 1; + mineBlock(block, true); + BOOST_CHECK(CheckAuxPowProofOfWork(block, params)); + + // Dogecoin block version 2 can be both AuxPoW and regular, so test 3 + + block.nVersion = 3; + mineBlock(block, true); + BOOST_CHECK(!CheckAuxPowProofOfWork(block, params)); + + block.SetBaseVersion(2, params.nAuxpowChainId); + mineBlock(block, true); + BOOST_CHECK(CheckAuxPowProofOfWork(block, params)); + + block.SetChainId(params.nAuxpowChainId + 1); + mineBlock(block, true); + BOOST_CHECK(!CheckAuxPowProofOfWork(block, params)); + + /* Check the case when the block does not have auxpow (this is true + right now). */ + + block.SetChainId(params.nAuxpowChainId); + block.SetAuxpowFlag(true); + mineBlock(block, true); + BOOST_CHECK(!CheckAuxPowProofOfWork(block, params)); + + block.SetAuxpowFlag(false); + mineBlock(block, true); + BOOST_CHECK(CheckAuxPowProofOfWork(block, params)); + mineBlock(block, false); + BOOST_CHECK(!CheckAuxPowProofOfWork(block, params)); + + /* ****************************************** */ + /* Check the case that the block has auxpow. */ + + CAuxpowBuilder builder(5, 42); + CAuxPow auxpow; + const int32_t ourChainId = params.nAuxpowChainId; + const unsigned height = 3; + const int nonce = 7; + const int index = CAuxPow::getExpectedIndex(nonce, ourChainId, height); + std::vector auxRoot, data; + + /* Valid auxpow, PoW check of parent block. */ + block.SetAuxpowFlag(true); + auxRoot = builder.buildAuxpowChain(block.GetHash(), height, index); + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + builder.setCoinbase(CScript() << data); + mineBlock(builder.parentBlock, false, block.nBits); + block.SetAuxpow(new CAuxPow(builder.get())); + BOOST_CHECK(!CheckAuxPowProofOfWork(block, params)); + mineBlock(builder.parentBlock, true, block.nBits); + block.SetAuxpow(new CAuxPow(builder.get())); + BOOST_CHECK(CheckAuxPowProofOfWork(block, params)); + + /* Mismatch between auxpow being present and block.nVersion. Note that + block.SetAuxpow sets also the version and that we want to ensure + that the block hash itself doesn't change due to version changes. + This requires some work arounds. */ + block.SetAuxpowFlag(false); + const uint256 hashAux = block.GetHash(); + auxRoot = builder.buildAuxpowChain(hashAux, height, index); + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + builder.setCoinbase(CScript() << data); + mineBlock(builder.parentBlock, true, block.nBits); + block.SetAuxpow(new CAuxPow(builder.get())); + BOOST_CHECK(hashAux != block.GetHash()); + block.SetAuxpowFlag(false); + BOOST_CHECK(hashAux == block.GetHash()); + BOOST_CHECK(!CheckAuxPowProofOfWork(block, params)); + + /* Modifying the block invalidates the PoW. */ + block.SetAuxpowFlag(true); + auxRoot = builder.buildAuxpowChain(block.GetHash(), height, index); + data = CAuxpowBuilder::buildCoinbaseData(true, auxRoot, height, nonce); + builder.setCoinbase(CScript() << data); + mineBlock(builder.parentBlock, true, block.nBits); + block.SetAuxpow(new CAuxPow(builder.get())); + BOOST_CHECK(CheckAuxPowProofOfWork(block, params)); + tamperWith(block.hashMerkleRoot); + BOOST_CHECK(!CheckAuxPowProofOfWork(block, params)); + } + +/* ************************************************************************** */ + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 80cd9bf57..bd90c2fc1 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -71,7 +71,8 @@ std::shared_ptr FinalizeBlock(std::shared_ptr pblock) { pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - while (!CheckProofOfWork(pblock->GetPoWHash(), pblock->nBits, Params().GetConsensus())) { + auto& miningHeader = CAuxPow::initAuxPow(*pblock); + while (!CheckProofOfWork(miningHeader->GetPoWHash(), pblock->nBits, Params().GetConsensus())) { ++(pblock->nNonce); } diff --git a/src/validation.cpp b/src/validation.cpp index e7cdc8b19..3da1917b9 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -926,6 +926,32 @@ static bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMes return true; } +bool CheckAuxPowProofOfWork(const CBlockHeader& block, const Consensus::Params& params) +{ + /* If there is no auxpow, just check the block hash. */ + if (!block.auxpow) { + if (block.IsAuxpow()) + return error("%s : no auxpow on block with auxpow version", + __func__); + + if (!CheckProofOfWork(block.GetPoWHash(), block.nBits, params)) + return error("%s : non-AUX proof of work failed", __func__); + + return true; + } + + /* We have auxpow. Check it. */ + if (!block.IsAuxpow()) + return error("%s : auxpow on block with non-auxpow version", __func__); + if (!block.auxpow->check(block.GetHash(), params)) + return error("%s : AUX POW is not valid", __func__); + if (!CheckProofOfWork(block.auxpow->getParentBlockPoWHash(), block.nBits, params)) + return error("%s : AUX proof of work failed", __func__); + + return true; +} + + bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams) { block.SetNull(); @@ -944,7 +970,7 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus: } // Check the header - if (!CheckProofOfWork(block.GetPoWHash(), block.nBits, consensusParams)) + if (!CheckAuxPowProofOfWork(block, consensusParams)) return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); return true; @@ -1642,6 +1668,8 @@ int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Para } } + TODO: include aux + return nVersion; } @@ -3101,7 +3129,7 @@ static bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true) { // Check proof of work matches claimed amount - if (fCheckPOW && !CheckProofOfWork(block.GetPoWHash(), block.nBits, consensusParams)) + if (fCheckPOW && !CheckAuxPowProofOfWork(block, consensusParams)) return state.DoS(50, false, REJECT_INVALID, "high-hash", false, "proof of work failed"); return true; @@ -3257,6 +3285,10 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false, "incorrect proof of work"); + if (block.IsAuxpow() && nHeight < consensusParams.nAuxPoWStartHeight) + return state.DoS(100, error("%s : auxpow blocks are not allowed at height %d", + __func__, pindexPrev->nHeight + 1), REJECT_INVALID, "early-auxpow-block"); + // Check against checkpoints if (fCheckpointsEnabled) { // Don't accept any forks from the main chain prior to last checkpoint.