Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions CMake/Findlz4.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Custom Findlz4.cmake that respects pre-existing lz4::lz4 target
# This prevents RocksDB's Findlz4.cmake from running find_library
# which finds NuGet packages with malformed paths on Windows CI

# If lz4::lz4 target already exists, just set found and return
if(TARGET lz4::lz4)
set(lz4_FOUND TRUE)
# Get properties from existing target for compatibility
get_target_property(lz4_LIBRARIES lz4::lz4 IMPORTED_LOCATION)
get_target_property(lz4_INCLUDE_DIRS lz4::lz4 INTERFACE_INCLUDE_DIRECTORIES)
return()
endif()

# Skip find_library on Windows - finds NuGet with malformed paths
# On Windows, LZ4 should be provided via cmake args from build script
if(WIN32)
set(lz4_FOUND FALSE)
return()
endif()

# Otherwise, fall back to standard detection (non-Windows only)
find_path(lz4_INCLUDE_DIRS
NAMES lz4.h
HINTS ${lz4_ROOT_DIR}/include)

find_library(lz4_LIBRARIES
NAMES lz4
HINTS ${lz4_ROOT_DIR}/lib)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(lz4 DEFAULT_MSG lz4_LIBRARIES lz4_INCLUDE_DIRS)

mark_as_advanced(
lz4_LIBRARIES
lz4_INCLUDE_DIRS)

if(lz4_FOUND AND NOT (TARGET lz4::lz4))
add_library(lz4::lz4 UNKNOWN IMPORTED GLOBAL)
set_target_properties(lz4::lz4
PROPERTIES
IMPORTED_LOCATION ${lz4_LIBRARIES}
INTERFACE_INCLUDE_DIRECTORIES ${lz4_INCLUDE_DIRS})
endif()
93 changes: 90 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,84 @@ if (TON_USE_ROCKSDB)
set(WITH_TOOLS OFF CACHE BOOL "build with tools")
set(USE_RTTI ON CACHE BOOL "use rtti")
set(FAIL_ON_WARNINGS OFF CACHE BOOL "fail on warnings")

# Detect LZ4 and pass to RocksDB for compression support
if (NOT LZ4_FOUND)
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
pkg_check_modules(LZ4 liblz4)
endif()
if (NOT LZ4_FOUND)
# Try find_library as fallback (skip on Windows to avoid NuGet interference)
if (NOT WIN32)
find_library(LZ4_LIBRARY NAMES lz4 liblz4)
find_path(LZ4_INCLUDE_DIR NAMES lz4.h)
if (LZ4_LIBRARY AND LZ4_INCLUDE_DIR)
set(LZ4_FOUND TRUE)
set(LZ4_LIBRARIES ${LZ4_LIBRARY})
set(LZ4_INCLUDE_DIRS ${LZ4_INCLUDE_DIR})
endif()
endif()
endif()
endif()

# Pass LZ4 configuration to RocksDB (handles both detected and user-provided LZ4)
if (LZ4_FOUND)
message(STATUS "LZ4 found for RocksDB: ${LZ4_LIBRARIES}")
set(WITH_LZ4 ON CACHE BOOL "build with lz4" FORCE)

# Resolve library path on non-Windows platforms only
# - On Windows: Build script provides full path; find_library finds NuGet with malformed paths
# - On Linux: pkg-config may return just "lz4" (library name, not path)
# - On macOS: Build script provides full path
if (NOT WIN32)
if (NOT IS_ABSOLUTE "${LZ4_LIBRARIES}" OR NOT EXISTS "${LZ4_LIBRARIES}")
# LZ4_LIBRARIES is not a valid file path, find the actual library
find_library(LZ4_LIBRARY_PATH NAMES lz4 liblz4
HINTS ${LZ4_LIBRARY_DIRS}
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/lib/aarch64-linux-gnu)
if (LZ4_LIBRARY_PATH)
set(LZ4_LIBRARIES "${LZ4_LIBRARY_PATH}")
message(STATUS "LZ4 library resolved to: ${LZ4_LIBRARIES}")
endif()
endif()
endif()

