diff --git a/src/avalanche/proofpool.h b/src/avalanche/proofpool.h new file mode 100644 --- /dev/null +++ b/src/avalanche/proofpool.h @@ -0,0 +1,113 @@ +// Copyright (c) 2021 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_AVALANCHE_PROOFPOOL_H +#define BITCOIN_AVALANCHE_PROOFPOOL_H + +#include +#include + +#include +#include +#include + +namespace avalanche { + +class SaltedProofIdHasher : private SaltedUint256Hasher { +public: + SaltedProofIdHasher() : SaltedUint256Hasher() {} + size_t operator()(const ProofId &proofid) const { return hash(proofid); } +}; + +// Extracts a ProofId from a Proof +struct proofid_extractor { + typedef ProofId result_type; + result_type operator()(const Proof &proof) const { return proof.getId(); } +}; + +struct by_sequence {}; +struct by_proofid {}; + +typedef boost::multi_index_container< + Proof, + boost::multi_index::indexed_by< + // keep insertion order + boost::multi_index::sequenced>, + // indexed by proofid + boost::multi_index::hashed_unique, + proofid_extractor, + SaltedProofIdHasher>>> + proofContainer; + +/** + * ProofPool stores orphan proofs waiting for their UTXOs to be found. + * + * When the pool is full, the oldest proof is removed before a new one is + * added. + */ +class ProofPool { + proofContainer proofs; + + const size_t maxNumberOfStakes; + size_t nStakes = 0; + + /** + * Trim the proof pool to given max size. + * It the current size is <= max size this has no effect. + */ + void trimToMaximumSize() { + auto &proofs_by_sequence = proofs.get(); + + while (nStakes > maxNumberOfStakes) { + Proof p = proofs_by_sequence.back(); + nStakes = nStakes - p.getStakes().size(); + proofs_by_sequence.pop_back(); + } + } + +public: + ProofPool(size_t maxNumberOfStakes) + : maxNumberOfStakes(maxNumberOfStakes) {} + + /** + * Add a proof to the pool. + * The caller is responsible for checking the proof. + */ + bool addProof(const Proof &proof) { + if (proofs.get().push_front(proof).second) { + nStakes = nStakes + proof.getStakes().size(); + trimToMaximumSize(); + return true; + } + return false; + } + + /** Remove a proof from the pool. */ + bool removeProof(const ProofId &proofId) { + auto &proofs_by_id = proofs.get(); + auto it = proofs_by_id.find(proofId); + if (it == proofs_by_id.end()) { + return false; + } + nStakes = nStakes - it->getStakes().size(); + return proofs_by_id.erase(proofId) == 1; + } + + /** + * Get a pointer to a proof by id, or nullptr if the proof is not in the + * pool. + */ + const Proof *getProof(ProofId proofId) const { + auto &proofs_by_proofid = proofs.get(); + auto it = proofs_by_proofid.find(proofId); + return it == proofs_by_proofid.end() ? nullptr : &*it; + } + + // For testing + size_t getSize() const { return proofs.get().size(); } + size_t getNStakes() const { return nStakes; } +}; + +} // namespace avalanche + +#endif // BITCOIN_AVALANCHE_PROOFPOOL_H \ No newline at end of file diff --git a/src/avalanche/test/CMakeLists.txt b/src/avalanche/test/CMakeLists.txt --- a/src/avalanche/test/CMakeLists.txt +++ b/src/avalanche/test/CMakeLists.txt @@ -17,6 +17,7 @@ peermanager_tests.cpp processor_tests.cpp proof_tests.cpp + proofpool_tests.cpp ) target_link_libraries(test-avalanche server testutil) diff --git a/src/avalanche/test/proofpool_tests.cpp b/src/avalanche/test/proofpool_tests.cpp new file mode 100644 --- /dev/null +++ b/src/avalanche/test/proofpool_tests.cpp @@ -0,0 +1,167 @@ +// Copyright (c) 2021 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include + +#include