This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git

commit 48fe6020d7bf6009118454bf130e1b95449d0b91
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Sep 15 17:24:18 2026 +0800

    branch-4.1: [fix](cloud) Decode transaction IDs safely through Versionstamp 
#67641 (#67954)
    
    Cherry-picked from #67641
    
    Co-authored-by: Gavin Chou <[email protected]>
---
 cloud/src/meta-service/doris_txn.cpp               |  34 ++--
 .../src/meta-service/meta_service_tablet_stats.cpp |   1 +
 cloud/src/meta-store/versionstamp.h                |  14 +-
 cloud/test/doris_txn_test.cpp                      | 176 +++++++++++++++++++++
 cloud/test/versionstamp_test.cpp                   |  23 +++
 5 files changed, 219 insertions(+), 29 deletions(-)

diff --git a/cloud/src/meta-service/doris_txn.cpp 
b/cloud/src/meta-service/doris_txn.cpp
index eb01dd4d874..5b8b2665a79 100644
--- a/cloud/src/meta-service/doris_txn.cpp
+++ b/cloud/src/meta-service/doris_txn.cpp
@@ -17,34 +17,24 @@
 
 #include "doris_txn.h"
 
+#include <array>
 #include <bit>
+#include <cstring>
+
+#include "meta-store/versionstamp.h"
 
 namespace doris::cloud {
 
 int get_txn_id_from_fdb_ts(std::string_view fdb_vts, int64_t* txn_id) {
     if (fdb_vts.size() != 10) return 1; // Malformed version timestamp
 
-    // fdb version timestamp is big-endian
-    //           MSB               LSB
-    //           0000000000000000 0000
-    //           ts               seq
-    // byte addr 0 1 2 3 4 5 6 7  8 9
-    int64_t ver = *reinterpret_cast<const int64_t*>(fdb_vts.data());
-
-    // TODO(gavin): implementation for big-endian or make it endian-independent
-    static_assert(std::endian::native == std::endian::little); // Since c++20
-    // Convert big endian to little endian
-    static auto to_little = [](int64_t v) {
-        v = ((v & 0xffffffff00000000) >> 32) | ((v & 0x00000000ffffffff) << 
32);
-        v = ((v & 0xffff0000ffff0000) >> 16) | ((v & 0x0000ffff0000ffff) << 
16);
-        v = ((v & 0xff00ff00ff00ff00) >> 8) | ((v & 0x00ff00ff00ff00ff) << 8);
-        return v;
-    };
-    ver = to_little(ver);
-
-    int64_t seq = *reinterpret_cast<const int64_t*>(fdb_vts.data() + 2);
-    seq = to_little(seq);
-    seq &= 0x000000000000ffff; // Strip off non-seq part
+    static_assert(std::endian::native == std::endian::little);
+    // Copy the possibly unaligned input before decoding its big-endian fields.
+    std::array<uint8_t, 10> bytes;
+    std::memcpy(bytes.data(), fdb_vts.data(), bytes.size());
+    const Versionstamp versionstamp(bytes);
+    uint64_t ver = versionstamp.version();
+    uint16_t seq = versionstamp.order();
 
     // CAUTION: DO NOT EVER TOUCH IT!!! UNLESS YOU ARE PREPARED FOR THE DOOM!!!
     // CAUTION: DO NOT EVER TOUCH IT!!! UNLESS YOU ARE PREPARED FOR THE DOOM!!!
@@ -61,7 +51,7 @@ int get_txn_id_from_fdb_ts(std::string_view fdb_vts, int64_t* 
txn_id) {
     ver <<= SEQ_RETAIN_BITS;
     ver |= seq;
 
-    *txn_id = ver;
+    *txn_id = static_cast<int64_t>(ver);
     return 0;
 }
 
diff --git a/cloud/src/meta-service/meta_service_tablet_stats.cpp 
b/cloud/src/meta-service/meta_service_tablet_stats.cpp
index af8b7051d54..b8260dd2639 100644
--- a/cloud/src/meta-service/meta_service_tablet_stats.cpp
+++ b/cloud/src/meta-service/meta_service_tablet_stats.cpp
@@ -17,6 +17,7 @@
 
 #include "meta-service/meta_service_tablet_stats.h"
 
+#include <byteswap.h>
 #include <fmt/core.h>
 #include <fmt/format.h>
 #include <gen_cpp/cloud.pb.h>
diff --git a/cloud/src/meta-store/versionstamp.h 
b/cloud/src/meta-store/versionstamp.h
index 0fe716e5092..a4276744ed3 100644
--- a/cloud/src/meta-store/versionstamp.h
+++ b/cloud/src/meta-store/versionstamp.h
@@ -17,11 +17,10 @@
 
 #pragma once
 
-#include <byteswap.h>
-#include <fmt/format.h>
-
+#include <algorithm>
 #include <array>
 #include <bit>
+#include <compare>
 #include <cstddef>
 #include <cstdint>
 #include <limits>
@@ -143,10 +142,11 @@ public:
     }
 
     std::string to_string() const {
-        std::string result;
-        result.reserve(21); // 10 bytes * 2 hex digits + 1 for null terminator
-        for (const auto& byte : data_) {
-            result += fmt::format("{:02x}", byte);
+        constexpr char hex_digits[] = "0123456789abcdef";
+        std::string result(data_.size() * 2, '0');
+        for (size_t i = 0; i < data_.size(); ++i) {
+            result[2 * i] = hex_digits[data_[i] >> 4];
+            result[2 * i + 1] = hex_digits[data_[i] & 0x0f];
         }
         return result;
     }
diff --git a/cloud/test/doris_txn_test.cpp b/cloud/test/doris_txn_test.cpp
index af386763e50..ad048b31eaa 100644
--- a/cloud/test/doris_txn_test.cpp
+++ b/cloud/test/doris_txn_test.cpp
@@ -21,10 +21,129 @@
 #include <brpc/controller.h>
 #include <gtest/gtest.h>
 
+#include <array>
+#include <bit>
+#include <cstring>
+#include <utility>
+
 #include "common/config.h"
 #include "common/util.h"
 #include "meta-service/meta_service.h"
 #include "meta-store/txn_kv.h"
+#include "meta-store/versionstamp.h"
+
+namespace {
+
+#if defined(__x86_64__) || defined(__aarch64__)
+// Preserve the original, unsafe load solely as a historical output oracle on
+// hosts that support unaligned integer loads. Suppress only its known 
alignment
+// violation, not instrumentation of the memcpy reference or production 
decoder.
+// Matching its results does not establish that the old code was well-defined.
+__attribute__((noinline, no_sanitize("alignment"))) int64_t 
legacy_aliased_load(const char* data) {
+    return *reinterpret_cast<const int64_t*>(data);
+}
+#endif
+
+int64_t legacy_memcpy_load(const char* data) {
+    int64_t value;
+    std::memcpy(&value, data, sizeof(value));
+    return value;
+}
+
+// Freeze the pre-Versionstamp algorithm, including the overlapping eight-byte
+// sequence read and its signed intermediate values. Do not use Versionstamp or
+// its byte-swap helpers here: this must remain an independent regression 
oracle.
+template <int64_t (*load)(const char*)>
+int legacy_get_txn_id_from_fdb_ts(std::string_view fdb_vts, int64_t* txn_id) {
+    if (fdb_vts.size() != 10) {
+        return 1;
+    }
+    static_assert(std::endian::native == std::endian::little);
+    auto to_little = [](int64_t v) {
+        v = ((v & 0xffffffff00000000) >> 32) | ((v & 0x00000000ffffffff) << 
32);
+        v = ((v & 0xffff0000ffff0000) >> 16) | ((v & 0x0000ffff0000ffff) << 
16);
+        v = ((v & 0xff00ff00ff00ff00) >> 8) | ((v & 0x00ff00ff00ff00ff) << 8);
+        return v;
+    };
+    int64_t ver = to_little(load(fdb_vts.data()));
+    int64_t seq = to_little(load(fdb_vts.data() + 2));
+    seq &= 0xffff;
+    static constexpr int SEQ_RETAIN_BITS = 10;
+    if (seq >= (1L << SEQ_RETAIN_BITS)) {
+        return 2;
+    }
+    seq &= ((1L << SEQ_RETAIN_BITS) - 1L);
+    ver <<= SEQ_RETAIN_BITS;
+    ver |= seq;
+    *txn_id = ver;
+    return 0;
+}
+
+using TxnIdDecoder = int (*)(std::string_view, int64_t*);
+
+void check_decoded_txn_id(TxnIdDecoder legacy_decode, std::string_view input, 
int expected_ret) {
+    int64_t expected_txn_id = -1;
+    int64_t actual_txn_id = -1;
+    ASSERT_EQ(legacy_decode(input, &expected_txn_id), expected_ret);
+    ASSERT_EQ(doris::cloud::get_txn_id_from_fdb_ts(input, &actual_txn_id), 
expected_ret);
+    ASSERT_EQ(actual_txn_id, expected_txn_id);
+    if (expected_ret != 0) {
+        ASSERT_EQ(actual_txn_id, -1);
+    }
+}
+
+void check_legacy_txn_id_compatibility(TxnIdDecoder legacy_decode) {
+    // Include the positive txn_id limit, the sign transition, and discarded 
high
+    // version bits. These latter cases preserve old behavior, not uniqueness.
+    constexpr std::array<uint64_t, 8> versions = {0,
+                                                  1,
+                                                  0x00000182a5ed173f,
+                                                  0x001f82a5ed173f80,
+                                                  0x001fffffffffffff,
+                                                  0x0020000000000000,
+                                                  0x0040000000000000,
+                                                  0x7fffffffffffffff};
+    alignas(int64_t) std::array<char, 10 + alignof(int64_t) - 1> buffer {};
+    for (uint64_t ver : versions) {
+        SCOPED_TRACE(ver);
+        std::array<uint8_t, 10> bytes {};
+        for (size_t i = 0; i < 8; ++i) {
+            bytes[i] = static_cast<uint8_t>(ver >> (8 * (7 - i)));
+        }
+        for (uint32_t seq = 0; seq <= 0xffff; ++seq) {
+            bytes[8] = static_cast<uint8_t>(seq >> 8);
+            bytes[9] = static_cast<uint8_t>(seq);
+            // Also check the complete fields: txn_id packing discards version
+            // bits and rejects most orders, so output equality alone misses 
them.
+            const doris::cloud::Versionstamp versionstamp(bytes);
+            ASSERT_EQ(std::make_pair(versionstamp.version(), 
versionstamp.order()),
+                      std::make_pair(ver, static_cast<uint16_t>(seq)));
+            for (size_t offset = 0; offset < alignof(int64_t); ++offset) {
+                std::memcpy(buffer.data() + offset, bytes.data(), 
bytes.size());
+                const std::string_view input(buffer.data() + offset, 
bytes.size());
+                ASSERT_NO_FATAL_FAILURE(
+                        check_decoded_txn_id(legacy_decode, input, seq < 1024 
? 0 : 2))
+                        << "seq=" << seq << " offset=" << offset;
+            }
+        }
+    }
+}
+
+void check_invalid_txn_id_inputs(TxnIdDecoder legacy_decode) {
+    // Check short/long inputs and that failures leave the output untouched.
+    std::array<char, 17> buffer {};
+    for (size_t size = 0; size <= buffer.size(); ++size) {
+        if (size == 10) {
+            continue;
+        }
+        SCOPED_TRACE(size);
+        const std::string_view input(buffer.data(), size);
+        ASSERT_NO_FATAL_FAILURE(check_decoded_txn_id(legacy_decode, input, 1));
+    }
+    check_decoded_txn_id(legacy_decode, {}, 1);
+}
+
+} // namespace
 
 int main(int argc, char** argv) {
     doris::cloud::config::init(nullptr, true);
@@ -98,3 +217,60 @@ TEST(TxnIdConvert, TxnIdTest) {
         ASSERT_EQ(ret, 1);
     }
 }
+
+TEST(TxnIdConvert, UnalignedVersionstamp) {
+    // Cover every input alignment, including odd addresses.
+    // The payload has high bits set in both version bytes and sequence bytes.
+    constexpr std::array<unsigned char, 10> versionstamp = {0x00, 0x1f, 0x82, 
0xa5, 0xed,
+                                                            0x17, 0x3f, 0x80, 
0x03, 0xff};
+    constexpr int64_t expected_txn_id = 0x7e0a97b45cfe03ff;
+    alignas(int64_t) std::array<char, 10 + alignof(int64_t) - 1> buffer {};
+    for (size_t offset = 0; offset < alignof(int64_t); ++offset) {
+        SCOPED_TRACE(offset);
+        std::memcpy(buffer.data() + offset, versionstamp.data(), 
versionstamp.size());
+        int64_t txn_id = -1;
+        ASSERT_EQ(doris::cloud::get_txn_id_from_fdb_ts(
+                          std::string_view(buffer.data() + offset, 
versionstamp.size()), &txn_id),
+                  0);
+        EXPECT_EQ(txn_id, expected_txn_id);
+    }
+}
+
+TEST(TxnIdConvert, SequenceValues) {
+    // Exercise all two-byte sequences at an odd address. In particular, 0x00ff
+    // must remain positive after decoding, and 0x0400 and above must fail.
+    alignas(int64_t) std::array<unsigned char, 11> buffer = {0,    0x00, 0x00, 
0x01, 0x82, 0xa5,
+                                                             0xed, 0x17, 0x3f, 
0,    0};
+    const std::string_view versionstamp(reinterpret_cast<const 
char*>(buffer.data() + 1), 10);
+    constexpr int64_t base_txn_id = 0x00060a97b45cfc00;
+    for (uint32_t seq = 0; seq <= 0xffff; ++seq) {
+        SCOPED_TRACE(seq);
+        buffer[9] = static_cast<unsigned char>(seq >> 8);
+        buffer[10] = static_cast<unsigned char>(seq);
+        int64_t txn_id = -1;
+        const int ret = doris::cloud::get_txn_id_from_fdb_ts(versionstamp, 
&txn_id);
+        if (seq < 1024) {
+            ASSERT_EQ(ret, 0);
+            EXPECT_EQ(txn_id, base_txn_id + seq);
+        } else {
+            ASSERT_EQ(ret, 2);
+            EXPECT_EQ(txn_id, -1);
+        }
+    }
+}
+
+TEST(TxnIdConvert, LegacyMemcpyCompatibility) {
+    ASSERT_NO_FATAL_FAILURE(
+            
check_legacy_txn_id_compatibility(legacy_get_txn_id_from_fdb_ts<legacy_memcpy_load>));
+    
check_invalid_txn_id_inputs(legacy_get_txn_id_from_fdb_ts<legacy_memcpy_load>);
+}
+
+TEST(TxnIdConvert, LegacyReinterpretCastCompatibility) {
+#if defined(__x86_64__) || defined(__aarch64__)
+    ASSERT_NO_FATAL_FAILURE(
+            
check_legacy_txn_id_compatibility(legacy_get_txn_id_from_fdb_ts<legacy_aliased_load>));
+    
check_invalid_txn_id_inputs(legacy_get_txn_id_from_fdb_ts<legacy_aliased_load>);
+#else
+    GTEST_SKIP() << "The original decoder requires a host that supports 
unaligned integer loads";
+#endif
+}
diff --git a/cloud/test/versionstamp_test.cpp b/cloud/test/versionstamp_test.cpp
index 5ca138506c1..9dac078f08f 100644
--- a/cloud/test/versionstamp_test.cpp
+++ b/cloud/test/versionstamp_test.cpp
@@ -19,8 +19,10 @@
 
 #include <bthread/bthread.h>
 #include <bthread/countdown_event.h>
+#include <fmt/format.h>
 #include <gtest/gtest.h>
 
+#include <array>
 #include <cstdint>
 #include <cstring>
 #include <iostream>
@@ -50,6 +52,27 @@ TEST(VersionstampTest, ByteSwap) {
     EXPECT_EQ(swapped16, 0x0201);
 }
 
+TEST(VersionstampTest, ToString) {
+    using doris::cloud::Versionstamp;
+
+    EXPECT_EQ(Versionstamp::min().to_string(), "00000000000000000000");
+    EXPECT_EQ(Versionstamp::max().to_string(), "ffffffffffffffffffff");
+    const Versionstamp versionstamp(0x0001020304050607, 0x08ff);
+    EXPECT_EQ(versionstamp.to_string(), "000102030405060708ff");
+
+    // Verify every byte at every position against an independent formatter.
+    for (size_t position = 0; position < 10; ++position) {
+        for (unsigned int byte = 0; byte <= 0xff; ++byte) {
+            std::array<uint8_t, 10> bytes {};
+            bytes[position] = static_cast<uint8_t>(byte);
+            const auto text = Versionstamp(bytes).to_string();
+            std::string expected(20, '0');
+            expected.replace(position * 2, 2, fmt::format("{:02x}", byte));
+            EXPECT_EQ(text, expected) << "position=" << position << " byte=" 
<< byte;
+        }
+    }
+}
+
 TEST(VersionstampTest, Usage) {
     using namespace doris::cloud;
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to