diff --git a/src/libxrpl/protocol/STIssue.cpp b/src/libxrpl/protocol/STIssue.cpp index c9b8109e32..bbf32f442b 100644 --- a/src/libxrpl/protocol/STIssue.cpp +++ b/src/libxrpl/protocol/STIssue.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include #include @@ -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); }); } diff --git a/src/test/protocol/STIssue_test.cpp b/src/test/protocol/STIssue_test.cpp index 1d6d750355..85a7351f0e 100644 --- a/src/test/protocol/STIssue_test.cpp +++ b/src/test/protocol/STIssue_test.cpp @@ -3,9 +3,12 @@ #include // IWYU pragma: keep #include +#include #include #include +#include #include +#include #include #include #include @@ -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()); + BEAST_EXPECT(decoded.value().get().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(); } };