#ifndef BITCOIN_CLAIMTRIE_H #define BITCOIN_CLAIMTRIE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include // leveldb keys #define TRIE_NODE 'n' // deprecated #define TRIE_NODE_CHILDREN 'b' #define TRIE_NODE_CLAIMS 'c' #define CLAIM_BY_ID 'i' #define CLAIM_QUEUE_ROW 'r' #define CLAIM_QUEUE_NAME_ROW 'm' #define CLAIM_EXP_QUEUE_ROW 'e' #define SUPPORT 's' #define SUPPORT_QUEUE_ROW 'u' #define SUPPORT_QUEUE_NAME_ROW 'p' #define SUPPORT_EXP_QUEUE_ROW 'x' uint256 getValueHash(const COutPoint& outPoint, int nHeightOfLastTakeover); struct CClaimValue { COutPoint outPoint; uint160 claimId; CAmount nAmount; CAmount nEffectiveAmount; int nHeight; int nValidAtHeight; CClaimValue() = default; CClaimValue(const COutPoint& outPoint, const uint160& claimId, CAmount nAmount, int nHeight, int nValidAtHeight) : outPoint(outPoint), claimId(claimId), nAmount(nAmount), nEffectiveAmount(nAmount), nHeight(nHeight), nValidAtHeight(nValidAtHeight) { } CClaimValue(CClaimValue&&) = default; CClaimValue(const CClaimValue&) = default; CClaimValue& operator=(CClaimValue&&) = default; CClaimValue& operator=(const CClaimValue&) = default; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(outPoint); READWRITE(claimId); READWRITE(nAmount); READWRITE(nHeight); READWRITE(nValidAtHeight); READWRITE(nEffectiveAmount); } bool operator<(const CClaimValue& other) const { if (nEffectiveAmount < other.nEffectiveAmount) return true; if (nEffectiveAmount != other.nEffectiveAmount) return false; if (nHeight > other.nHeight) return true; if (nHeight != other.nHeight) return false; return outPoint != other.outPoint && !(outPoint < other.outPoint); } bool operator==(const CClaimValue& other) const { return outPoint == other.outPoint && claimId == other.claimId && nAmount == other.nAmount && nHeight == other.nHeight && nValidAtHeight == other.nValidAtHeight; } bool operator!=(const CClaimValue& other) const { return !(*this == other); } }; struct CSupportValue { COutPoint outPoint; uint160 supportedClaimId; CAmount nAmount; int nHeight; int nValidAtHeight; CSupportValue() = default; CSupportValue(const COutPoint& outPoint, const uint160& supportedClaimId, CAmount nAmount, int nHeight, int nValidAtHeight) : outPoint(outPoint), supportedClaimId(supportedClaimId), nAmount(nAmount), nHeight(nHeight), nValidAtHeight(nValidAtHeight) { } CSupportValue(CSupportValue&&) = default; CSupportValue(const CSupportValue&) = default; CSupportValue& operator=(CSupportValue&&) = default; CSupportValue& operator=(const CSupportValue&) = default; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(outPoint); READWRITE(supportedClaimId); READWRITE(nAmount); READWRITE(nHeight); READWRITE(nValidAtHeight); } bool operator==(const CSupportValue& other) const { return outPoint == other.outPoint && supportedClaimId == other.supportedClaimId && nAmount == other.nAmount && nHeight == other.nHeight && nValidAtHeight == other.nValidAtHeight; } bool operator!=(const CSupportValue& other) const { return !(*this == other); } }; typedef std::vector claimEntryType; typedef std::vector supportEntryType; enum CClaimTrieDataFlags: uint32_t { HASH_DIRTY = 1U, CLAIMS_DIRTY = 2U, POTENTIAL_CHILDREN = 4U, // existing on disk }; struct CClaimTrieData { claimEntryType claims; int nHeightOfLastTakeover = 0; // non-serialized data: uint32_t flags = 0; uint256 hash; CClaimTrieData() = default; CClaimTrieData(CClaimTrieData&&) = default; CClaimTrieData(const CClaimTrieData&) = default; CClaimTrieData& operator=(CClaimTrieData&&) = default; CClaimTrieData& operator=(const CClaimTrieData& d) = default; bool insertClaim(const CClaimValue& claim); bool removeClaim(const COutPoint& outPoint, CClaimValue& claim); bool getBestClaim(CClaimValue& claim) const; bool haveClaim(const COutPoint& outPoint) const; void reorderClaims(const supportEntryType& support); ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(claims); READWRITE(nHeightOfLastTakeover); } bool operator==(const CClaimTrieData& other) const { return nHeightOfLastTakeover == other.nHeightOfLastTakeover && claims == other.claims; } bool operator!=(const CClaimTrieData& other) const { return !(*this == other); } bool empty() const { return claims.empty(); } }; struct CClaimTrieDataNode { uint256 hash; // we're using a vector to avoid RAM thrashing and for faster serialization ops. // We're assuming its data is inserted in order and never modified. std::vector children; CClaimTrieDataNode() = default; CClaimTrieDataNode(CClaimTrieDataNode&&) = default; CClaimTrieDataNode(const CClaimTrieDataNode&) = default; CClaimTrieDataNode& operator=(CClaimTrieDataNode&&) = default; CClaimTrieDataNode& operator=(const CClaimTrieDataNode& d) = default; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(hash); READWRITE(children); } }; struct COutPointHeightType { COutPoint outPoint; int nHeight; COutPointHeightType() = default; COutPointHeightType(const COutPoint& outPoint, int nHeight) : outPoint(outPoint), nHeight(nHeight) { } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(outPoint); READWRITE(nHeight); } }; struct CNameOutPointHeightType { std::string name; COutPoint outPoint; int nHeight; CNameOutPointHeightType() = default; CNameOutPointHeightType(std::string name, const COutPoint& outPoint, int nHeight) : name(std::move(name)), outPoint(outPoint), nHeight(nHeight) { } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(name); READWRITE(outPoint); READWRITE(nHeight); } }; struct CNameOutPointType { std::string name; COutPoint outPoint; CNameOutPointType() = default; CNameOutPointType(std::string name, const COutPoint& outPoint) : name(std::move(name)), outPoint(outPoint) { } bool operator==(const CNameOutPointType& other) const { return name == other.name && outPoint == other.outPoint; } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(name); READWRITE(outPoint); } }; struct CClaimIndexElement { CClaimIndexElement() = default; CClaimIndexElement(std::string name, CClaimValue claim) : name(std::move(name)), claim(std::move(claim)) { } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(name); READWRITE(claim); } std::string name; CClaimValue claim; }; struct CClaimsForNameType { claimEntryType claims; supportEntryType supports; int nLastTakeoverHeight; std::string name; CClaimsForNameType(claimEntryType claims, supportEntryType supports, int nLastTakeoverHeight, std::string name) : claims(std::move(claims)), supports(std::move(supports)), nLastTakeoverHeight(nLastTakeoverHeight), name(std::move(name)) { } CClaimsForNameType(CClaimsForNameType&&) = default; CClaimsForNameType(const CClaimsForNameType&) = default; CClaimsForNameType& operator=(CClaimsForNameType&&) = default; CClaimsForNameType& operator=(const CClaimsForNameType&) = default; }; class CClaimTrie { public: CClaimTrie() = default; virtual ~CClaimTrie() = default; CClaimTrie(CClaimTrie&&) = delete; CClaimTrie(const CClaimTrie&) = delete; CClaimTrie(bool fMemory, bool fWipe, int proportionalDelayFactor = 32); CClaimTrie& operator=(CClaimTrie&&) = delete; CClaimTrie& operator=(const CClaimTrie&) = delete; bool SyncToDisk(); friend class CClaimTrieCacheBase; friend struct ClaimTrieChainFixture; friend class CClaimTrieCacheExpirationFork; friend class CClaimTrieCacheNormalizationFork; std::size_t getTotalNamesInTrie() const; std::size_t getTotalClaimsInTrie() const; virtual bool checkConsistency(const uint256& rootHash) const; CAmount getTotalValueOfClaimsInTrie(bool fControllingOnly) const; bool contains(const std::string& key) const; bool empty() const; bool find(const std::string& key, CClaimTrieDataNode& node) const; bool find(const std::string& key, CClaimTrieData& claims) const; std::vector> nodes(const std::string& key) const; protected: int nNextHeight = 0; int nProportionalDelayFactor = 0; std::unique_ptr db; using recurseNodesCB = void(const std::string&, const CClaimTrieData&, const std::vector&); void recurseNodes(const std::string& name, const CClaimTrieDataNode& current, std::function function) const; }; struct CClaimTrieProofNode { CClaimTrieProofNode(std::vector> children, bool hasValue, const uint256& valHash) : children(std::move(children)), hasValue(hasValue), valHash(valHash) { } CClaimTrieProofNode(CClaimTrieProofNode&&) = default; CClaimTrieProofNode(const CClaimTrieProofNode&) = default; CClaimTrieProofNode& operator=(CClaimTrieProofNode&&) = default; CClaimTrieProofNode& operator=(const CClaimTrieProofNode&) = default; std::vector> children; bool hasValue; uint256 valHash; }; struct CClaimTrieProof { CClaimTrieProof() = default; CClaimTrieProof(CClaimTrieProof&&) = default; CClaimTrieProof(const CClaimTrieProof&) = default; CClaimTrieProof& operator=(CClaimTrieProof&&) = default; CClaimTrieProof& operator=(const CClaimTrieProof&) = default; std::vector> pairs; std::vector nodes; int nHeightOfLastTakeover = 0; bool hasValue = false; COutPoint outPoint; }; template class COptional { bool own; T* value; public: COptional(T* value = nullptr) : own(false), value(value) {} COptional(COptional&& o) { own = o.own; value = o.value; o.own = false; o.value = nullptr; } COptional(T&& o) : own(true) { value = new T(std::move(o)); } ~COptional() { if (own) delete value; } COptional& operator=(COptional&&) = delete; bool unique() const { return own; } operator bool() const { return value; } operator T*() const { return value; } T* operator->() const { return value; } operator T&() const { return *value; } T& operator*() const { return *value; } }; template using queueEntryType = std::pair; typedef std::vector> claimQueueRowType; typedef std::map claimQueueType; typedef std::vector> supportQueueRowType; typedef std::map supportQueueType; typedef std::vector queueNameRowType; typedef std::map queueNameType; typedef std::vector insertUndoType; typedef std::vector expirationQueueRowType; typedef std::map expirationQueueType; typedef std::set claimIndexClaimListType; typedef std::vector claimIndexElementListType; typedef CPrefixTrie CClaimPrefixTrie; class CClaimTrieCacheBase { public: explicit CClaimTrieCacheBase(CClaimTrie* base); virtual ~CClaimTrieCacheBase() = default; uint256 getMerkleHash(); bool getClaimById(const uint160& claimId, std::string& name, CClaimValue& claim) const; bool flush(); bool empty() const; bool ReadFromDisk(const CBlockIndex* tip); bool haveClaim(const std::string& name, const COutPoint& outPoint) const; bool haveClaimInQueue(const std::string& name, const COutPoint& outPoint, int& nValidAtHeight) const; bool haveSupport(const std::string& name, const COutPoint& outPoint) const; bool haveSupportInQueue(const std::string& name, const COutPoint& outPoint, int& nValidAtHeight) const; bool addClaim(const std::string& name, const COutPoint& outPoint, const uint160& claimId, CAmount nAmount, int nHeight); bool undoAddClaim(const std::string& name, const COutPoint& outPoint, int nHeight); bool spendClaim(const std::string& name, const COutPoint& outPoint, int nHeight, int& nValidAtHeight); bool undoSpendClaim(const std::string& name, const COutPoint& outPoint, const uint160& claimId, CAmount nAmount, int nHeight, int nValidAtHeight); bool addSupport(const std::string& name, const COutPoint& outPoint, CAmount nAmount, const uint160& supportedClaimId, int nHeight); bool undoAddSupport(const std::string& name, const COutPoint& outPoint, int nHeight); bool spendSupport(const std::string& name, const COutPoint& outPoint, int nHeight, int& nValidAtHeight); bool undoSpendSupport(const std::string& name, const COutPoint& outPoint, const uint160& supportedClaimId, CAmount nAmount, int nHeight, int nValidAtHeight); virtual bool incrementBlock(insertUndoType& insertUndo, claimQueueRowType& expireUndo, insertUndoType& insertSupportUndo, supportQueueRowType& expireSupportUndo, std::vector>& takeoverHeightUndo); virtual bool decrementBlock(insertUndoType& insertUndo, claimQueueRowType& expireUndo, insertUndoType& insertSupportUndo, supportQueueRowType& expireSupportUndo); virtual bool getProofForName(const std::string& name, CClaimTrieProof& proof); virtual bool getInfoForName(const std::string& name, CClaimValue& claim) const; virtual int expirationTime() const; virtual bool finalizeDecrement(std::vector>& takeoverHeightUndo); virtual CClaimsForNameType getClaimsForName(const std::string& name) const; CAmount getEffectiveAmountForClaim(const std::string& name, const uint160& claimId, std::vector* supports = nullptr) const; CAmount getEffectiveAmountForClaim(const CClaimsForNameType& claims, const uint160& claimId, std::vector* supports = nullptr) const; CClaimPrefixTrie::const_iterator begin() const; CClaimPrefixTrie::const_iterator end() const; void dumpToLog(CClaimPrefixTrie::const_iterator it, bool diffFromBase = true) const; virtual std::string adjustNameForValidHeight(const std::string& name, int validHeight) const; void recurseNodes(const std::string& name, std::function function) const; protected: CClaimTrie* base; CClaimPrefixTrie nodesToAddOrUpdate; // nodes pulled in from base (and possibly modified thereafter), written to base on flush std::unordered_set nodesAlreadyCached; // set of nodes already pulled into cache from base std::unordered_set namesToCheckForTakeover; // takeover numbers are updated on increment virtual uint256 recursiveComputeMerkleHash(CClaimPrefixTrie::iterator& it); virtual bool insertClaimIntoTrie(const std::string& name, const CClaimValue& claim, bool fCheckTakeover); virtual bool removeClaimFromTrie(const std::string& name, const COutPoint& outPoint, CClaimValue& claim, bool fCheckTakeover); virtual bool insertSupportIntoMap(const std::string& name, const CSupportValue& support, bool fCheckTakeover); virtual bool removeSupportFromMap(const std::string& name, const COutPoint& outPoint, CSupportValue& support, bool fCheckTakeover); supportEntryType getSupportsForName(const std::string& name) const; int getDelayForName(const std::string& name) const; virtual int getDelayForName(const std::string& name, const uint160& claimId) const; CClaimPrefixTrie::iterator cacheData(const std::string& name, bool create = true); bool getLastTakeoverForName(const std::string& name, uint160& claimId, int& takeoverHeight) const; int getNumBlocksOfContinuousOwnership(const std::string& name) const; void reactivateClaim(const expirationQueueRowType& row, int height, bool increment); void reactivateSupport(const expirationQueueRowType& row, int height, bool increment); expirationQueueType expirationQueueCache; expirationQueueType supportExpirationQueueCache; int nNextHeight; // Height of the block that is being worked on, which is // one greater than the height of the chain's tip private: uint256 hashBlock; std::unordered_map> takeoverCache; claimQueueType claimQueueCache; // claims not active yet: to be written to disk on flush queueNameType claimQueueNameCache; supportQueueType supportQueueCache; // supports not active yet: to be written to disk on flush queueNameType supportQueueNameCache; claimIndexElementListType claimsToAddToByIdIndex; // written to index on flush claimIndexClaimListType claimsToDeleteFromByIdIndex; std::unordered_map supportCache; // to be added/updated to base (and disk) on flush std::unordered_set nodesToDelete; // to be removed from base (and disk) on flush std::unordered_map takeoverWorkaround; std::unordered_set removalWorkaround; std::unordered_set forDeleteFromBase; bool shouldUseTakeoverWorkaround(const std::string& key) const; void addTakeoverWorkaroundPotential(const std::string& key); void confirmTakeoverWorkaroundNeeded(const std::string& key); bool clear(); void markAsDirty(const std::string& name, bool fCheckTakeover); bool removeSupport(const std::string& name, const COutPoint& outPoint, int nHeight, int& nValidAtHeight, bool fCheckTakeover); bool removeClaim(const std::string& name, const COutPoint& outPoint, int nHeight, int& nValidAtHeight, bool fCheckTakeover); bool validateTrieConsistency(const CBlockIndex* tip); template std::vector>* getQueueCacheRow(int nHeight, bool createIfNotExists); template COptional>> getQueueCacheRow(int nHeight) const; template queueNameRowType* getQueueCacheNameRow(const std::string& name, bool createIfNotExists); template COptional getQueueCacheNameRow(const std::string& name) const; template expirationQueueRowType* getExpirationQueueCacheRow(int nHeight, bool createIfNotExists); template bool haveInQueue(const std::string& name, const COutPoint& outPoint, int& nValidAtHeight) const; template T add(const std::string& name, const COutPoint& outPoint, const uint160& claimId, CAmount nAmount, int nHeight); template bool remove(T& value, const std::string& name, const COutPoint& outPoint, int nHeight, int& nValidAtHeight, bool fCheckTakeover = false); template bool addToQueue(const std::string& name, const T& value); template bool removeFromQueue(const std::string& name, const COutPoint& outPoint, T& value); template bool addToCache(const std::string& name, const T& value, bool fCheckTakeover = false); template bool removeFromCache(const std::string& name, const COutPoint& outPoint, T& value, bool fCheckTakeover = false); template bool undoSpend(const std::string& name, const T& value, int nValidAtHeight); template void undoIncrement(insertUndoType& insertUndo, std::vector>& expireUndo, std::set* deleted = nullptr); template void undoDecrement(insertUndoType& insertUndo, std::vector>& expireUndo, std::vector* added = nullptr, std::set* deleted = nullptr); template void undoIncrement(const std::string& name, insertUndoType& insertUndo, std::vector>& expireUndo); template void reactivate(const expirationQueueRowType& row, int height, bool increment); // for unit test friend struct ClaimTrieChainFixture; friend class CClaimTrieCacheTest; }; class CClaimTrieCacheExpirationFork : public CClaimTrieCacheBase { public: explicit CClaimTrieCacheExpirationFork(CClaimTrie* base); void setExpirationTime(int time); int expirationTime() const override; virtual void initializeIncrement(); bool finalizeDecrement(std::vector>& takeoverHeightUndo) override; bool incrementBlock(insertUndoType& insertUndo, claimQueueRowType& expireUndo, insertUndoType& insertSupportUndo, supportQueueRowType& expireSupportUndo, std::vector>& takeoverHeightUndo) override; bool decrementBlock(insertUndoType& insertUndo, claimQueueRowType& expireUndo, insertUndoType& insertSupportUndo, supportQueueRowType& expireSupportUndo) override; private: int nExpirationTime; bool forkForExpirationChange(bool increment); }; class CClaimTrieCacheNormalizationFork : public CClaimTrieCacheExpirationFork { public: explicit CClaimTrieCacheNormalizationFork(CClaimTrie* base) : CClaimTrieCacheExpirationFork(base), overrideInsertNormalization(false), overrideRemoveNormalization(false) { } bool shouldNormalize() const; // lower-case and normalize any input string name // see: https://unicode.org/reports/tr15/#Norm_Forms std::string normalizeClaimName(const std::string& name, bool force = false) const; // public only for validating name field on update op bool incrementBlock(insertUndoType& insertUndo, claimQueueRowType& expireUndo, insertUndoType& insertSupportUndo, supportQueueRowType& expireSupportUndo, std::vector>& takeoverHeightUndo) override; bool decrementBlock(insertUndoType& insertUndo, claimQueueRowType& expireUndo, insertUndoType& insertSupportUndo, supportQueueRowType& expireSupportUndo) override; bool getProofForName(const std::string& name, CClaimTrieProof& proof) override; bool getInfoForName(const std::string& name, CClaimValue& claim) const override; CClaimsForNameType getClaimsForName(const std::string& name) const override; std::string adjustNameForValidHeight(const std::string& name, int validHeight) const override; protected: bool insertClaimIntoTrie(const std::string& name, const CClaimValue& claim, bool fCheckTakeover) override; bool removeClaimFromTrie(const std::string& name, const COutPoint& outPoint, CClaimValue& claim, bool fCheckTakeover) override; bool insertSupportIntoMap(const std::string& name, const CSupportValue& support, bool fCheckTakeover) override; bool removeSupportFromMap(const std::string& name, const COutPoint& outPoint, CSupportValue& support, bool fCheckTakeover) override; int getDelayForName(const std::string& name, const uint160& claimId) const override; private: bool overrideInsertNormalization; bool overrideRemoveNormalization; bool normalizeAllNamesInTrieIfNecessary(insertUndoType& insertUndo, claimQueueRowType& removeUndo, insertUndoType& insertSupportUndo, supportQueueRowType& expireSupportUndo, std::vector>& takeoverHeightUndo); }; class CClaimTrieCacheHashFork : public CClaimTrieCacheNormalizationFork { public: explicit CClaimTrieCacheHashFork(CClaimTrie* base); bool getProofForName(const std::string& name, CClaimTrieProof& proof) override; bool getProofForName(const std::string& name, CClaimTrieProof& proof, const uint160& claimId); void initializeIncrement() override; bool finalizeDecrement(std::vector>& takeoverHeightUndo) override; protected: uint256 recursiveComputeMerkleHash(CClaimPrefixTrie::iterator& it) override; private: void copyAllBaseToCache(); }; class CClaimTrieHashFork : public CClaimTrie { public: using CClaimTrie::CClaimTrie; protected: bool checkConsistency(const uint256& rootHash) const override; }; typedef CClaimTrieCacheHashFork CClaimTrieCache; #endif // BITCOIN_CLAIMTRIE_H