mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-07 07:10:21 +00:00
Compare commits
114 Commits
ximinez/fi
...
bthomee/no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b2c02a443 | ||
|
|
289d17aa10 | ||
|
|
4afd978be3 | ||
|
|
f5e63f8a91 | ||
|
|
7a153a2bce | ||
|
|
81d5f3f4ae | ||
|
|
5883e66120 | ||
|
|
b8f8afb82f | ||
|
|
89fff13b35 | ||
|
|
6da2b9e925 | ||
|
|
b156c19f82 | ||
|
|
eda7ec104a | ||
|
|
6ffef55fac | ||
|
|
b82baaec0f | ||
|
|
5f59b7f4ad | ||
|
|
b8be772441 | ||
|
|
0c7873c9f1 | ||
|
|
ec290777c5 | ||
|
|
cd7babf4b6 | ||
|
|
3f4d730a61 | ||
|
|
41b258ae73 | ||
|
|
cf8744ebe3 | ||
|
|
6f7c6a79f5 | ||
|
|
35ffa71c0b | ||
|
|
4e8b6c4176 | ||
|
|
081d86653f | ||
|
|
4ba60eae21 | ||
|
|
e21f879437 | ||
|
|
5023558e11 | ||
|
|
4b83169922 | ||
|
|
d5d79c1d09 | ||
|
|
7912d112e7 | ||
|
|
1a35abbb43 | ||
|
|
95e3267b07 | ||
|
|
42e5683385 | ||
|
|
fe6ddccabb | ||
|
|
49faf696d4 | ||
|
|
5b5ad09c53 | ||
|
|
2405a3353c | ||
|
|
705622011b | ||
|
|
ae5f5cb92b | ||
|
|
cde8f17b5d | ||
|
|
c8fb69ee1a | ||
|
|
430f770f2a | ||
|
|
44590a7008 | ||
|
|
1934c316b2 | ||
|
|
fd2a8b5825 | ||
|
|
012144c16c | ||
|
|
8e2d949680 | ||
|
|
b781018fee | ||
|
|
d867c9b26d | ||
|
|
7749ed8488 | ||
|
|
4d01cac564 | ||
|
|
98e1ad2dec | ||
|
|
342171bf20 | ||
|
|
fe74f48e7a | ||
|
|
d6e05cf513 | ||
|
|
e31419aa06 | ||
|
|
05b4c67b96 | ||
|
|
2524476124 | ||
|
|
8007788d77 | ||
|
|
809395a59c | ||
|
|
3ef64e019b | ||
|
|
228ad1e98f | ||
|
|
c4b342a027 | ||
|
|
556d80e724 | ||
|
|
044f1e67b9 | ||
|
|
0ad72fae8f | ||
|
|
cc383c5fb8 | ||
|
|
88715f1e5a | ||
|
|
e1477cef0c | ||
|
|
942874d7b0 | ||
|
|
79326fc6b5 | ||
|
|
48535d5226 | ||
|
|
d1a6558080 | ||
|
|
84f86b354f | ||
|
|
40a3985b02 | ||
|
|
208bd35d45 | ||
|
|
e90fbbf7b2 | ||
|
|
277450e648 | ||
|
|
e6993524ea | ||
|
|
b117ecc6a2 | ||
|
|
6c3b00c342 | ||
|
|
8c296a935a | ||
|
|
573ba82181 | ||
|
|
1542ab7e27 | ||
|
|
6374f4886d | ||
|
|
ebf336f472 | ||
|
|
ddc15ad612 | ||
|
|
82db6ac498 | ||
|
|
f749c41306 | ||
|
|
f25e47a58d | ||
|
|
2396799bd8 | ||
|
|
4855b9f96a | ||
|
|
b2f65cb7eb | ||
|
|
c523673885 | ||
|
|
caac4d63d3 | ||
|
|
29b0076fa8 | ||
|
|
c9aa1094a7 | ||
|
|
b86f69cb82 | ||
|
|
5d0bf78512 | ||
|
|
554df631c6 | ||
|
|
5e704bfdfb | ||
|
|
fe8cc02bfa | ||
|
|
061c033f52 | ||
|
|
832a7e7e4a | ||
|
|
b2371c4c02 | ||
|
|
b94a7c4b44 | ||
|
|
9b9027112d | ||
|
|
8e7889c66e | ||
|
|
d836c3788d | ||
|
|
1cb7c0293f | ||
|
|
52dabc1f79 | ||
|
|
2d78d41f7b |
@@ -72,7 +72,6 @@ test.app > xrpl.server
|
||||
test.app > xrpl.shamap
|
||||
test.app > xrpl.tx
|
||||
test.basics > test.jtx
|
||||
test.basics > test.unit_test
|
||||
test.basics > xrpl.basics
|
||||
test.basics > xrpl.core
|
||||
test.basics > xrpld.rpc
|
||||
@@ -162,9 +161,6 @@ test.protocol > test.unit_test
|
||||
test.protocol > xrpl.basics
|
||||
test.protocol > xrpl.json
|
||||
test.protocol > xrpl.protocol
|
||||
test.resource > test.unit_test
|
||||
test.resource > xrpl.basics
|
||||
test.resource > xrpl.resource
|
||||
test.rpc > test.jtx
|
||||
test.rpc > xrpl.basics
|
||||
test.rpc > xrpl.config
|
||||
@@ -188,12 +184,6 @@ test.server > xrpld.core
|
||||
test.server > xrpl.json
|
||||
test.server > xrpl.protocol
|
||||
test.server > xrpl.server
|
||||
test.shamap > test.unit_test
|
||||
test.shamap > xrpl.basics
|
||||
test.shamap > xrpl.config
|
||||
test.shamap > xrpl.nodestore
|
||||
test.shamap > xrpl.protocol
|
||||
test.shamap > xrpl.shamap
|
||||
test.unit_test > xrpl.basics
|
||||
test.unit_test > xrpl.protocol
|
||||
tests.libxrpl > xrpl.basics
|
||||
@@ -205,6 +195,7 @@ tests.libxrpl > xrpl.net
|
||||
tests.libxrpl > xrpl.nodestore
|
||||
tests.libxrpl > xrpl.protocol
|
||||
tests.libxrpl > xrpl.protocol_autogen
|
||||
tests.libxrpl > xrpl.resource
|
||||
tests.libxrpl > xrpl.server
|
||||
tests.libxrpl > xrpl.shamap
|
||||
tests.libxrpl > xrpl.tx
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
@@ -233,4 +234,11 @@ makeSlice(std::basic_string<char, Traits, Alloc> const& s)
|
||||
return Slice(s.data(), s.size());
|
||||
}
|
||||
|
||||
template <class Traits>
|
||||
Slice
|
||||
makeSlice(std::basic_string_view<char, Traits> s)
|
||||
{
|
||||
return Slice(s.data(), s.size());
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
#include <xrpl/basics/IntrusivePointer.ipp>
|
||||
#include <xrpl/basics/Log.h> // IWYU pragma: keep
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -604,39 +601,8 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
std::vector<key_type> v;
|
||||
|
||||
{
|
||||
// Keep track of how many iterations are needed. Exit the loop if the number of retries gets
|
||||
// absurd. (Note that if this somehow ever happens, one more allocation will be done under
|
||||
// lock, which is undesirable, but really should be almost impossible.)
|
||||
std::size_t allocationIterations = 0;
|
||||
std::unique_lock lock(mutex_);
|
||||
for (auto size = cache_.size(); v.capacity() < size && allocationIterations < 20;
|
||||
size = cache_.size())
|
||||
{
|
||||
ScopeUnlock const unlock(lock);
|
||||
// Allocate the current size plus a little extra, in case the cache grows while
|
||||
// allocating. Each time another allocation is needed, the extra also gets bigger until
|
||||
// it ultimately doubles the size + 1.
|
||||
size += (size >> (4 - std::min(allocationIterations, std::size_t{4}))) + 1;
|
||||
v.reserve(size);
|
||||
++allocationIterations;
|
||||
}
|
||||
// In a normal operating environment, because of the padding added to size before
|
||||
// allocating, even 2 iterations is going to be very rare. If 3 or more are ever needed,
|
||||
// that's unusual enough that I want to know about it. Don't ask me to change it without
|
||||
// empirical data. - Ed H.
|
||||
XRPL_ASSERT(
|
||||
allocationIterations < 3,
|
||||
"xrpl::TaggedCache::getKeys(): limited allocation iterations");
|
||||
if (v.capacity() < cache_.size())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::TaggedCache::getKeys(): failed to allocate sufficient capacity");
|
||||
v.reserve(cache_.size());
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
XRPL_ASSERT(lock.owns_lock(), "xrpl::TaggedCache::getKeys(): owns lock");
|
||||
XRPL_ASSERT(
|
||||
v.capacity() >= cache_.size(), "xrpl::TaggedCache::getKeys(): sufficient capacity");
|
||||
std::scoped_lock const lock(mutex_);
|
||||
v.reserve(cache_.size());
|
||||
for (auto const& _ : cache_)
|
||||
v.push_back(_.first);
|
||||
}
|
||||
|
||||
@@ -246,7 +246,15 @@ message TMGetObjectByHash {
|
||||
|
||||
message TMLedgerNode {
|
||||
required bytes nodedata = 1;
|
||||
optional bytes nodeid = 2; // missing for ledger base data
|
||||
|
||||
// Used when protocol version <2.3. Not set for ledger base data.
|
||||
optional bytes nodeid = 2;
|
||||
|
||||
// Used when protocol version >=2.3. Neither value is set for ledger base data.
|
||||
oneof reference {
|
||||
bytes id = 3; // Set for inner nodes.
|
||||
uint32 depth = 4; // Set for leaf nodes.
|
||||
}
|
||||
}
|
||||
|
||||
enum TMLedgerInfoType {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/IntrusivePointer.h>
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
@@ -88,6 +87,17 @@ enum class SHAMapState {
|
||||
|
||||
See https://en.wikipedia.org/wiki/Merkle_tree
|
||||
*/
|
||||
|
||||
/** Holds a SHAMap node's identity, serialized data, and leaf status.
|
||||
Used by getNodeFat to return node data for peer synchronization.
|
||||
*/
|
||||
struct SHAMapNodeData
|
||||
{
|
||||
SHAMapNodeID nodeID;
|
||||
bool isLeaf;
|
||||
Blob data; // Placed last, so `isLeaf` can fit into the alignment padding of `nodeID`.
|
||||
};
|
||||
|
||||
class SHAMap
|
||||
{
|
||||
private:
|
||||
@@ -265,10 +275,10 @@ public:
|
||||
std::vector<std::pair<SHAMapNodeID, uint256>>
|
||||
getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter);
|
||||
|
||||
bool
|
||||
[[nodiscard]] bool
|
||||
getNodeFat(
|
||||
SHAMapNodeID const& wanted,
|
||||
std::vector<std::pair<SHAMapNodeID, Blob>>& data,
|
||||
std::vector<SHAMapNodeData>& data,
|
||||
bool fatLeaves,
|
||||
std::uint32_t depth) const;
|
||||
|
||||
@@ -295,10 +305,43 @@ public:
|
||||
void
|
||||
serializeRoot(Serializer& s) const;
|
||||
|
||||
/** Add a root node to the SHAMap during synchronization.
|
||||
*
|
||||
* This function is used when receiving the root node of a SHAMap from a peer during ledger
|
||||
* synchronization. The node must already have been deserialized.
|
||||
*
|
||||
* @param hash The expected hash of the root node.
|
||||
* @param rootNode A deserialized root node to add.
|
||||
* @param filter Optional sync filter to track received nodes.
|
||||
* @return Status indicating whether the node was useful, duplicate, or invalid.
|
||||
*
|
||||
* @note This function expects the rootNode to be a valid, deserialized SHAMapTreeNode. The
|
||||
* caller is responsible for deserialization and basic validation before calling this
|
||||
* function.
|
||||
*/
|
||||
SHAMapAddNode
|
||||
addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter);
|
||||
addRootNode(SHAMapHash const& hash, SHAMapTreeNodePtr rootNode, SHAMapSyncFilter const* filter);
|
||||
|
||||
/** Add a known node at a specific position in the SHAMap during synchronization.
|
||||
*
|
||||
* This function is used when receiving nodes from peers during ledger synchronization. The node
|
||||
* is inserted at the position specified by nodeID. The node must already have been
|
||||
* deserialized.
|
||||
*
|
||||
* @param nodeID The position in the tree where this node belongs.
|
||||
* @param treeNode A deserialized tree node to add.
|
||||
* @param filter Optional sync filter to track received nodes.
|
||||
* @return Status indicating whether the node was useful, duplicate, or invalid.
|
||||
*
|
||||
* @note This function expects the treeNode to be a valid, deserialized SHAMapTreeNode. The
|
||||
* caller is responsible for deserialization and basic validation before calling this
|
||||
* function. This also means that the nodeID must be consistent with the node's content.
|
||||
*/
|
||||
SHAMapAddNode
|
||||
addKnownNode(SHAMapNodeID const& nodeID, Slice const& rawNode, SHAMapSyncFilter const* filter);
|
||||
addKnownNode(
|
||||
SHAMapNodeID const& nodeID,
|
||||
SHAMapTreeNodePtr treeNode,
|
||||
SHAMapSyncFilter const* filter);
|
||||
|
||||
// status functions
|
||||
void
|
||||
|
||||
@@ -129,7 +129,8 @@ selectBranch(SHAMapNodeID const& id, uint256 const& hash)
|
||||
SHAMapNodeID
|
||||
SHAMapNodeID::createID(int depth, uint256 const& key)
|
||||
{
|
||||
XRPL_ASSERT((depth >= 0) && (depth < 65), "xrpl::SHAMapNodeID::createID : valid branch input");
|
||||
XRPL_ASSERT(
|
||||
depth >= 0 && depth <= SHAMap::kLeafDepth, "xrpl::SHAMapNodeID::createID : valid depth");
|
||||
return SHAMapNodeID(depth, key & depthMask(depth));
|
||||
}
|
||||
|
||||
|
||||
@@ -413,7 +413,7 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
|
||||
bool
|
||||
SHAMap::getNodeFat(
|
||||
SHAMapNodeID const& wanted,
|
||||
std::vector<std::pair<SHAMapNodeID, Blob>>& data,
|
||||
std::vector<SHAMapNodeData>& data,
|
||||
bool fatLeaves,
|
||||
std::uint32_t depth) const
|
||||
{
|
||||
@@ -459,7 +459,7 @@ SHAMap::getNodeFat(
|
||||
// Add this node to the reply
|
||||
s.erase();
|
||||
node->serializeForWire(s);
|
||||
data.emplace_back(nodeID, s.getData());
|
||||
data.emplace_back(nodeID, node->isLeaf(), s.getData());
|
||||
|
||||
if (node->isInner())
|
||||
{
|
||||
@@ -489,7 +489,7 @@ SHAMap::getNodeFat(
|
||||
// Just include this node
|
||||
s.erase();
|
||||
childNode->serializeForWire(s);
|
||||
data.emplace_back(childID, s.getData());
|
||||
data.emplace_back(childID, childNode->isLeaf(), s.getData());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -507,25 +507,32 @@ SHAMap::serializeRoot(Serializer& s) const
|
||||
}
|
||||
|
||||
SHAMapAddNode
|
||||
SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter)
|
||||
SHAMap::addRootNode(
|
||||
SHAMapHash const& hash,
|
||||
SHAMapTreeNodePtr rootNode,
|
||||
SHAMapSyncFilter const* filter)
|
||||
{
|
||||
XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid");
|
||||
XRPL_ASSERT(rootNode, "xrpl::SHAMap::addRootNode : non-null root node");
|
||||
|
||||
// we already have a root_ node
|
||||
if (root_->getHash().isNonZero())
|
||||
{
|
||||
JLOG(journal_.trace()) << "got root node, already have one";
|
||||
XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash input");
|
||||
JLOG(journal_.trace()) << "Got root node, already have one";
|
||||
XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash");
|
||||
return SHAMapAddNode::duplicate();
|
||||
}
|
||||
|
||||
XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid");
|
||||
auto node = SHAMapTreeNode::makeFromWire(rootNode);
|
||||
if (!node || node->getHash() != hash)
|
||||
if (rootNode->getHash() != hash)
|
||||
{
|
||||
JLOG(journal_.warn()) << "Corrupt node received";
|
||||
return SHAMapAddNode::invalid();
|
||||
}
|
||||
|
||||
if (backed_)
|
||||
canonicalize(hash, node);
|
||||
canonicalize(hash, rootNode);
|
||||
|
||||
root_ = node;
|
||||
root_ = std::move(rootNode);
|
||||
|
||||
if (root_->isLeaf())
|
||||
clearSynching();
|
||||
@@ -542,9 +549,20 @@ SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFil
|
||||
}
|
||||
|
||||
SHAMapAddNode
|
||||
SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncFilter const* filter)
|
||||
SHAMap::addKnownNode(
|
||||
SHAMapNodeID const& nodeID,
|
||||
SHAMapTreeNodePtr treeNode,
|
||||
SHAMapSyncFilter const* filter)
|
||||
{
|
||||
XRPL_ASSERT(!node.isRoot(), "xrpl::SHAMap::addKnownNode : valid node input");
|
||||
XRPL_ASSERT(!nodeID.isRoot(), "xrpl::SHAMap::addKnownNode : valid node");
|
||||
XRPL_ASSERT(treeNode, "xrpl::SHAMap::addKnownNode : non-null tree node");
|
||||
XRPL_ASSERT(
|
||||
!treeNode->isLeaf() ||
|
||||
SHAMapNodeID::createID(
|
||||
nodeID.getDepth(),
|
||||
safeDowncast<SHAMapLeafNode const*>(treeNode.get())->peekItem()->key())
|
||||
.getNodeID() == nodeID.getNodeID(),
|
||||
"xrpl::SHAMap::addKnownNode : leaf position consistent with node ID");
|
||||
|
||||
if (!isSynching())
|
||||
{
|
||||
@@ -558,14 +576,14 @@ SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncF
|
||||
|
||||
while (currNode->isInner() &&
|
||||
!safeDowncast<SHAMapInnerNode*>(currNode)->isFullBelow(generation) &&
|
||||
(currNodeID.getDepth() < node.getDepth()))
|
||||
(currNodeID.getDepth() < nodeID.getDepth()))
|
||||
{
|
||||
int const branch = selectBranch(currNodeID, node.getNodeID());
|
||||
int const branch = selectBranch(currNodeID, nodeID.getNodeID());
|
||||
XRPL_ASSERT(branch >= 0, "xrpl::SHAMap::addKnownNode : valid branch");
|
||||
auto inner = safeDowncast<SHAMapInnerNode*>(currNode);
|
||||
if (inner->isEmptyBranch(branch))
|
||||
{
|
||||
JLOG(journal_.warn()) << "Add known node for empty branch" << node;
|
||||
JLOG(journal_.warn()) << "Add known node for empty branch" << nodeID;
|
||||
return SHAMapAddNode::invalid();
|
||||
}
|
||||
|
||||
@@ -581,67 +599,44 @@ SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncF
|
||||
if (currNode != nullptr)
|
||||
continue;
|
||||
|
||||
auto newNode = SHAMapTreeNode::makeFromWire(rawNode);
|
||||
|
||||
if (!newNode || childHash != newNode->getHash())
|
||||
if (childHash != treeNode->getHash())
|
||||
{
|
||||
JLOG(journal_.warn()) << "Corrupt node received";
|
||||
return SHAMapAddNode::invalid();
|
||||
}
|
||||
|
||||
// In rare cases, a node can still be corrupt even after hash
|
||||
// validation. For leaf nodes, we perform an additional check to
|
||||
// ensure the node's position in the tree is consistent with its
|
||||
// content to prevent inconsistencies that could
|
||||
// propagate further down the line.
|
||||
if (newNode->isLeaf())
|
||||
{
|
||||
auto const& actualKey =
|
||||
safeDowncast<SHAMapLeafNode const*>(newNode.get())->peekItem()->key();
|
||||
|
||||
// Validate that this leaf belongs at the target position
|
||||
auto const expectedNodeID = SHAMapNodeID::createID(node.getDepth(), actualKey);
|
||||
if (expectedNodeID.getNodeID() != node.getNodeID())
|
||||
{
|
||||
JLOG(journal_.debug())
|
||||
<< "Leaf node position mismatch: "
|
||||
<< "expected=" << expectedNodeID.getNodeID() << ", actual=" << node.getNodeID();
|
||||
return SHAMapAddNode::invalid();
|
||||
}
|
||||
}
|
||||
|
||||
// Inner nodes must be at a level strictly less than 64
|
||||
// but leaf nodes (while notionally at level 64) can be
|
||||
// at any depth up to and including 64:
|
||||
if ((currNodeID.getDepth() > kLeafDepth) ||
|
||||
(newNode->isInner() && currNodeID.getDepth() == kLeafDepth))
|
||||
(treeNode->isInner() && currNodeID.getDepth() == kLeafDepth))
|
||||
{
|
||||
// Map is provably invalid
|
||||
state_ = SHAMapState::Invalid;
|
||||
return SHAMapAddNode::useful();
|
||||
}
|
||||
|
||||
if (currNodeID != node)
|
||||
if (currNodeID != nodeID)
|
||||
{
|
||||
// Either this node is broken or we didn't request it (yet)
|
||||
JLOG(journal_.warn()) << "unable to hook node " << node;
|
||||
JLOG(journal_.warn()) << "unable to hook node " << nodeID;
|
||||
JLOG(journal_.info()) << " stuck at " << currNodeID;
|
||||
JLOG(journal_.info()) << "got depth=" << node.getDepth()
|
||||
JLOG(journal_.info()) << "got depth=" << nodeID.getDepth()
|
||||
<< ", walked to= " << currNodeID.getDepth();
|
||||
return SHAMapAddNode::useful();
|
||||
}
|
||||
|
||||
if (backed_)
|
||||
canonicalize(childHash, newNode);
|
||||
canonicalize(childHash, treeNode);
|
||||
|
||||
newNode = prevNode->canonicalizeChild(branch, std::move(newNode));
|
||||
treeNode = prevNode->canonicalizeChild(branch, std::move(treeNode));
|
||||
|
||||
if (filter != nullptr)
|
||||
{
|
||||
Serializer s;
|
||||
newNode->serializeWithPrefix(s);
|
||||
treeNode->serializeWithPrefix(s);
|
||||
filter->gotNode(
|
||||
false, childHash, ledgerSeq_, std::move(s.modData()), newNode->getType());
|
||||
false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType());
|
||||
}
|
||||
|
||||
return SHAMapAddNode::useful();
|
||||
|
||||
260
src/test/app/LedgerNodeHelpers_test.cpp
Normal file
260
src/test/app/LedgerNodeHelpers_test.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include <xrpld/app/ledger/LedgerNodeHelpers.h>
|
||||
|
||||
#include <xrpl/basics/IntrusivePointer.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapAccountStateLeafNode.h>
|
||||
#include <xrpl/shamap/SHAMapInnerNode.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
class LedgerNodeHelpers_test : public beast::unit_test::Suite
|
||||
{
|
||||
static boost::intrusive_ptr<SHAMapItem>
|
||||
makeTestItem(std::uint32_t seed)
|
||||
{
|
||||
Serializer s;
|
||||
s.add32(seed);
|
||||
s.add32(seed + 1);
|
||||
s.add32(seed + 2);
|
||||
return makeShamapitem(s.getSHA512Half(), s.slice());
|
||||
}
|
||||
|
||||
static std::string
|
||||
serializeNode(SHAMapTreeNodePtr const& node)
|
||||
{
|
||||
Serializer s;
|
||||
node->serializeForWire(s);
|
||||
auto const slice = s.slice();
|
||||
return std::string(slice.begin(), slice.end());
|
||||
}
|
||||
|
||||
void
|
||||
testGetTreeNode()
|
||||
{
|
||||
testcase("getTreeNode");
|
||||
|
||||
// Valid: inner node. It must have at least one child for `serializeNode` to work.
|
||||
{
|
||||
auto const innerNode = intr_ptr::makeShared<SHAMapInnerNode>(1);
|
||||
auto const childNode = intr_ptr::makeShared<SHAMapInnerNode>(1);
|
||||
innerNode->setChild(0, childNode);
|
||||
auto const innerData = serializeNode(innerNode);
|
||||
auto const result = getTreeNode(innerData);
|
||||
BEAST_EXPECT(result && result->isInner());
|
||||
}
|
||||
|
||||
// Valid: leaf node.
|
||||
{
|
||||
auto const leafItem = makeTestItem(12345);
|
||||
auto const leafNode = intr_ptr::makeShared<SHAMapAccountStateLeafNode>(leafItem, 1);
|
||||
auto const leafData = serializeNode(leafNode);
|
||||
auto const result = getTreeNode(leafData);
|
||||
BEAST_EXPECT(result && result->isLeaf());
|
||||
}
|
||||
|
||||
// Invalid: empty data.
|
||||
{
|
||||
auto const result = getTreeNode("");
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
|
||||
// Invalid: garbage data.
|
||||
{
|
||||
auto const result = getTreeNode("invalid");
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
|
||||
// Invalid: truncated data.
|
||||
{
|
||||
auto const leafItem = makeTestItem(54321);
|
||||
auto const leafNode = intr_ptr::makeShared<SHAMapAccountStateLeafNode>(leafItem, 1);
|
||||
// Truncate the data to trigger an exception in SHAMapTreeNode::makeAccountState when
|
||||
// the data is used to deserialize the node.
|
||||
uint256 const tag;
|
||||
auto const leafData = serializeNode(leafNode).substr(0, tag.kBytes - 1);
|
||||
auto const result = getTreeNode(leafData);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGetSHAMapNodeID()
|
||||
{
|
||||
testcase("getSHAMapNodeID");
|
||||
|
||||
{
|
||||
// Tests using inner nodes at various depths.
|
||||
auto const innerNode = intr_ptr::makeShared<SHAMapInnerNode>(1);
|
||||
auto const childNode = intr_ptr::makeShared<SHAMapInnerNode>(1);
|
||||
innerNode->setChild(0, childNode);
|
||||
auto const innerData = serializeNode(innerNode);
|
||||
|
||||
// Valid: legacy `nodeid` field at arbitrary depth.
|
||||
{
|
||||
auto const innerDepth = 3;
|
||||
auto const innerID = SHAMapNodeID::createID(innerDepth, uint256{});
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(innerData);
|
||||
ledgerNode.set_nodeid(innerID.getRawString());
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *innerNode);
|
||||
BEAST_EXPECT(result == innerID);
|
||||
}
|
||||
|
||||
// Valid: new `id` field at minimum depth.
|
||||
{
|
||||
auto const innerDepth = 0;
|
||||
auto const innerID = SHAMapNodeID::createID(innerDepth, uint256{});
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(innerData);
|
||||
ledgerNode.set_id(innerID.getRawString());
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *innerNode);
|
||||
BEAST_EXPECT(result == innerID);
|
||||
}
|
||||
|
||||
// Invalid: new `depth` field should not be used for inner nodes.
|
||||
{
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(innerData);
|
||||
ledgerNode.set_depth(10);
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *innerNode);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
|
||||
// Invalid: both legacy `nodeid` and new `id` fields set for an inner node.
|
||||
{
|
||||
auto const innerDepth = 9;
|
||||
auto const innerID = SHAMapNodeID::createID(innerDepth, uint256{});
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(innerData);
|
||||
ledgerNode.set_nodeid(innerID.getRawString());
|
||||
ledgerNode.set_id(innerID.getRawString());
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *innerNode);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Tests using leaf nodes at various depths.
|
||||
auto const leafItem = makeTestItem(12345);
|
||||
auto const leafNode = intr_ptr::makeShared<SHAMapAccountStateLeafNode>(leafItem, 1);
|
||||
auto const leafData = serializeNode(leafNode);
|
||||
auto const leafKey = leafItem->key();
|
||||
|
||||
// Valid: legacy `nodeid` field at arbitrary depth.
|
||||
{
|
||||
auto const kLeafDepth = 5;
|
||||
auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey);
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(leafData);
|
||||
ledgerNode.set_nodeid(leafID.getRawString());
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *leafNode);
|
||||
BEAST_EXPECT(result == leafID);
|
||||
}
|
||||
|
||||
// Invalid: new `id` field should not be used for leaf nodes.
|
||||
{
|
||||
auto const kLeafDepth = 5;
|
||||
auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey);
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(leafData);
|
||||
ledgerNode.set_id(leafID.getRawString());
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *leafNode);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
|
||||
// Valid: new `depth` field at minimum depth.
|
||||
{
|
||||
auto const kLeafDepth = 0;
|
||||
auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey);
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(leafData);
|
||||
ledgerNode.set_depth(kLeafDepth);
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *leafNode);
|
||||
BEAST_EXPECT(result == leafID);
|
||||
}
|
||||
|
||||
// Valid: new `depth` field at arbitrary depth between minimum and maximum.
|
||||
{
|
||||
auto const kLeafDepth = 10;
|
||||
auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey);
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(leafData);
|
||||
ledgerNode.set_depth(kLeafDepth);
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *leafNode);
|
||||
BEAST_EXPECT(result == leafID);
|
||||
}
|
||||
|
||||
// Valid: new `depth` field at maximum depth.
|
||||
// Note that we do not test a depth greater than the maximum depth, because the proto
|
||||
// message is assumed to have been validated by the time the getSHAMapNodeID function is
|
||||
// called.
|
||||
{
|
||||
auto const kLeafDepth = SHAMap::kLeafDepth;
|
||||
auto const leafID = SHAMapNodeID::createID(kLeafDepth, leafKey);
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(leafData);
|
||||
ledgerNode.set_depth(kLeafDepth);
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *leafNode);
|
||||
BEAST_EXPECT(result == leafID);
|
||||
}
|
||||
|
||||
// Invalid: legacy `nodeid` field where the node ID is inconsistent with the key.
|
||||
{
|
||||
auto const otherItem = makeTestItem(54321);
|
||||
auto const otherNode =
|
||||
intr_ptr::makeShared<SHAMapAccountStateLeafNode>(otherItem, 1);
|
||||
auto const otherData = serializeNode(otherNode);
|
||||
auto const otherKey = otherItem->key();
|
||||
auto const otherDepth = 1;
|
||||
auto const otherID = SHAMapNodeID::createID(otherDepth, otherKey);
|
||||
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata(otherData);
|
||||
ledgerNode.set_nodeid(otherID.getRawString());
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *leafNode);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid: no field set.
|
||||
{
|
||||
auto const innerNode = intr_ptr::makeShared<SHAMapInnerNode>(1);
|
||||
protocol::TMLedgerNode ledgerNode;
|
||||
ledgerNode.set_nodedata("test_data");
|
||||
auto const result = getSHAMapNodeID(ledgerNode, *innerNode);
|
||||
BEAST_EXPECT(!result);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testGetTreeNode();
|
||||
testGetSHAMapNodeID();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LedgerNodeHelpers, app, xrpl);
|
||||
|
||||
} // namespace xrpl::tests
|
||||
@@ -1,268 +0,0 @@
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
struct Buffer_test : beast::unit_test::Suite
|
||||
{
|
||||
static bool
|
||||
sane(Buffer const& b)
|
||||
{
|
||||
if (b.empty())
|
||||
return b.data() == nullptr;
|
||||
|
||||
return b.data() != nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
std::uint8_t const data[] = {0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23,
|
||||
0x71, 0x6d, 0x2a, 0x18, 0xb4, 0x70, 0xcb, 0xf5,
|
||||
0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c, 0xf0, 0x2c,
|
||||
0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3};
|
||||
|
||||
Buffer const b0;
|
||||
BEAST_EXPECT(sane(b0));
|
||||
BEAST_EXPECT(b0.empty());
|
||||
|
||||
Buffer b1{0};
|
||||
BEAST_EXPECT(sane(b1));
|
||||
BEAST_EXPECT(b1.empty());
|
||||
std::memcpy(b1.alloc(16), data, 16);
|
||||
BEAST_EXPECT(sane(b1));
|
||||
BEAST_EXPECT(!b1.empty());
|
||||
BEAST_EXPECT(b1.size() == 16);
|
||||
|
||||
Buffer b2{b1.size()};
|
||||
BEAST_EXPECT(sane(b2));
|
||||
BEAST_EXPECT(!b2.empty());
|
||||
BEAST_EXPECT(b2.size() == b1.size());
|
||||
std::memcpy(b2.data(), data + 16, 16);
|
||||
|
||||
Buffer b3{data, sizeof(data)};
|
||||
BEAST_EXPECT(sane(b3));
|
||||
BEAST_EXPECT(!b3.empty());
|
||||
BEAST_EXPECT(b3.size() == sizeof(data));
|
||||
BEAST_EXPECT(std::memcmp(b3.data(), data, b3.size()) == 0);
|
||||
|
||||
// Check equality and inequality comparisons
|
||||
BEAST_EXPECT(b0 == b0);
|
||||
BEAST_EXPECT(b0 != b1);
|
||||
BEAST_EXPECT(b1 == b1);
|
||||
BEAST_EXPECT(b1 != b2);
|
||||
BEAST_EXPECT(b2 != b3);
|
||||
|
||||
// Check copy constructors and copy assignments:
|
||||
{
|
||||
testcase("Copy Construction / Assignment");
|
||||
|
||||
Buffer x{b0};
|
||||
BEAST_EXPECT(x == b0);
|
||||
BEAST_EXPECT(sane(x));
|
||||
Buffer y{b1};
|
||||
BEAST_EXPECT(y == b1);
|
||||
BEAST_EXPECT(sane(y));
|
||||
x = b2;
|
||||
BEAST_EXPECT(x == b2);
|
||||
BEAST_EXPECT(sane(x));
|
||||
x = y;
|
||||
BEAST_EXPECT(x == y);
|
||||
BEAST_EXPECT(sane(x));
|
||||
y = b3;
|
||||
BEAST_EXPECT(y == b3);
|
||||
BEAST_EXPECT(sane(y));
|
||||
x = b0;
|
||||
BEAST_EXPECT(x == b0);
|
||||
BEAST_EXPECT(sane(x));
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
|
||||
#endif
|
||||
|
||||
x = x;
|
||||
BEAST_EXPECT(x == b0);
|
||||
BEAST_EXPECT(sane(x));
|
||||
y = y;
|
||||
BEAST_EXPECT(y == b3);
|
||||
BEAST_EXPECT(sane(y));
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
// Check move constructor & move assignments:
|
||||
{
|
||||
testcase("Move Construction / Assignment");
|
||||
|
||||
static_assert(std::is_nothrow_move_constructible_v<Buffer>);
|
||||
static_assert(std::is_nothrow_move_assignable_v<Buffer>);
|
||||
|
||||
{ // Move-construct from empty buf
|
||||
Buffer x;
|
||||
Buffer const y{std::move(x)};
|
||||
BEAST_EXPECT(sane(x)); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(x.empty()); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(sane(y));
|
||||
BEAST_EXPECT(y.empty());
|
||||
BEAST_EXPECT(x == y); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move-construct from non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer const y{std::move(x)};
|
||||
BEAST_EXPECT(sane(x)); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(x.empty()); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(sane(y));
|
||||
BEAST_EXPECT(y == b1);
|
||||
}
|
||||
|
||||
{ // Move assign empty buf to empty buf
|
||||
Buffer x;
|
||||
Buffer y;
|
||||
|
||||
x = std::move(y);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x.empty());
|
||||
BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move assign non-empty buf to empty buf
|
||||
Buffer x;
|
||||
Buffer y{b1};
|
||||
|
||||
x = std::move(y);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x == b1);
|
||||
BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move assign empty buf to non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer y;
|
||||
|
||||
x = std::move(y);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x.empty());
|
||||
BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move assign non-empty buf to non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer y{b2};
|
||||
Buffer z{b3};
|
||||
|
||||
x = std::move(y);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(!x.empty());
|
||||
BEAST_EXPECT(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
|
||||
x = std::move(z);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(!x.empty());
|
||||
BEAST_EXPECT(sane(z)); // NOLINT(bugprone-use-after-move)
|
||||
BEAST_EXPECT(z.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Slice Conversion / Construction / Assignment");
|
||||
|
||||
Buffer w{static_cast<Slice>(b0)};
|
||||
BEAST_EXPECT(sane(w));
|
||||
BEAST_EXPECT(w == b0);
|
||||
|
||||
Buffer x{static_cast<Slice>(b1)};
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x == b1);
|
||||
|
||||
Buffer y{static_cast<Slice>(b2)};
|
||||
BEAST_EXPECT(sane(y));
|
||||
BEAST_EXPECT(y == b2);
|
||||
|
||||
Buffer z{static_cast<Slice>(b3)};
|
||||
BEAST_EXPECT(sane(z));
|
||||
BEAST_EXPECT(z == b3);
|
||||
|
||||
// Assign empty slice to empty buffer
|
||||
w = static_cast<Slice>(b0);
|
||||
BEAST_EXPECT(sane(w));
|
||||
BEAST_EXPECT(w == b0);
|
||||
|
||||
// Assign non-empty slice to empty buffer
|
||||
w = static_cast<Slice>(b1);
|
||||
BEAST_EXPECT(sane(w));
|
||||
BEAST_EXPECT(w == b1);
|
||||
|
||||
// Assign non-empty slice to non-empty buffer
|
||||
x = static_cast<Slice>(b2);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x == b2);
|
||||
|
||||
// Assign non-empty slice to non-empty buffer
|
||||
y = static_cast<Slice>(z);
|
||||
BEAST_EXPECT(sane(y));
|
||||
BEAST_EXPECT(y == z);
|
||||
|
||||
// Assign empty slice to non-empty buffer:
|
||||
z = static_cast<Slice>(b0);
|
||||
BEAST_EXPECT(sane(z));
|
||||
BEAST_EXPECT(z == b0);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Allocation, Deallocation and Clearing");
|
||||
|
||||
auto test = [this](Buffer const& b, std::size_t i) {
|
||||
Buffer x{b};
|
||||
|
||||
// Try to allocate some number of bytes, possibly
|
||||
// zero (which means clear) and sanity check
|
||||
x(i);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x.size() == i);
|
||||
BEAST_EXPECT((x.data() == nullptr) == (i == 0));
|
||||
|
||||
// Try to allocate some more data (always non-zero)
|
||||
x(i + 1);
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x.size() == i + 1);
|
||||
BEAST_EXPECT(x.data() != nullptr);
|
||||
|
||||
// Try to clear:
|
||||
x.clear();
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x.empty());
|
||||
BEAST_EXPECT(x.data() == nullptr);
|
||||
|
||||
// Try to clear again:
|
||||
x.clear();
|
||||
BEAST_EXPECT(sane(x));
|
||||
BEAST_EXPECT(x.empty());
|
||||
BEAST_EXPECT(x.data() == nullptr);
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < 16; ++i)
|
||||
{
|
||||
test(b0, i);
|
||||
test(b1, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Buffer, basics, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,64 +0,0 @@
|
||||
#include <test/unit_test/FileDirGuard.h>
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/FileUtilities.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <boost/system/detail/errc.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class FileUtilities_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testGetFileContents()
|
||||
{
|
||||
using namespace xrpl::detail;
|
||||
using namespace boost::system;
|
||||
|
||||
static constexpr char const* kExpectedContents =
|
||||
"This file is very short. That's all we need.";
|
||||
|
||||
FileDirGuard const file(
|
||||
*this, "test_file", "test.txt", "This is temporary text that should get overwritten");
|
||||
|
||||
error_code ec;
|
||||
auto const path = file.file();
|
||||
|
||||
writeFileContents(ec, path, kExpectedContents);
|
||||
BEAST_EXPECT(!ec);
|
||||
|
||||
{
|
||||
// Test with no max
|
||||
auto const good = getFileContents(ec, path);
|
||||
BEAST_EXPECT(!ec);
|
||||
BEAST_EXPECT(good == kExpectedContents);
|
||||
}
|
||||
|
||||
{
|
||||
// Test with large max
|
||||
auto const good = getFileContents(ec, path, kilobytes(1));
|
||||
BEAST_EXPECT(!ec);
|
||||
BEAST_EXPECT(good == kExpectedContents);
|
||||
}
|
||||
|
||||
{
|
||||
// Test with small max
|
||||
auto const bad = getFileContents(ec, path, 16);
|
||||
BEAST_EXPECT(ec && ec.value() == boost::system::errc::file_too_large);
|
||||
BEAST_EXPECT(bad.empty());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testGetFileContents();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(FileUtilities, basics, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,276 +0,0 @@
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class IOUAmount_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testZero()
|
||||
{
|
||||
testcase("zero");
|
||||
|
||||
IOUAmount const z(0, 0);
|
||||
|
||||
BEAST_EXPECT(z.mantissa() == 0);
|
||||
BEAST_EXPECT(z.exponent() == -100);
|
||||
BEAST_EXPECT(!z);
|
||||
BEAST_EXPECT(z.signum() == 0);
|
||||
BEAST_EXPECT(z == beast::kZero);
|
||||
|
||||
BEAST_EXPECT((z + z) == z);
|
||||
BEAST_EXPECT((z - z) == z);
|
||||
BEAST_EXPECT(z == -z);
|
||||
|
||||
IOUAmount const zz(beast::kZero);
|
||||
BEAST_EXPECT(z == zz);
|
||||
|
||||
// https://github.com/XRPLF/rippled/issues/5170
|
||||
IOUAmount const zzz{};
|
||||
BEAST_EXPECT(zzz == beast::kZero);
|
||||
// BEAST_EXPECT(zzz == zz);
|
||||
}
|
||||
|
||||
void
|
||||
testSigNum()
|
||||
{
|
||||
testcase("signum");
|
||||
|
||||
IOUAmount const neg(-1, 0);
|
||||
BEAST_EXPECT(neg.signum() < 0);
|
||||
|
||||
IOUAmount const zer(0, 0);
|
||||
BEAST_EXPECT(zer.signum() == 0);
|
||||
|
||||
IOUAmount const pos(1, 0);
|
||||
BEAST_EXPECT(pos.signum() > 0);
|
||||
}
|
||||
|
||||
void
|
||||
testBeastZero()
|
||||
{
|
||||
testcase("beast::Zero Comparisons");
|
||||
|
||||
using beast::kZero;
|
||||
|
||||
{
|
||||
IOUAmount const z(kZero);
|
||||
BEAST_EXPECT(z == kZero);
|
||||
BEAST_EXPECT(z >= kZero);
|
||||
BEAST_EXPECT(z <= kZero);
|
||||
unexpected(z != kZero);
|
||||
unexpected(z > kZero);
|
||||
unexpected(z < kZero);
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const neg(-2, 0);
|
||||
BEAST_EXPECT(neg < kZero);
|
||||
BEAST_EXPECT(neg <= kZero);
|
||||
BEAST_EXPECT(neg != kZero);
|
||||
unexpected(neg == kZero);
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const pos(2, 0);
|
||||
BEAST_EXPECT(pos > kZero);
|
||||
BEAST_EXPECT(pos >= kZero);
|
||||
BEAST_EXPECT(pos != kZero);
|
||||
unexpected(pos == kZero);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testComparisons()
|
||||
{
|
||||
testcase("IOU Comparisons");
|
||||
|
||||
IOUAmount const n(-2, 0);
|
||||
IOUAmount const z(0, 0);
|
||||
IOUAmount const p(2, 0);
|
||||
|
||||
BEAST_EXPECT(z == z);
|
||||
BEAST_EXPECT(z >= z);
|
||||
BEAST_EXPECT(z <= z);
|
||||
BEAST_EXPECT(z == -z);
|
||||
// NOLINTBEGIN(misc-redundant-expression)
|
||||
unexpected(z > z);
|
||||
unexpected(z < z);
|
||||
unexpected(z != z);
|
||||
// NOLINTEND(misc-redundant-expression)
|
||||
unexpected(z != -z);
|
||||
|
||||
BEAST_EXPECT(n < z);
|
||||
BEAST_EXPECT(n <= z);
|
||||
BEAST_EXPECT(n != z);
|
||||
unexpected(n > z);
|
||||
unexpected(n >= z);
|
||||
unexpected(n == z);
|
||||
|
||||
BEAST_EXPECT(p > z);
|
||||
BEAST_EXPECT(p >= z);
|
||||
BEAST_EXPECT(p != z);
|
||||
unexpected(p < z);
|
||||
unexpected(p <= z);
|
||||
unexpected(p == z);
|
||||
|
||||
BEAST_EXPECT(n < p);
|
||||
BEAST_EXPECT(n <= p);
|
||||
BEAST_EXPECT(n != p);
|
||||
unexpected(n > p);
|
||||
unexpected(n >= p);
|
||||
unexpected(n == p);
|
||||
|
||||
BEAST_EXPECT(p > n);
|
||||
BEAST_EXPECT(p >= n);
|
||||
BEAST_EXPECT(p != n);
|
||||
unexpected(p < n);
|
||||
unexpected(p <= n);
|
||||
unexpected(p == n);
|
||||
|
||||
BEAST_EXPECT(p > -p);
|
||||
BEAST_EXPECT(p >= -p);
|
||||
BEAST_EXPECT(p != -p);
|
||||
|
||||
BEAST_EXPECT(n < -n);
|
||||
BEAST_EXPECT(n <= -n);
|
||||
BEAST_EXPECT(n != -n);
|
||||
}
|
||||
|
||||
void
|
||||
testToString()
|
||||
{
|
||||
testcase("IOU strings");
|
||||
|
||||
auto test = [this](IOUAmount const& n, std::string const& expected) {
|
||||
auto const result = to_string(n);
|
||||
std::stringstream ss;
|
||||
ss << "to_string(" << result << "). Expected: " << expected;
|
||||
BEAST_EXPECTS(result == expected, ss.str());
|
||||
};
|
||||
|
||||
for (auto const mantissaSize : MantissaRange::getAllScales())
|
||||
{
|
||||
NumberMantissaScaleGuard const mg(mantissaSize);
|
||||
|
||||
test(IOUAmount(-2, 0), "-2");
|
||||
test(IOUAmount(0, 0), "0");
|
||||
test(IOUAmount(2, 0), "2");
|
||||
test(IOUAmount(25, -3), "0.025");
|
||||
test(IOUAmount(-25, -3), "-0.025");
|
||||
test(IOUAmount(25, 1), "250");
|
||||
test(IOUAmount(-25, 1), "-250");
|
||||
test(IOUAmount(2, 20), "2e20");
|
||||
test(IOUAmount(-2, -20), "-2e-20");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testMulRatio()
|
||||
{
|
||||
testcase("mulRatio");
|
||||
|
||||
/* The range for the mantissa when normalized */
|
||||
static constexpr std::int64_t kMinMantissa = 1000000000000000ull;
|
||||
static constexpr std::int64_t kMaxMantissa = 9999999999999999ull;
|
||||
// log(2,maxMantissa) ~ 53.15
|
||||
/* The range for the exponent when normalized */
|
||||
static constexpr int kMinExponent = -96;
|
||||
static constexpr int kMaxExponent = 80;
|
||||
constexpr auto kMaxUInt = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
{
|
||||
// multiply by a number that would overflow the mantissa, then
|
||||
// divide by the same number, and check we didn't lose any value
|
||||
IOUAmount const bigMan(kMaxMantissa, 0);
|
||||
BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, false));
|
||||
}
|
||||
{
|
||||
// Similar test as above, but for negative values
|
||||
IOUAmount const bigMan(-kMaxMantissa, 0);
|
||||
BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
BEAST_EXPECT(bigMan == mulRatio(bigMan, kMaxUInt, kMaxUInt, false));
|
||||
}
|
||||
|
||||
{
|
||||
// small amounts
|
||||
IOUAmount const tiny(kMinMantissa, kMinExponent);
|
||||
// Round up should give the smallest allowable number
|
||||
BEAST_EXPECT(tiny == mulRatio(tiny, 1, kMaxUInt, true));
|
||||
BEAST_EXPECT(tiny == mulRatio(tiny, kMaxUInt - 1, kMaxUInt, true));
|
||||
// rounding down should be zero
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tiny, 1, kMaxUInt, false));
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tiny, kMaxUInt - 1, kMaxUInt, false));
|
||||
|
||||
// tiny negative numbers
|
||||
IOUAmount const tinyNeg(-kMinMantissa, kMinExponent);
|
||||
// Round up should give zero
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, 1, kMaxUInt, true));
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, true));
|
||||
// rounding down should be tiny
|
||||
BEAST_EXPECT(tinyNeg == mulRatio(tinyNeg, 1, kMaxUInt, false));
|
||||
BEAST_EXPECT(tinyNeg == mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, false));
|
||||
}
|
||||
|
||||
{ // rounding
|
||||
{
|
||||
IOUAmount const one(1, 0);
|
||||
auto const rup = mulRatio(one, kMaxUInt - 1, kMaxUInt, true);
|
||||
auto const rdown = mulRatio(one, kMaxUInt - 1, kMaxUInt, false);
|
||||
BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1);
|
||||
}
|
||||
{
|
||||
IOUAmount const big(kMaxMantissa, kMaxExponent);
|
||||
auto const rup = mulRatio(big, kMaxUInt - 1, kMaxUInt, true);
|
||||
auto const rdown = mulRatio(big, kMaxUInt - 1, kMaxUInt, false);
|
||||
BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const negOne(-1, 0);
|
||||
auto const rup = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, true);
|
||||
auto const rdown = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, false);
|
||||
BEAST_EXPECT(rup.mantissa() - rdown.mantissa() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// division by zero
|
||||
IOUAmount one(1, 0);
|
||||
except([&] { mulRatio(one, 1, 0, true); });
|
||||
}
|
||||
|
||||
{
|
||||
// overflow
|
||||
IOUAmount big(kMaxMantissa, kMaxExponent);
|
||||
except([&] { mulRatio(big, 2, 0, true); });
|
||||
}
|
||||
} // namespace xrpl
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testZero();
|
||||
testSigNum();
|
||||
testBeastZero();
|
||||
testComparisons();
|
||||
testToString();
|
||||
testMulRatio();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(IOUAmount, basics, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,879 +0,0 @@
|
||||
|
||||
#include <xrpl/basics/IntrusivePointer.h> // IWYU pragma: keep
|
||||
#include <xrpl/basics/IntrusivePointer.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/basics/IntrusiveRefCounts.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <latch>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
/**
|
||||
Experimentally, we discovered that using std::barrier performs extremely
|
||||
poorly (~1 hour vs ~1 minute to run the test suite) in certain macOS
|
||||
environments. To unblock our macOS CI pipeline, we replaced std::barrier with a
|
||||
custom mutex-based barrier (Barrier) that significantly improves performance
|
||||
without compromising correctness. For future reference, if we ever consider
|
||||
reintroducing std::barrier, the following configuration is known to exhibit the
|
||||
problem:
|
||||
|
||||
Model Name: Mac mini
|
||||
Model Identifier: Mac14,3
|
||||
Model Number: Z16K000R4LL/A
|
||||
Chip: Apple M2
|
||||
Total Number of Cores: 8 (4 performance and 4 efficiency)
|
||||
Memory: 24 GB
|
||||
System Firmware Version: 11881.41.5
|
||||
OS Loader Version: 11881.1.1
|
||||
Apple clang version 16.0.0 (clang-1600.0.26.3)
|
||||
Target: arm64-apple-darwin24.0.0
|
||||
Thread model: posix
|
||||
|
||||
*/
|
||||
struct Barrier
|
||||
{
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
int count;
|
||||
int const initial;
|
||||
|
||||
Barrier(int n) : count(n), initial(n)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
arriveAndWait()
|
||||
{
|
||||
std::unique_lock lock(mtx);
|
||||
if (--count == 0)
|
||||
{
|
||||
count = initial;
|
||||
cv.notify_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
cv.wait(lock, [&] { return count == initial; });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
enum class TrackedState : std::uint8_t {
|
||||
Uninitialized,
|
||||
Alive,
|
||||
PartiallyDeletedStarted,
|
||||
PartiallyDeleted,
|
||||
DeletedStarted,
|
||||
Deleted
|
||||
};
|
||||
|
||||
class TIBase : public IntrusiveRefCounts
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t kMaxStates = 128;
|
||||
static std::array<std::atomic<TrackedState>, kMaxStates> state;
|
||||
static std::atomic<int> nextId;
|
||||
static TrackedState
|
||||
getState(int id)
|
||||
{
|
||||
assert(id < state.size());
|
||||
return state[id].load(std::memory_order_acquire);
|
||||
}
|
||||
static void
|
||||
resetStates(bool resetCallback)
|
||||
{
|
||||
for (int i = 0; i < kMaxStates; ++i)
|
||||
{
|
||||
state[i].store(TrackedState::Uninitialized, std::memory_order_release);
|
||||
}
|
||||
nextId.store(0, std::memory_order_release);
|
||||
if (resetCallback)
|
||||
TIBase::tracingCallback = [](TrackedState, std::optional<TrackedState>) {};
|
||||
}
|
||||
|
||||
struct ResetStatesGuard
|
||||
{
|
||||
bool resetCallback{false};
|
||||
|
||||
ResetStatesGuard(bool resetCallback) : resetCallback{resetCallback}
|
||||
{
|
||||
TIBase::resetStates(resetCallback);
|
||||
}
|
||||
~ResetStatesGuard()
|
||||
{
|
||||
TIBase::resetStates(resetCallback);
|
||||
}
|
||||
};
|
||||
|
||||
TIBase() : id{checkoutID()}
|
||||
{
|
||||
assert(state.size() > id);
|
||||
state[id].store(TrackedState::Alive, std::memory_order_relaxed);
|
||||
}
|
||||
~TIBase() override
|
||||
{
|
||||
using enum TrackedState;
|
||||
|
||||
assert(state.size() > id);
|
||||
tracingCallback(state[id].load(std::memory_order_relaxed), DeletedStarted);
|
||||
|
||||
assert(state.size() > id);
|
||||
// Use relaxed memory order to try to avoid atomic operations from
|
||||
// adding additional memory synchronizations that may hide threading
|
||||
// errors in the underlying shared pointer class.
|
||||
state[id].store(DeletedStarted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(DeletedStarted, Deleted);
|
||||
|
||||
assert(state.size() > id);
|
||||
state[id].store(TrackedState::Deleted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(TrackedState::Deleted, std::nullopt);
|
||||
}
|
||||
|
||||
void
|
||||
partialDestructor() const
|
||||
{
|
||||
using enum TrackedState;
|
||||
|
||||
assert(state.size() > id);
|
||||
tracingCallback(state[id].load(std::memory_order_relaxed), PartiallyDeletedStarted);
|
||||
|
||||
assert(state.size() > id);
|
||||
state[id].store(PartiallyDeletedStarted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(PartiallyDeletedStarted, PartiallyDeleted);
|
||||
|
||||
assert(state.size() > id);
|
||||
state[id].store(PartiallyDeleted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(PartiallyDeleted, std::nullopt);
|
||||
}
|
||||
|
||||
static std::function<void(TrackedState, std::optional<TrackedState>)> tracingCallback;
|
||||
|
||||
int id;
|
||||
|
||||
private:
|
||||
static int
|
||||
checkoutID()
|
||||
{
|
||||
return nextId.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
};
|
||||
|
||||
std::array<std::atomic<TrackedState>, TIBase::kMaxStates> TIBase::state;
|
||||
std::atomic<int> TIBase::nextId{0};
|
||||
|
||||
std::function<void(TrackedState, std::optional<TrackedState>)> TIBase::tracingCallback =
|
||||
[](TrackedState, std::optional<TrackedState>) {};
|
||||
|
||||
} // namespace
|
||||
|
||||
class IntrusiveShared_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testBasics()
|
||||
{
|
||||
testcase("Basics");
|
||||
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
TIBase const b;
|
||||
BEAST_EXPECT(b.useCount() == 1);
|
||||
b.addWeakRef();
|
||||
BEAST_EXPECT(b.useCount() == 1);
|
||||
auto s = b.releaseStrongRef();
|
||||
BEAST_EXPECT(s == ReleaseStrongRefAction::PartialDestroy);
|
||||
BEAST_EXPECT(b.useCount() == 0);
|
||||
TIBase const* pb = &b;
|
||||
partialDestructorFinished(&pb);
|
||||
BEAST_EXPECT(!pb);
|
||||
auto w = b.releaseWeakRef();
|
||||
BEAST_EXPECT(w == ReleaseWeakRefAction::Destroy);
|
||||
}
|
||||
|
||||
std::vector<SharedIntrusive<TIBase>> strong;
|
||||
std::vector<WeakIntrusive<TIBase>> weak;
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
using enum TrackedState;
|
||||
auto b = makeSharedIntrusive<TIBase>();
|
||||
auto id = b->id;
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
BEAST_EXPECT(b->useCount() == 1);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
strong.push_back(b);
|
||||
}
|
||||
b.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
strong.resize(strong.size() - 1);
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
strong.clear();
|
||||
BEAST_EXPECT(TIBase::getState(id) == Deleted);
|
||||
|
||||
b = makeSharedIntrusive<TIBase>();
|
||||
id = b->id;
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
BEAST_EXPECT(b->useCount() == 1);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
weak.emplace_back(b);
|
||||
BEAST_EXPECT(b->useCount() == 1);
|
||||
}
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
weak.resize(weak.size() - 1);
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
b.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted);
|
||||
while (!weak.empty())
|
||||
{
|
||||
weak.resize(weak.size() - 1);
|
||||
if (!weak.empty())
|
||||
BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted);
|
||||
}
|
||||
BEAST_EXPECT(TIBase::getState(id) == Deleted);
|
||||
}
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
using enum TrackedState;
|
||||
auto b = makeSharedIntrusive<TIBase>();
|
||||
auto id = b->id;
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
WeakIntrusive<TIBase> w{b};
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
auto s = w.lock();
|
||||
BEAST_EXPECT(s && s->useCount() == 2);
|
||||
b.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
BEAST_EXPECT(s && s->useCount() == 1);
|
||||
s.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted);
|
||||
BEAST_EXPECT(w.expired());
|
||||
s = w.lock();
|
||||
// Cannot convert a weak pointer to a strong pointer if object is
|
||||
// already partially deleted
|
||||
BEAST_EXPECT(!s);
|
||||
w.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == Deleted);
|
||||
}
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
using enum TrackedState;
|
||||
using swu = SharedWeakUnion<TIBase>;
|
||||
swu b = makeSharedIntrusive<TIBase>();
|
||||
BEAST_EXPECT(b.isStrong() && b.useCount() == 1);
|
||||
auto id = b.get()->id;
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
swu w = b;
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
BEAST_EXPECT(w.isStrong() && b.useCount() == 2);
|
||||
w.convertToWeak();
|
||||
BEAST_EXPECT(w.isWeak() && b.useCount() == 1);
|
||||
swu s = w;
|
||||
BEAST_EXPECT(s.isWeak() && b.useCount() == 1);
|
||||
s.convertToStrong();
|
||||
BEAST_EXPECT(s.isStrong() && b.useCount() == 2);
|
||||
b.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == Alive);
|
||||
BEAST_EXPECT(s.useCount() == 1);
|
||||
BEAST_EXPECT(!w.expired());
|
||||
s.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == PartiallyDeleted);
|
||||
BEAST_EXPECT(w.expired());
|
||||
w.convertToStrong();
|
||||
// Cannot convert a weak pointer to a strong pointer if object is
|
||||
// already partially deleted
|
||||
BEAST_EXPECT(w.isWeak());
|
||||
w.reset();
|
||||
BEAST_EXPECT(TIBase::getState(id) == Deleted);
|
||||
}
|
||||
{
|
||||
// Testing SharedWeakUnion assignment operator
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
auto strong1 = makeSharedIntrusive<TIBase>();
|
||||
auto strong2 = makeSharedIntrusive<TIBase>();
|
||||
|
||||
auto id1 = strong1->id;
|
||||
auto id2 = strong2->id;
|
||||
|
||||
BEAST_EXPECT(id1 != id2);
|
||||
|
||||
SharedWeakUnion<TIBase> union1 = strong1;
|
||||
SharedWeakUnion<TIBase> union2 = strong2;
|
||||
|
||||
BEAST_EXPECT(union1.isStrong());
|
||||
BEAST_EXPECT(union2.isStrong());
|
||||
BEAST_EXPECT(union1.get() == strong1.get());
|
||||
BEAST_EXPECT(union2.get() == strong2.get());
|
||||
|
||||
// 1) Normal assignment: explicitly calls SharedWeakUnion assignment
|
||||
union1 = union2;
|
||||
BEAST_EXPECT(union1.isStrong());
|
||||
BEAST_EXPECT(union2.isStrong());
|
||||
BEAST_EXPECT(union1.get() == union2.get());
|
||||
BEAST_EXPECT(TIBase::getState(id1) == TrackedState::Alive);
|
||||
BEAST_EXPECT(TIBase::getState(id2) == TrackedState::Alive);
|
||||
|
||||
// 2) Test self-assignment
|
||||
BEAST_EXPECT(union1.isStrong());
|
||||
BEAST_EXPECT(TIBase::getState(id1) == TrackedState::Alive);
|
||||
int const initialRefCount = strong1->useCount();
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
|
||||
union1 = union1; // Self-assignment
|
||||
#pragma clang diagnostic pop
|
||||
BEAST_EXPECT(union1.isStrong());
|
||||
BEAST_EXPECT(TIBase::getState(id1) == TrackedState::Alive);
|
||||
BEAST_EXPECT(strong1->useCount() == initialRefCount);
|
||||
|
||||
// 3) Test assignment from null union pointer
|
||||
union1 = SharedWeakUnion<TIBase>();
|
||||
BEAST_EXPECT(union1.get() == nullptr);
|
||||
|
||||
// 4) Test assignment to expired union pointer
|
||||
strong2.reset();
|
||||
union2.reset();
|
||||
union1 = union2;
|
||||
BEAST_EXPECT(union1.get() == nullptr);
|
||||
BEAST_EXPECT(TIBase::getState(id2) == TrackedState::Deleted);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPartialDelete()
|
||||
{
|
||||
testcase("Partial Delete");
|
||||
|
||||
// This test creates two threads. One with a strong pointer and one
|
||||
// with a weak pointer. The strong pointer is reset while the weak
|
||||
// pointer still holds a reference, triggering a partial delete.
|
||||
// While the partial delete function runs (a sleep is inserted) the
|
||||
// weak pointer is reset. The destructor should wait to run until
|
||||
// after the partial delete function has completed running.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
WeakIntrusive<TIBase> weak{strong};
|
||||
bool destructorRan = false;
|
||||
bool partialDeleteRan = false;
|
||||
std::latch partialDeleteStartedSyncPoint{2};
|
||||
strong->tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
if (next == DeletedStarted)
|
||||
{
|
||||
// strong goes out of scope while weak is still in scope
|
||||
// This checks that partialDelete has run to completion
|
||||
// before the destructor is called. A sleep is inserted
|
||||
// inside the partial delete to make sure the destructor is
|
||||
// given an opportunity to run during partial delete.
|
||||
BEAST_EXPECT(cur == PartiallyDeleted);
|
||||
}
|
||||
if (next == PartiallyDeletedStarted)
|
||||
{
|
||||
partialDeleteStartedSyncPoint.arrive_and_wait();
|
||||
using namespace std::chrono_literals;
|
||||
// Sleep and let the weak pointer go out of scope,
|
||||
// potentially triggering a destructor while partial delete
|
||||
// is running. The test is to make sure that doesn't happen.
|
||||
std::this_thread::sleep_for(800ms);
|
||||
}
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
BEAST_EXPECT(!partialDeleteRan && !destructorRan);
|
||||
partialDeleteRan = true;
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
BEAST_EXPECT(!destructorRan);
|
||||
destructorRan = true;
|
||||
}
|
||||
};
|
||||
std::thread t1{[&] {
|
||||
partialDeleteStartedSyncPoint.arrive_and_wait();
|
||||
weak.reset(); // Trigger a full delete as soon as the partial
|
||||
// delete starts
|
||||
}};
|
||||
std::thread t2{[&] {
|
||||
strong.reset(); // Trigger a partial delete
|
||||
}};
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
BEAST_EXPECT(destructorRan && partialDeleteRan);
|
||||
}
|
||||
|
||||
void
|
||||
testDestructor()
|
||||
{
|
||||
testcase("Destructor");
|
||||
|
||||
// This test creates two threads. One with a strong pointer and one
|
||||
// with a weak pointer. The weak pointer is reset while the strong
|
||||
// pointer still holds a reference. Then the strong pointer is
|
||||
// reset. Only the destructor should run. The partial destructor
|
||||
// should not be called. Since the weak reset runs to completion
|
||||
// before the strong pointer is reset, threading doesn't add much to
|
||||
// this test, but there is no harm in keeping it.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
WeakIntrusive<TIBase> weak{strong};
|
||||
bool destructorRan = false;
|
||||
bool partialDeleteRan = false;
|
||||
std::latch weakResetSyncPoint{2};
|
||||
strong->tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
BEAST_EXPECT(!partialDeleteRan && !destructorRan);
|
||||
partialDeleteRan = true;
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
BEAST_EXPECT(!destructorRan);
|
||||
destructorRan = true;
|
||||
}
|
||||
};
|
||||
std::thread t1{[&] {
|
||||
weak.reset();
|
||||
weakResetSyncPoint.arrive_and_wait();
|
||||
}};
|
||||
std::thread t2{[&] {
|
||||
weakResetSyncPoint.arrive_and_wait();
|
||||
strong.reset(); // Trigger a partial delete
|
||||
}};
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
BEAST_EXPECT(destructorRan && !partialDeleteRan);
|
||||
}
|
||||
|
||||
void
|
||||
testMultithreadedClearMixedVariant()
|
||||
{
|
||||
testcase("Multithreaded Clear Mixed Variant");
|
||||
|
||||
// This test creates and destroys many strong and weak pointers in a
|
||||
// loop. There is a random mix of strong and weak pointers stored in
|
||||
// a vector (held as a variant). Both threads clear all the pointers
|
||||
// and check that the invariants hold.
|
||||
|
||||
using enum TrackedState;
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
std::atomic<int> destructionState{0};
|
||||
// returns destructorRan and partialDestructorRan (in that order)
|
||||
auto getDestructorState = [&]() -> std::pair<bool, bool> {
|
||||
int const s = destructionState.load(std::memory_order_relaxed);
|
||||
return {(s & 1) != 0, (s & 2) != 0};
|
||||
};
|
||||
auto setDestructorRan = [&]() -> void {
|
||||
destructionState.fetch_or(1, std::memory_order_acq_rel);
|
||||
};
|
||||
auto setPartialDeleteRan = [&]() -> void {
|
||||
destructionState.fetch_or(2, std::memory_order_acq_rel);
|
||||
};
|
||||
auto tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
BEAST_EXPECT(!partialDeleteRan && !destructorRan);
|
||||
setPartialDeleteRan();
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
BEAST_EXPECT(!destructorRan);
|
||||
setDestructorRan();
|
||||
}
|
||||
};
|
||||
auto createVecOfPointers = [&](auto const& toClone, std::default_random_engine& eng)
|
||||
-> std::vector<std::variant<SharedIntrusive<TIBase>, WeakIntrusive<TIBase>>> {
|
||||
std::vector<std::variant<SharedIntrusive<TIBase>, WeakIntrusive<TIBase>>> result;
|
||||
std::uniform_int_distribution<> toCreateDist(4, 64);
|
||||
std::uniform_int_distribution<> isStrongDist(0, 1);
|
||||
auto numToCreate = toCreateDist(eng);
|
||||
result.reserve(numToCreate);
|
||||
for (int i = 0; i < numToCreate; ++i)
|
||||
{
|
||||
if (isStrongDist(eng))
|
||||
{
|
||||
result.emplace_back(SharedIntrusive<TIBase>(toClone));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.emplace_back(WeakIntrusive<TIBase>(toClone));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
static constexpr int kLoopIters = 2 * 1024;
|
||||
static constexpr int kNumThreads = 16;
|
||||
std::vector<SharedIntrusive<TIBase>> toClone;
|
||||
Barrier loopStartSyncPoint{kNumThreads};
|
||||
Barrier postCreateToCloneSyncPoint{kNumThreads};
|
||||
Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
|
||||
auto engines = [&]() -> std::vector<std::default_random_engine> {
|
||||
std::random_device rd;
|
||||
std::vector<std::default_random_engine> result;
|
||||
result.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
result.emplace_back(rd());
|
||||
return result;
|
||||
}();
|
||||
|
||||
// cloneAndDestroy clones the strong pointer into a vector of mixed
|
||||
// strong and weak pointers and destroys them all at once.
|
||||
// threadId==0 is special.
|
||||
auto cloneAndDestroy = [&](int threadId) {
|
||||
for (int i = 0; i < kLoopIters; ++i)
|
||||
{
|
||||
// ------ Sync Point ------
|
||||
loopStartSyncPoint.arriveAndWait();
|
||||
|
||||
// only thread 0 should reset the state
|
||||
std::optional<TIBase::ResetStatesGuard> rsg;
|
||||
if (threadId == 0)
|
||||
{
|
||||
// Thread 0 is the genesis thread. It creates the strong
|
||||
// pointers to be cloned by the other threads. This
|
||||
// thread will also check that the destructor ran and
|
||||
// clear the temporary variables.
|
||||
|
||||
rsg.emplace(false);
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
BEAST_EXPECT(!i || destructorRan);
|
||||
destructionState.store(0, std::memory_order_release);
|
||||
|
||||
toClone.clear();
|
||||
toClone.resize(kNumThreads);
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
strong->tracingCallback = tracingCallback;
|
||||
std::ranges::fill(toClone, strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateToCloneSyncPoint.arriveAndWait();
|
||||
|
||||
auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
|
||||
toClone[threadId].reset();
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateVecOfPointersSyncPoint.arriveAndWait();
|
||||
|
||||
v.clear();
|
||||
}
|
||||
};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads.emplace_back(cloneAndDestroy, i);
|
||||
}
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testMultithreadedClearMixedUnion()
|
||||
{
|
||||
testcase("Multithreaded Clear Mixed Union");
|
||||
|
||||
// This test creates and destroys many SharedWeak pointers in a
|
||||
// loop. All the pointers start as strong and a loop randomly
|
||||
// convert them between strong and weak pointers. Both threads clear
|
||||
// all the pointers and check that the invariants hold.
|
||||
//
|
||||
// Note: This test also differs from the test above in that the pointers
|
||||
// randomly change from strong to weak and from weak to strong in a
|
||||
// loop. This can't be done in the variant test above because variant is
|
||||
// not thread safe while the SharedWeakUnion is thread safe.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
std::atomic<int> destructionState{0};
|
||||
// returns destructorRan and partialDestructorRan (in that order)
|
||||
auto getDestructorState = [&]() -> std::pair<bool, bool> {
|
||||
int const s = destructionState.load(std::memory_order_relaxed);
|
||||
return {(s & 1) != 0, (s & 2) != 0};
|
||||
};
|
||||
auto setDestructorRan = [&]() -> void {
|
||||
destructionState.fetch_or(1, std::memory_order_acq_rel);
|
||||
};
|
||||
auto setPartialDeleteRan = [&]() -> void {
|
||||
destructionState.fetch_or(2, std::memory_order_acq_rel);
|
||||
};
|
||||
auto tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
BEAST_EXPECT(!partialDeleteRan && !destructorRan);
|
||||
setPartialDeleteRan();
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
BEAST_EXPECT(!destructorRan);
|
||||
setDestructorRan();
|
||||
}
|
||||
};
|
||||
auto createVecOfPointers =
|
||||
[&](auto const& toClone,
|
||||
std::default_random_engine& eng) -> std::vector<SharedWeakUnion<TIBase>> {
|
||||
std::vector<SharedWeakUnion<TIBase>> result;
|
||||
std::uniform_int_distribution<> toCreateDist(4, 64);
|
||||
auto numToCreate = toCreateDist(eng);
|
||||
result.reserve(numToCreate);
|
||||
for (int i = 0; i < numToCreate; ++i)
|
||||
result.emplace_back(SharedIntrusive<TIBase>(toClone));
|
||||
return result;
|
||||
};
|
||||
static constexpr int kLoopIters = 2 * 1024;
|
||||
static constexpr int kFlipPointersLoopIters = 256;
|
||||
static constexpr int kNumThreads = 16;
|
||||
std::vector<SharedIntrusive<TIBase>> toClone;
|
||||
Barrier loopStartSyncPoint{kNumThreads};
|
||||
Barrier postCreateToCloneSyncPoint{kNumThreads};
|
||||
Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
|
||||
Barrier postFlipPointersLoopSyncPoint{kNumThreads};
|
||||
auto engines = [&]() -> std::vector<std::default_random_engine> {
|
||||
std::random_device rd;
|
||||
std::vector<std::default_random_engine> result;
|
||||
result.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
result.emplace_back(rd());
|
||||
return result;
|
||||
}();
|
||||
|
||||
// cloneAndDestroy clones the strong pointer into a vector of
|
||||
// mixed strong and weak pointers, runs a loop that randomly
|
||||
// changes strong pointers to weak pointers, and destroys them
|
||||
// all at once.
|
||||
auto cloneAndDestroy = [&](int threadId) {
|
||||
for (int i = 0; i < kLoopIters; ++i)
|
||||
{
|
||||
// ------ Sync Point ------
|
||||
loopStartSyncPoint.arriveAndWait();
|
||||
|
||||
// only thread 0 should reset the state
|
||||
std::optional<TIBase::ResetStatesGuard> rsg;
|
||||
if (threadId == 0)
|
||||
{
|
||||
// threadId 0 is the genesis thread. It creates the
|
||||
// strong point to be cloned by the other threads. This
|
||||
// thread will also check that the destructor ran and
|
||||
// clear the temporary variables.
|
||||
rsg.emplace(false);
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
BEAST_EXPECT(!i || destructorRan);
|
||||
destructionState.store(0, std::memory_order_release);
|
||||
|
||||
toClone.clear();
|
||||
toClone.resize(kNumThreads);
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
strong->tracingCallback = tracingCallback;
|
||||
std::ranges::fill(toClone, strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateToCloneSyncPoint.arriveAndWait();
|
||||
|
||||
auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
|
||||
toClone[threadId].reset();
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateVecOfPointersSyncPoint.arriveAndWait();
|
||||
|
||||
std::uniform_int_distribution<> isStrongDist(0, 1);
|
||||
for (int f = 0; f < kFlipPointersLoopIters; ++f)
|
||||
{
|
||||
for (auto& p : v)
|
||||
{
|
||||
if (isStrongDist(engines[threadId]))
|
||||
{
|
||||
p.convertToStrong();
|
||||
}
|
||||
else
|
||||
{
|
||||
p.convertToWeak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postFlipPointersLoopSyncPoint.arriveAndWait();
|
||||
|
||||
v.clear();
|
||||
}
|
||||
};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads.emplace_back(cloneAndDestroy, i);
|
||||
}
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testMultithreadedLockingWeak()
|
||||
{
|
||||
testcase("Multithreaded Locking Weak");
|
||||
|
||||
// This test creates a single shared atomic pointer that multiple thread
|
||||
// create weak pointers from. The threads then lock the weak pointers.
|
||||
// Both threads clear all the pointers and check that the invariants
|
||||
// hold.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
std::atomic<int> destructionState{0};
|
||||
// returns destructorRan and partialDestructorRan (in that order)
|
||||
auto getDestructorState = [&]() -> std::pair<bool, bool> {
|
||||
int const s = destructionState.load(std::memory_order_relaxed);
|
||||
return {(s & 1) != 0, (s & 2) != 0};
|
||||
};
|
||||
auto setDestructorRan = [&]() -> void {
|
||||
destructionState.fetch_or(1, std::memory_order_acq_rel);
|
||||
};
|
||||
auto setPartialDeleteRan = [&]() -> void {
|
||||
destructionState.fetch_or(2, std::memory_order_acq_rel);
|
||||
};
|
||||
auto tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
BEAST_EXPECT(!partialDeleteRan && !destructorRan);
|
||||
setPartialDeleteRan();
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
BEAST_EXPECT(!destructorRan);
|
||||
setDestructorRan();
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr int kLoopIters = 2 * 1024;
|
||||
static constexpr int kLockWeakLoopIters = 256;
|
||||
static constexpr int kNumThreads = 16;
|
||||
std::vector<SharedIntrusive<TIBase>> toLock;
|
||||
Barrier loopStartSyncPoint{kNumThreads};
|
||||
Barrier postCreateToLockSyncPoint{kNumThreads};
|
||||
Barrier postLockWeakLoopSyncPoint{kNumThreads};
|
||||
|
||||
// lockAndDestroy creates weak pointers from the strong pointer
|
||||
// and runs a loop that locks the weak pointer. At the end of the loop
|
||||
// all the pointers are destroyed all at once.
|
||||
auto lockAndDestroy = [&](int threadId) {
|
||||
for (int i = 0; i < kLoopIters; ++i)
|
||||
{
|
||||
// ------ Sync Point ------
|
||||
loopStartSyncPoint.arriveAndWait();
|
||||
|
||||
// only thread 0 should reset the state
|
||||
std::optional<TIBase::ResetStatesGuard> rsg;
|
||||
if (threadId == 0)
|
||||
{
|
||||
// threadId 0 is the genesis thread. It creates the
|
||||
// strong point to be locked by the other threads. This
|
||||
// thread will also check that the destructor ran and
|
||||
// clear the temporary variables.
|
||||
rsg.emplace(false);
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
BEAST_EXPECT(!i || destructorRan);
|
||||
destructionState.store(0, std::memory_order_release);
|
||||
|
||||
toLock.clear();
|
||||
toLock.resize(kNumThreads);
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
strong->tracingCallback = tracingCallback;
|
||||
std::ranges::fill(toLock, strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateToLockSyncPoint.arriveAndWait();
|
||||
|
||||
// Multiple threads all create a weak pointer from the same
|
||||
// strong pointer
|
||||
WeakIntrusive const weak{toLock[threadId]};
|
||||
for (int wi = 0; wi < kLockWeakLoopIters; ++wi)
|
||||
{
|
||||
BEAST_EXPECT(!weak.expired());
|
||||
auto strong = weak.lock();
|
||||
BEAST_EXPECT(strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postLockWeakLoopSyncPoint.arriveAndWait();
|
||||
|
||||
toLock[threadId].reset();
|
||||
}
|
||||
};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads.emplace_back(lockAndDestroy, i);
|
||||
}
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testBasics();
|
||||
testPartialDelete();
|
||||
testDestructor();
|
||||
testMultithreadedClearMixedVariant();
|
||||
testMultithreadedClearMixedUnion();
|
||||
testMultithreadedLockingWeak();
|
||||
}
|
||||
}; // namespace tests
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(IntrusiveShared, basics, xrpl);
|
||||
} // namespace xrpl::tests
|
||||
@@ -1,82 +0,0 @@
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/TaggedCache.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class KeyCache_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch clock;
|
||||
clock.set(0);
|
||||
|
||||
using Key = std::string;
|
||||
using Cache = TaggedCache<Key, int, true>;
|
||||
|
||||
test::SuiteJournal j("KeyCacheTest", *this);
|
||||
|
||||
// Insert an item, retrieve it, and age it so it gets purged.
|
||||
{
|
||||
Cache c("test", LedgerIndex(1), 2s, clock, j);
|
||||
|
||||
BEAST_EXPECT(c.size() == 0);
|
||||
BEAST_EXPECT(c.insert("one"));
|
||||
BEAST_EXPECT(!c.insert("one"));
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
BEAST_EXPECT(c.touchIfExists("one"));
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.size() == 0);
|
||||
BEAST_EXPECT(!c.touchIfExists("one"));
|
||||
}
|
||||
|
||||
// Insert two items, have one expire
|
||||
{
|
||||
Cache c("test", LedgerIndex(2), 2s, clock, j);
|
||||
|
||||
BEAST_EXPECT(c.insert("one"));
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
BEAST_EXPECT(c.insert("two"));
|
||||
BEAST_EXPECT(c.size() == 2);
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.size() == 2);
|
||||
BEAST_EXPECT(c.touchIfExists("two"));
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
}
|
||||
|
||||
// Insert three items (1 over limit), sweep
|
||||
{
|
||||
Cache c("test", LedgerIndex(2), 3s, clock, j);
|
||||
|
||||
BEAST_EXPECT(c.insert("one"));
|
||||
++clock;
|
||||
BEAST_EXPECT(c.insert("two"));
|
||||
++clock;
|
||||
BEAST_EXPECT(c.insert("three"));
|
||||
++clock;
|
||||
BEAST_EXPECT(c.size() == 3);
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.size() < 3);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(KeyCache, basics, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,309 +0,0 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/ToString.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class StringUtilities_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testUnHexSuccess(std::string const& strIn, std::string const& strExpected)
|
||||
{
|
||||
auto rv = strUnHex(strIn);
|
||||
BEAST_EXPECT(rv);
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT(makeSlice(*rv) == makeSlice(strExpected));
|
||||
}
|
||||
|
||||
void
|
||||
testUnHexFailure(std::string const& strIn)
|
||||
{
|
||||
auto rv = strUnHex(strIn);
|
||||
BEAST_EXPECT(!rv);
|
||||
}
|
||||
|
||||
void
|
||||
testUnHex()
|
||||
{
|
||||
testcase("strUnHex");
|
||||
|
||||
testUnHexSuccess("526970706c6544", "RippleD");
|
||||
testUnHexSuccess("A", "\n");
|
||||
testUnHexSuccess("0A", "\n");
|
||||
testUnHexSuccess("D0A", "\r\n");
|
||||
testUnHexSuccess("0D0A", "\r\n");
|
||||
testUnHexSuccess("200D0A", " \r\n");
|
||||
testUnHexSuccess("282A2B2C2D2E2F29", "(*+,-./)");
|
||||
|
||||
// Check for things which contain some or only invalid characters
|
||||
testUnHexFailure("123X");
|
||||
testUnHexFailure("V");
|
||||
testUnHexFailure("XRP");
|
||||
}
|
||||
|
||||
void
|
||||
testParseUrl()
|
||||
{
|
||||
testcase("parseUrl");
|
||||
|
||||
// Expected passes.
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain.empty());
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
// RFC 3986:
|
||||
// > In general, a URI that uses the generic syntax for authority
|
||||
// with an empty path should be normalized to a path of "/".
|
||||
// Do we want to normalize paths?
|
||||
BEAST_EXPECT(pUrl.path.empty());
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme:///"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain.empty());
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "lower://domain"));
|
||||
BEAST_EXPECT(pUrl.scheme == "lower");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path.empty());
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "UPPER://domain:234/"));
|
||||
BEAST_EXPECT(pUrl.scheme == "upper");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(*pUrl.port == 234); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT(pUrl.path == "/");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "Mixed://domain/path"));
|
||||
BEAST_EXPECT(pUrl.scheme == "mixed");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/path");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://[::1]:123/path"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "::1");
|
||||
BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT(pUrl.path == "/path");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://user:pass@domain:123/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username == "user");
|
||||
BEAST_EXPECT(pUrl.password == "pass");
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://user@domain:123/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username == "user");
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://:pass@domain:123/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password == "pass");
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://domain:123/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(*pUrl.port == 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://user:pass@domain/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username == "user");
|
||||
BEAST_EXPECT(pUrl.password == "pass");
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://user@domain/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username == "user");
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://:pass@domain/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password == "pass");
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://domain/abc:321"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme:///path/to/file"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain.empty());
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/path/to/file");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://user:pass@domain/path/with/an@sign"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username == "user");
|
||||
BEAST_EXPECT(pUrl.password == "pass");
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/path/with/an@sign");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://domain/path/with/an@sign"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "domain");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/path/with/an@sign");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "scheme://:999/"));
|
||||
BEAST_EXPECT(pUrl.scheme == "scheme");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == ":999");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(parseUrl(pUrl, "http://::1:1234/validators"));
|
||||
BEAST_EXPECT(pUrl.scheme == "http");
|
||||
BEAST_EXPECT(pUrl.username.empty());
|
||||
BEAST_EXPECT(pUrl.password.empty());
|
||||
BEAST_EXPECT(pUrl.domain == "::0.1.18.52");
|
||||
BEAST_EXPECT(!pUrl.port);
|
||||
BEAST_EXPECT(pUrl.path == "/validators");
|
||||
}
|
||||
|
||||
// Expected fails.
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(!parseUrl(pUrl, ""));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "nonsense"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "://"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, ":///"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "scheme://user:pass@domain:65536/abc:321"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:23498765/"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:0/"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:+7/"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:-7234/"));
|
||||
BEAST_EXPECT(!parseUrl(pUrl, "UPPER://domain:@#$56!/"));
|
||||
}
|
||||
|
||||
{
|
||||
std::string const strUrl("s://" + std::string(8192, ':'));
|
||||
ParsedUrl pUrl;
|
||||
BEAST_EXPECT(!parseUrl(pUrl, strUrl));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testToString()
|
||||
{
|
||||
testcase("toString");
|
||||
auto result = to_string("hello");
|
||||
BEAST_EXPECT(result == "hello");
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testParseUrl();
|
||||
testUnHex();
|
||||
testToString();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(StringUtilities, basics, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,251 +0,0 @@
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
|
||||
#include <xrpl/basics/IntrusivePointer.h>
|
||||
#include <xrpl/basics/IntrusiveRefCounts.h>
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/TaggedCache.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
I guess you can put some items in, make sure they're still there. Let some
|
||||
time pass, make sure they're gone. Keep a strong pointer to one of them, make
|
||||
sure you can still find it even after time passes. Create two objects with
|
||||
the same key, canonicalize them both and make sure you get the same object.
|
||||
Put an object in but keep a strong pointer to it, advance the clock a lot,
|
||||
then canonicalize a new object with the same key, make sure you get the
|
||||
original object.
|
||||
*/
|
||||
|
||||
class TaggedCache_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
using beast::Severity;
|
||||
test::SuiteJournal journal("TaggedCache_test", *this);
|
||||
|
||||
TestStopwatch clock;
|
||||
clock.set(0);
|
||||
|
||||
using Key = LedgerIndex;
|
||||
using Value = std::string;
|
||||
using Cache = TaggedCache<Key, Value>;
|
||||
|
||||
Cache c("test", 1, 1s, clock, journal);
|
||||
|
||||
// Insert an item, retrieve it, and age it so it gets purged.
|
||||
{
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.getTrackSize() == 0);
|
||||
BEAST_EXPECT(!c.insert(1, "one"));
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.getTrackSize() == 1);
|
||||
|
||||
{
|
||||
std::string s;
|
||||
BEAST_EXPECT(c.retrieve(1, s));
|
||||
BEAST_EXPECT(s == "one");
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.getTrackSize() == 0);
|
||||
}
|
||||
|
||||
// Insert an item, maintain a strong pointer, age it, and
|
||||
// verify that the entry still exists.
|
||||
{
|
||||
BEAST_EXPECT(!c.insert(2, "two"));
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.getTrackSize() == 1);
|
||||
|
||||
{
|
||||
auto p = c.fetch(2);
|
||||
BEAST_EXPECT(p != nullptr);
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.getTrackSize() == 1);
|
||||
}
|
||||
|
||||
// Make sure its gone now that our reference is gone
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.getTrackSize() == 0);
|
||||
}
|
||||
|
||||
// Insert the same key/value pair and make sure we get the same result
|
||||
{
|
||||
BEAST_EXPECT(!c.insert(3, "three"));
|
||||
|
||||
{
|
||||
auto const p1 = c.fetch(3);
|
||||
auto p2 = std::make_shared<Value>("three");
|
||||
c.canonicalizeReplaceClient(3, p2);
|
||||
BEAST_EXPECT(p1.get() == p2.get());
|
||||
}
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.getTrackSize() == 0);
|
||||
}
|
||||
|
||||
// Put an object in but keep a strong pointer to it, advance the clock a
|
||||
// lot, then canonicalize a new object with the same key, make sure you
|
||||
// get the original object.
|
||||
{
|
||||
// Put an object in
|
||||
BEAST_EXPECT(!c.insert(4, "four"));
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.getTrackSize() == 1);
|
||||
|
||||
{
|
||||
// Keep a strong pointer to it
|
||||
auto const p1 = c.fetch(4);
|
||||
BEAST_EXPECT(p1 != nullptr);
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.getTrackSize() == 1);
|
||||
// Advance the clock a lot
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.getTrackSize() == 1);
|
||||
// Canonicalize a new object with the same key
|
||||
auto p2 = std::make_shared<std::string>("four");
|
||||
BEAST_EXPECT(c.canonicalizeReplaceClient(4, p2));
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.getTrackSize() == 1);
|
||||
// Make sure we get the original object
|
||||
BEAST_EXPECT(p1.get() == p2.get());
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.getTrackSize() == 0);
|
||||
}
|
||||
{
|
||||
BEAST_EXPECT(!c.insert(5, "five"));
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
|
||||
{
|
||||
auto const p1 = c.fetch(5);
|
||||
BEAST_EXPECT(p1 != nullptr);
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
|
||||
// Advance the clock a lot
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
|
||||
auto p2 = std::make_shared<std::string>("five_2");
|
||||
BEAST_EXPECT(c.canonicalizeReplaceCache(5, p2));
|
||||
BEAST_EXPECT(c.getCacheSize() == 1);
|
||||
BEAST_EXPECT(c.size() == 1);
|
||||
// Make sure the caller's original pointer is unchanged
|
||||
BEAST_EXPECT(p1.get() != p2.get());
|
||||
BEAST_EXPECT(*p2 == "five_2");
|
||||
|
||||
auto const p3 = c.fetch(5);
|
||||
BEAST_EXPECT(p3 != nullptr);
|
||||
BEAST_EXPECT(p3.get() == p2.get());
|
||||
BEAST_EXPECT(p3.get() != p1.get());
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
BEAST_EXPECT(c.getCacheSize() == 0);
|
||||
BEAST_EXPECT(c.size() == 0);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("intrptr");
|
||||
|
||||
struct MyRefCountObject : IntrusiveRefCounts
|
||||
{
|
||||
std::string data;
|
||||
|
||||
// Needed to support weak intrusive pointers
|
||||
virtual void
|
||||
partialDestructor() {};
|
||||
|
||||
MyRefCountObject() = default;
|
||||
explicit MyRefCountObject(std::string data) : data(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(std::string const& other) const
|
||||
{
|
||||
return data == other;
|
||||
}
|
||||
};
|
||||
|
||||
using IntrPtrCache = TaggedCache<
|
||||
Key,
|
||||
MyRefCountObject,
|
||||
/*IsKeyCache*/ false,
|
||||
intr_ptr::SharedWeakUnionPtr<MyRefCountObject>,
|
||||
intr_ptr::SharedPtr<MyRefCountObject>>;
|
||||
|
||||
IntrPtrCache intrPtrCache("IntrPtrTest", 1, 1s, clock, journal);
|
||||
|
||||
intrPtrCache.canonicalizeReplaceCache(1, intr_ptr::makeShared<MyRefCountObject>("one"));
|
||||
BEAST_EXPECT(intrPtrCache.getCacheSize() == 1);
|
||||
BEAST_EXPECT(intrPtrCache.size() == 1);
|
||||
|
||||
{
|
||||
{
|
||||
intrPtrCache.canonicalizeReplaceCache(
|
||||
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced"));
|
||||
|
||||
auto p = intrPtrCache.fetch(1);
|
||||
BEAST_EXPECT(*p == "one_replaced");
|
||||
|
||||
// Advance the clock a lot
|
||||
++clock;
|
||||
intrPtrCache.sweep();
|
||||
BEAST_EXPECT(intrPtrCache.getCacheSize() == 0);
|
||||
BEAST_EXPECT(intrPtrCache.size() == 1);
|
||||
|
||||
intrPtrCache.canonicalizeReplaceCache(
|
||||
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_2"));
|
||||
|
||||
auto p2 = intrPtrCache.fetch(1);
|
||||
BEAST_EXPECT(*p2 == "one_replaced_2");
|
||||
|
||||
intrPtrCache.del(1, true);
|
||||
}
|
||||
|
||||
intrPtrCache.canonicalizeReplaceCache(
|
||||
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_3"));
|
||||
auto p3 = intrPtrCache.fetch(1);
|
||||
BEAST_EXPECT(*p3 == "one_replaced_3");
|
||||
}
|
||||
|
||||
++clock;
|
||||
intrPtrCache.sweep();
|
||||
BEAST_EXPECT(intrPtrCache.getCacheSize() == 0);
|
||||
BEAST_EXPECT(intrPtrCache.size() == 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(TaggedCache, basics, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,344 +0,0 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class units_test : public beast::unit_test::Suite
|
||||
{
|
||||
private:
|
||||
void
|
||||
testTypes()
|
||||
{
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
{
|
||||
XRPAmount const x{100};
|
||||
BEAST_EXPECT(x.drops() == 100);
|
||||
BEAST_EXPECT((std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
BEAST_EXPECT(y.value() == 400);
|
||||
BEAST_EXPECT((std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
auto z = 4 * y;
|
||||
BEAST_EXPECT(z.value() == 1600);
|
||||
BEAST_EXPECT((std::is_same_v<decltype(z)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel32 const f{10};
|
||||
FeeLevel32 const baseFee{100};
|
||||
|
||||
auto drops = mulDiv(baseFee, x, f);
|
||||
|
||||
BEAST_EXPECT(drops);
|
||||
BEAST_EXPECT(drops.value() == 1000); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
unit::dropTag>));
|
||||
|
||||
BEAST_EXPECT((std::is_same_v<std::remove_reference_t<decltype(*drops)>, XRPAmount>));
|
||||
}
|
||||
{
|
||||
XRPAmount const x{100};
|
||||
BEAST_EXPECT(x.value() == 100);
|
||||
BEAST_EXPECT((std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
BEAST_EXPECT(y.value() == 400);
|
||||
BEAST_EXPECT((std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel64 const f{10};
|
||||
FeeLevel64 const baseFee{100};
|
||||
|
||||
auto drops = mulDiv(baseFee, x, f);
|
||||
|
||||
BEAST_EXPECT(drops);
|
||||
BEAST_EXPECT(drops.value() == 1000); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
unit::dropTag>));
|
||||
BEAST_EXPECT((std::is_same_v<std::remove_reference_t<decltype(*drops)>, XRPAmount>));
|
||||
}
|
||||
{
|
||||
FeeLevel64 const x{1024};
|
||||
BEAST_EXPECT(x.value() == 1024);
|
||||
BEAST_EXPECT((std::is_same_v<decltype(x)::unit_type, unit::feelevelTag>));
|
||||
std::uint64_t const m = 4;
|
||||
auto y = m * x;
|
||||
BEAST_EXPECT(y.value() == 4096);
|
||||
BEAST_EXPECT((std::is_same_v<decltype(y)::unit_type, unit::feelevelTag>));
|
||||
|
||||
XRPAmount const basefee{10};
|
||||
FeeLevel64 const referencefee{256};
|
||||
|
||||
auto drops = mulDiv(x, basefee, referencefee);
|
||||
|
||||
BEAST_EXPECT(drops);
|
||||
BEAST_EXPECT(drops.value() == 40); // NOLINT(bugprone-unchecked-optional-access)
|
||||
BEAST_EXPECT((std::is_same_v<
|
||||
std::remove_reference_t<decltype(*drops)>::unit_type,
|
||||
unit::dropTag>));
|
||||
BEAST_EXPECT((std::is_same_v<std::remove_reference_t<decltype(*drops)>, XRPAmount>));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testJson()
|
||||
{
|
||||
// Json value functionality
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
{
|
||||
FeeLevel32 const x{std::numeric_limits<std::uint32_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::UInt);
|
||||
BEAST_EXPECT(y == json::Value{x.fee()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevel32 const x{std::numeric_limits<std::uint32_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::UInt);
|
||||
BEAST_EXPECT(y == json::Value{x.fee()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevel64 const x{std::numeric_limits<std::uint64_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::UInt);
|
||||
BEAST_EXPECT(y == json::Value{std::numeric_limits<std::uint32_t>::max()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevel64 const x{std::numeric_limits<std::uint64_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::UInt);
|
||||
BEAST_EXPECT(y == json::Value{0});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevelDouble const x{std::numeric_limits<double>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::Real);
|
||||
BEAST_EXPECT(y == json::Value{std::numeric_limits<double>::max()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevelDouble const x{std::numeric_limits<double>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::Real);
|
||||
BEAST_EXPECT(y == json::Value{std::numeric_limits<double>::min()});
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const x{std::numeric_limits<std::int64_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::Int);
|
||||
BEAST_EXPECT(y == json::Value{std::numeric_limits<std::int32_t>::max()});
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const x{std::numeric_limits<std::int64_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
BEAST_EXPECT(y.type() == json::ValueType::Int);
|
||||
BEAST_EXPECT(y == json::Value{std::numeric_limits<std::int32_t>::min()});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFunctions()
|
||||
{
|
||||
// Explicitly test every defined function for the ValueUnit class
|
||||
// since some of them are templated, but not used anywhere else.
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
{
|
||||
auto make = [&](auto x) -> FeeLevel64 { return x; };
|
||||
auto explicitmake = [&](auto x) -> FeeLevel64 { return FeeLevel64{x}; };
|
||||
|
||||
[[maybe_unused]]
|
||||
FeeLevel64 const defaulted{};
|
||||
FeeLevel64 test{0};
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
test = explicitmake(beast::kZero);
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
test = beast::kZero;
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
test = explicitmake(100u);
|
||||
BEAST_EXPECT(test.fee() == 100);
|
||||
|
||||
FeeLevel64 const targetSame{200u};
|
||||
FeeLevel32 const targetOther{300u};
|
||||
test = make(targetSame);
|
||||
BEAST_EXPECT(test.fee() == 200);
|
||||
BEAST_EXPECT(test == targetSame);
|
||||
BEAST_EXPECT(test < FeeLevel64{1000});
|
||||
BEAST_EXPECT(test > FeeLevel64{100});
|
||||
test = make(targetOther);
|
||||
BEAST_EXPECT(test.fee() == 300);
|
||||
BEAST_EXPECT(test == targetOther);
|
||||
|
||||
test = std::uint64_t(200);
|
||||
BEAST_EXPECT(test.fee() == 200);
|
||||
test = std::uint32_t(300);
|
||||
BEAST_EXPECT(test.fee() == 300);
|
||||
|
||||
test = targetSame;
|
||||
BEAST_EXPECT(test.fee() == 200);
|
||||
test = targetOther.fee();
|
||||
BEAST_EXPECT(test.fee() == 300);
|
||||
BEAST_EXPECT(test == targetOther);
|
||||
|
||||
test = targetSame * 2;
|
||||
BEAST_EXPECT(test.fee() == 400);
|
||||
test = 3 * targetSame;
|
||||
BEAST_EXPECT(test.fee() == 600);
|
||||
test = targetSame / 10;
|
||||
BEAST_EXPECT(test.fee() == 20);
|
||||
|
||||
test += targetSame;
|
||||
BEAST_EXPECT(test.fee() == 220);
|
||||
|
||||
test -= targetSame;
|
||||
BEAST_EXPECT(test.fee() == 20);
|
||||
|
||||
test++;
|
||||
BEAST_EXPECT(test.fee() == 21);
|
||||
++test;
|
||||
BEAST_EXPECT(test.fee() == 22);
|
||||
test--;
|
||||
BEAST_EXPECT(test.fee() == 21);
|
||||
--test;
|
||||
BEAST_EXPECT(test.fee() == 20);
|
||||
|
||||
test *= 5;
|
||||
BEAST_EXPECT(test.fee() == 100);
|
||||
test /= 2;
|
||||
BEAST_EXPECT(test.fee() == 50);
|
||||
test %= 13;
|
||||
BEAST_EXPECT(test.fee() == 11);
|
||||
|
||||
/*
|
||||
// illegal with unsigned
|
||||
test = -test;
|
||||
BEAST_EXPECT(test.fee() == -11);
|
||||
BEAST_EXPECT(test.signum() == -1);
|
||||
BEAST_EXPECT(to_string(test) == "-11");
|
||||
*/
|
||||
|
||||
BEAST_EXPECT(test);
|
||||
test = 0;
|
||||
BEAST_EXPECT(!test);
|
||||
BEAST_EXPECT(test.signum() == 0);
|
||||
test = targetSame;
|
||||
BEAST_EXPECT(test.signum() == 1);
|
||||
BEAST_EXPECT(to_string(test) == "200");
|
||||
}
|
||||
{
|
||||
auto make = [&](auto x) -> FeeLevelDouble { return x; };
|
||||
auto explicitmake = [&](auto x) -> FeeLevelDouble { return FeeLevelDouble{x}; };
|
||||
|
||||
[[maybe_unused]]
|
||||
FeeLevelDouble const defaulted{};
|
||||
FeeLevelDouble test{0};
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
test = explicitmake(beast::kZero);
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
test = beast::kZero;
|
||||
BEAST_EXPECT(test.fee() == 0);
|
||||
|
||||
test = explicitmake(100.0);
|
||||
BEAST_EXPECT(test.fee() == 100);
|
||||
|
||||
FeeLevelDouble const targetSame{200.0};
|
||||
FeeLevel64 const targetOther{300};
|
||||
test = make(targetSame);
|
||||
BEAST_EXPECT(test.fee() == 200);
|
||||
BEAST_EXPECT(test == targetSame);
|
||||
BEAST_EXPECT(test < FeeLevelDouble{1000.0});
|
||||
BEAST_EXPECT(test > FeeLevelDouble{100.0});
|
||||
test = targetOther.fee();
|
||||
BEAST_EXPECT(test.fee() == 300);
|
||||
BEAST_EXPECT(test == targetOther);
|
||||
|
||||
test = 200.0;
|
||||
BEAST_EXPECT(test.fee() == 200);
|
||||
test = std::uint64_t(300);
|
||||
BEAST_EXPECT(test.fee() == 300);
|
||||
|
||||
test = targetSame;
|
||||
BEAST_EXPECT(test.fee() == 200);
|
||||
|
||||
test = targetSame * 2;
|
||||
BEAST_EXPECT(test.fee() == 400);
|
||||
test = 3 * targetSame;
|
||||
BEAST_EXPECT(test.fee() == 600);
|
||||
test = targetSame / 10;
|
||||
BEAST_EXPECT(test.fee() == 20);
|
||||
|
||||
test += targetSame;
|
||||
BEAST_EXPECT(test.fee() == 220);
|
||||
|
||||
test -= targetSame;
|
||||
BEAST_EXPECT(test.fee() == 20);
|
||||
|
||||
test++;
|
||||
BEAST_EXPECT(test.fee() == 21);
|
||||
++test;
|
||||
BEAST_EXPECT(test.fee() == 22);
|
||||
test--;
|
||||
BEAST_EXPECT(test.fee() == 21);
|
||||
--test;
|
||||
BEAST_EXPECT(test.fee() == 20);
|
||||
|
||||
test *= 5;
|
||||
BEAST_EXPECT(test.fee() == 100);
|
||||
test /= 2;
|
||||
BEAST_EXPECT(test.fee() == 50);
|
||||
/* illegal with floating
|
||||
test %= 13;
|
||||
BEAST_EXPECT(test.fee() == 11);
|
||||
*/
|
||||
|
||||
// legal with signed
|
||||
test = -test;
|
||||
BEAST_EXPECT(test.fee() == -50);
|
||||
BEAST_EXPECT(test.signum() == -1);
|
||||
BEAST_EXPECT(to_string(test) == "-50.000000");
|
||||
|
||||
BEAST_EXPECT(test);
|
||||
test = 0;
|
||||
BEAST_EXPECT(!test);
|
||||
BEAST_EXPECT(test.signum() == 0);
|
||||
test = targetSame;
|
||||
BEAST_EXPECT(test.signum() == 1);
|
||||
BEAST_EXPECT(to_string(test) == "200.000000");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
BEAST_EXPECT(kInitialXrp.drops() == 100'000'000'000'000'000);
|
||||
BEAST_EXPECT(kInitialXrp == XRPAmount{100'000'000'000'000'000});
|
||||
|
||||
testTypes();
|
||||
testJson();
|
||||
testFunctions();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(units, basics, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,326 +0,0 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class XRPAmount_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testSigNum()
|
||||
{
|
||||
testcase("signum");
|
||||
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
BEAST_EXPECT(x.signum() < 0);
|
||||
}
|
||||
else if (i > 0)
|
||||
{
|
||||
BEAST_EXPECT(x.signum() > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(x.signum() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testBeastZero()
|
||||
{
|
||||
testcase("beast::Zero Comparisons");
|
||||
|
||||
using beast::kZero;
|
||||
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
BEAST_EXPECT((i == 0) == (x == kZero));
|
||||
BEAST_EXPECT((i != 0) == (x != kZero));
|
||||
BEAST_EXPECT((i < 0) == (x < kZero));
|
||||
BEAST_EXPECT((i > 0) == (x > kZero));
|
||||
BEAST_EXPECT((i <= 0) == (x <= kZero));
|
||||
BEAST_EXPECT((i >= 0) == (x >= kZero));
|
||||
|
||||
BEAST_EXPECT((0 == i) == (kZero == x));
|
||||
BEAST_EXPECT((0 != i) == (kZero != x));
|
||||
BEAST_EXPECT((0 < i) == (kZero < x));
|
||||
BEAST_EXPECT((0 > i) == (kZero > x));
|
||||
BEAST_EXPECT((0 <= i) == (kZero <= x));
|
||||
BEAST_EXPECT((0 >= i) == (kZero >= x));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testComparisons()
|
||||
{
|
||||
testcase("XRP Comparisons");
|
||||
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
for (auto j : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const y(j);
|
||||
|
||||
BEAST_EXPECT((i == j) == (x == y));
|
||||
BEAST_EXPECT((i != j) == (x != y));
|
||||
BEAST_EXPECT((i < j) == (x < y));
|
||||
BEAST_EXPECT((i > j) == (x > y));
|
||||
BEAST_EXPECT((i <= j) == (x <= y));
|
||||
BEAST_EXPECT((i >= j) == (x >= y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAddSub()
|
||||
{
|
||||
testcase("Addition & Subtraction");
|
||||
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
for (auto j : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const y(j);
|
||||
|
||||
BEAST_EXPECT(XRPAmount(i + j) == (x + y));
|
||||
BEAST_EXPECT(XRPAmount(i - j) == (x - y));
|
||||
|
||||
BEAST_EXPECT((x + y) == (y + x)); // addition is commutative
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDecimal()
|
||||
{
|
||||
// Tautology
|
||||
BEAST_EXPECT(kDropsPerXrp.decimalXRP() == 1);
|
||||
|
||||
XRPAmount test{1};
|
||||
BEAST_EXPECT(test.decimalXRP() == 0.000001);
|
||||
|
||||
test = -test;
|
||||
BEAST_EXPECT(test.decimalXRP() == -0.000001);
|
||||
|
||||
test = 100'000'000;
|
||||
BEAST_EXPECT(test.decimalXRP() == 100);
|
||||
|
||||
test = -test;
|
||||
BEAST_EXPECT(test.decimalXRP() == -100);
|
||||
}
|
||||
|
||||
void
|
||||
testFunctions()
|
||||
{
|
||||
// Explicitly test every defined function for the XRPAmount class
|
||||
// since some of them are templated, but not used anywhere else.
|
||||
auto make = [&](auto x) -> XRPAmount { return XRPAmount{x}; };
|
||||
|
||||
XRPAmount const defaulted{};
|
||||
(void)defaulted;
|
||||
XRPAmount test{0};
|
||||
BEAST_EXPECT(test.drops() == 0);
|
||||
|
||||
test = make(beast::kZero);
|
||||
BEAST_EXPECT(test.drops() == 0);
|
||||
|
||||
test = beast::kZero;
|
||||
BEAST_EXPECT(test.drops() == 0);
|
||||
|
||||
test = make(100);
|
||||
BEAST_EXPECT(test.drops() == 100);
|
||||
|
||||
test = make(100u);
|
||||
BEAST_EXPECT(test.drops() == 100);
|
||||
|
||||
XRPAmount const targetSame{200u};
|
||||
test = make(targetSame);
|
||||
BEAST_EXPECT(test.drops() == 200);
|
||||
BEAST_EXPECT(test == targetSame);
|
||||
BEAST_EXPECT(test < XRPAmount{1000});
|
||||
BEAST_EXPECT(test > XRPAmount{100});
|
||||
|
||||
test = std::int64_t(200);
|
||||
BEAST_EXPECT(test.drops() == 200);
|
||||
test = std::uint32_t(300);
|
||||
BEAST_EXPECT(test.drops() == 300);
|
||||
|
||||
test = targetSame;
|
||||
BEAST_EXPECT(test.drops() == 200);
|
||||
auto testOther = test.dropsAs<std::uint32_t>();
|
||||
BEAST_EXPECT(testOther);
|
||||
BEAST_EXPECT(*testOther == 200); // NOLINT(bugprone-unchecked-optional-access)
|
||||
test = std::numeric_limits<std::uint64_t>::max();
|
||||
testOther = test.dropsAs<std::uint32_t>();
|
||||
BEAST_EXPECT(!testOther);
|
||||
test = -1;
|
||||
testOther = test.dropsAs<std::uint32_t>();
|
||||
BEAST_EXPECT(!testOther);
|
||||
|
||||
test = targetSame * 2;
|
||||
BEAST_EXPECT(test.drops() == 400);
|
||||
test = 3 * targetSame;
|
||||
BEAST_EXPECT(test.drops() == 600);
|
||||
test = 20;
|
||||
BEAST_EXPECT(test.drops() == 20);
|
||||
|
||||
test += targetSame;
|
||||
BEAST_EXPECT(test.drops() == 220);
|
||||
|
||||
test -= targetSame;
|
||||
BEAST_EXPECT(test.drops() == 20);
|
||||
|
||||
test *= 5;
|
||||
BEAST_EXPECT(test.drops() == 100);
|
||||
test = 50;
|
||||
BEAST_EXPECT(test.drops() == 50);
|
||||
test -= 39;
|
||||
BEAST_EXPECT(test.drops() == 11);
|
||||
|
||||
// legal with signed
|
||||
test = -test;
|
||||
BEAST_EXPECT(test.drops() == -11);
|
||||
BEAST_EXPECT(test.signum() == -1);
|
||||
BEAST_EXPECT(to_string(test) == "-11");
|
||||
|
||||
BEAST_EXPECT(test);
|
||||
test = 0;
|
||||
BEAST_EXPECT(!test);
|
||||
BEAST_EXPECT(test.signum() == 0);
|
||||
test = targetSame;
|
||||
BEAST_EXPECT(test.signum() == 1);
|
||||
BEAST_EXPECT(to_string(test) == "200");
|
||||
}
|
||||
|
||||
void
|
||||
testMulRatio()
|
||||
{
|
||||
testcase("mulRatio");
|
||||
|
||||
constexpr auto kMaxUInt32 = std::numeric_limits<std::uint32_t>::max();
|
||||
constexpr auto kMaxXrp = std::numeric_limits<XRPAmount::value_type>::max();
|
||||
constexpr auto kMinXrp = std::numeric_limits<XRPAmount::value_type>::min();
|
||||
|
||||
{
|
||||
// multiply by a number that would overflow then divide by the same
|
||||
// number, and check we didn't lose any value
|
||||
XRPAmount big(kMaxXrp);
|
||||
BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, false));
|
||||
|
||||
// multiply and divide by values that would overflow if done
|
||||
// naively, and check that it gives the correct answer
|
||||
big -= 0xf; // Subtract a little so it's divisible by 4
|
||||
BEAST_EXPECT(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3);
|
||||
BEAST_EXPECT(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3);
|
||||
BEAST_EXPECT((big.value() * 3) / 4 != (big.value() / 4) * 3);
|
||||
}
|
||||
|
||||
{
|
||||
// Similar test as above, but for negative values
|
||||
XRPAmount big(kMinXrp); // NOLINT TODO
|
||||
BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
BEAST_EXPECT(big == mulRatio(big, kMaxUInt32, kMaxUInt32, false));
|
||||
|
||||
// multiply and divide by values that would overflow if done
|
||||
// naively, and check that it gives the correct answer
|
||||
BEAST_EXPECT(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3);
|
||||
BEAST_EXPECT(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3);
|
||||
BEAST_EXPECT((big.value() * 3) / 4 != (big.value() / 4) * 3);
|
||||
}
|
||||
|
||||
{
|
||||
// small amounts
|
||||
XRPAmount const tiny(1);
|
||||
// Round up should give the smallest allowable number
|
||||
BEAST_EXPECT(tiny == mulRatio(tiny, 1, kMaxUInt32, true));
|
||||
// rounding down should be zero
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tiny, 1, kMaxUInt32, false));
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tiny, kMaxUInt32 - 1, kMaxUInt32, false));
|
||||
|
||||
// tiny negative numbers
|
||||
XRPAmount const tinyNeg(-1);
|
||||
// Round up should give zero
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, 1, kMaxUInt32, true));
|
||||
BEAST_EXPECT(beast::kZero == mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, true));
|
||||
// rounding down should be tiny
|
||||
BEAST_EXPECT(tinyNeg == mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, false));
|
||||
}
|
||||
|
||||
{ // rounding
|
||||
{
|
||||
XRPAmount const one(1);
|
||||
auto const rup = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, true);
|
||||
auto const rdown = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, false);
|
||||
BEAST_EXPECT(rup.drops() - rdown.drops() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const big(kMaxXrp);
|
||||
auto const rup = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, true);
|
||||
auto const rdown = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, false);
|
||||
BEAST_EXPECT(rup.drops() - rdown.drops() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const negOne(-1);
|
||||
auto const rup = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, true);
|
||||
auto const rdown = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, false);
|
||||
BEAST_EXPECT(rup.drops() - rdown.drops() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// division by zero
|
||||
XRPAmount one(1);
|
||||
except([&] { mulRatio(one, 1, 0, true); });
|
||||
}
|
||||
|
||||
{
|
||||
// overflow
|
||||
XRPAmount big(kMaxXrp);
|
||||
except([&] { mulRatio(big, 2, 1, true); });
|
||||
}
|
||||
|
||||
{
|
||||
// underflow
|
||||
XRPAmount const bigNegative(kMinXrp + 10);
|
||||
BEAST_EXPECT(mulRatio(bigNegative, 2, 1, true) == kMinXrp);
|
||||
}
|
||||
} // namespace xrpl
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testSigNum();
|
||||
testBeastZero();
|
||||
testComparisons();
|
||||
testAddSub();
|
||||
testDecimal();
|
||||
testFunctions();
|
||||
testMulRatio();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(XRPAmount, basics, xrpl);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,440 +0,0 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/detail/token_errors.h>
|
||||
|
||||
#include <boost/multiprecision/cpp_int.hpp> // IWYU pragma: keep
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#ifndef _MSC_VER
|
||||
|
||||
#include <xrpl/protocol/detail/b58_utils.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <random>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
|
||||
namespace xrpl::test {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
randEngine() -> std::mt19937&
|
||||
{
|
||||
static std::mt19937 kR = [] {
|
||||
std::random_device rd;
|
||||
return std::mt19937{rd()};
|
||||
}();
|
||||
return kR;
|
||||
}
|
||||
|
||||
constexpr int kNumTokenTypeIndexes = 9;
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
tokenTypeAndSize(int i) -> std::tuple<xrpl::TokenType, std::size_t>
|
||||
{
|
||||
assert(i < kNumTokenTypeIndexes);
|
||||
|
||||
switch (i)
|
||||
{
|
||||
using enum xrpl::TokenType;
|
||||
case 0:
|
||||
return {None, 20};
|
||||
case 1:
|
||||
return {NodePublic, 32};
|
||||
case 2:
|
||||
return {NodePublic, 33};
|
||||
case 3:
|
||||
return {NodePrivate, 32};
|
||||
case 4:
|
||||
return {AccountID, 20};
|
||||
case 5:
|
||||
return {AccountPublic, 32};
|
||||
case 6:
|
||||
return {AccountPublic, 33};
|
||||
case 7:
|
||||
return {AccountSecret, 32};
|
||||
case 8:
|
||||
return {FamilySeed, 16};
|
||||
default:
|
||||
throw std::invalid_argument(
|
||||
"Invalid token selection passed to tokenTypeAndSize() "
|
||||
"in " __FILE__);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
randomTokenTypeAndSize() -> std::tuple<xrpl::TokenType, std::size_t>
|
||||
{
|
||||
using namespace xrpl;
|
||||
auto& rng = randEngine();
|
||||
std::uniform_int_distribution<> d(0, 8);
|
||||
return tokenTypeAndSize(d(rng));
|
||||
}
|
||||
|
||||
// Return the token type and subspan of `d` to use as test data.
|
||||
[[nodiscard]] inline auto
|
||||
randomB256TestData(std::span<std::uint8_t> d)
|
||||
-> std::tuple<xrpl::TokenType, std::span<std::uint8_t>>
|
||||
{
|
||||
auto& rng = randEngine();
|
||||
std::uniform_int_distribution<std::uint8_t> dist(0, 255);
|
||||
auto [tokType, tokSize] = randomTokenTypeAndSize();
|
||||
std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); });
|
||||
return {tokType, d.subspan(0, tokSize)};
|
||||
}
|
||||
|
||||
inline void
|
||||
printAsChar(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
|
||||
{
|
||||
auto asString = [](std::span<std::uint8_t> s) {
|
||||
std::string r;
|
||||
r.resize(s.size());
|
||||
std::ranges::copy(s, r.begin());
|
||||
return r;
|
||||
};
|
||||
auto sa = asString(a);
|
||||
auto sb = asString(b);
|
||||
std::cerr << "\n\n" << sa << "\n" << sb << "\n";
|
||||
}
|
||||
|
||||
inline void
|
||||
printAsInt(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
|
||||
{
|
||||
auto asString = [](std::span<std::uint8_t> s) -> std::string {
|
||||
std::stringstream sstr;
|
||||
for (auto i : s)
|
||||
{
|
||||
sstr << std::setw(3) << int(i) << ',';
|
||||
}
|
||||
return sstr.str();
|
||||
};
|
||||
auto sa = asString(a);
|
||||
auto sb = asString(b);
|
||||
std::cerr << "\n\n" << sa << "\n" << sb << "\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace multiprecision_utils {
|
||||
|
||||
boost::multiprecision::checked_uint512_t
|
||||
toBoostMP(std::span<std::uint64_t> in)
|
||||
{
|
||||
boost::multiprecision::checked_uint512_t mbp = 0;
|
||||
for (auto& word : std::views::reverse(in))
|
||||
{
|
||||
mbp <<= 64;
|
||||
mbp += word;
|
||||
}
|
||||
return mbp;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t>
|
||||
randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5)
|
||||
{
|
||||
auto eng = randEngine();
|
||||
std::uniform_int_distribution<std::uint8_t> numCoeffDist(minSize, maxSize);
|
||||
std::uniform_int_distribution<std::uint64_t> dist;
|
||||
auto const numCoeff = numCoeffDist(eng);
|
||||
std::vector<std::uint64_t> coeffs;
|
||||
coeffs.reserve(numCoeff);
|
||||
for (int i = 0; i < numCoeff; ++i)
|
||||
{
|
||||
coeffs.push_back(dist(eng));
|
||||
}
|
||||
return coeffs;
|
||||
}
|
||||
} // namespace multiprecision_utils
|
||||
|
||||
class base58_test : public beast::unit_test::Suite
|
||||
{
|
||||
void
|
||||
testMultiprecision()
|
||||
{
|
||||
testcase("b58_multiprecision");
|
||||
|
||||
using namespace boost::multiprecision;
|
||||
|
||||
static constexpr std::size_t kIters = 100000;
|
||||
auto eng = randEngine();
|
||||
std::uniform_int_distribution<std::uint64_t> dist;
|
||||
std::uniform_int_distribution<std::uint64_t> dist1(1);
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
if (d == 0u)
|
||||
continue;
|
||||
auto bigInt = multiprecision_utils::randomBigInt();
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refDiv = boostBigInt / d;
|
||||
auto const refMod = boostBigInt % d;
|
||||
|
||||
auto const mod = b58_fast::detail::inplaceBigintDivRem(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
auto const foundDiv = multiprecision_utils::toBoostMP(bigInt);
|
||||
BEAST_EXPECT(refMod.convert_to<std::uint64_t>() == mod);
|
||||
BEAST_EXPECT(foundDiv == refDiv);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2);
|
||||
if (bigInt[bigInt.size() - 1] == std::numeric_limits<std::uint64_t>::max())
|
||||
{
|
||||
bigInt[bigInt.size() - 1] -= 1; // Prevent overflow
|
||||
}
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refAdd = boostBigInt + d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintAdd(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
BEAST_EXPECT(result == TokenCodecErrc::Success);
|
||||
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
|
||||
BEAST_EXPECT(refAdd == foundAdd);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist1(eng);
|
||||
// Force overflow
|
||||
std::vector<std::uint64_t> bigInt(5, std::numeric_limits<std::uint64_t>::max());
|
||||
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refAdd = boostBigInt + d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintAdd(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
BEAST_EXPECT(result == TokenCodecErrc::OverflowAdd);
|
||||
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
|
||||
BEAST_EXPECT(refAdd != foundAdd);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2);
|
||||
// inplace mul requires the most significant coeff to be zero to
|
||||
// hold the result.
|
||||
bigInt[bigInt.size() - 1] = 0;
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refMul = boostBigInt * d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintMul(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
BEAST_EXPECT(result == TokenCodecErrc::Success);
|
||||
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
|
||||
BEAST_EXPECT(refMul == foundMul);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist1(eng);
|
||||
// Force overflow
|
||||
std::vector<std::uint64_t> bigInt(5, std::numeric_limits<std::uint64_t>::max());
|
||||
auto const boostBigInt = multiprecision_utils::toBoostMP(
|
||||
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refMul = boostBigInt * d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintMul(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
BEAST_EXPECT(result == TokenCodecErrc::InputTooLarge);
|
||||
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
|
||||
BEAST_EXPECT(refMul != foundMul);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFastMatchesRef()
|
||||
{
|
||||
testcase("fast_matches_ref");
|
||||
auto testRawEncode = [&](std::span<std::uint8_t> const& b256Data) {
|
||||
std::array<std::uint8_t, 64> b58ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b58Result;
|
||||
|
||||
std::array<std::uint8_t, 64> b256ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b256Result;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b58ResultBuf[i]};
|
||||
if (i == 0)
|
||||
{
|
||||
auto const r = xrpl::b58_fast::detail::b256ToB58Be(b256Data, outBuf);
|
||||
BEAST_EXPECT(r);
|
||||
b58Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::array<std::uint8_t, 128> tmpBuf{};
|
||||
std::string const s = xrpl::b58_ref::detail::encodeBase58(
|
||||
b256Data.data(), b256Data.size(), tmpBuf.data(), tmpBuf.size());
|
||||
BEAST_EXPECT(s.size());
|
||||
b58Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b58Result[i].begin());
|
||||
}
|
||||
}
|
||||
if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size()))
|
||||
{
|
||||
if (!BEAST_EXPECT(
|
||||
memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0))
|
||||
{
|
||||
printAsChar(b58Result[0], b58Result[1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
std::string const in(
|
||||
b58Result[i].data(), b58Result[i].data() + b58Result[i].size());
|
||||
auto const r = xrpl::b58_fast::detail::b58ToB256Be(in, outBuf);
|
||||
BEAST_EXPECT(r);
|
||||
b256Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const st(b58Result[i].begin(), b58Result[i].end());
|
||||
std::string const s = xrpl::b58_ref::detail::decodeBase58(st);
|
||||
BEAST_EXPECT(s.size());
|
||||
b256Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b256Result[i].begin());
|
||||
}
|
||||
}
|
||||
|
||||
if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size()))
|
||||
{
|
||||
if (!BEAST_EXPECT(
|
||||
memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) ==
|
||||
0))
|
||||
{
|
||||
printAsInt(b256Result[0], b256Result[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto testTokenEncode = [&](xrpl::TokenType const tokType,
|
||||
std::span<std::uint8_t> const& b256Data) {
|
||||
std::array<std::uint8_t, 64> b58ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b58Result;
|
||||
|
||||
std::array<std::uint8_t, 64> b256ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b256Result;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b58ResultBuf[i].data(), b58ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
auto const r = xrpl::b58_fast::encodeBase58Token(tokType, b256Data, outBuf);
|
||||
BEAST_EXPECT(r);
|
||||
b58Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const s =
|
||||
xrpl::b58_ref::encodeBase58Token(tokType, b256Data.data(), b256Data.size());
|
||||
BEAST_EXPECT(s.size());
|
||||
b58Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b58Result[i].begin());
|
||||
}
|
||||
}
|
||||
if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size()))
|
||||
{
|
||||
if (!BEAST_EXPECT(
|
||||
memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0))
|
||||
{
|
||||
printAsChar(b58Result[0], b58Result[1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
std::string const in(
|
||||
b58Result[i].data(), b58Result[i].data() + b58Result[i].size());
|
||||
auto const r = xrpl::b58_fast::decodeBase58Token(tokType, in, outBuf);
|
||||
BEAST_EXPECT(r);
|
||||
b256Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const st(b58Result[i].begin(), b58Result[i].end());
|
||||
std::string const s = xrpl::b58_ref::decodeBase58Token(st, tokType);
|
||||
BEAST_EXPECT(s.size());
|
||||
b256Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b256Result[i].begin());
|
||||
}
|
||||
}
|
||||
|
||||
if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size()))
|
||||
{
|
||||
if (!BEAST_EXPECT(
|
||||
memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) ==
|
||||
0))
|
||||
{
|
||||
printAsInt(b256Result[0], b256Result[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto testIt = [&](xrpl::TokenType const tokType, std::span<std::uint8_t> const& b256Data) {
|
||||
testRawEncode(b256Data);
|
||||
testTokenEncode(tokType, b256Data);
|
||||
};
|
||||
|
||||
// test every token type with data where every byte is the same and the
|
||||
// bytes range from 0-255
|
||||
for (int i = 0; i < kNumTokenTypeIndexes; ++i)
|
||||
{
|
||||
std::array<std::uint8_t, 128> b256DataBuf{};
|
||||
auto const [tokType, tokSize] = tokenTypeAndSize(i);
|
||||
for (int d = 0; d <= 255; ++d)
|
||||
{
|
||||
memset(b256DataBuf.data(), d, tokSize);
|
||||
testIt(tokType, std::span(b256DataBuf.data(), tokSize));
|
||||
}
|
||||
}
|
||||
|
||||
// test with random data
|
||||
static constexpr std::size_t kIters = 100000;
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::array<std::uint8_t, 128> b256DataBuf{};
|
||||
auto const [tokType, b256Data] = randomB256TestData(b256DataBuf);
|
||||
testIt(tokType, b256Data);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testMultiprecision();
|
||||
testFastMatchesRef();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(base58, basics, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
|
||||
#endif // _MSC_VER
|
||||
@@ -1,376 +0,0 @@
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/hardened_hash.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
|
||||
#include <boost/endian/detail/order.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <complex>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// a non-hashing Hasher that just copies the bytes.
|
||||
// Used to test hash_append in base_uint
|
||||
template <std::size_t Bits>
|
||||
struct Nonhash
|
||||
{
|
||||
static constexpr auto kEndian = boost::endian::order::big;
|
||||
static constexpr std::size_t kWidth = Bits / 8;
|
||||
|
||||
std::array<std::uint8_t, kWidth> data;
|
||||
|
||||
Nonhash() = default;
|
||||
|
||||
void
|
||||
operator()(void const* key, std::size_t len) noexcept
|
||||
{
|
||||
assert(len == kWidth);
|
||||
memcpy(data.data(), key, len);
|
||||
}
|
||||
|
||||
explicit
|
||||
operator std::size_t() noexcept
|
||||
{
|
||||
return kWidth;
|
||||
}
|
||||
};
|
||||
|
||||
struct base_uint_test : beast::unit_test::Suite
|
||||
{
|
||||
using test96 = BaseUInt<96>;
|
||||
static_assert(std::is_copy_constructible_v<test96>);
|
||||
static_assert(std::is_copy_assignable_v<test96>);
|
||||
|
||||
void
|
||||
testComparisons()
|
||||
{
|
||||
{
|
||||
static constexpr std::array<std::pair<std::string_view, std::string_view>, 6> kTestArgs{
|
||||
{{"0000000000000000", "0000000000000001"},
|
||||
{"0000000000000000", "ffffffffffffffff"},
|
||||
{"1234567812345678", "2345678923456789"},
|
||||
{"8000000000000000", "8000000000000001"},
|
||||
{"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"},
|
||||
{"fffffffffffffffe", "ffffffffffffffff"}}};
|
||||
|
||||
for (auto const& arg : kTestArgs)
|
||||
{
|
||||
xrpl::BaseUInt<64> const u{arg.first}, v{arg.second};
|
||||
BEAST_EXPECT(u < v);
|
||||
BEAST_EXPECT(u <= v);
|
||||
BEAST_EXPECT(u != v);
|
||||
BEAST_EXPECT(!(u == v));
|
||||
BEAST_EXPECT(!(u > v));
|
||||
BEAST_EXPECT(!(u >= v));
|
||||
BEAST_EXPECT(!(v < u));
|
||||
BEAST_EXPECT(!(v <= u));
|
||||
BEAST_EXPECT(v != u);
|
||||
BEAST_EXPECT(!(v == u));
|
||||
BEAST_EXPECT(v > u);
|
||||
BEAST_EXPECT(v >= u);
|
||||
BEAST_EXPECT(u == u);
|
||||
BEAST_EXPECT(v == v);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
static constexpr std::array<std::pair<std::string_view, std::string_view>, 6> kTestArgs{
|
||||
{
|
||||
{"000000000000000000000000", "000000000000000000000001"},
|
||||
{"000000000000000000000000", "ffffffffffffffffffffffff"},
|
||||
{"0123456789ab0123456789ab", "123456789abc123456789abc"},
|
||||
{"555555555555555555555555", "55555555555a555555555555"},
|
||||
{"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||
{"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"},
|
||||
}};
|
||||
|
||||
for (auto const& arg : kTestArgs)
|
||||
{
|
||||
xrpl::BaseUInt<96> const u{arg.first}, v{arg.second};
|
||||
BEAST_EXPECT(u < v);
|
||||
BEAST_EXPECT(u <= v);
|
||||
BEAST_EXPECT(u != v);
|
||||
BEAST_EXPECT(!(u == v));
|
||||
BEAST_EXPECT(!(u > v));
|
||||
BEAST_EXPECT(!(u >= v));
|
||||
BEAST_EXPECT(!(v < u));
|
||||
BEAST_EXPECT(!(v <= u));
|
||||
BEAST_EXPECT(v != u);
|
||||
BEAST_EXPECT(!(v == u));
|
||||
BEAST_EXPECT(v > u);
|
||||
BEAST_EXPECT(v >= u);
|
||||
BEAST_EXPECT(u == u);
|
||||
BEAST_EXPECT(v == v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFromRawSizeMismatch()
|
||||
{
|
||||
testcase("base_uint: fromRaw size mismatch");
|
||||
|
||||
// Container larger than the base_uint (16 bytes vs 12 bytes for test96).
|
||||
// Only the first 12 bytes are copied; the extra bytes are ignored.
|
||||
{
|
||||
Blob const tooBig{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
test96 const result = test96::fromRaw(tooBig);
|
||||
BEAST_EXPECT(to_string(result) == "0102030405060708090A0B0C");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testcase("base_uint: general purpose tests");
|
||||
|
||||
#ifdef NDEBUG
|
||||
testFromRawSizeMismatch();
|
||||
#endif
|
||||
|
||||
static_assert(!std::is_constructible_v<test96, std::complex<double>>);
|
||||
static_assert(!std::is_assignable_v<test96&, std::complex<double>>);
|
||||
|
||||
testComparisons();
|
||||
|
||||
// used to verify set insertion (hashing required)
|
||||
std::unordered_set<test96, HardenedHash<>> uset;
|
||||
|
||||
Blob const raw{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||
BEAST_EXPECT(test96::kBytes == raw.size());
|
||||
|
||||
test96 u = test96::fromRaw(raw);
|
||||
uset.insert(u);
|
||||
BEAST_EXPECT(raw.size() == u.size());
|
||||
BEAST_EXPECT(to_string(u) == "0102030405060708090A0B0C");
|
||||
BEAST_EXPECT(toShortString(u) == "01020304...");
|
||||
BEAST_EXPECT(*u.data() == 1);
|
||||
BEAST_EXPECT(u.signum() == 1);
|
||||
BEAST_EXPECT(!!u);
|
||||
BEAST_EXPECT(!u.isZero());
|
||||
BEAST_EXPECT(u.isNonZero());
|
||||
unsigned char t = 0;
|
||||
for (auto& d : u)
|
||||
{
|
||||
BEAST_EXPECT(d == ++t);
|
||||
}
|
||||
|
||||
// Test hash_append by "hashing" with a no-op hasher (h)
|
||||
// and then extracting the bytes that were written during hashing
|
||||
// back into another base_uint (w) for comparison with the original
|
||||
Nonhash<96> h{};
|
||||
hash_append(h, u);
|
||||
test96 const w = test96::fromRaw(std::vector<std::uint8_t>(h.data.begin(), h.data.end()));
|
||||
BEAST_EXPECT(w == u);
|
||||
|
||||
test96 v{~u};
|
||||
uset.insert(v);
|
||||
BEAST_EXPECT(to_string(v) == "FEFDFCFBFAF9F8F7F6F5F4F3");
|
||||
BEAST_EXPECT(toShortString(v) == "FEFDFCFB...");
|
||||
BEAST_EXPECT(*v.data() == 0xfe);
|
||||
BEAST_EXPECT(v.signum() == 1);
|
||||
BEAST_EXPECT(!!v);
|
||||
BEAST_EXPECT(!v.isZero());
|
||||
BEAST_EXPECT(v.isNonZero());
|
||||
t = 0xff;
|
||||
for (auto& d : v)
|
||||
{
|
||||
BEAST_EXPECT(d == --t);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(u < v);
|
||||
BEAST_EXPECT(v > u);
|
||||
|
||||
v = u;
|
||||
BEAST_EXPECT(v == u);
|
||||
|
||||
test96 z{beast::kZero};
|
||||
uset.insert(z);
|
||||
BEAST_EXPECT(to_string(z) == "000000000000000000000000");
|
||||
BEAST_EXPECT(toShortString(z) == "00000000...");
|
||||
BEAST_EXPECT(*z.data() == 0);
|
||||
BEAST_EXPECT(*z.begin() == 0);
|
||||
BEAST_EXPECT(*std::prev(z.end(), 1) == 0);
|
||||
BEAST_EXPECT(z.signum() == 0);
|
||||
BEAST_EXPECT(!z);
|
||||
BEAST_EXPECT(z.isZero());
|
||||
BEAST_EXPECT(!z.isNonZero());
|
||||
for (auto& d : z)
|
||||
{
|
||||
BEAST_EXPECT(d == 0);
|
||||
}
|
||||
|
||||
test96 n{z};
|
||||
n++;
|
||||
BEAST_EXPECT(n == test96(1));
|
||||
n--;
|
||||
BEAST_EXPECT(n == beast::kZero);
|
||||
BEAST_EXPECT(n == z);
|
||||
n--;
|
||||
BEAST_EXPECT(to_string(n) == "FFFFFFFFFFFFFFFFFFFFFFFF");
|
||||
BEAST_EXPECT(toShortString(n) == "FFFFFFFF...");
|
||||
n = beast::kZero;
|
||||
BEAST_EXPECT(n == z);
|
||||
|
||||
test96 zp1{z};
|
||||
zp1++;
|
||||
test96 zm1{z};
|
||||
zm1--;
|
||||
test96 const x{zm1 ^ zp1};
|
||||
uset.insert(x);
|
||||
BEAST_EXPECTS(to_string(x) == "FFFFFFFFFFFFFFFFFFFFFFFE", to_string(x));
|
||||
BEAST_EXPECTS(toShortString(x) == "FFFFFFFF...", toShortString(x));
|
||||
|
||||
BEAST_EXPECT(uset.size() == 4);
|
||||
|
||||
test96 tmp;
|
||||
BEAST_EXPECT(tmp.parseHex(to_string(u)));
|
||||
BEAST_EXPECT(tmp == u);
|
||||
tmp = z;
|
||||
|
||||
// fails with extra char
|
||||
BEAST_EXPECT(!tmp.parseHex("A" + to_string(u)));
|
||||
tmp = z;
|
||||
|
||||
// fails with extra char at end
|
||||
BEAST_EXPECT(!tmp.parseHex(to_string(u) + "A"));
|
||||
|
||||
// fails with a non-hex character at some point in the string:
|
||||
tmp = z;
|
||||
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string x = to_string(z);
|
||||
x[i] = ('G' + (i % 10));
|
||||
BEAST_EXPECT(!tmp.parseHex(x));
|
||||
}
|
||||
|
||||
// Walking 1s:
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string s1 = "000000000000000000000000";
|
||||
s1[i] = '1';
|
||||
|
||||
BEAST_EXPECT(tmp.parseHex(s1));
|
||||
BEAST_EXPECT(to_string(tmp) == s1);
|
||||
}
|
||||
|
||||
// Walking 0s:
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string s1 = "111111111111111111111111";
|
||||
s1[i] = '0';
|
||||
|
||||
BEAST_EXPECT(tmp.parseHex(s1));
|
||||
BEAST_EXPECT(to_string(tmp) == s1);
|
||||
}
|
||||
|
||||
// Constexpr constructors
|
||||
{
|
||||
static_assert(test96{}.signum() == 0);
|
||||
static_assert(test96("0").signum() == 0);
|
||||
static_assert(test96("000000000000000000000000").signum() == 0);
|
||||
static_assert(test96("000000000000000000000001").signum() == 1);
|
||||
static_assert(test96("800000000000000000000000").signum() == 1);
|
||||
|
||||
// Everything within the #if should fail during compilation.
|
||||
#if 0
|
||||
// Too few characters
|
||||
static_assert(test96("00000000000000000000000").signum() == 0);
|
||||
|
||||
// Too many characters
|
||||
static_assert(test96("0000000000000000000000000").signum() == 0);
|
||||
|
||||
// Non-hex characters
|
||||
static_assert(test96("00000000000000000000000 ").signum() == 1);
|
||||
static_assert(test96("00000000000000000000000/").signum() == 1);
|
||||
static_assert(test96("00000000000000000000000:").signum() == 1);
|
||||
static_assert(test96("00000000000000000000000@").signum() == 1);
|
||||
static_assert(test96("00000000000000000000000G").signum() == 1);
|
||||
static_assert(test96("00000000000000000000000`").signum() == 1);
|
||||
static_assert(test96("00000000000000000000000g").signum() == 1);
|
||||
static_assert(test96("00000000000000000000000~").signum() == 1);
|
||||
#endif // 0
|
||||
|
||||
// Using the constexpr constructor in a non-constexpr context
|
||||
// with an error in the parsing throws an exception.
|
||||
{
|
||||
// Invalid length for string.
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
// Try to prevent constant evaluation.
|
||||
std::vector<char> str(23, '7');
|
||||
std::string_view const sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 const t96(sView);
|
||||
}
|
||||
catch (std::invalid_argument const& e)
|
||||
{
|
||||
BEAST_EXPECT(e.what() == std::string("invalid length for hex string"));
|
||||
caught = true;
|
||||
}
|
||||
BEAST_EXPECT(caught);
|
||||
}
|
||||
{
|
||||
// Invalid character in string.
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
// Try to prevent constant evaluation.
|
||||
std::vector<char> str(23, '7');
|
||||
str.push_back('G');
|
||||
std::string_view const sView(str.data(), str.size());
|
||||
[[maybe_unused]] test96 const t96(sView);
|
||||
}
|
||||
catch (std::range_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(e.what() == std::string("invalid hex character"));
|
||||
caught = true;
|
||||
}
|
||||
BEAST_EXPECT(caught);
|
||||
}
|
||||
|
||||
// Verify that constexpr base_uints interpret a string the same
|
||||
// way parseHex() does.
|
||||
struct StrBaseUInt
|
||||
{
|
||||
char const* const str;
|
||||
test96 tst;
|
||||
|
||||
constexpr StrBaseUInt(char const* s) : str(s), tst(s)
|
||||
{
|
||||
}
|
||||
};
|
||||
static constexpr StrBaseUInt kTestCases[] = {
|
||||
"000000000000000000000000",
|
||||
"000000000000000000000001",
|
||||
"fedcba9876543210ABCDEF91",
|
||||
"19FEDCBA0123456789abcdef",
|
||||
"800000000000000000000000",
|
||||
"fFfFfFfFfFfFfFfFfFfFfFfF"};
|
||||
|
||||
for (StrBaseUInt const& t : kTestCases)
|
||||
{
|
||||
test96 t96;
|
||||
BEAST_EXPECT(t96.parseHex(t.str));
|
||||
BEAST_EXPECT(t96 == t.tst);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(base_uint, basics, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,83 +0,0 @@
|
||||
#include <test/jtx/Account.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/join.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
struct join_test : beast::unit_test::Suite
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
auto test = [this](auto collectionanddelimiter, std::string expected) {
|
||||
std::stringstream ss;
|
||||
// Put something else in the buffer before and after to ensure that
|
||||
// the << operator returns the stream correctly.
|
||||
ss << "(" << collectionanddelimiter << ")";
|
||||
auto const str = ss.str();
|
||||
BEAST_EXPECT(str.substr(1, str.length() - 2) == expected);
|
||||
BEAST_EXPECT(str.front() == '(');
|
||||
BEAST_EXPECT(str.back() == ')');
|
||||
};
|
||||
|
||||
// C++ array
|
||||
test(CollectionAndDelimiter(std::array<int, 4>{2, -1, 5, 10}, "/"), "2/-1/5/10");
|
||||
// One item C++ array edge case
|
||||
test(CollectionAndDelimiter(std::array<std::string, 1>{"test"}, " & "), "test");
|
||||
// Empty C++ array edge case
|
||||
test(CollectionAndDelimiter(std::array<int, 0>{}, ","), "");
|
||||
{
|
||||
// C-style array
|
||||
char letters[4]{'w', 'a', 's', 'd'};
|
||||
test(CollectionAndDelimiter(letters, std::to_string(0)), "w0a0s0d");
|
||||
}
|
||||
{
|
||||
// Auto sized C-style array
|
||||
std::string words[]{"one", "two", "three", "four"};
|
||||
test(CollectionAndDelimiter(words, "\n"), "one\ntwo\nthree\nfour");
|
||||
}
|
||||
{
|
||||
// One item C-style array edge case
|
||||
std::string words[]{"thing"};
|
||||
test(CollectionAndDelimiter(words, "\n"), "thing");
|
||||
}
|
||||
// Initializer list
|
||||
test(CollectionAndDelimiter(std::initializer_list<size_t>{19, 25}, "+"), "19+25");
|
||||
// vector
|
||||
test(CollectionAndDelimiter(std::vector<int>{0, 42}, std::to_string(99)), "09942");
|
||||
{
|
||||
// vector with one item edge case
|
||||
using namespace jtx;
|
||||
test(
|
||||
CollectionAndDelimiter(std::vector<Account>{Account::kMaster}, "xxx"),
|
||||
Account::kMaster.human());
|
||||
}
|
||||
// empty vector edge case
|
||||
test(CollectionAndDelimiter(std::vector<uint256>{}, ","), "");
|
||||
// C-style string
|
||||
test(CollectionAndDelimiter("string", " "), "s t r i n g");
|
||||
// Empty C-style string edge case
|
||||
test(CollectionAndDelimiter("", "*"), "");
|
||||
// Single char C-style string edge case
|
||||
test(CollectionAndDelimiter("x", "*"), "x");
|
||||
// std::string
|
||||
test(CollectionAndDelimiter(std::string{"string"}, "-"), "s-t-r-i-n-g");
|
||||
// Empty std::string edge case
|
||||
test(CollectionAndDelimiter(std::string{""}, "*"), "");
|
||||
// Single char std::string edge case
|
||||
test(CollectionAndDelimiter(std::string{"y"}, "*"), "y");
|
||||
}
|
||||
}; // namespace test
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(join, basics, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -63,8 +63,8 @@ public:
|
||||
negotiateProtocolVersion("RTXP/1.2, XRPL/2.0, XRPL/2.1") == makeProtocol(2, 1));
|
||||
BEAST_EXPECT(negotiateProtocolVersion("XRPL/2.2") == makeProtocol(2, 2));
|
||||
BEAST_EXPECT(
|
||||
negotiateProtocolVersion("RTXP/1.2, XRPL/2.2, XRPL/2.3, XRPL/999.999") ==
|
||||
makeProtocol(2, 2));
|
||||
negotiateProtocolVersion("RTXP/1.2, XRPL/2.3, XRPL/2.4, XRPL/999.999") ==
|
||||
makeProtocol(2, 3));
|
||||
BEAST_EXPECT(negotiateProtocolVersion("XRPL/999.999, WebSocket/1.0") == std::nullopt);
|
||||
BEAST_EXPECT(negotiateProtocolVersion("") == std::nullopt);
|
||||
}
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/insight/NullCollector.h>
|
||||
#include <xrpl/beast/net/IPAddressV4.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/resource/Disposition.h>
|
||||
#include <xrpl/resource/Gossip.h>
|
||||
#include <xrpl/resource/detail/Logic.h>
|
||||
#include <xrpl/resource/detail/Tuning.h>
|
||||
|
||||
#include <boost/utility/base_from_member.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
|
||||
class ResourceManager_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
class TestLogic : private boost::base_from_member<TestStopwatch>, public Logic
|
||||
|
||||
{
|
||||
private:
|
||||
using clock_type = boost::base_from_member<TestStopwatch>;
|
||||
|
||||
public:
|
||||
explicit TestLogic(beast::Journal journal)
|
||||
: Logic(beast::insight::NullCollector::make(), member, journal)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
advance()
|
||||
{
|
||||
++member;
|
||||
}
|
||||
|
||||
TestStopwatch&
|
||||
clock()
|
||||
{
|
||||
return member;
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
createGossip(Gossip& gossip)
|
||||
{
|
||||
std::uint8_t const v(10 + randInt(9));
|
||||
std::uint8_t const n(10 + randInt(9));
|
||||
gossip.items.reserve(n);
|
||||
for (std::uint8_t i = 0; i < n; ++i)
|
||||
{
|
||||
Gossip::Item item;
|
||||
item.balance = 100 + randInt(499);
|
||||
beast::IP::AddressV4::bytes_type const d = {
|
||||
{192, 0, 2, static_cast<std::uint8_t>(v + i)}};
|
||||
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
|
||||
gossip.items.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
testDrop(beast::Journal j, bool limited)
|
||||
{
|
||||
if (limited)
|
||||
{
|
||||
testcase("Limited warn/drop");
|
||||
}
|
||||
else
|
||||
{
|
||||
testcase("Unlimited warn/drop");
|
||||
}
|
||||
|
||||
TestLogic logic(j);
|
||||
|
||||
Charge const fee(kDropThreshold + 1);
|
||||
beast::IP::Endpoint const addr(beast::IP::Endpoint::fromString("192.0.2.2"));
|
||||
|
||||
std::function<Consumer(beast::IP::Endpoint)> const ep =
|
||||
[&logic, limited](beast::IP::Endpoint const& address) {
|
||||
return limited ? logic.newInboundEndpoint(address)
|
||||
: logic.newUnlimitedEndpoint(address);
|
||||
};
|
||||
|
||||
{
|
||||
Consumer c(ep(addr));
|
||||
|
||||
// Create load until we get a warning
|
||||
int n = 10000;
|
||||
|
||||
while (--n >= 0)
|
||||
{
|
||||
if (n == 0)
|
||||
{
|
||||
if (limited)
|
||||
{
|
||||
fail("Loop count exceeded without warning");
|
||||
}
|
||||
else
|
||||
{
|
||||
pass();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (c.charge(fee) == Disposition::Warn)
|
||||
{
|
||||
if (limited)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
else
|
||||
{
|
||||
fail("Should loop forever with no warning");
|
||||
}
|
||||
break;
|
||||
}
|
||||
++logic.clock();
|
||||
}
|
||||
|
||||
// Create load until we get dropped
|
||||
while (--n >= 0)
|
||||
{
|
||||
if (n == 0)
|
||||
{
|
||||
if (limited)
|
||||
{
|
||||
fail("Loop count exceeded without dropping");
|
||||
}
|
||||
else
|
||||
{
|
||||
pass();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (c.charge(fee) == Disposition::Drop)
|
||||
{
|
||||
// Disconnect abusive Consumer
|
||||
BEAST_EXPECT(c.disconnect(j) == limited);
|
||||
break;
|
||||
}
|
||||
++logic.clock();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the consumer is on the blacklist for a while.
|
||||
{
|
||||
Consumer const c(logic.newInboundEndpoint(addr));
|
||||
logic.periodicActivity();
|
||||
if (c.disposition() != Disposition::Drop)
|
||||
{
|
||||
if (limited)
|
||||
{
|
||||
fail("Dropped consumer not put on blacklist");
|
||||
}
|
||||
else
|
||||
{
|
||||
pass();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Makes sure the Consumer is eventually removed from blacklist
|
||||
bool readmitted = false;
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
// Give Consumer time to become readmitted. Should never
|
||||
// exceed expiration time.
|
||||
auto n = kSecondsUntilExpiration + 1s;
|
||||
while (--n > 0s)
|
||||
{
|
||||
++logic.clock();
|
||||
logic.periodicActivity();
|
||||
Consumer const c(logic.newInboundEndpoint(addr));
|
||||
if (c.disposition() != Disposition::Drop)
|
||||
{
|
||||
readmitted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!readmitted)
|
||||
{
|
||||
fail("Dropped Consumer left on blacklist too long");
|
||||
return;
|
||||
}
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
testImports(beast::Journal j)
|
||||
{
|
||||
testcase("Imports");
|
||||
|
||||
TestLogic logic(j);
|
||||
|
||||
Gossip g[5];
|
||||
|
||||
for (auto& gossip : g)
|
||||
createGossip(gossip);
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
logic.importConsumers(std::to_string(i), g[i]);
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
testImport(beast::Journal j)
|
||||
{
|
||||
testcase("Import");
|
||||
|
||||
TestLogic logic(j);
|
||||
|
||||
Gossip g;
|
||||
Gossip::Item item;
|
||||
item.balance = 100;
|
||||
beast::IP::AddressV4::bytes_type const d = {{192, 0, 2, 1}};
|
||||
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
|
||||
g.items.push_back(item);
|
||||
|
||||
logic.importConsumers("g", g);
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
testCharges(beast::Journal j)
|
||||
{
|
||||
testcase("Charge");
|
||||
|
||||
TestLogic logic(j);
|
||||
|
||||
{
|
||||
beast::IP::Endpoint const address(beast::IP::Endpoint::fromString("192.0.2.1"));
|
||||
Consumer c(logic.newInboundEndpoint(address));
|
||||
Charge const fee(1000);
|
||||
JLOG(j.info()) << "Charging " << c.toString() << " " << fee << " per second";
|
||||
c.charge(fee);
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
JLOG(j.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
|
||||
<< ", Balance = " << c.balance();
|
||||
logic.advance();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
beast::IP::Endpoint const address(beast::IP::Endpoint::fromString("192.0.2.2"));
|
||||
Consumer c(logic.newInboundEndpoint(address));
|
||||
Charge const fee(1000);
|
||||
JLOG(j.info()) << "Charging " << c.toString() << " " << fee << " per second";
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
c.charge(fee);
|
||||
JLOG(j.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
|
||||
<< ", Balance = " << c.balance();
|
||||
logic.advance();
|
||||
}
|
||||
}
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using beast::Severity;
|
||||
test::SuiteJournal journal("ResourceManager_test", *this);
|
||||
|
||||
testDrop(journal, true);
|
||||
testDrop(journal, false);
|
||||
testCharges(journal);
|
||||
testImports(journal);
|
||||
testImport(journal);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ResourceManager, resource, xrpl);
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
@@ -1,177 +0,0 @@
|
||||
#include <test/shamap/common.h>
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
class SHAMapSync_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
beast::xor_shift_engine eng;
|
||||
|
||||
boost::intrusive_ptr<SHAMapItem>
|
||||
makeRandomAS()
|
||||
{
|
||||
Serializer s;
|
||||
|
||||
for (int d = 0; d < 3; ++d)
|
||||
s.add32(randInt<std::uint32_t>(eng));
|
||||
return makeShamapitem(s.getSHA512Half(), s.slice());
|
||||
}
|
||||
|
||||
bool
|
||||
confuseMap(SHAMap& map, int count)
|
||||
{
|
||||
// add a bunch of random states to a map, then remove them
|
||||
// map should be the same
|
||||
SHAMapHash const beforeHash = map.getHash();
|
||||
|
||||
std::list<uint256> items;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
auto item = makeRandomAS();
|
||||
items.push_back(item->key());
|
||||
|
||||
if (!map.addItem(SHAMapNodeType::TnAccountState, item))
|
||||
{
|
||||
log << "Unable to add item to map\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& item : items)
|
||||
{
|
||||
if (!map.delItem(item))
|
||||
{
|
||||
log << "Unable to remove item from map\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (beforeHash != map.getHash())
|
||||
{
|
||||
log << "Hashes do not match " << beforeHash << " " << map.getHash() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using beast::Severity;
|
||||
test::SuiteJournal journal("SHAMapSync_test", *this);
|
||||
|
||||
TestNodeFamily f(journal), f2(journal);
|
||||
SHAMap source(SHAMapType::FREE, f);
|
||||
SHAMap destination(SHAMapType::FREE, f2);
|
||||
|
||||
int const items = 10000;
|
||||
for (int i = 0; i < items; ++i)
|
||||
{
|
||||
source.addItem(SHAMapNodeType::TnAccountState, makeRandomAS());
|
||||
if (i % 100 == 0)
|
||||
source.invariants();
|
||||
}
|
||||
|
||||
source.invariants();
|
||||
BEAST_EXPECT(confuseMap(source, 500));
|
||||
source.invariants();
|
||||
|
||||
source.setImmutable();
|
||||
|
||||
int count = 0;
|
||||
source.visitLeaves([&count](auto const& item) { ++count; });
|
||||
BEAST_EXPECT(count == items);
|
||||
|
||||
std::vector<SHAMapMissingNode> missingNodes;
|
||||
source.walkMap(missingNodes, 2048);
|
||||
BEAST_EXPECT(missingNodes.empty());
|
||||
|
||||
destination.setSynching();
|
||||
|
||||
{
|
||||
std::vector<std::pair<SHAMapNodeID, Blob>> a;
|
||||
|
||||
BEAST_EXPECT(source.getNodeFat(SHAMapNodeID(), a, randBool(eng), randInt(eng, 2)));
|
||||
|
||||
unexpected(a.empty(), "NodeSize");
|
||||
|
||||
BEAST_EXPECT(destination.addRootNode(source.getHash(), makeSlice(a[0].second), nullptr)
|
||||
.isGood());
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
f.clock().advance(std::chrono::seconds(1));
|
||||
|
||||
// get the list of nodes we know we need
|
||||
auto nodesMissing = destination.getMissingNodes(2048, nullptr);
|
||||
|
||||
if (nodesMissing.empty())
|
||||
break;
|
||||
|
||||
// get as many nodes as possible based on this information
|
||||
std::vector<std::pair<SHAMapNodeID, Blob>> b;
|
||||
|
||||
for (auto& it : nodesMissing)
|
||||
{
|
||||
// Don't use BEAST_EXPECT here b/c it will be called a
|
||||
// non-deterministic number of times and the number of tests run
|
||||
// should be deterministic
|
||||
if (!source.getNodeFat(it.first, b, randBool(eng), randInt(eng, 2)))
|
||||
fail("", __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
// Don't use BEAST_EXPECT here b/c it will be called a
|
||||
// non-deterministic number of times and the number of tests run
|
||||
// should be deterministic
|
||||
if (b.empty())
|
||||
fail("", __FILE__, __LINE__);
|
||||
|
||||
for (auto& node : b)
|
||||
{
|
||||
// Don't use BEAST_EXPECT here b/c it will be called a
|
||||
// non-deterministic number of times and the number of tests run
|
||||
// should be deterministic
|
||||
if (!destination.addKnownNode(node.first, makeSlice(node.second), nullptr)
|
||||
.isUseful())
|
||||
fail("", __FILE__, __LINE__);
|
||||
}
|
||||
} while (true);
|
||||
|
||||
destination.clearSynching();
|
||||
|
||||
BEAST_EXPECT(source.deepCompare(destination));
|
||||
|
||||
destination.invariants();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SHAMapSync, shamap, xrpl);
|
||||
|
||||
} // namespace xrpl::tests
|
||||
@@ -1,427 +0,0 @@
|
||||
#include <test/shamap/common.h>
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapInnerNode.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
#include <xrpl/shamap/SHAMapLeafNode.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
#ifndef __INTELLISENSE__
|
||||
static_assert(std::is_nothrow_destructible<SHAMap>{});
|
||||
static_assert(!std::is_default_constructible<SHAMap>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMap>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMap>{});
|
||||
static_assert(!std::is_move_constructible<SHAMap>{});
|
||||
static_assert(!std::is_move_assignable<SHAMap>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_copy_constructible<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_copy_assignable<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_move_constructible<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_move_assignable<SHAMap::ConstIterator>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapItem>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapItem>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapItem>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_default_constructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_copy_constructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_copy_assignable<SHAMapNodeID>{});
|
||||
static_assert(std::is_move_constructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_move_assignable<SHAMapNodeID>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapHash>{});
|
||||
static_assert(std::is_default_constructible<SHAMapHash>{});
|
||||
static_assert(std::is_copy_constructible<SHAMapHash>{});
|
||||
static_assert(std::is_copy_assignable<SHAMapHash>{});
|
||||
static_assert(std::is_move_constructible<SHAMapHash>{});
|
||||
static_assert(std::is_move_assignable<SHAMapHash>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_move_constructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_move_assignable<SHAMapTreeNode>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_move_constructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_move_assignable<SHAMapInnerNode>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_move_constructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_move_assignable<SHAMapLeafNode>{});
|
||||
#endif
|
||||
|
||||
inline bool
|
||||
operator==(SHAMapItem const& a, SHAMapItem const& b)
|
||||
{
|
||||
return a.key() == b.key();
|
||||
}
|
||||
inline bool
|
||||
operator!=(SHAMapItem const& a, SHAMapItem const& b)
|
||||
{
|
||||
return a.key() != b.key();
|
||||
}
|
||||
inline bool
|
||||
operator==(SHAMapItem const& a, uint256 const& b)
|
||||
{
|
||||
return a.key() == b;
|
||||
}
|
||||
inline bool
|
||||
operator!=(SHAMapItem const& a, uint256 const& b)
|
||||
{
|
||||
return a.key() != b;
|
||||
}
|
||||
|
||||
class SHAMap_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
static Buffer
|
||||
intToVuc(int v)
|
||||
{
|
||||
Buffer vuc(32);
|
||||
std::fill_n(vuc.data(), vuc.size(), static_cast<std::uint8_t>(v));
|
||||
return vuc;
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using beast::Severity;
|
||||
test::SuiteJournal journal("SHAMap_test", *this);
|
||||
|
||||
run(true, journal);
|
||||
run(false, journal);
|
||||
}
|
||||
|
||||
void
|
||||
run(bool backed, beast::Journal const& journal)
|
||||
{
|
||||
if (backed)
|
||||
{
|
||||
testcase("add/traverse backed");
|
||||
}
|
||||
else
|
||||
{
|
||||
testcase("add/traverse unbacked");
|
||||
}
|
||||
|
||||
tests::TestNodeFamily f(journal);
|
||||
|
||||
// h3 and h4 differ only in the leaf, same terminal node (level 19)
|
||||
constexpr uint256 kH1("092891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
|
||||
constexpr uint256 kH2("436ccbac3347baa1f1e53baeef1f43334da88f1f6d70d963b833afd6dfa289fe");
|
||||
constexpr uint256 kH3("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8");
|
||||
constexpr uint256 kH4("b92891fe4ef6cee585fdc6fda2e09eb4d386363158ec3321b8123e5a772c6ca8");
|
||||
constexpr uint256 kH5("a92891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
|
||||
|
||||
SHAMap sMap(SHAMapType::FREE, f);
|
||||
sMap.invariants();
|
||||
if (!backed)
|
||||
sMap.setUnbacked();
|
||||
|
||||
auto i1 = makeShamapitem(kH1, intToVuc(1));
|
||||
auto i2 = makeShamapitem(kH2, intToVuc(2));
|
||||
auto i3 = makeShamapitem(kH3, intToVuc(3));
|
||||
auto i4 = makeShamapitem(kH4, intToVuc(4));
|
||||
auto i5 = makeShamapitem(kH5, intToVuc(5));
|
||||
|
||||
unexpected(!sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i2)), "no add");
|
||||
sMap.invariants();
|
||||
unexpected(!sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i1)), "no add");
|
||||
sMap.invariants();
|
||||
|
||||
auto i = sMap.begin();
|
||||
auto e = sMap.end();
|
||||
unexpected(i == e || (*i != *i1), "bad traverse");
|
||||
++i;
|
||||
unexpected(i == e || (*i != *i2), "bad traverse");
|
||||
++i;
|
||||
unexpected(i != e, "bad traverse");
|
||||
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i4));
|
||||
sMap.invariants();
|
||||
sMap.delItem(i2->key());
|
||||
sMap.invariants();
|
||||
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i3));
|
||||
sMap.invariants();
|
||||
i = sMap.begin();
|
||||
e = sMap.end();
|
||||
unexpected(i == e || (*i != *i1), "bad traverse");
|
||||
++i;
|
||||
unexpected(i == e || (*i != *i3), "bad traverse");
|
||||
++i;
|
||||
unexpected(i == e || (*i != *i4), "bad traverse");
|
||||
++i;
|
||||
unexpected(i != e, "bad traverse");
|
||||
|
||||
if (backed)
|
||||
{
|
||||
testcase("snapshot backed");
|
||||
}
|
||||
else
|
||||
{
|
||||
testcase("snapshot unbacked");
|
||||
}
|
||||
|
||||
SHAMapHash const mapHash = sMap.getHash();
|
||||
std::shared_ptr<SHAMap> const map2 = sMap.snapShot(false);
|
||||
map2->invariants();
|
||||
unexpected(sMap.getHash() != mapHash, "bad snapshot");
|
||||
unexpected(map2->getHash() != mapHash, "bad snapshot");
|
||||
|
||||
SHAMap::Delta delta;
|
||||
BEAST_EXPECT(sMap.compare(*map2, delta, 100));
|
||||
BEAST_EXPECT(delta.empty());
|
||||
|
||||
unexpected(!sMap.delItem(sMap.begin()->key()), "bad mod");
|
||||
sMap.invariants();
|
||||
unexpected(sMap.getHash() == mapHash, "bad snapshot");
|
||||
unexpected(map2->getHash() != mapHash, "bad snapshot");
|
||||
|
||||
BEAST_EXPECT(sMap.compare(*map2, delta, 100));
|
||||
BEAST_EXPECT(delta.size() == 1);
|
||||
BEAST_EXPECT(delta.begin()->first == kH1);
|
||||
BEAST_EXPECT(delta.begin()->second.first == nullptr);
|
||||
BEAST_EXPECT(delta.begin()->second.second->key() == kH1);
|
||||
|
||||
sMap.dump();
|
||||
|
||||
if (backed)
|
||||
{
|
||||
testcase("build/tear backed");
|
||||
}
|
||||
else
|
||||
{
|
||||
testcase("build/tear unbacked");
|
||||
}
|
||||
{
|
||||
static constexpr std::array keys{
|
||||
uint256(
|
||||
"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8")};
|
||||
|
||||
static constexpr std::array kHashes{
|
||||
uint256(
|
||||
"B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C"
|
||||
"64B6281F7F"),
|
||||
uint256(
|
||||
"FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A883184546"
|
||||
"7FB2ECE266"),
|
||||
uint256(
|
||||
"4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51A"
|
||||
"E756795B75"),
|
||||
uint256(
|
||||
"7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC"
|
||||
"6C74F93E07"),
|
||||
uint256(
|
||||
"395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596"
|
||||
"462B0E3A3E"),
|
||||
uint256(
|
||||
"D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D9"
|
||||
"8A84D9DDE4"),
|
||||
uint256(
|
||||
"76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF9"
|
||||
"4361143615"),
|
||||
uint256(
|
||||
"DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA0"
|
||||
"2BBE7230E5")};
|
||||
|
||||
SHAMap map(SHAMapType::FREE, f);
|
||||
if (!backed)
|
||||
map.setUnbacked();
|
||||
|
||||
BEAST_EXPECT(map.getHash() == beast::kZero);
|
||||
for (int k = 0; k < keys.size(); ++k)
|
||||
{
|
||||
BEAST_EXPECT(map.addItem(
|
||||
SHAMapNodeType::TnTransactionNm, makeShamapitem(keys[k], intToVuc(k))));
|
||||
BEAST_EXPECT(map.getHash().asUInt256() == kHashes[k]);
|
||||
map.invariants();
|
||||
}
|
||||
for (int k = keys.size() - 1; k >= 0; --k)
|
||||
{
|
||||
BEAST_EXPECT(map.getHash().asUInt256() == kHashes[k]);
|
||||
BEAST_EXPECT(map.delItem(keys[k]));
|
||||
map.invariants();
|
||||
}
|
||||
BEAST_EXPECT(map.getHash() == beast::kZero);
|
||||
}
|
||||
|
||||
if (backed)
|
||||
{
|
||||
testcase("iterate backed");
|
||||
}
|
||||
else
|
||||
{
|
||||
testcase("iterate unbacked");
|
||||
}
|
||||
|
||||
{
|
||||
static constexpr std::array keys{
|
||||
uint256(
|
||||
"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8"),
|
||||
uint256(
|
||||
"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
|
||||
"5a772c6ca8")};
|
||||
|
||||
tests::TestNodeFamily tf{journal};
|
||||
SHAMap map{SHAMapType::FREE, tf};
|
||||
if (!backed)
|
||||
map.setUnbacked();
|
||||
for (auto const& k : keys)
|
||||
{
|
||||
map.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(k, intToVuc(0)));
|
||||
map.invariants();
|
||||
}
|
||||
|
||||
int h = 7;
|
||||
for (auto const& k : map)
|
||||
{
|
||||
BEAST_EXPECT(k.key() == keys[h]);
|
||||
--h;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SHAMapPathProof_test : public beast::unit_test::Suite
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
test::SuiteJournal journal("SHAMapPathProof_test", *this);
|
||||
|
||||
tests::TestNodeFamily tf{journal};
|
||||
SHAMap map{SHAMapType::FREE, tf};
|
||||
map.setUnbacked();
|
||||
|
||||
uint256 key;
|
||||
uint256 rootHash;
|
||||
std::vector<Blob> goodPath;
|
||||
|
||||
for (unsigned char c = 1; c < 100; ++c)
|
||||
{
|
||||
uint256 k(c);
|
||||
map.addItem(
|
||||
SHAMapNodeType::TnAccountState, makeShamapitem(k, Slice{k.data(), k.size()}));
|
||||
map.invariants();
|
||||
|
||||
auto root = map.getHash().asUInt256();
|
||||
auto path = map.getProofPath(k);
|
||||
BEAST_EXPECT(path);
|
||||
if (!path)
|
||||
break;
|
||||
BEAST_EXPECT(map.verifyProofPath(root, k, *path));
|
||||
if (c == 1)
|
||||
{
|
||||
// extra node
|
||||
path->insert(path->begin(), path->front());
|
||||
BEAST_EXPECT(!map.verifyProofPath(root, k, *path));
|
||||
// wrong key
|
||||
uint256 const wrongKey(c + 1);
|
||||
BEAST_EXPECT(!map.getProofPath(wrongKey));
|
||||
}
|
||||
if (c == 99)
|
||||
{
|
||||
key = k;
|
||||
rootHash = root;
|
||||
goodPath = std::move(*path);
|
||||
}
|
||||
}
|
||||
|
||||
// still good
|
||||
BEAST_EXPECT(map.verifyProofPath(rootHash, key, goodPath));
|
||||
// empty path
|
||||
std::vector<Blob> badPath;
|
||||
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
|
||||
// too long
|
||||
badPath = goodPath;
|
||||
badPath.push_back(goodPath.back());
|
||||
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
|
||||
// bad node
|
||||
badPath.clear();
|
||||
badPath.emplace_back(100, 100);
|
||||
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
|
||||
// bad node type
|
||||
badPath.clear();
|
||||
badPath.push_back(goodPath.front());
|
||||
badPath.front().back()--; // change node type
|
||||
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
|
||||
// all inner
|
||||
badPath.clear();
|
||||
badPath = goodPath;
|
||||
badPath.erase(badPath.begin());
|
||||
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SHAMap, shamap, xrpl);
|
||||
BEAST_DEFINE_TESTSUITE(SHAMapPathProof, shamap, xrpl);
|
||||
} // namespace xrpl::tests
|
||||
@@ -29,6 +29,8 @@ set(test_modules
|
||||
basics
|
||||
crypto
|
||||
json
|
||||
resource
|
||||
shamap
|
||||
tx
|
||||
protocol_autogen
|
||||
)
|
||||
|
||||
260
src/tests/libxrpl/basics/Buffer.cpp
Normal file
260
src/tests/libxrpl/basics/Buffer.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
struct BufferTest : public ::testing::Test
|
||||
{
|
||||
static bool
|
||||
sane(Buffer const& b)
|
||||
{
|
||||
if (b.empty())
|
||||
return b.data() == nullptr;
|
||||
|
||||
return b.data() != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BufferTest, buffer)
|
||||
{
|
||||
std::uint8_t const data[] = {0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, 0x71, 0x6d, 0x2a,
|
||||
0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c,
|
||||
0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3};
|
||||
|
||||
Buffer const b0;
|
||||
EXPECT_TRUE(sane(b0));
|
||||
EXPECT_TRUE(b0.empty());
|
||||
|
||||
Buffer b1{0};
|
||||
EXPECT_TRUE(sane(b1));
|
||||
EXPECT_TRUE(b1.empty());
|
||||
std::memcpy(b1.alloc(16), data, 16);
|
||||
EXPECT_TRUE(sane(b1));
|
||||
EXPECT_FALSE(b1.empty());
|
||||
EXPECT_EQ(b1.size(), 16);
|
||||
|
||||
Buffer b2{b1.size()};
|
||||
EXPECT_TRUE(sane(b2));
|
||||
EXPECT_FALSE(b2.empty());
|
||||
EXPECT_EQ(b2.size(), b1.size());
|
||||
std::memcpy(b2.data(), data + 16, 16);
|
||||
|
||||
Buffer b3{data, sizeof(data)};
|
||||
EXPECT_TRUE(sane(b3));
|
||||
EXPECT_FALSE(b3.empty());
|
||||
EXPECT_EQ(b3.size(), sizeof(data));
|
||||
EXPECT_EQ(std::memcmp(b3.data(), data, b3.size()), 0);
|
||||
|
||||
// Check equality and inequality comparisons.
|
||||
// For code readability, we want to use general
|
||||
// EXPECT_TRUE instead of specific EXPECT_EQ etc.
|
||||
EXPECT_TRUE(b0 == b0);
|
||||
EXPECT_TRUE(b0 != b1);
|
||||
EXPECT_TRUE(b1 == b1);
|
||||
EXPECT_TRUE(b1 != b2);
|
||||
EXPECT_TRUE(b2 != b3);
|
||||
|
||||
// Check copy constructors and copy assignments:
|
||||
{
|
||||
Buffer x{b0};
|
||||
EXPECT_EQ(x, b0);
|
||||
EXPECT_TRUE(sane(x));
|
||||
Buffer y{b1};
|
||||
EXPECT_EQ(y, b1);
|
||||
EXPECT_TRUE(sane(y));
|
||||
x = b2;
|
||||
EXPECT_EQ(x, b2);
|
||||
EXPECT_TRUE(sane(x));
|
||||
x = y;
|
||||
EXPECT_EQ(x, y);
|
||||
EXPECT_TRUE(sane(x));
|
||||
y = b3;
|
||||
EXPECT_EQ(y, b3);
|
||||
EXPECT_TRUE(sane(y));
|
||||
x = b0;
|
||||
EXPECT_EQ(x, b0);
|
||||
EXPECT_TRUE(sane(x));
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
|
||||
#endif
|
||||
|
||||
x = x;
|
||||
EXPECT_EQ(x, b0);
|
||||
EXPECT_TRUE(sane(x));
|
||||
y = y;
|
||||
EXPECT_EQ(y, b3);
|
||||
EXPECT_TRUE(sane(y));
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
// Check move constructor & move assignments:
|
||||
{
|
||||
static_assert(std::is_nothrow_move_constructible_v<Buffer>);
|
||||
static_assert(std::is_nothrow_move_assignable_v<Buffer>);
|
||||
|
||||
{ // Move-construct from empty buf
|
||||
Buffer x;
|
||||
Buffer const y{std::move(x)};
|
||||
EXPECT_TRUE(sane(x)); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(x.empty()); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(sane(y));
|
||||
EXPECT_TRUE(y.empty());
|
||||
EXPECT_EQ(x, y); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move-construct from non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer const y{std::move(x)};
|
||||
EXPECT_TRUE(sane(x)); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(x.empty()); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(sane(y));
|
||||
EXPECT_EQ(y, b1);
|
||||
}
|
||||
|
||||
{ // Move assign empty buf to empty buf
|
||||
Buffer x;
|
||||
Buffer y;
|
||||
|
||||
x = std::move(y);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_TRUE(x.empty());
|
||||
EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move assign non-empty buf to empty buf
|
||||
Buffer x;
|
||||
Buffer y{b1};
|
||||
|
||||
x = std::move(y);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_EQ(x, b1);
|
||||
EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move assign empty buf to non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer y;
|
||||
|
||||
x = std::move(y);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_TRUE(x.empty());
|
||||
EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
{ // Move assign non-empty buf to non-empty buf
|
||||
Buffer x{b1};
|
||||
Buffer y{b2};
|
||||
Buffer z{b3};
|
||||
|
||||
x = std::move(y);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_FALSE(x.empty());
|
||||
EXPECT_TRUE(sane(y)); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(y.empty()); // NOLINT(bugprone-use-after-move)
|
||||
|
||||
x = std::move(z);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_FALSE(x.empty());
|
||||
EXPECT_TRUE(sane(z)); // NOLINT(bugprone-use-after-move)
|
||||
EXPECT_TRUE(z.empty()); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Buffer w{static_cast<Slice>(b0)};
|
||||
EXPECT_TRUE(sane(w));
|
||||
EXPECT_EQ(w, b0);
|
||||
|
||||
Buffer x{static_cast<Slice>(b1)};
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_EQ(x, b1);
|
||||
|
||||
Buffer y{static_cast<Slice>(b2)};
|
||||
EXPECT_TRUE(sane(y));
|
||||
EXPECT_EQ(y, b2);
|
||||
|
||||
Buffer z{static_cast<Slice>(b3)};
|
||||
EXPECT_TRUE(sane(z));
|
||||
EXPECT_EQ(z, b3);
|
||||
|
||||
// Assign empty slice to empty buffer
|
||||
w = static_cast<Slice>(b0);
|
||||
EXPECT_TRUE(sane(w));
|
||||
EXPECT_EQ(w, b0);
|
||||
|
||||
// Assign non-empty slice to empty buffer
|
||||
w = static_cast<Slice>(b1);
|
||||
EXPECT_TRUE(sane(w));
|
||||
EXPECT_EQ(w, b1);
|
||||
|
||||
// Assign non-empty slice to non-empty buffer
|
||||
x = static_cast<Slice>(b2);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_EQ(x, b2);
|
||||
|
||||
// Assign non-empty slice to non-empty buffer
|
||||
y = static_cast<Slice>(z);
|
||||
EXPECT_TRUE(sane(y));
|
||||
EXPECT_EQ(y, z);
|
||||
|
||||
// Assign empty slice to non-empty buffer:
|
||||
z = static_cast<Slice>(b0);
|
||||
EXPECT_TRUE(sane(z));
|
||||
EXPECT_EQ(z, b0);
|
||||
}
|
||||
|
||||
{
|
||||
auto test = [](Buffer const& b, std::size_t i) {
|
||||
Buffer x{b};
|
||||
|
||||
// Try to allocate some number of bytes, possibly
|
||||
// zero (which means clear) and sanity check
|
||||
x(i);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_EQ(x.size(), i);
|
||||
EXPECT_EQ((x.data() == nullptr), (i == 0));
|
||||
|
||||
// Try to allocate some more data (always non-zero)
|
||||
x(i + 1);
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_EQ(x.size(), i + 1);
|
||||
EXPECT_NE(x.data(), nullptr);
|
||||
|
||||
// Try to clear:
|
||||
x.clear();
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_TRUE(x.empty());
|
||||
EXPECT_EQ(x.data(), nullptr);
|
||||
|
||||
// Try to clear again:
|
||||
x.clear();
|
||||
EXPECT_TRUE(sane(x));
|
||||
EXPECT_TRUE(x.empty());
|
||||
EXPECT_EQ(x.data(), nullptr);
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < 16; ++i)
|
||||
{
|
||||
test(b0, i);
|
||||
test(b1, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
94
src/tests/libxrpl/basics/FileUtilities.cpp
Normal file
94
src/tests/libxrpl/basics/FileUtilities.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include <xrpl/basics/FileUtilities.h>
|
||||
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/system/detail/errc.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
class TempFile
|
||||
{
|
||||
public:
|
||||
explicit TempFile(boost::filesystem::path file, std::string const& contents)
|
||||
: dir_(
|
||||
boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path("xrpl-file-utilities-%%%%-%%%%-%%%%"))
|
||||
, file_(dir_ / file)
|
||||
{
|
||||
boost::filesystem::create_directory(dir_);
|
||||
|
||||
std::ofstream output(file_.string());
|
||||
if (!output)
|
||||
throw std::runtime_error("Unable to create temporary test file");
|
||||
|
||||
output << contents;
|
||||
}
|
||||
|
||||
~TempFile()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(file_, ec);
|
||||
boost::filesystem::remove(dir_, ec);
|
||||
}
|
||||
|
||||
[[nodiscard]] boost::filesystem::path const&
|
||||
file() const
|
||||
{
|
||||
return file_;
|
||||
}
|
||||
|
||||
private:
|
||||
boost::filesystem::path dir_;
|
||||
boost::filesystem::path file_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(FileUtilitiesTest, get_file_contents)
|
||||
{
|
||||
using namespace boost::system;
|
||||
|
||||
constexpr char const* kExpectedContents = "This file is very short. That's all we need.";
|
||||
|
||||
TempFile const file("test_file", "This is temporary text that should get overwritten");
|
||||
|
||||
error_code ec;
|
||||
auto const path = file.file();
|
||||
|
||||
writeFileContents(ec, path, kExpectedContents);
|
||||
EXPECT_FALSE(ec);
|
||||
|
||||
{
|
||||
// Test with no max
|
||||
auto const good = getFileContents(ec, path);
|
||||
EXPECT_FALSE(ec);
|
||||
EXPECT_EQ(good, kExpectedContents);
|
||||
}
|
||||
|
||||
{
|
||||
// Test with large max
|
||||
auto const good = getFileContents(ec, path, kilobytes(1));
|
||||
EXPECT_FALSE(ec);
|
||||
EXPECT_EQ(good, kExpectedContents);
|
||||
}
|
||||
|
||||
{
|
||||
// Test with small max
|
||||
auto const bad = getFileContents(ec, path, 16);
|
||||
EXPECT_TRUE(ec && ec.value() == boost::system::errc::file_too_large);
|
||||
EXPECT_TRUE(bad.empty());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
243
src/tests/libxrpl/basics/IOUAmount.cpp
Normal file
243
src/tests/libxrpl/basics/IOUAmount.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TEST(IOUAmountTest, zero)
|
||||
{
|
||||
IOUAmount const z(0, 0);
|
||||
|
||||
EXPECT_EQ(z.mantissa(), 0);
|
||||
EXPECT_EQ(z.exponent(), -100);
|
||||
EXPECT_FALSE(z);
|
||||
EXPECT_EQ(z.signum(), 0);
|
||||
EXPECT_EQ(z, beast::kZero);
|
||||
|
||||
EXPECT_EQ((z + z), z);
|
||||
EXPECT_EQ((z - z), z);
|
||||
EXPECT_EQ(z, -z);
|
||||
|
||||
IOUAmount const zz(beast::kZero);
|
||||
EXPECT_EQ(z, zz);
|
||||
|
||||
// https://github.com/XRPLF/rippled/issues/5170
|
||||
IOUAmount const zzz{};
|
||||
EXPECT_EQ(zzz, beast::kZero);
|
||||
// EXPECT_EQ(zzz, zz);
|
||||
}
|
||||
|
||||
TEST(IOUAmountTest, sig_num)
|
||||
{
|
||||
IOUAmount const neg(-1, 0);
|
||||
EXPECT_LT(neg.signum(), 0);
|
||||
|
||||
IOUAmount const zer(0, 0);
|
||||
EXPECT_EQ(zer.signum(), 0);
|
||||
|
||||
IOUAmount const pos(1, 0);
|
||||
EXPECT_GT(pos.signum(), 0);
|
||||
}
|
||||
|
||||
TEST(IOUAmountTest, beast_zero)
|
||||
{
|
||||
using beast::kZero;
|
||||
|
||||
{
|
||||
IOUAmount const z(kZero);
|
||||
EXPECT_TRUE(z == kZero);
|
||||
EXPECT_TRUE(z >= kZero);
|
||||
EXPECT_TRUE(z <= kZero);
|
||||
EXPECT_FALSE(z != kZero);
|
||||
EXPECT_FALSE(z > kZero);
|
||||
EXPECT_FALSE(z < kZero);
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const neg(-2, 0);
|
||||
EXPECT_TRUE(neg < kZero);
|
||||
EXPECT_TRUE(neg <= kZero);
|
||||
EXPECT_TRUE(neg != kZero);
|
||||
EXPECT_FALSE(neg == kZero);
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const pos(2, 0);
|
||||
EXPECT_TRUE(pos > kZero);
|
||||
EXPECT_TRUE(pos >= kZero);
|
||||
EXPECT_TRUE(pos != kZero);
|
||||
EXPECT_FALSE(pos == kZero);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IOUAmountTest, comparisons)
|
||||
{
|
||||
IOUAmount const n(-2, 0);
|
||||
IOUAmount const z(0, 0);
|
||||
IOUAmount const p(2, 0);
|
||||
// For code readability, we want to use general
|
||||
// EXPECT_TRUE instead of specific EXPECT_EQ etc.
|
||||
EXPECT_TRUE(z == z);
|
||||
EXPECT_TRUE(z >= z);
|
||||
EXPECT_TRUE(z <= z);
|
||||
EXPECT_TRUE(z == -z);
|
||||
// NOLINTBEGIN(misc-redundant-expression)
|
||||
EXPECT_FALSE(z > z);
|
||||
EXPECT_FALSE(z < z);
|
||||
EXPECT_FALSE(z != z);
|
||||
// NOLINTEND(misc-redundant-expression)
|
||||
EXPECT_FALSE(z != -z);
|
||||
|
||||
EXPECT_TRUE(n < z);
|
||||
EXPECT_TRUE(n <= z);
|
||||
EXPECT_TRUE(n != z);
|
||||
EXPECT_FALSE(n > z);
|
||||
EXPECT_FALSE(n >= z);
|
||||
EXPECT_FALSE(n == z);
|
||||
|
||||
EXPECT_TRUE(p > z);
|
||||
EXPECT_TRUE(p >= z);
|
||||
EXPECT_TRUE(p != z);
|
||||
EXPECT_FALSE(p < z);
|
||||
EXPECT_FALSE(p <= z);
|
||||
EXPECT_FALSE(p == z);
|
||||
|
||||
EXPECT_TRUE(n < p);
|
||||
EXPECT_TRUE(n <= p);
|
||||
EXPECT_TRUE(n != p);
|
||||
EXPECT_FALSE(n > p);
|
||||
EXPECT_FALSE(n >= p);
|
||||
EXPECT_FALSE(n == p);
|
||||
|
||||
EXPECT_TRUE(p > n);
|
||||
EXPECT_TRUE(p >= n);
|
||||
EXPECT_TRUE(p != n);
|
||||
EXPECT_FALSE(p < n);
|
||||
EXPECT_FALSE(p <= n);
|
||||
EXPECT_FALSE(p == n);
|
||||
|
||||
EXPECT_TRUE(p > -p);
|
||||
EXPECT_TRUE(p >= -p);
|
||||
EXPECT_TRUE(p != -p);
|
||||
|
||||
EXPECT_TRUE(n < -n);
|
||||
EXPECT_TRUE(n <= -n);
|
||||
EXPECT_TRUE(n != -n);
|
||||
}
|
||||
|
||||
TEST(IOUAmountTest, to_string)
|
||||
{
|
||||
auto test = [](IOUAmount const& n, std::string const& expected) {
|
||||
auto const result = to_string(n);
|
||||
std::stringstream ss;
|
||||
ss << "to_string(" << result << "). Expected: " << expected;
|
||||
EXPECT_EQ(result, expected) << ss.str();
|
||||
};
|
||||
|
||||
for (auto const mantissaSize : MantissaRange::getAllScales())
|
||||
{
|
||||
NumberMantissaScaleGuard const mg(mantissaSize);
|
||||
|
||||
test(IOUAmount(-2, 0), "-2");
|
||||
test(IOUAmount(0, 0), "0");
|
||||
test(IOUAmount(2, 0), "2");
|
||||
test(IOUAmount(25, -3), "0.025");
|
||||
test(IOUAmount(-25, -3), "-0.025");
|
||||
test(IOUAmount(25, 1), "250");
|
||||
test(IOUAmount(-25, 1), "-250");
|
||||
test(IOUAmount(2, 20), "2e20");
|
||||
test(IOUAmount(-2, -20), "-2e-20");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IOUAmountTest, mul_ratio)
|
||||
{
|
||||
/* The range for the mantissa when normalized */
|
||||
constexpr std::int64_t kMinMantissa = 1000000000000000ull;
|
||||
constexpr std::int64_t kMaxMantissa = 9999999999999999ull;
|
||||
// log(2,maxMantissa) ~ 53.15
|
||||
/* The range for the exponent when normalized */
|
||||
constexpr int kMinExponent = -96;
|
||||
constexpr int kMaxExponent = 80;
|
||||
constexpr auto kMaxUInt = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
{
|
||||
// multiply by a number that would overflow the mantissa, then
|
||||
// divide by the same number, and check we didn't lose any value
|
||||
IOUAmount const bigMan(kMaxMantissa, 0);
|
||||
EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, false));
|
||||
}
|
||||
{
|
||||
// Similar test as above, but for negative values
|
||||
IOUAmount const bigMan(-kMaxMantissa, 0);
|
||||
EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
EXPECT_EQ(bigMan, mulRatio(bigMan, kMaxUInt, kMaxUInt, false));
|
||||
}
|
||||
|
||||
{
|
||||
// small amounts
|
||||
IOUAmount const tiny(kMinMantissa, kMinExponent);
|
||||
// Round up should give the smallest allowable number
|
||||
EXPECT_EQ(tiny, mulRatio(tiny, 1, kMaxUInt, true));
|
||||
EXPECT_EQ(tiny, mulRatio(tiny, kMaxUInt - 1, kMaxUInt, true));
|
||||
// rounding down should be zero
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tiny, 1, kMaxUInt, false));
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tiny, kMaxUInt - 1, kMaxUInt, false));
|
||||
|
||||
// tiny negative numbers
|
||||
IOUAmount const tinyNeg(-kMinMantissa, kMinExponent);
|
||||
// Round up should give zero
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, 1, kMaxUInt, true));
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, true));
|
||||
// rounding down should be tiny
|
||||
EXPECT_EQ(tinyNeg, mulRatio(tinyNeg, 1, kMaxUInt, false));
|
||||
EXPECT_EQ(tinyNeg, mulRatio(tinyNeg, kMaxUInt - 1, kMaxUInt, false));
|
||||
}
|
||||
|
||||
{ // rounding
|
||||
{
|
||||
IOUAmount const one(1, 0);
|
||||
auto const rup = mulRatio(one, kMaxUInt - 1, kMaxUInt, true);
|
||||
auto const rdown = mulRatio(one, kMaxUInt - 1, kMaxUInt, false);
|
||||
EXPECT_EQ(rup.mantissa() - rdown.mantissa(), 1);
|
||||
}
|
||||
{
|
||||
IOUAmount const big(kMaxMantissa, kMaxExponent);
|
||||
auto const rup = mulRatio(big, kMaxUInt - 1, kMaxUInt, true);
|
||||
auto const rdown = mulRatio(big, kMaxUInt - 1, kMaxUInt, false);
|
||||
EXPECT_EQ(rup.mantissa() - rdown.mantissa(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
IOUAmount const negOne(-1, 0);
|
||||
auto const rup = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, true);
|
||||
auto const rdown = mulRatio(negOne, kMaxUInt - 1, kMaxUInt, false);
|
||||
EXPECT_EQ(rup.mantissa() - rdown.mantissa(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// division by zero
|
||||
IOUAmount const one(1, 0);
|
||||
EXPECT_ANY_THROW({ mulRatio(one, 1, 0, true); });
|
||||
}
|
||||
|
||||
{
|
||||
// overflow
|
||||
IOUAmount const big(kMaxMantissa, kMaxExponent);
|
||||
EXPECT_ANY_THROW({ mulRatio(big, 2, 0, true); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
844
src/tests/libxrpl/basics/IntrusiveShared.cpp
Normal file
844
src/tests/libxrpl/basics/IntrusiveShared.cpp
Normal file
@@ -0,0 +1,844 @@
|
||||
#include <xrpl/basics/IntrusivePointer.h> // IWYU pragma: keep
|
||||
#include <xrpl/basics/IntrusivePointer.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/basics/IntrusiveRefCounts.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <latch>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
/*
|
||||
* Experimentally, we discovered that using std::barrier performs extremely
|
||||
* poorly (~1 hour vs ~1 minute to run the test suite) in certain macOS
|
||||
* environments. To unblock our macOS CI pipeline, we replaced std::barrier with a
|
||||
* custom mutex-based barrier (Barrier) that significantly improves performance
|
||||
* without compromising correctness. For future reference, if we ever consider
|
||||
* reintroducing std::barrier, the following configuration is known to exhibit the
|
||||
* problem:
|
||||
*
|
||||
* Model Name: Mac mini
|
||||
* Model Identifier: Mac14,3
|
||||
* Model Number: Z16K000R4LL/A
|
||||
* Chip: Apple M2
|
||||
* Total Number of Cores: 8 (4 performance and 4 efficiency)
|
||||
* Memory: 24 GB
|
||||
* System Firmware Version: 11881.41.5
|
||||
* OS Loader Version: 11881.1.1
|
||||
* Apple clang version 16.0.0 (clang-1600.0.26.3)
|
||||
* Target: arm64-apple-darwin24.0.0
|
||||
* Thread model: posix
|
||||
*
|
||||
*/
|
||||
struct Barrier
|
||||
{
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
int count;
|
||||
int const initial;
|
||||
std::size_t generation{0};
|
||||
|
||||
explicit Barrier(int n) : count(n), initial(n)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
arriveAndWait()
|
||||
{
|
||||
std::unique_lock lock(mtx);
|
||||
auto const currentGeneration = generation;
|
||||
if (--count == 0)
|
||||
{
|
||||
++generation;
|
||||
count = initial;
|
||||
cv.notify_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
cv.wait(lock, [&] { return generation != currentGeneration; });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
enum class TrackedState : std::uint8_t {
|
||||
Uninitialized,
|
||||
Alive,
|
||||
PartiallyDeletedStarted,
|
||||
PartiallyDeleted,
|
||||
DeletedStarted,
|
||||
Deleted
|
||||
};
|
||||
|
||||
class TIBase : public IntrusiveRefCounts
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t kMaxStates = 128;
|
||||
static std::array<std::atomic<TrackedState>, kMaxStates> state;
|
||||
static std::atomic<std::size_t> nextId;
|
||||
static TrackedState
|
||||
getState(std::size_t id)
|
||||
{
|
||||
if (id >= state.size())
|
||||
throw std::out_of_range("TIBase state id out of range");
|
||||
|
||||
return state[id].load(std::memory_order_acquire);
|
||||
}
|
||||
static void
|
||||
resetStates(bool resetCallback)
|
||||
{
|
||||
for (std::size_t i = 0; i < kMaxStates; ++i)
|
||||
{
|
||||
state[i].store(TrackedState::Uninitialized, std::memory_order_release);
|
||||
}
|
||||
nextId.store(0, std::memory_order_release);
|
||||
if (resetCallback)
|
||||
TIBase::tracingCallback = [](TrackedState, std::optional<TrackedState>) {};
|
||||
}
|
||||
|
||||
struct ResetStatesGuard
|
||||
{
|
||||
bool resetCallback{false};
|
||||
|
||||
ResetStatesGuard(bool resetCallback) : resetCallback{resetCallback}
|
||||
{
|
||||
TIBase::resetStates(resetCallback);
|
||||
}
|
||||
~ResetStatesGuard()
|
||||
{
|
||||
TIBase::resetStates(resetCallback);
|
||||
}
|
||||
};
|
||||
|
||||
TIBase() : id{checkoutID()}
|
||||
{
|
||||
state[id].store(TrackedState::Alive, std::memory_order_relaxed);
|
||||
}
|
||||
~TIBase() override
|
||||
{
|
||||
using enum TrackedState;
|
||||
|
||||
tracingCallback(state[id].load(std::memory_order_relaxed), DeletedStarted);
|
||||
|
||||
// Use relaxed memory order to try to avoid atomic operations from
|
||||
// adding additional memory synchronizations that may hide threading
|
||||
// errors in the underlying shared pointer class.
|
||||
state[id].store(DeletedStarted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(DeletedStarted, Deleted);
|
||||
|
||||
state[id].store(TrackedState::Deleted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(TrackedState::Deleted, std::nullopt);
|
||||
}
|
||||
|
||||
void
|
||||
partialDestructor() const
|
||||
{
|
||||
using enum TrackedState;
|
||||
|
||||
tracingCallback(state[id].load(std::memory_order_relaxed), PartiallyDeletedStarted);
|
||||
|
||||
state[id].store(PartiallyDeletedStarted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(PartiallyDeletedStarted, PartiallyDeleted);
|
||||
|
||||
state[id].store(PartiallyDeleted, std::memory_order_relaxed);
|
||||
|
||||
tracingCallback(PartiallyDeleted, std::nullopt);
|
||||
}
|
||||
|
||||
static std::function<void(TrackedState, std::optional<TrackedState>)> tracingCallback;
|
||||
|
||||
std::size_t const id;
|
||||
|
||||
private:
|
||||
static std::size_t
|
||||
checkoutID()
|
||||
{
|
||||
auto const id = nextId.fetch_add(1, std::memory_order_acq_rel);
|
||||
if (id >= state.size())
|
||||
throw std::out_of_range("TIBase state capacity exceeded");
|
||||
|
||||
return id;
|
||||
}
|
||||
};
|
||||
|
||||
std::array<std::atomic<TrackedState>, TIBase::kMaxStates> TIBase::state;
|
||||
std::atomic<std::size_t> TIBase::nextId{0};
|
||||
|
||||
std::function<void(TrackedState, std::optional<TrackedState>)> TIBase::tracingCallback =
|
||||
[](TrackedState, std::optional<TrackedState>) {};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(IntrusiveSharedTest, basics)
|
||||
{
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
TIBase const b;
|
||||
EXPECT_EQ(b.useCount(), 1);
|
||||
b.addWeakRef();
|
||||
EXPECT_EQ(b.useCount(), 1);
|
||||
auto s = b.releaseStrongRef();
|
||||
EXPECT_EQ(s, ReleaseStrongRefAction::PartialDestroy);
|
||||
EXPECT_EQ(b.useCount(), 0);
|
||||
TIBase const* pb = &b;
|
||||
partialDestructorFinished(&pb);
|
||||
EXPECT_FALSE(pb);
|
||||
auto w = b.releaseWeakRef();
|
||||
EXPECT_EQ(w, ReleaseWeakRefAction::Destroy);
|
||||
}
|
||||
|
||||
std::vector<SharedIntrusive<TIBase>> strong;
|
||||
std::vector<WeakIntrusive<TIBase>> weak;
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
using enum TrackedState;
|
||||
auto b = makeSharedIntrusive<TIBase>();
|
||||
auto id = b->id;
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
EXPECT_EQ(b->useCount(), 1);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
strong.push_back(b);
|
||||
}
|
||||
b.reset();
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
strong.resize(strong.size() - 1);
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
strong.clear();
|
||||
EXPECT_EQ(TIBase::getState(id), Deleted);
|
||||
|
||||
b = makeSharedIntrusive<TIBase>();
|
||||
id = b->id;
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
EXPECT_EQ(b->useCount(), 1);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
weak.emplace_back(b);
|
||||
EXPECT_EQ(b->useCount(), 1);
|
||||
}
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
weak.resize(weak.size() - 1);
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
b.reset();
|
||||
EXPECT_EQ(TIBase::getState(id), PartiallyDeleted);
|
||||
while (!weak.empty())
|
||||
{
|
||||
weak.resize(weak.size() - 1);
|
||||
if (!weak.empty())
|
||||
{
|
||||
EXPECT_EQ(TIBase::getState(id), PartiallyDeleted);
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(TIBase::getState(id), Deleted);
|
||||
}
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
using enum TrackedState;
|
||||
auto b = makeSharedIntrusive<TIBase>();
|
||||
auto id = b->id;
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
WeakIntrusive<TIBase> w{b};
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
auto s = w.lock();
|
||||
EXPECT_TRUE(s && s->useCount() == 2);
|
||||
b.reset();
|
||||
EXPECT_TRUE(TIBase::getState(id) == Alive);
|
||||
EXPECT_TRUE(s && s->useCount() == 1);
|
||||
s.reset();
|
||||
EXPECT_EQ(TIBase::getState(id), PartiallyDeleted);
|
||||
EXPECT_TRUE(w.expired());
|
||||
s = w.lock();
|
||||
// Cannot convert a weak pointer to a strong pointer if object is
|
||||
// already partially deleted
|
||||
EXPECT_FALSE(s);
|
||||
w.reset();
|
||||
EXPECT_EQ(TIBase::getState(id), Deleted);
|
||||
}
|
||||
{
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
using enum TrackedState;
|
||||
using swu = SharedWeakUnion<TIBase>;
|
||||
swu b = makeSharedIntrusive<TIBase>();
|
||||
EXPECT_TRUE(b.isStrong() && b.useCount() == 1);
|
||||
auto id = b.get()->id;
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
swu w = b;
|
||||
EXPECT_TRUE(TIBase::getState(id) == Alive);
|
||||
EXPECT_TRUE(w.isStrong() && b.useCount() == 2);
|
||||
w.convertToWeak();
|
||||
EXPECT_TRUE(w.isWeak() && b.useCount() == 1);
|
||||
swu s = w;
|
||||
EXPECT_TRUE(s.isWeak() && b.useCount() == 1);
|
||||
s.convertToStrong();
|
||||
EXPECT_TRUE(s.isStrong() && b.useCount() == 2);
|
||||
b.reset();
|
||||
EXPECT_EQ(TIBase::getState(id), Alive);
|
||||
EXPECT_EQ(s.useCount(), 1);
|
||||
EXPECT_FALSE(w.expired());
|
||||
s.reset();
|
||||
EXPECT_EQ(TIBase::getState(id), PartiallyDeleted);
|
||||
EXPECT_TRUE(w.expired());
|
||||
w.convertToStrong();
|
||||
// Cannot convert a weak pointer to a strong pointer if object is
|
||||
// already partially deleted
|
||||
EXPECT_TRUE(w.isWeak());
|
||||
w.reset();
|
||||
EXPECT_EQ(TIBase::getState(id), Deleted);
|
||||
}
|
||||
{
|
||||
// Testing SharedWeakUnion assignment operator
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
auto strong1 = makeSharedIntrusive<TIBase>();
|
||||
auto strong2 = makeSharedIntrusive<TIBase>();
|
||||
|
||||
auto id1 = strong1->id;
|
||||
auto id2 = strong2->id;
|
||||
|
||||
EXPECT_NE(id1, id2);
|
||||
|
||||
SharedWeakUnion<TIBase> union1 = strong1;
|
||||
SharedWeakUnion<TIBase> union2 = strong2;
|
||||
|
||||
EXPECT_TRUE(union1.isStrong());
|
||||
EXPECT_TRUE(union2.isStrong());
|
||||
EXPECT_EQ(union1.get(), strong1.get());
|
||||
EXPECT_EQ(union2.get(), strong2.get());
|
||||
|
||||
// 1) Normal assignment: explicitly calls SharedWeakUnion assignment
|
||||
union1 = union2;
|
||||
EXPECT_TRUE(union1.isStrong());
|
||||
EXPECT_TRUE(union2.isStrong());
|
||||
EXPECT_EQ(union1.get(), union2.get());
|
||||
EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive);
|
||||
EXPECT_EQ(TIBase::getState(id2), TrackedState::Alive);
|
||||
|
||||
// 2) Test self-assignment
|
||||
EXPECT_TRUE(union1.isStrong());
|
||||
EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive);
|
||||
int const initialRefCount = strong1->useCount();
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
|
||||
union1 = union1; // Self-assignment
|
||||
#pragma clang diagnostic pop
|
||||
EXPECT_TRUE(union1.isStrong());
|
||||
EXPECT_EQ(TIBase::getState(id1), TrackedState::Alive);
|
||||
EXPECT_EQ(strong1->useCount(), initialRefCount);
|
||||
|
||||
// 3) Test assignment from null union pointer
|
||||
union1 = SharedWeakUnion<TIBase>();
|
||||
EXPECT_EQ(union1.get(), nullptr);
|
||||
|
||||
// 4) Test assignment to expired union pointer
|
||||
strong2.reset();
|
||||
union2.reset();
|
||||
union1 = union2;
|
||||
EXPECT_EQ(union1.get(), nullptr);
|
||||
EXPECT_EQ(TIBase::getState(id2), TrackedState::Deleted);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IntrusiveSharedTest, partial_delete)
|
||||
{
|
||||
// This test creates two threads. One with a strong pointer and one
|
||||
// with a weak pointer. The strong pointer is reset while the weak
|
||||
// pointer still holds a reference, triggering a partial delete.
|
||||
// While the partial delete function runs (a sleep is inserted) the
|
||||
// weak pointer is reset. The destructor should wait to run until
|
||||
// after the partial delete function has completed running.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
WeakIntrusive<TIBase> weak{strong};
|
||||
std::atomic<bool> destructorRan{false};
|
||||
std::atomic<bool> partialDeleteRan{false};
|
||||
std::latch partialDeleteStartedSyncPoint{2};
|
||||
strong->tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
if (next == DeletedStarted)
|
||||
{
|
||||
// strong goes out of scope while weak is still in scope
|
||||
// This checks that partialDelete has run to completion
|
||||
// before the destructor is called. A sleep is inserted
|
||||
// inside the partial delete to make sure the destructor is
|
||||
// given an opportunity to run during partial delete.
|
||||
EXPECT_EQ(cur, PartiallyDeleted);
|
||||
}
|
||||
if (next == PartiallyDeletedStarted)
|
||||
{
|
||||
partialDeleteStartedSyncPoint.arrive_and_wait();
|
||||
using namespace std::chrono_literals;
|
||||
// Sleep and let the weak pointer go out of scope,
|
||||
// potentially triggering a destructor while partial delete
|
||||
// is running. The test is to make sure that doesn't happen.
|
||||
std::this_thread::sleep_for(800ms);
|
||||
}
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load());
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
EXPECT_FALSE(destructorRan.exchange(true));
|
||||
}
|
||||
};
|
||||
std::thread t1{[&] {
|
||||
partialDeleteStartedSyncPoint.arrive_and_wait();
|
||||
weak.reset(); // Trigger a full delete as soon as the partial
|
||||
// delete starts
|
||||
}};
|
||||
std::thread t2{[&] {
|
||||
strong.reset(); // Trigger a partial delete
|
||||
}};
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
EXPECT_TRUE(destructorRan.load() && partialDeleteRan.load());
|
||||
}
|
||||
|
||||
TEST(IntrusiveSharedTest, destructor)
|
||||
{
|
||||
// This test creates two threads. One with a strong pointer and one
|
||||
// with a weak pointer. The weak pointer is reset while the strong
|
||||
// pointer still holds a reference. Then the strong pointer is
|
||||
// reset. Only the destructor should run. The partial destructor
|
||||
// should not be called. Since the weak reset runs to completion
|
||||
// before the strong pointer is reset, threading doesn't add much to
|
||||
// this test, but there is no harm in keeping it.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
WeakIntrusive<TIBase> weak{strong};
|
||||
std::atomic<bool> destructorRan{false};
|
||||
std::atomic<bool> partialDeleteRan{false};
|
||||
std::latch weakResetSyncPoint{2};
|
||||
strong->tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
EXPECT_FALSE(partialDeleteRan.exchange(true) || destructorRan.load());
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
EXPECT_FALSE(destructorRan.exchange(true));
|
||||
}
|
||||
};
|
||||
std::thread t1{[&] {
|
||||
weak.reset();
|
||||
weakResetSyncPoint.arrive_and_wait();
|
||||
}};
|
||||
std::thread t2{[&] {
|
||||
weakResetSyncPoint.arrive_and_wait();
|
||||
strong.reset(); // Trigger a partial delete
|
||||
}};
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
EXPECT_TRUE(destructorRan.load() && !partialDeleteRan.load());
|
||||
}
|
||||
|
||||
TEST(IntrusiveSharedTest, multithreaded_clear_mixed_variant)
|
||||
{
|
||||
// This test creates and destroys many strong and weak pointers in a
|
||||
// loop. There is a random mix of strong and weak pointers stored in
|
||||
// a vector (held as a variant). Both threads clear all the pointers
|
||||
// and check that the invariants hold.
|
||||
|
||||
using enum TrackedState;
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
std::atomic<int> destructionState{0};
|
||||
// returns destructorRan and partialDestructorRan (in that order)
|
||||
auto getDestructorState = [&]() -> std::pair<bool, bool> {
|
||||
int const s = destructionState.load(std::memory_order_relaxed);
|
||||
return {(s & 1) != 0, (s & 2) != 0};
|
||||
};
|
||||
auto setDestructorRan = [&]() -> void {
|
||||
destructionState.fetch_or(1, std::memory_order_acq_rel);
|
||||
};
|
||||
auto setPartialDeleteRan = [&]() -> void {
|
||||
destructionState.fetch_or(2, std::memory_order_acq_rel);
|
||||
};
|
||||
auto tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
EXPECT_FALSE(partialDeleteRan || destructorRan);
|
||||
setPartialDeleteRan();
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
EXPECT_FALSE(destructorRan);
|
||||
setDestructorRan();
|
||||
}
|
||||
};
|
||||
auto createVecOfPointers = [&](auto const& toClone, std::default_random_engine& eng)
|
||||
-> std::vector<std::variant<SharedIntrusive<TIBase>, WeakIntrusive<TIBase>>> {
|
||||
std::vector<std::variant<SharedIntrusive<TIBase>, WeakIntrusive<TIBase>>> result;
|
||||
std::uniform_int_distribution<> toCreateDist(4, 64);
|
||||
std::uniform_int_distribution<> isStrongDist(0, 1);
|
||||
auto numToCreate = toCreateDist(eng);
|
||||
result.reserve(numToCreate);
|
||||
for (int i = 0; i < numToCreate; ++i)
|
||||
{
|
||||
if (isStrongDist(eng))
|
||||
{
|
||||
result.emplace_back(SharedIntrusive<TIBase>(toClone));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.emplace_back(WeakIntrusive<TIBase>(toClone));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
constexpr int kLoopIters = 2 * 1024;
|
||||
constexpr int kNumThreads = 16;
|
||||
std::vector<SharedIntrusive<TIBase>> toClone;
|
||||
Barrier loopStartSyncPoint{kNumThreads};
|
||||
Barrier postCreateToCloneSyncPoint{kNumThreads};
|
||||
Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
|
||||
auto engines = [&]() -> std::vector<std::default_random_engine> {
|
||||
std::random_device rd;
|
||||
std::vector<std::default_random_engine> result;
|
||||
result.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
result.emplace_back(rd());
|
||||
return result;
|
||||
}();
|
||||
|
||||
// cloneAndDestroy clones the strong pointer into a vector of mixed
|
||||
// strong and weak pointers and destroys them all at once.
|
||||
// threadId==0 is special.
|
||||
auto cloneAndDestroy = [&](int threadId) {
|
||||
for (int i = 0; i < kLoopIters; ++i)
|
||||
{
|
||||
// ------ Sync Point ------
|
||||
loopStartSyncPoint.arriveAndWait();
|
||||
|
||||
// only thread 0 should reset the state
|
||||
std::optional<TIBase::ResetStatesGuard> rsg;
|
||||
if (threadId == 0)
|
||||
{
|
||||
// Thread 0 is the genesis thread. It creates the strong
|
||||
// pointers to be cloned by the other threads. This
|
||||
// thread will also check that the destructor ran and
|
||||
// clear the temporary variables.
|
||||
|
||||
rsg.emplace(false);
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
EXPECT_TRUE(i == 0 || destructorRan);
|
||||
destructionState.store(0, std::memory_order_release);
|
||||
|
||||
toClone.clear();
|
||||
toClone.resize(kNumThreads);
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
strong->tracingCallback = tracingCallback;
|
||||
std::ranges::fill(toClone, strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateToCloneSyncPoint.arriveAndWait();
|
||||
|
||||
auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
|
||||
toClone[threadId].reset();
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateVecOfPointersSyncPoint.arriveAndWait();
|
||||
|
||||
v.clear();
|
||||
}
|
||||
};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads.emplace_back(cloneAndDestroy, i);
|
||||
}
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IntrusiveSharedTest, multithreaded_clear_mixed_union)
|
||||
{
|
||||
// This test creates and destroys many SharedWeak pointers in a
|
||||
// loop. All the pointers start as strong and a loop randomly
|
||||
// convert them between strong and weak pointers. Both threads clear
|
||||
// all the pointers and check that the invariants hold.
|
||||
//
|
||||
// Note: This test also differs from the test above in that the pointers
|
||||
// randomly change from strong to weak and from weak to strong in a
|
||||
// loop. This can't be done in the variant test above because variant is
|
||||
// not thread safe while the SharedWeakUnion is thread safe.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
std::atomic<int> destructionState{0};
|
||||
// returns destructorRan and partialDestructorRan (in that order)
|
||||
auto getDestructorState = [&]() -> std::pair<bool, bool> {
|
||||
int const s = destructionState.load(std::memory_order_relaxed);
|
||||
return {(s & 1) != 0, (s & 2) != 0};
|
||||
};
|
||||
auto setDestructorRan = [&]() -> void {
|
||||
destructionState.fetch_or(1, std::memory_order_acq_rel);
|
||||
};
|
||||
auto setPartialDeleteRan = [&]() -> void {
|
||||
destructionState.fetch_or(2, std::memory_order_acq_rel);
|
||||
};
|
||||
auto tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
EXPECT_FALSE(partialDeleteRan || destructorRan);
|
||||
setPartialDeleteRan();
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
EXPECT_FALSE(destructorRan);
|
||||
setDestructorRan();
|
||||
}
|
||||
};
|
||||
auto createVecOfPointers =
|
||||
[&](auto const& toClone,
|
||||
std::default_random_engine& eng) -> std::vector<SharedWeakUnion<TIBase>> {
|
||||
std::vector<SharedWeakUnion<TIBase>> result;
|
||||
std::uniform_int_distribution<> toCreateDist(4, 64);
|
||||
auto numToCreate = toCreateDist(eng);
|
||||
result.reserve(numToCreate);
|
||||
for (int i = 0; i < numToCreate; ++i)
|
||||
result.emplace_back(SharedIntrusive<TIBase>(toClone));
|
||||
return result;
|
||||
};
|
||||
constexpr int kLoopIters = 2 * 1024;
|
||||
constexpr int kFlipPointersLoopIters = 256;
|
||||
constexpr int kNumThreads = 16;
|
||||
std::vector<SharedIntrusive<TIBase>> toClone;
|
||||
Barrier loopStartSyncPoint{kNumThreads};
|
||||
Barrier postCreateToCloneSyncPoint{kNumThreads};
|
||||
Barrier postCreateVecOfPointersSyncPoint{kNumThreads};
|
||||
Barrier postFlipPointersLoopSyncPoint{kNumThreads};
|
||||
auto engines = [&]() -> std::vector<std::default_random_engine> {
|
||||
std::random_device rd;
|
||||
std::vector<std::default_random_engine> result;
|
||||
result.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
result.emplace_back(rd());
|
||||
return result;
|
||||
}();
|
||||
|
||||
// cloneAndDestroy clones the strong pointer into a vector of
|
||||
// mixed strong and weak pointers, runs a loop that randomly
|
||||
// changes strong pointers to weak pointers, and destroys them
|
||||
// all at once.
|
||||
auto cloneAndDestroy = [&](int threadId) {
|
||||
for (int i = 0; i < kLoopIters; ++i)
|
||||
{
|
||||
// ------ Sync Point ------
|
||||
loopStartSyncPoint.arriveAndWait();
|
||||
|
||||
// only thread 0 should reset the state
|
||||
std::optional<TIBase::ResetStatesGuard> rsg;
|
||||
if (threadId == 0)
|
||||
{
|
||||
// threadId 0 is the genesis thread. It creates the
|
||||
// strong point to be cloned by the other threads. This
|
||||
// thread will also check that the destructor ran and
|
||||
// clear the temporary variables.
|
||||
rsg.emplace(false);
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
EXPECT_TRUE(i == 0 || destructorRan);
|
||||
destructionState.store(0, std::memory_order_release);
|
||||
|
||||
toClone.clear();
|
||||
toClone.resize(kNumThreads);
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
strong->tracingCallback = tracingCallback;
|
||||
std::ranges::fill(toClone, strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateToCloneSyncPoint.arriveAndWait();
|
||||
|
||||
auto v = createVecOfPointers(toClone[threadId], engines[threadId]);
|
||||
toClone[threadId].reset();
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateVecOfPointersSyncPoint.arriveAndWait();
|
||||
|
||||
std::uniform_int_distribution<> isStrongDist(0, 1);
|
||||
for (int f = 0; f < kFlipPointersLoopIters; ++f)
|
||||
{
|
||||
for (auto& p : v)
|
||||
{
|
||||
if (isStrongDist(engines[threadId]))
|
||||
{
|
||||
p.convertToStrong();
|
||||
}
|
||||
else
|
||||
{
|
||||
p.convertToWeak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postFlipPointersLoopSyncPoint.arriveAndWait();
|
||||
|
||||
v.clear();
|
||||
}
|
||||
};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads.emplace_back(cloneAndDestroy, i);
|
||||
}
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IntrusiveSharedTest, multithreaded_locking_weak)
|
||||
{
|
||||
// This test creates a single shared atomic pointer that multiple thread
|
||||
// create weak pointers from. The threads then lock the weak pointers.
|
||||
// Both threads clear all the pointers and check that the invariants
|
||||
// hold.
|
||||
|
||||
using enum TrackedState;
|
||||
|
||||
TIBase::ResetStatesGuard const rsg{true};
|
||||
|
||||
std::atomic<int> destructionState{0};
|
||||
// returns destructorRan and partialDestructorRan (in that order)
|
||||
auto getDestructorState = [&]() -> std::pair<bool, bool> {
|
||||
int const s = destructionState.load(std::memory_order_relaxed);
|
||||
return {(s & 1) != 0, (s & 2) != 0};
|
||||
};
|
||||
auto setDestructorRan = [&]() -> void {
|
||||
destructionState.fetch_or(1, std::memory_order_acq_rel);
|
||||
};
|
||||
auto setPartialDeleteRan = [&]() -> void {
|
||||
destructionState.fetch_or(2, std::memory_order_acq_rel);
|
||||
};
|
||||
auto tracingCallback = [&](TrackedState cur, std::optional<TrackedState> next) {
|
||||
using enum TrackedState;
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
if (next == PartiallyDeleted)
|
||||
{
|
||||
EXPECT_FALSE(partialDeleteRan || destructorRan);
|
||||
setPartialDeleteRan();
|
||||
}
|
||||
if (next == Deleted)
|
||||
{
|
||||
EXPECT_FALSE(destructorRan);
|
||||
setDestructorRan();
|
||||
}
|
||||
};
|
||||
|
||||
constexpr int kLoopIters = 2 * 1024;
|
||||
constexpr int kLockWeakLoopIters = 256;
|
||||
constexpr int kNumThreads = 16;
|
||||
std::vector<SharedIntrusive<TIBase>> toLock;
|
||||
Barrier loopStartSyncPoint{kNumThreads};
|
||||
Barrier postCreateToLockSyncPoint{kNumThreads};
|
||||
Barrier postLockWeakLoopSyncPoint{kNumThreads};
|
||||
|
||||
// lockAndDestroy creates weak pointers from the strong pointer
|
||||
// and runs a loop that locks the weak pointer. At the end of the loop
|
||||
// all the pointers are destroyed all at once.
|
||||
auto lockAndDestroy = [&](int threadId) {
|
||||
for (int i = 0; i < kLoopIters; ++i)
|
||||
{
|
||||
// ------ Sync Point ------
|
||||
loopStartSyncPoint.arriveAndWait();
|
||||
|
||||
// only thread 0 should reset the state
|
||||
std::optional<TIBase::ResetStatesGuard> rsg;
|
||||
if (threadId == 0)
|
||||
{
|
||||
// threadId 0 is the genesis thread. It creates the
|
||||
// strong point to be locked by the other threads. This
|
||||
// thread will also check that the destructor ran and
|
||||
// clear the temporary variables.
|
||||
rsg.emplace(false);
|
||||
auto [destructorRan, partialDeleteRan] = getDestructorState();
|
||||
EXPECT_TRUE(i == 0 || destructorRan);
|
||||
destructionState.store(0, std::memory_order_release);
|
||||
|
||||
toLock.clear();
|
||||
toLock.resize(kNumThreads);
|
||||
auto strong = makeSharedIntrusive<TIBase>();
|
||||
strong->tracingCallback = tracingCallback;
|
||||
std::ranges::fill(toLock, strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postCreateToLockSyncPoint.arriveAndWait();
|
||||
|
||||
// Multiple threads all create a weak pointer from the same
|
||||
// strong pointer
|
||||
WeakIntrusive const weak{toLock[threadId]};
|
||||
for (int wi = 0; wi < kLockWeakLoopIters; ++wi)
|
||||
{
|
||||
EXPECT_FALSE(weak.expired());
|
||||
auto strong = weak.lock();
|
||||
EXPECT_TRUE(strong);
|
||||
}
|
||||
|
||||
// ------ Sync Point ------
|
||||
postLockWeakLoopSyncPoint.arriveAndWait();
|
||||
|
||||
toLock[threadId].reset();
|
||||
}
|
||||
};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(kNumThreads);
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads.emplace_back(lockAndDestroy, i);
|
||||
}
|
||||
for (int i = 0; i < kNumThreads; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl::tests
|
||||
81
src/tests/libxrpl/basics/KeyCache.cpp
Normal file
81
src/tests/libxrpl/basics/KeyCache.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/TaggedCache.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <helpers/TestSink.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class KeyCacheTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
};
|
||||
|
||||
TEST_F(KeyCacheTest, key_cache)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
TestStopwatch clock;
|
||||
clock.set(0);
|
||||
|
||||
using Key = std::string;
|
||||
using Cache = TaggedCache<Key, int, true>;
|
||||
|
||||
beast::Journal const j{TestSink::instance()};
|
||||
|
||||
// Insert an item, retrieve it, and age it so it gets purged.
|
||||
{
|
||||
Cache c("test", LedgerIndex(1), 2s, clock, j);
|
||||
|
||||
EXPECT_EQ(c.size(), 0);
|
||||
EXPECT_TRUE(c.insert("one"));
|
||||
EXPECT_FALSE(c.insert("one"));
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
EXPECT_TRUE(c.touchIfExists("one"));
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.size(), 0);
|
||||
EXPECT_FALSE(c.touchIfExists("one"));
|
||||
}
|
||||
|
||||
// Insert two items, have one expire
|
||||
{
|
||||
Cache c("test", LedgerIndex(2), 2s, clock, j);
|
||||
|
||||
EXPECT_TRUE(c.insert("one"));
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
EXPECT_TRUE(c.insert("two"));
|
||||
EXPECT_EQ(c.size(), 2);
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.size(), 2);
|
||||
EXPECT_TRUE(c.touchIfExists("two"));
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
}
|
||||
|
||||
// Insert three items (1 over limit), sweep
|
||||
{
|
||||
Cache c("test", LedgerIndex(2), 3s, clock, j);
|
||||
|
||||
EXPECT_TRUE(c.insert("one"));
|
||||
++clock;
|
||||
EXPECT_TRUE(c.insert("two"));
|
||||
++clock;
|
||||
EXPECT_TRUE(c.insert("three"));
|
||||
++clock;
|
||||
EXPECT_EQ(c.size(), 3);
|
||||
c.sweep();
|
||||
EXPECT_LT(c.size(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large
Load Diff
293
src/tests/libxrpl/basics/StringUtilities.cpp
Normal file
293
src/tests/libxrpl/basics/StringUtilities.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/ToString.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class StringUtilitiesTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
static void
|
||||
testUnHexSuccess(std::string const& strIn, std::string const& strExpected)
|
||||
{
|
||||
auto rv = strUnHex(strIn);
|
||||
EXPECT_TRUE(rv);
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
||||
EXPECT_EQ(makeSlice(*rv), makeSlice(strExpected));
|
||||
}
|
||||
|
||||
static void
|
||||
testUnHexFailure(std::string const& strIn)
|
||||
{
|
||||
auto rv = strUnHex(strIn);
|
||||
EXPECT_FALSE(rv);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(StringUtilitiesTest, un_hex)
|
||||
{
|
||||
testUnHexSuccess("526970706c6544", "RippleD");
|
||||
testUnHexSuccess("A", "\n");
|
||||
testUnHexSuccess("0A", "\n");
|
||||
testUnHexSuccess("D0A", "\r\n");
|
||||
testUnHexSuccess("0D0A", "\r\n");
|
||||
testUnHexSuccess("200D0A", " \r\n");
|
||||
testUnHexSuccess("282A2B2C2D2E2F29", "(*+,-./)");
|
||||
|
||||
// Check for things which contain some or only invalid characters
|
||||
testUnHexFailure("123X");
|
||||
testUnHexFailure("V");
|
||||
testUnHexFailure("XRP");
|
||||
}
|
||||
|
||||
TEST_F(StringUtilitiesTest, parse_url)
|
||||
{
|
||||
// Expected passes.
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_TRUE(pUrl.domain.empty());
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
// RFC 3986:
|
||||
// > In general, a URI that uses the generic syntax for authority
|
||||
// with an empty path should be normalized to a path of "/".
|
||||
// Do we want to normalize paths?
|
||||
EXPECT_TRUE(pUrl.path.empty());
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme:///"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_TRUE(pUrl.domain.empty());
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "lower://domain"));
|
||||
EXPECT_EQ(pUrl.scheme, "lower");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_TRUE(pUrl.path.empty());
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "UPPER://domain:234/"));
|
||||
EXPECT_EQ(pUrl.scheme, "upper");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_EQ(*pUrl.port, 234); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_EQ(pUrl.path, "/");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "Mixed://domain/path"));
|
||||
EXPECT_EQ(pUrl.scheme, "mixed");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/path");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://[::1]:123/path"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "::1");
|
||||
EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_EQ(pUrl.path, "/path");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://user:pass@domain:123/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_EQ(pUrl.username, "user");
|
||||
EXPECT_EQ(pUrl.password, "pass");
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://user@domain:123/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_EQ(pUrl.username, "user");
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://:pass@domain:123/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_EQ(pUrl.password, "pass");
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://domain:123/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_EQ(*pUrl.port, 123); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://user:pass@domain/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_EQ(pUrl.username, "user");
|
||||
EXPECT_EQ(pUrl.password, "pass");
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://user@domain/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_EQ(pUrl.username, "user");
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://:pass@domain/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_EQ(pUrl.password, "pass");
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://domain/abc:321"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/abc:321");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme:///path/to/file"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_TRUE(pUrl.domain.empty());
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/path/to/file");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://user:pass@domain/path/with/an@sign"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_EQ(pUrl.username, "user");
|
||||
EXPECT_EQ(pUrl.password, "pass");
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/path/with/an@sign");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://domain/path/with/an@sign"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "domain");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/path/with/an@sign");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "scheme://:999/"));
|
||||
EXPECT_EQ(pUrl.scheme, "scheme");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, ":999");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/");
|
||||
}
|
||||
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_TRUE(parseUrl(pUrl, "http://::1:1234/validators"));
|
||||
EXPECT_EQ(pUrl.scheme, "http");
|
||||
EXPECT_TRUE(pUrl.username.empty());
|
||||
EXPECT_TRUE(pUrl.password.empty());
|
||||
EXPECT_EQ(pUrl.domain, "::0.1.18.52");
|
||||
EXPECT_FALSE(pUrl.port);
|
||||
EXPECT_EQ(pUrl.path, "/validators");
|
||||
}
|
||||
|
||||
// Expected fails.
|
||||
{
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_FALSE(parseUrl(pUrl, ""));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "nonsense"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "://"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, ":///"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "scheme://user:pass@domain:65536/abc:321"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:23498765/"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:0/"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:+7/"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:-7234/"));
|
||||
EXPECT_FALSE(parseUrl(pUrl, "UPPER://domain:@#$56!/"));
|
||||
}
|
||||
|
||||
{
|
||||
std::string const strUrl("s://" + std::string(8192, ':'));
|
||||
ParsedUrl pUrl;
|
||||
EXPECT_FALSE(parseUrl(pUrl, strUrl));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(StringUtilitiesTest, to_string)
|
||||
{
|
||||
auto result = to_string("hello");
|
||||
EXPECT_EQ(result, "hello");
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
246
src/tests/libxrpl/basics/TaggedCache.cpp
Normal file
246
src/tests/libxrpl/basics/TaggedCache.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
|
||||
#include <xrpl/basics/IntrusivePointer.h>
|
||||
#include <xrpl/basics/IntrusiveRefCounts.h>
|
||||
#include <xrpl/basics/TaggedCache.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <helpers/TestSink.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
I guess you can put some items in, make sure they're still there. Let some
|
||||
time pass, make sure they're gone. Keep a strong pointer to one of them, make
|
||||
sure you can still find it even after time passes. Create two objects with
|
||||
the same key, canonicalize them both and make sure you get the same object.
|
||||
Put an object in but keep a strong pointer to it, advance the clock a lot,
|
||||
then canonicalize a new object with the same key, make sure you get the
|
||||
original object.
|
||||
*/
|
||||
|
||||
TEST(TaggedCacheTest, tagged_cache)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
beast::Journal const journal{TestSink::instance()};
|
||||
|
||||
TestStopwatch clock;
|
||||
clock.set(0);
|
||||
|
||||
using Key = LedgerIndex;
|
||||
using Value = std::string;
|
||||
using Cache = TaggedCache<Key, Value>;
|
||||
|
||||
Cache c("test", 1, 1s, clock, journal);
|
||||
|
||||
// Insert an item, retrieve it, and age it so it gets purged.
|
||||
{
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.getTrackSize(), 0);
|
||||
EXPECT_FALSE(c.insert(1, "one"));
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.getTrackSize(), 1);
|
||||
|
||||
{
|
||||
std::string s;
|
||||
EXPECT_TRUE(c.retrieve(1, s));
|
||||
EXPECT_EQ(s, "one");
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.getTrackSize(), 0);
|
||||
}
|
||||
|
||||
// Insert an item, maintain a strong pointer, age it, and
|
||||
// verify that the entry still exists.
|
||||
{
|
||||
EXPECT_FALSE(c.insert(2, "two"));
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.getTrackSize(), 1);
|
||||
|
||||
{
|
||||
auto p = c.fetch(2);
|
||||
EXPECT_NE(p, nullptr);
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.getTrackSize(), 1);
|
||||
}
|
||||
|
||||
// Make sure its gone now that our reference is gone
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.getTrackSize(), 0);
|
||||
}
|
||||
|
||||
// Insert the same key/value pair and make sure we get the same result
|
||||
{
|
||||
EXPECT_FALSE(c.insert(3, "three"));
|
||||
|
||||
{
|
||||
auto const p1 = c.fetch(3);
|
||||
auto p2 = std::make_shared<Value>("three");
|
||||
c.canonicalizeReplaceClient(3, p2);
|
||||
EXPECT_EQ(p1.get(), p2.get());
|
||||
}
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.getTrackSize(), 0);
|
||||
}
|
||||
|
||||
// Put an object in but keep a strong pointer to it, advance the clock a
|
||||
// lot, then canonicalize a new object with the same key, make sure you
|
||||
// get the original object.
|
||||
{
|
||||
// Put an object in
|
||||
EXPECT_FALSE(c.insert(4, "four"));
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.getTrackSize(), 1);
|
||||
|
||||
{
|
||||
// Keep a strong pointer to it
|
||||
auto const p1 = c.fetch(4);
|
||||
EXPECT_NE(p1, nullptr);
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.getTrackSize(), 1);
|
||||
// Advance the clock a lot
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.getTrackSize(), 1);
|
||||
// Canonicalize a new object with the same key
|
||||
auto p2 = std::make_shared<std::string>("four");
|
||||
EXPECT_TRUE(c.canonicalizeReplaceClient(4, p2));
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.getTrackSize(), 1);
|
||||
// Make sure we get the original object
|
||||
EXPECT_EQ(p1.get(), p2.get());
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.getTrackSize(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
EXPECT_FALSE(c.insert(5, "five"));
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
|
||||
{
|
||||
auto const p1 = c.fetch(5);
|
||||
EXPECT_NE(p1, nullptr);
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
|
||||
// Advance the clock a lot
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
|
||||
auto p2 = std::make_shared<std::string>("five_2");
|
||||
EXPECT_TRUE(c.canonicalizeReplaceCache(5, p2));
|
||||
EXPECT_EQ(c.getCacheSize(), 1);
|
||||
EXPECT_EQ(c.size(), 1);
|
||||
// Make sure the caller's original pointer is unchanged
|
||||
EXPECT_NE(p1.get(), p2.get());
|
||||
EXPECT_EQ(*p2, "five_2");
|
||||
|
||||
auto const p3 = c.fetch(5);
|
||||
EXPECT_NE(p3, nullptr);
|
||||
EXPECT_EQ(p3.get(), p2.get());
|
||||
EXPECT_NE(p3.get(), p1.get());
|
||||
}
|
||||
|
||||
++clock;
|
||||
c.sweep();
|
||||
EXPECT_EQ(c.getCacheSize(), 0);
|
||||
EXPECT_EQ(c.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
struct MyRefCountObject : IntrusiveRefCounts
|
||||
{
|
||||
std::string data;
|
||||
|
||||
// Needed to support weak intrusive pointers
|
||||
virtual void
|
||||
partialDestructor()
|
||||
{
|
||||
}
|
||||
|
||||
MyRefCountObject() = default;
|
||||
explicit MyRefCountObject(std::string data) : data(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(std::string const& other) const
|
||||
{
|
||||
return data == other;
|
||||
}
|
||||
};
|
||||
|
||||
using IntrPtrCache = TaggedCache<
|
||||
Key,
|
||||
MyRefCountObject,
|
||||
/*IsKeyCache*/ false,
|
||||
intr_ptr::SharedWeakUnionPtr<MyRefCountObject>,
|
||||
intr_ptr::SharedPtr<MyRefCountObject>>;
|
||||
|
||||
IntrPtrCache intrPtrCache("IntrPtrTest", 1, 1s, clock, journal);
|
||||
|
||||
intrPtrCache.canonicalizeReplaceCache(1, intr_ptr::makeShared<MyRefCountObject>("one"));
|
||||
EXPECT_EQ(intrPtrCache.getCacheSize(), 1);
|
||||
EXPECT_EQ(intrPtrCache.size(), 1);
|
||||
|
||||
{
|
||||
{
|
||||
intrPtrCache.canonicalizeReplaceCache(
|
||||
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced"));
|
||||
|
||||
auto p = intrPtrCache.fetch(1);
|
||||
EXPECT_EQ(*p, "one_replaced");
|
||||
|
||||
// Advance the clock a lot
|
||||
++clock;
|
||||
intrPtrCache.sweep();
|
||||
EXPECT_EQ(intrPtrCache.getCacheSize(), 0);
|
||||
EXPECT_EQ(intrPtrCache.size(), 1);
|
||||
|
||||
intrPtrCache.canonicalizeReplaceCache(
|
||||
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_2"));
|
||||
|
||||
auto p2 = intrPtrCache.fetch(1);
|
||||
EXPECT_EQ(*p2, "one_replaced_2");
|
||||
|
||||
intrPtrCache.del(1, true);
|
||||
}
|
||||
|
||||
intrPtrCache.canonicalizeReplaceCache(
|
||||
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_3"));
|
||||
auto p3 = intrPtrCache.fetch(1);
|
||||
EXPECT_EQ(*p3, "one_replaced_3");
|
||||
}
|
||||
|
||||
++clock;
|
||||
intrPtrCache.sweep();
|
||||
EXPECT_EQ(intrPtrCache.getCacheSize(), 0);
|
||||
EXPECT_EQ(intrPtrCache.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
328
src/tests/libxrpl/basics/Units.cpp
Normal file
328
src/tests/libxrpl/basics/Units.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(UnitsTest, types)
|
||||
{
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
{
|
||||
XRPAmount const x{100};
|
||||
EXPECT_EQ(x.drops(), 100);
|
||||
EXPECT_TRUE((std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
EXPECT_EQ(y.value(), 400);
|
||||
EXPECT_TRUE((std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
auto z = 4 * y;
|
||||
EXPECT_EQ(z.value(), 1600);
|
||||
EXPECT_TRUE((std::is_same_v<decltype(z)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel32 const f{10};
|
||||
FeeLevel32 const baseFee{100};
|
||||
|
||||
auto drops = mulDiv(baseFee, x, f);
|
||||
|
||||
EXPECT_TRUE(drops);
|
||||
EXPECT_EQ(drops.value(), 1000); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_TRUE(
|
||||
(std::is_same_v<std::remove_reference_t<decltype(*drops)>::unit_type, unit::dropTag>));
|
||||
|
||||
EXPECT_TRUE((std::is_same_v<std::remove_reference_t<decltype(*drops)>, XRPAmount>));
|
||||
}
|
||||
{
|
||||
XRPAmount const x{100};
|
||||
EXPECT_EQ(x.value(), 100);
|
||||
EXPECT_TRUE((std::is_same_v<decltype(x)::unit_type, unit::dropTag>));
|
||||
auto y = 4u * x;
|
||||
EXPECT_EQ(y.value(), 400);
|
||||
EXPECT_TRUE((std::is_same_v<decltype(y)::unit_type, unit::dropTag>));
|
||||
|
||||
FeeLevel64 const f{10};
|
||||
FeeLevel64 const baseFee{100};
|
||||
|
||||
auto drops = mulDiv(baseFee, x, f);
|
||||
|
||||
EXPECT_TRUE(drops);
|
||||
EXPECT_EQ(drops.value(), 1000); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_TRUE(
|
||||
(std::is_same_v<std::remove_reference_t<decltype(*drops)>::unit_type, unit::dropTag>));
|
||||
EXPECT_TRUE((std::is_same_v<std::remove_reference_t<decltype(*drops)>, XRPAmount>));
|
||||
}
|
||||
{
|
||||
FeeLevel64 const x{1024};
|
||||
EXPECT_EQ(x.value(), 1024);
|
||||
EXPECT_TRUE((std::is_same_v<decltype(x)::unit_type, unit::feelevelTag>));
|
||||
std::uint64_t const m = 4;
|
||||
auto y = m * x;
|
||||
EXPECT_EQ(y.value(), 4096);
|
||||
EXPECT_TRUE((std::is_same_v<decltype(y)::unit_type, unit::feelevelTag>));
|
||||
|
||||
XRPAmount const basefee{10};
|
||||
FeeLevel64 const referencefee{256};
|
||||
|
||||
auto drops = mulDiv(x, basefee, referencefee);
|
||||
|
||||
EXPECT_TRUE(drops);
|
||||
EXPECT_EQ(drops.value(), 40); // NOLINT(bugprone-unchecked-optional-access)
|
||||
EXPECT_TRUE(
|
||||
(std::is_same_v<std::remove_reference_t<decltype(*drops)>::unit_type, unit::dropTag>));
|
||||
EXPECT_TRUE((std::is_same_v<std::remove_reference_t<decltype(*drops)>, XRPAmount>));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UnitsTest, json)
|
||||
{
|
||||
// Json value functionality
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
{
|
||||
FeeLevel32 const x{std::numeric_limits<std::uint32_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::UInt);
|
||||
EXPECT_EQ(y, json::Value{x.fee()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevel32 const x{std::numeric_limits<std::uint32_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::UInt);
|
||||
EXPECT_EQ(y, json::Value{x.fee()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevel64 const x{std::numeric_limits<std::uint64_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::UInt);
|
||||
EXPECT_EQ(y, json::Value{std::numeric_limits<std::uint32_t>::max()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevel64 const x{std::numeric_limits<std::uint64_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::UInt);
|
||||
EXPECT_EQ(y, json::Value{0});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevelDouble const x{std::numeric_limits<double>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::Real);
|
||||
EXPECT_EQ(y, json::Value{std::numeric_limits<double>::max()});
|
||||
}
|
||||
|
||||
{
|
||||
FeeLevelDouble const x{std::numeric_limits<double>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::Real);
|
||||
EXPECT_EQ(y, json::Value{std::numeric_limits<double>::min()});
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const x{std::numeric_limits<std::int64_t>::max()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::Int);
|
||||
EXPECT_EQ(y, json::Value{std::numeric_limits<std::int32_t>::max()});
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const x{std::numeric_limits<std::int64_t>::min()};
|
||||
auto y = x.jsonClipped();
|
||||
EXPECT_EQ(y.type(), json::ValueType::Int);
|
||||
EXPECT_EQ(y, json::Value{std::numeric_limits<std::int32_t>::min()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UnitsTest, functions)
|
||||
{
|
||||
// Explicitly test every defined function for the ValueUnit class
|
||||
// since some of them are templated, but not used anywhere else.
|
||||
using FeeLevel32 = FeeLevel<std::uint32_t>;
|
||||
|
||||
{
|
||||
auto make = [&](auto x) -> FeeLevel64 { return x; };
|
||||
auto explicitmake = [&](auto x) -> FeeLevel64 { return FeeLevel64{x}; };
|
||||
|
||||
[[maybe_unused]]
|
||||
FeeLevel64 const defaulted{};
|
||||
FeeLevel64 test{0};
|
||||
EXPECT_EQ(test.fee(), 0);
|
||||
|
||||
test = explicitmake(beast::kZero);
|
||||
EXPECT_EQ(test.fee(), 0);
|
||||
|
||||
test = beast::kZero;
|
||||
EXPECT_EQ(test.fee(), 0);
|
||||
|
||||
test = explicitmake(100u);
|
||||
EXPECT_EQ(test.fee(), 100);
|
||||
|
||||
FeeLevel64 const targetSame{200u};
|
||||
FeeLevel32 const targetOther{300u};
|
||||
test = make(targetSame);
|
||||
EXPECT_EQ(test.fee(), 200);
|
||||
EXPECT_EQ(test, targetSame);
|
||||
EXPECT_TRUE(test < FeeLevel64{1000});
|
||||
EXPECT_TRUE(test > FeeLevel64{100});
|
||||
test = make(targetOther);
|
||||
EXPECT_EQ(test.fee(), 300);
|
||||
EXPECT_EQ(test, targetOther);
|
||||
|
||||
test = std::uint64_t(200);
|
||||
EXPECT_EQ(test.fee(), 200);
|
||||
test = std::uint32_t(300);
|
||||
EXPECT_EQ(test.fee(), 300);
|
||||
|
||||
test = targetSame;
|
||||
EXPECT_EQ(test.fee(), 200);
|
||||
test = targetOther.fee();
|
||||
EXPECT_EQ(test.fee(), 300);
|
||||
EXPECT_EQ(test, targetOther);
|
||||
|
||||
test = targetSame * 2;
|
||||
EXPECT_EQ(test.fee(), 400);
|
||||
test = 3 * targetSame;
|
||||
EXPECT_EQ(test.fee(), 600);
|
||||
test = targetSame / 10;
|
||||
EXPECT_EQ(test.fee(), 20);
|
||||
|
||||
test += targetSame;
|
||||
EXPECT_EQ(test.fee(), 220);
|
||||
|
||||
test -= targetSame;
|
||||
EXPECT_EQ(test.fee(), 20);
|
||||
|
||||
test++;
|
||||
EXPECT_EQ(test.fee(), 21);
|
||||
++test;
|
||||
EXPECT_EQ(test.fee(), 22);
|
||||
test--;
|
||||
EXPECT_EQ(test.fee(), 21);
|
||||
--test;
|
||||
EXPECT_EQ(test.fee(), 20);
|
||||
|
||||
test *= 5;
|
||||
EXPECT_EQ(test.fee(), 100);
|
||||
test /= 2;
|
||||
EXPECT_EQ(test.fee(), 50);
|
||||
test %= 13;
|
||||
EXPECT_EQ(test.fee(), 11);
|
||||
|
||||
/*
|
||||
// illegal with unsigned
|
||||
test = -test;
|
||||
EXPECT_EQ(test.fee(), -11);
|
||||
EXPECT_EQ(test.signum(), -1);
|
||||
EXPECT_EQ(to_string(test), "-11");
|
||||
*/
|
||||
|
||||
EXPECT_TRUE(test);
|
||||
test = 0;
|
||||
EXPECT_FALSE(test);
|
||||
EXPECT_EQ(test.signum(), 0);
|
||||
test = targetSame;
|
||||
EXPECT_EQ(test.signum(), 1);
|
||||
EXPECT_EQ(to_string(test), "200");
|
||||
}
|
||||
{
|
||||
auto make = [&](auto x) -> FeeLevelDouble { return x; };
|
||||
auto explicitmake = [&](auto x) -> FeeLevelDouble { return FeeLevelDouble{x}; };
|
||||
|
||||
[[maybe_unused]]
|
||||
FeeLevelDouble const defaulted{};
|
||||
FeeLevelDouble test{0};
|
||||
EXPECT_EQ(test.fee(), 0);
|
||||
|
||||
test = explicitmake(beast::kZero);
|
||||
EXPECT_EQ(test.fee(), 0);
|
||||
|
||||
test = beast::kZero;
|
||||
EXPECT_EQ(test.fee(), 0);
|
||||
|
||||
test = explicitmake(100.0);
|
||||
EXPECT_EQ(test.fee(), 100);
|
||||
|
||||
FeeLevelDouble const targetSame{200.0};
|
||||
FeeLevel64 const targetOther{300};
|
||||
test = make(targetSame);
|
||||
EXPECT_EQ(test.fee(), 200);
|
||||
EXPECT_EQ(test, targetSame);
|
||||
EXPECT_TRUE(test < FeeLevelDouble{1000.0});
|
||||
EXPECT_TRUE(test > FeeLevelDouble{100.0});
|
||||
test = targetOther.fee();
|
||||
EXPECT_EQ(test.fee(), 300);
|
||||
EXPECT_EQ(test, targetOther);
|
||||
|
||||
test = 200.0;
|
||||
EXPECT_EQ(test.fee(), 200);
|
||||
test = std::uint64_t(300);
|
||||
EXPECT_EQ(test.fee(), 300);
|
||||
|
||||
test = targetSame;
|
||||
EXPECT_EQ(test.fee(), 200);
|
||||
|
||||
test = targetSame * 2;
|
||||
EXPECT_EQ(test.fee(), 400);
|
||||
test = 3 * targetSame;
|
||||
EXPECT_EQ(test.fee(), 600);
|
||||
test = targetSame / 10;
|
||||
EXPECT_EQ(test.fee(), 20);
|
||||
|
||||
test += targetSame;
|
||||
EXPECT_EQ(test.fee(), 220);
|
||||
|
||||
test -= targetSame;
|
||||
EXPECT_EQ(test.fee(), 20);
|
||||
|
||||
test++;
|
||||
EXPECT_EQ(test.fee(), 21);
|
||||
++test;
|
||||
EXPECT_EQ(test.fee(), 22);
|
||||
test--;
|
||||
EXPECT_EQ(test.fee(), 21);
|
||||
--test;
|
||||
EXPECT_EQ(test.fee(), 20);
|
||||
|
||||
test *= 5;
|
||||
EXPECT_EQ(test.fee(), 100);
|
||||
test /= 2;
|
||||
EXPECT_EQ(test.fee(), 50);
|
||||
/* illegal with floating
|
||||
test %= 13;
|
||||
EXPECT_EQ(test.fee(), 11);
|
||||
*/
|
||||
|
||||
// legal with signed
|
||||
test = -test;
|
||||
EXPECT_EQ(test.fee(), -50);
|
||||
EXPECT_EQ(test.signum(), -1);
|
||||
EXPECT_EQ(to_string(test), "-50.000000");
|
||||
|
||||
EXPECT_TRUE(test);
|
||||
test = 0;
|
||||
EXPECT_FALSE(test);
|
||||
EXPECT_EQ(test.signum(), 0);
|
||||
test = targetSame;
|
||||
EXPECT_EQ(test.signum(), 1);
|
||||
EXPECT_EQ(to_string(test), "200.000000");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UnitsTest, initial_xrp)
|
||||
{
|
||||
EXPECT_EQ(kInitialXrp.drops(), 100'000'000'000'000'000);
|
||||
EXPECT_EQ(kInitialXrp, XRPAmount{100'000'000'000'000'000});
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
295
src/tests/libxrpl/basics/XRPAmount.cpp
Normal file
295
src/tests/libxrpl/basics/XRPAmount.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TEST(XRPAmountTest, sig_num)
|
||||
{
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
if (i < 0)
|
||||
{
|
||||
EXPECT_TRUE(x.signum() < 0);
|
||||
}
|
||||
else if (i > 0)
|
||||
{
|
||||
EXPECT_TRUE(x.signum() > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_EQ(x.signum(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(XRPAmountTest, beast_zero)
|
||||
{
|
||||
using beast::kZero;
|
||||
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
EXPECT_TRUE((i == 0) == (x == kZero));
|
||||
EXPECT_TRUE((i != 0) == (x != kZero));
|
||||
EXPECT_TRUE((i < 0) == (x < kZero));
|
||||
EXPECT_TRUE((i > 0) == (x > kZero));
|
||||
EXPECT_TRUE((i <= 0) == (x <= kZero));
|
||||
EXPECT_TRUE((i >= 0) == (x >= kZero));
|
||||
|
||||
EXPECT_TRUE((0 == i) == (kZero == x));
|
||||
EXPECT_TRUE((0 != i) == (kZero != x));
|
||||
EXPECT_TRUE((0 < i) == (kZero < x));
|
||||
EXPECT_TRUE((0 > i) == (kZero > x));
|
||||
EXPECT_TRUE((0 <= i) == (kZero <= x));
|
||||
EXPECT_TRUE((0 >= i) == (kZero >= x));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(XRPAmountTest, comparisons)
|
||||
{
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
for (auto j : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const y(j);
|
||||
|
||||
EXPECT_EQ((i == j), (x == y));
|
||||
EXPECT_EQ((i != j), (x != y));
|
||||
EXPECT_EQ((i < j), (x < y));
|
||||
EXPECT_EQ((i > j), (x > y));
|
||||
EXPECT_EQ((i <= j), (x <= y));
|
||||
EXPECT_EQ((i >= j), (x >= y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(XRPAmountTest, add_sub)
|
||||
{
|
||||
for (auto i : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const x(i);
|
||||
|
||||
for (auto j : {-1, 0, 1})
|
||||
{
|
||||
XRPAmount const y(j);
|
||||
|
||||
EXPECT_EQ(XRPAmount(i + j), (x + y));
|
||||
EXPECT_EQ(XRPAmount(i - j), (x - y));
|
||||
|
||||
EXPECT_EQ((x + y), (y + x)); // addition is commutative
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(XRPAmountTest, decimal)
|
||||
{
|
||||
// Tautology
|
||||
EXPECT_EQ(kDropsPerXrp.decimalXRP(), 1);
|
||||
|
||||
XRPAmount test{1};
|
||||
EXPECT_EQ(test.decimalXRP(), 0.000001);
|
||||
|
||||
test = -test;
|
||||
EXPECT_EQ(test.decimalXRP(), -0.000001);
|
||||
|
||||
test = 100'000'000;
|
||||
EXPECT_EQ(test.decimalXRP(), 100);
|
||||
|
||||
test = -test;
|
||||
EXPECT_EQ(test.decimalXRP(), -100);
|
||||
}
|
||||
|
||||
TEST(XRPAmountTest, functions)
|
||||
{
|
||||
// Explicitly test every defined function for the XRPAmount class
|
||||
// since some of them are templated, but not used anywhere else.
|
||||
auto make = [&](auto x) -> XRPAmount { return XRPAmount{x}; };
|
||||
|
||||
XRPAmount const defaulted{};
|
||||
(void)defaulted;
|
||||
XRPAmount test{0};
|
||||
EXPECT_EQ(test.drops(), 0);
|
||||
|
||||
test = make(beast::kZero);
|
||||
EXPECT_EQ(test.drops(), 0);
|
||||
|
||||
test = beast::kZero;
|
||||
EXPECT_EQ(test.drops(), 0);
|
||||
|
||||
test = make(100);
|
||||
EXPECT_EQ(test.drops(), 100);
|
||||
|
||||
test = make(100u);
|
||||
EXPECT_EQ(test.drops(), 100);
|
||||
|
||||
XRPAmount const targetSame{200u};
|
||||
test = make(targetSame);
|
||||
EXPECT_EQ(test.drops(), 200);
|
||||
EXPECT_EQ(test, targetSame);
|
||||
EXPECT_TRUE(test < XRPAmount{1000});
|
||||
EXPECT_TRUE(test > XRPAmount{100});
|
||||
|
||||
test = std::int64_t(200);
|
||||
EXPECT_EQ(test.drops(), 200);
|
||||
test = std::uint32_t(300);
|
||||
EXPECT_EQ(test.drops(), 300);
|
||||
|
||||
test = targetSame;
|
||||
EXPECT_EQ(test.drops(), 200);
|
||||
auto testOther = test.dropsAs<std::uint32_t>();
|
||||
EXPECT_TRUE(testOther);
|
||||
EXPECT_EQ(*testOther, 200); // NOLINT(bugprone-unchecked-optional-access)
|
||||
test = std::numeric_limits<std::uint64_t>::max();
|
||||
testOther = test.dropsAs<std::uint32_t>();
|
||||
EXPECT_FALSE(testOther);
|
||||
test = -1;
|
||||
testOther = test.dropsAs<std::uint32_t>();
|
||||
EXPECT_FALSE(testOther);
|
||||
|
||||
test = targetSame * 2;
|
||||
EXPECT_EQ(test.drops(), 400);
|
||||
test = 3 * targetSame;
|
||||
EXPECT_EQ(test.drops(), 600);
|
||||
test = 20;
|
||||
EXPECT_EQ(test.drops(), 20);
|
||||
|
||||
test += targetSame;
|
||||
EXPECT_EQ(test.drops(), 220);
|
||||
|
||||
test -= targetSame;
|
||||
EXPECT_EQ(test.drops(), 20);
|
||||
|
||||
test *= 5;
|
||||
EXPECT_EQ(test.drops(), 100);
|
||||
test = 50;
|
||||
EXPECT_EQ(test.drops(), 50);
|
||||
test -= 39;
|
||||
EXPECT_EQ(test.drops(), 11);
|
||||
|
||||
// legal with signed
|
||||
test = -test;
|
||||
EXPECT_EQ(test.drops(), -11);
|
||||
EXPECT_EQ(test.signum(), -1);
|
||||
EXPECT_EQ(to_string(test), "-11");
|
||||
|
||||
EXPECT_TRUE(test);
|
||||
test = 0;
|
||||
EXPECT_FALSE(test);
|
||||
EXPECT_EQ(test.signum(), 0);
|
||||
test = targetSame;
|
||||
EXPECT_EQ(test.signum(), 1);
|
||||
EXPECT_EQ(to_string(test), "200");
|
||||
}
|
||||
|
||||
TEST(XRPAmountTest, mul_ratio)
|
||||
{
|
||||
constexpr auto kMaxUInt32 = std::numeric_limits<std::uint32_t>::max();
|
||||
constexpr auto kMaxXrp = std::numeric_limits<XRPAmount::value_type>::max();
|
||||
constexpr auto kMinXrp = std::numeric_limits<XRPAmount::value_type>::min();
|
||||
|
||||
{
|
||||
// multiply by a number that would overflow then divide by the same
|
||||
// number, and check we didn't lose any value
|
||||
XRPAmount big(kMaxXrp);
|
||||
EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, false));
|
||||
|
||||
// multiply and divide by values that would overflow if done
|
||||
// naively, and check that it gives the correct answer
|
||||
big -= 0xf; // Subtract a little so it's divisible by 4
|
||||
EXPECT_EQ(mulRatio(big, 3, 4, false).value(), (big.value() / 4) * 3);
|
||||
EXPECT_EQ(mulRatio(big, 3, 4, true).value(), (big.value() / 4) * 3);
|
||||
EXPECT_EQ(big.value() % 4, 0);
|
||||
EXPECT_GT(big.value(), kMaxXrp / 3);
|
||||
EXPECT_LE(big.value() / 4, kMaxXrp / 3);
|
||||
}
|
||||
|
||||
{
|
||||
// Similar test as above, but for negative values
|
||||
XRPAmount big(kMinXrp); // NOLINT TODO
|
||||
EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, true));
|
||||
// rounding mode shouldn't matter as the result is exact
|
||||
EXPECT_EQ(big, mulRatio(big, kMaxUInt32, kMaxUInt32, false));
|
||||
|
||||
// multiply and divide by values that would overflow if done
|
||||
// naively, and check that it gives the correct answer
|
||||
EXPECT_EQ(mulRatio(big, 3, 4, false).value(), (big.value() / 4) * 3);
|
||||
EXPECT_EQ(mulRatio(big, 3, 4, true).value(), (big.value() / 4) * 3);
|
||||
EXPECT_EQ(big.value() % 4, 0);
|
||||
EXPECT_LT(big.value(), kMinXrp / 3);
|
||||
EXPECT_GE(big.value() / 4, kMinXrp / 3);
|
||||
}
|
||||
|
||||
{
|
||||
// small amounts
|
||||
XRPAmount const tiny(1);
|
||||
// Round up should give the smallest allowable number
|
||||
EXPECT_EQ(tiny, mulRatio(tiny, 1, kMaxUInt32, true));
|
||||
// rounding down should be zero
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tiny, 1, kMaxUInt32, false));
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tiny, kMaxUInt32 - 1, kMaxUInt32, false));
|
||||
|
||||
// tiny negative numbers
|
||||
XRPAmount const tinyNeg(-1);
|
||||
// Round up should give zero
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, 1, kMaxUInt32, true));
|
||||
EXPECT_EQ(beast::kZero, mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, true));
|
||||
// rounding down should be tiny
|
||||
EXPECT_EQ(tinyNeg, mulRatio(tinyNeg, kMaxUInt32 - 1, kMaxUInt32, false));
|
||||
}
|
||||
|
||||
{ // rounding
|
||||
{
|
||||
XRPAmount const one(1);
|
||||
auto const rup = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, true);
|
||||
auto const rdown = mulRatio(one, kMaxUInt32 - 1, kMaxUInt32, false);
|
||||
EXPECT_EQ(rup.drops() - rdown.drops(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const big(kMaxXrp);
|
||||
auto const rup = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, true);
|
||||
auto const rdown = mulRatio(big, kMaxUInt32 - 1, kMaxUInt32, false);
|
||||
EXPECT_EQ(rup.drops() - rdown.drops(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
XRPAmount const negOne(-1);
|
||||
auto const rup = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, true);
|
||||
auto const rdown = mulRatio(negOne, kMaxUInt32 - 1, kMaxUInt32, false);
|
||||
EXPECT_EQ(rup.drops() - rdown.drops(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// division by zero
|
||||
XRPAmount const one(1);
|
||||
EXPECT_ANY_THROW({ mulRatio(one, 1, 0, true); });
|
||||
}
|
||||
|
||||
{
|
||||
// overflow
|
||||
XRPAmount const big(kMaxXrp);
|
||||
EXPECT_ANY_THROW({ mulRatio(big, 2, 1, true); });
|
||||
}
|
||||
|
||||
{
|
||||
// underflow
|
||||
XRPAmount const bigNegative(kMinXrp + 10);
|
||||
EXPECT_EQ(mulRatio(bigNegative, 2, 1, true), kMinXrp);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
438
src/tests/libxrpl/basics/base58.cpp
Normal file
438
src/tests/libxrpl/basics/base58.cpp
Normal file
@@ -0,0 +1,438 @@
|
||||
#include <xrpl/protocol/detail/token_errors.h>
|
||||
|
||||
#include <boost/multiprecision/cpp_int.hpp> // IWYU pragma: keep
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#ifndef _MSC_VER
|
||||
|
||||
#include <xrpl/protocol/detail/b58_utils.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <random>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
|
||||
namespace xrpl::test {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
randEngine() -> std::mt19937&
|
||||
{
|
||||
static std::mt19937 kR = [] {
|
||||
std::random_device rd;
|
||||
return std::mt19937{rd()};
|
||||
}();
|
||||
return kR;
|
||||
}
|
||||
|
||||
constexpr int kNumTokenTypeIndexes = 9;
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
tokenTypeAndSize(int i) -> std::tuple<xrpl::TokenType, std::size_t>
|
||||
{
|
||||
assert(i < kNumTokenTypeIndexes);
|
||||
|
||||
switch (i)
|
||||
{
|
||||
using enum xrpl::TokenType;
|
||||
case 0:
|
||||
return {None, 20};
|
||||
case 1:
|
||||
return {NodePublic, 32};
|
||||
case 2:
|
||||
return {NodePublic, 33};
|
||||
case 3:
|
||||
return {NodePrivate, 32};
|
||||
case 4:
|
||||
return {AccountID, 20};
|
||||
case 5:
|
||||
return {AccountPublic, 32};
|
||||
case 6:
|
||||
return {AccountPublic, 33};
|
||||
case 7:
|
||||
return {AccountSecret, 32};
|
||||
case 8:
|
||||
return {FamilySeed, 16};
|
||||
default:
|
||||
throw std::invalid_argument(
|
||||
"Invalid token selection passed to tokenTypeAndSize() "
|
||||
"in " __FILE__);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
randomTokenTypeAndSize() -> std::tuple<xrpl::TokenType, std::size_t>
|
||||
{
|
||||
using namespace xrpl;
|
||||
auto& rng = randEngine();
|
||||
std::uniform_int_distribution<> d(0, 8);
|
||||
return tokenTypeAndSize(d(rng));
|
||||
}
|
||||
|
||||
// Return the token type and subspan of `d` to use as test data.
|
||||
[[nodiscard]] inline auto
|
||||
randomB256TestData(std::span<std::uint8_t> d)
|
||||
-> std::tuple<xrpl::TokenType, std::span<std::uint8_t>>
|
||||
{
|
||||
auto& rng = randEngine();
|
||||
std::uniform_int_distribution<std::uint8_t> dist(0, 255);
|
||||
auto [tokType, tokSize] = randomTokenTypeAndSize();
|
||||
std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); });
|
||||
return {tokType, d.subspan(0, tokSize)};
|
||||
}
|
||||
|
||||
inline void
|
||||
printAsChar(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
|
||||
{
|
||||
auto asString = [](std::span<std::uint8_t> s) {
|
||||
std::string r;
|
||||
r.resize(s.size());
|
||||
std::ranges::copy(s, r.begin());
|
||||
return r;
|
||||
};
|
||||
auto sa = asString(a);
|
||||
auto sb = asString(b);
|
||||
std::cerr << "\n\n" << sa << "\n" << sb << "\n";
|
||||
}
|
||||
|
||||
inline void
|
||||
printAsInt(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
|
||||
{
|
||||
auto asString = [](std::span<std::uint8_t> s) -> std::string {
|
||||
std::stringstream sstr;
|
||||
for (auto i : s)
|
||||
{
|
||||
sstr << std::setw(3) << int(i) << ',';
|
||||
}
|
||||
return sstr.str();
|
||||
};
|
||||
auto sa = asString(a);
|
||||
auto sb = asString(b);
|
||||
std::cerr << "\n\n" << sa << "\n" << sb << "\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace multiprecision_utils {
|
||||
|
||||
boost::multiprecision::checked_uint512_t
|
||||
toBoostMP(std::span<std::uint64_t> in)
|
||||
{
|
||||
boost::multiprecision::checked_uint512_t mbp = 0;
|
||||
for (auto const& i : std::views::reverse(in))
|
||||
{
|
||||
mbp <<= 64;
|
||||
mbp += i;
|
||||
}
|
||||
return mbp;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t>
|
||||
randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5)
|
||||
{
|
||||
auto eng = randEngine();
|
||||
std::uniform_int_distribution<std::uint8_t> numCoeffDist(minSize, maxSize);
|
||||
std::uniform_int_distribution<std::uint64_t> dist;
|
||||
auto const numCoeff = numCoeffDist(eng);
|
||||
std::vector<std::uint64_t> coeffs;
|
||||
coeffs.reserve(numCoeff);
|
||||
for (int i = 0; i < numCoeff; ++i)
|
||||
{
|
||||
coeffs.push_back(dist(eng));
|
||||
}
|
||||
return coeffs;
|
||||
}
|
||||
} // namespace multiprecision_utils
|
||||
|
||||
TEST(Base58Test, multiprecision)
|
||||
{
|
||||
using namespace boost::multiprecision;
|
||||
|
||||
constexpr std::size_t kIters = 100000;
|
||||
auto eng = randEngine();
|
||||
std::uniform_int_distribution<std::uint64_t> dist;
|
||||
std::uniform_int_distribution<std::uint64_t> dist1(1);
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
if (d == 0u)
|
||||
continue;
|
||||
auto bigInt = multiprecision_utils::randomBigInt();
|
||||
auto const boostBigInt =
|
||||
multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refDiv = boostBigInt / d;
|
||||
auto const refMod = boostBigInt % d;
|
||||
|
||||
auto const mod = b58_fast::detail::inplaceBigintDivRem(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
auto const foundDiv = multiprecision_utils::toBoostMP(bigInt);
|
||||
EXPECT_EQ(refMod.convert_to<std::uint64_t>(), mod);
|
||||
EXPECT_EQ(foundDiv, refDiv);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2);
|
||||
if (bigInt[bigInt.size() - 1] == std::numeric_limits<std::uint64_t>::max())
|
||||
{
|
||||
bigInt[bigInt.size() - 1] -= 1; // Prevent overflow
|
||||
}
|
||||
auto const boostBigInt =
|
||||
multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refAdd = boostBigInt + d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintAdd(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
EXPECT_EQ(result, TokenCodecErrc::Success);
|
||||
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
|
||||
EXPECT_EQ(refAdd, foundAdd);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist1(eng);
|
||||
// Force overflow
|
||||
std::vector<std::uint64_t> bigInt(5, std::numeric_limits<std::uint64_t>::max());
|
||||
|
||||
auto const boostBigInt =
|
||||
multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refAdd = boostBigInt + d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintAdd(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
EXPECT_EQ(result, TokenCodecErrc::OverflowAdd);
|
||||
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
|
||||
EXPECT_NE(refAdd, foundAdd);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist(eng);
|
||||
auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2);
|
||||
// inplace mul requires the most significant coeff to be zero to
|
||||
// hold the result.
|
||||
bigInt[bigInt.size() - 1] = 0;
|
||||
auto const boostBigInt =
|
||||
multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refMul = boostBigInt * d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintMul(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
EXPECT_EQ(result, TokenCodecErrc::Success);
|
||||
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
|
||||
EXPECT_EQ(refMul, foundMul);
|
||||
}
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::uint64_t const d = dist1(eng);
|
||||
// Force overflow
|
||||
std::vector<std::uint64_t> bigInt(5, std::numeric_limits<std::uint64_t>::max());
|
||||
auto const boostBigInt =
|
||||
multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
|
||||
|
||||
auto const refMul = boostBigInt * d;
|
||||
|
||||
auto const result = b58_fast::detail::inplaceBigintMul(
|
||||
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
|
||||
EXPECT_EQ(result, TokenCodecErrc::InputTooLarge);
|
||||
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
|
||||
EXPECT_NE(refMul, foundMul);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Base58Test, fast_matches_ref)
|
||||
{
|
||||
auto testRawEncode = [&](std::span<std::uint8_t> const& b256Data) {
|
||||
std::array<std::uint8_t, 64> b58ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b58Result;
|
||||
|
||||
std::array<std::uint8_t, 64> b256ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b256Result;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b58ResultBuf[i]};
|
||||
if (i == 0)
|
||||
{
|
||||
auto const r = xrpl::b58_fast::detail::b256ToB58Be(b256Data, outBuf);
|
||||
EXPECT_TRUE(r);
|
||||
b58Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::array<std::uint8_t, 128> tmpBuf{};
|
||||
std::string const s = xrpl::b58_ref::detail::encodeBase58(
|
||||
b256Data.data(), b256Data.size(), tmpBuf.data(), tmpBuf.size());
|
||||
EXPECT_TRUE(s.size());
|
||||
b58Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b58Result[i].begin());
|
||||
}
|
||||
}
|
||||
auto const rawB58SameSize = b58Result[0].size() == b58Result[1].size();
|
||||
EXPECT_TRUE(rawB58SameSize);
|
||||
if (rawB58SameSize)
|
||||
{
|
||||
auto const rawB58SameData =
|
||||
memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0;
|
||||
EXPECT_TRUE(rawB58SameData);
|
||||
if (!rawB58SameData)
|
||||
{
|
||||
printAsChar(b58Result[0], b58Result[1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
std::string const in(
|
||||
b58Result[i].data(), b58Result[i].data() + b58Result[i].size());
|
||||
auto const r = xrpl::b58_fast::detail::b58ToB256Be(in, outBuf);
|
||||
EXPECT_TRUE(r);
|
||||
b256Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const st(b58Result[i].begin(), b58Result[i].end());
|
||||
std::string const s = xrpl::b58_ref::detail::decodeBase58(st);
|
||||
EXPECT_TRUE(s.size());
|
||||
b256Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b256Result[i].begin());
|
||||
}
|
||||
}
|
||||
|
||||
auto const rawB256SameSize = b256Result[0].size() == b256Result[1].size();
|
||||
EXPECT_TRUE(rawB256SameSize);
|
||||
if (rawB256SameSize)
|
||||
{
|
||||
auto const rawB256SameData =
|
||||
memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0;
|
||||
EXPECT_TRUE(rawB256SameData);
|
||||
if (!rawB256SameData)
|
||||
{
|
||||
printAsInt(b256Result[0], b256Result[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto testTokenEncode = [&](xrpl::TokenType const tokType,
|
||||
std::span<std::uint8_t> const& b256Data) {
|
||||
std::array<std::uint8_t, 64> b58ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b58Result;
|
||||
|
||||
std::array<std::uint8_t, 64> b256ResultBuf[2];
|
||||
std::array<std::span<std::uint8_t>, 2> b256Result;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b58ResultBuf[i].data(), b58ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
auto const r = xrpl::b58_fast::encodeBase58Token(tokType, b256Data, outBuf);
|
||||
EXPECT_TRUE(r);
|
||||
b58Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const s =
|
||||
xrpl::b58_ref::encodeBase58Token(tokType, b256Data.data(), b256Data.size());
|
||||
EXPECT_TRUE(s.size());
|
||||
b58Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b58Result[i].begin());
|
||||
}
|
||||
}
|
||||
auto const tokenB58SameSize = b58Result[0].size() == b58Result[1].size();
|
||||
EXPECT_TRUE(tokenB58SameSize);
|
||||
if (tokenB58SameSize)
|
||||
{
|
||||
auto const tokenB58SameData =
|
||||
memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0;
|
||||
EXPECT_TRUE(tokenB58SameData);
|
||||
if (!tokenB58SameData)
|
||||
{
|
||||
printAsChar(b58Result[0], b58Result[1]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()};
|
||||
if (i == 0)
|
||||
{
|
||||
std::string const in(
|
||||
b58Result[i].data(), b58Result[i].data() + b58Result[i].size());
|
||||
auto const r = xrpl::b58_fast::decodeBase58Token(tokType, in, outBuf);
|
||||
EXPECT_TRUE(r);
|
||||
b256Result[i] = r.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string const st(b58Result[i].begin(), b58Result[i].end());
|
||||
std::string const s = xrpl::b58_ref::decodeBase58Token(st, tokType);
|
||||
EXPECT_TRUE(s.size());
|
||||
b256Result[i] = outBuf.subspan(0, s.size());
|
||||
std::ranges::copy(s, b256Result[i].begin());
|
||||
}
|
||||
}
|
||||
|
||||
auto const tokenB256SameSize = b256Result[0].size() == b256Result[1].size();
|
||||
EXPECT_TRUE(tokenB256SameSize);
|
||||
if (tokenB256SameSize)
|
||||
{
|
||||
auto const tokenB256SameData =
|
||||
memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0;
|
||||
EXPECT_TRUE(tokenB256SameData);
|
||||
if (!tokenB256SameData)
|
||||
{
|
||||
printAsInt(b256Result[0], b256Result[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto testIt = [&](xrpl::TokenType const tokType, std::span<std::uint8_t> const& b256Data) {
|
||||
testRawEncode(b256Data);
|
||||
testTokenEncode(tokType, b256Data);
|
||||
};
|
||||
|
||||
// test every token type with data where every byte is the same and the
|
||||
// bytes range from 0-255
|
||||
for (int i = 0; i < kNumTokenTypeIndexes; ++i)
|
||||
{
|
||||
std::array<std::uint8_t, 128> b256DataBuf{};
|
||||
auto const [tokType, tokSize] = tokenTypeAndSize(i);
|
||||
for (int d = 0; d <= 255; ++d)
|
||||
{
|
||||
memset(b256DataBuf.data(), d, tokSize);
|
||||
testIt(tokType, std::span(b256DataBuf.data(), tokSize));
|
||||
}
|
||||
}
|
||||
|
||||
// test with random data
|
||||
constexpr std::size_t kIters = 100000;
|
||||
for (int i = 0; i < kIters; ++i)
|
||||
{
|
||||
std::array<std::uint8_t, 128> b256DataBuf{};
|
||||
auto const [tokType, b256Data] = randomB256TestData(b256DataBuf);
|
||||
testIt(tokType, b256Data);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
|
||||
#endif // _MSC_VER
|
||||
360
src/tests/libxrpl/basics/base_uint_test.cpp
Normal file
360
src/tests/libxrpl/basics/base_uint_test.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/hardened_hash.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
|
||||
#include <boost/endian/detail/order.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <complex>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// a non-hashing Hasher that just copies the bytes.
|
||||
// Used to test hash_append in base_uint
|
||||
template <std::size_t Bits>
|
||||
struct Nonhash
|
||||
{
|
||||
static constexpr auto const kEndian = boost::endian::order::big;
|
||||
static constexpr std::size_t kWidth = Bits / 8;
|
||||
|
||||
std::array<std::uint8_t, kWidth> data;
|
||||
|
||||
Nonhash() = default;
|
||||
|
||||
void
|
||||
operator()(void const* key, std::size_t len) noexcept
|
||||
{
|
||||
assert(len == kWidth);
|
||||
memcpy(data.data(), key, len);
|
||||
}
|
||||
|
||||
explicit
|
||||
operator std::size_t() noexcept
|
||||
{
|
||||
return kWidth;
|
||||
}
|
||||
};
|
||||
|
||||
struct BaseUintTest : public ::testing::Test
|
||||
{
|
||||
using BaseUInt96 = BaseUInt<96>;
|
||||
static_assert(std::is_copy_constructible_v<BaseUInt96>);
|
||||
static_assert(std::is_copy_assignable_v<BaseUInt96>);
|
||||
|
||||
static void
|
||||
testComparisons()
|
||||
{
|
||||
{
|
||||
static constexpr std::array<std::pair<std::string_view, std::string_view>, 6> kTestArgs{
|
||||
{{"0000000000000000", "0000000000000001"},
|
||||
{"0000000000000000", "ffffffffffffffff"},
|
||||
{"1234567812345678", "2345678923456789"},
|
||||
{"8000000000000000", "8000000000000001"},
|
||||
{"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"},
|
||||
{"fffffffffffffffe", "ffffffffffffffff"}}};
|
||||
|
||||
for (auto const& arg : kTestArgs)
|
||||
{
|
||||
xrpl::BaseUInt<64> const u{arg.first}, v{arg.second};
|
||||
// For code readability, we want to use general boolean
|
||||
// expectations instead of specific EXPECT_LT etc.
|
||||
EXPECT_TRUE(u < v);
|
||||
EXPECT_TRUE(u <= v);
|
||||
EXPECT_TRUE(u != v);
|
||||
EXPECT_FALSE(u == v);
|
||||
EXPECT_FALSE(u > v);
|
||||
EXPECT_FALSE(u >= v);
|
||||
EXPECT_FALSE(v < u);
|
||||
EXPECT_FALSE(v <= u);
|
||||
EXPECT_TRUE(v != u);
|
||||
EXPECT_FALSE(v == u);
|
||||
EXPECT_TRUE(v > u);
|
||||
EXPECT_TRUE(v >= u);
|
||||
EXPECT_TRUE(u == u);
|
||||
EXPECT_TRUE(v == v);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
static constexpr std::array<std::pair<std::string_view, std::string_view>, 6> kTestArgs{
|
||||
{
|
||||
{"000000000000000000000000", "000000000000000000000001"},
|
||||
{"000000000000000000000000", "ffffffffffffffffffffffff"},
|
||||
{"0123456789ab0123456789ab", "123456789abc123456789abc"},
|
||||
{"555555555555555555555555", "55555555555a555555555555"},
|
||||
{"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||
{"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"},
|
||||
}};
|
||||
|
||||
for (auto const& arg : kTestArgs)
|
||||
{
|
||||
xrpl::BaseUInt<96> const u{arg.first}, v{arg.second};
|
||||
EXPECT_TRUE(u < v);
|
||||
EXPECT_TRUE(u <= v);
|
||||
EXPECT_TRUE(u != v);
|
||||
EXPECT_FALSE(u == v);
|
||||
EXPECT_FALSE(u > v);
|
||||
EXPECT_FALSE(u >= v);
|
||||
EXPECT_FALSE(v < u);
|
||||
EXPECT_FALSE(v <= u);
|
||||
EXPECT_TRUE(v != u);
|
||||
EXPECT_FALSE(v == u);
|
||||
EXPECT_TRUE(v > u);
|
||||
EXPECT_TRUE(v >= u);
|
||||
EXPECT_TRUE(u == u);
|
||||
EXPECT_TRUE(v == v);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BaseUintTest, base_uint)
|
||||
{
|
||||
static_assert(!std::is_constructible_v<BaseUInt96, std::complex<double>>);
|
||||
static_assert(!std::is_assignable_v<BaseUInt96&, std::complex<double>>);
|
||||
|
||||
testComparisons();
|
||||
|
||||
// used to verify set insertion (hashing required)
|
||||
std::unordered_set<BaseUInt96, HardenedHash<>> uset;
|
||||
|
||||
Blob const raw{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||
EXPECT_EQ(BaseUInt96::kBytes, raw.size());
|
||||
|
||||
BaseUInt96 u = BaseUInt96::fromRaw(raw);
|
||||
uset.insert(u);
|
||||
EXPECT_EQ(raw.size(), u.size());
|
||||
EXPECT_EQ(to_string(u), "0102030405060708090A0B0C");
|
||||
EXPECT_EQ(toShortString(u), "01020304...");
|
||||
EXPECT_EQ(*u.data(), 1);
|
||||
EXPECT_EQ(u.signum(), 1);
|
||||
EXPECT_FALSE(!u);
|
||||
EXPECT_FALSE(u.isZero());
|
||||
EXPECT_TRUE(u.isNonZero());
|
||||
unsigned char t = 0;
|
||||
for (auto& d : u)
|
||||
{
|
||||
EXPECT_EQ(d, ++t);
|
||||
}
|
||||
|
||||
// Test hash_append by "hashing" with a no-op hasher (h)
|
||||
// and then extracting the bytes that were written during hashing
|
||||
// back into another base_uint (w) for comparison with the original
|
||||
Nonhash<96> h{};
|
||||
hash_append(h, u);
|
||||
BaseUInt96 const w =
|
||||
BaseUInt96::fromRaw(std::vector<std::uint8_t>(h.data.begin(), h.data.end()));
|
||||
EXPECT_EQ(w, u);
|
||||
|
||||
BaseUInt96 v{~u};
|
||||
uset.insert(v);
|
||||
EXPECT_EQ(to_string(v), "FEFDFCFBFAF9F8F7F6F5F4F3");
|
||||
EXPECT_EQ(toShortString(v), "FEFDFCFB...");
|
||||
EXPECT_EQ(*v.data(), 0xfe);
|
||||
EXPECT_EQ(v.signum(), 1);
|
||||
EXPECT_FALSE(!v);
|
||||
EXPECT_FALSE(v.isZero());
|
||||
EXPECT_TRUE(v.isNonZero());
|
||||
|
||||
t = 0xff;
|
||||
for (auto& d : v)
|
||||
{
|
||||
EXPECT_EQ(d, --t);
|
||||
}
|
||||
|
||||
EXPECT_LT(u, v);
|
||||
EXPECT_GT(v, u);
|
||||
|
||||
v = u;
|
||||
EXPECT_EQ(v, u);
|
||||
|
||||
BaseUInt96 z{beast::kZero};
|
||||
uset.insert(z);
|
||||
EXPECT_EQ(to_string(z), "000000000000000000000000");
|
||||
EXPECT_EQ(toShortString(z), "00000000...");
|
||||
EXPECT_EQ(*z.data(), 0);
|
||||
EXPECT_EQ(*z.begin(), 0);
|
||||
EXPECT_EQ(*std::prev(z.end(), 1), 0);
|
||||
EXPECT_EQ(z.signum(), 0);
|
||||
EXPECT_TRUE(!z);
|
||||
EXPECT_TRUE(z.isZero());
|
||||
EXPECT_FALSE(z.isNonZero());
|
||||
for (auto& d : z)
|
||||
{
|
||||
EXPECT_EQ(d, 0);
|
||||
}
|
||||
|
||||
BaseUInt96 n{z};
|
||||
n++;
|
||||
EXPECT_EQ(n, BaseUInt96(1));
|
||||
n--;
|
||||
EXPECT_EQ(n, beast::kZero);
|
||||
EXPECT_EQ(n, z);
|
||||
n--;
|
||||
EXPECT_EQ(to_string(n), "FFFFFFFFFFFFFFFFFFFFFFFF");
|
||||
EXPECT_EQ(toShortString(n), "FFFFFFFF...");
|
||||
n = beast::kZero;
|
||||
EXPECT_EQ(n, z);
|
||||
|
||||
BaseUInt96 zp1{z};
|
||||
zp1++;
|
||||
BaseUInt96 zm1{z};
|
||||
zm1--;
|
||||
BaseUInt96 const x{zm1 ^ zp1};
|
||||
uset.insert(x);
|
||||
EXPECT_EQ(to_string(x), "FFFFFFFFFFFFFFFFFFFFFFFE") << to_string(x);
|
||||
EXPECT_EQ(toShortString(x), "FFFFFFFF...") << toShortString(x);
|
||||
|
||||
EXPECT_EQ(uset.size(), 4);
|
||||
|
||||
BaseUInt96 tmp;
|
||||
EXPECT_TRUE(tmp.parseHex(to_string(u)));
|
||||
EXPECT_EQ(tmp, u);
|
||||
tmp = z;
|
||||
|
||||
// fails with extra char
|
||||
EXPECT_FALSE(tmp.parseHex("A" + to_string(u)));
|
||||
tmp = z;
|
||||
|
||||
// fails with extra char at end
|
||||
EXPECT_FALSE(tmp.parseHex(to_string(u) + "A"));
|
||||
|
||||
// fails with a non-hex character at some point in the string:
|
||||
tmp = z;
|
||||
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string x = to_string(z);
|
||||
x[i] = ('G' + (i % 10));
|
||||
EXPECT_FALSE(tmp.parseHex(x));
|
||||
}
|
||||
|
||||
// Walking 1s:
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string s1 = "000000000000000000000000";
|
||||
s1[i] = '1';
|
||||
|
||||
EXPECT_TRUE(tmp.parseHex(s1));
|
||||
EXPECT_EQ(to_string(tmp), s1);
|
||||
}
|
||||
|
||||
// Walking 0s:
|
||||
for (std::size_t i = 0; i != 24; ++i)
|
||||
{
|
||||
std::string s1 = "111111111111111111111111";
|
||||
s1[i] = '0';
|
||||
|
||||
EXPECT_TRUE(tmp.parseHex(s1));
|
||||
EXPECT_EQ(to_string(tmp), s1);
|
||||
}
|
||||
|
||||
// Constexpr constructors
|
||||
{
|
||||
static_assert(BaseUInt96{}.signum() == 0);
|
||||
static_assert(BaseUInt96("0").signum() == 0);
|
||||
static_assert(BaseUInt96("000000000000000000000000").signum() == 0);
|
||||
static_assert(BaseUInt96("000000000000000000000001").signum() == 1);
|
||||
static_assert(BaseUInt96("800000000000000000000000").signum() == 1);
|
||||
|
||||
// Everything within the #if should fail during compilation.
|
||||
#if 0
|
||||
// Too few characters
|
||||
static_assert(BaseUInt96("00000000000000000000000").signum() == 0);
|
||||
|
||||
// Too many characters
|
||||
static_assert(BaseUInt96("0000000000000000000000000").signum() == 0);
|
||||
|
||||
// Non-hex characters
|
||||
static_assert(BaseUInt96("00000000000000000000000 ").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000/").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000:").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000@").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000G").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000`").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000g").signum() == 1);
|
||||
static_assert(BaseUInt96("00000000000000000000000~").signum() == 1);
|
||||
#endif // 0
|
||||
|
||||
// Using the constexpr constructor in a non-constexpr context
|
||||
// with an error in the parsing throws an exception.
|
||||
{
|
||||
// Invalid length for string.
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
// Try to prevent constant evaluation.
|
||||
std::vector<char> str(23, '7');
|
||||
std::string_view const sView(str.data(), str.size());
|
||||
[[maybe_unused]] BaseUInt96 const t96(sView);
|
||||
}
|
||||
catch (std::invalid_argument const& e)
|
||||
{
|
||||
EXPECT_EQ(e.what(), std::string("invalid length for hex string"));
|
||||
caught = true;
|
||||
}
|
||||
EXPECT_TRUE(caught);
|
||||
}
|
||||
{
|
||||
// Invalid character in string.
|
||||
bool caught = false;
|
||||
try
|
||||
{
|
||||
// Try to prevent constant evaluation.
|
||||
std::vector<char> str(23, '7');
|
||||
str.push_back('G');
|
||||
std::string_view const sView(str.data(), str.size());
|
||||
[[maybe_unused]] BaseUInt96 const t96(sView);
|
||||
}
|
||||
catch (std::range_error const& e)
|
||||
{
|
||||
EXPECT_EQ(e.what(), std::string("invalid hex character"));
|
||||
caught = true;
|
||||
}
|
||||
EXPECT_TRUE(caught);
|
||||
}
|
||||
|
||||
// Verify that constexpr base_uints interpret a string the same
|
||||
// way parseHex() does.
|
||||
struct StrBaseUInt
|
||||
{
|
||||
char const* const str;
|
||||
BaseUInt96 tst;
|
||||
|
||||
constexpr StrBaseUInt(char const* s) : str(s), tst(s)
|
||||
{
|
||||
}
|
||||
};
|
||||
constexpr StrBaseUInt kTestCases[] = {
|
||||
"000000000000000000000000",
|
||||
"000000000000000000000001",
|
||||
"fedcba9876543210ABCDEF91",
|
||||
"19FEDCBA0123456789abcdef",
|
||||
"800000000000000000000000",
|
||||
"fFfFfFfFfFfFfFfFfFfFfFfF",
|
||||
};
|
||||
|
||||
for (StrBaseUInt const& t : kTestCases)
|
||||
{
|
||||
BaseUInt96 t96;
|
||||
EXPECT_TRUE(t96.parseHex(t.str));
|
||||
EXPECT_EQ(t96, t.tst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <xrpl/basics/hardened_hash.h>
|
||||
|
||||
#include <xrpl/beast/hash/hash_append.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
@@ -153,20 +155,20 @@ static_assert(sha256_t::kBits == 256, "sha256_t must have 256 bits");
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class hardened_hash_test : public beast::unit_test::Suite
|
||||
class HardenedHashTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
template <class T>
|
||||
void
|
||||
static void
|
||||
check()
|
||||
{
|
||||
T t{};
|
||||
HardenedHash<>()(t);
|
||||
pass();
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
template <template <class T> class U>
|
||||
void
|
||||
static void
|
||||
checkUserType()
|
||||
{
|
||||
check<U<bool>>();
|
||||
@@ -191,48 +193,35 @@ public:
|
||||
}
|
||||
|
||||
template <template <class T> class C>
|
||||
void
|
||||
static void
|
||||
checkContainer()
|
||||
{
|
||||
{
|
||||
C<detail::TestUserTypeMember<std::string>> const c;
|
||||
}
|
||||
|
||||
pass();
|
||||
SUCCEED();
|
||||
|
||||
{
|
||||
C<detail::TestUserTypeFree<std::string>> const c;
|
||||
}
|
||||
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
testUserTypes()
|
||||
{
|
||||
testcase("user types");
|
||||
checkUserType<detail::TestUserTypeMember>();
|
||||
checkUserType<detail::TestUserTypeFree>();
|
||||
}
|
||||
|
||||
void
|
||||
testContainers()
|
||||
{
|
||||
testcase("containers");
|
||||
checkContainer<detail::test_hardened_unordered_set>();
|
||||
checkContainer<detail::test_hardened_unordered_map>();
|
||||
checkContainer<detail::test_hardened_unordered_multiset>();
|
||||
checkContainer<detail::test_hardened_unordered_multimap>();
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testUserTypes();
|
||||
testContainers();
|
||||
SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(hardened_hash, basics, xrpl);
|
||||
TEST_F(HardenedHashTest, user_types)
|
||||
{
|
||||
checkUserType<detail::TestUserTypeMember>();
|
||||
checkUserType<detail::TestUserTypeFree>();
|
||||
}
|
||||
|
||||
TEST_F(HardenedHashTest, containers)
|
||||
{
|
||||
checkContainer<detail::test_hardened_unordered_set>();
|
||||
checkContainer<detail::test_hardened_unordered_map>();
|
||||
checkContainer<detail::test_hardened_unordered_multiset>();
|
||||
checkContainer<detail::test_hardened_unordered_multimap>();
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
80
src/tests/libxrpl/basics/join.cpp
Normal file
80
src/tests/libxrpl/basics/join.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <xrpl/basics/join.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
struct JoinTest : public ::testing::Test
|
||||
{
|
||||
};
|
||||
|
||||
TEST_F(JoinTest, join)
|
||||
{
|
||||
auto test = [](auto collectionanddelimiter, std::string expected) {
|
||||
std::stringstream ss;
|
||||
// Put something else in the buffer before and after to ensure that
|
||||
// the << operator returns the stream correctly.
|
||||
ss << "(" << collectionanddelimiter << ")";
|
||||
auto const str = ss.str();
|
||||
EXPECT_EQ(str.substr(1, str.length() - 2), expected);
|
||||
EXPECT_EQ(str.front(), '(');
|
||||
EXPECT_EQ(str.back(), ')');
|
||||
};
|
||||
|
||||
// C++ array
|
||||
test(CollectionAndDelimiter(std::array<int, 4>{2, -1, 5, 10}, "/"), "2/-1/5/10");
|
||||
// One item C++ array edge case
|
||||
test(CollectionAndDelimiter(std::array<std::string, 1>{"test"}, " & "), "test");
|
||||
// Empty C++ array edge case
|
||||
test(CollectionAndDelimiter(std::array<int, 0>{}, ","), "");
|
||||
{
|
||||
// C-style array
|
||||
char letters[4]{'w', 'a', 's', 'd'};
|
||||
test(CollectionAndDelimiter(letters, std::to_string(0)), "w0a0s0d");
|
||||
}
|
||||
{
|
||||
// Auto sized C-style array
|
||||
std::string words[]{"one", "two", "three", "four"};
|
||||
test(CollectionAndDelimiter(words, "\n"), "one\ntwo\nthree\nfour");
|
||||
}
|
||||
{
|
||||
// One item C-style array edge case
|
||||
std::string words[]{"thing"};
|
||||
test(CollectionAndDelimiter(words, "\n"), "thing");
|
||||
}
|
||||
// Initializer list
|
||||
test(CollectionAndDelimiter(std::initializer_list<size_t>{19, 25}, "+"), "19+25");
|
||||
// vector
|
||||
test(CollectionAndDelimiter(std::vector<int>{0, 42}, std::to_string(99)), "09942");
|
||||
// vector with one item edge case
|
||||
test(CollectionAndDelimiter(std::vector<std::string>{"master"}, "xxx"), "master");
|
||||
// vector with one non-trivial streamable item edge case
|
||||
test(
|
||||
CollectionAndDelimiter(std::vector<uint256>{uint256{1}}, "xxx"),
|
||||
"0000000000000000000000000000000000000000000000000000000000000001");
|
||||
// empty vector edge case
|
||||
test(CollectionAndDelimiter(std::vector<uint256>{}, ","), "");
|
||||
// C-style string
|
||||
test(CollectionAndDelimiter("string", " "), "s t r i n g");
|
||||
// Empty C-style string edge case
|
||||
test(CollectionAndDelimiter("", "*"), "");
|
||||
// Single char C-style string edge case
|
||||
test(CollectionAndDelimiter("x", "*"), "x");
|
||||
// std::string
|
||||
test(CollectionAndDelimiter(std::string{"string"}, "-"), "s-t-r-i-n-g");
|
||||
// Empty std::string edge case
|
||||
test(CollectionAndDelimiter(std::string{""}, "*"), "");
|
||||
// Single char std::string edge case
|
||||
test(CollectionAndDelimiter(std::string{"y"}, "*"), "y");
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
241
src/tests/libxrpl/resource/Logic.cpp
Normal file
241
src/tests/libxrpl/resource/Logic.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
#include <xrpl/resource/detail/Logic.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/insight/NullCollector.h>
|
||||
#include <xrpl/beast/net/IPAddressV4.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/resource/Disposition.h>
|
||||
#include <xrpl/resource/Gossip.h>
|
||||
#include <xrpl/resource/detail/Tuning.h>
|
||||
|
||||
#include <boost/utility/base_from_member.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <helpers/TestSink.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::Resource {
|
||||
|
||||
class ResourceManagerTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
beast::Journal const j_{TestSink::instance()};
|
||||
|
||||
class TestLogic : private boost::base_from_member<TestStopwatch>, public Logic
|
||||
{
|
||||
private:
|
||||
using clock_type = boost::base_from_member<TestStopwatch>;
|
||||
|
||||
public:
|
||||
explicit TestLogic(beast::Journal journal)
|
||||
: Logic(beast::insight::NullCollector::make(), member, journal)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
advance()
|
||||
{
|
||||
++member;
|
||||
}
|
||||
|
||||
TestStopwatch&
|
||||
clock()
|
||||
{
|
||||
return member;
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
populateGossip(Gossip& gossip)
|
||||
{
|
||||
std::uint8_t const v(10 + randInt(9));
|
||||
std::uint8_t const n(10 + randInt(9));
|
||||
gossip.items.reserve(n);
|
||||
for (std::uint8_t i = 0; i < n; ++i)
|
||||
{
|
||||
Gossip::Item item;
|
||||
item.balance = 100 + randInt(499);
|
||||
beast::IP::AddressV4::bytes_type const d = {{
|
||||
192,
|
||||
0,
|
||||
2,
|
||||
static_cast<std::uint8_t>(v + i),
|
||||
}};
|
||||
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
|
||||
gossip.items.push_back(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ResourceManagerTest, limited_warn_drop)
|
||||
{
|
||||
TestLogic logic{j_};
|
||||
|
||||
Charge const fee{kDropThreshold + 1};
|
||||
beast::IP::Endpoint const addr{beast::IP::Endpoint::fromString("192.0.2.2")};
|
||||
|
||||
{
|
||||
Consumer c{logic.newInboundEndpoint(addr)};
|
||||
|
||||
// Create load until we get a warning
|
||||
int n = 10000;
|
||||
bool warned = false;
|
||||
|
||||
while (--n >= 0)
|
||||
{
|
||||
if (c.charge(fee) == Disposition::Warn)
|
||||
{
|
||||
warned = true;
|
||||
break;
|
||||
}
|
||||
++logic.clock();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(warned) << "Loop count exceeded without warning";
|
||||
|
||||
// Create load until we get dropped
|
||||
bool dropped = false;
|
||||
while (--n >= 0)
|
||||
{
|
||||
if (c.charge(fee) == Disposition::Drop)
|
||||
{
|
||||
dropped = true;
|
||||
// Disconnect abusive Consumer
|
||||
EXPECT_TRUE(c.disconnect(j_));
|
||||
break;
|
||||
}
|
||||
++logic.clock();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(dropped) << "Loop count exceeded without dropping";
|
||||
}
|
||||
|
||||
// Make sure the consumer is on the blacklist for a while.
|
||||
{
|
||||
Consumer const c{logic.newInboundEndpoint(addr)};
|
||||
logic.periodicActivity();
|
||||
EXPECT_EQ(c.disposition(), Disposition::Drop) << "Dropped consumer not put on blacklist";
|
||||
}
|
||||
|
||||
// Makes sure the Consumer is eventually removed from blacklist
|
||||
bool readmitted = false;
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
// Give Consumer time to become readmitted. Should never
|
||||
// exceed expiration time.
|
||||
auto n = kSecondsUntilExpiration + 1s;
|
||||
while (--n > 0s)
|
||||
{
|
||||
++logic.clock();
|
||||
logic.periodicActivity();
|
||||
Consumer const c{logic.newInboundEndpoint(addr)};
|
||||
if (c.disposition() != Disposition::Drop)
|
||||
{
|
||||
readmitted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(readmitted) << "Dropped Consumer left on blacklist too long";
|
||||
}
|
||||
|
||||
TEST_F(ResourceManagerTest, unlimited_warn_drop)
|
||||
{
|
||||
TestLogic logic{j_};
|
||||
|
||||
Charge const fee{kDropThreshold + 1};
|
||||
beast::IP::Endpoint const addr{beast::IP::Endpoint::fromString("192.0.2.2")};
|
||||
Consumer c{logic.newUnlimitedEndpoint(addr)};
|
||||
|
||||
// Create load until we get a warning
|
||||
int n = 10000;
|
||||
bool warned = false;
|
||||
|
||||
while (--n >= 0)
|
||||
{
|
||||
if (c.charge(fee) == Disposition::Warn)
|
||||
{
|
||||
warned = true;
|
||||
break;
|
||||
}
|
||||
++logic.clock();
|
||||
}
|
||||
|
||||
EXPECT_FALSE(warned) << "Should loop forever with no warning";
|
||||
}
|
||||
|
||||
TEST_F(ResourceManagerTest, charges)
|
||||
{
|
||||
TestLogic logic{j_};
|
||||
|
||||
{
|
||||
beast::IP::Endpoint const address{beast::IP::Endpoint::fromString("192.0.2.1")};
|
||||
Consumer c{logic.newInboundEndpoint(address)};
|
||||
Charge const fee{1000};
|
||||
JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second";
|
||||
c.charge(fee);
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
JLOG(j_.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
|
||||
<< ", Balance = " << c.balance();
|
||||
logic.advance();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
beast::IP::Endpoint const address{beast::IP::Endpoint::fromString("192.0.2.2")};
|
||||
Consumer c{logic.newInboundEndpoint(address)};
|
||||
Charge const fee{1000};
|
||||
JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second";
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
c.charge(fee);
|
||||
JLOG(j_.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
|
||||
<< ", Balance = " << c.balance();
|
||||
logic.advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ResourceManagerTest, imports)
|
||||
{
|
||||
TestLogic logic{j_};
|
||||
|
||||
Gossip g[5];
|
||||
|
||||
for (auto& i : g)
|
||||
populateGossip(i);
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
logic.importConsumers(std::to_string(i), g[i]);
|
||||
}
|
||||
|
||||
TEST_F(ResourceManagerTest, import)
|
||||
{
|
||||
TestLogic logic{j_};
|
||||
|
||||
Gossip g;
|
||||
Gossip::Item item;
|
||||
item.balance = 100;
|
||||
beast::IP::AddressV4::bytes_type const d = {{
|
||||
192,
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
}};
|
||||
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
|
||||
g.items.push_back(item);
|
||||
|
||||
logic.importConsumers("g", g);
|
||||
}
|
||||
|
||||
} // namespace xrpl::Resource
|
||||
22
src/tests/libxrpl/shamap/FetchPack.cpp
Normal file
22
src/tests/libxrpl/shamap/FetchPack.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <helpers/TestSink.h>
|
||||
#include <shamap/common.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
TEST(FetchPackTest, construct_table)
|
||||
{
|
||||
beast::Journal const j{TestSink::instance()};
|
||||
TestNodeFamily f{j};
|
||||
std::shared_ptr<SHAMap> const t1{std::make_shared<SHAMap>(SHAMapType::FREE, f)};
|
||||
|
||||
EXPECT_NE(t1, nullptr);
|
||||
}
|
||||
|
||||
} // namespace xrpl::tests
|
||||
349
src/tests/libxrpl/shamap/SHAMap.cpp
Normal file
349
src/tests/libxrpl/shamap/SHAMap.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/shamap/SHAMapInnerNode.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
#include <xrpl/shamap/SHAMapLeafNode.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <helpers/TestSink.h>
|
||||
#include <shamap/common.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
#ifndef __INTELLISENSE__
|
||||
static_assert(std::is_nothrow_destructible<SHAMap>{});
|
||||
static_assert(!std::is_default_constructible<SHAMap>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMap>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMap>{});
|
||||
static_assert(!std::is_move_constructible<SHAMap>{});
|
||||
static_assert(!std::is_move_assignable<SHAMap>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_copy_constructible<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_copy_assignable<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_move_constructible<SHAMap::ConstIterator>{});
|
||||
static_assert(std::is_move_assignable<SHAMap::ConstIterator>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapItem>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapItem>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapItem>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_default_constructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_copy_constructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_copy_assignable<SHAMapNodeID>{});
|
||||
static_assert(std::is_move_constructible<SHAMapNodeID>{});
|
||||
static_assert(std::is_move_assignable<SHAMapNodeID>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapHash>{});
|
||||
static_assert(std::is_default_constructible<SHAMapHash>{});
|
||||
static_assert(std::is_copy_constructible<SHAMapHash>{});
|
||||
static_assert(std::is_copy_assignable<SHAMapHash>{});
|
||||
static_assert(std::is_move_constructible<SHAMapHash>{});
|
||||
static_assert(std::is_move_assignable<SHAMapHash>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_move_constructible<SHAMapTreeNode>{});
|
||||
static_assert(!std::is_move_assignable<SHAMapTreeNode>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_move_constructible<SHAMapInnerNode>{});
|
||||
static_assert(!std::is_move_assignable<SHAMapInnerNode>{});
|
||||
|
||||
static_assert(std::is_nothrow_destructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_default_constructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_copy_constructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_copy_assignable<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_move_constructible<SHAMapLeafNode>{});
|
||||
static_assert(!std::is_move_assignable<SHAMapLeafNode>{});
|
||||
#endif
|
||||
|
||||
inline bool
|
||||
operator!=(SHAMapItem const& a, SHAMapItem const& b)
|
||||
{
|
||||
return a.key() != b.key();
|
||||
}
|
||||
|
||||
struct SHAMapBackingMode
|
||||
{
|
||||
bool backed;
|
||||
std::string_view testName;
|
||||
};
|
||||
|
||||
constexpr SHAMapBackingMode kBackedMode{.backed = true, .testName = "backed"};
|
||||
constexpr SHAMapBackingMode kUnbackedMode{.backed = false, .testName = "unbacked"};
|
||||
|
||||
std::string
|
||||
shamapBackingModeName(::testing::TestParamInfo<SHAMapBackingMode> const& info)
|
||||
{
|
||||
return std::string{info.param.testName};
|
||||
}
|
||||
|
||||
class SHAMapTest : public ::testing::TestWithParam<SHAMapBackingMode>
|
||||
{
|
||||
protected:
|
||||
beast::Journal const j_{TestSink::instance()};
|
||||
|
||||
static Buffer
|
||||
intToVuc(std::uint8_t v)
|
||||
{
|
||||
Buffer vuc{32};
|
||||
std::fill_n(vuc.data(), vuc.size(), v);
|
||||
return vuc;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(SHAMapTest, add_traverse_snapshot_build_tear_and_iterate)
|
||||
{
|
||||
auto const testMode = GetParam();
|
||||
tests::TestNodeFamily f{j_};
|
||||
|
||||
// kH3 and kH4 differ only in the leaf, same terminal node (level 19)
|
||||
constexpr uint256 kH1("092891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
|
||||
constexpr uint256 kH2("436ccbac3347baa1f1e53baeef1f43334da88f1f6d70d963b833afd6dfa289fe");
|
||||
constexpr uint256 kH3("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8");
|
||||
constexpr uint256 kH4("b92891fe4ef6cee585fdc6fda2e09eb4d386363158ec3321b8123e5a772c6ca8");
|
||||
|
||||
SHAMap sMap{SHAMapType::FREE, f};
|
||||
sMap.invariants();
|
||||
if (!testMode.backed)
|
||||
sMap.setUnbacked();
|
||||
|
||||
auto i1 = makeShamapitem(kH1, intToVuc(1));
|
||||
auto i2 = makeShamapitem(kH2, intToVuc(2));
|
||||
auto i3 = makeShamapitem(kH3, intToVuc(3));
|
||||
auto i4 = makeShamapitem(kH4, intToVuc(4));
|
||||
|
||||
EXPECT_TRUE(sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i2))) << "no add";
|
||||
sMap.invariants();
|
||||
EXPECT_TRUE(sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i1))) << "no add";
|
||||
sMap.invariants();
|
||||
|
||||
auto i = sMap.begin();
|
||||
auto e = sMap.end();
|
||||
EXPECT_FALSE(i == e || (*i != *i1)) << "bad traverse";
|
||||
++i;
|
||||
EXPECT_FALSE(i == e || (*i != *i2)) << "bad traverse";
|
||||
++i;
|
||||
EXPECT_EQ(i, e) << "bad traverse";
|
||||
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i4));
|
||||
sMap.invariants();
|
||||
sMap.delItem(i2->key());
|
||||
sMap.invariants();
|
||||
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i3));
|
||||
sMap.invariants();
|
||||
i = sMap.begin();
|
||||
e = sMap.end();
|
||||
EXPECT_FALSE(i == e || (*i != *i1)) << "bad traverse";
|
||||
++i;
|
||||
EXPECT_FALSE(i == e || (*i != *i3)) << "bad traverse";
|
||||
++i;
|
||||
EXPECT_FALSE(i == e || (*i != *i4)) << "bad traverse";
|
||||
++i;
|
||||
EXPECT_EQ(i, e) << "bad traverse";
|
||||
|
||||
SHAMapHash const mapHash = sMap.getHash();
|
||||
std::shared_ptr<SHAMap> const map2 = sMap.snapShot(false);
|
||||
map2->invariants();
|
||||
EXPECT_EQ(sMap.getHash(), mapHash) << "bad snapshot";
|
||||
EXPECT_EQ(map2->getHash(), mapHash) << "bad snapshot";
|
||||
|
||||
SHAMap::Delta delta;
|
||||
ASSERT_TRUE(sMap.compare(*map2, delta, 100));
|
||||
EXPECT_TRUE(delta.empty());
|
||||
|
||||
EXPECT_TRUE(sMap.delItem(sMap.begin()->key())) << "bad mod";
|
||||
sMap.invariants();
|
||||
EXPECT_NE(sMap.getHash(), mapHash) << "bad snapshot";
|
||||
EXPECT_EQ(map2->getHash(), mapHash) << "bad snapshot";
|
||||
|
||||
ASSERT_TRUE(sMap.compare(*map2, delta, 100));
|
||||
ASSERT_EQ(delta.size(), 1);
|
||||
EXPECT_EQ(delta.begin()->first, kH1);
|
||||
EXPECT_EQ(delta.begin()->second.first, nullptr);
|
||||
ASSERT_NE(delta.begin()->second.second, nullptr);
|
||||
EXPECT_EQ(delta.begin()->second.second->key(), kH1);
|
||||
|
||||
sMap.dump();
|
||||
{
|
||||
constexpr std::array kKeys{
|
||||
uint256{"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
};
|
||||
|
||||
constexpr std::array kHashes{
|
||||
uint256{"B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C64B6281F7F"},
|
||||
uint256{"FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A8831845467FB2ECE266"},
|
||||
uint256{"4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51AE756795B75"},
|
||||
uint256{"7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC6C74F93E07"},
|
||||
uint256{"395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596462B0E3A3E"},
|
||||
uint256{"D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D98A84D9DDE4"},
|
||||
uint256{"76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF94361143615"},
|
||||
uint256{"DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA02BBE7230E5"},
|
||||
};
|
||||
|
||||
SHAMap map{SHAMapType::FREE, f};
|
||||
if (!testMode.backed)
|
||||
map.setUnbacked();
|
||||
|
||||
EXPECT_EQ(map.getHash(), beast::kZero);
|
||||
for (std::size_t k = 0; k < kKeys.size(); ++k)
|
||||
{
|
||||
EXPECT_TRUE(map.addItem(
|
||||
SHAMapNodeType::TnTransactionNm,
|
||||
makeShamapitem(kKeys[k], intToVuc(static_cast<std::uint8_t>(k)))));
|
||||
EXPECT_EQ(map.getHash().asUInt256(), kHashes[k]);
|
||||
map.invariants();
|
||||
}
|
||||
for (std::size_t k = kKeys.size(); k-- > 0;)
|
||||
{
|
||||
EXPECT_EQ(map.getHash().asUInt256(), kHashes[k]);
|
||||
EXPECT_TRUE(map.delItem(kKeys[k]));
|
||||
map.invariants();
|
||||
}
|
||||
EXPECT_EQ(map.getHash(), beast::kZero);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr std::array kKeys{
|
||||
uint256{"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
uint256{"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
|
||||
};
|
||||
|
||||
tests::TestNodeFamily tf{j_};
|
||||
SHAMap map{SHAMapType::FREE, tf};
|
||||
if (!testMode.backed)
|
||||
map.setUnbacked();
|
||||
for (auto const& k : kKeys)
|
||||
{
|
||||
map.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(k, intToVuc(0)));
|
||||
map.invariants();
|
||||
}
|
||||
|
||||
int h = 7;
|
||||
for (auto const& k : map)
|
||||
{
|
||||
EXPECT_EQ(k.key(), kKeys[h]);
|
||||
--h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
BackingMode,
|
||||
SHAMapTest,
|
||||
::testing::Values(kBackedMode, kUnbackedMode),
|
||||
shamapBackingModeName);
|
||||
|
||||
class SHAMapPathProof : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
beast::Journal const j_{TestSink::instance()};
|
||||
};
|
||||
|
||||
TEST_F(SHAMapPathProof, verify_proof_path)
|
||||
{
|
||||
tests::TestNodeFamily tf{j_};
|
||||
SHAMap map{SHAMapType::FREE, tf};
|
||||
map.setUnbacked();
|
||||
|
||||
uint256 key;
|
||||
uint256 rootHash;
|
||||
std::vector<Blob> goodPath;
|
||||
|
||||
for (unsigned char c = 1; c < 100; ++c)
|
||||
{
|
||||
uint256 k(c);
|
||||
map.addItem(SHAMapNodeType::TnAccountState, makeShamapitem(k, Slice{k.data(), k.size()}));
|
||||
map.invariants();
|
||||
|
||||
auto root = map.getHash().asUInt256();
|
||||
auto path = map.getProofPath(k);
|
||||
if (!path)
|
||||
{
|
||||
ADD_FAILURE() << "Missing proof path";
|
||||
return;
|
||||
}
|
||||
auto& proofPath = *path;
|
||||
|
||||
EXPECT_TRUE(map.verifyProofPath(root, k, proofPath));
|
||||
if (c == 1)
|
||||
{
|
||||
// extra node
|
||||
proofPath.insert(proofPath.begin(), proofPath.front());
|
||||
EXPECT_FALSE(map.verifyProofPath(root, k, proofPath));
|
||||
// wrong key
|
||||
uint256 const wrongKey(c + 1);
|
||||
EXPECT_FALSE(map.getProofPath(wrongKey));
|
||||
}
|
||||
if (c == 99)
|
||||
{
|
||||
key = k;
|
||||
rootHash = root;
|
||||
goodPath = std::move(proofPath);
|
||||
}
|
||||
}
|
||||
|
||||
// still good
|
||||
EXPECT_TRUE(map.verifyProofPath(rootHash, key, goodPath));
|
||||
// empty path
|
||||
std::vector<Blob> badPath;
|
||||
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
|
||||
// too long
|
||||
badPath = goodPath;
|
||||
badPath.push_back(goodPath.back());
|
||||
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
|
||||
// bad node
|
||||
badPath.clear();
|
||||
badPath.emplace_back(100, 100);
|
||||
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
|
||||
// bad node type
|
||||
badPath.clear();
|
||||
badPath.push_back(goodPath.front());
|
||||
badPath.front().back()--; // change node type
|
||||
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
|
||||
// all inner
|
||||
badPath.clear();
|
||||
badPath = goodPath;
|
||||
badPath.erase(badPath.begin());
|
||||
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
|
||||
}
|
||||
|
||||
} // namespace xrpl::tests
|
||||
176
src/tests/libxrpl/shamap/SHAMapSync.cpp
Normal file
176
src/tests/libxrpl/shamap/SHAMapSync.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <helpers/TestSink.h>
|
||||
#include <shamap/common.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::tests {
|
||||
|
||||
class SHAMapSyncTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
beast::Journal const j_{TestSink::instance()};
|
||||
beast::xor_shift_engine eng_;
|
||||
|
||||
boost::intrusive_ptr<SHAMapItem>
|
||||
makeRandomAS()
|
||||
{
|
||||
Serializer s;
|
||||
|
||||
for (int d = 0; d < 3; ++d)
|
||||
s.add32(randInt<std::uint32_t>(eng_));
|
||||
return makeShamapitem(s.getSHA512Half(), s.slice());
|
||||
}
|
||||
|
||||
bool
|
||||
confuseMap(SHAMap& map, int count)
|
||||
{
|
||||
// add a bunch of random states to a map, then remove them
|
||||
// map should be the same
|
||||
SHAMapHash const beforeHash = map.getHash();
|
||||
|
||||
std::list<uint256> items;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
auto item = makeRandomAS();
|
||||
items.push_back(item->key());
|
||||
|
||||
if (!map.addItem(SHAMapNodeType::TnAccountState, item))
|
||||
{
|
||||
ADD_FAILURE() << "Unable to add item to map";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& item : items)
|
||||
{
|
||||
if (!map.delItem(item))
|
||||
{
|
||||
ADD_FAILURE() << "Unable to remove item from map";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (beforeHash != map.getHash())
|
||||
{
|
||||
ADD_FAILURE() << "Hashes do not match " << beforeHash << " " << map.getHash();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SHAMapSyncTest, sync)
|
||||
{
|
||||
TestNodeFamily f{j_}, f2{j_};
|
||||
SHAMap source{SHAMapType::FREE, f};
|
||||
SHAMap destination{SHAMapType::FREE, f2};
|
||||
|
||||
int const items = 10000;
|
||||
for (int i = 0; i < items; ++i)
|
||||
{
|
||||
source.addItem(SHAMapNodeType::TnAccountState, makeRandomAS());
|
||||
if (i % 100 == 0)
|
||||
source.invariants();
|
||||
}
|
||||
|
||||
source.invariants();
|
||||
ASSERT_TRUE(confuseMap(source, 500));
|
||||
source.invariants();
|
||||
|
||||
source.setImmutable();
|
||||
|
||||
int count = 0;
|
||||
source.visitLeaves([&count]([[maybe_unused]] auto const& item) { ++count; });
|
||||
EXPECT_EQ(count, items);
|
||||
|
||||
std::vector<SHAMapMissingNode> missingNodes;
|
||||
source.walkMap(missingNodes, 2048);
|
||||
EXPECT_TRUE(missingNodes.empty());
|
||||
|
||||
destination.setSynching();
|
||||
|
||||
{
|
||||
std::vector<SHAMapNodeData> a;
|
||||
|
||||
ASSERT_TRUE(source.getNodeFat(SHAMapNodeID(), a, randBool(eng_), randInt(eng_, 2)));
|
||||
|
||||
ASSERT_FALSE(a.empty()) << "NodeSize";
|
||||
|
||||
auto node = SHAMapTreeNode::makeFromWire(makeSlice(a[0].data));
|
||||
if (!node)
|
||||
FAIL() << "Could not create node";
|
||||
ASSERT_TRUE(destination.addRootNode(source.getHash(), std::move(node), nullptr).isGood());
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
f.clock().advance(std::chrono::seconds(1));
|
||||
|
||||
// get the list of nodes we know we need
|
||||
auto nodesMissing = destination.getMissingNodes(2048, nullptr);
|
||||
|
||||
if (nodesMissing.empty())
|
||||
break;
|
||||
|
||||
// get as many nodes as possible based on this information
|
||||
std::vector<SHAMapNodeData> b;
|
||||
|
||||
for (auto& it : nodesMissing)
|
||||
{
|
||||
// Keep failures fatal here because this loop is data-dependent.
|
||||
// non-deterministic number of times and the number of tests run
|
||||
// should be deterministic
|
||||
if (!source.getNodeFat(it.first, b, randBool(eng_), randInt(eng_, 2)))
|
||||
FAIL() << "Unable to fetch node";
|
||||
}
|
||||
|
||||
// Keep failures fatal here because this loop is data-dependent.
|
||||
// non-deterministic number of times and the number of tests run
|
||||
// should be deterministic
|
||||
if (b.empty())
|
||||
FAIL() << "No nodes returned";
|
||||
|
||||
for (auto const& i : b)
|
||||
{
|
||||
// Keep failures fatal here because this loop is data-dependent.
|
||||
// non-deterministic number of times and the number of tests run
|
||||
// should be deterministic
|
||||
auto node = SHAMapTreeNode::makeFromWire(makeSlice(i.data));
|
||||
if (!node)
|
||||
FAIL() << "Could not create node";
|
||||
if (i.isLeaf != node->isLeaf())
|
||||
FAIL() << "Node is not a leaf";
|
||||
if (!destination.addKnownNode(i.nodeID, std::move(node), nullptr).isUseful())
|
||||
FAIL() << "Known node was not useful";
|
||||
}
|
||||
} while (true);
|
||||
|
||||
destination.clearSynching();
|
||||
|
||||
EXPECT_TRUE(source.deepCompare(destination));
|
||||
|
||||
destination.invariants();
|
||||
}
|
||||
|
||||
} // namespace xrpl::tests
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/clock/manual_clock.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/config/BasicConfig.h>
|
||||
#include <xrpl/config/Constants.h>
|
||||
@@ -92,13 +91,17 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
missingNodeAcquireBySeq(std::uint32_t refNum, uint256 const& nodeHash) override
|
||||
missingNodeAcquireBySeq(
|
||||
[[maybe_unused]] std::uint32_t refNum,
|
||||
[[maybe_unused]] uint256 const& nodeHash) override
|
||||
{
|
||||
Throw<std::runtime_error>("missing node");
|
||||
}
|
||||
|
||||
void
|
||||
missingNodeAcquireByHash(uint256 const& refHash, std::uint32_t refNum) override
|
||||
missingNodeAcquireByHash(
|
||||
[[maybe_unused]] uint256 const& refHash,
|
||||
[[maybe_unused]] std::uint32_t refNum) override
|
||||
{
|
||||
Throw<std::runtime_error>("missing node");
|
||||
}
|
||||
@@ -110,7 +113,7 @@ public:
|
||||
(*tnCache_).reset();
|
||||
}
|
||||
|
||||
beast::ManualClock<std::chrono::steady_clock>
|
||||
TestStopwatch&
|
||||
clock()
|
||||
{
|
||||
return clock_;
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <xrpld/overlay/PeerSet.h>
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/clock/abstract_clock.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
@@ -24,7 +23,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -148,16 +147,19 @@ private:
|
||||
processData(std::shared_ptr<Peer> peer, protocol::TMLedgerData const& data);
|
||||
|
||||
bool
|
||||
takeHeader(std::string const& data);
|
||||
takeHeader(std::string_view data);
|
||||
|
||||
void
|
||||
receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode&);
|
||||
receiveNode(
|
||||
std::shared_ptr<Peer> const& peer,
|
||||
protocol::TMLedgerData const& packet,
|
||||
SHAMapAddNode& san);
|
||||
|
||||
bool
|
||||
takeTxRootNode(Slice const& data, SHAMapAddNode&);
|
||||
takeTxRootNode(std::string_view data, SHAMapAddNode& san);
|
||||
|
||||
bool
|
||||
takeAsRootNode(Slice const& data, SHAMapAddNode&);
|
||||
takeAsRootNode(std::string_view data, SHAMapAddNode& san);
|
||||
|
||||
std::vector<uint256>
|
||||
neededTxHashes(int max, SHAMapSyncFilter const* filter) const;
|
||||
|
||||
52
src/xrpld/app/ledger/LedgerNodeHelpers.h
Normal file
52
src/xrpld/app/ledger/LedgerNodeHelpers.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace protocol {
|
||||
class TMLedgerNode;
|
||||
} // namespace protocol
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Deserializes a SHAMapTreeNode from wire format data.
|
||||
*
|
||||
* This function attempts to create a SHAMapTreeNode from the provided data string. If the data is
|
||||
* malformed or deserialization fails, the function returns a nullptr instead of throwing an
|
||||
* exception.
|
||||
*
|
||||
* @param data The serialized node data in wire format.
|
||||
* @return The deserialized tree node if successful, or a nullptr if deserialization fails.
|
||||
*/
|
||||
[[nodiscard]] SHAMapTreeNodePtr
|
||||
getTreeNode(std::string_view data);
|
||||
|
||||
/**
|
||||
* @brief Extracts or reconstructs the SHAMapNodeID from a ledger node proto message.
|
||||
*
|
||||
* This function retrieves the SHAMapNodeID for a tree node, with behavior that depends on which
|
||||
* field is set and the node type (inner vs. leaf).
|
||||
*
|
||||
* When the legacy `nodeid` field is set in the message:
|
||||
* - For all nodes: Deserializes the node ID from the field.
|
||||
* - For leaf nodes: Validates that the node ID is consistent with the leaf's key.
|
||||
*
|
||||
* When the new `id` or `depth` field is set in the message:
|
||||
* - For inner nodes: Deserializes the node ID from the `id` field.
|
||||
* - For leaf nodes: Reconstructs the node ID using both the depth from the `depth` field and the
|
||||
* key from the leaf node's item.
|
||||
* Note that root nodes may be inner nodes or leaf nodes.
|
||||
*
|
||||
* @param ledgerNode The validated protocol message containing the ledger node data.
|
||||
* @param treeNode The deserialized tree node (inner or leaf node).
|
||||
* @return An optional containing the node ID if extraction/reconstruction succeeds, or std::nullopt
|
||||
* if the required fields are missing or validation fails.
|
||||
*/
|
||||
[[nodiscard]] std::optional<SHAMapNodeID>
|
||||
getSHAMapNodeID(protocol::TMLedgerNode const& ledgerNode, SHAMapTreeNode const& treeNode);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpld/app/ledger/AccountStateSF.h>
|
||||
#include <xrpld/app/ledger/InboundLedgers.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/ledger/LedgerNodeHelpers.h>
|
||||
#include <xrpld/app/ledger/TransactionStateSF.h>
|
||||
#include <xrpld/app/ledger/detail/TimeoutCounter.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
@@ -44,8 +45,8 @@
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -774,7 +775,7 @@ InboundLedger::filterNodes(
|
||||
*/
|
||||
// data must not have hash prefix
|
||||
bool
|
||||
InboundLedger::takeHeader(std::string const& data)
|
||||
InboundLedger::takeHeader(std::string_view data)
|
||||
{
|
||||
// Return value: true=normal, false=bad data
|
||||
JLOG(journal_.trace()) << "got header acquiring ledger " << hash_;
|
||||
@@ -819,7 +820,10 @@ InboundLedger::takeHeader(std::string const& data)
|
||||
Call with a lock
|
||||
*/
|
||||
void
|
||||
InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode& san)
|
||||
InboundLedger::receiveNode(
|
||||
std::shared_ptr<Peer> const& peer,
|
||||
protocol::TMLedgerData const& packet,
|
||||
SHAMapAddNode& san)
|
||||
{
|
||||
if (!haveHeader_)
|
||||
{
|
||||
@@ -862,32 +866,47 @@ InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode&
|
||||
{
|
||||
auto const f = filter.get();
|
||||
|
||||
for (auto const& node : packet.nodes())
|
||||
for (auto const& ledgerNode : packet.nodes())
|
||||
{
|
||||
auto const nodeID = deserializeSHAMapNodeID(node.nodeid());
|
||||
auto treeNode = getTreeNode(ledgerNode.nodedata());
|
||||
if (!treeNode)
|
||||
{
|
||||
JLOG(journal_.warn()) << "Got invalid node data";
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_node.node_data invalid");
|
||||
san.incInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
auto const nodeID = getSHAMapNodeID(ledgerNode, *treeNode);
|
||||
if (!nodeID)
|
||||
throw std::runtime_error("data does not properly deserialize");
|
||||
{
|
||||
JLOG(journal_.warn()) << "Got invalid node id";
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_node.node_id invalid");
|
||||
san.incInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodeID->isRoot())
|
||||
{
|
||||
san += map.addRootNode(rootHash, makeSlice(node.nodedata()), f);
|
||||
san += map.addRootNode(rootHash, std::move(treeNode), f);
|
||||
}
|
||||
else
|
||||
{
|
||||
san += map.addKnownNode(*nodeID, makeSlice(node.nodedata()), f);
|
||||
san += map.addKnownNode(*nodeID, std::move(treeNode), f);
|
||||
}
|
||||
|
||||
if (!san.isGood())
|
||||
{
|
||||
JLOG(journal_.warn()) << "Received bad node data";
|
||||
JLOG(journal_.warn()) << "Got invalid node";
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_node invalid");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(journal_.error()) << "Received bad node data: " << e.what();
|
||||
// If we get here it is not necessarily because the node was bad, so don't charge the peer.
|
||||
JLOG(journal_.error()) << "Could not process node: " << e.what();
|
||||
san.incInvalid();
|
||||
return;
|
||||
}
|
||||
@@ -915,7 +934,7 @@ InboundLedger::receiveNode(protocol::TMLedgerData const& packet, SHAMapAddNode&
|
||||
Call with a lock
|
||||
*/
|
||||
bool
|
||||
InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san)
|
||||
InboundLedger::takeAsRootNode(std::string_view data, SHAMapAddNode& san)
|
||||
{
|
||||
if (failed_ || haveState_)
|
||||
{
|
||||
@@ -931,9 +950,17 @@ InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto treeNode = getTreeNode(data);
|
||||
if (!treeNode)
|
||||
{
|
||||
JLOG(journal_.warn()) << "Got invalid node data";
|
||||
san.incInvalid();
|
||||
return false;
|
||||
}
|
||||
|
||||
AccountStateSF filter(ledger_->stateMap().family().db(), app_.getLedgerMaster());
|
||||
san +=
|
||||
ledger_->stateMap().addRootNode(SHAMapHash{ledger_->header().accountHash}, data, &filter);
|
||||
san += ledger_->stateMap().addRootNode(
|
||||
SHAMapHash{ledger_->header().accountHash}, std::move(treeNode), &filter);
|
||||
return san.isGood();
|
||||
}
|
||||
|
||||
@@ -941,7 +968,7 @@ InboundLedger::takeAsRootNode(Slice const& data, SHAMapAddNode& san)
|
||||
Call with a lock
|
||||
*/
|
||||
bool
|
||||
InboundLedger::takeTxRootNode(Slice const& data, SHAMapAddNode& san)
|
||||
InboundLedger::takeTxRootNode(std::string_view data, SHAMapAddNode& san)
|
||||
{
|
||||
if (failed_ || haveTransactions_)
|
||||
{
|
||||
@@ -957,8 +984,17 @@ InboundLedger::takeTxRootNode(Slice const& data, SHAMapAddNode& san)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto treeNode = getTreeNode(data);
|
||||
if (!treeNode)
|
||||
{
|
||||
JLOG(journal_.warn()) << "Got invalid node data";
|
||||
san.incInvalid();
|
||||
return false;
|
||||
}
|
||||
|
||||
TransactionStateSF filter(ledger_->txMap().family().db(), app_.getLedgerMaster());
|
||||
san += ledger_->txMap().addRootNode(SHAMapHash{ledger_->header().txHash}, data, &filter);
|
||||
san += ledger_->txMap().addRootNode(
|
||||
SHAMapHash{ledger_->header().txHash}, std::move(treeNode), &filter);
|
||||
return san.isGood();
|
||||
}
|
||||
|
||||
@@ -1055,15 +1091,25 @@ InboundLedger::processData(std::shared_ptr<Peer> peer, protocol::TMLedgerData co
|
||||
}
|
||||
|
||||
if (!haveState_ && (packet.nodes().size() > 1) &&
|
||||
!takeAsRootNode(makeSlice(packet.nodes(1).nodedata()), san))
|
||||
!takeAsRootNode(packet.nodes(1).nodedata(), san))
|
||||
{
|
||||
JLOG(journal_.warn()) << "Included AS root invalid";
|
||||
if (san.isInvalid())
|
||||
{
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_data invalid AS root");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!haveTransactions_ && (packet.nodes().size() > 2) &&
|
||||
!takeTxRootNode(makeSlice(packet.nodes(2).nodedata()), san))
|
||||
!takeTxRootNode(packet.nodes(2).nodedata(), san))
|
||||
{
|
||||
JLOG(journal_.warn()) << "Included TX root invalid";
|
||||
if (san.isInvalid())
|
||||
{
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_data invalid TX root");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
@@ -1092,24 +1138,18 @@ InboundLedger::processData(std::shared_ptr<Peer> peer, protocol::TMLedgerData co
|
||||
|
||||
ScopedLockType const sl(mtx_);
|
||||
|
||||
// Verify node IDs and data are complete
|
||||
for (auto const& node : packet.nodes())
|
||||
{
|
||||
if (!node.has_nodeid() || !node.has_nodedata())
|
||||
{
|
||||
JLOG(journal_.warn()) << "Got bad node";
|
||||
peer->charge(Resource::kFeeMalformedRequest, "ledger_data bad node");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
SHAMapAddNode san;
|
||||
receiveNode(packet, san);
|
||||
receiveNode(peer, packet, san);
|
||||
|
||||
JLOG(journal_.debug()) << "Ledger "
|
||||
<< ((packet.type() == protocol::liTX_NODE) ? "TX" : "AS")
|
||||
<< " node stats: " << san.get();
|
||||
|
||||
// Note: Peer charges for invalid/malformed data are issued from within receiveNode at the
|
||||
// exact failure site, so the peer is only charged for problems they are responsible for.
|
||||
if (san.isInvalid())
|
||||
return -1;
|
||||
|
||||
if (san.isUseful())
|
||||
progress_ = true;
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
#include <xrpld/app/ledger/InboundLedger.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/ledger/LedgerNodeHelpers.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/PeerSet.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/DecayingSample.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
@@ -248,23 +248,17 @@ public:
|
||||
Serializer s;
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < packetPtr->nodes().size(); ++i)
|
||||
for (auto const& ledgerNode : packetPtr->nodes())
|
||||
{
|
||||
auto const& node = packetPtr->nodes(i);
|
||||
|
||||
if (!node.has_nodeid() || !node.has_nodedata())
|
||||
return;
|
||||
|
||||
auto newNode = SHAMapTreeNode::makeFromWire(makeSlice(node.nodedata()));
|
||||
|
||||
if (!newNode)
|
||||
auto const treeNode = getTreeNode(ledgerNode.nodedata());
|
||||
if (!treeNode)
|
||||
return;
|
||||
|
||||
s.erase();
|
||||
newNode->serializeWithPrefix(s);
|
||||
treeNode->serializeWithPrefix(s);
|
||||
|
||||
app_.getLedgerMaster().addFetchPack(
|
||||
newNode->getHash().asUInt256(), std::make_shared<Blob>(s.begin(), s.end()));
|
||||
treeNode->getHash().asUInt256(), std::make_shared<Blob>(s.begin(), s.end()));
|
||||
}
|
||||
}
|
||||
catch (std::exception const&) // NOLINT(bugprone-empty-catch)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include <xrpld/app/ledger/InboundTransactions.h>
|
||||
|
||||
#include <xrpld/app/ledger/LedgerNodeHelpers.h>
|
||||
#include <xrpld/app/ledger/detail/TransactionAcquire.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/PeerSet.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/beast/insight/Collector.h>
|
||||
#include <xrpl/protocol/RippleLedgerHash.h>
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
@@ -136,34 +137,43 @@ public:
|
||||
|
||||
if (ta == nullptr)
|
||||
{
|
||||
peer->charge(Resource::kFeeUselessData, "ledger_data");
|
||||
peer->charge(Resource::kFeeUselessData, "ledger_data useless");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::pair<SHAMapNodeID, Slice>> data;
|
||||
std::vector<std::pair<SHAMapNodeID, SHAMapTreeNodePtr>> data;
|
||||
data.reserve(packet.nodes().size());
|
||||
|
||||
for (auto const& node : packet.nodes())
|
||||
for (auto const& ledgerNode : packet.nodes())
|
||||
{
|
||||
if (!node.has_nodeid() || !node.has_nodedata())
|
||||
auto treeNode = getTreeNode(ledgerNode.nodedata());
|
||||
if (!treeNode)
|
||||
{
|
||||
peer->charge(Resource::kFeeMalformedRequest, "ledger_data");
|
||||
JLOG(j_.warn()) << "Got invalid node data";
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_node.node_data invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
auto const id = deserializeSHAMapNodeID(node.nodeid());
|
||||
|
||||
if (!id)
|
||||
auto const nodeID = getSHAMapNodeID(ledgerNode, *treeNode);
|
||||
if (!nodeID)
|
||||
{
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_data");
|
||||
JLOG(j_.warn()) << "Got invalid node id";
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_node.node_id invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
data.emplace_back(*id, makeSlice(node.nodedata()));
|
||||
data.emplace_back(*nodeID, std::move(treeNode));
|
||||
}
|
||||
|
||||
if (!ta->takeNodes(data, peer).isUseful())
|
||||
peer->charge(Resource::kFeeUselessData, "ledger_data not useful");
|
||||
auto const san = ta->takeNodes(std::move(data), peer);
|
||||
if (san.isInvalid())
|
||||
{
|
||||
peer->charge(Resource::kFeeInvalidData, "ledger_data invalid");
|
||||
}
|
||||
else if (!san.isUseful())
|
||||
{
|
||||
peer->charge(Resource::kFeeUselessData, "ledger_data useless");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
81
src/xrpld/app/ledger/detail/LedgerNodeHelpers.cpp
Normal file
81
src/xrpld/app/ledger/detail/LedgerNodeHelpers.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <xrpld/app/ledger/LedgerNodeHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapLeafNode.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
getTreeNode(std::string_view data)
|
||||
{
|
||||
auto const slice = makeSlice(data);
|
||||
try
|
||||
{
|
||||
return SHAMapTreeNode::makeFromWire(slice);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<SHAMapNodeID>
|
||||
getSHAMapNodeID(protocol::TMLedgerNode const& ledgerNode, SHAMapTreeNode const& treeNode)
|
||||
{
|
||||
if (ledgerNode.has_id() || ledgerNode.has_depth())
|
||||
{
|
||||
// Reject ambiguous messages that mix the legacy and new reference fields.
|
||||
if (ledgerNode.has_nodeid())
|
||||
return std::nullopt;
|
||||
|
||||
if (treeNode.isInner())
|
||||
{
|
||||
if (!ledgerNode.has_id())
|
||||
return std::nullopt;
|
||||
|
||||
return deserializeSHAMapNodeID(ledgerNode.id());
|
||||
}
|
||||
|
||||
if (treeNode.isLeaf())
|
||||
{
|
||||
if (!ledgerNode.has_depth() || ledgerNode.depth() > SHAMap::kLeafDepth)
|
||||
return std::nullopt;
|
||||
|
||||
auto const key = safeDowncast<SHAMapLeafNode const*>(&treeNode)->peekItem()->key();
|
||||
return SHAMapNodeID::createID(ledgerNode.depth(), key);
|
||||
}
|
||||
|
||||
UNREACHABLE("xrpl::getSHAMapNodeID : tree node is neither inner nor leaf");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!ledgerNode.has_nodeid())
|
||||
return std::nullopt;
|
||||
|
||||
auto nodeID = deserializeSHAMapNodeID(ledgerNode.nodeid());
|
||||
if (!nodeID.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
if (treeNode.isLeaf())
|
||||
{
|
||||
auto const key = safeDowncast<SHAMapLeafNode const*>(&treeNode)->peekItem()->key();
|
||||
auto const expectedID = SHAMapNodeID::createID(static_cast<int>(nodeID->getDepth()), key);
|
||||
if (nodeID->getNodeID() != expectedID.getNodeID())
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return nodeID;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -7,13 +7,13 @@
|
||||
#include <xrpld/overlay/PeerSet.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/core/Job.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapAddNode.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
@@ -171,7 +171,7 @@ TransactionAcquire::trigger(std::shared_ptr<Peer> const& peer)
|
||||
|
||||
SHAMapAddNode
|
||||
TransactionAcquire::takeNodes(
|
||||
std::vector<std::pair<SHAMapNodeID, Slice>> const& data,
|
||||
std::vector<std::pair<SHAMapNodeID, SHAMapTreeNodePtr>> data,
|
||||
std::shared_ptr<Peer> const& peer)
|
||||
{
|
||||
ScopedLockType const sl(mtx_);
|
||||
@@ -195,7 +195,7 @@ TransactionAcquire::takeNodes(
|
||||
|
||||
ConsensusTransSetSF sf(app_, app_.getTempNodeCache());
|
||||
|
||||
for (auto const& d : data)
|
||||
for (auto& d : data)
|
||||
{
|
||||
if (d.first.isRoot())
|
||||
{
|
||||
@@ -203,7 +203,8 @@ TransactionAcquire::takeNodes(
|
||||
{
|
||||
JLOG(journal_.debug()) << "Got root TXS node, already have it";
|
||||
}
|
||||
else if (!map_->addRootNode(SHAMapHash{hash_}, d.second, nullptr).isGood())
|
||||
else if (!map_->addRootNode(SHAMapHash{hash_}, std::move(d.second), nullptr)
|
||||
.isGood())
|
||||
{
|
||||
JLOG(journal_.warn()) << "TX acquire got bad root node";
|
||||
}
|
||||
@@ -212,7 +213,7 @@ TransactionAcquire::takeNodes(
|
||||
haveRoot_ = true;
|
||||
}
|
||||
}
|
||||
else if (!map_->addKnownNode(d.first, d.second, &sf).isGood())
|
||||
else if (!map_->addKnownNode(d.first, std::move(d.second), &sf).isGood())
|
||||
{
|
||||
JLOG(journal_.warn()) << "TX acquire got bad non-root node";
|
||||
return SHAMapAddNode::invalid();
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
#include <xrpld/overlay/PeerSet.h>
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapAddNode.h>
|
||||
#include <xrpl/shamap/SHAMapTreeNode.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
@@ -32,8 +32,8 @@ public:
|
||||
|
||||
SHAMapAddNode
|
||||
takeNodes(
|
||||
std::vector<std::pair<SHAMapNodeID, Slice>> const& data,
|
||||
std::shared_ptr<Peer> const&);
|
||||
std::vector<std::pair<SHAMapNodeID, SHAMapTreeNodePtr>> data,
|
||||
std::shared_ptr<Peer> const& peer);
|
||||
|
||||
void
|
||||
init(int startPeers);
|
||||
|
||||
@@ -23,6 +23,7 @@ enum class ProtocolFeature {
|
||||
ValidatorListPropagation,
|
||||
ValidatorList2Propagation,
|
||||
LedgerReplay,
|
||||
LedgerNodeDepth,
|
||||
};
|
||||
|
||||
/** Represents a peer connection in the overlay. */
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpld/app/ledger/InboundLedgers.h>
|
||||
#include <xrpld/app/ledger/InboundTransactions.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/ledger/LedgerNodeHelpers.h>
|
||||
#include <xrpld/app/ledger/TransactionMaster.h>
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/app/misc/ValidatorList.h>
|
||||
@@ -61,6 +62,7 @@
|
||||
#include <xrpl/resource/Gossip.h>
|
||||
#include <xrpl/server/LoadFeeTrack.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
#include <xrpl/tx/apply.h>
|
||||
|
||||
@@ -539,6 +541,8 @@ PeerImp::supportsFeature(ProtocolFeature f) const
|
||||
return protocol_ >= makeProtocol(2, 1);
|
||||
case ProtocolFeature::ValidatorList2Propagation:
|
||||
return protocol_ >= makeProtocol(2, 2);
|
||||
case ProtocolFeature::LedgerNodeDepth:
|
||||
return protocol_ >= makeProtocol(2, 3);
|
||||
case ProtocolFeature::LedgerReplay:
|
||||
return ledgerReplayEnabled_;
|
||||
}
|
||||
@@ -1473,23 +1477,12 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetLedger> const& m)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify ledger node IDs
|
||||
if (itype != protocol::liBASE)
|
||||
// Verify ledger node counts. Full parsing of the node IDs is deferred to the job, so the I/O
|
||||
// thread is not burdened with SHAMapNodeID deserialization for every TMGetLedger message.
|
||||
if (itype != protocol::liBASE && m->nodeids_size() <= 0)
|
||||
{
|
||||
if (m->nodeids_size() <= 0)
|
||||
{
|
||||
badData("Invalid ledger node IDs");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const& nodeId : m->nodeids())
|
||||
{
|
||||
if (deserializeSHAMapNodeID(nodeId) == std::nullopt)
|
||||
{
|
||||
badData("Invalid SHAMap node ID");
|
||||
return;
|
||||
}
|
||||
}
|
||||
badData("Invalid ledger node IDs");
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify query type
|
||||
@@ -1509,11 +1502,40 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetLedger> const& m)
|
||||
}
|
||||
}
|
||||
|
||||
// Queue a job to process the request
|
||||
// Queue a job to process the request.
|
||||
std::weak_ptr<PeerImp> const weak = shared_from_this();
|
||||
app_.getJobQueue().addJob(JtLedgerReq, "RcvGetLedger", [weak, m]() {
|
||||
if (auto peer = weak.lock())
|
||||
peer->processLedgerRequest(m);
|
||||
app_.getJobQueue().addJob(JtLedgerReq, "RcvGetLedger", [weak, m, itype]() {
|
||||
auto peer = weak.lock();
|
||||
if (!peer)
|
||||
return;
|
||||
|
||||
std::vector<SHAMapNodeID> nodeIDs;
|
||||
if (itype != protocol::liBASE)
|
||||
{
|
||||
nodeIDs.reserve(std::min(m->nodeids_size(), Tuning::kSoftMaxReplyNodes));
|
||||
for (auto const& nodeId : m->nodeids())
|
||||
{
|
||||
if (nodeIDs.size() >= Tuning::kSoftMaxReplyNodes)
|
||||
{
|
||||
// Charge the peer for requesting too many node IDs, but continue processing the
|
||||
// received node IDs up to the limit. If the request is legitimate then at least
|
||||
// they will get a response and won't have to resend these nodes in their next
|
||||
// request.
|
||||
peer->charge(
|
||||
Resource::kFeeModerateBurdenPeer, "TMGetLedger: too many node IDs");
|
||||
break;
|
||||
}
|
||||
auto parsed = deserializeSHAMapNodeID(nodeId);
|
||||
if (!parsed)
|
||||
{
|
||||
peer->charge(Resource::kFeeInvalidData, "TMGetLedger: Invalid node ID");
|
||||
return;
|
||||
}
|
||||
nodeIDs.push_back(std::move(*parsed));
|
||||
}
|
||||
}
|
||||
|
||||
peer->processLedgerRequest(m, std::move(nodeIDs));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1678,12 +1700,44 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMLedgerData> const& m)
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is a request cookie, attempt to relay the message
|
||||
// If there is a request cookie, attempt to relay the message.
|
||||
if (m->has_requestcookie())
|
||||
{
|
||||
if (auto peer = overlay_.findPeerByShortID(m->requestcookie()))
|
||||
{
|
||||
m->clear_requestcookie();
|
||||
|
||||
// If the original requester doesn't support the new depth-based format, rewrite any
|
||||
// nodes that use it back to the legacy nodeid format before relaying. Once all nodes
|
||||
// have upgraded, the old protocol version and this code can be removed.
|
||||
if (!peer->supportsFeature(ProtocolFeature::LedgerNodeDepth))
|
||||
{
|
||||
for (int i = 0; i < m->nodes_size(); ++i)
|
||||
{
|
||||
auto* ledgerNode = m->mutable_nodes(i);
|
||||
if (ledgerNode->reference_case() != ledgerNode->REFERENCE_NOT_SET)
|
||||
{
|
||||
auto treeNode = getTreeNode(ledgerNode->nodedata());
|
||||
if (!treeNode)
|
||||
{
|
||||
JLOG(pJournal_.warn()) << "Unable to get tree node";
|
||||
return;
|
||||
}
|
||||
|
||||
auto const nodeID = getSHAMapNodeID(*ledgerNode, *treeNode);
|
||||
if (!nodeID)
|
||||
{
|
||||
JLOG(pJournal_.warn()) << "Unable to get node ID";
|
||||
return;
|
||||
}
|
||||
|
||||
ledgerNode->set_nodeid(nodeID->getRawString());
|
||||
ledgerNode->clear_id();
|
||||
ledgerNode->clear_depth();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
peer->send(std::make_shared<Message>(*m, protocol::mtLEDGER_DATA));
|
||||
}
|
||||
else
|
||||
@@ -3283,7 +3337,9 @@ PeerImp::getTxSet(std::shared_ptr<protocol::TMGetLedger> const& m) const
|
||||
}
|
||||
|
||||
void
|
||||
PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
|
||||
PeerImp::processLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& m,
|
||||
std::vector<SHAMapNodeID> nodeIDs)
|
||||
{
|
||||
// Do not resource charge a peer responding to a relay
|
||||
if (!m->has_requestcookie())
|
||||
@@ -3368,26 +3424,25 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
|
||||
}
|
||||
|
||||
// Add requested node data to reply
|
||||
if (m->nodeids_size() > 0)
|
||||
if (!nodeIDs.empty())
|
||||
{
|
||||
std::uint32_t const defaultDepth = isHighLatency() ? 2 : 1;
|
||||
auto const queryDepth{m->has_querydepth() ? m->querydepth() : defaultDepth};
|
||||
|
||||
std::vector<std::pair<SHAMapNodeID, Blob>> data;
|
||||
std::vector<SHAMapNodeData> data;
|
||||
data.reserve(Tuning::kSoftMaxReplyNodes);
|
||||
auto const useLedgerNodeDepth = supportsFeature(ProtocolFeature::LedgerNodeDepth);
|
||||
|
||||
for (int i = 0;
|
||||
i < m->nodeids_size() && ledgerData.nodes_size() < Tuning::kSoftMaxReplyNodes;
|
||||
++i)
|
||||
for (auto const& nodeID : nodeIDs)
|
||||
{
|
||||
auto const shaMapNodeId{deserializeSHAMapNodeID(m->nodeids(i))};
|
||||
if (ledgerData.nodes_size() >= Tuning::kSoftMaxReplyNodes)
|
||||
break;
|
||||
|
||||
data.clear();
|
||||
data.reserve(Tuning::kSoftMaxReplyNodes);
|
||||
|
||||
try
|
||||
{
|
||||
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) nodeids checked in onGetLedger
|
||||
if (map->getNodeFat(*shaMapNodeId, data, fatLeaves, queryDepth))
|
||||
if (map->getNodeFat(nodeID, data, fatLeaves, queryDepth))
|
||||
{
|
||||
JLOG(pJournal_.trace())
|
||||
<< "processLedgerRequest: getNodeFat got " << data.size() << " nodes";
|
||||
@@ -3396,9 +3451,25 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
|
||||
{
|
||||
if (ledgerData.nodes_size() >= Tuning::kHardMaxReplyNodes)
|
||||
break;
|
||||
|
||||
protocol::TMLedgerNode* node{ledgerData.add_nodes()};
|
||||
node->set_nodeid(d.first.getRawString());
|
||||
node->set_nodedata(d.second.data(), d.second.size());
|
||||
node->set_nodedata(d.data.data(), d.data.size());
|
||||
|
||||
// When the LedgerNodeDepth protocol feature is not supported by the peer,
|
||||
// we always set the `nodeid` field. However, when it is supported then we
|
||||
// set the `id` field for inner nodes and the `depth` field for leaf nodes.
|
||||
if (!useLedgerNodeDepth)
|
||||
{
|
||||
node->set_nodeid(d.nodeID.getRawString());
|
||||
}
|
||||
else if (d.isLeaf)
|
||||
{
|
||||
node->set_depth(d.nodeID.getDepth());
|
||||
}
|
||||
else
|
||||
{
|
||||
node->set_id(d.nodeID.getRawString());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3437,7 +3508,7 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
|
||||
info += ", no hash specified";
|
||||
|
||||
JLOG(pJournal_.warn())
|
||||
<< "processLedgerRequest: getNodeFat with nodeId " << *shaMapNodeId
|
||||
<< "processLedgerRequest: getNodeFat with nodeId " << nodeID
|
||||
<< " and ledger info type " << info << " throws exception: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
@@ -659,7 +660,9 @@ private:
|
||||
getTxSet(std::shared_ptr<protocol::TMGetLedger> const& m) const;
|
||||
|
||||
void
|
||||
processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m);
|
||||
processLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& m,
|
||||
std::vector<SHAMapNodeID> nodeIDs);
|
||||
|
||||
protected:
|
||||
// Kept `protected` so test subclasses (see
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace xrpl {
|
||||
constexpr ProtocolVersion const kSupportedProtocolList[]{
|
||||
{2, 1},
|
||||
{2, 2},
|
||||
{2, 3},
|
||||
};
|
||||
|
||||
// This ugly construct ensures that supportedProtocolList is sorted in strictly
|
||||
|
||||
Reference in New Issue
Block a user