# Pass library paths to prevent RocksDB from doing its own detection
if (LZ4_INCLUDE_DIRS)
set(LZ4_INCLUDE_DIR "${LZ4_INCLUDE_DIRS}" CACHE PATH "lz4 include dir" FORCE)
endif()
if (LZ4_LIBRARIES)
set(lz4_LIBRARY "${LZ4_LIBRARIES}" CACHE FILEPATH "lz4 library" FORCE)
# Also set uppercase variant for find_package compatibility
set(LZ4_LIBRARY "${LZ4_LIBRARIES}" CACHE FILEPATH "lz4 library" FORCE)
endif()

# Set lowercase variables that RocksDB's Findlz4.cmake expects
set(lz4_FOUND TRUE CACHE BOOL "lz4 found" FORCE)
set(lz4_LIBRARIES "${LZ4_LIBRARIES}" CACHE FILEPATH "lz4 library" FORCE)
set(lz4_INCLUDE_DIRS "${LZ4_INCLUDE_DIRS}" CACHE PATH "lz4 include dir" FORCE)

# Create the lz4::lz4 imported target that RocksDB expects
# GLOBAL makes it visible to find_package calls from subdirectories
if (NOT TARGET lz4::lz4)
add_library(lz4::lz4 UNKNOWN IMPORTED GLOBAL)
set_target_properties(lz4::lz4 PROPERTIES
IMPORTED_LOCATION "${LZ4_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${LZ4_INCLUDE_DIRS}")
endif()
else()
message(WARNING "LZ4 not found - RocksDB will be built without LZ4 compression support")
set(WITH_LZ4 OFF CACHE BOOL "build with lz4" FORCE)
endif()

# Use our custom Findlz4.cmake to prevent RocksDB from finding NuGet package
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake")

# Skip thirdparty.inc on Windows - it overwrites LZ4 paths with NuGet patterns
# TON handles LZ4 detection above, so thirdparty.inc is not needed
set(ROCKSDB_SKIP_THIRDPARTY ON CACHE BOOL "skip thirdparty.inc" FORCE)

message("Add rocksdb")
add_subdirectory(third-party/rocksdb EXCLUDE_FROM_ALL)
# Broken CMake in rocksdb alters properties it has no business changing.
Expand Down Expand Up @@ -330,10 +408,19 @@ if (GCC OR CLANG)
endif()

