Fix MPT STIssue serialization uses host-endian sequence bytes

This commit is contained in:
Gregory Tsipenyuk
2026-06-08 11:02:31 -04:00
parent 79f4ddc4a6
commit 2f015e9c65
2 changed files with 63 additions and 0 deletions

View File

@@ -11,6 +11,8 @@
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <boost/endian/conversion.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
@@ -45,6 +47,9 @@ STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
{
MPTID mptID;
std::uint32_t sequence = sit.get32();
// Preserve the existing LE memcpy result on every host endian.
// Wire 04 03 02 01 becomes MPTID bytes 01 02 03 04 on both.
sequence = boost::endian::native_to_little(sequence);
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
memcpy(mptID.data(), &sequence, sizeof(sequence));
memcpy(
@@ -100,6 +105,9 @@ STIssue::add(Serializer& s) const
s.addBitString(noAccount());
std::uint32_t sequence = 0;
memcpy(&sequence, issue.getMptID().data(), sizeof(sequence));
// Preserve the existing LE ledger bytes on every host endian.
// MPTID bytes 01 02 03 04 become wire bytes 04 03 02 01 on both.
sequence = boost::endian::native_to_little(sequence);
s.add32(sequence);
});
}

View File

@@ -3,9 +3,12 @@
#include <test/jtx/amount.h> // IWYU pragma: keep
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STIssue.h>
#include <xrpl/protocol/Serializer.h>
@@ -137,12 +140,64 @@ public:
"000000000000000000000000000000000000000000000002");
}
void
testMPTSerialization()
{
testcase("MPT serialization");
using namespace jtx;
Account const alice{"alice"};
struct Vector
{
std::uint32_t sequence;
std::uint32_t legacySequence;
};
// 0x01020304 pins canonical MPTID bytes 01 02 03 04 and
// preserved STIssue wire bytes 04 03 02 01 on BE and LE.
Vector const vectors[] = {
{.sequence = 0x00000001, .legacySequence = 0x01000000},
{.sequence = 0x01020304, .legacySequence = 0x04030201},
{.sequence = 0xa1b2c3d4, .legacySequence = 0xd4c3b2a1},
};
for (auto const& vector : vectors)
{
MPTID const mptID = makeMptID(vector.sequence, alice);
MPTIssue const issue{mptID};
STIssue const stIssue(sfAsset, Asset{issue});
Serializer actual;
stIssue.add(actual);
// STIssue preserves the existing little-endian validator ledger bytes.
Serializer expected;
expected.addBitString(alice.id());
expected.addBitString(noAccount());
expected.add32(vector.legacySequence);
BEAST_EXPECTS(strHex(actual) == strHex(expected), strHex(actual));
// Decoding the preserved wire format must recover the canonical MPTID.
SerialIter iter(expected.slice());
STIssue const decoded(iter, sfAsset);
BEAST_EXPECT(decoded.holds<MPTIssue>());
BEAST_EXPECT(decoded.value().get<MPTIssue>().getMptID() == mptID);
// A decoded ledger value must serialize back to the same bytes.
Serializer roundTrip;
decoded.add(roundTrip);
BEAST_EXPECTS(strHex(roundTrip) == strHex(expected), strHex(roundTrip));
}
}
void
run() override
{
// compliments other unit tests to ensure complete coverage
testConstructor();
testCompare();
testMPTSerialization();
}
};