diff --git a/src/addrdb.h b/src/addrdb.h index 77f055919..5008fa4ff 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -1,97 +1,97 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #ifndef BITCOIN_ADDRDB_H #define BITCOIN_ADDRDB_H #include "fs.h" #include "serialize.h" #include #include class CSubNet; class CAddrMan; class CDataStream; class CChainParams; typedef enum BanReason { BanReasonUnknown = 0, BanReasonNodeMisbehaving = 1, BanReasonManuallyAdded = 2 } BanReason; class CBanEntry { public: static const int CURRENT_VERSION = 1; int nVersion; int64_t nCreateTime; int64_t nBanUntil; uint8_t banReason; CBanEntry() { SetNull(); } explicit CBanEntry(int64_t nCreateTimeIn) { SetNull(); nCreateTime = nCreateTimeIn; } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(this->nVersion); READWRITE(nCreateTime); READWRITE(nBanUntil); READWRITE(banReason); } void SetNull() { nVersion = CBanEntry::CURRENT_VERSION; nCreateTime = 0; nBanUntil = 0; banReason = BanReasonUnknown; } - std::string banReasonToString() { + std::string banReasonToString() const { switch (banReason) { case BanReasonNodeMisbehaving: return "node misbehaving"; case BanReasonManuallyAdded: return "manually added"; default: return "unknown"; } } }; typedef std::map banmap_t; /** Access to the (IP) address database (peers.dat) */ class CAddrDB { private: fs::path pathAddr; const CChainParams &chainParams; public: CAddrDB(const CChainParams &chainParams); bool Write(const CAddrMan &addr); bool Read(CAddrMan &addr); bool Read(CAddrMan &addr, CDataStream &ssPeers); }; /** Access to the banlist database (banlist.dat) */ class CBanDB { private: fs::path pathBanlist; const CChainParams &chainParams; public: CBanDB(const CChainParams &chainParams); bool Write(const banmap_t &banSet); bool Read(banmap_t &banSet); }; #endif // BITCOIN_ADDRDB_H diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index 557dbe254..06ad2ea11 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -1,227 +1,227 @@ // Copyright (c) 2012-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. #include "dbwrapper.h" #include "random.h" #include "util.h" #include #include #include #include #include #include #include class CBitcoinLevelDBLogger : public leveldb::Logger { public: // This code is adapted from posix_logger.h, which is why it is using // vsprintf. // Please do not do this in normal code void Logv(const char *format, va_list ap) override { if (!LogAcceptCategory(BCLog::LEVELDB)) { return; } char buffer[500]; for (int iter = 0; iter < 2; iter++) { char *base; int bufsize; if (iter == 0) { bufsize = sizeof(buffer); base = buffer; } else { bufsize = 30000; base = new char[bufsize]; } char *p = base; char *limit = base + bufsize; // Print the message if (p < limit) { va_list backup_ap; va_copy(backup_ap, ap); // Do not use vsnprintf elsewhere in bitcoin source code, see // above. p += vsnprintf(p, limit - p, format, backup_ap); va_end(backup_ap); } // Truncate to available space if necessary if (p >= limit) { if (iter == 0) { continue; // Try again with larger buffer } else { p = limit - 1; } } // Add newline if necessary if (p == base || p[-1] != '\n') { *p++ = '\n'; } assert(p <= limit); base[std::min(bufsize - 1, (int)(p - base))] = '\0'; LogPrintf("leveldb: %s", base); if (base != buffer) { delete[] base; } break; } } }; static leveldb::Options GetOptions(size_t nCacheSize) { leveldb::Options options; options.block_cache = leveldb::NewLRUCache(nCacheSize / 2); // up to two write buffers may be held in memory simultaneously options.write_buffer_size = nCacheSize / 4; options.filter_policy = leveldb::NewBloomFilterPolicy(10); options.compression = leveldb::kNoCompression; options.max_open_files = 64; options.info_log = new CBitcoinLevelDBLogger(); if (leveldb::kMajorVersion > 1 || (leveldb::kMajorVersion == 1 && leveldb::kMinorVersion >= 16)) { // LevelDB versions before 1.16 consider short writes to be corruption. // Only trigger error on corruption in later versions. options.paranoid_checks = true; } return options; } CDBWrapper::CDBWrapper(const fs::path &path, size_t nCacheSize, bool fMemory, bool fWipe, bool obfuscate) { penv = nullptr; readoptions.verify_checksums = true; iteroptions.verify_checksums = true; iteroptions.fill_cache = false; syncoptions.sync = true; options = GetOptions(nCacheSize); options.create_if_missing = true; if (fMemory) { penv = leveldb::NewMemEnv(leveldb::Env::Default()); options.env = penv; } else { if (fWipe) { LogPrintf("Wiping LevelDB in %s\n", path.string()); leveldb::Status result = leveldb::DestroyDB(path.string(), options); dbwrapper_private::HandleError(result); } TryCreateDirectories(path); LogPrintf("Opening LevelDB in %s\n", path.string()); } leveldb::Status status = leveldb::DB::Open(options, path.string(), &pdb); dbwrapper_private::HandleError(status); LogPrintf("Opened LevelDB successfully\n"); if (gArgs.GetBoolArg("-forcecompactdb", false)) { LogPrintf("Starting database compaction of %s\n", path.string()); pdb->CompactRange(nullptr, nullptr); LogPrintf("Finished database compaction of %s\n", path.string()); } // The base-case obfuscation key, which is a noop. obfuscate_key = std::vector(OBFUSCATE_KEY_NUM_BYTES, '\000'); bool key_exists = Read(OBFUSCATE_KEY_KEY, obfuscate_key); if (!key_exists && obfuscate && IsEmpty()) { // Initialize non-degenerate obfuscation if it won't upset existing, // non-obfuscated data. std::vector new_key = CreateObfuscateKey(); // Write `new_key` so we don't obfuscate the key with itself Write(OBFUSCATE_KEY_KEY, new_key); obfuscate_key = new_key; LogPrintf("Wrote new obfuscate key for %s: %s\n", path.string(), HexStr(obfuscate_key)); } LogPrintf("Using obfuscation key for %s: %s\n", path.string(), HexStr(obfuscate_key)); } CDBWrapper::~CDBWrapper() { delete pdb; pdb = nullptr; delete options.filter_policy; options.filter_policy = nullptr; delete options.info_log; options.info_log = nullptr; delete options.block_cache; options.block_cache = nullptr; delete penv; options.env = nullptr; } bool CDBWrapper::WriteBatch(CDBBatch &batch, bool fSync) { leveldb::Status status = pdb->Write(fSync ? syncoptions : writeoptions, &batch.batch); dbwrapper_private::HandleError(status); return true; } // Prefixed with null character to avoid collisions with other keys // // We must use a string constructor which specifies length so that we copy past // the null-terminator. const std::string CDBWrapper::OBFUSCATE_KEY_KEY("\000obfuscate_key", 14); const unsigned int CDBWrapper::OBFUSCATE_KEY_NUM_BYTES = 8; /** * Returns a string (consisting of 8 random bytes) suitable for use as an * obfuscating XOR key. */ std::vector CDBWrapper::CreateObfuscateKey() const { uint8_t buff[OBFUSCATE_KEY_NUM_BYTES]; GetRandBytes(buff, OBFUSCATE_KEY_NUM_BYTES); return std::vector(&buff[0], &buff[OBFUSCATE_KEY_NUM_BYTES]); } bool CDBWrapper::IsEmpty() { std::unique_ptr it(NewIterator()); it->SeekToFirst(); return !(it->Valid()); } CDBIterator::~CDBIterator() { delete piter; } -bool CDBIterator::Valid() { +bool CDBIterator::Valid() const { return piter->Valid(); } void CDBIterator::SeekToFirst() { piter->SeekToFirst(); } void CDBIterator::Next() { piter->Next(); } namespace dbwrapper_private { void HandleError(const leveldb::Status &status) { if (status.ok()) { return; } LogPrintf("%s\n", status.ToString()); if (status.IsCorruption()) { throw dbwrapper_error("Database corrupted"); } if (status.IsIOError()) { throw dbwrapper_error("Database I/O error"); } if (status.IsNotFound()) { throw dbwrapper_error("Database entry missing"); } throw dbwrapper_error("Unknown database error"); } const std::vector &GetObfuscateKey(const CDBWrapper &w) { return w.obfuscate_key; } }; // namespace dbwrapper_private diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 848d6bcb9..690a7425f 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -1,331 +1,331 @@ // Copyright (c) 2012-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. #ifndef BITCOIN_DBWRAPPER_H #define BITCOIN_DBWRAPPER_H #include "clientversion.h" #include "fs.h" #include "serialize.h" #include "streams.h" #include "util.h" #include "utilstrencodings.h" #include "version.h" #include #include static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64; static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024; class dbwrapper_error : public std::runtime_error { public: explicit dbwrapper_error(const std::string &msg) : std::runtime_error(msg) {} }; class CDBWrapper; /** * These should be considered an implementation detail of the specific database. */ namespace dbwrapper_private { /** * Handle database error by throwing dbwrapper_error exception. */ void HandleError(const leveldb::Status &status); /** * Work around circular dependency, as well as for testing in dbwrapper_tests. * Database obfuscation should be considered an implementation detail of the * specific database. */ const std::vector &GetObfuscateKey(const CDBWrapper &w); }; // namespace dbwrapper_private /** Batch of changes queued to be written to a CDBWrapper */ class CDBBatch { friend class CDBWrapper; private: const CDBWrapper &parent; leveldb::WriteBatch batch; CDataStream ssKey; CDataStream ssValue; size_t size_estimate; public: /** * @param[in] _parent CDBWrapper that this batch is to be submitted to */ explicit CDBBatch(const CDBWrapper &_parent) : parent(_parent), ssKey(SER_DISK, CLIENT_VERSION), ssValue(SER_DISK, CLIENT_VERSION), size_estimate(0){}; void Clear() { batch.Clear(); size_estimate = 0; } template void Write(const K &key, const V &value) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE); ssValue << value; ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); leveldb::Slice slValue(ssValue.data(), ssValue.size()); batch.Put(slKey, slValue); // LevelDB serializes writes as: // - byte: header // - varint: key length (1 byte up to 127B, 2 bytes up to 16383B, ...) // - byte[]: key // - varint: value length // - byte[]: value // The formula below assumes the key and value are both less than 16k. size_estimate += 3 + (slKey.size() > 127) + slKey.size() + (slValue.size() > 127) + slValue.size(); ssKey.clear(); ssValue.clear(); } template void Erase(const K &key) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); batch.Delete(slKey); // LevelDB serializes erases as: // - byte: header // - varint: key length // - byte[]: key // The formula below assumes the key is less than 16kB. size_estimate += 2 + (slKey.size() > 127) + slKey.size(); ssKey.clear(); } size_t SizeEstimate() const { return size_estimate; } }; class CDBIterator { private: const CDBWrapper &parent; leveldb::Iterator *piter; public: /** * @param[in] _parent Parent CDBWrapper instance. * @param[in] _piter The original leveldb iterator. */ CDBIterator(const CDBWrapper &_parent, leveldb::Iterator *_piter) : parent(_parent), piter(_piter){}; ~CDBIterator(); - bool Valid(); + bool Valid() const; void SeekToFirst(); template void Seek(const K &key) { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); piter->Seek(slKey); } void Next(); template bool GetKey(K &key) { leveldb::Slice slKey = piter->key(); try { CDataStream ssKey(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION); ssKey >> key; } catch (const std::exception &) { return false; } return true; } template bool GetValue(V &value) { leveldb::Slice slValue = piter->value(); try { CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION); ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); ssValue >> value; } catch (const std::exception &) { return false; } return true; } unsigned int GetValueSize() { return piter->value().size(); } }; class CDBWrapper { friend const std::vector & dbwrapper_private::GetObfuscateKey(const CDBWrapper &w); private: //! custom environment this database is using (may be nullptr in case of //! default environment) leveldb::Env *penv; //! database options used leveldb::Options options; //! options used when reading from the database leveldb::ReadOptions readoptions; //! options used when iterating over values of the database leveldb::ReadOptions iteroptions; //! options used when writing to the database leveldb::WriteOptions writeoptions; //! options used when sync writing to the database leveldb::WriteOptions syncoptions; //! the database itself leveldb::DB *pdb; //! a key used for optional XOR-obfuscation of the database std::vector obfuscate_key; //! the key under which the obfuscation key is stored static const std::string OBFUSCATE_KEY_KEY; //! the length of the obfuscate key in number of bytes static const unsigned int OBFUSCATE_KEY_NUM_BYTES; std::vector CreateObfuscateKey() const; public: /** * @param[in] path Location in the filesystem where leveldb data will * be stored. * @param[in] nCacheSize Configures various leveldb cache settings. * @param[in] fMemory If true, use leveldb's memory environment. * @param[in] fWipe If true, remove all existing data. * @param[in] obfuscate If true, store data obfuscated via simple XOR. If * false, XOR * with a zero'd byte array. */ CDBWrapper(const fs::path &path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false); ~CDBWrapper(); template bool Read(const K &key, V &value) const { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); std::string strValue; leveldb::Status status = pdb->Get(readoptions, slKey, &strValue); if (!status.ok()) { if (status.IsNotFound()) return false; LogPrintf("LevelDB read failure: %s\n", status.ToString()); dbwrapper_private::HandleError(status); } try { CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION); ssValue.Xor(obfuscate_key); ssValue >> value; } catch (const std::exception &) { return false; } return true; } template bool Write(const K &key, const V &value, bool fSync = false) { CDBBatch batch(*this); batch.Write(key, value); return WriteBatch(batch, fSync); } template bool Exists(const K &key) const { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; leveldb::Slice slKey(ssKey.data(), ssKey.size()); std::string strValue; leveldb::Status status = pdb->Get(readoptions, slKey, &strValue); if (!status.ok()) { if (status.IsNotFound()) return false; LogPrintf("LevelDB read failure: %s\n", status.ToString()); dbwrapper_private::HandleError(status); } return true; } template bool Erase(const K &key, bool fSync = false) { CDBBatch batch(*this); batch.Erase(key); return WriteBatch(batch, fSync); } bool WriteBatch(CDBBatch &batch, bool fSync = false); // not available for LevelDB; provide for compatibility with BDB bool Flush() { return true; } bool Sync() { CDBBatch batch(*this); return WriteBatch(batch, true); } CDBIterator *NewIterator() { return new CDBIterator(*this, pdb->NewIterator(iteroptions)); } /** * Return true if the database managed by this class contains no entries. */ bool IsEmpty(); template size_t EstimateSize(const K &key_begin, const K &key_end) const { CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION); ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey1 << key_begin; ssKey2 << key_end; leveldb::Slice slKey1(ssKey1.data(), ssKey1.size()); leveldb::Slice slKey2(ssKey2.data(), ssKey2.size()); uint64_t size = 0; leveldb::Range range(slKey1, slKey2); pdb->GetApproximateSizes(&range, 1, &size); return size; } /** * Compact a certain range of keys in the database. */ template void CompactRange(const K &key_begin, const K &key_end) const { CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION); ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey1 << key_begin; ssKey2 << key_end; leveldb::Slice slKey1(ssKey1.data(), ssKey1.size()); leveldb::Slice slKey2(ssKey2.data(), ssKey2.size()); pdb->CompactRange(&slKey1, &slKey2); } }; #endif // BITCOIN_DBWRAPPER_H diff --git a/src/merkleblock.h b/src/merkleblock.h index 1ca2c264d..eccd04145 100644 --- a/src/merkleblock.h +++ b/src/merkleblock.h @@ -1,184 +1,184 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #ifndef BITCOIN_MERKLEBLOCK_H #define BITCOIN_MERKLEBLOCK_H #include "bloom.h" #include "primitives/block.h" #include "serialize.h" #include "uint256.h" #include /** * Data structure that represents a partial merkle tree. * * It represents a subset of the txid's of a known block, in a way that * allows recovery of the list of txid's and the merkle root, in an * authenticated way. * * The encoding works as follows: we traverse the tree in depth-first order, * storing a bit for each traversed node, signifying whether the node is the * parent of at least one matched leaf txid (or a matched txid itself). In * case we are at the leaf level, or this bit is 0, its merkle node hash is * stored, and its children are not explorer further. Otherwise, no hash is * stored, but we recurse into both (or the only) child branch. During * decoding, the same depth-first traversal is performed, consuming bits and * hashes as they written during encoding. * * The serialization is fixed and provides a hard guarantee about the * encoded size: * * SIZE <= 10 + ceil(32.25*N) * * Where N represents the number of leaf nodes of the partial tree. N itself * is bounded by: * * N <= total_transactions * N <= 1 + matched_transactions*tree_height * * The serialization format: * - uint32 total_transactions (4 bytes) * - varint number of hashes (1-3 bytes) * - uint256[] hashes in depth-first order (<= 32*N bytes) * - varint number of bytes of flag bits (1-3 bytes) * - byte[] flag bits, packed per 8 in a byte, least significant bit first * (<= 2*N-1 bits) * The size constraints follow from this. */ class CPartialMerkleTree { protected: /** the total number of transactions in the block */ unsigned int nTransactions; /** node-is-parent-of-matched-txid bits */ std::vector vBits; /** txids and internal hashes */ std::vector vHash; /** flag set when encountering invalid data */ bool fBad; /** * Helper function to efficiently calculate the number of nodes at given * height in the merkle tree. */ - unsigned int CalcTreeWidth(int height) { + unsigned int CalcTreeWidth(int height) const { return (nTransactions + (1 << height) - 1) >> height; } /** * Calculate the hash of a node in the merkle tree (at leaf level: the * txid's themselves) */ uint256 CalcHash(int height, unsigned int pos, const std::vector &vTxid); /** * Recursive function that traverses tree nodes, storing the data as bits * and hashes. */ void TraverseAndBuild(int height, unsigned int pos, const std::vector &vTxid, const std::vector &vMatch); /** * Recursive function that traverses tree nodes, consuming the bits and * hashes produced by TraverseAndBuild. It returns the hash of the * respective node and its respective index. */ uint256 TraverseAndExtract(int height, unsigned int pos, unsigned int &nBitsUsed, unsigned int &nHashUsed, std::vector &vMatch, std::vector &vnIndex); public: /** serialization implementation */ ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(nTransactions); READWRITE(vHash); std::vector vBytes; if (ser_action.ForRead()) { READWRITE(vBytes); CPartialMerkleTree &us = *(const_cast(this)); us.vBits.resize(vBytes.size() * 8); for (unsigned int p = 0; p < us.vBits.size(); p++) { us.vBits[p] = (vBytes[p / 8] & (1 << (p % 8))) != 0; } us.fBad = false; } else { vBytes.resize((vBits.size() + 7) / 8); for (unsigned int p = 0; p < vBits.size(); p++) { vBytes[p / 8] |= vBits[p] << (p % 8); } READWRITE(vBytes); } } /** * Construct a partial merkle tree from a list of transaction ids, and a * mask that selects a subset of them. */ CPartialMerkleTree(const std::vector &vTxid, const std::vector &vMatch); CPartialMerkleTree(); /** * Extract the matching txid's represented by this partial merkle tree and * their respective indices within the partial tree. Returns the merkle * root, or 0 in case of failure. */ uint256 ExtractMatches(std::vector &vMatch, std::vector &vnIndex); }; /** * Used to create a Merkle proof (usually from a subset of transactions), * which consists of a block header and partial Merkle Tree. * SPV clients typically use this Merkle proof to limit bandwidth and * computation requirements to process incoming transactions. * From the peer-node's perspective, the SPV client is a "filtered node". * See BIP37 for details: * https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki */ class CMerkleBlock { public: /** Public only for unit testing */ CBlockHeader header; CPartialMerkleTree txn; /** Public only for unit testing and relay testing (not relayed) */ std::vector> vMatchedTxn; /** * Create a Merkle proof according to a bloom filter. Note * that this will call IsRelevantAndUpdate on the filter for each * transaction, thus the filter will likely be modified. */ CMerkleBlock(const CBlock &block, CBloomFilter &filter); /** * Create a Merkle proof for a set of transactions. */ CMerkleBlock(const CBlock &block, const std::set &txids); CMerkleBlock() {} ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(header); READWRITE(txn); } }; #endif // BITCOIN_MERKLEBLOCK_H diff --git a/src/miner.cpp b/src/miner.cpp index a156d8634..487ed3b40 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -1,713 +1,714 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #include "miner.h" #include "amount.h" #include "chain.h" #include "chainparams.h" #include "coins.h" #include "config.h" #include "consensus/activation.h" #include "consensus/consensus.h" #include "consensus/merkle.h" #include "consensus/tx_verify.h" #include "consensus/validation.h" #include "hash.h" #include "net.h" #include "policy/policy.h" #include "pow.h" #include "primitives/transaction.h" #include "script/standard.h" #include "timedata.h" #include "txmempool.h" #include "util.h" #include "utilmoneystr.h" #include "validation.h" #include "validationinterface.h" #include #include #include ////////////////////////////////////////////////////////////////////////////// // // BitcoinMiner // // // Unconfirmed transactions in the memory pool often depend on other // transactions in the memory pool. When we select transactions from the // pool, we select by highest priority or fee rate, so we might consider // transactions that depend on transactions that aren't yet in the block. uint64_t nLastBlockTx = 0; uint64_t nLastBlockSize = 0; int64_t UpdateTime(CBlockHeader *pblock, const Config &config, const CBlockIndex *pindexPrev) { int64_t nOldTime = pblock->nTime; int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast() + 1, GetAdjustedTime()); if (nOldTime < nNewTime) { pblock->nTime = nNewTime; } const Consensus::Params &consensusParams = config.GetChainParams().GetConsensus(); // Updating time can change work required on testnet: if (consensusParams.fPowAllowMinDifficultyBlocks) { pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, config); } return nNewTime - nOldTime; } static uint64_t ComputeMaxGeneratedBlockSize(const Config &config, const CBlockIndex *pindexPrev) { // Block resource limits // If -blockmaxsize is not given, limit to DEFAULT_MAX_GENERATED_BLOCK_SIZE // If only one is given, only restrict the specified resource. // If both are given, restrict both. uint64_t nMaxGeneratedBlockSize = DEFAULT_MAX_GENERATED_BLOCK_SIZE; if (gArgs.IsArgSet("-blockmaxsize")) { nMaxGeneratedBlockSize = gArgs.GetArg("-blockmaxsize", DEFAULT_MAX_GENERATED_BLOCK_SIZE); } // Limit size to between 1K and MaxBlockSize-1K for sanity: nMaxGeneratedBlockSize = std::max(uint64_t(1000), std::min(config.GetMaxBlockSize() - 1000, nMaxGeneratedBlockSize)); return nMaxGeneratedBlockSize; } BlockAssembler::BlockAssembler(const Config &_config, const CTxMemPool &mpool) : config(&_config), mempool(&mpool) { if (gArgs.IsArgSet("-blockmintxfee")) { Amount n = Amount::zero(); ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n); blockMinFeeRate = CFeeRate(n); } else { blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB); } LOCK(cs_main); nMaxGeneratedBlockSize = ComputeMaxGeneratedBlockSize(*config, chainActive.Tip()); } void BlockAssembler::resetBlock() { inBlock.clear(); // Reserve space for coinbase tx. nBlockSize = 1000; nBlockSigOps = 100; // These counters do not include coinbase tx. nBlockTx = 0; nFees = Amount::zero(); lastFewTxs = 0; } static const std::vector getExcessiveBlockSizeSig(const Config &config) { std::string cbmsg = "/EB" + getSubVersionEB(config.GetMaxBlockSize()) + "/"; const char *cbcstr = cbmsg.c_str(); std::vector vec(cbcstr, cbcstr + cbmsg.size()); return vec; } std::unique_ptr BlockAssembler::CreateNewBlock(const CScript &scriptPubKeyIn) { int64_t nTimeStart = GetTimeMicros(); resetBlock(); pblocktemplate.reset(new CBlockTemplate()); if (!pblocktemplate.get()) { return nullptr; } // Pointer for convenience. pblock = &pblocktemplate->block; // Add dummy coinbase tx as first transaction. It is updated at the end. pblocktemplate->entries.emplace_back(CTransactionRef(), -SATOSHI, -1); LOCK2(cs_main, mempool->cs); CBlockIndex *pindexPrev = chainActive.Tip(); nHeight = pindexPrev->nHeight + 1; const CChainParams &chainparams = config->GetChainParams(); pblock->nVersion = ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios if (chainparams.MineBlocksOnDemand()) { pblock->nVersion = gArgs.GetArg("-blockversion", pblock->nVersion); } pblock->nTime = GetAdjustedTime(); nMaxGeneratedBlockSize = ComputeMaxGeneratedBlockSize(*config, pindexPrev); nMedianTimePast = pindexPrev->GetMedianTimePast(); nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST) ? nMedianTimePast : pblock->GetBlockTime(); addPriorityTxs(); int nPackagesSelected = 0; int nDescendantsUpdated = 0; addPackageTxs(nPackagesSelected, nDescendantsUpdated); if (IsMagneticAnomalyEnabled(*config, pindexPrev)) { // If magnetic anomaly is enabled, we make sure transaction are // canonically ordered. // FIXME: Use a zipped list. See T479 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(); nLastBlockTx = nBlockTx; nLastBlockSize = nBlockSize; // Create coinbase transaction. CMutableTransaction coinbaseTx; coinbaseTx.vin.resize(1); coinbaseTx.vin[0].prevout = COutPoint(); coinbaseTx.vout.resize(1); coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn; coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus()); coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; // Make sure the coinbase is big enough. uint64_t coinbaseSize = ::GetSerializeSize(coinbaseTx, SER_NETWORK, PROTOCOL_VERSION); if (coinbaseSize < MIN_TX_SIZE) { coinbaseTx.vin[0].scriptSig << std::vector(MIN_TX_SIZE - coinbaseSize - 1); } pblocktemplate->entries[0].tx = MakeTransactionRef(coinbaseTx); pblocktemplate->entries[0].fees = -1 * nFees; uint64_t nSerializeSize = GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION); LogPrintf("CreateNewBlock(): total size: %u txs: %u fees: %ld sigops %d\n", nSerializeSize, nBlockTx, nFees, nBlockSigOps); // Fill in header. pblock->hashPrevBlock = pindexPrev->GetBlockHash(); UpdateTime(pblock, *config, pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, *config); pblock->nNonce = 0; pblocktemplate->entries[0].sigOpCount = GetSigOpCountWithoutP2SH( *pblocktemplate->entries[0].tx, STANDARD_CHECKDATASIG_VERIFY_FLAGS); // Copy all the transactions into the block // FIXME: This should be removed as it is significant overhead. // See T479 for (const CBlockTemplateEntry &tx : pblocktemplate->entries) { pblock->vtx.push_back(tx.tx); } CValidationState state; BlockValidationOptions validationOptions(false, false); if (!TestBlockValidity(*config, state, *pblock, pindexPrev, validationOptions)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state))); } int64_t nTime2 = GetTimeMicros(); LogPrint( BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d " "updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart)); return std::move(pblocktemplate); } bool BlockAssembler::isStillDependent(CTxMemPool::txiter iter) { for (CTxMemPool::txiter parent : mempool->GetMemPoolParents(iter)) { if (!inBlock.count(parent)) { return true; } } return false; } void BlockAssembler::onlyUnconfirmed(CTxMemPool::setEntries &testSet) { for (CTxMemPool::setEntries::iterator iit = testSet.begin(); iit != testSet.end();) { // Only test txs not already in the block. if (inBlock.count(*iit)) { testSet.erase(iit++); } else { iit++; } } } -bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOps) { +bool BlockAssembler::TestPackage(uint64_t packageSize, + int64_t packageSigOps) const { auto blockSizeWithPackage = nBlockSize + packageSize; if (blockSizeWithPackage >= nMaxGeneratedBlockSize) { return false; } if (nBlockSigOps + packageSigOps >= GetMaxBlockSigOpsCount(blockSizeWithPackage)) { return false; } return true; } /** * Perform transaction-level checks before adding to block: * - Transaction finality (locktime) * - Serialized size (in case -blockmaxsize is in use) */ bool BlockAssembler::TestPackageTransactions( const CTxMemPool::setEntries &package) { uint64_t nPotentialBlockSize = nBlockSize; for (const CTxMemPool::txiter it : package) { CValidationState state; if (!ContextualCheckTransaction(*config, it->GetTx(), state, nHeight, nLockTimeCutoff, nMedianTimePast)) { return false; } uint64_t nTxSize = ::GetSerializeSize(it->GetTx(), SER_NETWORK, PROTOCOL_VERSION); if (nPotentialBlockSize + nTxSize >= nMaxGeneratedBlockSize) { return false; } nPotentialBlockSize += nTxSize; } return true; } BlockAssembler::TestForBlockResult BlockAssembler::TestForBlock(CTxMemPool::txiter it) { auto blockSizeWithTx = nBlockSize + ::GetSerializeSize(it->GetTx(), SER_NETWORK, PROTOCOL_VERSION); if (blockSizeWithTx >= nMaxGeneratedBlockSize) { if (nBlockSize > nMaxGeneratedBlockSize - 100 || lastFewTxs > 50) { return TestForBlockResult::BlockFinished; } if (nBlockSize > nMaxGeneratedBlockSize - 1000) { lastFewTxs++; } return TestForBlockResult::TXCantFit; } auto maxBlockSigOps = GetMaxBlockSigOpsCount(blockSizeWithTx); if (nBlockSigOps + it->GetSigOpCount() >= maxBlockSigOps) { // If the block has room for no more sig ops then flag that the block is // finished. // TODO: We should consider adding another transaction that isn't very // dense in sigops instead of bailing out so easily. if (nBlockSigOps > maxBlockSigOps - 2) { return TestForBlockResult::BlockFinished; } // Otherwise attempt to find another tx with fewer sigops to put in the // block. return TestForBlockResult::TXCantFit; } // Must check that lock times are still valid. This can be removed once MTP // is always enforced as long as reorgs keep the mempool consistent. CValidationState state; if (!ContextualCheckTransaction(*config, it->GetTx(), state, nHeight, nLockTimeCutoff, nMedianTimePast)) { return TestForBlockResult::TXCantFit; } return TestForBlockResult::TXFits; } void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) { pblocktemplate->entries.emplace_back(iter->GetSharedTx(), iter->GetFee(), iter->GetSigOpCount()); nBlockSize += iter->GetTxSize(); ++nBlockTx; nBlockSigOps += iter->GetSigOpCount(); nFees += iter->GetFee(); inBlock.insert(iter); bool fPrintPriority = gArgs.GetBoolArg("-printpriority", DEFAULT_PRINTPRIORITY); if (fPrintPriority) { double dPriority = iter->GetPriority(nHeight); Amount dummy; mempool->ApplyDeltas(iter->GetTx().GetId(), dPriority, dummy); LogPrintf( "priority %.1f fee %s txid %s\n", dPriority, CFeeRate(iter->GetModifiedFee(), iter->GetTxSize()).ToString(), iter->GetTx().GetId().ToString()); } } int BlockAssembler::UpdatePackagesForAdded( const CTxMemPool::setEntries &alreadyAdded, indexed_modified_transaction_set &mapModifiedTx) { int nDescendantsUpdated = 0; for (const CTxMemPool::txiter it : alreadyAdded) { CTxMemPool::setEntries descendants; mempool->CalculateDescendants(it, descendants); // Insert all descendants (not yet in block) into the modified set. for (CTxMemPool::txiter desc : descendants) { if (alreadyAdded.count(desc)) { continue; } ++nDescendantsUpdated; modtxiter mit = mapModifiedTx.find(desc); if (mit == mapModifiedTx.end()) { CTxMemPoolModifiedEntry modEntry(desc); modEntry.nSizeWithAncestors -= it->GetTxSize(); modEntry.nBillableSizeWithAncestors -= it->GetTxBillableSize(); modEntry.nModFeesWithAncestors -= it->GetModifiedFee(); modEntry.nSigOpCountWithAncestors -= it->GetSigOpCount(); mapModifiedTx.insert(modEntry); } else { mapModifiedTx.modify(mit, update_for_parent_inclusion(it)); } } } return nDescendantsUpdated; } // Skip entries in mapTx that are already in a block or are present in // mapModifiedTx (which implies that the mapTx ancestor state is stale due to // ancestor inclusion in the block). Also skip transactions that we've already // failed to add. This can happen if we consider a transaction in mapModifiedTx // and it fails: we can then potentially consider it again while walking mapTx. // It's currently guaranteed to fail again, but as a belt-and-suspenders check // we put it in failedTx and avoid re-evaluation, since the re-evaluation would // be using cached size/sigops/fee values that are not actually correct. bool BlockAssembler::SkipMapTxEntry( CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx) { assert(it != mempool->mapTx.end()); return mapModifiedTx.count(it) || inBlock.count(it) || failedTx.count(it); } void BlockAssembler::SortForBlock( const CTxMemPool::setEntries &package, CTxMemPool::txiter entry, std::vector &sortedEntries) { // Sort package by ancestor count. If a transaction A depends on transaction // B, then A's ancestor count must be greater than B's. So this is // sufficient to validly order the transactions for block inclusion. sortedEntries.clear(); sortedEntries.insert(sortedEntries.begin(), package.begin(), package.end()); std::sort(sortedEntries.begin(), sortedEntries.end(), CompareTxIterByAncestorCount()); } /** * addPackageTx includes transactions paying a fee by ensuring that * the partial ordering of transactions is maintained. That is to say * children come after parents, despite having a potentially larger fee. * @param[out] nPackagesSelected How many packages were selected * @param[out] nDescendantsUpdated Number of descendant transactions updated */ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated) { // selection algorithm orders the mempool based on feerate of a // transaction including all unconfirmed ancestors. Since we don't remove // transactions from the mempool as we select them for block inclusion, we // need an alternate method of updating the feerate of a transaction with // its not-yet-selected ancestors as we go. This is accomplished by // walking the in-mempool descendants of selected transactions and storing // a temporary modified state in mapModifiedTxs. Each time through the // loop, we compare the best transaction in mapModifiedTxs with the next // transaction in the mempool to decide what transaction package to work // on next. // mapModifiedTx will store sorted packages after they are modified because // some of their txs are already in the block. indexed_modified_transaction_set mapModifiedTx; // Keep track of entries that failed inclusion, to avoid duplicate work. CTxMemPool::setEntries failedTx; // Start by adding all descendants of previously added txs to mapModifiedTx // and modifying them for their already included ancestors. UpdatePackagesForAdded(inBlock, mapModifiedTx); CTxMemPool::indexed_transaction_set::index::type::iterator mi = mempool->mapTx.get().begin(); CTxMemPool::txiter iter; // Limit the number of attempts to add transactions to the block when it is // close to full; this is just a simple heuristic to finish quickly if the // mempool has a lot of entries. const int64_t MAX_CONSECUTIVE_FAILURES = 1000; int64_t nConsecutiveFailed = 0; while (mi != mempool->mapTx.get().end() || !mapModifiedTx.empty()) { // First try to find a new transaction in mapTx to evaluate. if (mi != mempool->mapTx.get().end() && SkipMapTxEntry(mempool->mapTx.project<0>(mi), mapModifiedTx, failedTx)) { ++mi; continue; } // Now that mi is not stale, determine which transaction to evaluate: // the next entry from mapTx, or the best from mapModifiedTx? bool fUsingModified = false; modtxscoreiter modit = mapModifiedTx.get().begin(); if (mi == mempool->mapTx.get().end()) { // We're out of entries in mapTx; use the entry from mapModifiedTx iter = modit->iter; fUsingModified = true; } else { // Try to compare the mapTx entry to the mapModifiedTx entry. iter = mempool->mapTx.project<0>(mi); if (modit != mapModifiedTx.get().end() && CompareModifiedEntry()(*modit, CTxMemPoolModifiedEntry(iter))) { // The best entry in mapModifiedTx has higher score than the one // from mapTx. Switch which transaction (package) to consider iter = modit->iter; fUsingModified = true; } else { // Either no entry in mapModifiedTx, or it's worse than mapTx. // Increment mi for the next loop iteration. ++mi; } } // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't // contain anything that is inBlock. assert(!inBlock.count(iter)); uint64_t packageSize = iter->GetSizeWithAncestors(); Amount packageFees = iter->GetModFeesWithAncestors(); int64_t packageSigOps = iter->GetSigOpCountWithAncestors(); if (fUsingModified) { packageSize = modit->nSizeWithAncestors; packageFees = modit->nModFeesWithAncestors; packageSigOps = modit->nSigOpCountWithAncestors; } if (packageFees < blockMinFeeRate.GetFee(packageSize)) { // Everything else we might consider has a lower fee rate return; } if (!TestPackage(packageSize, packageSigOps)) { if (fUsingModified) { // Since we always look at the best entry in mapModifiedTx, we // must erase failed entries so that we can consider the next // best entry on the next loop iteration mapModifiedTx.get().erase(modit); failedTx.insert(iter); } ++nConsecutiveFailed; if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockSize > nMaxGeneratedBlockSize - 1000) { // Give up if we're close to full and haven't succeeded in a // while. break; } continue; } CTxMemPool::setEntries ancestors; uint64_t nNoLimit = std::numeric_limits::max(); std::string dummy; mempool->CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); onlyUnconfirmed(ancestors); ancestors.insert(iter); // Test if all tx's are Final. if (!TestPackageTransactions(ancestors)) { if (fUsingModified) { mapModifiedTx.get().erase(modit); failedTx.insert(iter); } continue; } // This transaction will make it in; reset the failed counter. nConsecutiveFailed = 0; // Package can be added. Sort the entries in a valid order. std::vector sortedEntries; SortForBlock(ancestors, iter, sortedEntries); for (auto &entry : sortedEntries) { AddToBlock(entry); // Erase from the modified set, if present mapModifiedTx.erase(entry); } ++nPackagesSelected; // Update transactions that depend on each of these nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx); } } void BlockAssembler::addPriorityTxs() { // How much of the block should be dedicated to high-priority transactions, // included regardless of the fees they pay. if (config->GetBlockPriorityPercentage() == 0) { return; } uint64_t nBlockPrioritySize = nMaxGeneratedBlockSize * config->GetBlockPriorityPercentage() / 100; // This vector will be sorted into a priority queue: std::vector vecPriority; TxCoinAgePriorityCompare pricomparer; std::map waitPriMap; typedef std::map::iterator waitPriIter; double actualPriority = -1; vecPriority.reserve(mempool->mapTx.size()); for (CTxMemPool::indexed_transaction_set::iterator mi = mempool->mapTx.begin(); mi != mempool->mapTx.end(); ++mi) { double dPriority = mi->GetPriority(nHeight); Amount dummy; mempool->ApplyDeltas(mi->GetTx().GetId(), dPriority, dummy); vecPriority.push_back(TxCoinAgePriority(dPriority, mi)); } std::make_heap(vecPriority.begin(), vecPriority.end(), pricomparer); CTxMemPool::txiter iter; // Add a tx from priority queue to fill the part of block reserved to // priority transactions. while (!vecPriority.empty()) { iter = vecPriority.front().second; actualPriority = vecPriority.front().first; std::pop_heap(vecPriority.begin(), vecPriority.end(), pricomparer); vecPriority.pop_back(); // If tx already in block, skip. if (inBlock.count(iter)) { // Shouldn't happen for priority txs. assert(false); continue; } // If tx is dependent on other mempool txs which haven't yet been // included then put it in the waitSet. if (isStillDependent(iter)) { waitPriMap.insert(std::make_pair(iter, actualPriority)); continue; } TestForBlockResult testResult = TestForBlock(iter); // Break if the block is completed if (testResult == TestForBlockResult::BlockFinished) { break; } // If this tx does not fit in the block, skip to next transaction. if (testResult != TestForBlockResult::TXFits) { continue; } AddToBlock(iter); // If now that this txs is added we've surpassed our desired priority // size, then we're done adding priority transactions. if (nBlockSize >= nBlockPrioritySize) { break; } // if we have dropped below the AllowFreeThreshold, then we're done // adding priority transactions. if (!AllowFree(actualPriority)) { break; } // This tx was successfully added, so add transactions that depend // on this one to the priority queue to try again. for (CTxMemPool::txiter child : mempool->GetMemPoolChildren(iter)) { waitPriIter wpiter = waitPriMap.find(child); if (wpiter == waitPriMap.end()) { continue; } vecPriority.push_back(TxCoinAgePriority(wpiter->second, child)); std::push_heap(vecPriority.begin(), vecPriority.end(), pricomparer); waitPriMap.erase(wpiter); } } } void IncrementExtraNonce(const Config &config, CBlock *pblock, const CBlockIndex *pindexPrev, unsigned int &nExtraNonce) { // Update nExtraNonce static uint256 hashPrevBlock; if (hashPrevBlock != pblock->hashPrevBlock) { nExtraNonce = 0; hashPrevBlock = pblock->hashPrevBlock; } ++nExtraNonce; // Height first in coinbase required for block.version=2 unsigned int nHeight = pindexPrev->nHeight + 1; CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce) << getExcessiveBlockSizeSig(config)) + COINBASE_FLAGS; // Make sure the coinbase is big enough. uint64_t coinbaseSize = ::GetSerializeSize(txCoinbase, SER_NETWORK, PROTOCOL_VERSION); if (coinbaseSize < MIN_TX_SIZE) { txCoinbase.vin[0].scriptSig << std::vector(MIN_TX_SIZE - coinbaseSize - 1); } assert(txCoinbase.vin[0].scriptSig.size() <= MAX_COINBASE_SCRIPTSIG_SIZE); assert(::GetSerializeSize(txCoinbase, SER_NETWORK, PROTOCOL_VERSION) >= MIN_TX_SIZE); pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); } diff --git a/src/miner.h b/src/miner.h index 97941e02c..2a9e11c04 100644 --- a/src/miner.h +++ b/src/miner.h @@ -1,237 +1,237 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #ifndef BITCOIN_MINER_H #define BITCOIN_MINER_H #include "primitives/block.h" #include "txmempool.h" #include #include #include #include class CBlockIndex; class CChainParams; class Config; class CReserveKey; class CScript; class CWallet; 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 entries; }; // Container for tracking updates to ancestor feerate as we include (parent) // transactions in a block struct CTxMemPoolModifiedEntry { explicit CTxMemPoolModifiedEntry(CTxMemPool::txiter entry) { iter = entry; nSizeWithAncestors = entry->GetSizeWithAncestors(); nBillableSizeWithAncestors = entry->GetBillableSizeWithAncestors(); nModFeesWithAncestors = entry->GetModFeesWithAncestors(); nSigOpCountWithAncestors = entry->GetSigOpCountWithAncestors(); } CTxMemPool::txiter iter; uint64_t nSizeWithAncestors; uint64_t nBillableSizeWithAncestors; Amount nModFeesWithAncestors; int64_t nSigOpCountWithAncestors; }; /** * Comparator for CTxMemPool::txiter objects. * It simply compares the internal memory address of the CTxMemPoolEntry object * pointed to. This means it has no meaning, and is only useful for using them * as key in other indexes. */ struct CompareCTxMemPoolIter { bool operator()(const CTxMemPool::txiter &a, const CTxMemPool::txiter &b) const { return &(*a) < &(*b); } }; struct modifiedentry_iter { typedef CTxMemPool::txiter result_type; result_type operator()(const CTxMemPoolModifiedEntry &entry) const { return entry.iter; } }; // This matches the calculation in CompareTxMemPoolEntryByAncestorFee, // except operating on CTxMemPoolModifiedEntry. // TODO: refactor to avoid duplication of this logic. struct CompareModifiedEntry { bool operator()(const CTxMemPoolModifiedEntry &a, const CTxMemPoolModifiedEntry &b) const { double f1 = b.nSizeWithAncestors * (a.nModFeesWithAncestors / SATOSHI); double f2 = a.nSizeWithAncestors * (b.nModFeesWithAncestors / SATOSHI); if (f1 == f2) { return CTxMemPool::CompareIteratorByHash()(a.iter, b.iter); } return f1 > f2; } }; // A comparator that sorts transactions based on number of ancestors. // This is sufficient to sort an ancestor package in an order that is valid // to appear in a block. struct CompareTxIterByAncestorCount { bool operator()(const CTxMemPool::txiter &a, const CTxMemPool::txiter &b) const { if (a->GetCountWithAncestors() != b->GetCountWithAncestors()) { return a->GetCountWithAncestors() < b->GetCountWithAncestors(); } return CTxMemPool::CompareIteratorByHash()(a, b); } }; typedef boost::multi_index_container< CTxMemPoolModifiedEntry, boost::multi_index::indexed_by< boost::multi_index::ordered_unique, // sorted by modified ancestor fee rate boost::multi_index::ordered_non_unique< // Reuse same tag from CTxMemPool's similar index boost::multi_index::tag, boost::multi_index::identity, CompareModifiedEntry>>> indexed_modified_transaction_set; typedef indexed_modified_transaction_set::nth_index<0>::type::iterator modtxiter; typedef indexed_modified_transaction_set::index::type::iterator modtxscoreiter; struct update_for_parent_inclusion { explicit update_for_parent_inclusion(CTxMemPool::txiter it) : iter(it) {} void operator()(CTxMemPoolModifiedEntry &e) { e.nModFeesWithAncestors -= iter->GetFee(); e.nSizeWithAncestors -= iter->GetTxSize(); e.nBillableSizeWithAncestors -= iter->GetTxBillableSize(); e.nSigOpCountWithAncestors -= iter->GetSigOpCount(); } CTxMemPool::txiter iter; }; /** Generate a new block, without valid proof-of-work */ class BlockAssembler { private: // The constructed block template std::unique_ptr pblocktemplate; // A convenience pointer that always refers to the CBlock in pblocktemplate CBlock *pblock; // Configuration parameters for the block size uint64_t nMaxGeneratedBlockSize; CFeeRate blockMinFeeRate; // Information on the current status of the block uint64_t nBlockSize; uint64_t nBlockTx; uint64_t nBlockSigOps; Amount nFees; CTxMemPool::setEntries inBlock; // Chain context for the block int nHeight; int64_t nLockTimeCutoff; int64_t nMedianTimePast; const Config *config; const CTxMemPool *mempool; // Variables used for addPriorityTxs int lastFewTxs; public: BlockAssembler(const Config &_config, const CTxMemPool &mempool); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr CreateNewBlock(const CScript &scriptPubKeyIn); uint64_t GetMaxGeneratedBlockSize() const { return nMaxGeneratedBlockSize; } private: // utility functions /** Clear the block's state and prepare for assembling a new block */ void resetBlock(); /** Add a tx to the block */ void AddToBlock(CTxMemPool::txiter iter); // Methods for how to add transactions to a block. /** Add transactions based on tx "priority" */ void addPriorityTxs(); /** Add transactions based on feerate including unconfirmed ancestors * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated); /** Enum for the results from TestForBlock */ enum class TestForBlockResult : uint8_t { TXFits = 0, TXCantFit = 1, BlockFinished = 3, }; // helper function for addPriorityTxs /** Test if tx will still "fit" in the block */ TestForBlockResult TestForBlock(CTxMemPool::txiter iter); /** Test if tx still has unconfirmed parents not yet in block */ bool isStillDependent(CTxMemPool::txiter iter); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ void onlyUnconfirmed(CTxMemPool::setEntries &testSet); /** Test if a new package would "fit" in the block */ - bool TestPackage(uint64_t packageSize, int64_t packageSigOpsCost); + bool TestPackage(uint64_t packageSize, int64_t packageSigOpsCost) const; /** Perform checks on each transaction in a package: * locktime, serialized size (if necessary) * These checks should always succeed, and they're here * only as an extra check in case of suboptimal node configuration */ bool TestPackageTransactions(const CTxMemPool::setEntries &package); /** Return true if given transaction from mapTx has already been evaluated, * or if the transaction's cached data in mapTx is incorrect. */ bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, CTxMemPool::setEntries &failedTx); /** Sort the package in an order that is valid to appear in a block */ void SortForBlock(const CTxMemPool::setEntries &package, CTxMemPool::txiter entry, std::vector &sortedEntries); /** Add descendants of given transactions to mapModifiedTx with ancestor * state updated assuming given transactions are inBlock. Returns number * of updated descendants. */ int UpdatePackagesForAdded(const CTxMemPool::setEntries &alreadyAdded, indexed_modified_transaction_set &mapModifiedTx); }; /** Modify the extranonce in a block */ void IncrementExtraNonce(const Config &config, CBlock *pblock, const CBlockIndex *pindexPrev, unsigned int &nExtraNonce); int64_t UpdateTime(CBlockHeader *pblock, const Config &config, const CBlockIndex *pindexPrev); #endif // BITCOIN_MINER_H diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index d9e91122a..6de4acec0 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -1,821 +1,821 @@ // Copyright (c) 2011-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. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "bitcoingui.h" #include "chainparams.h" #include "clientmodel.h" #include "config.h" #include "guiconstants.h" #include "guiutil.h" #include "httprpc.h" #include "intro.h" #include "networkstyle.h" #include "optionsmodel.h" #include "platformstyle.h" #include "splashscreen.h" #include "utilitydialog.h" #include "winshutdownmonitor.h" #ifdef ENABLE_WALLET #include "paymentserver.h" #include "walletmodel.h" #endif #include "init.h" #include "rpc/server.h" #include "ui_interface.h" #include "uint256.h" #include "util.h" #include "warnings.h" #ifdef ENABLE_WALLET #include "wallet/wallet.h" #endif #include "walletinitinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(QT_STATICPLUGIN) #include #if QT_VERSION < 0x050400 Q_IMPORT_PLUGIN(AccessibleFactory) #endif #if defined(QT_QPA_PLATFORM_XCB) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_WINDOWS) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); #endif #endif // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool *) Q_DECLARE_METATYPE(Amount) Q_DECLARE_METATYPE(uint256) // Config is non-copyable so we can only register pointers to it Q_DECLARE_METATYPE(Config *) static void InitMessage(const std::string &message) { LogPrintf("init message: %s\n", message); } /** * Translate string to current locale using Qt. */ static std::string Translate(const char *psz) { return QCoreApplication::translate("bitcoin-abc", psz).toStdString(); } static QString GetLangTerritory() { QSettings settings; // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = QLocale::system().name(); // 2) Language from QSettings QString lang_territory_qsettings = settings.value("language", "").toString(); if (!lang_territory_qsettings.isEmpty()) { lang_territory = lang_territory_qsettings; } // 3) -lang command line argument lang_territory = QString::fromStdString( gArgs.GetArg("-lang", lang_territory.toStdString())); return lang_territory; } /** Set up translations */ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) { // Remove old translators QApplication::removeTranslator(&qtTranslatorBase); QApplication::removeTranslator(&qtTranslator); QApplication::removeTranslator(&translatorBase); QApplication::removeTranslator(&translator); // Get desired locale (e.g. "de_DE") // 1) System default language QString lang_territory = GetLangTerritory(); // Convert to "de" only by truncating "_DE" QString lang = lang_territory; lang.truncate(lang_territory.lastIndexOf('_')); // Load language files for configured locale: // - First load the translator for the base language, without territory // - Then load the more specific locale translator // Load e.g. qt_de.qm if (qtTranslatorBase.load( "qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { QApplication::installTranslator(&qtTranslatorBase); } // Load e.g. qt_de_DE.qm if (qtTranslator.load( "qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { QApplication::installTranslator(&qtTranslator); } // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in // bitcoin.qrc) if (translatorBase.load(lang, ":/translations/")) { QApplication::installTranslator(&translatorBase); } // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in // bitcoin.qrc) if (translator.load(lang_territory, ":/translations/")) { QApplication::installTranslator(&translator); } } /* qDebug() message handler --> debug.log */ void DebugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context); if (type == QtDebugMsg) { LogPrint(BCLog::QT, "GUI: %s\n", msg.toStdString()); } else { LogPrintf("GUI: %s\n", msg.toStdString()); } } /** * Class encapsulating Bitcoin ABC startup and shutdown. * Allows running startup and shutdown in a different thread from the UI thread. */ class BitcoinABC : public QObject { Q_OBJECT public: explicit BitcoinABC(); /** * Basic initialization, before starting initialization/shutdown thread. * Return true on success. */ static bool baseInitialize(Config &config, RPCServer &rpcServer); public Q_SLOTS: void initialize(Config *config, HTTPRPCRequestProcessor *httpRPCRequestProcessor); void shutdown(); Q_SIGNALS: void initializeResult(bool success); void shutdownResult(); void runawayException(const QString &message); private: /// Pass fatal exception message to UI thread void handleRunawayException(const std::exception *e); }; /** Main Bitcoin application object */ class BitcoinApplication : public QApplication { Q_OBJECT public: explicit BitcoinApplication(int &argc, char **argv); ~BitcoinApplication(); #ifdef ENABLE_WALLET /// Create payment server void createPaymentServer(); #endif /// parameter interaction/setup based on rules void parameterSetup(); /// Create options model void createOptionsModel(bool resetSettings); /// Create main window void createWindow(const Config *, const NetworkStyle *networkStyle); /// Create splash screen void createSplashScreen(const NetworkStyle *networkStyle); /// Request core initialization void requestInitialize(Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor, RPCServer &rpcServer); /// Request core shutdown void requestShutdown(Config &config); /// Get process return value - int getReturnValue() { return returnValue; } + int getReturnValue() const { return returnValue; } /// Get window identifier of QMainWindow (BitcoinGUI) WId getMainWinId() const; public Q_SLOTS: void initializeResult(bool success); void shutdownResult(); /// Handle runaway exceptions. Shows a message box with the problem and /// quits the program. void handleRunawayException(const QString &message); Q_SIGNALS: void requestedInitialize(Config *config, HTTPRPCRequestProcessor *httpRPCRequestProcessor, RPCServer *rpcServer); void requestedShutdown(); void stopThread(); void splashFinished(QWidget *window); private: QThread *coreThread; OptionsModel *optionsModel; ClientModel *clientModel; BitcoinGUI *window; QTimer *pollShutdownTimer; #ifdef ENABLE_WALLET PaymentServer *paymentServer; std::vector m_wallet_models; #endif int returnValue; const PlatformStyle *platformStyle; std::unique_ptr shutdownWindow; void startThread(); }; #include "bitcoin.moc" BitcoinABC::BitcoinABC() : QObject() {} void BitcoinABC::handleRunawayException(const std::exception *e) { PrintExceptionContinue(e, "Runaway exception"); Q_EMIT runawayException(QString::fromStdString(GetWarnings("gui"))); } bool BitcoinABC::baseInitialize(Config &config, RPCServer &rpcServer) { if (!AppInitBasicSetup()) { return false; } if (!AppInitParameterInteraction(config, rpcServer)) { return false; } if (!AppInitSanityChecks()) { return false; } if (!AppInitLockDataDirectory()) { return false; } return true; } void BitcoinABC::initialize(Config *cfg, HTTPRPCRequestProcessor *httpRPCRequestProcessor) { Config &config(*cfg); try { qDebug() << __func__ << ": Running initialization in thread"; bool rv = AppInitMain(config, *httpRPCRequestProcessor); Q_EMIT initializeResult(rv); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } void BitcoinABC::shutdown() { try { qDebug() << __func__ << ": Running Shutdown in thread"; Interrupt(); Shutdown(); qDebug() << __func__ << ": Shutdown finished"; Q_EMIT shutdownResult(); } catch (const std::exception &e) { handleRunawayException(&e); } catch (...) { handleRunawayException(nullptr); } } BitcoinApplication::BitcoinApplication(int &argc, char **argv) : QApplication(argc, argv), coreThread(0), optionsModel(0), clientModel(0), window(0), pollShutdownTimer(0), #ifdef ENABLE_WALLET paymentServer(0), m_wallet_models(), #endif returnValue(0) { setQuitOnLastWindowClosed(false); // UI per-platform customization. // This must be done inside the BitcoinApplication constructor, or after it, // because PlatformStyle::instantiate requires a QApplication. std::string platformName; platformName = gArgs.GetArg("-uiplatform", BitcoinGUI::DEFAULT_UIPLATFORM); platformStyle = PlatformStyle::instantiate(QString::fromStdString(platformName)); // Fall back to "other" if specified name not found. if (!platformStyle) { platformStyle = PlatformStyle::instantiate("other"); } assert(platformStyle); } BitcoinApplication::~BitcoinApplication() { if (coreThread) { qDebug() << __func__ << ": Stopping thread"; Q_EMIT stopThread(); coreThread->wait(); qDebug() << __func__ << ": Stopped thread"; } delete window; window = 0; #ifdef ENABLE_WALLET delete paymentServer; paymentServer = 0; #endif delete optionsModel; optionsModel = 0; delete platformStyle; platformStyle = 0; } #ifdef ENABLE_WALLET void BitcoinApplication::createPaymentServer() { paymentServer = new PaymentServer(this); } #endif void BitcoinApplication::createOptionsModel(bool resetSettings) { optionsModel = new OptionsModel(nullptr, resetSettings); } void BitcoinApplication::createWindow(const Config *config, const NetworkStyle *networkStyle) { window = new BitcoinGUI(config, platformStyle, networkStyle, 0); pollShutdownTimer = new QTimer(window); connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); pollShutdownTimer->start(200); } void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle) { SplashScreen *splash = new SplashScreen(0, networkStyle); // We don't hold a direct pointer to the splash screen after creation, but // the splash screen will take care of deleting itself when slotFinish // happens. splash->show(); connect(this, SIGNAL(splashFinished(QWidget *)), splash, SLOT(slotFinish(QWidget *))); connect(this, SIGNAL(requestedShutdown()), splash, SLOT(close())); } void BitcoinApplication::startThread() { if (coreThread) { return; } coreThread = new QThread(this); BitcoinABC *executor = new BitcoinABC(); executor->moveToThread(coreThread); /* communication to and from thread */ connect(executor, SIGNAL(initializeResult(bool)), this, SLOT(initializeResult(bool))); connect(executor, SIGNAL(shutdownResult()), this, SLOT(shutdownResult())); connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); // Note on how Qt works: it tries to directly invoke methods if the signal // is emitted on the same thread that the target object 'lives' on. // But if the target object 'lives' on another thread (executor here does) // the SLOT will be invoked asynchronously at a later time in the thread // of the target object. So.. we pass a pointer around. If you pass // a reference around (even if it's non-const) you'll get Qt generating // code to copy-construct the parameter in question (Q_DECLARE_METATYPE // and qRegisterMetaType generate this code). For the Config class, // which is noncopyable, we can't do this. So.. we have to pass // pointers to Config around. Make sure Config &/Config * isn't a // temporary (eg it lives somewhere aside from the stack) or this will // crash because initialize() gets executed in another thread at some // unspecified time (after) requestedInitialize() is emitted! connect(this, SIGNAL(requestedInitialize( Config *, HTTPRPCRequestProcessor *, RPCServer *)), executor, SLOT(initialize(Config *, HTTPRPCRequestProcessor *))); connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); /* make sure executor object is deleted in its own thread */ connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); connect(this, SIGNAL(stopThread()), coreThread, SLOT(quit())); coreThread->start(); } void BitcoinApplication::parameterSetup() { InitLogging(); InitParameterInteraction(); } void BitcoinApplication::requestInitialize( Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor, RPCServer &rpcServer) { qDebug() << __func__ << ": Requesting initialize"; startThread(); // IMPORTANT: config must NOT be a reference to a temporary because below // signal may be connected to a slot that will be executed as a queued // connection in another thread! Q_EMIT requestedInitialize(&config, &httpRPCRequestProcessor, &rpcServer); } void BitcoinApplication::requestShutdown(Config &config) { // Show a simple window indicating shutdown status. Do this first as some of // the steps may take some time below, for example the RPC console may still // be executing a command. shutdownWindow.reset(ShutdownWindow::showShutdownWindow(window)); qDebug() << __func__ << ": Requesting shutdown"; startThread(); window->hide(); window->setClientModel(0); pollShutdownTimer->stop(); #ifdef ENABLE_WALLET window->removeAllWallets(); for (WalletModel *walletModel : m_wallet_models) { delete walletModel; } m_wallet_models.clear(); #endif delete clientModel; clientModel = 0; StartShutdown(); // Request shutdown from core thread Q_EMIT requestedShutdown(); } void BitcoinApplication::initializeResult(bool success) { qDebug() << __func__ << ": Initialization result: " << success; returnValue = success ? EXIT_SUCCESS : EXIT_FAILURE; if (!success) { // Make sure splash screen doesn't stick around during shutdown. Q_EMIT splashFinished(window); // Exit first main loop invocation. quit(); return; } // Log this only after AppInit2 finishes, as then logging setup is // guaranteed complete. qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET PaymentServer::LoadRootCAs(); paymentServer->setOptionsModel(optionsModel); #endif clientModel = new ClientModel(optionsModel); window->setClientModel(clientModel); #ifdef ENABLE_WALLET bool fFirstWallet = true; for (CWalletRef pwallet : vpwallets) { WalletModel *const walletModel = new WalletModel(platformStyle, pwallet, optionsModel); window->addWallet(walletModel); if (fFirstWallet) { window->setCurrentWallet(walletModel->getWalletName()); fFirstWallet = false; } connect(walletModel, SIGNAL(coinsSent(CWallet *, SendCoinsRecipient, QByteArray)), paymentServer, SLOT(fetchPaymentACK(CWallet *, const SendCoinsRecipient &, QByteArray))); m_wallet_models.push_back(walletModel); } #endif // If -min option passed, start window minimized. if (gArgs.GetBoolArg("-min", false)) { window->showMinimized(); } else { window->show(); } Q_EMIT splashFinished(window); #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // bitcoincash: URIs or payment requests: connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), window, SLOT(handlePaymentRequest(SendCoinsRecipient))); connect(window, SIGNAL(receivedURI(QString)), paymentServer, SLOT(handleURIOrFile(QString))); connect(paymentServer, SIGNAL(message(QString, QString, unsigned int)), window, SLOT(message(QString, QString, unsigned int))); QTimer::singleShot(100, paymentServer, SLOT(uiReady())); #endif } void BitcoinApplication::shutdownResult() { // Exit second main loop invocation after shutdown finished. quit(); } void BitcoinApplication::handleRunawayException(const QString &message) { QMessageBox::critical( 0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue " "safely and will quit.") + QString("\n\n") + message); ::exit(EXIT_FAILURE); } WId BitcoinApplication::getMainWinId() const { if (!window) { return 0; } return window->winId(); } #ifndef BITCOIN_QT_TEST static void MigrateSettings() { assert(!QApplication::applicationName().isEmpty()); static const QString legacyAppName("Bitcoin-Qt"), #ifdef Q_OS_DARWIN // Macs and/or iOS et al use a domain-style name for Settings // files. All other platforms use a simple orgname. This // difference is documented in the QSettings class documentation. legacyOrg("bitcoin.org"); #else legacyOrg("Bitcoin"); #endif QSettings // below picks up settings file location based on orgname,appname legacy(legacyOrg, legacyAppName), // default c'tor below picks up settings file location based on // QApplication::applicationName(), et al -- which was already set // in main() abc; #ifdef Q_OS_DARWIN // Disable bogus OSX keys from MacOS system-wide prefs that may cloud our // judgement ;) (this behavior is also documented in QSettings docs) legacy.setFallbacksEnabled(false); abc.setFallbacksEnabled(false); #endif const QStringList legacyKeys(legacy.allKeys()); // We only migrate settings if we have Core settings but no Bitcoin-ABC // settings if (!legacyKeys.isEmpty() && abc.allKeys().isEmpty()) { for (const QString &key : legacyKeys) { // now, copy settings over abc.setValue(key, legacy.value(key)); } } } int main(int argc, char *argv[]) { SetupEnvironment(); /// 1. Parse command-line options. These take precedence over anything else. // Command-line options take precedence: gArgs.ParseParameters(argc, argv); // Do not refer to data directory yet, this can be overridden by // Intro::pickDataDirectory /// 2. Basic Qt initialization (not dependent on parameters or /// configuration) Q_INIT_RESOURCE(bitcoin); Q_INIT_RESOURCE(bitcoin_locale); BitcoinApplication app(argc, argv); #if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif #if QT_VERSION >= 0x050600 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #ifdef Q_OS_MAC QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif #if QT_VERSION >= 0x050500 // Because of the POODLE attack it is recommended to disable SSLv3 // (https://disablessl3.com/), so set SSL protocols to TLS1.0+. QSslConfiguration sslconf = QSslConfiguration::defaultConfiguration(); sslconf.setProtocol(QSsl::TlsV1_0OrLater); QSslConfiguration::setDefaultConfiguration(sslconf); #endif // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType(); // Need to pass name here as Amount is a typedef (see // http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType("Amount"); qRegisterMetaType>("std::function"); // Need to register any types Qt doesn't know about if you intend // to use them with the signal/slot mechanism Qt provides. Even pointers. // Note that class Config is noncopyable and so we can't register a // non-pointer version of it with Qt, because Qt expects to be able to // copy-construct non-pointers to objects for invoking slots // behind-the-scenes in the 'Queued' connection case. qRegisterMetaType(); /// 3. Application identification // must be set before OptionsModel is initialized or translations are // loaded, as it is used to locate QSettings. // Note: If you move these calls somewhere else, be sure to bring // MigrateSettings() below along for the ride. QApplication::setOrganizationName(QAPP_ORG_NAME); QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN); QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT); // Migrate settings from core's/our old GUI settings to Bitcoin ABC // only if core's exist but Bitcoin ABC's doesn't. // NOTE -- this function needs to be called *after* the above 3 lines // that set the app orgname and app name! If you move the above 3 lines // to elsewhere, take this call with you! MigrateSettings(); GUIUtil::SubstituteFonts(GetLangTerritory()); /// 4. Initialization of translations, so that intro dialog is in user's /// language. Now that QSettings are accessible, initialize translations. QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); translationInterface.Translate.connect(Translate); // Show help message immediately after parsing command-line options (for // "-lang") and setting locale, but before showing splash screen. if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { HelpMessageDialog help(nullptr, gArgs.IsArgSet("-version")); help.showOrPrint(); return EXIT_SUCCESS; } /// 5. Now that settings and translations are available, ask user for data /// directory. User language is set up: pick a data directory. if (!Intro::pickDataDirectory()) { return EXIT_SUCCESS; } /// 6. Determine availability of data directory and parse bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes. if (!fs::is_directory(GetDataDir(false))) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr( "Error: Specified data directory \"%1\" does not exist.") .arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } try { gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); } catch (const std::exception &e) { QMessageBox::critical( 0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: Cannot parse configuration file: %1. Only use " "key=value syntax.") .arg(e.what())); return EXIT_FAILURE; } /// 7. Determine network (and switch to network specific options) // - Do not call Params() before this step. // - Do this after parsing the configuration file, as the network can be // switched there. // - QSettings() will use the new application name after this, resulting in // network-specific settings. // - Needs to be done before createOptionsModel. // Check for -testnet or -regtest parameter (Params() calls are only valid // after this clause) try { SelectParams(gArgs.GetChainName()); } catch (std::exception &e) { QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: %1").arg(e.what())); return EXIT_FAILURE; } #ifdef ENABLE_WALLET // Parse URIs on command line -- this can affect Params() PaymentServer::ipcParseCommandLine(argc, argv); #endif QScopedPointer networkStyle(NetworkStyle::instantiate( QString::fromStdString(Params().NetworkIDString()))); assert(!networkStyle.isNull()); // Allow for separate UI settings for testnets QApplication::setApplicationName(networkStyle->getAppName()); // Re-initialize translations after changing application name (language in // network-specific settings can be different) initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); #ifdef ENABLE_WALLET /// 8. URI IPC sending // - Do this early as we don't want to bother initializing if we are just // calling IPC // - Do this *after* setting up the data directory, as the data directory // hash is used in the name // of the server. // - Do this after creating app and setting up translations, so errors are // translated properly. if (PaymentServer::ipcSendCommandLine()) { exit(EXIT_SUCCESS); } // Start up the payment server early, too, so impatient users that click on // bitcoincash: links repeatedly have their payment requests routed to this // process: app.createPaymentServer(); #endif /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be // word-wrapped. app.installEventFilter( new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); #if defined(Q_OS_WIN) // Install global event filter for processing Windows session related // Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) qApp->installNativeEventFilter(new WinShutdownMonitor()); #endif // Install qDebug() message handler to route to debug.log qInstallMessageHandler(DebugMessageHandler); // Allow parameter interaction before we create the options model app.parameterSetup(); // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); // Subscribe to global signals from core uiInterface.InitMessage.connect(InitMessage); // Get global config Config &config = const_cast(GetConfig()); if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) { app.createSplashScreen(networkStyle.data()); } RPCServer rpcServer; HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); try { app.createWindow(&config, networkStyle.data()); // Perform base initialization before spinning up // initialization/shutdown thread. This is acceptable because this // function only contains steps that are quick to execute, so the GUI // thread won't be held up. if (!BitcoinABC::baseInitialize(config, rpcServer)) { // A dialog with detailed error will have been shown by InitError() return EXIT_FAILURE; } app.requestInitialize(config, httpRPCRequestProcessor, rpcServer); #if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason( QObject::tr("%1 didn't yet exit safely...") .arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); #endif app.exec(); app.requestShutdown(config); app.exec(); return app.getReturnValue(); } catch (const std::exception &e) { PrintExceptionContinue(&e, "Runaway exception"); app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); } catch (...) { PrintExceptionContinue(nullptr, "Runaway exception"); app.handleRunawayException(QString::fromStdString(GetWarnings("gui"))); } return EXIT_FAILURE; } #endif // BITCOIN_QT_TEST diff --git a/src/qt/modaloverlay.h b/src/qt/modaloverlay.h index 121a5823a..1b7acc319 100644 --- a/src/qt/modaloverlay.h +++ b/src/qt/modaloverlay.h @@ -1,51 +1,51 @@ // Copyright (c) 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. #ifndef BITCOIN_QT_MODALOVERLAY_H #define BITCOIN_QT_MODALOVERLAY_H #include #include //! The required delta of headers to the estimated number of available headers //! until we show the IBD progress static constexpr int HEADER_HEIGHT_DELTA_SYNC = 24; namespace Ui { class ModalOverlay; } /** Modal overlay to display information about the chain-sync state */ class ModalOverlay : public QWidget { Q_OBJECT public: explicit ModalOverlay(QWidget *parent); ~ModalOverlay(); public Q_SLOTS: void tipUpdate(int count, const QDateTime &blockDate, double nVerificationProgress); void setKnownBestHeight(int count, const QDateTime &blockDate); void toggleVisibility(); // will show or hide the modal layer void showHide(bool hide = false, bool userRequested = false); void closeClicked(); - bool isLayerVisible() { return layerIsVisible; } + bool isLayerVisible() const { return layerIsVisible; } protected: bool eventFilter(QObject *obj, QEvent *ev) override; bool event(QEvent *ev) override; private: Ui::ModalOverlay *ui; int bestHeaderHeight; // best known height (based on the headers) QDateTime bestHeaderDate; QVector> blockProcessTime; bool layerIsVisible; bool userClosed; }; #endif // BITCOIN_QT_MODALOVERLAY_H diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index ca5e11e51..b69217836 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -1,534 +1,534 @@ // Copyright (c) 2011-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. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "optionsmodel.h" #include "bitcoinunits.h" #include "guiutil.h" #include "amount.h" #include "init.h" #include "intro.h" #include "net.h" #include "netbase.h" #include "txdb.h" // for -dbcache defaults #include "validation.h" // For DEFAULT_SCRIPTCHECK_THREADS #ifdef ENABLE_WALLET #include "wallet/wallet.h" #include "wallet/walletdb.h" #endif #include #include #include OptionsModel::OptionsModel(QObject *parent, bool resetSettings) : QAbstractListModel(parent) { Init(resetSettings); } void OptionsModel::addOverriddenOption(const std::string &option) { strOverriddenByCommandLine += QString::fromStdString(option) + "=" + QString::fromStdString(gArgs.GetArg(option, "")) + " "; } // Writes all missing QSettings with their default values void OptionsModel::Init(bool resetSettings) { if (resetSettings) { Reset(); } checkAndMigrate(); QSettings settings; // Ensure restart flag is unset on client startup setRestartRequired(false); // These are Qt-only settings: // Window if (!settings.contains("fHideTrayIcon")) { settings.setValue("fHideTrayIcon", false); } fHideTrayIcon = settings.value("fHideTrayIcon").toBool(); Q_EMIT hideTrayIconChanged(fHideTrayIcon); if (!settings.contains("fMinimizeToTray")) { settings.setValue("fMinimizeToTray", false); } fMinimizeToTray = settings.value("fMinimizeToTray").toBool() && !fHideTrayIcon; if (!settings.contains("fMinimizeOnClose")) { settings.setValue("fMinimizeOnClose", false); } fMinimizeOnClose = settings.value("fMinimizeOnClose").toBool(); // Display if (!settings.contains("nDisplayUnit")) { settings.setValue("nDisplayUnit", BitcoinUnits::BCH); } nDisplayUnit = settings.value("nDisplayUnit").toInt(); if (!settings.contains("strThirdPartyTxUrls")) { settings.setValue("strThirdPartyTxUrls", ""); } strThirdPartyTxUrls = settings.value("strThirdPartyTxUrls", "").toString(); if (!settings.contains("fCoinControlFeatures")) { settings.setValue("fCoinControlFeatures", false); } fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool(); // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. // // If setting doesn't exist create it with defaults. // // If gArgs.SoftSetArg() or gArgs.SoftSetBoolArg() return false we were // overridden // by command-line and show this in the UI. // Main if (!settings.contains("nDatabaseCache")) { settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); } if (!gArgs.SoftSetArg( "-dbcache", settings.value("nDatabaseCache").toString().toStdString())) { addOverriddenOption("-dbcache"); } if (!settings.contains("nThreadsScriptVerif")) { settings.setValue("nThreadsScriptVerif", DEFAULT_SCRIPTCHECK_THREADS); } if (!gArgs.SoftSetArg( "-par", settings.value("nThreadsScriptVerif").toString().toStdString())) { addOverriddenOption("-par"); } if (!settings.contains("strDataDir")) { settings.setValue("strDataDir", Intro::getDefaultDataDirectory()); } // Wallet #ifdef ENABLE_WALLET if (!settings.contains("bSpendZeroConfChange")) { settings.setValue("bSpendZeroConfChange", true); } if (!gArgs.SoftSetBoolArg( "-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool())) { addOverriddenOption("-spendzeroconfchange"); } #endif // Network if (!settings.contains("fUseUPnP")) { settings.setValue("fUseUPnP", DEFAULT_UPNP); } if (!gArgs.SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool())) { addOverriddenOption("-upnp"); } if (!settings.contains("fListen")) { settings.setValue("fListen", DEFAULT_LISTEN); } if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool())) { addOverriddenOption("-listen"); } if (!settings.contains("fUseProxy")) { settings.setValue("fUseProxy", false); } if (!settings.contains("addrProxy")) { settings.setValue("addrProxy", "127.0.0.1:9050"); } // Only try to set -proxy, if user has enabled fUseProxy if (settings.value("fUseProxy").toBool() && !gArgs.SoftSetArg( "-proxy", settings.value("addrProxy").toString().toStdString())) { addOverriddenOption("-proxy"); } else if (!settings.value("fUseProxy").toBool() && !gArgs.GetArg("-proxy", "").empty()) { addOverriddenOption("-proxy"); } if (!settings.contains("fUseSeparateProxyTor")) { settings.setValue("fUseSeparateProxyTor", false); } if (!settings.contains("addrSeparateProxyTor")) { settings.setValue("addrSeparateProxyTor", "127.0.0.1:9050"); } // Only try to set -onion, if user has enabled fUseSeparateProxyTor if (settings.value("fUseSeparateProxyTor").toBool() && !gArgs.SoftSetArg( "-onion", settings.value("addrSeparateProxyTor").toString().toStdString())) { addOverriddenOption("-onion"); } else if (!settings.value("fUseSeparateProxyTor").toBool() && !gArgs.GetArg("-onion", "").empty()) { addOverriddenOption("-onion"); } // Display if (!settings.contains("language")) { settings.setValue("language", ""); } if (!gArgs.SoftSetArg( "-lang", settings.value("language").toString().toStdString())) { addOverriddenOption("-lang"); } language = settings.value("language").toString(); } void OptionsModel::Reset() { QSettings settings; // Save the strDataDir setting QString dataDir = Intro::getDefaultDataDirectory(); dataDir = settings.value("strDataDir", dataDir).toString(); // Remove all entries from our QSettings object settings.clear(); // Set strDataDir settings.setValue("strDataDir", dataDir); // Set that this was reset settings.setValue("fReset", true); // default setting for OptionsModel::StartAtStartup - disabled if (GUIUtil::GetStartOnSystemStartup()) { GUIUtil::SetStartOnSystemStartup(false); } } int OptionsModel::rowCount(const QModelIndex &parent) const { return OptionIDRowCount; } // read QSettings values and return them QVariant OptionsModel::data(const QModelIndex &index, int role) const { if (role == Qt::EditRole) { QSettings settings; switch (index.row()) { case StartAtStartup: return GUIUtil::GetStartOnSystemStartup(); case HideTrayIcon: return fHideTrayIcon; case MinimizeToTray: return fMinimizeToTray; case MapPortUPnP: #ifdef USE_UPNP return settings.value("fUseUPnP"); #else return false; #endif case MinimizeOnClose: return fMinimizeOnClose; // default proxy case ProxyUse: return settings.value("fUseProxy", false); case ProxyIP: { // contains IP at index 0 and port at index 1 QStringList strlIpPort = settings.value("addrProxy") .toString() .split(":", QString::SkipEmptyParts); return strlIpPort.at(0); } case ProxyPort: { // contains IP at index 0 and port at index 1 QStringList strlIpPort = settings.value("addrProxy") .toString() .split(":", QString::SkipEmptyParts); return strlIpPort.at(1); } // separate Tor proxy case ProxyUseTor: return settings.value("fUseSeparateProxyTor", false); case ProxyIPTor: { // contains IP at index 0 and port at index 1 QStringList strlIpPort = settings.value("addrSeparateProxyTor") .toString() .split(":", QString::SkipEmptyParts); return strlIpPort.at(0); } case ProxyPortTor: { // contains IP at index 0 and port at index 1 QStringList strlIpPort = settings.value("addrSeparateProxyTor") .toString() .split(":", QString::SkipEmptyParts); return strlIpPort.at(1); } #ifdef ENABLE_WALLET case SpendZeroConfChange: return settings.value("bSpendZeroConfChange"); #endif case DisplayUnit: return nDisplayUnit; case ThirdPartyTxUrls: return strThirdPartyTxUrls; case Language: return settings.value("language"); case CoinControlFeatures: return fCoinControlFeatures; case DatabaseCache: return settings.value("nDatabaseCache"); case ThreadsScriptVerif: return settings.value("nThreadsScriptVerif"); case Listen: return settings.value("fListen"); default: return QVariant(); } } return QVariant(); } // write QSettings values bool OptionsModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool successful = true; /* set to false on parse error */ if (role == Qt::EditRole) { QSettings settings; switch (index.row()) { case StartAtStartup: successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); break; case HideTrayIcon: fHideTrayIcon = value.toBool(); settings.setValue("fHideTrayIcon", fHideTrayIcon); Q_EMIT hideTrayIconChanged(fHideTrayIcon); break; case MinimizeToTray: fMinimizeToTray = value.toBool(); settings.setValue("fMinimizeToTray", fMinimizeToTray); break; case MapPortUPnP: // core option - can be changed on-the-fly settings.setValue("fUseUPnP", value.toBool()); if (value.toBool()) { StartMapPort(); } else { InterruptMapPort(); StopMapPort(); } break; case MinimizeOnClose: fMinimizeOnClose = value.toBool(); settings.setValue("fMinimizeOnClose", fMinimizeOnClose); break; // default proxy case ProxyUse: if (settings.value("fUseProxy") != value) { settings.setValue("fUseProxy", value.toBool()); setRestartRequired(true); } break; case ProxyIP: { // contains current IP at index 0 and current port at index 1 QStringList strlIpPort = settings.value("addrProxy") .toString() .split(":", QString::SkipEmptyParts); // if that key doesn't exist or has a changed IP if (!settings.contains("addrProxy") || strlIpPort.at(0) != value.toString()) { // construct new value from new IP and current port QString strNewValue = value.toString() + ":" + strlIpPort.at(1); settings.setValue("addrProxy", strNewValue); setRestartRequired(true); } } break; case ProxyPort: { // contains current IP at index 0 and current port at index 1 QStringList strlIpPort = settings.value("addrProxy") .toString() .split(":", QString::SkipEmptyParts); // if that key doesn't exist or has a changed port if (!settings.contains("addrProxy") || strlIpPort.at(1) != value.toString()) { // construct new value from current IP and new port QString strNewValue = strlIpPort.at(0) + ":" + value.toString(); settings.setValue("addrProxy", strNewValue); setRestartRequired(true); } } break; // separate Tor proxy case ProxyUseTor: if (settings.value("fUseSeparateProxyTor") != value) { settings.setValue("fUseSeparateProxyTor", value.toBool()); setRestartRequired(true); } break; case ProxyIPTor: { // contains current IP at index 0 and current port at index 1 QStringList strlIpPort = settings.value("addrSeparateProxyTor") .toString() .split(":", QString::SkipEmptyParts); // if that key doesn't exist or has a changed IP if (!settings.contains("addrSeparateProxyTor") || strlIpPort.at(0) != value.toString()) { // construct new value from new IP and current port QString strNewValue = value.toString() + ":" + strlIpPort.at(1); settings.setValue("addrSeparateProxyTor", strNewValue); setRestartRequired(true); } } break; case ProxyPortTor: { // contains current IP at index 0 and current port at index 1 QStringList strlIpPort = settings.value("addrSeparateProxyTor") .toString() .split(":", QString::SkipEmptyParts); // if that key doesn't exist or has a changed port if (!settings.contains("addrSeparateProxyTor") || strlIpPort.at(1) != value.toString()) { // construct new value from current IP and new port QString strNewValue = strlIpPort.at(0) + ":" + value.toString(); settings.setValue("addrSeparateProxyTor", strNewValue); setRestartRequired(true); } } break; #ifdef ENABLE_WALLET case SpendZeroConfChange: if (settings.value("bSpendZeroConfChange") != value) { settings.setValue("bSpendZeroConfChange", value); setRestartRequired(true); } break; #endif case DisplayUnit: setDisplayUnit(value); break; case ThirdPartyTxUrls: if (strThirdPartyTxUrls != value.toString()) { strThirdPartyTxUrls = value.toString(); settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); setRestartRequired(true); } break; case Language: if (settings.value("language") != value) { settings.setValue("language", value); setRestartRequired(true); } break; case CoinControlFeatures: fCoinControlFeatures = value.toBool(); settings.setValue("fCoinControlFeatures", fCoinControlFeatures); Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); break; case DatabaseCache: if (settings.value("nDatabaseCache") != value) { settings.setValue("nDatabaseCache", value); setRestartRequired(true); } break; case ThreadsScriptVerif: if (settings.value("nThreadsScriptVerif") != value) { settings.setValue("nThreadsScriptVerif", value); setRestartRequired(true); } break; case Listen: if (settings.value("fListen") != value) { settings.setValue("fListen", value); setRestartRequired(true); } break; default: break; } } Q_EMIT dataChanged(index, index); return successful; } /** Updates current unit in memory, settings and emits * displayUnitChanged(newUnit) signal */ void OptionsModel::setDisplayUnit(const QVariant &value) { if (!value.isNull()) { QSettings settings; nDisplayUnit = value.toInt(); settings.setValue("nDisplayUnit", nDisplayUnit); Q_EMIT displayUnitChanged(nDisplayUnit); } } bool OptionsModel::getProxySettings(QNetworkProxy &proxy) const { // Directly query current base proxy, because // GUI settings can be overridden with -proxy. proxyType curProxy; if (GetProxy(NET_IPV4, curProxy)) { proxy.setType(QNetworkProxy::Socks5Proxy); proxy.setHostName(QString::fromStdString(curProxy.proxy.ToStringIP())); proxy.setPort(curProxy.proxy.GetPort()); return true; } else proxy.setType(QNetworkProxy::NoProxy); return false; } void OptionsModel::setRestartRequired(bool fRequired) { QSettings settings; return settings.setValue("fRestartRequired", fRequired); } -bool OptionsModel::isRestartRequired() { +bool OptionsModel::isRestartRequired() const { QSettings settings; return settings.value("fRestartRequired", false).toBool(); } void OptionsModel::checkAndMigrate() { // Migration of default values // Check if the QSettings container was already loaded with this client // version QSettings settings; static const char strSettingsVersionKey[] = "nSettingsVersion"; int settingsVersion = settings.contains(strSettingsVersionKey) ? settings.value(strSettingsVersionKey).toInt() : 0; if (settingsVersion < CLIENT_VERSION) { // -dbcache was bumped from 100 to 300 in 0.13 // see https://github.com/bitcoin/bitcoin/pull/8273 // force people to upgrade to the new value if they are using 100MB if (settingsVersion < 130000 && settings.contains("nDatabaseCache") && settings.value("nDatabaseCache").toLongLong() == 100) settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); settings.setValue(strSettingsVersionKey, CLIENT_VERSION); } } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 610b49464..ae209e395 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -1,103 +1,103 @@ // Copyright (c) 2011-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. #ifndef BITCOIN_QT_OPTIONSMODEL_H #define BITCOIN_QT_OPTIONSMODEL_H #include "amount.h" #include QT_BEGIN_NAMESPACE class QNetworkProxy; QT_END_NAMESPACE /** Interface from Qt to configuration data structure for Bitcoin client. To Qt, the options are presented as a list with the different options laid out vertically. This can be changed to a tree once the settings become sufficiently complex. */ class OptionsModel : public QAbstractListModel { Q_OBJECT public: explicit OptionsModel(QObject *parent = 0, bool resetSettings = false); enum OptionID { StartAtStartup, // bool HideTrayIcon, // bool MinimizeToTray, // bool MapPortUPnP, // bool MinimizeOnClose, // bool ProxyUse, // bool ProxyIP, // QString ProxyPort, // int ProxyUseTor, // bool ProxyIPTor, // QString ProxyPortTor, // int DisplayUnit, // BitcoinUnits::Unit ThirdPartyTxUrls, // QString Language, // QString CoinControlFeatures, // bool ThreadsScriptVerif, // int DatabaseCache, // int SpendZeroConfChange, // bool Listen, // bool OptionIDRowCount, }; void Init(bool resetSettings = false); void Reset(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; /** Updates current unit in memory, settings and emits * displayUnitChanged(newUnit) signal */ void setDisplayUnit(const QVariant &value); /* Explicit getters */ - bool getHideTrayIcon() { return fHideTrayIcon; } - bool getMinimizeToTray() { return fMinimizeToTray; } - bool getMinimizeOnClose() { return fMinimizeOnClose; } - int getDisplayUnit() { return nDisplayUnit; } - QString getThirdPartyTxUrls() { return strThirdPartyTxUrls; } + bool getHideTrayIcon() const { return fHideTrayIcon; } + bool getMinimizeToTray() const { return fMinimizeToTray; } + bool getMinimizeOnClose() const { return fMinimizeOnClose; } + int getDisplayUnit() const { return nDisplayUnit; } + QString getThirdPartyTxUrls() const { return strThirdPartyTxUrls; } bool getProxySettings(QNetworkProxy &proxy) const; - bool getCoinControlFeatures() { return fCoinControlFeatures; } + bool getCoinControlFeatures() const { return fCoinControlFeatures; } const QString &getOverriddenByCommandLine() { return strOverriddenByCommandLine; } /* Restart flag helper */ void setRestartRequired(bool fRequired); - bool isRestartRequired(); + bool isRestartRequired() const; private: /* Qt-only settings */ bool fHideTrayIcon; bool fMinimizeToTray; bool fMinimizeOnClose; QString language; int nDisplayUnit; QString strThirdPartyTxUrls; bool fCoinControlFeatures; /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; // Add option to list of GUI options overridden through command line/config // file void addOverriddenOption(const std::string &option); // Check settings version and upgrade default values if required void checkAndMigrate(); Q_SIGNALS: void displayUnitChanged(int unit); void coinControlFeaturesChanged(bool); void hideTrayIconChanged(bool); }; #endif // BITCOIN_QT_OPTIONSMODEL_H diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 8e3b0c09f..939ca876d 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -1,235 +1,235 @@ // Copyright (c) 2011-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. #include "transactionrecord.h" #include "consensus/consensus.h" #include "dstencode.h" #include "timedata.h" #include "validation.h" #include "wallet/finaltx.h" #include "wallet/wallet.h" #include /** * Return positive answer if transaction should be shown in list. */ bool TransactionRecord::showTransaction(const CWalletTx &wtx) { // There are currently no cases where we hide transactions, but we may want // to use this in the future for things like RBF. return true; } /** * Decompose CWallet transaction to model transaction records. */ QList TransactionRecord::decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx) { QList parts; int64_t nTime = wtx.GetTxTime(); Amount nCredit = wtx.GetCredit(ISMINE_ALL); Amount nDebit = wtx.GetDebit(ISMINE_ALL); Amount nNet = nCredit - nDebit; const TxId &txid = wtx.GetId(); std::map mapValue = wtx.mapValue; if (nNet > Amount::zero() || wtx.IsCoinBase()) { // // Credit // for (size_t i = 0; i < wtx.tx->vout.size(); i++) { const CTxOut &txout = wtx.tx->vout[i]; isminetype mine = wallet->IsMine(txout); if (mine) { TransactionRecord sub(txid, nTime); CTxDestination address; sub.idx = i; // vout index sub.credit = txout.nValue; sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) { // Received by Bitcoin Address sub.type = TransactionRecord::RecvWithAddress; sub.address = EncodeDestination(address); } else { // Received by IP connection (deprecated features), or a // multisignature or other non-simple transaction sub.type = TransactionRecord::RecvFromOther; sub.address = mapValue["from"]; } if (wtx.IsCoinBase()) { // Generated sub.type = TransactionRecord::Generated; } parts.append(sub); } } } else { bool involvesWatchAddress = false; isminetype fAllFromMe = ISMINE_SPENDABLE; for (const CTxIn &txin : wtx.tx->vin) { isminetype mine = wallet->IsMine(txin); if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllFromMe > mine) { fAllFromMe = mine; } } isminetype fAllToMe = ISMINE_SPENDABLE; for (const CTxOut &txout : wtx.tx->vout) { isminetype mine = wallet->IsMine(txout); if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllToMe > mine) { fAllToMe = mine; } } if (fAllFromMe && fAllToMe) { // Payment to self Amount nChange = wtx.GetChange(); parts.append(TransactionRecord( txid, nTime, TransactionRecord::SendToSelf, "", -1 * (nDebit - nChange), (nCredit - nChange))); // maybe pass to TransactionRecord as constructor argument parts.last().involvesWatchAddress = involvesWatchAddress; } else if (fAllFromMe) { // // Debit // Amount nTxFee = nDebit - wtx.tx->GetValueOut(); for (size_t nOut = 0; nOut < wtx.tx->vout.size(); nOut++) { const CTxOut &txout = wtx.tx->vout[nOut]; TransactionRecord sub(txid, nTime); sub.idx = nOut; sub.involvesWatchAddress = involvesWatchAddress; if (wallet->IsMine(txout)) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. continue; } CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address)) { // Sent to Bitcoin Address sub.type = TransactionRecord::SendToAddress; sub.address = EncodeDestination(address); } else { // Sent to IP, or other non-address transaction like OP_EVAL sub.type = TransactionRecord::SendToOther; sub.address = mapValue["to"]; } Amount nValue = txout.nValue; /* Add fee to first output */ if (nTxFee > Amount::zero()) { nValue += nTxFee; nTxFee = Amount::zero(); } sub.debit = -1 * nValue; parts.append(sub); } } else { // // Mixed debit transaction, can't break down payees // parts.append(TransactionRecord(txid, nTime, TransactionRecord::Other, "", nNet, Amount::zero())); parts.last().involvesWatchAddress = involvesWatchAddress; } } return parts; } void TransactionRecord::updateStatus(const CWalletTx &wtx) { AssertLockHeld(cs_main); // Determine transaction status // Find the block the tx is in CBlockIndex *pindex = nullptr; BlockMap::iterator mi = mapBlockIndex.find(wtx.hashBlock); if (mi != mapBlockIndex.end()) { pindex = (*mi).second; } // Sort order, unrecorded transactions sort to the top status.sortKey = strprintf("%010d-%01d-%010u-%03d", (pindex ? pindex->nHeight : std::numeric_limits::max()), (wtx.IsCoinBase() ? 1 : 0), wtx.nTimeReceived, idx); status.countsForBalance = wtx.IsTrusted() && !(wtx.GetBlocksToMaturity() > 0); status.depth = wtx.GetDepthInMainChain(); status.cur_num_blocks = chainActive.Height(); if (!CheckFinalTx(wtx)) { if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; status.open_for = wtx.tx->nLockTime - chainActive.Height(); } else { status.status = TransactionStatus::OpenUntilDate; status.open_for = wtx.tx->nLockTime; } } else if (type == TransactionRecord::Generated) { // For generated transactions, determine maturity if (wtx.GetBlocksToMaturity() > 0) { status.status = TransactionStatus::Immature; if (wtx.IsInMainChain()) { status.matures_in = wtx.GetBlocksToMaturity(); // Check if the block was requested by anyone if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) { status.status = TransactionStatus::MaturesWarning; } } else { status.status = TransactionStatus::NotAccepted; } } else { status.status = TransactionStatus::Confirmed; } } else { if (status.depth < 0) { status.status = TransactionStatus::Conflicted; } else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) { status.status = TransactionStatus::Offline; } else if (status.depth == 0) { status.status = TransactionStatus::Unconfirmed; if (wtx.isAbandoned()) { status.status = TransactionStatus::Abandoned; } } else if (status.depth < RecommendedNumConfirmations) { status.status = TransactionStatus::Confirming; } else { status.status = TransactionStatus::Confirmed; } } } -bool TransactionRecord::statusUpdateNeeded() { +bool TransactionRecord::statusUpdateNeeded() const { AssertLockHeld(cs_main); return status.cur_num_blocks != chainActive.Height(); } QString TransactionRecord::getTxID() const { return QString::fromStdString(txid.ToString()); } int TransactionRecord::getOutputIndex() const { return idx; } diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 8f4f8c1e1..9e9a2677b 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -1,155 +1,155 @@ // Copyright (c) 2011-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. #ifndef BITCOIN_QT_TRANSACTIONRECORD_H #define BITCOIN_QT_TRANSACTIONRECORD_H #include "amount.h" #include "primitives/txid.h" #include #include class CWallet; class CWalletTx; /** * UI model for transaction status. The transaction status is the part of a * transaction that will change over time. */ class TransactionStatus { public: TransactionStatus() : countsForBalance(false), sortKey(""), matures_in(0), status(Offline), depth(0), open_for(0), cur_num_blocks(-1) {} enum Status { /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/ Confirmed, /// Normal (sent/received) transactions /**< Transaction not yet final, waiting for date */ OpenUntilDate, /**< Transaction not yet final, waiting for block */ OpenUntilBlock, /**< Not sent to any other nodes **/ Offline, /**< Not yet mined into a block **/ Unconfirmed, /**< Confirmed, but waiting for the recommended number of confirmations **/ Confirming, /**< Conflicts with other transaction or mempool **/ Conflicted, /**< Abandoned from the wallet **/ Abandoned, /// Generated (mined) transactions /**< Mined but waiting for maturity */ Immature, /**< Transaction will likely not mature because no nodes have confirmed */ MaturesWarning, /**< Mined but not accepted */ NotAccepted }; /// Transaction counts towards available balance bool countsForBalance; /// Sorting key based on status std::string sortKey; /** @name Generated (mined) transactions @{*/ int matures_in; /**@}*/ /** @name Reported status @{*/ Status status; qint64 depth; /**< Timestamp if status==OpenUntilDate, otherwise number of additional * blocks that need to be mined before finalization */ qint64 open_for; /**@}*/ /** Current number of blocks (to know whether cached status is still valid) */ int cur_num_blocks; }; /** * UI model for a transaction. A core transaction can be represented by multiple * UI transactions if it has multiple outputs. */ class TransactionRecord { public: enum Type { Other, Generated, SendToAddress, SendToOther, RecvWithAddress, RecvFromOther, SendToSelf }; /** Number of confirmation recommended for accepting a transaction */ static const int RecommendedNumConfirmations = 6; TransactionRecord() : txid(), time(0), type(Other), address(""), debit(), credit(), idx(0) { } TransactionRecord(TxId _txid, qint64 _time) : txid(_txid), time(_time), type(Other), address(""), debit(), credit(), idx(0) {} TransactionRecord(TxId _txid, qint64 _time, Type _type, const std::string &_address, const Amount _debit, const Amount _credit) : txid(_txid), time(_time), type(_type), address(_address), debit(_debit), credit(_credit), idx(0) {} /** Decompose CWallet transaction to model transaction records. */ static bool showTransaction(const CWalletTx &wtx); static QList decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx); /** @name Immutable transaction attributes @{*/ TxId txid; qint64 time; Type type; std::string address; Amount debit; Amount credit; /**@}*/ /** Subtransaction index, for sort key */ int idx; /** Status: can change with block chain update */ TransactionStatus status; /** Whether the transaction was sent/received with a watch-only address */ bool involvesWatchAddress; /** Return the unique identifier for this transaction (part) */ QString getTxID() const; /** Return the output index of the subtransaction */ int getOutputIndex() const; /** Update status from core wallet tx. */ void updateStatus(const CWalletTx &wtx); /** Return whether a status update is needed. */ - bool statusUpdateNeeded(); + bool statusUpdateNeeded() const; }; #endif // BITCOIN_QT_TRANSACTIONRECORD_H diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index 9b7af2419..be0175f44 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -1,140 +1,140 @@ // Copyright (c) 2011-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. #ifndef BITCOIN_QT_TRANSACTIONTABLEMODEL_H #define BITCOIN_QT_TRANSACTIONTABLEMODEL_H #include "bitcoinunits.h" #include #include class PlatformStyle; class TransactionRecord; class TransactionTablePriv; class WalletModel; class CWallet; /** * UI model for the transaction table of a wallet. */ class TransactionTableModel : public QAbstractTableModel { Q_OBJECT public: explicit TransactionTableModel(const PlatformStyle *platformStyle, CWallet *wallet, WalletModel *parent = 0); ~TransactionTableModel(); enum ColumnIndex { Status = 0, Watchonly = 1, Date = 2, Type = 3, ToAddress = 4, Amount = 5 }; /** * Roles to get specific information from a transaction row. * These are independent of column. */ enum RoleIndex { /** Type of transaction */ TypeRole = Qt::UserRole, /** Date and time this transaction was created */ DateRole, /** Watch-only boolean */ WatchonlyRole, /** Watch-only icon */ WatchonlyDecorationRole, /** Long description (HTML format) */ LongDescriptionRole, /** Address of transaction */ AddressRole, /** Label of address related to transaction */ LabelRole, /** Net amount of transaction */ AmountRole, /** Unique identifier */ TxIDRole, /** Transaction hash */ TxHashRole, /** Transaction data, hex-encoded */ TxHexRole, /** Whole transaction as plain text */ TxPlainTextRole, /** Is transaction confirmed? */ ConfirmedRole, /** Formatted amount, without brackets when unconfirmed */ FormattedAmountRole, /** Transaction status (TransactionRecord::Status) */ StatusRole, /** Unprocessed icon */ RawDecorationRole, }; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - bool processingQueuedTransactions() { + bool processingQueuedTransactions() const { return fProcessingQueuedTransactions; } private: CWallet *wallet; WalletModel *walletModel; QStringList columns; TransactionTablePriv *priv; bool fProcessingQueuedTransactions; const PlatformStyle *platformStyle; void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); QString lookupAddress(const std::string &address, bool tooltip) const; QVariant addressColor(const TransactionRecord *wtx) const; QString formatTxStatus(const TransactionRecord *wtx) const; QString formatTxDate(const TransactionRecord *wtx) const; QString formatTxType(const TransactionRecord *wtx) const; QString formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const; QString formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed = true, BitcoinUnits::SeparatorStyle separators = BitcoinUnits::separatorStandard) const; QString formatTooltip(const TransactionRecord *rec) const; QVariant txStatusDecoration(const TransactionRecord *wtx) const; QVariant txWatchonlyDecoration(const TransactionRecord *wtx) const; QVariant txAddressDecoration(const TransactionRecord *wtx) const; public Q_SLOTS: /** * New transaction, or transaction changed status. */ void updateTransaction(const QString &hash, int status, bool showTransaction); void updateConfirmations(); void updateDisplayUnit(); /** * Updates the column title to "Amount (DisplayUnit)" and emits * headerDataChanged() signal for table headers to react. */ void updateAmountColumnTitle(); /** * Needed to update fProcessingQueuedTransactions through a * QueuedConnection. */ void setProcessingQueuedTransactions(bool value) { fProcessingQueuedTransactions = value; } friend class TransactionTablePriv; }; #endif // BITCOIN_QT_TRANSACTIONTABLEMODEL_H diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index 064b99209..6b767c949 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -1,89 +1,89 @@ // Copyright (c) 2011-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. #include "walletmodeltransaction.h" #include "policy/policy.h" #include "wallet/wallet.h" WalletModelTransaction::WalletModelTransaction( const QList &_recipients) : recipients(_recipients), walletTransaction(0), keyChange(0), fee() { walletTransaction = new CWalletTx(); } WalletModelTransaction::~WalletModelTransaction() { delete keyChange; delete walletTransaction; } -QList WalletModelTransaction::getRecipients() { +QList WalletModelTransaction::getRecipients() const { return recipients; } -CWalletTx *WalletModelTransaction::getTransaction() { +CWalletTx *WalletModelTransaction::getTransaction() const { return walletTransaction; } unsigned int WalletModelTransaction::getTransactionSize() { return !walletTransaction ? 0 : CTransaction(*walletTransaction).GetTotalSize(); } -Amount WalletModelTransaction::getTransactionFee() { +Amount WalletModelTransaction::getTransactionFee() const { return fee; } void WalletModelTransaction::setTransactionFee(const Amount newFee) { fee = newFee; } void WalletModelTransaction::reassignAmounts(int nChangePosRet) { int i = 0; for (SendCoinsRecipient &rcp : recipients) { if (rcp.paymentRequest.IsInitialized()) { Amount subtotal = Amount::zero(); const payments::PaymentDetails &details = rcp.paymentRequest.getDetails(); for (int j = 0; j < details.outputs_size(); j++) { const payments::Output &out = details.outputs(j); if (out.amount() <= 0) { continue; } if (i == nChangePosRet) { i++; } subtotal += walletTransaction->tx->vout[i].nValue; i++; } rcp.amount = subtotal; } else { // normal recipient (no payment request) if (i == nChangePosRet) { i++; } rcp.amount = walletTransaction->tx->vout[i].nValue; i++; } } } -Amount WalletModelTransaction::getTotalTransactionAmount() { +Amount WalletModelTransaction::getTotalTransactionAmount() const { Amount totalTransactionAmount = Amount::zero(); for (const SendCoinsRecipient &rcp : recipients) { totalTransactionAmount += rcp.amount; } return totalTransactionAmount; } void WalletModelTransaction::newPossibleKeyChange(CWallet *wallet) { keyChange = new CReserveKey(wallet); } CReserveKey *WalletModelTransaction::getPossibleKeyChange() { return keyChange; } diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index fdd925721..91c32456a 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -1,48 +1,48 @@ // Copyright (c) 2011-2014 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_WALLETMODELTRANSACTION_H #define BITCOIN_QT_WALLETMODELTRANSACTION_H #include "walletmodel.h" #include class SendCoinsRecipient; class CReserveKey; class CWallet; class CWalletTx; /** Data model for a walletmodel transaction. */ class WalletModelTransaction { public: explicit WalletModelTransaction( const QList &recipients); ~WalletModelTransaction(); - QList getRecipients(); + QList getRecipients() const; - CWalletTx *getTransaction(); + CWalletTx *getTransaction() const; unsigned int getTransactionSize(); void setTransactionFee(const Amount newFee); - Amount getTransactionFee(); + Amount getTransactionFee() const; - Amount getTotalTransactionAmount(); + Amount getTotalTransactionAmount() const; void newPossibleKeyChange(CWallet *wallet); CReserveKey *getPossibleKeyChange(); // needed for the subtract-fee-from-amount feature void reassignAmounts(int nChangePosRet); private: QList recipients; CWalletTx *walletTransaction; CReserveKey *keyChange; Amount fee; }; #endif // BITCOIN_QT_WALLETMODELTRANSACTION_H diff --git a/src/scheduler.h b/src/scheduler.h index b0d6bc9f0..cc48ee5a0 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -1,120 +1,120 @@ // Copyright (c) 2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SCHEDULER_H #define BITCOIN_SCHEDULER_H #include "sync.h" // // NOTE: // boost::thread / boost::chrono should be ported to // std::thread / std::chrono when we support C++11. // #include #include #include // // Simple class for background tasks that should be run periodically or once // "after a while" // // Usage: // // CScheduler* s = new CScheduler(); // s->scheduleFromNow(doSomething, 11); // Assuming a: void doSomething() { } // s->scheduleFromNow(std::bind(Class::func, this, argument), 3); // boost::thread* t = new boost::thread(boost::bind(CScheduler::serviceQueue, // s)); // // ... then at program shutdown, clean up the thread running serviceQueue: // t->interrupt(); // t->join(); // delete t; // delete s; // Must be done after thread is interrupted/joined. // class CScheduler { public: CScheduler(); ~CScheduler(); typedef std::function Function; typedef std::function Predicate; // Call func at/after time t void schedule(Function f, boost::chrono::system_clock::time_point t = boost::chrono::system_clock::now()); // Convenience method: call f once deltaMilliSeconds from now void scheduleFromNow(Function f, int64_t deltaMilliSeconds); // Another convenience method: call f approximately every deltaMilliSeconds // forever, starting deltaMilliSeconds from now. To be more precise: every // time f is finished, it is rescheduled to run deltaMilliSeconds later. If // you need more accurate scheduling, don't use this method. void scheduleEvery(Predicate p, int64_t deltaMilliSeconds); // To keep things as simple as possible, there is no unschedule. // Services the queue 'forever'. Should be run in a thread, and interrupted // using boost::interrupt_thread void serviceQueue(); // Tell any threads running serviceQueue to stop as soon as they're done // servicing whatever task they're currently servicing (drain=false) or when // there is no work left to be done (drain=true) void stop(bool drain = false); // Returns number of tasks waiting to be serviced, and first and last task // times size_t getQueueInfo(boost::chrono::system_clock::time_point &first, boost::chrono::system_clock::time_point &last) const; // Returns true if there are threads actively running in serviceQueue() bool AreThreadsServicingQueue() const; private: std::multimap taskQueue; boost::condition_variable newTaskScheduled; mutable boost::mutex newTaskMutex; int nThreadsServicingQueue; bool stopRequested; bool stopWhenEmpty; - bool shouldStop() { + bool shouldStop() const { return stopRequested || (stopWhenEmpty && taskQueue.empty()); } }; /** * Class used by CScheduler clients which may schedule multiple jobs * which are required to be run serially. Does not require such jobs * to be executed on the same thread, but no two jobs will be executed * at the same time. */ class SingleThreadedSchedulerClient { private: CScheduler *m_pscheduler; CCriticalSection m_cs_callbacks_pending; std::list> m_callbacks_pending; bool m_are_callbacks_running = false; void MaybeScheduleProcessQueue(); void ProcessQueue(); public: explicit SingleThreadedSchedulerClient(CScheduler *pschedulerIn) : m_pscheduler(pschedulerIn) {} void AddToProcessQueue(std::function func); // Processes all remaining queue members on the calling thread, blocking // until queue is empty // Must be called after the CScheduler has no remaining processing threads! void EmptyQueue(); }; #endif diff --git a/src/streams.h b/src/streams.h index db54fa956..717bc3b14 100644 --- a/src/streams.h +++ b/src/streams.h @@ -1,660 +1,660 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #ifndef BITCOIN_STREAMS_H #define BITCOIN_STREAMS_H #include "serialize.h" #include "support/allocators/zeroafterfree.h" #include #include #include #include #include #include #include #include #include #include #include #include template class OverrideStream { Stream *stream; const int nType; const int nVersion; public: OverrideStream(Stream *stream_, int nType_, int nVersion_) : stream(stream_), nType(nType_), nVersion(nVersion_) {} template OverrideStream &operator<<(const T &obj) { // Serialize to this stream ::Serialize(*this, obj); return (*this); } template OverrideStream &operator>>(T &obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); } void write(const char *pch, size_t nSize) { stream->write(pch, nSize); } void read(char *pch, size_t nSize) { stream->read(pch, nSize); } int GetVersion() const { return nVersion; } int GetType() const { return nType; } }; template OverrideStream WithOrVersion(S *s, int nVersionFlag) { return OverrideStream(s, s->GetType(), s->GetVersion() | nVersionFlag); } /** * Minimal stream for overwriting and/or appending to an existing byte vector. * * The referenced vector will grow as necessary. */ class CVectorWriter { public: /** * @param[in] nTypeIn Serialization Type * @param[in] nVersionIn Serialization Version (including any flags) * @param[in] vchDataIn Referenced byte vector to overwrite/append * @param[in] nPosIn Starting position. Vector index where writes should * start. The vector will initially grow as necessary to max(index, * vec.size()). So to append, use vec.size(). */ CVectorWriter(int nTypeIn, int nVersionIn, std::vector &vchDataIn, size_t nPosIn) : nType(nTypeIn), nVersion(nVersionIn), vchData(vchDataIn), nPos(nPosIn) { if (nPos > vchData.size()) vchData.resize(nPos); } /** * (other params same as above) * @param[in] args A list of items to serialize starting at nPos. */ template CVectorWriter(int nTypeIn, int nVersionIn, std::vector &vchDataIn, size_t nPosIn, Args &&... args) : CVectorWriter(nTypeIn, nVersionIn, vchDataIn, nPosIn) { ::SerializeMany(*this, std::forward(args)...); } void write(const char *pch, size_t nSize) { assert(nPos <= vchData.size()); size_t nOverwrite = std::min(nSize, vchData.size() - nPos); if (nOverwrite) { memcpy(vchData.data() + nPos, reinterpret_cast(pch), nOverwrite); } if (nOverwrite < nSize) { vchData.insert(vchData.end(), reinterpret_cast(pch) + nOverwrite, reinterpret_cast(pch) + nSize); } nPos += nSize; } template CVectorWriter &operator<<(const T &obj) { // Serialize to this stream ::Serialize(*this, obj); return (*this); } int GetVersion() const { return nVersion; } int GetType() const { return nType; } void seek(size_t nSize) { nPos += nSize; if (nPos > vchData.size()) vchData.resize(nPos); } private: const int nType; const int nVersion; std::vector &vchData; size_t nPos; }; /** * Double ended buffer combining vector and stream-like interfaces. * * >> and << read and write unformatted data using the above serialization * templates. Fills with data in linear time; some stringstream implementations * take N^2 time. */ class CDataStream { protected: typedef CSerializeData vector_type; vector_type vch; unsigned int nReadPos; int nType; int nVersion; public: typedef vector_type::allocator_type allocator_type; typedef vector_type::size_type size_type; typedef vector_type::difference_type difference_type; typedef vector_type::reference reference; typedef vector_type::const_reference const_reference; typedef vector_type::value_type value_type; typedef vector_type::iterator iterator; typedef vector_type::const_iterator const_iterator; typedef vector_type::reverse_iterator reverse_iterator; explicit CDataStream(int nTypeIn, int nVersionIn) { Init(nTypeIn, nVersionIn); } CDataStream(const_iterator pbegin, const_iterator pend, int nTypeIn, int nVersionIn) : vch(pbegin, pend) { Init(nTypeIn, nVersionIn); } CDataStream(const char *pbegin, const char *pend, int nTypeIn, int nVersionIn) : vch(pbegin, pend) { Init(nTypeIn, nVersionIn); } CDataStream(const vector_type &vchIn, int nTypeIn, int nVersionIn) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } CDataStream(const std::vector &vchIn, int nTypeIn, int nVersionIn) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } CDataStream(const std::vector &vchIn, int nTypeIn, int nVersionIn) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } template CDataStream(int nTypeIn, int nVersionIn, Args &&... args) { Init(nTypeIn, nVersionIn); ::SerializeMany(*this, std::forward(args)...); } void Init(int nTypeIn, int nVersionIn) { nReadPos = 0; nType = nTypeIn; nVersion = nVersionIn; } CDataStream &operator+=(const CDataStream &b) { vch.insert(vch.end(), b.begin(), b.end()); return *this; } friend CDataStream operator+(const CDataStream &a, const CDataStream &b) { CDataStream ret = a; ret += b; return (ret); } std::string str() const { return (std::string(begin(), end())); } // // Vector subset // const_iterator begin() const { return vch.begin() + nReadPos; } iterator begin() { return vch.begin() + nReadPos; } const_iterator end() const { return vch.end(); } iterator end() { return vch.end(); } size_type size() const { return vch.size() - nReadPos; } bool empty() const { return vch.size() == nReadPos; } void resize(size_type n, value_type c = 0) { vch.resize(n + nReadPos, c); } void reserve(size_type n) { vch.reserve(n + nReadPos); } const_reference operator[](size_type pos) const { return vch[pos + nReadPos]; } reference operator[](size_type pos) { return vch[pos + nReadPos]; } void clear() { vch.clear(); nReadPos = 0; } iterator insert(iterator it, const char &x = char()) { return vch.insert(it, x); } void insert(iterator it, size_type n, const char &x) { vch.insert(it, n, x); } value_type *data() { return vch.data() + nReadPos; } const value_type *data() const { return vch.data() + nReadPos; } void insert(iterator it, std::vector::const_iterator first, std::vector::const_iterator last) { if (last == first) { return; } assert(last - first > 0); if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos) { // special case for inserting at the front when there's room nReadPos -= (last - first); memcpy(&vch[nReadPos], &first[0], last - first); } else { vch.insert(it, first, last); } } void insert(iterator it, const char *first, const char *last) { if (last == first) { return; } assert(last - first > 0); if (it == vch.begin() + nReadPos && (unsigned int)(last - first) <= nReadPos) { // special case for inserting at the front when there's room nReadPos -= (last - first); memcpy(&vch[nReadPos], &first[0], last - first); } else { vch.insert(it, first, last); } } iterator erase(iterator it) { if (it == vch.begin() + nReadPos) { // special case for erasing from the front if (++nReadPos >= vch.size()) { // whenever we reach the end, we take the opportunity to clear // the buffer nReadPos = 0; return vch.erase(vch.begin(), vch.end()); } return vch.begin() + nReadPos; } else { return vch.erase(it); } } iterator erase(iterator first, iterator last) { if (first == vch.begin() + nReadPos) { // special case for erasing from the front if (last == vch.end()) { nReadPos = 0; return vch.erase(vch.begin(), vch.end()); } else { nReadPos = (last - vch.begin()); return last; } } else return vch.erase(first, last); } inline void Compact() { vch.erase(vch.begin(), vch.begin() + nReadPos); nReadPos = 0; } bool Rewind(size_type n) { // Rewind by n characters if the buffer hasn't been compacted yet if (n > nReadPos) return false; nReadPos -= n; return true; } // // Stream subset // bool eof() const { return size() == 0; } CDataStream *rdbuf() { return this; } - int in_avail() { return size(); } + int in_avail() const { return size(); } void SetType(int n) { nType = n; } int GetType() const { return nType; } void SetVersion(int n) { nVersion = n; } int GetVersion() const { return nVersion; } void read(char *pch, size_t nSize) { if (nSize == 0) { return; } // Read from the beginning of the buffer unsigned int nReadPosNext = nReadPos + nSize; if (nReadPosNext > vch.size()) { throw std::ios_base::failure("CDataStream::read(): end of data"); } memcpy(pch, &vch[nReadPos], nSize); if (nReadPosNext == vch.size()) { nReadPos = 0; vch.clear(); return; } nReadPos = nReadPosNext; } void ignore(int nSize) { // Ignore from the beginning of the buffer if (nSize < 0) { throw std::ios_base::failure( "CDataStream::ignore(): nSize negative"); } unsigned int nReadPosNext = nReadPos + nSize; if (nReadPosNext >= vch.size()) { if (nReadPosNext > vch.size()) throw std::ios_base::failure( "CDataStream::ignore(): end of data"); nReadPos = 0; vch.clear(); return; } nReadPos = nReadPosNext; } void write(const char *pch, size_t nSize) { // Write to the end of the buffer vch.insert(vch.end(), pch, pch + nSize); } template void Serialize(Stream &s) const { // Special case: stream << stream concatenates like stream += stream if (!vch.empty()) s.write((char *)&vch[0], vch.size() * sizeof(vch[0])); } template CDataStream &operator<<(const T &obj) { // Serialize to this stream ::Serialize(*this, obj); return (*this); } template CDataStream &operator>>(T &obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); } void GetAndClear(CSerializeData &d) { d.insert(d.end(), begin(), end()); clear(); } /** * XOR the contents of this stream with a certain key. * * @param[in] key The key used to XOR the data in this stream. */ void Xor(const std::vector &key) { if (key.size() == 0) { return; } for (size_type i = 0, j = 0; i != size(); i++) { vch[i] ^= key[j++]; // This potentially acts on very many bytes of data, so it's // important that we calculate `j`, i.e. the `key` index in this way // instead of doing a %, which would effectively be a division for // each byte Xor'd -- much slower than need be. if (j == key.size()) j = 0; } } }; /** * Non-refcounted RAII wrapper for FILE* * * Will automatically close the file when it goes out of scope if not null. If * you're returning the file pointer, return file.release(). If you need to * close the file early, use file.fclose() instead of fclose(file). */ class CAutoFile { private: // Disallow copies CAutoFile(const CAutoFile &); CAutoFile &operator=(const CAutoFile &); const int nType; const int nVersion; FILE *file; public: CAutoFile(FILE *filenew, int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn) { file = filenew; } ~CAutoFile() { fclose(); } void fclose() { if (file) { ::fclose(file); file = nullptr; } } /** * Get wrapped FILE* with transfer of ownership. * @note This will invalidate the CAutoFile object, and makes it the * responsibility of the caller of this function to clean up the returned * FILE*. */ FILE *release() { FILE *ret = file; file = nullptr; return ret; } /** * Get wrapped FILE* without transfer of ownership. * @note Ownership of the FILE* will remain with this class. Use this only * if the scope of the CAutoFile outlives use of the passed pointer. */ FILE *Get() const { return file; } /** Return true if the wrapped FILE* is nullptr, false otherwise. */ bool IsNull() const { return (file == nullptr); } // // Stream subset // int GetType() const { return nType; } int GetVersion() const { return nVersion; } void read(char *pch, size_t nSize) { if (!file) throw std::ios_base::failure( "CAutoFile::read: file handle is nullptr"); if (fread(pch, 1, nSize, file) != nSize) throw std::ios_base::failure(feof(file) ? "CAutoFile::read: end of file" : "CAutoFile::read: fread failed"); } void ignore(size_t nSize) { if (!file) throw std::ios_base::failure( "CAutoFile::ignore: file handle is nullptr"); uint8_t data[4096]; while (nSize > 0) { size_t nNow = std::min(nSize, sizeof(data)); if (fread(data, 1, nNow, file) != nNow) throw std::ios_base::failure( feof(file) ? "CAutoFile::ignore: end of file" : "CAutoFile::read: fread failed"); nSize -= nNow; } } void write(const char *pch, size_t nSize) { if (!file) throw std::ios_base::failure( "CAutoFile::write: file handle is nullptr"); if (fwrite(pch, 1, nSize, file) != nSize) throw std::ios_base::failure("CAutoFile::write: write failed"); } template CAutoFile &operator<<(const T &obj) { // Serialize to this stream if (!file) throw std::ios_base::failure( "CAutoFile::operator<<: file handle is nullptr"); ::Serialize(*this, obj); return (*this); } template CAutoFile &operator>>(T &obj) { // Unserialize from this stream if (!file) throw std::ios_base::failure( "CAutoFile::operator>>: file handle is nullptr"); ::Unserialize(*this, obj); return (*this); } }; /** * Non-refcounted RAII wrapper around a FILE* that implements a ring buffer to * deserialize from. It guarantees the ability to rewind a given number of * bytes. * * Will automatically close the file when it goes out of scope if not null. If * you need to close the file early, use file.fclose() instead of fclose(file). */ class CBufferedFile { private: // Disallow copies CBufferedFile(const CBufferedFile &); CBufferedFile &operator=(const CBufferedFile &); const int nType; const int nVersion; // source file FILE *src; // how many bytes have been read from source uint64_t nSrcPos; // how many bytes have been read from this uint64_t nReadPos; // up to which position we're allowed to read uint64_t nReadLimit; // how many bytes we guarantee to rewind uint64_t nRewind; // the buffer std::vector vchBuf; protected: // read data from the source to fill the buffer bool Fill() { unsigned int pos = nSrcPos % vchBuf.size(); unsigned int readNow = vchBuf.size() - pos; unsigned int nAvail = vchBuf.size() - (nSrcPos - nReadPos) - nRewind; if (nAvail < readNow) readNow = nAvail; if (readNow == 0) return false; size_t nBytes = fread((void *)&vchBuf[pos], 1, readNow, src); if (nBytes == 0) { throw std::ios_base::failure( feof(src) ? "CBufferedFile::Fill: end of file" : "CBufferedFile::Fill: fread failed"); } else { nSrcPos += nBytes; return true; } } public: CBufferedFile(FILE *fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), nReadPos(0), nReadLimit((uint64_t)(-1)), nRewind(nRewindIn), vchBuf(nBufSize, 0) { src = fileIn; } ~CBufferedFile() { fclose(); } int GetVersion() const { return nVersion; } int GetType() const { return nType; } void fclose() { if (src) { ::fclose(src); src = nullptr; } } // check whether we're at the end of the source file bool eof() const { return nReadPos == nSrcPos && feof(src); } // read a number of bytes void read(char *pch, size_t nSize) { if (nSize + nReadPos > nReadLimit) throw std::ios_base::failure("Read attempted past buffer limit"); if (nSize + nRewind > vchBuf.size()) throw std::ios_base::failure("Read larger than buffer size"); while (nSize > 0) { if (nReadPos == nSrcPos) Fill(); unsigned int pos = nReadPos % vchBuf.size(); size_t nNow = nSize; if (nNow + pos > vchBuf.size()) nNow = vchBuf.size() - pos; if (nNow + nReadPos > nSrcPos) nNow = nSrcPos - nReadPos; memcpy(pch, &vchBuf[pos], nNow); nReadPos += nNow; pch += nNow; nSize -= nNow; } } // return the current reading position - uint64_t GetPos() { return nReadPos; } + uint64_t GetPos() const { return nReadPos; } // rewind to a given reading position bool SetPos(uint64_t nPos) { nReadPos = nPos; if (nReadPos + nRewind < nSrcPos) { nReadPos = nSrcPos - nRewind; return false; } else if (nReadPos > nSrcPos) { nReadPos = nSrcPos; return false; } else { return true; } } bool Seek(uint64_t nPos) { long nLongPos = nPos; if (nPos != (uint64_t)nLongPos) return false; if (fseek(src, nLongPos, SEEK_SET)) return false; nLongPos = ftell(src); nSrcPos = nLongPos; nReadPos = nLongPos; return true; } // Prevent reading beyond a certain position. No argument removes the limit. bool SetLimit(uint64_t nPos = (uint64_t)(-1)) { if (nPos < nReadPos) return false; nReadLimit = nPos; return true; } template CBufferedFile &operator>>(T &obj) { // Unserialize from this stream ::Unserialize(*this, obj); return (*this); } // search for a given byte in the stream, and remain positioned on it void FindByte(char ch) { while (true) { if (nReadPos == nSrcPos) Fill(); if (vchBuf[nReadPos % vchBuf.size()] == ch) break; nReadPos++; } } }; #endif // BITCOIN_STREAMS_H diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index e8fb03714..dc366d574 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -1,897 +1,897 @@ // 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. #include "coins.h" #include "consensus/validation.h" #include "script/standard.h" #include "test/test_bitcoin.h" #include "uint256.h" #include "undo.h" #include "utilstrencodings.h" #include "validation.h" #include #include #include namespace { //! equality test bool operator==(const Coin &a, const Coin &b) { // Empty Coin objects are always equal. if (a.IsSpent() && b.IsSpent()) { return true; } return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() && a.GetTxOut() == b.GetTxOut(); } class CCoinsViewTest : public CCoinsView { uint256 hashBestBlock_; std::map map_; public: bool GetCoin(const COutPoint &outpoint, Coin &coin) const override { std::map::const_iterator it = map_.find(outpoint); if (it == map_.end()) { return false; } coin = it->second; if (coin.IsSpent() && InsecureRandBool() == 0) { // Randomly return false in case of an empty entry. return false; } return true; } bool HaveCoin(const COutPoint &outpoint) const override { Coin coin; return GetCoin(outpoint, coin); } uint256 GetBestBlock() const override { return hashBestBlock_; } bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Same optimization used in CCoinsViewDB is to only write dirty // entries. map_[it->first] = it->second.coin; if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) { // Randomly delete empty entries on write. map_.erase(it->first); } } mapCoins.erase(it++); } if (!hashBlock.IsNull()) { hashBestBlock_ = hashBlock; } return true; } }; class CCoinsViewCacheTest : public CCoinsViewCache { public: explicit CCoinsViewCacheTest(CCoinsView *_base) : CCoinsViewCache(_base) {} void SelfTest() const { // Manually recompute the dynamic usage of the whole data, and compare // it. size_t ret = memusage::DynamicUsage(cacheCoins); size_t count = 0; for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) { ret += it->second.coin.DynamicMemoryUsage(); count++; } BOOST_CHECK_EQUAL(GetCacheSize(), count); BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret); } - CCoinsMap &map() { return cacheCoins; } - size_t &usage() { return cachedCoinsUsage; } + CCoinsMap &map() const { return cacheCoins; } + size_t &usage() const { return cachedCoinsUsage; } }; } // namespace BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup) static const unsigned int NUM_SIMULATION_ITERATIONS = 40000; // This is a large randomized insert/remove simulation test on a variable-size // stack of caches on top of CCoinsViewTest. // // It will randomly create/update/delete Coin entries to a tip of caches, with // txids picked from a limited list of random 256-bit hashes. Occasionally, a // new tip is added to the stack of caches, or the tip is flushed and removed. // // During the process, booleans are kept to make sure that the randomized // operation hits all branches. BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) { // Various coverage trackers. bool removed_all_caches = false; bool reached_4_caches = false; bool added_an_entry = false; bool added_an_unspendable_entry = false; bool removed_an_entry = false; bool updated_an_entry = false; bool found_an_entry = false; bool missed_an_entry = false; bool uncached_an_entry = false; // A simple map to track what we expect the cache stack to represent. std::map result; // The cache stack. // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A stack of CCoinsViewCaches on top. std::vector stack; // Start with one cache. stack.push_back(new CCoinsViewCacheTest(&base)); // Use a limited set of random transaction ids, so we do test overwriting // entries. std::vector txids; txids.resize(NUM_SIMULATION_ITERATIONS / 8); for (size_t i = 0; i < txids.size(); i++) { txids[i] = TxId(InsecureRand256()); } for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { // Do a random modification. { // txid we're going to modify in this iteration. TxId txid = txids[InsecureRandRange(txids.size())]; Coin &coin = result[COutPoint(txid, 0)]; const Coin &entry = (InsecureRandRange(500) == 0) ? AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0)); BOOST_CHECK(coin == entry); if (InsecureRandRange(5) == 0 || coin.IsSpent()) { CTxOut txout; txout.nValue = int64_t(insecure_rand()) * SATOSHI; if (InsecureRandRange(16) == 0 && coin.IsSpent()) { txout.scriptPubKey.assign(1 + InsecureRandBits(6), OP_RETURN); BOOST_CHECK(txout.scriptPubKey.IsUnspendable()); added_an_unspendable_entry = true; } else { // Random sizes so we can test memory usage accounting txout.scriptPubKey.assign(InsecureRandBits(6), 0); (coin.IsSpent() ? added_an_entry : updated_an_entry) = true; coin = Coin(txout, 1, false); } Coin newcoin(txout, 1, false); stack.back()->AddCoin(COutPoint(txid, 0), newcoin, !coin.IsSpent() || insecure_rand() & 1); } else { removed_an_entry = true; coin.Clear(); stack.back()->SpendCoin(COutPoint(txid, 0)); } } // One every 10 iterations, remove a random entry from the cache if (InsecureRandRange(10)) { COutPoint out(txids[insecure_rand() % txids.size()], 0); int cacheid = insecure_rand() % stack.size(); stack[cacheid]->Uncache(out); uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out); } // Once every 1000 iterations and at the end, verify the full cache. if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { for (auto it = result.begin(); it != result.end(); it++) { bool have = stack.back()->HaveCoin(it->first); const Coin &coin = stack.back()->AccessCoin(it->first); BOOST_CHECK(have == !coin.IsSpent()); BOOST_CHECK(coin == it->second); if (coin.IsSpent()) { missed_an_entry = true; } else { BOOST_CHECK(stack.back()->HaveCoinInCache(it->first)); found_an_entry = true; } } for (const CCoinsViewCacheTest *test : stack) { test->SelfTest(); } } // Every 100 iterations, flush an intermediate cache if (InsecureRandRange(100) == 0) { if (stack.size() > 1 && InsecureRandBool() == 0) { unsigned int flushIndex = InsecureRandRange(stack.size() - 1); stack[flushIndex]->Flush(); } } if (InsecureRandRange(100) == 0) { // Every 100 iterations, change the cache stack. if (stack.size() > 0 && InsecureRandBool() == 0) { // Remove the top cache stack.back()->Flush(); delete stack.back(); stack.pop_back(); } if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) { // Add a new cache CCoinsView *tip = &base; if (stack.size() > 0) { tip = stack.back(); } else { removed_all_caches = true; } stack.push_back(new CCoinsViewCacheTest(tip)); if (stack.size() == 4) { reached_4_caches = true; } } } } // Clean up the stack. while (stack.size() > 0) { delete stack.back(); stack.pop_back(); } // Verify coverage. BOOST_CHECK(removed_all_caches); BOOST_CHECK(reached_4_caches); BOOST_CHECK(added_an_entry); BOOST_CHECK(added_an_unspendable_entry); BOOST_CHECK(removed_an_entry); BOOST_CHECK(updated_an_entry); BOOST_CHECK(found_an_entry); BOOST_CHECK(missed_an_entry); BOOST_CHECK(uncached_an_entry); } // Store of all necessary tx and undo data for next test typedef std::map> UtxoData; UtxoData utxoData; UtxoData::iterator FindRandomFrom(const std::set &utxoSet) { assert(utxoSet.size()); auto utxoSetIt = utxoSet.lower_bound(COutPoint(InsecureRand256(), 0)); if (utxoSetIt == utxoSet.end()) { utxoSetIt = utxoSet.begin(); } auto utxoDataIt = utxoData.find(*utxoSetIt); assert(utxoDataIt != utxoData.end()); return utxoDataIt; } // This test is similar to the previous test except the emphasis is on testing // the functionality of UpdateCoins random txs are created and UpdateCoins is // used to update the cache stack. In particular it is tested that spending a // duplicate coinbase tx has the expected effect (the other duplicate is // overwitten at all cache levels) BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) { bool spent_a_duplicate_coinbase = false; // A simple map to track what we expect the cache stack to represent. std::map result; // The cache stack. // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A stack of CCoinsViewCaches on top. std::vector stack; // Start with one cache. stack.push_back(new CCoinsViewCacheTest(&base)); // Track the txids we've used in various sets std::set coinbase_coins; std::set disconnected_coins; std::set duplicate_coins; std::set utxoset; for (int64_t i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { uint32_t randiter = insecure_rand(); // 19/20 txs add a new transaction if (randiter % 20 < 19) { CMutableTransaction tx; tx.vin.resize(1); tx.vout.resize(1); // Keep txs unique unless intended to duplicate. tx.vout[0].nValue = i * SATOSHI; // Random sizes so we can test memory usage accounting tx.vout[0].scriptPubKey.assign(insecure_rand() & 0x3F, 0); unsigned int height = insecure_rand(); Coin old_coin; // 2/20 times create a new coinbase if (randiter % 20 < 2 || coinbase_coins.size() < 10) { // 1/10 of those times create a duplicate coinbase if (InsecureRandRange(10) == 0 && coinbase_coins.size()) { auto utxod = FindRandomFrom(coinbase_coins); // Reuse the exact same coinbase tx = std::get<0>(utxod->second); // shouldn't be available for reconnection if its been // duplicated disconnected_coins.erase(utxod->first); duplicate_coins.insert(utxod->first); } else { coinbase_coins.insert(COutPoint(tx.GetId(), 0)); } assert(CTransaction(tx).IsCoinBase()); } // 17/20 times reconnect previous or add a regular tx else { COutPoint prevout; // 1/20 times reconnect a previously disconnected tx if (randiter % 20 == 2 && disconnected_coins.size()) { auto utxod = FindRandomFrom(disconnected_coins); tx = std::get<0>(utxod->second); prevout = tx.vin[0].prevout; if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) { disconnected_coins.erase(utxod->first); continue; } // If this tx is already IN the UTXO, then it must be a // coinbase, and it must be a duplicate if (utxoset.count(utxod->first)) { assert(CTransaction(tx).IsCoinBase()); assert(duplicate_coins.count(utxod->first)); } disconnected_coins.erase(utxod->first); } // 16/20 times create a regular tx else { auto utxod = FindRandomFrom(utxoset); prevout = utxod->first; // Construct the tx to spend the coins of prevouthash tx.vin[0].prevout = COutPoint(prevout.GetTxId(), 0); assert(!CTransaction(tx).IsCoinBase()); } // In this simple test coins only have two states, spent or // unspent, save the unspent state to restore old_coin = result[prevout]; // Update the expected result of prevouthash to know these coins // are spent result[prevout].Clear(); utxoset.erase(prevout); // The test is designed to ensure spending a duplicate coinbase // will work properly if that ever happens and not resurrect the // previously overwritten coinbase if (duplicate_coins.count(prevout)) { spent_a_duplicate_coinbase = true; } } // Update the expected result to know about the new output coins assert(tx.vout.size() == 1); const COutPoint outpoint(tx.GetId(), 0); result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase()); // Call UpdateCoins on the top cache CTxUndo undo; UpdateCoins(*(stack.back()), CTransaction(tx), undo, height); // Update the utxo set for future spends utxoset.insert(outpoint); // Track this tx and undo info to use later utxoData.emplace(outpoint, std::make_tuple(CTransaction(tx), undo, old_coin)); } // 1/20 times undo a previous transaction else if (utxoset.size()) { auto utxod = FindRandomFrom(utxoset); CTransaction &tx = std::get<0>(utxod->second); CTxUndo &undo = std::get<1>(utxod->second); Coin &orig_coin = std::get<2>(utxod->second); // Update the expected result // Remove new outputs result[utxod->first].Clear(); // If not coinbase restore prevout if (!tx.IsCoinBase()) { result[tx.vin[0].prevout] = orig_coin; } // Disconnect the tx from the current UTXO // See code in DisconnectBlock // remove outputs stack.back()->SpendCoin(utxod->first); // restore inputs if (!tx.IsCoinBase()) { const COutPoint &out = tx.vin[0].prevout; UndoCoinSpend(undo.vprevout[0], *(stack.back()), out); } // Store as a candidate for reconnection disconnected_coins.insert(utxod->first); // Update the utxoset utxoset.erase(utxod->first); if (!tx.IsCoinBase()) { utxoset.insert(tx.vin[0].prevout); } } // Once every 1000 iterations and at the end, verify the full cache. if (InsecureRandRange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { for (auto it = result.begin(); it != result.end(); it++) { bool have = stack.back()->HaveCoin(it->first); const Coin &coin = stack.back()->AccessCoin(it->first); BOOST_CHECK(have == !coin.IsSpent()); BOOST_CHECK(coin == it->second); } } // One every 10 iterations, remove a random entry from the cache if (utxoset.size() > 1 && InsecureRandRange(30)) { stack[insecure_rand() % stack.size()]->Uncache( FindRandomFrom(utxoset)->first); } if (disconnected_coins.size() > 1 && InsecureRandRange(30)) { stack[insecure_rand() % stack.size()]->Uncache( FindRandomFrom(disconnected_coins)->first); } if (duplicate_coins.size() > 1 && InsecureRandRange(30)) { stack[insecure_rand() % stack.size()]->Uncache( FindRandomFrom(duplicate_coins)->first); } if (InsecureRandRange(100) == 0) { // Every 100 iterations, flush an intermediate cache if (stack.size() > 1 && InsecureRandBool() == 0) { unsigned int flushIndex = InsecureRandRange(stack.size() - 1); stack[flushIndex]->Flush(); } } if (InsecureRandRange(100) == 0) { // Every 100 iterations, change the cache stack. if (stack.size() > 0 && InsecureRandBool() == 0) { stack.back()->Flush(); delete stack.back(); stack.pop_back(); } if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) { CCoinsView *tip = &base; if (stack.size() > 0) { tip = stack.back(); } stack.push_back(new CCoinsViewCacheTest(tip)); } } } // Clean up the stack. while (stack.size() > 0) { delete stack.back(); stack.pop_back(); } // Verify coverage. BOOST_CHECK(spent_a_duplicate_coinbase); } BOOST_AUTO_TEST_CASE(coin_serialization) { // Good example CDataStream ss1( ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION); Coin c1; ss1 >> c1; BOOST_CHECK_EQUAL(c1.IsCoinBase(), false); BOOST_CHECK_EQUAL(c1.GetHeight(), 203998U); BOOST_CHECK_EQUAL(c1.GetTxOut().nValue, int64_t(60000000000) * SATOSHI); BOOST_CHECK_EQUAL(HexStr(c1.GetTxOut().scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex( "816115944e077fe7c803cfa57f29b36bf87c1d35")))))); // Good example CDataStream ss2( ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION); Coin c2; ss2 >> c2; BOOST_CHECK_EQUAL(c2.IsCoinBase(), true); BOOST_CHECK_EQUAL(c2.GetHeight(), 120891); BOOST_CHECK_EQUAL(c2.GetTxOut().nValue, 110397 * SATOSHI); BOOST_CHECK_EQUAL(HexStr(c2.GetTxOut().scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex( "8c988f1a4a4de2161e0f50aac7f17e7f9555caa4")))))); // Smallest possible example CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION); Coin c3; ss3 >> c3; BOOST_CHECK_EQUAL(c3.IsCoinBase(), false); BOOST_CHECK_EQUAL(c3.GetHeight(), 0); BOOST_CHECK_EQUAL(c3.GetTxOut().nValue, Amount::zero()); BOOST_CHECK_EQUAL(c3.GetTxOut().scriptPubKey.size(), 0); // scriptPubKey that ends beyond the end of the stream CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION); try { Coin c4; ss4 >> c4; BOOST_CHECK_MESSAGE(false, "We should have thrown"); } catch (const std::ios_base::failure &e) { } // Very large scriptPubKey (3*10^9 bytes) past the end of the stream CDataStream tmp(SER_DISK, CLIENT_VERSION); uint64_t x = 3000000000ULL; tmp << VARINT(x); BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00"); CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION); try { Coin c5; ss5 >> c5; BOOST_CHECK_MESSAGE(false, "We should have thrown"); } catch (const std::ios_base::failure &e) { } } static const COutPoint OUTPOINT; static const Amount PRUNED(-1 * SATOSHI); static const Amount ABSENT(-2 * SATOSHI); static const Amount FAIL(-3 * SATOSHI); static const Amount VALUE1(100 * SATOSHI); static const Amount VALUE2(200 * SATOSHI); static const Amount VALUE3(300 * SATOSHI); static const char DIRTY = CCoinsCacheEntry::DIRTY; static const char FRESH = CCoinsCacheEntry::FRESH; static const char NO_ENTRY = -1; static const auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)}; static const auto CLEAN_FLAGS = {char(0), FRESH}; static const auto ABSENT_FLAGS = {NO_ENTRY}; static void SetCoinValue(const Amount value, Coin &coin) { assert(value != ABSENT); coin.Clear(); assert(coin.IsSpent()); if (value != PRUNED) { CTxOut out; out.nValue = value; coin = Coin(std::move(out), 1, false); assert(!coin.IsSpent()); } } size_t InsertCoinMapEntry(CCoinsMap &map, const Amount value, char flags) { if (value == ABSENT) { assert(flags == NO_ENTRY); return 0; } assert(flags != NO_ENTRY); CCoinsCacheEntry entry; entry.flags = flags; SetCoinValue(value, entry.coin); auto inserted = map.emplace(OUTPOINT, std::move(entry)); assert(inserted.second); return inserted.first->second.coin.DynamicMemoryUsage(); } void GetCoinMapEntry(const CCoinsMap &map, Amount &value, char &flags) { auto it = map.find(OUTPOINT); if (it == map.end()) { value = ABSENT; flags = NO_ENTRY; } else { if (it->second.coin.IsSpent()) { value = PRUNED; } else { value = it->second.coin.GetTxOut().nValue; } flags = it->second.flags; assert(flags != NO_ENTRY); } } void WriteCoinViewEntry(CCoinsView &view, const Amount value, char flags) { CCoinsMap map; InsertCoinMapEntry(map, value, flags); view.BatchWrite(map, {}); } class SingleEntryCacheTest { public: SingleEntryCacheTest(const Amount base_value, const Amount cache_value, char cache_flags) { WriteCoinViewEntry(base, base_value, base_value == ABSENT ? NO_ENTRY : DIRTY); cache.usage() += InsertCoinMapEntry(cache.map(), cache_value, cache_flags); } CCoinsView root; CCoinsViewCacheTest base{&root}; CCoinsViewCacheTest cache{&base}; }; void CheckAccessCoin(const Amount base_value, const Amount cache_value, const Amount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); test.cache.AccessCoin(OUTPOINT); test.cache.SelfTest(); Amount result_value; char result_flags; GetCoinMapEntry(test.cache.map(), result_value, result_flags); BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); } BOOST_AUTO_TEST_CASE(coin_access) { /* Check AccessCoin behavior, requesting a coin from a cache view layered on * top of a base view, and checking the resulting entry in the cache after * the access. * * Base Cache Result Cache Result * Value Value Value Flags Flags */ CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); CheckAccessCoin(ABSENT, PRUNED, PRUNED, 0, 0); CheckAccessCoin(ABSENT, PRUNED, PRUNED, FRESH, FRESH); CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY, DIRTY); CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0, 0); CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH, FRESH); CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY, DIRTY); CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(PRUNED, ABSENT, PRUNED, NO_ENTRY, FRESH); CheckAccessCoin(PRUNED, PRUNED, PRUNED, 0, 0); CheckAccessCoin(PRUNED, PRUNED, PRUNED, FRESH, FRESH); CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY); CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(PRUNED, VALUE2, VALUE2, 0, 0); CheckAccessCoin(PRUNED, VALUE2, VALUE2, FRESH, FRESH); CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY); CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY, 0); CheckAccessCoin(VALUE1, PRUNED, PRUNED, 0, 0); CheckAccessCoin(VALUE1, PRUNED, PRUNED, FRESH, FRESH); CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY); CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH); CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0, 0); CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH, FRESH); CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY); CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); } void CheckSpendCoin(Amount base_value, Amount cache_value, Amount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); test.cache.SpendCoin(OUTPOINT); test.cache.SelfTest(); Amount result_value; char result_flags; GetCoinMapEntry(test.cache.map(), result_value, result_flags); BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); }; BOOST_AUTO_TEST_CASE(coin_spend) { /** * Check SpendCoin behavior, requesting a coin from a cache view layered on * top of a base view, spending, and then checking the resulting entry in * the cache after the modification. * * Base Cache Result Cache Result * Value Value Value Flags Flags */ CheckSpendCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); CheckSpendCoin(ABSENT, PRUNED, PRUNED, 0, DIRTY); CheckSpendCoin(ABSENT, PRUNED, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(ABSENT, PRUNED, PRUNED, DIRTY, DIRTY); CheckSpendCoin(ABSENT, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(ABSENT, VALUE2, PRUNED, 0, DIRTY); CheckSpendCoin(ABSENT, VALUE2, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(ABSENT, VALUE2, PRUNED, DIRTY, DIRTY); CheckSpendCoin(ABSENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(PRUNED, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); CheckSpendCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY); CheckSpendCoin(PRUNED, PRUNED, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY); CheckSpendCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(PRUNED, VALUE2, PRUNED, 0, DIRTY); CheckSpendCoin(PRUNED, VALUE2, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(PRUNED, VALUE2, PRUNED, DIRTY, DIRTY); CheckSpendCoin(PRUNED, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, ABSENT, PRUNED, NO_ENTRY, DIRTY); CheckSpendCoin(VALUE1, PRUNED, PRUNED, 0, DIRTY); CheckSpendCoin(VALUE1, PRUNED, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY); CheckSpendCoin(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, VALUE2, PRUNED, 0, DIRTY); CheckSpendCoin(VALUE1, VALUE2, ABSENT, FRESH, NO_ENTRY); CheckSpendCoin(VALUE1, VALUE2, PRUNED, DIRTY, DIRTY); CheckSpendCoin(VALUE1, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); } void CheckAddCoinBase(Amount base_value, Amount cache_value, Amount modify_value, Amount expected_value, char cache_flags, char expected_flags, bool coinbase) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); Amount result_value; char result_flags; try { CTxOut output; output.nValue = modify_value; test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase); test.cache.SelfTest(); GetCoinMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error &e) { result_value = FAIL; result_flags = NO_ENTRY; } BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); } // Simple wrapper for CheckAddCoinBase function above that loops through // different possible base_values, making sure each one gives the same results. // This wrapper lets the coin_add test below be shorter and less repetitive, // while still verifying that the CoinsViewCache::AddCoin implementation ignores // base values. template void CheckAddCoin(Args &&... args) { for (Amount base_value : {ABSENT, PRUNED, VALUE1}) { CheckAddCoinBase(base_value, std::forward(args)...); } } BOOST_AUTO_TEST_CASE(coin_add) { /** * Check AddCoin behavior, requesting a new coin from a cache view, writing * a modification to the coin, and then checking the resulting entry in the * cache after the modification. Verify behavior with the with the AddCoin * potential_overwrite argument set to false, and to true. * * Cache Write Result Cache Result potential_overwrite * Value Value Value Flags Flags */ CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY | FRESH, false); CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY, true); CheckAddCoin(PRUNED, VALUE3, VALUE3, 0, DIRTY | FRESH, false); CheckAddCoin(PRUNED, VALUE3, VALUE3, 0, DIRTY, true); CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH, DIRTY | FRESH, false); CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY, DIRTY, false); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY, DIRTY, true); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, false); CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true); CheckAddCoin(VALUE2, VALUE3, FAIL, 0, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, 0, DIRTY, true); CheckAddCoin(VALUE2, VALUE3, FAIL, FRESH, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true); CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY, DIRTY, true); CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY | FRESH, NO_ENTRY, false); CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true); } void CheckWriteCoin(Amount parent_value, Amount child_value, Amount expected_value, char parent_flags, char child_flags, char expected_flags) { SingleEntryCacheTest test(ABSENT, parent_value, parent_flags); Amount result_value; char result_flags; try { WriteCoinViewEntry(test.cache, child_value, child_flags); test.cache.SelfTest(); GetCoinMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error &e) { result_value = FAIL; result_flags = NO_ENTRY; } BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); } BOOST_AUTO_TEST_CASE(coin_write) { /* Check BatchWrite behavior, flushing one entry from a child cache to a * parent cache, and checking the resulting entry in the parent cache * after the write. * * Parent Child Result Parent Child Result * Value Value Value Flags Flags Flags */ CheckWriteCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY, NO_ENTRY); CheckWriteCoin(ABSENT, PRUNED, PRUNED, NO_ENTRY, DIRTY, DIRTY); CheckWriteCoin(ABSENT, PRUNED, ABSENT, NO_ENTRY, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY, DIRTY); CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY | FRESH, DIRTY | FRESH); CheckWriteCoin(PRUNED, ABSENT, PRUNED, 0, NO_ENTRY, 0); CheckWriteCoin(PRUNED, ABSENT, PRUNED, FRESH, NO_ENTRY, FRESH); CheckWriteCoin(PRUNED, ABSENT, PRUNED, DIRTY, NO_ENTRY, DIRTY); CheckWriteCoin(PRUNED, ABSENT, PRUNED, DIRTY | FRESH, NO_ENTRY, DIRTY | FRESH); CheckWriteCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY, DIRTY); CheckWriteCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY | FRESH, DIRTY); CheckWriteCoin(PRUNED, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(PRUNED, PRUNED, ABSENT, FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY); CheckWriteCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY | FRESH, DIRTY); CheckWriteCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(PRUNED, VALUE2, VALUE2, 0, DIRTY, DIRTY); CheckWriteCoin(PRUNED, VALUE2, VALUE2, 0, DIRTY | FRESH, DIRTY); CheckWriteCoin(PRUNED, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(PRUNED, VALUE2, VALUE2, FRESH, DIRTY | FRESH, DIRTY | FRESH); CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY | FRESH, DIRTY); CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH, DIRTY | FRESH); CheckWriteCoin(VALUE1, ABSENT, VALUE1, 0, NO_ENTRY, 0); CheckWriteCoin(VALUE1, ABSENT, VALUE1, FRESH, NO_ENTRY, FRESH); CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY, NO_ENTRY, DIRTY); CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY | FRESH, NO_ENTRY, DIRTY | FRESH); CheckWriteCoin(VALUE1, PRUNED, PRUNED, 0, DIRTY, DIRTY); CheckWriteCoin(VALUE1, PRUNED, FAIL, 0, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(VALUE1, PRUNED, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY); CheckWriteCoin(VALUE1, PRUNED, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); CheckWriteCoin(VALUE1, PRUNED, FAIL, DIRTY | FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, 0, DIRTY, DIRTY); CheckWriteCoin(VALUE1, VALUE2, FAIL, 0, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(VALUE1, VALUE2, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH); CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY | FRESH, DIRTY | FRESH, NO_ENTRY); // The checks above omit cases where the child flags are not DIRTY, since // they would be too repetitive (the parent cache is never updated in these // cases). The loop below covers these cases and makes sure the parent cache // is always left unchanged. for (Amount parent_value : {ABSENT, PRUNED, VALUE1}) { for (Amount child_value : {ABSENT, PRUNED, VALUE2}) { for (char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) { for (char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) { CheckWriteCoin(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags); } } } } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index da4a61370..30191495b 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -1,263 +1,263 @@ // Copyright (c) 2015-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. #include "prevector.h" #include #include "reverse_iterator.h" #include "serialize.h" #include "streams.h" #include "test/test_bitcoin.h" #include BOOST_FIXTURE_TEST_SUITE(prevector_tests, TestingSetup) template class prevector_tester { typedef std::vector realtype; realtype real_vector; realtype real_vector_alt; typedef prevector pretype; pretype pre_vector; pretype pre_vector_alt; typedef typename pretype::size_type Size; bool passed = true; FastRandomContext rand_cache; uint256 rand_seed; template void local_check_equal(A a, B b) { local_check(a == b); } void local_check(bool b) { passed &= b; } void test() { const pretype &const_pre_vector = pre_vector; local_check_equal(real_vector.size(), pre_vector.size()); local_check_equal(real_vector.empty(), pre_vector.empty()); for (Size s = 0; s < real_vector.size(); s++) { local_check(real_vector[s] == pre_vector[s]); local_check(&(pre_vector[s]) == &(pre_vector.begin()[s])); local_check(&(pre_vector[s]) == &*(pre_vector.begin() + s)); local_check(&(pre_vector[s]) == &*((pre_vector.end() + s) - real_vector.size())); } // local_check(realtype(pre_vector) == real_vector); local_check(pretype(real_vector.begin(), real_vector.end()) == pre_vector); local_check(pretype(pre_vector.begin(), pre_vector.end()) == pre_vector); size_t pos = 0; for (const T &v : pre_vector) { local_check(v == real_vector[pos++]); } for (const T &v : reverse_iterate(pre_vector)) { local_check(v == real_vector[--pos]); } for (const T &v : const_pre_vector) { local_check(v == real_vector[pos++]); } for (const T &v : reverse_iterate(const_pre_vector)) { local_check(v == real_vector[--pos]); } CDataStream ss1(SER_DISK, 0); CDataStream ss2(SER_DISK, 0); ss1 << real_vector; ss2 << pre_vector; local_check_equal(ss1.size(), ss2.size()); for (Size s = 0; s < ss1.size(); s++) { local_check_equal(ss1[s], ss2[s]); } } public: void resize(Size s) { real_vector.resize(s); local_check_equal(real_vector.size(), s); pre_vector.resize(s); local_check_equal(pre_vector.size(), s); test(); } void reserve(Size s) { real_vector.reserve(s); local_check(real_vector.capacity() >= s); pre_vector.reserve(s); local_check(pre_vector.capacity() >= s); test(); } void insert(Size position, const T &value) { real_vector.insert(real_vector.begin() + position, value); pre_vector.insert(pre_vector.begin() + position, value); test(); } void insert(Size position, Size count, const T &value) { real_vector.insert(real_vector.begin() + position, count, value); pre_vector.insert(pre_vector.begin() + position, count, value); test(); } template void insert_range(Size position, I first, I last) { real_vector.insert(real_vector.begin() + position, first, last); pre_vector.insert(pre_vector.begin() + position, first, last); test(); } void erase(Size position) { real_vector.erase(real_vector.begin() + position); pre_vector.erase(pre_vector.begin() + position); test(); } void erase(Size first, Size last) { real_vector.erase(real_vector.begin() + first, real_vector.begin() + last); pre_vector.erase(pre_vector.begin() + first, pre_vector.begin() + last); test(); } void update(Size pos, const T &value) { real_vector[pos] = value; pre_vector[pos] = value; test(); } void push_back(const T &value) { real_vector.push_back(value); pre_vector.push_back(value); test(); } void pop_back() { real_vector.pop_back(); pre_vector.pop_back(); test(); } void clear() { real_vector.clear(); pre_vector.clear(); } void assign(Size n, const T &value) { real_vector.assign(n, value); pre_vector.assign(n, value); } - Size size() { return real_vector.size(); } + Size size() const { return real_vector.size(); } - Size capacity() { return pre_vector.capacity(); } + Size capacity() const { return pre_vector.capacity(); } void shrink_to_fit() { pre_vector.shrink_to_fit(); test(); } void swap() { real_vector.swap(real_vector_alt); pre_vector.swap(pre_vector_alt); test(); } void move() { real_vector = std::move(real_vector_alt); real_vector_alt.clear(); pre_vector = std::move(pre_vector_alt); pre_vector_alt.clear(); } void copy() { real_vector = real_vector_alt; pre_vector = pre_vector_alt; } ~prevector_tester() { BOOST_CHECK_MESSAGE(passed, "insecure_rand: " + rand_seed.ToString()); } prevector_tester() { SeedInsecureRand(); rand_seed = insecure_rand_seed; rand_cache = insecure_rand_ctx; } }; BOOST_AUTO_TEST_CASE(PrevectorTestInt) { for (int j = 0; j < 64; j++) { prevector_tester<8, int> test; for (int i = 0; i < 2048; i++) { if (InsecureRandBits(2) == 0) { test.insert(InsecureRandRange(test.size() + 1), insecure_rand()); } if (test.size() > 0 && InsecureRandBits(2) == 1) { test.erase(InsecureRandRange(test.size())); } if (InsecureRandBits(3) == 2) { int new_size = std::max( 0, std::min(30, test.size() + (InsecureRandRange(5)) - 2)); test.resize(new_size); } if (InsecureRandBits(3) == 3) { test.insert(InsecureRandRange(test.size() + 1), 1 + InsecureRandBool(), insecure_rand()); } if (InsecureRandBits(3) == 4) { int del = std::min(test.size(), 1 + (InsecureRandBool())); int beg = InsecureRandRange(test.size() + 1 - del); test.erase(beg, beg + del); } if (InsecureRandBits(4) == 5) { test.push_back(insecure_rand()); } if (test.size() > 0 && InsecureRandBits(4) == 6) { test.pop_back(); } if (InsecureRandBits(5) == 7) { int values[4]; int num = 1 + (InsecureRandBits(2)); for (int k = 0; k < num; k++) { values[k] = insecure_rand(); } test.insert_range(InsecureRandRange(test.size() + 1), values, values + num); } if (InsecureRandBits(5) == 8) { int del = std::min(test.size(), 1 + (InsecureRandBits(2))); int beg = InsecureRandRange(test.size() + 1 - del); test.erase(beg, beg + del); } if (InsecureRandBits(5) == 9) { test.reserve(InsecureRandBits(5)); } if (InsecureRandBits(6) == 10) { test.shrink_to_fit(); } if (test.size() > 0) { test.update(InsecureRandRange(test.size()), insecure_rand()); } if (InsecureRandBits(10) == 11) { test.clear(); } if (InsecureRandBits(9) == 12) { test.assign(InsecureRandBits(5), insecure_rand()); } if (InsecureRandBits(3) == 3) { test.swap(); } if (InsecureRandBits(4) == 8) { test.copy(); } if (InsecureRandBits(5) == 18) { test.move(); } } } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 01a6bf9a8..b19d53c2f 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -1,2069 +1,2069 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Copyright (c) 2017-2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "data/script_tests.json.h" #include "core_io.h" #include "key.h" #include "keystore.h" #include "rpc/server.h" #include "script/script.h" #include "script/script_error.h" #include "script/sighashtype.h" #include "script/sign.h" #include "test/jsonutil.h" #include "test/scriptflags.h" #include "test/sigutil.h" #include "test/test_bitcoin.h" #include "util.h" #include "utilstrencodings.h" #if defined(HAVE_CONSENSUS_LIB) #include "script/bitcoinconsensus.h" #endif #include #include #include #include #include #include // Uncomment if you want to output updated JSON tests. // #define UPDATE_JSON_TESTS static const uint32_t gFlags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC; struct ScriptErrorDesc { ScriptError_t err; const char *name; }; static ScriptErrorDesc script_errors[] = { {SCRIPT_ERR_OK, "OK"}, {SCRIPT_ERR_UNKNOWN_ERROR, "UNKNOWN_ERROR"}, {SCRIPT_ERR_EVAL_FALSE, "EVAL_FALSE"}, {SCRIPT_ERR_OP_RETURN, "OP_RETURN"}, {SCRIPT_ERR_SCRIPT_SIZE, "SCRIPT_SIZE"}, {SCRIPT_ERR_PUSH_SIZE, "PUSH_SIZE"}, {SCRIPT_ERR_OP_COUNT, "OP_COUNT"}, {SCRIPT_ERR_STACK_SIZE, "STACK_SIZE"}, {SCRIPT_ERR_SIG_COUNT, "SIG_COUNT"}, {SCRIPT_ERR_PUBKEY_COUNT, "PUBKEY_COUNT"}, {SCRIPT_ERR_INVALID_OPERAND_SIZE, "OPERAND_SIZE"}, {SCRIPT_ERR_INVALID_NUMBER_RANGE, "INVALID_NUMBER_RANGE"}, {SCRIPT_ERR_IMPOSSIBLE_ENCODING, "IMPOSSIBLE_ENCODING"}, {SCRIPT_ERR_INVALID_SPLIT_RANGE, "SPLIT_RANGE"}, {SCRIPT_ERR_VERIFY, "VERIFY"}, {SCRIPT_ERR_EQUALVERIFY, "EQUALVERIFY"}, {SCRIPT_ERR_CHECKMULTISIGVERIFY, "CHECKMULTISIGVERIFY"}, {SCRIPT_ERR_CHECKSIGVERIFY, "CHECKSIGVERIFY"}, {SCRIPT_ERR_CHECKDATASIGVERIFY, "CHECKDATASIGVERIFY"}, {SCRIPT_ERR_NUMEQUALVERIFY, "NUMEQUALVERIFY"}, {SCRIPT_ERR_BAD_OPCODE, "BAD_OPCODE"}, {SCRIPT_ERR_DISABLED_OPCODE, "DISABLED_OPCODE"}, {SCRIPT_ERR_INVALID_STACK_OPERATION, "INVALID_STACK_OPERATION"}, {SCRIPT_ERR_INVALID_ALTSTACK_OPERATION, "INVALID_ALTSTACK_OPERATION"}, {SCRIPT_ERR_UNBALANCED_CONDITIONAL, "UNBALANCED_CONDITIONAL"}, {SCRIPT_ERR_NEGATIVE_LOCKTIME, "NEGATIVE_LOCKTIME"}, {SCRIPT_ERR_UNSATISFIED_LOCKTIME, "UNSATISFIED_LOCKTIME"}, {SCRIPT_ERR_SIG_HASHTYPE, "SIG_HASHTYPE"}, {SCRIPT_ERR_SIG_DER, "SIG_DER"}, {SCRIPT_ERR_MINIMALDATA, "MINIMALDATA"}, {SCRIPT_ERR_SIG_PUSHONLY, "SIG_PUSHONLY"}, {SCRIPT_ERR_SIG_HIGH_S, "SIG_HIGH_S"}, {SCRIPT_ERR_SIG_NULLDUMMY, "SIG_NULLDUMMY"}, {SCRIPT_ERR_PUBKEYTYPE, "PUBKEYTYPE"}, {SCRIPT_ERR_CLEANSTACK, "CLEANSTACK"}, {SCRIPT_ERR_MINIMALIF, "MINIMALIF"}, {SCRIPT_ERR_SIG_NULLFAIL, "NULLFAIL"}, {SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, "DISCOURAGE_UPGRADABLE_NOPS"}, {SCRIPT_ERR_NONCOMPRESSED_PUBKEY, "NONCOMPRESSED_PUBKEY"}, {SCRIPT_ERR_ILLEGAL_FORKID, "ILLEGAL_FORKID"}, {SCRIPT_ERR_MUST_USE_FORKID, "MISSING_FORKID"}, {SCRIPT_ERR_DIV_BY_ZERO, "DIV_BY_ZERO"}, {SCRIPT_ERR_MOD_BY_ZERO, "MOD_BY_ZERO"}, }; const char *FormatScriptError(ScriptError_t err) { for (size_t i = 0; i < ARRAYLEN(script_errors); ++i) { if (script_errors[i].err == err) { return script_errors[i].name; } } BOOST_ERROR("Unknown scripterror enumeration value, update script_errors " "in script_tests.cpp."); return ""; } ScriptError_t ParseScriptError(const std::string &name) { for (size_t i = 0; i < ARRAYLEN(script_errors); ++i) { if (script_errors[i].name == name) { return script_errors[i].err; } } BOOST_ERROR("Unknown scripterror \"" << name << "\" in test description"); return SCRIPT_ERR_UNKNOWN_ERROR; } BOOST_FIXTURE_TEST_SUITE(script_tests, BasicTestingSetup) static CMutableTransaction BuildCreditingTransaction(const CScript &scriptPubKey, const Amount nValue) { CMutableTransaction txCredit; txCredit.nVersion = 1; txCredit.nLockTime = 0; txCredit.vin.resize(1); txCredit.vout.resize(1); txCredit.vin[0].prevout = COutPoint(); txCredit.vin[0].scriptSig = CScript() << CScriptNum(0) << CScriptNum(0); txCredit.vin[0].nSequence = CTxIn::SEQUENCE_FINAL; txCredit.vout[0].scriptPubKey = scriptPubKey; txCredit.vout[0].nValue = nValue; return txCredit; } static CMutableTransaction BuildSpendingTransaction(const CScript &scriptSig, const CMutableTransaction &txCredit) { CMutableTransaction txSpend; txSpend.nVersion = 1; txSpend.nLockTime = 0; txSpend.vin.resize(1); txSpend.vout.resize(1); txSpend.vin[0].prevout = COutPoint(txCredit.GetId(), 0); txSpend.vin[0].scriptSig = scriptSig; txSpend.vin[0].nSequence = CTxIn::SEQUENCE_FINAL; txSpend.vout[0].scriptPubKey = CScript(); txSpend.vout[0].nValue = txCredit.vout[0].nValue; return txSpend; } static void DoTest(const CScript &scriptPubKey, const CScript &scriptSig, uint32_t flags, const std::string &message, int scriptError, const Amount nValue) { bool expect = (scriptError == SCRIPT_ERR_OK); if (flags & SCRIPT_VERIFY_CLEANSTACK) { flags |= SCRIPT_VERIFY_P2SH; } ScriptError err; CMutableTransaction txCredit = BuildCreditingTransaction(scriptPubKey, nValue); CMutableTransaction tx = BuildSpendingTransaction(scriptSig, txCredit); CMutableTransaction tx2 = tx; BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, flags, MutableTransactionSignatureChecker( &tx, 0, txCredit.vout[0].nValue), &err) == expect, message); BOOST_CHECK_MESSAGE( err == scriptError, std::string(FormatScriptError(err)) + " where " + std::string(FormatScriptError((ScriptError_t)scriptError)) + " expected: " + message); #if defined(HAVE_CONSENSUS_LIB) CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << tx2; uint32_t libconsensus_flags = flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL; if (libconsensus_flags == flags) { if (flags & bitcoinconsensus_SCRIPT_ENABLE_SIGHASH_FORKID) { BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount( scriptPubKey.data(), scriptPubKey.size(), txCredit.vout[0].nValue / SATOSHI, (const uint8_t *)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect, message); } else { BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount( scriptPubKey.data(), scriptPubKey.size(), 0, (const uint8_t *)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect, message); BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script( scriptPubKey.data(), scriptPubKey.size(), (const uint8_t *)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect, message); } } #endif } namespace { const uint8_t vchKey0[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; const uint8_t vchKey1[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}; const uint8_t vchKey2[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; struct KeyData { CKey key0, key0C, key1, key1C, key2, key2C; CPubKey pubkey0, pubkey0C, pubkey0H; CPubKey pubkey1, pubkey1C; CPubKey pubkey2, pubkey2C; KeyData() { key0.Set(vchKey0, vchKey0 + 32, false); key0C.Set(vchKey0, vchKey0 + 32, true); pubkey0 = key0.GetPubKey(); pubkey0H = key0.GetPubKey(); pubkey0C = key0C.GetPubKey(); *const_cast(&pubkey0H[0]) = 0x06 | (pubkey0H[64] & 1); key1.Set(vchKey1, vchKey1 + 32, false); key1C.Set(vchKey1, vchKey1 + 32, true); pubkey1 = key1.GetPubKey(); pubkey1C = key1C.GetPubKey(); key2.Set(vchKey2, vchKey2 + 32, false); key2C.Set(vchKey2, vchKey2 + 32, true); pubkey2 = key2.GetPubKey(); pubkey2C = key2C.GetPubKey(); } }; class TestBuilder { private: //! Actually executed script CScript script; //! The P2SH redeemscript CScript redeemscript; CTransactionRef creditTx; CMutableTransaction spendTx; bool havePush; std::vector push; std::string comment; uint32_t flags; int scriptError; Amount nValue; void DoPush() { if (havePush) { spendTx.vin[0].scriptSig << push; havePush = false; } } void DoPush(const std::vector &data) { DoPush(); push = data; havePush = true; } std::vector DoSign(const CKey &key, const uint256 &hash, unsigned int lenR = 32, unsigned int lenS = 32) const { std::vector vchSig, r, s; uint32_t iter = 0; do { key.Sign(hash, vchSig, iter++); if ((lenS == 33) != (vchSig[5 + vchSig[3]] == 33)) { NegateSignatureS(vchSig); } r = std::vector(vchSig.begin() + 4, vchSig.begin() + 4 + vchSig[3]); s = std::vector(vchSig.begin() + 6 + vchSig[3], vchSig.begin() + 6 + vchSig[3] + vchSig[5 + vchSig[3]]); } while (lenR != r.size() || lenS != s.size()); return vchSig; } public: TestBuilder(const CScript &script_, const std::string &comment_, uint32_t flags_, bool P2SH = false, Amount nValue_ = Amount::zero()) : script(script_), havePush(false), comment(comment_), flags(flags_), scriptError(SCRIPT_ERR_OK), nValue(nValue_) { CScript scriptPubKey = script; if (P2SH) { redeemscript = scriptPubKey; scriptPubKey = CScript() << OP_HASH160 << ToByteVector(CScriptID(redeemscript)) << OP_EQUAL; } creditTx = MakeTransactionRef(BuildCreditingTransaction(scriptPubKey, nValue)); spendTx = BuildSpendingTransaction(CScript(), *creditTx); } TestBuilder &ScriptError(ScriptError_t err) { scriptError = err; return *this; } TestBuilder &Add(const CScript &_script) { DoPush(); spendTx.vin[0].scriptSig += _script; return *this; } TestBuilder &Num(int num) { DoPush(); spendTx.vin[0].scriptSig << num; return *this; } TestBuilder &Push(const std::string &hex) { DoPush(ParseHex(hex)); return *this; } TestBuilder &Push(const uint256 &hash) { DoPush(ToByteVector(hash)); return *this; } TestBuilder &Push(const CScript &_script) { DoPush(std::vector(_script.begin(), _script.end())); return *this; } TestBuilder &PushSig(const CKey &key, SigHashType sigHashType = SigHashType(), unsigned int lenR = 32, unsigned int lenS = 32, Amount amount = Amount::zero(), uint32_t sigFlags = SCRIPT_ENABLE_SIGHASH_FORKID) { uint256 hash = SignatureHash(script, CTransaction(spendTx), 0, sigHashType, amount, nullptr, sigFlags); std::vector vchSig = DoSign(key, hash, lenR, lenS); vchSig.push_back(static_cast(sigHashType.getRawSigHashType())); DoPush(vchSig); return *this; } TestBuilder &PushDataSig(const CKey &key, const std::vector &data, unsigned int lenR = 32, unsigned int lenS = 32) { std::vector vchHash(32); CSHA256().Write(data.data(), data.size()).Finalize(vchHash.data()); DoPush(DoSign(key, uint256(vchHash), lenR, lenS)); return *this; } TestBuilder &Push(const CPubKey &pubkey) { DoPush(std::vector(pubkey.begin(), pubkey.end())); return *this; } TestBuilder &PushRedeem() { DoPush(std::vector(redeemscript.begin(), redeemscript.end())); return *this; } TestBuilder &EditPush(unsigned int pos, const std::string &hexin, const std::string &hexout) { assert(havePush); std::vector datain = ParseHex(hexin); std::vector dataout = ParseHex(hexout); assert(pos + datain.size() <= push.size()); BOOST_CHECK_MESSAGE( std::vector(push.begin() + pos, push.begin() + pos + datain.size()) == datain, comment); push.erase(push.begin() + pos, push.begin() + pos + datain.size()); push.insert(push.begin() + pos, dataout.begin(), dataout.end()); return *this; } TestBuilder &DamagePush(unsigned int pos) { assert(havePush); assert(pos < push.size()); push[pos] ^= 1; return *this; } TestBuilder &Test() { // Make a copy so we can rollback the push. TestBuilder copy = *this; DoPush(); DoTest(creditTx->vout[0].scriptPubKey, spendTx.vin[0].scriptSig, flags, comment, scriptError, nValue); *this = copy; return *this; } UniValue GetJSON() { DoPush(); UniValue array(UniValue::VARR); if (nValue != Amount::zero()) { UniValue amount(UniValue::VARR); amount.push_back(ValueFromAmount(nValue)); array.push_back(amount); } array.push_back(FormatScript(spendTx.vin[0].scriptSig)); array.push_back(FormatScript(creditTx->vout[0].scriptPubKey)); array.push_back(FormatScriptFlags(flags)); array.push_back(FormatScriptError((ScriptError_t)scriptError)); array.push_back(comment); return array; } - std::string GetComment() { return comment; } + std::string GetComment() const { return comment; } }; std::string JSONPrettyPrint(const UniValue &univalue) { std::string ret = univalue.write(4); // Workaround for libunivalue pretty printer, which puts a space between // commas and newlines size_t pos = 0; while ((pos = ret.find(" \n", pos)) != std::string::npos) { ret.replace(pos, 2, "\n"); pos++; } return ret; } } // namespace BOOST_AUTO_TEST_CASE(script_build) { const KeyData keys; std::vector tests; tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK", 0) .PushSig(keys.key0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK, bad sig", 0) .PushSig(keys.key0) .DamagePush(10) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << OP_DUP << OP_HASH160 << ToByteVector(keys.pubkey1C.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG, "P2PKH", 0) .PushSig(keys.key1) .Push(keys.pubkey1C)); tests.push_back(TestBuilder(CScript() << OP_DUP << OP_HASH160 << ToByteVector(keys.pubkey2C.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG, "P2PKH, bad pubkey", 0) .PushSig(keys.key2) .Push(keys.pubkey2C) .DamagePush(5) .ScriptError(SCRIPT_ERR_EQUALVERIFY)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG, "P2PK anyonecanpay", 0) .PushSig(keys.key1, SigHashType().withAnyoneCanPay())); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG, "P2PK anyonecanpay marked with normal hashtype", 0) .PushSig(keys.key1, SigHashType().withAnyoneCanPay()) .EditPush(70, "81", "01") .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0C) << OP_CHECKSIG, "P2SH(P2PK)", SCRIPT_VERIFY_P2SH, true) .PushSig(keys.key0) .PushRedeem()); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0C) << OP_CHECKSIG, "P2SH(P2PK), bad redeemscript", SCRIPT_VERIFY_P2SH, true) .PushSig(keys.key0) .PushRedeem() .DamagePush(10) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << OP_DUP << OP_HASH160 << ToByteVector(keys.pubkey0.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG, "P2SH(P2PKH)", SCRIPT_VERIFY_P2SH, true) .PushSig(keys.key0) .Push(keys.pubkey0) .PushRedeem()); tests.push_back(TestBuilder(CScript() << OP_DUP << OP_HASH160 << ToByteVector(keys.pubkey1.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG, "P2SH(P2PKH), bad sig but no VERIFY_P2SH", 0, true) .PushSig(keys.key0) .DamagePush(10) .PushRedeem()); tests.push_back(TestBuilder(CScript() << OP_DUP << OP_HASH160 << ToByteVector(keys.pubkey1.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG, "P2SH(P2PKH), bad sig", SCRIPT_VERIFY_P2SH, true) .PushSig(keys.key0) .DamagePush(10) .PushRedeem() .ScriptError(SCRIPT_ERR_EQUALVERIFY)); tests.push_back(TestBuilder(CScript() << OP_3 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG, "3-of-3", 0) .Num(0) .PushSig(keys.key0) .PushSig(keys.key1) .PushSig(keys.key2)); tests.push_back(TestBuilder(CScript() << OP_3 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG, "3-of-3, 2 sigs", 0) .Num(0) .PushSig(keys.key0) .PushSig(keys.key1) .Num(0) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG, "P2SH(2-of-3)", SCRIPT_VERIFY_P2SH, true) .Num(0) .PushSig(keys.key1) .PushSig(keys.key2) .PushRedeem()); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG, "P2SH(2-of-3), 1 sig", SCRIPT_VERIFY_P2SH, true) .Num(0) .PushSig(keys.key1) .Num(0) .PushRedeem() .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "P2PK with too much R padding but no DERSIG", 0) .PushSig(keys.key1, SigHashType(), 31, 32) .EditPush(1, "43021F", "44022000")); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "P2PK with too much R padding", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key1, SigHashType(), 31, 32) .EditPush(1, "43021F", "44022000") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "P2PK with too much S padding but no DERSIG", 0) .PushSig(keys.key1) .EditPush(1, "44", "45") .EditPush(37, "20", "2100")); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "P2PK with too much S padding", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key1) .EditPush(1, "44", "45") .EditPush(37, "20", "2100") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "P2PK with too little R padding but no DERSIG", 0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220")); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "P2PK with too little R padding", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder( CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG << OP_NOT, "P2PK NOT with bad sig with too much R padding but no DERSIG", 0) .PushSig(keys.key2, SigHashType(), 31, 32) .EditPush(1, "43021F", "44022000") .DamagePush(10)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG << OP_NOT, "P2PK NOT with bad sig with too much R padding", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key2, SigHashType(), 31, 32) .EditPush(1, "43021F", "44022000") .DamagePush(10) .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG << OP_NOT, "P2PK NOT with too much R padding but no DERSIG", 0) .PushSig(keys.key2, SigHashType(), 31, 32) .EditPush(1, "43021F", "44022000") .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG << OP_NOT, "P2PK NOT with too much R padding", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key2, SigHashType(), 31, 32) .EditPush(1, "43021F", "44022000") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "BIP66 example 1, without DERSIG", 0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220")); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "BIP66 example 1, with DERSIG", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 2, without DERSIG", 0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 2, with DERSIG", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "BIP66 example 3, without DERSIG", 0) .Num(0) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "BIP66 example 3, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 4, without DERSIG", 0) .Num(0)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 4, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0)); tests.push_back( TestBuilder( CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 4, with DERSIG, non-null DER-compliant signature", SCRIPT_VERIFY_DERSIG) .Push("300602010102010101")); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 4, with DERSIG and NULLFAIL", SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_NULLFAIL) .Num(0)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 4, with DERSIG and NULLFAIL, " "non-null DER-compliant signature", SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_NULLFAIL) .Push("300602010102010101") .ScriptError(SCRIPT_ERR_SIG_NULLFAIL)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "BIP66 example 5, without DERSIG", 0) .Num(1) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG, "BIP66 example 5, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(1) .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 6, without DERSIG", 0) .Num(1)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKSIG << OP_NOT, "BIP66 example 6, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(1) .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG, "BIP66 example 7, without DERSIG", 0) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .PushSig(keys.key2)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG, "BIP66 example 7, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .PushSig(keys.key2) .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG << OP_NOT, "BIP66 example 8, without DERSIG", 0) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .PushSig(keys.key2) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG << OP_NOT, "BIP66 example 8, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .PushSig(keys.key2) .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG, "BIP66 example 9, without DERSIG", 0) .Num(0) .Num(0) .PushSig(keys.key2, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG, "BIP66 example 9, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0) .Num(0) .PushSig(keys.key2, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG << OP_NOT, "BIP66 example 10, without DERSIG", 0) .Num(0) .Num(0) .PushSig(keys.key2, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220")); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG << OP_NOT, "BIP66 example 10, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0) .Num(0) .PushSig(keys.key2, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG, "BIP66 example 11, without DERSIG", 0) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .Num(0) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG, "BIP66 example 11, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .Num(0) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG << OP_NOT, "BIP66 example 12, without DERSIG", 0) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .Num(0)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_2 << OP_CHECKMULTISIG << OP_NOT, "BIP66 example 12, with DERSIG", SCRIPT_VERIFY_DERSIG) .Num(0) .PushSig(keys.key1, SigHashType(), 33, 32) .EditPush(1, "45022100", "440220") .Num(0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2PK with multi-byte hashtype, without DERSIG", 0) .PushSig(keys.key2) .EditPush(70, "01", "0101")); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2PK with multi-byte hashtype, with DERSIG", SCRIPT_VERIFY_DERSIG) .PushSig(keys.key2) .EditPush(70, "01", "0101") .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2PK with high S but no LOW_S", 0) .PushSig(keys.key2, SigHashType(), 32, 33)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2PK with high S", SCRIPT_VERIFY_LOW_S) .PushSig(keys.key2, SigHashType(), 32, 33) .ScriptError(SCRIPT_ERR_SIG_HIGH_S)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKSIG, "P2PK with hybrid pubkey but no STRICTENC", 0) .PushSig(keys.key0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKSIG, "P2PK with hybrid pubkey", SCRIPT_VERIFY_STRICTENC) .PushSig(keys.key0, SigHashType()) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKSIG << OP_NOT, "P2PK NOT with hybrid pubkey but no STRICTENC", 0) .PushSig(keys.key0) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKSIG << OP_NOT, "P2PK NOT with hybrid pubkey", SCRIPT_VERIFY_STRICTENC) .PushSig(keys.key0) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKSIG << OP_NOT, "P2PK NOT with invalid hybrid pubkey but no STRICTENC", 0) .PushSig(keys.key0) .DamagePush(10)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKSIG << OP_NOT, "P2PK NOT with invalid hybrid pubkey", SCRIPT_VERIFY_STRICTENC) .PushSig(keys.key0) .DamagePush(10) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); tests.push_back( TestBuilder(CScript() << OP_1 << ToByteVector(keys.pubkey0H) << ToByteVector(keys.pubkey1C) << OP_2 << OP_CHECKMULTISIG, "1-of-2 with the second 1 hybrid pubkey and no STRICTENC", 0) .Num(0) .PushSig(keys.key1)); tests.push_back(TestBuilder(CScript() << OP_1 << ToByteVector(keys.pubkey0H) << ToByteVector(keys.pubkey1C) << OP_2 << OP_CHECKMULTISIG, "1-of-2 with the second 1 hybrid pubkey", SCRIPT_VERIFY_STRICTENC) .Num(0) .PushSig(keys.key1)); tests.push_back(TestBuilder(CScript() << OP_1 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey0H) << OP_2 << OP_CHECKMULTISIG, "1-of-2 with the first 1 hybrid pubkey", SCRIPT_VERIFY_STRICTENC) .Num(0) .PushSig(keys.key1) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG, "P2PK with undefined hashtype but no STRICTENC", 0) .PushSig(keys.key1, SigHashType(5))); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG, "P2PK with undefined hashtype", SCRIPT_VERIFY_STRICTENC) .PushSig(keys.key1, SigHashType(5)) .ScriptError(SCRIPT_ERR_SIG_HASHTYPE)); // Generate P2PKH tests for invalid SigHashType tests.push_back( TestBuilder(CScript() << OP_DUP << OP_HASH160 << ToByteVector(keys.pubkey0.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG, "P2PKH with invalid sighashtype", 0) .PushSig(keys.key0, SigHashType(0x21), 32, 32, Amount::zero(), 0) .Push(keys.pubkey0)); tests.push_back(TestBuilder(CScript() << OP_DUP << OP_HASH160 << ToByteVector(keys.pubkey0.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG, "P2PKH with invalid sighashtype and STRICTENC", SCRIPT_VERIFY_STRICTENC) .PushSig(keys.key0, SigHashType(0x21), 32, 32, Amount::zero(), SCRIPT_VERIFY_STRICTENC) .Push(keys.pubkey0) // Should fail for STRICTENC .ScriptError(SCRIPT_ERR_SIG_HASHTYPE)); // Generate P2SH tests for invalid SigHashType tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG, "P2SH(P2PK) with invalid sighashtype", SCRIPT_VERIFY_P2SH, true) .PushSig(keys.key1, SigHashType(0x21)) .PushRedeem()); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG, "P2SH(P2PK) with invalid sighashtype and STRICTENC", SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, true) .PushSig(keys.key1, SigHashType(0x21)) .PushRedeem() // Should fail for STRICTENC .ScriptError(SCRIPT_ERR_SIG_HASHTYPE)); tests.push_back( TestBuilder( CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG << OP_NOT, "P2PK NOT with invalid sig and undefined hashtype but no STRICTENC", 0) .PushSig(keys.key1, SigHashType(5)) .DamagePush(10)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1) << OP_CHECKSIG << OP_NOT, "P2PK NOT with invalid sig and undefined hashtype", SCRIPT_VERIFY_STRICTENC) .PushSig(keys.key1, SigHashType(5)) .DamagePush(10) .ScriptError(SCRIPT_ERR_SIG_HASHTYPE)); tests.push_back(TestBuilder(CScript() << OP_3 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG, "3-of-3 with nonzero dummy but no NULLDUMMY", 0) .Num(1) .PushSig(keys.key0) .PushSig(keys.key1) .PushSig(keys.key2)); tests.push_back(TestBuilder(CScript() << OP_3 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG, "3-of-3 with nonzero dummy", SCRIPT_VERIFY_NULLDUMMY) .Num(1) .PushSig(keys.key0) .PushSig(keys.key1) .PushSig(keys.key2) .ScriptError(SCRIPT_ERR_SIG_NULLDUMMY)); tests.push_back( TestBuilder( CScript() << OP_3 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG << OP_NOT, "3-of-3 NOT with invalid sig and nonzero dummy but no NULLDUMMY", 0) .Num(1) .PushSig(keys.key0) .PushSig(keys.key1) .PushSig(keys.key2) .DamagePush(10)); tests.push_back( TestBuilder(CScript() << OP_3 << ToByteVector(keys.pubkey0C) << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey2C) << OP_3 << OP_CHECKMULTISIG << OP_NOT, "3-of-3 NOT with invalid sig with nonzero dummy", SCRIPT_VERIFY_NULLDUMMY) .Num(1) .PushSig(keys.key0) .PushSig(keys.key1) .PushSig(keys.key2) .DamagePush(10) .ScriptError(SCRIPT_ERR_SIG_NULLDUMMY)); tests.push_back(TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey1C) << OP_2 << OP_CHECKMULTISIG, "2-of-2 with two identical keys and sigs " "pushed using OP_DUP but no SIGPUSHONLY", 0) .Num(0) .PushSig(keys.key1) .Add(CScript() << OP_DUP)); tests.push_back( TestBuilder( CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey1C) << OP_2 << OP_CHECKMULTISIG, "2-of-2 with two identical keys and sigs pushed using OP_DUP", SCRIPT_VERIFY_SIGPUSHONLY) .Num(0) .PushSig(keys.key1) .Add(CScript() << OP_DUP) .ScriptError(SCRIPT_ERR_SIG_PUSHONLY)); tests.push_back( TestBuilder( CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2SH(P2PK) with non-push scriptSig but no P2SH or SIGPUSHONLY", 0, true) .PushSig(keys.key2) .Add(CScript() << OP_NOP8) .PushRedeem()); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2PK with non-push scriptSig but with P2SH validation", 0) .PushSig(keys.key2) .Add(CScript() << OP_NOP8)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2SH(P2PK) with non-push scriptSig but no SIGPUSHONLY", SCRIPT_VERIFY_P2SH, true) .PushSig(keys.key2) .Add(CScript() << OP_NOP8) .PushRedeem() .ScriptError(SCRIPT_ERR_SIG_PUSHONLY)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey2C) << OP_CHECKSIG, "P2SH(P2PK) with non-push scriptSig but not P2SH", SCRIPT_VERIFY_SIGPUSHONLY, true) .PushSig(keys.key2) .Add(CScript() << OP_NOP8) .PushRedeem() .ScriptError(SCRIPT_ERR_SIG_PUSHONLY)); tests.push_back( TestBuilder(CScript() << OP_2 << ToByteVector(keys.pubkey1C) << ToByteVector(keys.pubkey1C) << OP_2 << OP_CHECKMULTISIG, "2-of-2 with two identical keys and sigs pushed", SCRIPT_VERIFY_SIGPUSHONLY) .Num(0) .PushSig(keys.key1) .PushSig(keys.key1)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK with unnecessary input but no CLEANSTACK", SCRIPT_VERIFY_P2SH) .Num(11) .PushSig(keys.key0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK with unnecessary input", SCRIPT_VERIFY_CLEANSTACK | SCRIPT_VERIFY_P2SH) .Num(11) .PushSig(keys.key0) .ScriptError(SCRIPT_ERR_CLEANSTACK)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2SH with unnecessary input but no CLEANSTACK", SCRIPT_VERIFY_P2SH, true) .Num(11) .PushSig(keys.key0) .PushRedeem()); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2SH with unnecessary input", SCRIPT_VERIFY_CLEANSTACK | SCRIPT_VERIFY_P2SH, true) .Num(11) .PushSig(keys.key0) .PushRedeem() .ScriptError(SCRIPT_ERR_CLEANSTACK)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2SH with CLEANSTACK", SCRIPT_VERIFY_CLEANSTACK | SCRIPT_VERIFY_P2SH, true) .PushSig(keys.key0) .PushRedeem()); static const Amount TEST_AMOUNT(int64_t(12345000000000) * SATOSHI); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK FORKID", SCRIPT_ENABLE_SIGHASH_FORKID, false, TEST_AMOUNT) .PushSig(keys.key0, SigHashType().withForkId(), 32, 32, TEST_AMOUNT)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK INVALID AMOUNT", SCRIPT_ENABLE_SIGHASH_FORKID, false, TEST_AMOUNT) .PushSig(keys.key0, SigHashType().withForkId(), 32, 32, TEST_AMOUNT + SATOSHI) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK INVALID FORKID", SCRIPT_VERIFY_STRICTENC, false, TEST_AMOUNT) .PushSig(keys.key0, SigHashType().withForkId(), 32, 32, TEST_AMOUNT) .ScriptError(SCRIPT_ERR_ILLEGAL_FORKID)); // Test replay protection tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK REPLAY PROTECTED", SCRIPT_ENABLE_SIGHASH_FORKID | SCRIPT_ENABLE_REPLAY_PROTECTION, false, TEST_AMOUNT) .PushSig(keys.key0, SigHashType().withForkId(), 32, 32, TEST_AMOUNT, SCRIPT_ENABLE_SIGHASH_FORKID | SCRIPT_ENABLE_REPLAY_PROTECTION)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0) << OP_CHECKSIG, "P2PK REPLAY PROTECTED", SCRIPT_ENABLE_SIGHASH_FORKID | SCRIPT_ENABLE_REPLAY_PROTECTION, false, TEST_AMOUNT) .PushSig(keys.key0, SigHashType().withForkId(), 32, 32, TEST_AMOUNT, SCRIPT_ENABLE_SIGHASH_FORKID) .ScriptError(SCRIPT_ERR_EVAL_FALSE)); // Test OP_CHECKDATASIG const uint32_t checkdatasigflags = SCRIPT_VERIFY_STRICTENC | SCRIPT_VERIFY_NULLFAIL | SCRIPT_ENABLE_CHECKDATASIG; tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG, "Standard CHECKDATASIG", checkdatasigflags) .PushDataSig(keys.key1, {}) .Num(0)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG << OP_NOT, "CHECKDATASIG with NULLFAIL flags", checkdatasigflags) .PushDataSig(keys.key1, {}) .Num(1) .ScriptError(SCRIPT_ERR_SIG_NULLFAIL)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG << OP_NOT, "CHECKDATASIG without NULLFAIL flags", checkdatasigflags & ~SCRIPT_VERIFY_NULLFAIL) .PushDataSig(keys.key1, {}) .Num(1)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG << OP_NOT, "CHECKDATASIG empty signature", checkdatasigflags) .Num(0) .Num(0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG, "CHECKDATASIG with High S but no Low S", checkdatasigflags) .PushDataSig(keys.key1, {}, 32, 33) .Num(0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG, "CHECKDATASIG with High S", checkdatasigflags | SCRIPT_VERIFY_LOW_S) .PushDataSig(keys.key1, {}, 32, 33) .Num(0) .ScriptError(SCRIPT_ERR_SIG_HIGH_S)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG, "CHECKDATASIG with too little R padding but no DERSIG", checkdatasigflags & ~SCRIPT_VERIFY_STRICTENC) .PushDataSig(keys.key1, {}, 33, 32) .EditPush(1, "45022100", "440220") .Num(0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIG, "CHECKDATASIG with too little R padding", checkdatasigflags) .PushDataSig(keys.key1, {}, 33, 32) .EditPush(1, "45022100", "440220") .Num(0) .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIG, "CHECKDATASIG with hybrid pubkey but no STRICTENC", checkdatasigflags & ~SCRIPT_VERIFY_STRICTENC) .PushDataSig(keys.key0, {}) .Num(0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIG, "CHECKDATASIG with hybrid pubkey", checkdatasigflags) .PushDataSig(keys.key0, {}) .Num(0) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIG << OP_NOT, "CHECKDATASIG with invalid hybrid pubkey but no STRICTENC", SCRIPT_ENABLE_CHECKDATASIG) .PushDataSig(keys.key0, {}) .DamagePush(10) .Num(0)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIG, "CHECKDATASIG with invalid hybrid pubkey", checkdatasigflags) .PushDataSig(keys.key0, {}) .DamagePush(10) .Num(0) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); // Test OP_CHECKDATASIGVERIFY tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "Standard CHECKDATASIGVERIFY", checkdatasigflags) .PushDataSig(keys.key1, {}) .Num(0)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY with NULLFAIL flags", checkdatasigflags) .PushDataSig(keys.key1, {}) .Num(1) .ScriptError(SCRIPT_ERR_SIG_NULLFAIL)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY without NULLFAIL flags", checkdatasigflags & ~SCRIPT_VERIFY_NULLFAIL) .PushDataSig(keys.key1, {}) .Num(1) .ScriptError(SCRIPT_ERR_CHECKDATASIGVERIFY)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY empty signature", checkdatasigflags) .Num(0) .Num(0) .ScriptError(SCRIPT_ERR_CHECKDATASIGVERIFY)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIG with High S but no Low S", checkdatasigflags) .PushDataSig(keys.key1, {}, 32, 33) .Num(0)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIG with High S", checkdatasigflags | SCRIPT_VERIFY_LOW_S) .PushDataSig(keys.key1, {}, 32, 33) .Num(0) .ScriptError(SCRIPT_ERR_SIG_HIGH_S)); tests.push_back( TestBuilder( CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY with too little R padding but no DERSIG", checkdatasigflags & ~SCRIPT_VERIFY_STRICTENC) .PushDataSig(keys.key1, {}, 33, 32) .EditPush(1, "45022100", "440220") .Num(0)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey1C) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY with too little R padding", checkdatasigflags) .PushDataSig(keys.key1, {}, 33, 32) .EditPush(1, "45022100", "440220") .Num(0) .ScriptError(SCRIPT_ERR_SIG_DER)); tests.push_back( TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY with hybrid pubkey but no STRICTENC", checkdatasigflags & ~SCRIPT_VERIFY_STRICTENC) .PushDataSig(keys.key0, {}) .Num(0)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY with hybrid pubkey", checkdatasigflags) .PushDataSig(keys.key0, {}) .Num(0) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); tests.push_back( TestBuilder( CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY with invalid hybrid pubkey but no STRICTENC", SCRIPT_ENABLE_CHECKDATASIG) .PushDataSig(keys.key0, {}) .DamagePush(10) .Num(0) .ScriptError(SCRIPT_ERR_CHECKDATASIGVERIFY)); tests.push_back(TestBuilder(CScript() << ToByteVector(keys.pubkey0H) << OP_CHECKDATASIGVERIFY << OP_TRUE, "CHECKDATASIGVERIFY with invalid hybrid pubkey", checkdatasigflags) .PushDataSig(keys.key0, {}) .DamagePush(10) .Num(0) .ScriptError(SCRIPT_ERR_PUBKEYTYPE)); std::set tests_set; { UniValue json_tests = read_json(std::string( json_tests::script_tests, json_tests::script_tests + sizeof(json_tests::script_tests))); for (unsigned int idx = 0; idx < json_tests.size(); idx++) { const UniValue &tv = json_tests[idx]; tests_set.insert(JSONPrettyPrint(tv.get_array())); } } std::string strGen; for (TestBuilder &test : tests) { test.Test(); std::string str = JSONPrettyPrint(test.GetJSON()); #ifndef UPDATE_JSON_TESTS if (tests_set.count(str) == 0) { BOOST_CHECK_MESSAGE( false, "Missing auto script_valid test: " + test.GetComment()); } #endif strGen += str + ",\n"; } #ifdef UPDATE_JSON_TESTS FILE *file = fopen("script_tests.json.gen", "w"); fputs(strGen.c_str(), file); fclose(file); #endif } BOOST_AUTO_TEST_CASE(script_json_test) { // Read tests from test/data/script_tests.json // Format is an array of arrays // Inner arrays are [ ["wit"..., nValue]?, "scriptSig", "scriptPubKey", // "flags", "expected_scripterror" ] // ... where scriptSig and scriptPubKey are stringified // scripts. UniValue tests = read_json(std::string( json_tests::script_tests, json_tests::script_tests + sizeof(json_tests::script_tests))); for (unsigned int idx = 0; idx < tests.size(); idx++) { UniValue test = tests[idx]; std::string strTest = test.write(); Amount nValue = Amount::zero(); unsigned int pos = 0; if (test.size() > 0 && test[pos].isArray()) { nValue = AmountFromValue(test[pos][0]); pos++; } // Allow size > 3; extra stuff ignored (useful for comments) if (test.size() < 4 + pos) { if (test.size() != 1) { BOOST_ERROR("Bad test: " << strTest); } continue; } std::string scriptSigString = test[pos++].get_str(); std::string scriptPubKeyString = test[pos++].get_str(); try { CScript scriptSig = ParseScript(scriptSigString); CScript scriptPubKey = ParseScript(scriptPubKeyString); unsigned int scriptflags = ParseScriptFlags(test[pos++].get_str()); int scriptError = ParseScriptError(test[pos++].get_str()); DoTest(scriptPubKey, scriptSig, scriptflags, strTest, scriptError, nValue); } catch (std::runtime_error &e) { BOOST_TEST_MESSAGE("Script test failed. scriptSig: " << scriptSigString << " scriptPubKey: " << scriptPubKeyString); BOOST_TEST_MESSAGE("Exception: " << e.what()); throw; } } } BOOST_AUTO_TEST_CASE(script_PushData) { // Check that PUSHDATA1, PUSHDATA2, and PUSHDATA4 create the same value on // the stack as the 1-75 opcodes do. static const uint8_t direct[] = {1, 0x5a}; static const uint8_t pushdata1[] = {OP_PUSHDATA1, 1, 0x5a}; static const uint8_t pushdata2[] = {OP_PUSHDATA2, 1, 0, 0x5a}; static const uint8_t pushdata4[] = {OP_PUSHDATA4, 1, 0, 0, 0, 0x5a}; ScriptError err; std::vector> directStack; BOOST_CHECK(EvalScript(directStack, CScript(&direct[0], &direct[sizeof(direct)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); std::vector> pushdata1Stack; BOOST_CHECK(EvalScript( pushdata1Stack, CScript(&pushdata1[0], &pushdata1[sizeof(pushdata1)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), &err)); BOOST_CHECK(pushdata1Stack == directStack); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); std::vector> pushdata2Stack; BOOST_CHECK(EvalScript( pushdata2Stack, CScript(&pushdata2[0], &pushdata2[sizeof(pushdata2)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), &err)); BOOST_CHECK(pushdata2Stack == directStack); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); std::vector> pushdata4Stack; BOOST_CHECK(EvalScript( pushdata4Stack, CScript(&pushdata4[0], &pushdata4[sizeof(pushdata4)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), &err)); BOOST_CHECK(pushdata4Stack == directStack); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } CScript sign_multisig(CScript scriptPubKey, std::vector keys, CTransaction transaction) { uint256 hash = SignatureHash(scriptPubKey, transaction, 0, SigHashType(), Amount::zero()); CScript result; // // NOTE: CHECKMULTISIG has an unfortunate bug; it requires one extra item on // the stack, before the signatures. Putting OP_0 on the stack is the // workaround; fixing the bug would mean splitting the block chain (old // clients would not accept new CHECKMULTISIG transactions, and vice-versa) // result << OP_0; for (const CKey &key : keys) { std::vector vchSig; BOOST_CHECK(key.Sign(hash, vchSig)); vchSig.push_back(uint8_t(SIGHASH_ALL)); result << vchSig; } return result; } CScript sign_multisig(CScript scriptPubKey, const CKey &key, CTransaction transaction) { std::vector keys; keys.push_back(key); return sign_multisig(scriptPubKey, keys, transaction); } BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12) { ScriptError err; CKey key1, key2, key3; key1.MakeNewKey(true); key2.MakeNewKey(false); key3.MakeNewKey(true); CScript scriptPubKey12; scriptPubKey12 << OP_1 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << OP_2 << OP_CHECKMULTISIG; CMutableTransaction txFrom12 = BuildCreditingTransaction(scriptPubKey12, Amount::zero()); CMutableTransaction txTo12 = BuildSpendingTransaction(CScript(), txFrom12); CScript goodsig1 = sign_multisig(scriptPubKey12, key1, CTransaction(txTo12)); BOOST_CHECK(VerifyScript( goodsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); txTo12.vout[0].nValue = 2 * SATOSHI; BOOST_CHECK(!VerifyScript( goodsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); CScript goodsig2 = sign_multisig(scriptPubKey12, key2, CTransaction(txTo12)); BOOST_CHECK(VerifyScript( goodsig2, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); CScript badsig1 = sign_multisig(scriptPubKey12, key3, CTransaction(txTo12)); BOOST_CHECK(!VerifyScript( badsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); } BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) { ScriptError err; CKey key1, key2, key3, key4; key1.MakeNewKey(true); key2.MakeNewKey(false); key3.MakeNewKey(true); key4.MakeNewKey(false); CScript scriptPubKey23; scriptPubKey23 << OP_2 << ToByteVector(key1.GetPubKey()) << ToByteVector(key2.GetPubKey()) << ToByteVector(key3.GetPubKey()) << OP_3 << OP_CHECKMULTISIG; CMutableTransaction txFrom23 = BuildCreditingTransaction(scriptPubKey23, Amount::zero()); CMutableTransaction mutableTxTo23 = BuildSpendingTransaction(CScript(), txFrom23); // after it has been set up, mutableTxTo23 does not change in this test, // so we can convert it to readonly transaction and use // TransactionSignatureChecker // instead of MutableTransactionSignatureChecker const CTransaction txTo23(mutableTxTo23); std::vector keys; keys.push_back(key1); keys.push_back(key2); CScript goodsig1 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(VerifyScript( goodsig1, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key1); keys.push_back(key3); CScript goodsig2 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(VerifyScript( goodsig2, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key3); CScript goodsig3 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(VerifyScript( goodsig3, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key2); // Can't re-use sig CScript badsig1 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript( badsig1, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key1); // sigs must be in correct order CScript badsig2 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript( badsig2, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key3); keys.push_back(key2); // sigs must be in correct order CScript badsig3 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript( badsig3, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key4); keys.push_back(key2); // sigs must match pubkeys CScript badsig4 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript( badsig4, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key1); keys.push_back(key4); // sigs must match pubkeys CScript badsig5 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript( badsig5, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); // Must have signatures CScript badsig6 = sign_multisig(scriptPubKey23, keys, txTo23); BOOST_CHECK(!VerifyScript( badsig6, scriptPubKey23, gFlags, TransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); } BOOST_AUTO_TEST_CASE(script_combineSigs) { // Test the CombineSignatures function Amount amount = Amount::zero(); CBasicKeyStore keystore; std::vector keys; std::vector pubkeys; for (int i = 0; i < 3; i++) { CKey key; key.MakeNewKey(i % 2 == 1); keys.push_back(key); pubkeys.push_back(key.GetPubKey()); keystore.AddKey(key); } CMutableTransaction txFrom = BuildCreditingTransaction( GetScriptForDestination(keys[0].GetPubKey().GetID()), Amount::zero()); CMutableTransaction txTo = BuildSpendingTransaction(CScript(), txFrom); CScript &scriptPubKey = txFrom.vout[0].scriptPubKey; CScript &scriptSig = txTo.vin[0].scriptSig; // Although it looks like CMutableTransaction is not modified after it’s // been set up (it is not passed as parameter to any non-const function), // it is actually modified when new value is assigned to scriptPubKey, // which points to mutableTxFrom.vout[0].scriptPubKey. Therefore we can // not use single instance of CTransaction in this test. // CTransaction creates a copy of CMutableTransaction and is not modified // when scriptPubKey is assigned to. SignatureData empty; SignatureData combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, empty); BOOST_CHECK(combined.scriptSig.empty()); // Single signature case: SignSignature(keystore, CTransaction(txFrom), txTo, 0, SigHashType()); // changes scriptSig combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); BOOST_CHECK(combined.scriptSig == scriptSig); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); BOOST_CHECK(combined.scriptSig == scriptSig); CScript scriptSigCopy = scriptSig; // Signing again will give a different, valid signature: SignSignature(keystore, CTransaction(txFrom), txTo, 0, SigHashType()); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); BOOST_CHECK(combined.scriptSig == scriptSigCopy || combined.scriptSig == scriptSig); // P2SH, single-signature case: CScript pkSingle; pkSingle << ToByteVector(keys[0].GetPubKey()) << OP_CHECKSIG; keystore.AddCScript(pkSingle); scriptPubKey = GetScriptForDestination(CScriptID(pkSingle)); SignSignature(keystore, CTransaction(txFrom), txTo, 0, SigHashType()); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); BOOST_CHECK(combined.scriptSig == scriptSig); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); BOOST_CHECK(combined.scriptSig == scriptSig); scriptSigCopy = scriptSig; SignSignature(keystore, CTransaction(txFrom), txTo, 0, SigHashType()); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); BOOST_CHECK(combined.scriptSig == scriptSigCopy || combined.scriptSig == scriptSig); // dummy scriptSigCopy with placeholder, should always choose // non-placeholder: scriptSigCopy = CScript() << OP_0 << std::vector(pkSingle.begin(), pkSingle.end()); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSigCopy), SignatureData(scriptSig)); BOOST_CHECK(combined.scriptSig == scriptSig); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), SignatureData(scriptSigCopy)); BOOST_CHECK(combined.scriptSig == scriptSig); // Hardest case: Multisig 2-of-3 scriptPubKey = GetScriptForMultisig(2, pubkeys); keystore.AddCScript(scriptPubKey); SignSignature(keystore, CTransaction(txFrom), txTo, 0, SigHashType()); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(scriptSig), empty); BOOST_CHECK(combined.scriptSig == scriptSig); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), empty, SignatureData(scriptSig)); BOOST_CHECK(combined.scriptSig == scriptSig); // A couple of partially-signed versions: std::vector sig1; uint256 hash1 = SignatureHash(scriptPubKey, CTransaction(txTo), 0, SigHashType(), Amount::zero()); BOOST_CHECK(keys[0].Sign(hash1, sig1)); sig1.push_back(SIGHASH_ALL); std::vector sig2; uint256 hash2 = SignatureHash( scriptPubKey, CTransaction(txTo), 0, SigHashType().withBaseType(BaseSigHashType::NONE), Amount::zero()); BOOST_CHECK(keys[1].Sign(hash2, sig2)); sig2.push_back(SIGHASH_NONE); std::vector sig3; uint256 hash3 = SignatureHash( scriptPubKey, CTransaction(txTo), 0, SigHashType().withBaseType(BaseSigHashType::SINGLE), Amount::zero()); BOOST_CHECK(keys[2].Sign(hash3, sig3)); sig3.push_back(SIGHASH_SINGLE); // Not fussy about order (or even existence) of placeholders or signatures: CScript partial1a = CScript() << OP_0 << sig1 << OP_0; CScript partial1b = CScript() << OP_0 << OP_0 << sig1; CScript partial2a = CScript() << OP_0 << sig2; CScript partial2b = CScript() << sig2 << OP_0; CScript partial3a = CScript() << sig3; CScript partial3b = CScript() << OP_0 << OP_0 << sig3; CScript partial3c = CScript() << OP_0 << sig3 << OP_0; CScript complete12 = CScript() << OP_0 << sig1 << sig2; CScript complete13 = CScript() << OP_0 << sig1 << sig3; CScript complete23 = CScript() << OP_0 << sig2 << sig3; combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1a), SignatureData(partial1b)); BOOST_CHECK(combined.scriptSig == partial1a); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1a), SignatureData(partial2a)); BOOST_CHECK(combined.scriptSig == complete12); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial2a), SignatureData(partial1a)); BOOST_CHECK(combined.scriptSig == complete12); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial1b), SignatureData(partial2b)); BOOST_CHECK(combined.scriptSig == complete12); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial1b)); BOOST_CHECK(combined.scriptSig == complete13); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial2a), SignatureData(partial3a)); BOOST_CHECK(combined.scriptSig == complete23); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial2b)); BOOST_CHECK(combined.scriptSig == complete23); combined = CombineSignatures( scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, amount), SignatureData(partial3b), SignatureData(partial3a)); BOOST_CHECK(combined.scriptSig == partial3c); } BOOST_AUTO_TEST_CASE(script_standard_push) { ScriptError err; for (int i = 0; i < 67000; i++) { CScript script; script << i; BOOST_CHECK_MESSAGE(script.IsPushOnly(), "Number " << i << " is not pure push."); BOOST_CHECK_MESSAGE(VerifyScript(script, CScript() << OP_1, SCRIPT_VERIFY_MINIMALDATA, BaseSignatureChecker(), &err), "Number " << i << " push is not minimal data."); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } for (unsigned int i = 0; i <= MAX_SCRIPT_ELEMENT_SIZE; i++) { std::vector data(i, '\111'); CScript script; script << data; BOOST_CHECK_MESSAGE(script.IsPushOnly(), "Length " << i << " is not pure push."); BOOST_CHECK_MESSAGE(VerifyScript(script, CScript() << OP_1, SCRIPT_VERIFY_MINIMALDATA, BaseSignatureChecker(), &err), "Length " << i << " push is not minimal data."); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } } BOOST_AUTO_TEST_CASE(script_IsPushOnly_on_invalid_scripts) { // IsPushOnly returns false when given a script containing only pushes that // are invalid due to truncation. IsPushOnly() is consensus critical because // P2SH evaluation uses it, although this specific behavior should not be // consensus critical as the P2SH evaluation would fail first due to the // invalid push. Still, it doesn't hurt to test it explicitly. static const uint8_t direct[] = {1}; BOOST_CHECK(!CScript(direct, direct + sizeof(direct)).IsPushOnly()); } BOOST_AUTO_TEST_CASE(script_GetScriptAsm) { BOOST_CHECK_EQUAL("OP_CHECKLOCKTIMEVERIFY", ScriptToAsmStr(CScript() << OP_NOP2, true)); BOOST_CHECK_EQUAL( "OP_CHECKLOCKTIMEVERIFY", ScriptToAsmStr(CScript() << OP_CHECKLOCKTIMEVERIFY, true)); BOOST_CHECK_EQUAL("OP_CHECKLOCKTIMEVERIFY", ScriptToAsmStr(CScript() << OP_NOP2)); BOOST_CHECK_EQUAL("OP_CHECKLOCKTIMEVERIFY", ScriptToAsmStr(CScript() << OP_CHECKLOCKTIMEVERIFY)); std::string derSig("304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e" "3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38" "d782e53023ee313d741ad0cfbc0c5090"); std::string pubKey( "03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2"); std::vector vchPubKey = ToByteVector(ParseHex(pubKey)); BOOST_CHECK_EQUAL( derSig + "00 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "00")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "80 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "80")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[ALL] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "01")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[ALL|ANYONECANPAY] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "81")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[ALL|FORKID] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "41")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[ALL|FORKID|ANYONECANPAY] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "c1")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[NONE] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "02")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[NONE|ANYONECANPAY] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "82")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[NONE|FORKID] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "42")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[NONE|FORKID|ANYONECANPAY] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "c2")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[SINGLE] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "03")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[SINGLE|ANYONECANPAY] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "83")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[SINGLE|FORKID] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "43")) << vchPubKey, true)); BOOST_CHECK_EQUAL( derSig + "[SINGLE|FORKID|ANYONECANPAY] " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "c3")) << vchPubKey, true)); BOOST_CHECK_EQUAL(derSig + "00 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "00")) << vchPubKey)); BOOST_CHECK_EQUAL(derSig + "80 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "80")) << vchPubKey)); BOOST_CHECK_EQUAL(derSig + "01 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "01")) << vchPubKey)); BOOST_CHECK_EQUAL(derSig + "02 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "02")) << vchPubKey)); BOOST_CHECK_EQUAL(derSig + "03 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "03")) << vchPubKey)); BOOST_CHECK_EQUAL(derSig + "81 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "81")) << vchPubKey)); BOOST_CHECK_EQUAL(derSig + "82 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "82")) << vchPubKey)); BOOST_CHECK_EQUAL(derSig + "83 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "83")) << vchPubKey)); } static CScript ScriptFromHex(const char *hex) { std::vector data = ParseHex(hex); return CScript(data.begin(), data.end()); } BOOST_AUTO_TEST_CASE(script_FindAndDelete) { // Exercise the FindAndDelete functionality CScript s; CScript d; CScript expect; s = CScript() << OP_1 << OP_2; // delete nothing should be a no-op d = CScript(); expect = s; BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0); BOOST_CHECK(s == expect); s = CScript() << OP_1 << OP_2 << OP_3; d = CScript() << OP_2; expect = CScript() << OP_1 << OP_3; BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1); BOOST_CHECK(s == expect); s = CScript() << OP_3 << OP_1 << OP_3 << OP_3 << OP_4 << OP_3; d = CScript() << OP_3; expect = CScript() << OP_1 << OP_4; BOOST_CHECK_EQUAL(s.FindAndDelete(d), 4); BOOST_CHECK(s == expect); // PUSH 0x02ff03 onto stack s = ScriptFromHex("0302ff03"); d = ScriptFromHex("0302ff03"); expect = CScript(); BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1); BOOST_CHECK(s == expect); // PUSH 0x2ff03 PUSH 0x2ff03 s = ScriptFromHex("0302ff030302ff03"); d = ScriptFromHex("0302ff03"); expect = CScript(); BOOST_CHECK_EQUAL(s.FindAndDelete(d), 2); BOOST_CHECK(s == expect); s = ScriptFromHex("0302ff030302ff03"); d = ScriptFromHex("02"); expect = s; // FindAndDelete matches entire opcodes BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0); BOOST_CHECK(s == expect); s = ScriptFromHex("0302ff030302ff03"); d = ScriptFromHex("ff"); expect = s; BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0); BOOST_CHECK(s == expect); // This is an odd edge case: strip of the push-three-bytes prefix, leaving // 02ff03 which is push-two-bytes: s = ScriptFromHex("0302ff030302ff03"); d = ScriptFromHex("03"); expect = CScript() << ParseHex("ff03") << ParseHex("ff03"); BOOST_CHECK_EQUAL(s.FindAndDelete(d), 2); BOOST_CHECK(s == expect); // Byte sequence that spans multiple opcodes: // PUSH(0xfeed) OP_1 OP_VERIFY s = ScriptFromHex("02feed5169"); d = ScriptFromHex("feed51"); expect = s; // doesn't match 'inside' opcodes BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0); BOOST_CHECK(s == expect); // PUSH(0xfeed) OP_1 OP_VERIFY s = ScriptFromHex("02feed5169"); d = ScriptFromHex("02feed51"); expect = ScriptFromHex("69"); BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1); BOOST_CHECK(s == expect); s = ScriptFromHex("516902feed5169"); d = ScriptFromHex("feed51"); expect = s; BOOST_CHECK_EQUAL(s.FindAndDelete(d), 0); BOOST_CHECK(s == expect); s = ScriptFromHex("516902feed5169"); d = ScriptFromHex("02feed51"); expect = ScriptFromHex("516969"); BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1); BOOST_CHECK(s == expect); s = CScript() << OP_0 << OP_0 << OP_1 << OP_1; d = CScript() << OP_0 << OP_1; // FindAndDelete is single-pass expect = CScript() << OP_0 << OP_1; BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1); BOOST_CHECK(s == expect); s = CScript() << OP_0 << OP_0 << OP_1 << OP_0 << OP_1 << OP_1; d = CScript() << OP_0 << OP_1; // FindAndDelete is single-pass expect = CScript() << OP_0 << OP_1; BOOST_CHECK_EQUAL(s.FindAndDelete(d), 2); BOOST_CHECK(s == expect); // Another weird edge case: // End with invalid push (not enough data)... s = ScriptFromHex("0003feed"); // ... can remove the invalid push d = ScriptFromHex("03feed"); expect = ScriptFromHex("00"); BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1); BOOST_CHECK(s == expect); s = ScriptFromHex("0003feed"); d = ScriptFromHex("00"); expect = ScriptFromHex("03feed"); BOOST_CHECK_EQUAL(s.FindAndDelete(d), 1); BOOST_CHECK(s == expect); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/txmempool.h b/src/txmempool.h index b343b77b7..d8afbf2da 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -1,952 +1,952 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #ifndef BITCOIN_TXMEMPOOL_H #define BITCOIN_TXMEMPOOL_H #include "amount.h" #include "coins.h" #include "indirectmap.h" #include "primitives/transaction.h" #include "random.h" #include "sync.h" #include #include #include #include #include #include #include #include #include #include #include class CAutoFile; class CBlockIndex; class Config; inline double AllowFreeThreshold() { return (144 * COIN) / (250 * SATOSHI); } inline bool AllowFree(double dPriority) { // Large (in bytes) low-priority (new, small-coin) transactions need a fee. return dPriority > AllowFreeThreshold(); } /** * Fake height value used in Coins to signify they are only in the memory * pool(since 0.8) */ static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF; struct LockPoints { // Will be set to the blockchain height and median time past values that // would be necessary to satisfy all relative locktime constraints (BIP68) // of this tx given our view of block chain history int height; int64_t time; // As long as the current chain descends from the highest height block // containing one of the inputs used in the calculation, then the cached // values are still valid even after a reorg. CBlockIndex *maxInputBlock; LockPoints() : height(0), time(0), maxInputBlock(nullptr) {} }; class CTxMemPool; /** \class CTxMemPoolEntry * * CTxMemPoolEntry stores data about the corresponding transaction, as well as * data about all in-mempool transactions that depend on the transaction * ("descendant" transactions). * * When a new entry is added to the mempool, we update the descendant state * (nCountWithDescendants, nSizeWithDescendants, and nModFeesWithDescendants) * for all ancestors of the newly added transaction. * * If updating the descendant state is skipped, we can mark the entry as * "dirty", and set nSizeWithDescendants/nModFeesWithDescendants to equal * nTxSize/nFee+feeDelta. (This can potentially happen during a reorg, where we * limit the amount of work we're willing to do to avoid consuming too much * CPU.) */ class CTxMemPoolEntry { private: CTransactionRef tx; //!< Cached to avoid expensive parent-transaction lookups Amount nFee; //!< ... and avoid recomputing tx size size_t nTxSize; //!< ... and billable size for billing size_t nTxBillableSize; //!< ... and modified size for priority size_t nModSize; //!< ... and total memory usage size_t nUsageSize; //!< Local time when entering the mempool int64_t nTime; //!< Priority when entering the mempool double entryPriority; //!< Chain height when entering the mempool unsigned int entryHeight; //!< Sum of all txin values that are already in blockchain Amount inChainInputValue; //!< keep track of transactions that spend a coinbase bool spendsCoinbase; //!< Total sigop plus P2SH sigops count int64_t sigOpCount; //!< Used for determining the priority of the transaction for mining in a //! block Amount feeDelta; //!< Track the height and time at which tx was final LockPoints lockPoints; // Information about descendants of this transaction that are in the // mempool; if we remove this transaction we must remove all of these // descendants as well. if nCountWithDescendants is 0, treat this entry as // dirty, and nSizeWithDescendants and nModFeesWithDescendants will not be // correct. //!< number of descendant transactions uint64_t nCountWithDescendants; //!< ... and size uint64_t nSizeWithDescendants; uint64_t nBillableSizeWithDescendants; //!< ... and total fees (all including us) Amount nModFeesWithDescendants; // Analogous statistics for ancestor transactions uint64_t nCountWithAncestors; uint64_t nSizeWithAncestors; uint64_t nBillableSizeWithAncestors; Amount nModFeesWithAncestors; int64_t nSigOpCountWithAncestors; public: CTxMemPoolEntry(const CTransactionRef &_tx, const Amount _nFee, int64_t _nTime, double _entryPriority, unsigned int _entryHeight, Amount _inChainInputValue, bool spendsCoinbase, int64_t nSigOpsCost, LockPoints lp); const CTransaction &GetTx() const { return *this->tx; } CTransactionRef GetSharedTx() const { return this->tx; } /** * Fast calculation of lower bound of current priority as update from entry * priority. Only inputs that were originally in-chain will age. */ double GetPriority(unsigned int currentHeight) const; const Amount GetFee() const { return nFee; } size_t GetTxSize() const { return nTxSize; } size_t GetTxBillableSize() const { return nTxBillableSize; } int64_t GetTime() const { return nTime; } unsigned int GetHeight() const { return entryHeight; } int64_t GetSigOpCount() const { return sigOpCount; } Amount GetModifiedFee() const { return nFee + feeDelta; } size_t DynamicMemoryUsage() const { return nUsageSize; } const LockPoints &GetLockPoints() const { return lockPoints; } // Adjusts the descendant state, if this entry is not dirty. void UpdateDescendantState(int64_t modifySize, int64_t modifyBillableSize, Amount modifyFee, int64_t modifyCount); // Adjusts the ancestor state void UpdateAncestorState(int64_t modifySize, int64_t modifyBillableSize, Amount modifyFee, int64_t modifyCount, int modifySigOps); // Updates the fee delta used for mining priority score, and the // modified fees with descendants. void UpdateFeeDelta(Amount feeDelta); // Update the LockPoints after a reorg void UpdateLockPoints(const LockPoints &lp); uint64_t GetCountWithDescendants() const { return nCountWithDescendants; } uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; } uint64_t GetBillableSizeWithDescendants() const { return nBillableSizeWithDescendants; } Amount GetModFeesWithDescendants() const { return nModFeesWithDescendants; } bool GetSpendsCoinbase() const { return spendsCoinbase; } uint64_t GetCountWithAncestors() const { return nCountWithAncestors; } uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; } uint64_t GetBillableSizeWithAncestors() const { return nBillableSizeWithAncestors; } Amount GetModFeesWithAncestors() const { return nModFeesWithAncestors; } int64_t GetSigOpCountWithAncestors() const { return nSigOpCountWithAncestors; } //!< Index in mempool's vTxHashes mutable size_t vTxHashesIdx; }; // Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index. struct update_descendant_state { update_descendant_state(int64_t _modifySize, int64_t _modifyBillableSize, Amount _modifyFee, int64_t _modifyCount) : modifySize(_modifySize), modifyBillableSize(_modifyBillableSize), modifyFee(_modifyFee), modifyCount(_modifyCount) {} void operator()(CTxMemPoolEntry &e) { e.UpdateDescendantState(modifySize, modifyBillableSize, modifyFee, modifyCount); } private: int64_t modifySize; int64_t modifyBillableSize; Amount modifyFee; int64_t modifyCount; }; struct update_ancestor_state { update_ancestor_state(int64_t _modifySize, int64_t _modifyBillableSize, Amount _modifyFee, int64_t _modifyCount, int64_t _modifySigOpsCost) : modifySize(_modifySize), modifyBillableSize(_modifyBillableSize), modifyFee(_modifyFee), modifyCount(_modifyCount), modifySigOpsCost(_modifySigOpsCost) {} void operator()(CTxMemPoolEntry &e) { e.UpdateAncestorState(modifySize, modifyBillableSize, modifyFee, modifyCount, modifySigOpsCost); } private: int64_t modifySize; int64_t modifyBillableSize; Amount modifyFee; int64_t modifyCount; int64_t modifySigOpsCost; }; struct update_fee_delta { explicit update_fee_delta(Amount _feeDelta) : feeDelta(_feeDelta) {} void operator()(CTxMemPoolEntry &e) { e.UpdateFeeDelta(feeDelta); } private: Amount feeDelta; }; struct update_lock_points { explicit update_lock_points(const LockPoints &_lp) : lp(_lp) {} void operator()(CTxMemPoolEntry &e) { e.UpdateLockPoints(lp); } private: const LockPoints &lp; }; // extracts a transaction hash from CTxMempoolEntry or CTransactionRef struct mempoolentry_txid { typedef uint256 result_type; result_type operator()(const CTxMemPoolEntry &entry) const { return entry.GetTx().GetId(); } result_type operator()(const CTransactionRef &tx) const { return tx->GetId(); } }; /** \class CompareTxMemPoolEntryByDescendantScore * * Sort an entry by max(score/size of entry's tx, score/size with all * descendants). */ class CompareTxMemPoolEntryByDescendantScore { public: bool operator()(const CTxMemPoolEntry &a, const CTxMemPoolEntry &b) const { bool fUseADescendants = UseDescendantScore(a); bool fUseBDescendants = UseDescendantScore(b); double aModFee = (fUseADescendants ? a.GetModFeesWithDescendants() : a.GetModifiedFee()) / SATOSHI; double aSize = fUseADescendants ? a.GetSizeWithDescendants() : a.GetTxSize(); double bModFee = (fUseBDescendants ? b.GetModFeesWithDescendants() : b.GetModifiedFee()) / SATOSHI; double bSize = fUseBDescendants ? b.GetSizeWithDescendants() : b.GetTxSize(); // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). double f1 = aModFee * bSize; double f2 = aSize * bModFee; if (f1 == f2) { return a.GetTime() >= b.GetTime(); } return f1 < f2; } // Calculate which score to use for an entry (avoiding division). bool UseDescendantScore(const CTxMemPoolEntry &a) const { double f1 = a.GetSizeWithDescendants() * (a.GetModifiedFee() / SATOSHI); double f2 = a.GetTxSize() * (a.GetModFeesWithDescendants() / SATOSHI); return f2 > f1; } }; /** \class CompareTxMemPoolEntryByScore * * Sort by score of entry ((fee+delta)/size) in descending order */ class CompareTxMemPoolEntryByScore { public: bool operator()(const CTxMemPoolEntry &a, const CTxMemPoolEntry &b) const { double f1 = b.GetTxSize() * (a.GetModifiedFee() / SATOSHI); double f2 = a.GetTxSize() * (b.GetModifiedFee() / SATOSHI); if (f1 == f2) { return b.GetTx().GetId() < a.GetTx().GetId(); } return f1 > f2; } }; class CompareTxMemPoolEntryByEntryTime { public: bool operator()(const CTxMemPoolEntry &a, const CTxMemPoolEntry &b) const { return a.GetTime() < b.GetTime(); } }; class CompareTxMemPoolEntryByAncestorFee { public: bool operator()(const CTxMemPoolEntry &a, const CTxMemPoolEntry &b) const { double aFees = a.GetModFeesWithAncestors() / SATOSHI; double aSize = a.GetSizeWithAncestors(); double bFees = b.GetModFeesWithAncestors() / SATOSHI; double bSize = b.GetSizeWithAncestors(); // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). double f1 = aFees * bSize; double f2 = aSize * bFees; if (f1 == f2) { return a.GetTx().GetId() < b.GetTx().GetId(); } return f1 > f2; } }; // Multi_index tag names struct descendant_score {}; struct entry_time {}; struct mining_score {}; struct ancestor_score {}; class CBlockPolicyEstimator; /** * Information about a mempool transaction. */ struct TxMempoolInfo { /** The transaction itself */ CTransactionRef tx; /** Time the transaction entered the mempool. */ int64_t nTime; /** Feerate of the transaction. */ CFeeRate feeRate; /** The fee delta. */ Amount nFeeDelta; }; /** * Reason why a transaction was removed from the mempool, this is passed to the * notification signal. */ enum class MemPoolRemovalReason { //! Manually removed or unknown reason UNKNOWN = 0, //! Expired from mempool EXPIRY, //! Removed in size limiting SIZELIMIT, //! Removed for reorganization REORG, //! Removed for block BLOCK, //! Removed for conflict with in-block transaction CONFLICT, //! Removed for replacement REPLACED }; class SaltedTxidHasher { private: /** Salt */ const uint64_t k0, k1; public: SaltedTxidHasher(); size_t operator()(const uint256 &txid) const { return SipHashUint256(k0, k1, txid); } }; typedef std::pair TXModifier; /** * CTxMemPool stores valid-according-to-the-current-best-chain transactions that * may be included in the next block. * * Transactions are added when they are seen on the network (or created by the * local node), but not all transactions seen are added to the pool. For * example, the following new transactions will not be added to the mempool: * - a transaction which doesn't meet the minimum fee requirements. * - a new transaction that double-spends an input of a transaction already in * the pool where the new transaction does not meet the Replace-By-Fee * requirements as defined in BIP 125. * - a non-standard transaction. * * CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping: * * mapTx is a boost::multi_index that sorts the mempool on 4 criteria: * - transaction hash * - feerate [we use max(feerate of tx, feerate of tx with all descendants)] * - time in mempool * - mining score (feerate modified by any fee deltas from * PrioritiseTransaction) * * Note: the term "descendant" refers to in-mempool transactions that depend on * this one, while "ancestor" refers to in-mempool transactions that a given * transaction depends on. * * In order for the feerate sort to remain correct, we must update transactions * in the mempool when new descendants arrive. To facilitate this, we track the * set of in-mempool direct parents and direct children in mapLinks. Within each * CTxMemPoolEntry, we track the size and fees of all descendants. * * Usually when a new transaction is added to the mempool, it has no in-mempool * children (because any such children would be an orphan). So in * addUnchecked(), we: * - update a new entry's setMemPoolParents to include all in-mempool parents * - update the new entry's direct parents to include the new tx as a child * - update all ancestors of the transaction to include the new tx's size/fee * * When a transaction is removed from the mempool, we must: * - update all in-mempool parents to not track the tx in setMemPoolChildren * - update all ancestors to not include the tx's size/fees in descendant state * - update all in-mempool children to not include it as a parent * * These happen in UpdateForRemoveFromMempool(). (Note that when removing a * transaction along with its descendants, we must calculate that set of * transactions to be removed before doing the removal, or else the mempool can * be in an inconsistent state where it's impossible to walk the ancestors of a * transaction.) * * In the event of a reorg, the assumption that a newly added tx has no * in-mempool children is false. In particular, the mempool is in an * inconsistent state while new transactions are being added, because there may * be descendant transactions of a tx coming from a disconnected block that are * unreachable from just looking at transactions in the mempool (the linking * transactions may also be in the disconnected block, waiting to be added). * Because of this, there's not much benefit in trying to search for in-mempool * children in addUnchecked(). Instead, in the special case of transactions * being added from a disconnected block, we require the caller to clean up the * state, to account for in-mempool, out-of-block descendants for all the * in-block transactions by calling UpdateTransactionsFromBlock(). Note that * until this is called, the mempool state is not consistent, and in particular * mapLinks may not be correct (and therefore functions like * CalculateMemPoolAncestors() and CalculateDescendants() that rely on them to * walk the mempool are not generally safe to use). * * Computational limits: * * Updating all in-mempool ancestors of a newly added transaction can be slow, * if no bound exists on how many in-mempool ancestors there may be. * CalculateMemPoolAncestors() takes configurable limits that are designed to * prevent these calculations from being too CPU intensive. * * Adding transactions from a disconnected block can be very time consuming, * because we don't have a way to limit the number of in-mempool descendants. To * bound CPU processing, we limit the amount of work we're willing to do to * properly update the descendant information for a tx being added from a * disconnected block. If we would exceed the limit, then we instead mark the * entry as "dirty", and set the feerate for sorting purposes to be equal the * feerate of the transaction without any descendants. */ class CTxMemPool { private: //!< Value n means that n times in 2^32 we check. uint32_t nCheckFrequency; unsigned int nTransactionsUpdated; CBlockPolicyEstimator *minerPolicyEstimator; //!< sum of all mempool tx's virtual sizes. uint64_t totalTxSize; //!< sum of dynamic memory usage of all the map elements (NOT the maps //! themselves) uint64_t cachedInnerUsage; mutable int64_t lastRollingFeeUpdate; mutable bool blockSinceLastRollingFeeBump; //!< minimum fee to get into the pool, decreases exponentially mutable double rollingMinimumFeeRate; void trackPackageRemoved(const CFeeRate &rate); public: // public only for testing static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12; typedef boost::multi_index_container< CTxMemPoolEntry, boost::multi_index::indexed_by< // sorted by txid boost::multi_index::hashed_unique< mempoolentry_txid, SaltedTxidHasher>, // sorted by fee rate boost::multi_index::ordered_non_unique< boost::multi_index::tag, boost::multi_index::identity, CompareTxMemPoolEntryByDescendantScore>, // sorted by entry time boost::multi_index::ordered_non_unique< boost::multi_index::tag, boost::multi_index::identity, CompareTxMemPoolEntryByEntryTime>, // sorted by score (for mining prioritization) boost::multi_index::ordered_unique< boost::multi_index::tag, boost::multi_index::identity, CompareTxMemPoolEntryByScore>, // sorted by fee rate with ancestors boost::multi_index::ordered_non_unique< boost::multi_index::tag, boost::multi_index::identity, CompareTxMemPoolEntryByAncestorFee>>> indexed_transaction_set; mutable CCriticalSection cs; indexed_transaction_set mapTx; typedef indexed_transaction_set::nth_index<0>::type::iterator txiter; //!< All tx hashes/entries in mapTx, in random order std::vector> vTxHashes; struct CompareIteratorByHash { bool operator()(const txiter &a, const txiter &b) const { return a->GetTx().GetId() < b->GetTx().GetId(); } }; typedef std::set setEntries; const setEntries &GetMemPoolParents(txiter entry) const; const setEntries &GetMemPoolChildren(txiter entry) const; private: typedef std::map cacheMap; struct TxLinks { setEntries parents; setEntries children; }; typedef std::map txlinksMap; txlinksMap mapLinks; void UpdateParent(txiter entry, txiter parent, bool add); void UpdateChild(txiter entry, txiter child, bool add); std::vector GetSortedDepthAndScore() const; public: indirectmap mapNextTx; std::map mapDeltas; /** Create a new CTxMemPool. */ CTxMemPool(); ~CTxMemPool(); /** * If sanity-checking is turned on, check makes sure the pool is consistent * (does not contain two transactions that spend the same inputs, all inputs * are in the mapNextTx array). If sanity-checking is turned off, check does * nothing. */ void check(const CCoinsViewCache *pcoins) const; void setSanityCheck(double dFrequency = 1.0) { nCheckFrequency = dFrequency * 4294967295.0; } // addUnchecked must updated state for all ancestors of a given transaction, // to track size/count of descendant transactions. First version of // addUnchecked can be used to have it call CalculateMemPoolAncestors(), and // then invoke the second version. // Note that addUnchecked is ONLY called from ATMP outside of tests // and any other callers may break wallet's in-mempool tracking (due to // lack of CValidationInterface::TransactionAddedToMempool callbacks). bool addUnchecked(const uint256 &hash, const CTxMemPoolEntry &entry, bool validFeeEstimate = true); bool addUnchecked(const uint256 &hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool validFeeEstimate = true); void removeRecursive( const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); void removeForReorg(const Config &config, const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags); void removeConflicts(const CTransaction &tx); void removeForBlock(const std::vector &vtx, unsigned int nBlockHeight); void clear(); // lock free void _clear(); bool CompareDepthAndScore(const uint256 &hasha, const uint256 &hashb); void queryHashes(std::vector &vtxid); bool isSpent(const COutPoint &outpoint); unsigned int GetTransactionsUpdated() const; void AddTransactionsUpdated(unsigned int n); /** * Check that none of this transactions inputs are in the mempool, and thus * the tx is not dependent on other mempool transactions to be included in a * block. */ bool HasNoInputsOf(const CTransaction &tx) const; /** Affect CreateNewBlock prioritisation of transactions */ void PrioritiseTransaction(const uint256 hash, const std::string strHash, double dPriorityDelta, const Amount nFeeDelta); void ApplyDeltas(const uint256 hash, double &dPriorityDelta, Amount &nFeeDelta) const; void ClearPrioritisation(const uint256 hash); public: /** * Remove a set of transactions from the mempool. If a transaction is in * this set, then all in-mempool descendants must also be in the set, unless * this transaction is being removed for being in a block. Set * updateDescendants to true when removing a tx that was in a block, so that * any in-mempool descendants have their ancestor state updated. */ void RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); /** * When adding transactions from a disconnected block back to the mempool, * new mempool entries may have children in the mempool (which is generally * not the case when otherwise adding transactions). * UpdateTransactionsFromBlock() will find child transactions and update the * descendant state for each transaction in txidsToUpdate (excluding any * child transactions present in txidsToUpdate, which are already accounted * for). * Note: txidsToUpdate should be the set of transactions from the * disconnected block that have been accepted back into the mempool. */ void UpdateTransactionsFromBlock(const std::vector &txidsToUpdate); /** * Try to calculate all in-mempool ancestors of entry. * (these are all calculated including the tx itself) * limitAncestorCount = max number of ancestors * limitAncestorSize = max size of ancestors * limitDescendantCount = max number of descendants any ancestor can have * limitDescendantSize = max size of descendants any ancestor can have * errString = populated with error reason if any limits are hit * fSearchForParents = whether to search a tx's vin for in-mempool parents, * or look up parents from mapLinks. Must be true for entries not in the * mempool */ bool CalculateMemPoolAncestors( const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents = true) const; /** * Populate setDescendants with all in-mempool descendants of hash. * Assumes that setDescendants includes all in-mempool descendants of * anything already in it. */ void CalculateDescendants(txiter it, setEntries &setDescendants) const; /** * The minimum fee to get into the mempool, which may itself not be enough * for larger-sized transactions. The incrementalRelayFee policy variable is * used to bound the time it takes the fee rate to go back down all the way * to 0. When the feerate would otherwise be half of this, it is set to 0 * instead. */ CFeeRate GetMinFee(size_t sizelimit) const; /** * Remove transactions from the mempool until its dynamic size is <= * sizelimit. pvNoSpendsRemaining, if set, will be populated with the list * of outpoints which are not in mempool which no longer have any spends in * this mempool. */ void TrimToSize(size_t sizelimit, std::vector *pvNoSpendsRemaining = nullptr); /** * Expire all transaction (and their dependencies) in the mempool older than * time. Return the number of removed transactions. */ int Expire(int64_t time); /** * Reduce the size of the mempool by expiring and then trimming the mempool. */ void LimitSize(size_t limit, unsigned long age); /** * Returns false if the transaction is in the mempool and not within the * chain limit specified. */ bool TransactionWithinChainLimit(const uint256 &txid, size_t chainLimit) const; unsigned long size() { LOCK(cs); return mapTx.size(); } - uint64_t GetTotalTxSize() { + uint64_t GetTotalTxSize() const { LOCK(cs); return totalTxSize; } bool exists(uint256 hash) const { LOCK(cs); return mapTx.count(hash) != 0; } bool exists(const COutPoint &outpoint) const { LOCK(cs); auto it = mapTx.find(outpoint.GetTxId()); return it != mapTx.end() && outpoint.GetN() < it->GetTx().vout.size(); } CTransactionRef get(const uint256 &hash) const; TxMempoolInfo info(const uint256 &hash) const; std::vector infoAll() const; /** * Estimate fee rate needed to get into the next nBlocks. If no answer can * be given at nBlocks, return an estimate at the lowest number of blocks * where one can be given. */ CFeeRate estimateSmartFee(int nBlocks, int *answerFoundAtBlocks = nullptr) const; /** Estimate fee rate needed to get into the next nBlocks */ CFeeRate estimateFee(int nBlocks) const; /** Write/Read estimates to disk */ bool WriteFeeEstimates(CAutoFile &fileout) const; bool ReadFeeEstimates(CAutoFile &filein); size_t DynamicMemoryUsage() const; boost::signals2::signal NotifyEntryAdded; boost::signals2::signal NotifyEntryRemoved; private: /** * UpdateForDescendants is used by UpdateTransactionsFromBlock to update the * descendants for a single transaction that has been added to the mempool * but may have child transactions in the mempool, eg during a chain reorg. * setExclude is the set of descendant transactions in the mempool that must * not be accounted for (because any descendants in setExclude were added to * the mempool after the transaction being updated and hence their state is * already reflected in the parent state). * * cachedDescendants will be updated with the descendants of the transaction * being updated, so that future invocations don't need to walk the same * transaction again, if encountered in another transaction chain. */ void UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendants, const std::set &setExclude); /** * Update ancestors of hash to add/remove it as a descendant transaction. */ void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors); /** Set ancestor state for an entry */ void UpdateEntryForAncestors(txiter it, const setEntries &setAncestors); /** * For each transaction being removed, update ancestors and any direct * children. If updateDescendants is true, then also update in-mempool * descendants' ancestor state. */ void UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants); /** Sever link between specified transaction and direct children. */ void UpdateChildrenForRemoval(txiter entry); /** * Before calling removeUnchecked for a given transaction, * UpdateForRemoveFromMempool must be called on the entire (dependent) set * of transactions being removed at the same time. We use each * CTxMemPoolEntry's setMemPoolParents in order to walk ancestors of a given * transaction that is removed, so we can't remove intermediate transactions * in a chain before we've updated all the state for the removal. */ void removeUnchecked( txiter entry, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); }; /** * CCoinsView that brings transactions from a memorypool into view. * It does not check for spendings by memory pool transactions. */ class CCoinsViewMemPool : public CCoinsViewBacked { protected: const CTxMemPool &mempool; public: CCoinsViewMemPool(CCoinsView *baseIn, const CTxMemPool &mempoolIn); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; }; // We want to sort transactions by coin age priority typedef std::pair TxCoinAgePriority; struct TxCoinAgePriorityCompare { bool operator()(const TxCoinAgePriority &a, const TxCoinAgePriority &b) { if (a.first == b.first) { // Reverse order to make sort less than return CompareTxMemPoolEntryByScore()(*(b.second), *(a.second)); } return a.first < b.first; } }; /** * DisconnectedBlockTransactions * * During the reorg, it's desirable to re-add previously confirmed transactions * to the mempool, so that anything not re-confirmed in the new chain is * available to be mined. However, it's more efficient to wait until the reorg * is complete and process all still-unconfirmed transactions at that time, * since we expect most confirmed transactions to (typically) still be * confirmed in the new chain, and re-accepting to the memory pool is expensive * (and therefore better to not do in the middle of reorg-processing). * Instead, store the disconnected transactions (in order!) as we go, remove any * that are included in blocks in the new chain, and then process the remaining * still-unconfirmed transactions at the end. */ // multi_index tag names struct txid_index {}; struct insertion_order {}; class DisconnectedBlockTransactions { private: typedef boost::multi_index_container< CTransactionRef, boost::multi_index::indexed_by< // sorted by txid boost::multi_index::hashed_unique< boost::multi_index::tag, mempoolentry_txid, SaltedTxidHasher>, // sorted by order in the blockchain boost::multi_index::sequenced< boost::multi_index::tag>>> indexed_disconnected_transactions; indexed_disconnected_transactions queuedTx; uint64_t cachedInnerUsage = 0; void addTransaction(const CTransactionRef &tx) { queuedTx.insert(tx); cachedInnerUsage += RecursiveDynamicUsage(tx); } public: // It's almost certainly a logic bug if we don't clear out queuedTx before // destruction, as we add to it while disconnecting blocks, and then we // need to re-process remaining transactions to ensure mempool consistency. // For now, assert() that we've emptied out this object on destruction. // This assert() can always be removed if the reorg-processing code were // to be refactored such that this assumption is no longer true (for // instance if there was some other way we cleaned up the mempool after a // reorg, besides draining this object). ~DisconnectedBlockTransactions() { assert(queuedTx.empty()); } // Estimate the overhead of queuedTx to be 6 pointers + an allocation, as // no exact formula for boost::multi_index_contained is implemented. size_t DynamicMemoryUsage() const { return memusage::MallocUsage(sizeof(CTransactionRef) + 6 * sizeof(void *)) * queuedTx.size() + cachedInnerUsage; } const indexed_disconnected_transactions &GetQueuedTx() const { return queuedTx; } // Add entries for a block while reconstructing the topological ordering so // they can be added back to the mempool simply. void addForBlock(const std::vector &vtx); // Remove entries based on txid_index, and update memory usage. void removeForBlock(const std::vector &vtx) { // Short-circuit in the common case of a block being added to the tip if (queuedTx.empty()) { return; } for (auto const &tx : vtx) { auto it = queuedTx.find(tx->GetId()); if (it != queuedTx.end()) { cachedInnerUsage -= RecursiveDynamicUsage(*it); queuedTx.erase(it); } } } // Remove an entry by insertion_order index, and update memory usage. void removeEntry(indexed_disconnected_transactions::index< insertion_order>::type::iterator entry) { cachedInnerUsage -= RecursiveDynamicUsage(*entry); queuedTx.get().erase(entry); } void clear() { cachedInnerUsage = 0; queuedTx.clear(); } /** * Make mempool consistent after a reorg, by re-adding or recursively * erasing disconnected block transactions from the mempool, and also * removing any other transactions from the mempool that are no longer valid * given the new tip/height. * * Note: we assume that disconnectpool only contains transactions that are * NOT confirmed in the current chain nor already in the mempool (otherwise, * in-mempool descendants of such transactions would be removed). * * Passing fAddToMempool=false will skip trying to add the transactions * back, and instead just erase from the mempool as needed. */ void updateMempoolForReorg(const Config &config, bool fAddToMempool); }; #endif // BITCOIN_TXMEMPOOL_H diff --git a/src/wallet/db.h b/src/wallet/db.h index 52815ed90..04fc3c562 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -1,389 +1,389 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #ifndef BITCOIN_WALLET_DB_H #define BITCOIN_WALLET_DB_H #include "clientversion.h" #include "fs.h" #include "serialize.h" #include "streams.h" #include "sync.h" #include "version.h" #include #include #include #include #include static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100; static const bool DEFAULT_WALLET_PRIVDB = true; class CDBEnv { private: bool fDbEnvInit; bool fMockDb; // Don't change into fs::path, as that can result in // shutdown problems/crashes caused by a static initialized internal // pointer. std::string strPath; void EnvShutdown(); public: mutable CCriticalSection cs_db; std::unique_ptr dbenv; std::map mapFileUseCount; std::map mapDb; CDBEnv(); ~CDBEnv(); void Reset(); void MakeMock(); - bool IsMock() { return fMockDb; } + bool IsMock() const { return fMockDb; } /** * Verify that database file strFile is OK. If it is not, call the callback * to try to recover. * This must be called BEFORE strFile is opened. * Returns true if strFile is OK. */ enum VerifyResult { VERIFY_OK, RECOVER_OK, RECOVER_FAIL }; typedef bool (*recoverFunc_type)(const std::string &strFile, std::string &out_backup_filename); VerifyResult Verify(const std::string &strFile, recoverFunc_type recoverFunc, std::string &out_backup_filename); /** * Salvage data from a file that Verify says is bad. * fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method * documentation). * Appends binary key/value pairs to vResult, returns true if successful. * NOTE: reads the entire database into memory, so cannot be used * for huge databases. */ typedef std::pair, std::vector> KeyValPair; bool Salvage(const std::string &strFile, bool fAggressive, std::vector &vResult); bool Open(const fs::path &path); void Close(); void Flush(bool fShutdown); void CheckpointLSN(const std::string &strFile); void CloseDb(const std::string &strFile); DbTxn *TxnBegin(int flags = DB_TXN_WRITE_NOSYNC) { DbTxn *ptxn = nullptr; int ret = dbenv->txn_begin(nullptr, &ptxn, flags); if (!ptxn || ret != 0) return nullptr; return ptxn; } }; extern CDBEnv bitdb; /** * An instance of this class represents one database. * For BerkeleyDB this is just a (env, strFile) tuple. */ class CWalletDBWrapper { friend class CDB; public: /** Create dummy DB handle */ CWalletDBWrapper() : nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr) {} /** Create DB handle to real database */ CWalletDBWrapper(CDBEnv *env_in, const std::string &strFile_in) : nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(env_in), strFile(strFile_in) {} /** Rewrite the entire database on disk, with the exception of key pszSkip * if non-zero */ bool Rewrite(const char *pszSkip = nullptr); /** Back up the entire database to a file. */ bool Backup(const std::string &strDest); /** Get a name for this database, for debugging etc. */ std::string GetName() const { return strFile; } /** Make sure all changes are flushed to disk. */ void Flush(bool shutdown); void IncrementUpdateCounter(); std::atomic nUpdateCounter; unsigned int nLastSeen; unsigned int nLastFlushed; int64_t nLastWalletUpdate; private: /** BerkeleyDB specific */ CDBEnv *env; std::string strFile; /** * Return whether this database handle is a dummy for testing. * Only to be used at a low level, application should ideally not care * about this. */ bool IsDummy() { return env == nullptr; } }; /** RAII class that provides access to a Berkeley database */ class CDB { protected: Db *pdb; std::string strFile; DbTxn *activeTxn; bool fReadOnly; bool fFlushOnClose; CDBEnv *env; public: explicit CDB(CWalletDBWrapper &dbw, const char *pszMode = "r+", bool fFlushOnCloseIn = true); ~CDB() { Close(); } void Flush(); void Close(); static bool Recover(const std::string &filename, void *callbackDataIn, bool (*recoverKVcallback)(void *callbackData, CDataStream ssKey, CDataStream ssValue), std::string &out_backup_filename); /* flush the wallet passively (TRY_LOCK) ideal to be called periodically */ static bool PeriodicFlush(CWalletDBWrapper &dbw); /* verifies the database environment */ static bool VerifyEnvironment(const std::string &walletFile, const fs::path &dataDir, std::string &errorStr); /* verifies the database file */ static bool VerifyDatabaseFile(const std::string &walletFile, const fs::path &dataDir, std::string &warningStr, std::string &errorStr, CDBEnv::recoverFunc_type recoverFunc); private: CDB(const CDB &); void operator=(const CDB &); public: template bool Read(const K &key, T &value) { if (!pdb) { return false; } // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; Dbt datKey(ssKey.data(), ssKey.size()); // Read Dbt datValue; datValue.set_flags(DB_DBT_MALLOC); int ret = pdb->get(activeTxn, &datKey, &datValue, 0); memset(datKey.get_data(), 0, datKey.get_size()); if (datValue.get_data() == nullptr) { return false; } // Unserialize value try { CDataStream ssValue((char *)datValue.get_data(), (char *)datValue.get_data() + datValue.get_size(), SER_DISK, CLIENT_VERSION); ssValue >> value; } catch (const std::exception &) { return false; } // Clear and free memory memset(datValue.get_data(), 0, datValue.get_size()); free(datValue.get_data()); return (ret == 0); } template bool Write(const K &key, const T &value, bool fOverwrite = true) { if (!pdb) { return true; } if (fReadOnly) { assert(!"Write called on database in read-only mode"); } // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; Dbt datKey(ssKey.data(), ssKey.size()); // Value CDataStream ssValue(SER_DISK, CLIENT_VERSION); ssValue.reserve(10000); ssValue << value; Dbt datValue(ssValue.data(), ssValue.size()); // Write int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); // Clear memory in case it was a private key memset(datKey.get_data(), 0, datKey.get_size()); memset(datValue.get_data(), 0, datValue.get_size()); return (ret == 0); } template bool Erase(const K &key) { if (!pdb) { return false; } if (fReadOnly) { assert(!"Erase called on database in read-only mode"); } // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; Dbt datKey(ssKey.data(), ssKey.size()); // Erase int ret = pdb->del(activeTxn, &datKey, 0); // Clear memory memset(datKey.get_data(), 0, datKey.get_size()); return (ret == 0 || ret == DB_NOTFOUND); } template bool Exists(const K &key) { if (!pdb) { return false; } // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; Dbt datKey(ssKey.data(), ssKey.size()); // Exists int ret = pdb->exists(activeTxn, &datKey, 0); // Clear memory memset(datKey.get_data(), 0, datKey.get_size()); return (ret == 0); } Dbc *GetCursor() { if (!pdb) { return nullptr; } Dbc *pcursor = nullptr; int ret = pdb->cursor(nullptr, &pcursor, 0); if (ret != 0) { return nullptr; } return pcursor; } int ReadAtCursor(Dbc *pcursor, CDataStream &ssKey, CDataStream &ssValue, bool setRange = false) { // Read at cursor Dbt datKey; unsigned int fFlags = DB_NEXT; if (setRange) { datKey.set_data(ssKey.data()); datKey.set_size(ssKey.size()); fFlags = DB_SET_RANGE; } Dbt datValue; datKey.set_flags(DB_DBT_MALLOC); datValue.set_flags(DB_DBT_MALLOC); int ret = pcursor->get(&datKey, &datValue, fFlags); if (ret != 0) { return ret; } else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr) { return 99999; } // Convert to streams ssKey.SetType(SER_DISK); ssKey.clear(); ssKey.write((char *)datKey.get_data(), datKey.get_size()); ssValue.SetType(SER_DISK); ssValue.clear(); ssValue.write((char *)datValue.get_data(), datValue.get_size()); // Clear and free memory memset(datKey.get_data(), 0, datKey.get_size()); memset(datValue.get_data(), 0, datValue.get_size()); free(datKey.get_data()); free(datValue.get_data()); return 0; } public: bool TxnBegin() { if (!pdb || activeTxn) { return false; } DbTxn *ptxn = bitdb.TxnBegin(); if (!ptxn) { return false; } activeTxn = ptxn; return true; } bool TxnCommit() { if (!pdb || !activeTxn) { return false; } int ret = activeTxn->commit(0); activeTxn = nullptr; return (ret == 0); } bool TxnAbort() { if (!pdb || !activeTxn) { return false; } int ret = activeTxn->abort(); activeTxn = nullptr; return (ret == 0); } bool ReadVersion(int &nVersion) { nVersion = 0; return Read(std::string("version"), nVersion); } bool WriteVersion(int nVersion) { return Write(std::string("version"), nVersion); } static bool Rewrite(CWalletDBWrapper &dbw, const char *pszSkip = nullptr); }; #endif // BITCOIN_WALLET_DB_H