mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-05 09:46:53 +00:00
refactor: Replace intr_ptr::SharedPtr<SHAMapTreeNode> by SHAMapTreeNodePtr (#7396)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
This commit is contained in:
@@ -97,10 +97,7 @@ SHAMap::snapShot(bool isMutable) const
|
||||
}
|
||||
|
||||
void
|
||||
SHAMap::dirtyUp(
|
||||
SharedPtrNodeStack& stack,
|
||||
uint256 const& target,
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> child)
|
||||
SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr child)
|
||||
{
|
||||
// walk the tree up from through the inner nodes to the root_
|
||||
// update hashes and links
|
||||
@@ -165,7 +162,7 @@ SHAMap::findKey(uint256 const& id) const
|
||||
return leaf;
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const
|
||||
{
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::fetchNodeFromDB : is backed");
|
||||
@@ -173,7 +170,7 @@ SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const
|
||||
return finishFetch(hash, obj);
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr<NodeObject> const& object) const
|
||||
{
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::finishFetch : is backed");
|
||||
@@ -208,7 +205,7 @@ SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr<NodeObject> const& o
|
||||
}
|
||||
|
||||
// See if a sync filter has a node
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const
|
||||
{
|
||||
if (auto nodeData = filter->getNode(hash))
|
||||
@@ -234,7 +231,7 @@ SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const
|
||||
|
||||
// Get a node without throwing
|
||||
// Used on maps where missing nodes are expected
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const
|
||||
{
|
||||
auto node = cacheLookup(hash);
|
||||
@@ -257,7 +254,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const
|
||||
return node;
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::fetchNodeNT(SHAMapHash const& hash) const
|
||||
{
|
||||
auto node = cacheLookup(hash);
|
||||
@@ -269,7 +266,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash) const
|
||||
}
|
||||
|
||||
// Throw if the node is missing
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::fetchNode(SHAMapHash const& hash) const
|
||||
{
|
||||
auto node = fetchNodeNT(hash);
|
||||
@@ -291,10 +288,10 @@ SHAMap::descendThrow(SHAMapInnerNode* parent, int branch) const
|
||||
return ret;
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::descendThrow(SHAMapInnerNode& parent, int branch) const
|
||||
{
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> ret = descend(parent, branch);
|
||||
SHAMapTreeNodePtr ret = descend(parent, branch);
|
||||
|
||||
if (!ret && !parent.isEmptyBranch(branch))
|
||||
Throw<SHAMapMissingNode>(type_, parent.getChildHash(branch));
|
||||
@@ -309,7 +306,7 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const
|
||||
if ((ret != nullptr) || !backed_)
|
||||
return ret;
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node = fetchNodeNT(parent->getChildHash(branch));
|
||||
SHAMapTreeNodePtr node = fetchNodeNT(parent->getChildHash(branch));
|
||||
if (!node)
|
||||
return nullptr;
|
||||
|
||||
@@ -317,10 +314,10 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const
|
||||
return node.get();
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::descend(SHAMapInnerNode& parent, int branch) const
|
||||
{
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node = parent.getChild(branch);
|
||||
SHAMapTreeNodePtr node = parent.getChild(branch);
|
||||
if (node || !backed_)
|
||||
return node;
|
||||
|
||||
@@ -334,10 +331,10 @@ SHAMap::descend(SHAMapInnerNode& parent, int branch) const
|
||||
|
||||
// Gets the node that would be hooked to this branch,
|
||||
// but doesn't hook it up.
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::descendNoStore(SHAMapInnerNode& parent, int branch) const
|
||||
{
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> ret = parent.getChild(branch);
|
||||
SHAMapTreeNodePtr ret = parent.getChild(branch);
|
||||
if (!ret && backed_)
|
||||
ret = fetchNode(parent.getChildHash(branch));
|
||||
return ret;
|
||||
@@ -361,7 +358,7 @@ SHAMap::descend(
|
||||
if (child == nullptr)
|
||||
{
|
||||
auto const& childHash = parent->getChildHash(branch);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> childNode = fetchNodeNT(childHash, filter);
|
||||
SHAMapTreeNodePtr childNode = fetchNodeNT(childHash, filter);
|
||||
|
||||
if (childNode)
|
||||
{
|
||||
@@ -434,7 +431,7 @@ SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, SHAMapNodeID const& nodeID)
|
||||
|
||||
SHAMapLeafNode*
|
||||
SHAMap::belowHelper(
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node,
|
||||
SHAMapTreeNodePtr node,
|
||||
SharedPtrNodeStack& stack,
|
||||
int branch,
|
||||
std::tuple<int, std::function<bool(int)>, std::function<void(int&)>> const& loopParams) const
|
||||
@@ -479,8 +476,7 @@ SHAMap::belowHelper(
|
||||
return nullptr;
|
||||
}
|
||||
SHAMapLeafNode*
|
||||
SHAMap::lastBelow(intr_ptr::SharedPtr<SHAMapTreeNode> node, SharedPtrNodeStack& stack, int branch)
|
||||
const
|
||||
SHAMap::lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch) const
|
||||
{
|
||||
auto init = kBranchFactor - 1;
|
||||
auto cmp = [](int i) { return i >= 0; };
|
||||
@@ -489,8 +485,7 @@ SHAMap::lastBelow(intr_ptr::SharedPtr<SHAMapTreeNode> node, SharedPtrNodeStack&
|
||||
return belowHelper(node, stack, branch, {init, cmp, incr});
|
||||
}
|
||||
SHAMapLeafNode*
|
||||
SHAMap::firstBelow(intr_ptr::SharedPtr<SHAMapTreeNode> node, SharedPtrNodeStack& stack, int branch)
|
||||
const
|
||||
SHAMap::firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch) const
|
||||
{
|
||||
auto init = 0;
|
||||
auto cmp = [](int i) { return i <= kBranchFactor; };
|
||||
@@ -699,10 +694,8 @@ SHAMap::delItem(uint256 const& id)
|
||||
|
||||
SHAMapNodeType const type = leaf->getType();
|
||||
|
||||
using TreeNodeType = intr_ptr::SharedPtr<SHAMapTreeNode>;
|
||||
|
||||
// What gets attached to the end of the chain (For now, nothing, since we deleted the leaf)
|
||||
TreeNodeType prevNode;
|
||||
SHAMapTreeNodePtr prevNode;
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
@@ -728,7 +721,7 @@ SHAMap::delItem(uint256 const& id)
|
||||
// no children below this branch
|
||||
//
|
||||
// Note: This is unnecessary due to the std::move above but left here for safety
|
||||
prevNode = TreeNodeType{};
|
||||
prevNode = SHAMapTreeNodePtr{};
|
||||
}
|
||||
else if (bc == 1)
|
||||
{
|
||||
@@ -741,7 +734,7 @@ SHAMap::delItem(uint256 const& id)
|
||||
{
|
||||
if (!node->isEmptyBranch(i))
|
||||
{
|
||||
node->setChild(i, TreeNodeType{});
|
||||
node->setChild(i, SHAMapTreeNodePtr{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -937,8 +930,8 @@ SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter* filter)
|
||||
@note The node must have already been unshared by having the caller
|
||||
first call SHAMapTreeNode::unshare().
|
||||
*/
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::writeNode(NodeObjectType t, intr_ptr::SharedPtr<SHAMapTreeNode> node) const
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const
|
||||
{
|
||||
XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::writeNode : valid input node");
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::writeNode : is backed");
|
||||
@@ -1155,7 +1148,7 @@ SHAMap::dump(bool hash) const
|
||||
JLOG(journal_.info()) << leafCount << " resident leaves";
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::cacheLookup(SHAMapHash const& hash) const
|
||||
{
|
||||
auto ret = f_.getTreeNodeCache()->fetch(hash.asUInt256());
|
||||
@@ -1164,7 +1157,7 @@ SHAMap::cacheLookup(SHAMapHash const& hash) const
|
||||
}
|
||||
|
||||
void
|
||||
SHAMap::canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr<SHAMapTreeNode>& node) const
|
||||
SHAMap::canonicalize(SHAMapHash const& hash, SHAMapTreeNodePtr& node) const
|
||||
{
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::canonicalize : is backed");
|
||||
XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::canonicalize : valid node input");
|
||||
|
||||
@@ -261,7 +261,7 @@ SHAMap::walkMap(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing) co
|
||||
{
|
||||
if (!node->isEmptyBranch(i))
|
||||
{
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> const nextNode = descendNoStore(*node, i);
|
||||
SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i);
|
||||
|
||||
if (nextNode)
|
||||
{
|
||||
@@ -286,7 +286,7 @@ SHAMap::walkMapParallel(std::vector<SHAMapMissingNode>& missingNodes, int maxMis
|
||||
return false;
|
||||
|
||||
using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
|
||||
std::array<intr_ptr::SharedPtr<SHAMapTreeNode>, 16> topChildren;
|
||||
std::array<SHAMapTreeNodePtr, 16> topChildren;
|
||||
{
|
||||
auto const& innerRoot = intr_ptr::staticPointerCast<SHAMapInnerNode>(root_);
|
||||
for (int i = 0; i < 16; ++i)
|
||||
@@ -331,8 +331,7 @@ SHAMap::walkMapParallel(std::vector<SHAMapMissingNode>& missingNodes, int maxMis
|
||||
{
|
||||
if (node->isEmptyBranch(i))
|
||||
continue;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> const nextNode =
|
||||
descendNoStore(*node, i);
|
||||
SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i);
|
||||
|
||||
if (nextNode)
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ SHAMapInnerNode::~SHAMapInnerNode() = default;
|
||||
void
|
||||
SHAMapInnerNode::partialDestructor()
|
||||
{
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>* children = nullptr;
|
||||
SHAMapTreeNodePtr* children = nullptr;
|
||||
// structured bindings can't be captured in c++ 17; use tie instead
|
||||
std::tie(std::ignore, std::ignore, children) = hashesAndChildren_.getHashesAndChildren();
|
||||
iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { children[indexNum].reset(); });
|
||||
@@ -69,7 +69,7 @@ SHAMapInnerNode::getChildIndex(int i) const
|
||||
return hashesAndChildren_.getChildIndex(isBranch_, i);
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapInnerNode::clone(std::uint32_t cowid) const
|
||||
{
|
||||
auto const branchCount = getBranchCount();
|
||||
@@ -78,8 +78,10 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const
|
||||
p->hash_ = hash_;
|
||||
p->isBranch_ = isBranch_;
|
||||
p->fullBelowGen_ = fullBelowGen_;
|
||||
SHAMapHash *cloneHashes = nullptr, *thisHashes = nullptr;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>*cloneChildren = nullptr, *thisChildren = nullptr;
|
||||
SHAMapHash* cloneHashes = nullptr;
|
||||
SHAMapHash* thisHashes = nullptr;
|
||||
SHAMapTreeNodePtr* cloneChildren = nullptr;
|
||||
SHAMapTreeNodePtr* thisChildren = nullptr;
|
||||
// structured bindings can't be captured in c++ 17; use tie instead
|
||||
std::tie(std::ignore, cloneHashes, cloneChildren) =
|
||||
p->hashesAndChildren_.getHashesAndChildren();
|
||||
@@ -118,7 +120,7 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const
|
||||
return p;
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
// A full inner node is serialized as 16 256-bit hashes, back to back:
|
||||
@@ -153,7 +155,7 @@ SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashVali
|
||||
return ret;
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapInnerNode::makeCompressedInner(Slice data)
|
||||
{
|
||||
// A compressed inner node is serialized as a series of 33 byte chunks,
|
||||
@@ -207,7 +209,7 @@ void
|
||||
SHAMapInnerNode::updateHashDeep()
|
||||
{
|
||||
SHAMapHash* hashes = nullptr;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>* children = nullptr;
|
||||
SHAMapTreeNodePtr* children = nullptr;
|
||||
// structured bindings can't be captured in c++ 17; use tie instead
|
||||
std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren();
|
||||
iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
|
||||
@@ -265,7 +267,7 @@ SHAMapInnerNode::getString(SHAMapNodeID const& id) const
|
||||
|
||||
// We are modifying an inner node
|
||||
void
|
||||
SHAMapInnerNode::setChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> child)
|
||||
SHAMapInnerNode::setChild(int m, SHAMapTreeNodePtr child)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::setChild : valid branch input");
|
||||
@@ -307,7 +309,7 @@ SHAMapInnerNode::setChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> child)
|
||||
|
||||
// finished modifying, now make shareable
|
||||
void
|
||||
SHAMapInnerNode::shareChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> const& child)
|
||||
SHAMapInnerNode::shareChild(int m, SHAMapTreeNodePtr const& child)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::shareChild : valid branch input");
|
||||
@@ -337,7 +339,7 @@ SHAMapInnerNode::getChildPointer(int branch)
|
||||
return hashesAndChildren_.getChildren()[index].get();
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapInnerNode::getChild(int branch)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -365,8 +367,8 @@ SHAMapInnerNode::getChildHash(int m) const
|
||||
return kZeroShaMapHash;
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapInnerNode::canonicalizeChild(int branch, intr_ptr::SharedPtr<SHAMapTreeNode> node)
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapInnerNode::canonicalizeChild(int branch, SHAMapTreeNodePtr node)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
branch >= 0 && branch < kBranchFactor,
|
||||
|
||||
@@ -66,7 +66,7 @@ SHAMap::visitNodes(std::function<bool(SHAMapTreeNode&)> const& function) const
|
||||
{
|
||||
if (!node->isEmptyBranch(pos))
|
||||
{
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> const child = descendNoStore(*node, pos);
|
||||
SHAMapTreeNodePtr const child = descendNoStore(*node, pos);
|
||||
if (!function(*child))
|
||||
return;
|
||||
|
||||
@@ -204,8 +204,7 @@ SHAMap::gmnProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se)
|
||||
branch,
|
||||
mn.filter,
|
||||
pending,
|
||||
[node, nodeID, branch, &mn](
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> found, SHAMapHash const&) {
|
||||
[node, nodeID, branch, &mn](SHAMapTreeNodePtr found, SHAMapHash const&) {
|
||||
// a read completed asynchronously
|
||||
std::unique_lock<std::mutex> const lock{mn.deferLock};
|
||||
mn.finishedReads.emplace_back(node, nodeID, branch, std::move(found));
|
||||
@@ -266,8 +265,7 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn)
|
||||
int complete = 0;
|
||||
while (complete != mn.deferred)
|
||||
{
|
||||
std::tuple<SHAMapInnerNode*, SHAMapNodeID, int, intr_ptr::SharedPtr<SHAMapTreeNode>>
|
||||
deferredNode;
|
||||
std::tuple<SHAMapInnerNode*, SHAMapNodeID, int, SHAMapTreeNodePtr> deferredNode;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{mn.deferLock};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
if (data.size() < kMinShaMapItemBytes)
|
||||
@@ -43,7 +43,7 @@ SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashVal
|
||||
return intr_ptr::makeShared<SHAMapTxLeafNode>(std::move(item), 0);
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
Serializer s(data.data(), data.size());
|
||||
@@ -83,7 +83,7 @@ SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool
|
||||
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(std::move(item), 0);
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
Serializer s(data.data(), data.size());
|
||||
@@ -124,7 +124,7 @@ SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashVa
|
||||
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(std::move(item), 0);
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapTreeNode::makeFromWire(Slice rawNode)
|
||||
{
|
||||
if (rawNode.empty())
|
||||
@@ -155,7 +155,7 @@ SHAMapTreeNode::makeFromWire(Slice rawNode)
|
||||
Throw<std::runtime_error>("wire: Unknown type (" + std::to_string(type) + ")");
|
||||
}
|
||||
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
|
||||
{
|
||||
if (rawNode.size() < 4)
|
||||
|
||||
Reference in New Issue
Block a user