Use node-specific size for serializer

This commit is contained in:
Bart
2026-06-08 20:30:45 -04:00
parent 231a7750f3
commit 8690820060
8 changed files with 47 additions and 6 deletions

View File

@@ -45,6 +45,12 @@ public:
hash_ = SHAMapHash{sha512Half(HashPrefix::LeafNode, item_->slice(), item_->key())};
}
std::size_t
sizeForWire() const final
{
return item_->size() + uint256::kBytes + sizeof(std::uint8_t);
}
void
serializeForWire(Serializer& s) const final
{

View File

@@ -17,6 +17,12 @@ public:
/** Each inner node has 16 children (the 'radix tree' part of the map) */
static constexpr unsigned int kBranchFactor = 16;
/** Branch count below which compressed wire format is used instead of full.
Compressed format encodes only populated branches, costing 33 bytes each
plus a 1-byte type tag. Full format always emits all 16 hashes (513
bytes). At this threshold, compressed is still smaller than full. */
static constexpr unsigned int kCompressedThreshold = 12;
private:
/** Opaque type that contains the `hashes` array (array of type
`SHAMapHash`) and the `children` array (array of type
@@ -149,6 +155,9 @@ public:
void
updateHashDeep();
std::size_t
sizeForWire() const override;
void
serializeForWire(Serializer&) const override;

View File

@@ -36,6 +36,9 @@ public:
return false;
}
std::size_t
sizeForWire() const override = 0;
void
invariants(bool isRoot = false) const final;

View File

@@ -142,6 +142,10 @@ public:
virtual bool
isInner() const = 0;
/** Returns the exact number of bytes that serializeForWire will emit. */
virtual std::size_t
sizeForWire() const = 0;
/** Serialize the node in a format appropriate for sending over the wire */
virtual void
serializeForWire(Serializer&) const = 0;

View File

@@ -44,6 +44,12 @@ public:
hash_ = SHAMapHash{sha512Half(HashPrefix::TransactionId, item_->slice())};
}
std::size_t
sizeForWire() const final
{
return item_->size() + sizeof(std::uint8_t);
}
void
serializeForWire(Serializer& s) const final
{

View File

@@ -45,6 +45,12 @@ public:
hash_ = SHAMapHash{sha512Half(HashPrefix::TxNode, item_->slice(), item_->key())};
}
std::size_t
sizeForWire() const final
{
return item_->size() + uint256::kBytes + sizeof(std::uint8_t);
}
void
serializeForWire(Serializer& s) const final
{

View File

@@ -219,13 +219,22 @@ SHAMapInnerNode::updateHashDeep()
updateHash();
}
std::size_t
SHAMapInnerNode::sizeForWire() const
{
auto const n = getBranchCount();
if (n < kCompressedThreshold)
return n * (uint256::kBytes + sizeof(std::uint8_t)) + sizeof(std::uint8_t);
return kBranchFactor * uint256::kBytes + sizeof(std::uint8_t);
}
void
SHAMapInnerNode::serializeForWire(Serializer& s) const
{
XRPL_ASSERT(!isEmpty(), "xrpl::SHAMapInnerNode::serializeForWire : is non-empty");
// If the node is sparse, then only send non-empty branches:
if (getBranchCount() < 12)
if (getBranchCount() < kCompressedThreshold)
{
// compressed node
auto hashes = hashesAndChildren_.getHashes();

View File

@@ -454,9 +454,7 @@ SHAMap::getNodeFat(
std::tie(node, nodeID, depth) = stack.top();
stack.pop();
// Use a fresh Serializer per node and move its buffer into `data` rather than copying it
// via Serializer::getData(): the move is O(1) whereas the copy was O(node size).
Serializer s;
Serializer s(node->sizeForWire());
node->serializeForWire(s);
data.emplace_back(nodeID, std::move(s.modData()));
@@ -486,7 +484,7 @@ SHAMap::getNodeFat(
else if (childNode->isInner() || fatLeaves)
{
// Just include this node
Serializer cs;
Serializer cs(childNode->sizeForWire());
childNode->serializeForWire(cs);
data.emplace_back(childID, std::move(cs.modData()));
}
@@ -793,7 +791,7 @@ SHAMap::getProofPath(uint256 const& key) const
path.reserve(stack.size());
while (!stack.empty())
{
Serializer s;
Serializer s(stack.top().first->sizeForWire());
stack.top().first->serializeForWire(s);
path.emplace_back(std::move(s.modData()));
stack.pop();