if (GCC OR CLANG)
if (CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
# For historical reasons, CMake falls back to -O2 optimization level when CMAKE_BUILD_TYPE is
# set to RelWithDebInfo.
# Enable -O3 optimization for all Release and RelWithDebInfo builds
if (CMAKE_BUILD_TYPE MATCHES "Release|RelWithDebInfo")
add_compile_options(-O3)
# Additional optimizations for Release builds
add_compile_options(-funroll-loops) # Unroll loops for better performance
if (CLANG)
add_compile_options(-fvectorize) # Enable auto-vectorization
add_compile_options(-fslp-vectorize) # Enable SLP vectorization
endif()
endif()
# Add -mtune=native for better instruction scheduling (in addition to -march)
if (TON_ARCH STREQUAL "native" AND NOT MSVC)
add_compile_options(-mtune=native)
endif()
endif()

Expand Down
2 changes: 2 additions & 0 deletions adnl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ set(ADNL_HEADERS
adnl-network-manager.hpp
adnl-node.h
adnl-packet.h
adnl-packet-compression.h
adnl-peer-table.h
adnl-peer-table.hpp
adnl-peer.h
Expand All @@ -40,6 +41,7 @@ set(ADNL_SOURCE
adnl-node.cpp
adnl-node-id.cpp
adnl-packet.cpp
adnl-packet-compression.cpp
adnl-peer-table.cpp
adnl-peer.cpp
adnl-query.cpp
Expand Down
6 changes: 5 additions & 1 deletion adnl/adnl-channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "adnl-channel.hpp"
#include "adnl-peer.h"
#include "adnl-peer-table.h"
#include "adnl-packet-compression.h"

#include "td/utils/crypto.h"
#include "crypto/Ed25519.h"
Expand Down Expand Up @@ -85,7 +86,10 @@ AdnlChannelImpl::AdnlChannelImpl(AdnlNodeIdShort local_id, AdnlNodeIdShort peer_
void AdnlChannelImpl::decrypt(td::BufferSlice raw_data, td::Promise<AdnlPacket> promise) {
TRY_RESULT_PROMISE_PREFIX(promise, data, decryptor_->decrypt(raw_data.as_slice()),
"failed to decrypt channel message: ");
TRY_RESULT_PROMISE_PREFIX(promise, tl_packet, fetch_tl_object<ton_api::adnl_packetContents>(std::move(data), true),
// Decompress packet if it was compressed
TRY_RESULT_PROMISE_PREFIX(promise, decompressed_data, maybe_decompress_packet(std::move(data)),
"failed to decompress channel packet: ");
TRY_RESULT_PROMISE_PREFIX(promise, tl_packet, fetch_tl_object<ton_api::adnl_packetContents>(std::move(decompressed_data), true),
"decrypted channel packet contains invalid TL scheme: ");
TRY_RESULT_PROMISE_PREFIX(promise, packet, AdnlPacket::create(std::move(tl_packet)), "received bad packet: ");
if (packet.inited_from_short() && packet.from_short() != peer_id_) {
Expand Down
10 changes: 9 additions & 1 deletion adnl/adnl-local-id.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "td/utils/Random.h"

#include "adnl-local-id.h"
#include "adnl-packet-compression.h"
#include "keys/encryptor.h"
#include "utils.hpp"

Expand Down Expand Up @@ -244,7 +245,14 @@ void AdnlLocalId::decrypt(td::BufferSlice data, td::Promise<AdnlPacket> promise)
}

void AdnlLocalId::decrypt_continue(td::BufferSlice data, td::Promise<AdnlPacket> promise) {
auto R = fetch_tl_object<ton_api::adnl_packetContents>(std::move(data), true);
// Decompress packet if it was compressed
auto decompressed_result = maybe_decompress_packet(std::move(data));
if (decompressed_result.is_error()) {
promise.set_error(decompressed_result.move_as_error_prefix("failed to decompress packet: "));
return;
}

auto R = fetch_tl_object<ton_api::adnl_packetContents>(decompressed_result.move_as_ok(), true);
if (R.is_error()) {
promise.set_error(R.move_as_error());
return;
Expand Down
112 changes: 112 additions & 0 deletions adnl/adnl-packet-compression.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
This file is part of TON Blockchain Library.

TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "adnl-packet-compression.h"
#include "td/utils/config.h"

#if TD_HAVE_LZ4
#include "td/utils/lz4.h"
#include "td/utils/logging.h"
#include <cstring>
#endif

namespace ton {
namespace adnl {

td::BufferSlice maybe_compress_packet(td::BufferSlice data) {
#if TD_HAVE_LZ4
// Don't compress if below threshold
if (data.size() < kCompressionThreshold) {
return data;
}

// Compress the data
auto compressed = td::lz4_compress(data.as_slice());

// Only use compression if it actually reduces size (add header overhead)
if (compressed.size() + kCompressionHeaderSize >= data.size()) {
LOG(DEBUG) << "Compression not beneficial: " << data.size() << " -> "
<< (compressed.size() + kCompressionHeaderSize) << " bytes";
return data;
}

// Create buffer with header + compressed data
td::BufferSlice result(kCompressionHeaderSize + compressed.size());
auto slice = result.as_slice();

// Write magic bytes (little-endian)
std::memcpy(slice.data(), &kCompressionMagic, 4);

// Write uncompressed size (little-endian)
uint32_t uncompressed_size = static_cast<uint32_t>(data.size());
std::memcpy(slice.data() + 4, &uncompressed_size, 4);

// Write compressed data
std::memcpy(slice.data() + kCompressionHeaderSize, compressed.data(), compressed.size());

LOG(DEBUG) << "Compressed packet: " << data.size() << " -> " << result.size()
<< " bytes (" << (100 * result.size() / data.size()) << "%)";

return result;
#else
// LZ4 not available, return uncompressed
return data;
#endif
}

td::Result<td::BufferSlice> maybe_decompress_packet(td::BufferSlice data) {
#if TD_HAVE_LZ4
// Check if data has compression header
if (data.size() < kCompressionHeaderSize) {
return std::move(data); // Too small to be compressed
}

// Check magic bytes
uint32_t magic;
std::memcpy(&magic, data.data(), 4);

if (magic != kCompressionMagic) {
return std::move(data); // Not compressed
}

// Read uncompressed size
uint32_t uncompressed_size;
std::memcpy(&uncompressed_size, data.data() + 4, 4);

// Sanity check: uncompressed size should be reasonable (< 16MB for ADNL packets)
constexpr uint32_t kMaxUncompressedSize = 16 * 1024 * 1024;
if (uncompressed_size == 0 || uncompressed_size > kMaxUncompressedSize) {
return td::Status::Error("Invalid uncompressed size in packet header");
}

// Extract compressed data (skip header)
auto compressed_slice = data.as_slice();
compressed_slice.remove_prefix(kCompressionHeaderSize);

// Decompress
TRY_RESULT(decompressed, td::lz4_decompress(compressed_slice, uncompressed_size));

LOG(DEBUG) << "Decompressed packet: " << data.size() << " -> " << decompressed.size() << " bytes";

return std::move(decompressed);
#else
// LZ4 not available, return as-is
return std::move(data);
#endif
}

} // namespace adnl
} // namespace ton
52 changes: 52 additions & 0 deletions adnl/adnl-packet-compression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
This file is part of TON Blockchain Library.

TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once

#include "td/utils/buffer.h"
#include "td/utils/Status.h"

namespace ton {
namespace adnl {

// Compression threshold: compress packets larger than 4KB
constexpr size_t kCompressionThreshold = 4096;

// Magic bytes to identify compressed packets: "ADLZ" (ADNL LZ4)
constexpr uint32_t kCompressionMagic = 0x415D4C5A; // "ADLZ" in ASCII

// Header size: 4 bytes magic + 4 bytes uncompressed size
constexpr size_t kCompressionHeaderSize = 8;

/**
* Compresses packet data if it exceeds the compression threshold.
* Format: [4 bytes magic][4 bytes uncompressed_size][compressed data]
*
* @param data The packet data to potentially compress
* @return Compressed data if size > threshold, otherwise original data
*/
td::BufferSlice maybe_compress_packet(td::BufferSlice data);

/**
* Decompresses packet data if it has the compression magic header.
*
* @param data The packet data to potentially decompress
* @return Decompressed data if compressed, otherwise original data
*/
td::Result<td::BufferSlice> maybe_decompress_packet(td::BufferSlice data);

} // namespace adnl
} // namespace ton
5 changes: 5 additions & 0 deletions adnl/adnl-peer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "adnl-local-id.h"

#include "utils.hpp"
#include "adnl-packet-compression.h"

#include "td/actor/PromiseFuture.h"
#include "td/utils/base64.h"
Expand Down Expand Up @@ -421,6 +422,10 @@ void AdnlPeerPairImpl::send_packet_continue(AdnlPacket packet, td::actor::ActorI
}
packet.run_basic_checks().ensure();
auto B = serialize_tl_object(packet.tl(), true);

// Apply LZ4 compression for packets > 4KB
B = maybe_compress_packet(std::move(B));

if (via_channel) {
if (channel_ready_) {
add_packet_stats(B.size(), /* in = */ false, /* channel = */ true);
Expand Down
Loading