mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 01:07:57 +00:00
Compare commits
35 Commits
tapanito/f
...
Bronek/upg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3d5be39ed | ||
|
|
482315fc42 | ||
|
|
4e3810e0b2 | ||
|
|
ed2fa19997 | ||
|
|
ad184d9ef8 | ||
|
|
597de4dbb0 | ||
|
|
804614e2a0 | ||
|
|
f8a9886ece | ||
|
|
de54c7ead3 | ||
|
|
3a94f22da7 | ||
|
|
9169b58c08 | ||
|
|
3c3bc8fa21 | ||
|
|
ab857068ed | ||
|
|
7f60624ccd | ||
|
|
759d6a3f2b | ||
|
|
6cb1ae6666 | ||
|
|
47875cee52 | ||
|
|
8b50263c2b | ||
|
|
64233c9f47 | ||
|
|
2ea247bb9c | ||
|
|
bd8242d805 | ||
|
|
1043388407 | ||
|
|
6ba0ec479b | ||
|
|
b527194743 | ||
|
|
615e380fab | ||
|
|
901654a966 | ||
|
|
8ad9f4b768 | ||
|
|
dd68fe0e2c | ||
|
|
95a8d84f1f | ||
|
|
5a2004332b | ||
|
|
9a56a5b788 | ||
|
|
605c8bd377 | ||
|
|
900131d09f | ||
|
|
01bdb87b39 | ||
|
|
274a7303ff |
3
.github/actions/dependencies/action.yml
vendored
3
.github/actions/dependencies/action.yml
vendored
@@ -11,6 +11,7 @@ runs:
|
||||
run: |
|
||||
conan export --version 1.1.10 external/snappy
|
||||
conan export --version 4.0.3 external/soci
|
||||
conan export --version 6.30.1 external/protobuf
|
||||
- name: add Ripple Conan remote
|
||||
if: env.CONAN_URL != ''
|
||||
shell: bash
|
||||
@@ -22,7 +23,7 @@ runs:
|
||||
conan remote add --index 0 ripple "${CONAN_URL}"
|
||||
echo "Added conan remote ripple at ${CONAN_URL}"
|
||||
- name: try to authenticate to Ripple Conan remote
|
||||
if: env.CONAN_LOGIN_USERNAME_RIPPLE != '' && env.CONAN_PASSWORD_RIPPLE != ''
|
||||
if: env.CONAN_URL != '' && env.CONAN_LOGIN_USERNAME_RIPPLE != '' && env.CONAN_PASSWORD_RIPPLE != ''
|
||||
id: remote
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
3
.github/workflows/macos.yml
vendored
3
.github/workflows/macos.yml
vendored
@@ -18,7 +18,7 @@ concurrency:
|
||||
# This part of Conan configuration is specific to this workflow only; we do not want
|
||||
# to pollute conan/profiles directory with settings which might not work for others
|
||||
env:
|
||||
CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||
# CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
||||
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
||||
CONAN_GLOBAL_CONF: |
|
||||
@@ -95,6 +95,7 @@ jobs:
|
||||
run: |
|
||||
conan export --version 1.1.10 external/snappy
|
||||
conan export --version 4.0.3 external/soci
|
||||
conan export --version 6.30.1 external/protobuf
|
||||
- name: add Ripple Conan remote
|
||||
if: env.CONAN_URL != ''
|
||||
shell: bash
|
||||
|
||||
2
.github/workflows/nix.yml
vendored
2
.github/workflows/nix.yml
vendored
@@ -19,7 +19,7 @@ concurrency:
|
||||
# This part of Conan configuration is specific to this workflow only; we do not want
|
||||
# to pollute conan/profiles directory with settings which might not work for others
|
||||
env:
|
||||
CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||
# CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
||||
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
||||
CONAN_GLOBAL_CONF: |
|
||||
|
||||
7
.github/workflows/windows.yml
vendored
7
.github/workflows/windows.yml
vendored
@@ -21,15 +21,13 @@ concurrency:
|
||||
# This part of Conan configuration is specific to this workflow only; we do not want
|
||||
# to pollute conan/profiles directory with settings which might not work for others
|
||||
env:
|
||||
CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||
# CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
||||
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
||||
CONAN_GLOBAL_CONF: |
|
||||
core.download:parallel={{os.cpu_count()}}
|
||||
core.upload:parallel={{os.cpu_count()}}
|
||||
tools.build:jobs=24
|
||||
tools.build:verbosity=verbose
|
||||
tools.compilation:verbosity=verbose
|
||||
tools.build:jobs=6
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -90,6 +88,7 @@ jobs:
|
||||
run: |
|
||||
conan export --version 1.1.10 external/snappy
|
||||
conan export --version 4.0.3 external/soci
|
||||
conan export --version 6.30.1 external/protobuf
|
||||
- name: add Ripple Conan remote
|
||||
if: env.CONAN_URL != ''
|
||||
shell: bash
|
||||
|
||||
8
BUILD.md
8
BUILD.md
@@ -179,6 +179,14 @@ It patches their CMake to correctly import its dependencies.
|
||||
conan export --version 4.0.3 external/soci
|
||||
```
|
||||
|
||||
Export our [Conan recipe for Protobuf](./external/rocksdb).
|
||||
It fixes compilation errors with clang-16 and Visual Studio.
|
||||
|
||||
```
|
||||
# Conan 2.x
|
||||
conan export --version 6.30.1 external/protobuf
|
||||
```
|
||||
|
||||
### Build and Test
|
||||
|
||||
1. Create a build directory and move into it.
|
||||
|
||||
@@ -70,8 +70,8 @@ if (MSVC)
|
||||
-Zc:forScope
|
||||
>
|
||||
# static runtime
|
||||
$<$<CONFIG:Debug>:-MTd>
|
||||
$<$<NOT:$<CONFIG:Debug>>:-MT>
|
||||
$<$<CONFIG:Debug>:-MDd>
|
||||
$<$<NOT:$<CONFIG:Debug>>:-MD>
|
||||
$<$<BOOL:${werr}>:-WX>
|
||||
)
|
||||
target_compile_definitions (common
|
||||
|
||||
@@ -14,7 +14,7 @@ compiler={{compiler}}
|
||||
compiler.version={{ compiler_version }}
|
||||
compiler.cppstd=20
|
||||
{% if os == "Windows" %}
|
||||
compiler.runtime=static
|
||||
compiler.runtime=dynamic
|
||||
{% else %}
|
||||
compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}}
|
||||
{% endif %}
|
||||
@@ -29,6 +29,10 @@ tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw']
|
||||
{% if compiler == "gcc" and compiler_version < 13 %}
|
||||
tools.build:cxxflags=['-Wno-restrict']
|
||||
{% endif %}
|
||||
{% if compiler == "msvc" %}
|
||||
tools.build:cxxflags=['-wd5287']
|
||||
tools.build:cflags=['-wd5287']
|
||||
{% endif %}
|
||||
|
||||
[tool_requires]
|
||||
!cmake/*: cmake/[>=3 <4]
|
||||
|
||||
14
conanfile.py
14
conanfile.py
@@ -24,7 +24,7 @@ class Xrpl(ConanFile):
|
||||
}
|
||||
|
||||
requires = [
|
||||
'grpc/1.50.1',
|
||||
'grpc/1.72.0',
|
||||
'libarchive/3.8.1',
|
||||
'nudb/2.0.9',
|
||||
'openssl/1.1.1w',
|
||||
@@ -37,7 +37,7 @@ class Xrpl(ConanFile):
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
'protobuf/3.21.12',
|
||||
'protobuf/6.30.1',
|
||||
]
|
||||
|
||||
default_options = {
|
||||
@@ -98,6 +98,14 @@ class Xrpl(ConanFile):
|
||||
self.version = match.group(1)
|
||||
|
||||
def configure(self):
|
||||
# Disable grpc plugins, we do not use any
|
||||
self.options["grpc"].csharp_plugin = False
|
||||
self.options["grpc"].node_plugin = False
|
||||
self.options["grpc"].objective_c_plugin = False
|
||||
self.options["grpc"].php_plugin = False
|
||||
self.options["grpc"].python_plugin = False
|
||||
self.options["grpc"].ruby_plugin = False
|
||||
self.options["grpc"].otel_plugin = False
|
||||
if self.settings.compiler == 'apple-clang':
|
||||
self.options['boost'].visibility = 'global'
|
||||
|
||||
@@ -107,7 +115,7 @@ class Xrpl(ConanFile):
|
||||
self.requires('boost/1.86.0', force=True, **transitive_headers_opt)
|
||||
self.requires('date/3.0.4', **transitive_headers_opt)
|
||||
self.requires('lz4/1.10.0', force=True)
|
||||
self.requires('protobuf/3.21.12', force=True)
|
||||
self.requires('protobuf/6.30.1', force=True)
|
||||
self.requires('sqlite3/3.49.1', force=True)
|
||||
if self.options.jemalloc:
|
||||
self.requires('jemalloc/5.3.0')
|
||||
|
||||
28
docs/build/environment.md
vendored
28
docs/build/environment.md
vendored
@@ -53,34 +53,6 @@ minimum required (see [BUILD.md][]).
|
||||
clang --version
|
||||
```
|
||||
|
||||
### Install Xcode Specific Version (Optional)
|
||||
|
||||
If you develop other applications using XCode you might be consistently updating to the newest version of Apple Clang.
|
||||
This will likely cause issues building rippled. You may want to install a specific version of Xcode:
|
||||
|
||||
1. **Download Xcode**
|
||||
|
||||
- Visit [Apple Developer Downloads](https://developer.apple.com/download/more/)
|
||||
- Sign in with your Apple Developer account
|
||||
- Search for an Xcode version that includes **Apple Clang (Expected Version)**
|
||||
- Download the `.xip` file
|
||||
|
||||
2. **Install and Configure Xcode**
|
||||
|
||||
```bash
|
||||
# Extract the .xip file and rename for version management
|
||||
# Example: Xcode_16.2.app
|
||||
|
||||
# Move to Applications directory
|
||||
sudo mv Xcode_16.2.app /Applications/
|
||||
|
||||
# Set as default toolchain (persistent)
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
|
||||
# Set as environment variable (temporary)
|
||||
export DEVELOPER_DIR=/Applications/Xcode_16.2.app/Contents/Developer
|
||||
```
|
||||
|
||||
The command line developer tools should include Git too:
|
||||
|
||||
```
|
||||
|
||||
54
external/protobuf/conandata.yml
vendored
Normal file
54
external/protobuf/conandata.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
sources:
|
||||
"6.30.1":
|
||||
url: "https://github.com/protocolbuffers/protobuf/archive/refs/tags/v6.30.1.tar.gz"
|
||||
sha256: "c97cc064278ef2b8c4da66c1f85613642ecbd5a0c4217c0defdf7ad1b3de9fa5"
|
||||
patches:
|
||||
"6.30.1":
|
||||
- patch_file: "patches/protobuf-6.30.1-change-empty-string.patch"
|
||||
patch_description: "Change how we decide which empty string implementation to use"
|
||||
patch_type: "backport"
|
||||
patch_source: "https://github.com/protocolbuffers/protobuf/issues/20645"
|
||||
- patch_file: "patches/protobuf-6.30.1-disable-fixed-string-MSVC.patch"
|
||||
patch_description: "Disable the optimization for fixed_address_empty_string for MSVC"
|
||||
patch_type: "backport"
|
||||
patch_source: "https://github.com/protocolbuffers/protobuf/issues/21957"
|
||||
absl_deps:
|
||||
# reference: https://github.com/protocolbuffers/protobuf/blob/main/cmake/abseil-cpp.cmake
|
||||
"6.30.1":
|
||||
- absl_absl_check
|
||||
- absl_absl_log
|
||||
- absl_algorithm
|
||||
- absl_base
|
||||
- absl_bind_front
|
||||
- absl_bits
|
||||
- absl_btree
|
||||
- absl_cleanup
|
||||
- absl_cord
|
||||
- absl_core_headers
|
||||
- absl_debugging
|
||||
- absl_die_if_null
|
||||
- absl_dynamic_annotations
|
||||
- absl_flags
|
||||
- absl_flat_hash_map
|
||||
- absl_flat_hash_set
|
||||
- absl_function_ref
|
||||
- absl_hash
|
||||
- absl_layout
|
||||
- absl_log_initialize
|
||||
- absl_log_globals
|
||||
- absl_log_severity
|
||||
- absl_memory
|
||||
- absl_node_hash_map
|
||||
- absl_node_hash_set
|
||||
- absl_optional
|
||||
- absl_random_distributions
|
||||
- absl_random_random
|
||||
- absl_span
|
||||
- absl_status
|
||||
- absl_statusor
|
||||
- absl_strings
|
||||
- absl_synchronization
|
||||
- absl_time
|
||||
- absl_type_traits
|
||||
- absl_utility
|
||||
- absl_variant
|
||||
330
external/protobuf/conanfile.py
vendored
Normal file
330
external/protobuf/conanfile.py
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
from conan import ConanFile
|
||||
from conan.errors import ConanInvalidConfiguration
|
||||
from conan.tools.apple import is_apple_os
|
||||
from conan.tools.build import check_min_cppstd
|
||||
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout
|
||||
from conan.tools.files import copy, rename, get, apply_conandata_patches, export_conandata_patches, replace_in_file, rmdir, rm, save
|
||||
from conan.tools.microsoft import check_min_vs, msvc_runtime_flag, is_msvc, is_msvc_static_runtime
|
||||
from conan.tools.scm import Version
|
||||
|
||||
import os
|
||||
|
||||
required_conan_version = ">=1.53"
|
||||
|
||||
|
||||
class ProtobufConan(ConanFile):
|
||||
name = "protobuf"
|
||||
description = "Protocol Buffers - Google's data interchange format"
|
||||
topics = ("protocol-buffers", "protocol-compiler", "serialization", "rpc", "protocol-compiler")
|
||||
url = "https://github.com/conan-io/conan-center-index"
|
||||
homepage = "https://github.com/protocolbuffers/protobuf"
|
||||
license = "BSD-3-Clause"
|
||||
package_type = "library"
|
||||
settings = "os", "arch", "compiler", "build_type"
|
||||
options = {
|
||||
"shared": [True, False],
|
||||
"fPIC": [True, False],
|
||||
"with_zlib": [True, False],
|
||||
"with_rtti": [True, False],
|
||||
"lite": [True, False],
|
||||
"upb": [True, False],
|
||||
"debug_suffix": [True, False],
|
||||
}
|
||||
default_options = {
|
||||
"shared": False,
|
||||
"fPIC": True,
|
||||
"with_zlib": True,
|
||||
"with_rtti": True,
|
||||
"lite": False,
|
||||
"upb": False,
|
||||
"debug_suffix": True,
|
||||
}
|
||||
|
||||
short_paths = True
|
||||
|
||||
@property
|
||||
def _is_clang_cl(self):
|
||||
return self.settings.compiler == "clang" and self.settings.os == "Windows"
|
||||
|
||||
@property
|
||||
def _is_clang_x86(self):
|
||||
return self.settings.compiler == "clang" and self.settings.arch == "x86"
|
||||
|
||||
@property
|
||||
def _protobuf_release(self):
|
||||
current_ver = Version(self.version)
|
||||
return Version(f"{current_ver.minor}.{current_ver.patch}")
|
||||
|
||||
def export_sources(self):
|
||||
export_conandata_patches(self)
|
||||
copy(self, "protobuf-conan-protoc-target.cmake", self.recipe_folder, os.path.join(self.export_sources_folder, "src"))
|
||||
|
||||
def config_options(self):
|
||||
if self.settings.os == "Windows":
|
||||
del self.options.fPIC
|
||||
|
||||
def configure(self):
|
||||
if self.options.shared:
|
||||
self.options.rm_safe("fPIC")
|
||||
|
||||
if self._protobuf_release < "27.0":
|
||||
self.options.rm_safe("upb")
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self, src_folder="src")
|
||||
|
||||
def requirements(self):
|
||||
if self.options.with_zlib:
|
||||
self.requires("zlib/[>=1.2.11 <2]")
|
||||
|
||||
if self._protobuf_release >= "22.0":
|
||||
self.requires("abseil/[>=20230802.1 <=20250127.0]", transitive_headers=True)
|
||||
|
||||
@property
|
||||
def _compilers_minimum_version(self):
|
||||
return {
|
||||
"gcc": "6",
|
||||
"clang": "5",
|
||||
"apple-clang": "10",
|
||||
"Visual Studio": "15",
|
||||
"msvc": "191",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.options.shared and is_msvc_static_runtime(self):
|
||||
raise ConanInvalidConfiguration("Protobuf can't be built with shared + MT(d) runtimes")
|
||||
|
||||
if is_msvc(self) and self._protobuf_release >= "22" and self.options.shared and \
|
||||
not self.dependencies["abseil"].options.shared:
|
||||
raise ConanInvalidConfiguration("When building protobuf as a shared library on Windows, "
|
||||
"abseil needs to be a shared library too")
|
||||
if self._protobuf_release >= "30.1":
|
||||
check_min_cppstd(self, 17)
|
||||
elif self._protobuf_release >= "22.0":
|
||||
if self.settings.compiler.get_safe("cppstd"):
|
||||
check_min_cppstd(self, 14)
|
||||
else:
|
||||
minimum_version = self._compilers_minimum_version.get(str(self.settings.compiler), None)
|
||||
compiler_version = Version(self.settings.compiler.version)
|
||||
if minimum_version and compiler_version < minimum_version:
|
||||
raise ConanInvalidConfiguration(
|
||||
f"{self.ref} requires C++14, which your compiler does not support.",
|
||||
)
|
||||
|
||||
check_min_vs(self, "190")
|
||||
|
||||
if self.settings.compiler == "clang":
|
||||
if Version(self.settings.compiler.version) < "4":
|
||||
raise ConanInvalidConfiguration(f"{self.ref} doesn't support clang < 4")
|
||||
|
||||
if "abseil" in self.dependencies.host:
|
||||
abseil_cppstd = self.dependencies.host['abseil'].info.settings.compiler.cppstd
|
||||
if abseil_cppstd != self.settings.compiler.cppstd:
|
||||
raise ConanInvalidConfiguration(f"Protobuf and abseil must be built with the same compiler.cppstd setting")
|
||||
|
||||
def source(self):
|
||||
get(self, **self.conan_data["sources"][self.version], strip_root=True)
|
||||
|
||||
def build_requirements(self):
|
||||
if self._protobuf_release >= "30.1":
|
||||
self.tool_requires("cmake/[>=3.16 <4]")
|
||||
|
||||
@property
|
||||
def _cmake_install_base_path(self):
|
||||
return os.path.join("lib", "cmake", "protobuf")
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
if self._protobuf_release >= "30.1":
|
||||
tc.cache_variables["protobuf_LOCAL_DEPENDENCIES_ONLY"] = True
|
||||
tc.cache_variables["CMAKE_INSTALL_CMAKEDIR"] = self._cmake_install_base_path.replace("\\", "/")
|
||||
tc.cache_variables["protobuf_WITH_ZLIB"] = self.options.with_zlib
|
||||
tc.cache_variables["protobuf_BUILD_TESTS"] = False
|
||||
tc.cache_variables["protobuf_BUILD_PROTOC_BINARIES"] = self.settings.os != "tvOS"
|
||||
if not self.options.debug_suffix:
|
||||
tc.cache_variables["protobuf_DEBUG_POSTFIX"] = ""
|
||||
tc.cache_variables["protobuf_BUILD_LIBPROTOC"] = self.settings.os != "tvOS"
|
||||
tc.cache_variables["protobuf_DISABLE_RTTI"] = not self.options.with_rtti
|
||||
tc.cache_variables["protobuf_BUILD_LIBUPB"] = self.options.get_safe("upb")
|
||||
if self._protobuf_release >= "22.0":
|
||||
tc.cache_variables["protobuf_ABSL_PROVIDER"] = "package"
|
||||
if not self.settings.compiler.get_safe("cppstd") and self._protobuf_release >= "22.0":
|
||||
tc.variables["CMAKE_CXX_STANDARD"] = 14
|
||||
if is_msvc(self) or self._is_clang_cl:
|
||||
runtime = self.settings.get_safe("compiler.runtime")
|
||||
if runtime:
|
||||
tc.cache_variables["protobuf_MSVC_STATIC_RUNTIME"] = runtime == "static"
|
||||
if is_apple_os(self) and self.options.shared:
|
||||
# Workaround against SIP on macOS for consumers while invoking protoc when protobuf lib is shared
|
||||
tc.variables["CMAKE_INSTALL_RPATH"] = "@loader_path/../lib"
|
||||
|
||||
if self.settings.os == "Linux":
|
||||
# Use RPATH instead of RUNPATH to help with specific case
|
||||
# in the grpc recipe when grpc_cpp_plugin is run with protoc
|
||||
# in the same build. RPATH ensures that the rpath in the binary
|
||||
# is respected for transitive dependencies too
|
||||
project_include = os.path.join(self.generators_folder, "protobuf_project_include.cmake")
|
||||
save(self, project_include, "add_link_options(-Wl,--disable-new-dtags)")
|
||||
tc.variables["CMAKE_PROJECT_INCLUDE"] = project_include
|
||||
# Note: conan2 only could be:
|
||||
# tc.extra_exelinkflags.append("-Wl,--disable-new-dtags")
|
||||
# tc.extra_sharedlinkflags.append("-Wl,--disable-new-dtags")
|
||||
|
||||
tc.generate()
|
||||
|
||||
deps = CMakeDeps(self)
|
||||
deps.generate()
|
||||
|
||||
def _patch_sources(self):
|
||||
apply_conandata_patches(self)
|
||||
|
||||
if self._protobuf_release < "22.0":
|
||||
# In older versions of protobuf, this file defines the `protobuf_generate` function
|
||||
protobuf_config_cmake = os.path.join(self.source_folder, "cmake", "protobuf-config.cmake.in")
|
||||
replace_in_file(self, protobuf_config_cmake, "@_protobuf_FIND_ZLIB@", "")
|
||||
replace_in_file(self, protobuf_config_cmake,
|
||||
"include(\"${CMAKE_CURRENT_LIST_DIR}/protobuf-targets.cmake\")",
|
||||
""
|
||||
)
|
||||
|
||||
# Disable a potential warning in protobuf-module.cmake.in
|
||||
# TODO: remove this patch? Is it really useful?
|
||||
protobuf_module_cmake = os.path.join(self.source_folder, "cmake", "protobuf-module.cmake.in")
|
||||
replace_in_file(self,
|
||||
protobuf_module_cmake,
|
||||
"if(DEFINED Protobuf_SRC_ROOT_FOLDER)",
|
||||
"if(0)\nif(DEFINED Protobuf_SRC_ROOT_FOLDER)",
|
||||
)
|
||||
replace_in_file(self,
|
||||
protobuf_module_cmake,
|
||||
"# Define upper case versions of output variables",
|
||||
"endif()",
|
||||
)
|
||||
|
||||
def build(self):
|
||||
self._patch_sources()
|
||||
cmake = CMake(self)
|
||||
cmake_root = "cmake" if Version(self.version) < "3.21" else None
|
||||
cmake.configure(build_script_folder=cmake_root)
|
||||
cmake.build()
|
||||
|
||||
def package(self):
|
||||
copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
|
||||
cmake = CMake(self)
|
||||
cmake.install()
|
||||
rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig"))
|
||||
rmdir(self, os.path.join(self.package_folder, "lib", "cmake", "utf8_range"))
|
||||
if self._protobuf_release < "22.0":
|
||||
rename(self, os.path.join(self.package_folder, self._cmake_install_base_path, "protobuf-config.cmake"),
|
||||
os.path.join(self.package_folder, self._cmake_install_base_path, "protobuf-generate.cmake"))
|
||||
|
||||
cmake_config_folder = os.path.join(self.package_folder, self._cmake_install_base_path)
|
||||
rm(self, "protobuf-config*.cmake", folder=cmake_config_folder)
|
||||
rm(self, "protobuf-targets*.cmake", folder=cmake_config_folder)
|
||||
copy(self, "protobuf-conan-protoc-target.cmake", src=self.source_folder, dst=cmake_config_folder)
|
||||
|
||||
if not self.options.lite:
|
||||
rm(self, "libprotobuf-lite*", os.path.join(self.package_folder, "lib"))
|
||||
rm(self, "libprotobuf-lite*", os.path.join(self.package_folder, "bin"))
|
||||
|
||||
def package_info(self):
|
||||
self.cpp_info.set_property("cmake_find_mode", "both")
|
||||
self.cpp_info.set_property("cmake_module_file_name", "Protobuf")
|
||||
self.cpp_info.set_property("cmake_file_name", "protobuf")
|
||||
self.cpp_info.set_property("pkg_config_name", "protobuf_full_package") # unofficial, but required to avoid side effects (libprotobuf component "steals" the default global pkg_config name)
|
||||
|
||||
build_modules = [
|
||||
os.path.join(self._cmake_install_base_path, "protobuf-generate.cmake"),
|
||||
os.path.join(self._cmake_install_base_path, "protobuf-module.cmake"),
|
||||
os.path.join(self._cmake_install_base_path, "protobuf-options.cmake"),
|
||||
os.path.join(self._cmake_install_base_path, "protobuf-conan-protoc-target.cmake"),
|
||||
]
|
||||
self.cpp_info.set_property("cmake_build_modules", build_modules)
|
||||
|
||||
lib_prefix = "lib" if (is_msvc(self) or self._is_clang_cl) else ""
|
||||
lib_suffix = "d" if self.settings.build_type == "Debug" and self.options.debug_suffix else ""
|
||||
|
||||
if self._protobuf_release >= "22.0":
|
||||
absl_deps = [f"abseil::{c}" for c in self.conan_data["absl_deps"][self.version]]
|
||||
|
||||
if self._protobuf_release >= "22.0" and (not self.options.shared or self.options.get_safe("upb")):
|
||||
# utf8 libraries
|
||||
# it's a private dependency and unconditionally built as a static library, should only
|
||||
# be exposed when protobuf itself is static (or if upb is being built)
|
||||
self.cpp_info.components["utf8_range"].set_property("cmake_target_name", "utf8_range::utf8_range")
|
||||
self.cpp_info.components["utf8_validity"].set_property("cmake_target_name", "utf8_range::utf8_validity")
|
||||
# https://github.com/protocolbuffers/protobuf/blob/0d815c5b74281f081c1ee4b431a4d5bbb1615c97/third_party/utf8_range/CMakeLists.txt#L24
|
||||
if self._protobuf_release >= "30.1" and self.settings.os == "Windows":
|
||||
self.cpp_info.components["utf8_range"].libs = ["libutf8_range"]
|
||||
self.cpp_info.components["utf8_validity"].libs = ["libutf8_validity"]
|
||||
else:
|
||||
self.cpp_info.components["utf8_range"].libs = ["utf8_range"]
|
||||
self.cpp_info.components["utf8_validity"].libs = ["utf8_validity"]
|
||||
self.cpp_info.components["utf8_validity"].requires = ["abseil::absl_strings"]
|
||||
|
||||
if self.options.get_safe("upb"):
|
||||
# upb libraries: note that these are unconditionally static
|
||||
self.cpp_info.components["upb"].set_property("cmake_target_name", "protobuf::libupb")
|
||||
self.cpp_info.components["upb"].libs = [lib_prefix + "upb" + lib_suffix]
|
||||
self.cpp_info.components["upb"].requires = ["utf8_range"]
|
||||
|
||||
# libprotobuf
|
||||
self.cpp_info.components["libprotobuf"].set_property("cmake_target_name", "protobuf::libprotobuf")
|
||||
self.cpp_info.components["libprotobuf"].set_property("pkg_config_name", "protobuf")
|
||||
self.cpp_info.components["libprotobuf"].builddirs.append(self._cmake_install_base_path)
|
||||
self.cpp_info.components["libprotobuf"].libs = [lib_prefix + "protobuf" + lib_suffix]
|
||||
if self.options.with_zlib:
|
||||
self.cpp_info.components["libprotobuf"].requires = ["zlib::zlib"]
|
||||
if self._protobuf_release >= "22.0":
|
||||
self.cpp_info.components["libprotobuf"].requires.extend(absl_deps)
|
||||
if not self.options.shared:
|
||||
self.cpp_info.components["libprotobuf"].requires.extend(["utf8_validity"])
|
||||
|
||||
if self.settings.os in ["Linux", "FreeBSD"]:
|
||||
self.cpp_info.components["libprotobuf"].system_libs.extend(["m", "pthread"])
|
||||
if self._is_clang_x86 or "arm" in str(self.settings.arch):
|
||||
self.cpp_info.components["libprotobuf"].system_libs.append("atomic")
|
||||
if self.settings.os == "Android":
|
||||
self.cpp_info.components["libprotobuf"].system_libs.append("log")
|
||||
if self.settings.os == "Windows":
|
||||
if self.options.shared:
|
||||
self.cpp_info.components["libprotobuf"].defines = ["PROTOBUF_USE_DLLS"]
|
||||
|
||||
# libprotoc
|
||||
if self.settings.os != "tvOS":
|
||||
self.cpp_info.components["libprotoc"].set_property("cmake_target_name", "protobuf::libprotoc")
|
||||
self.cpp_info.components["libprotoc"].libs = [lib_prefix + "protoc" + lib_suffix]
|
||||
self.cpp_info.components["libprotoc"].requires = ["libprotobuf"]
|
||||
if self._protobuf_release >= "22.0":
|
||||
self.cpp_info.components["libprotoc"].requires.extend(absl_deps)
|
||||
|
||||
# libprotobuf-lite
|
||||
if self.options.lite:
|
||||
self.cpp_info.components["libprotobuf-lite"].set_property("cmake_target_name", "protobuf::libprotobuf-lite")
|
||||
self.cpp_info.components["libprotobuf-lite"].set_property("pkg_config_name", "protobuf-lite")
|
||||
self.cpp_info.components["libprotobuf-lite"].builddirs.append(self._cmake_install_base_path)
|
||||
self.cpp_info.components["libprotobuf-lite"].libs = [lib_prefix + "protobuf-lite" + lib_suffix]
|
||||
if self.settings.os in ["Linux", "FreeBSD"]:
|
||||
self.cpp_info.components["libprotobuf-lite"].system_libs.extend(["m", "pthread"])
|
||||
if self._is_clang_x86 or "arm" in str(self.settings.arch):
|
||||
self.cpp_info.components["libprotobuf-lite"].system_libs.append("atomic")
|
||||
if self.settings.os == "Windows":
|
||||
if self.options.shared:
|
||||
self.cpp_info.components["libprotobuf-lite"].defines = ["PROTOBUF_USE_DLLS"]
|
||||
if self.settings.os == "Android":
|
||||
self.cpp_info.components["libprotobuf-lite"].system_libs.append("log")
|
||||
if self._protobuf_release >= "22.0":
|
||||
self.cpp_info.components["libprotobuf-lite"].requires.extend(absl_deps)
|
||||
if not self.options.shared:
|
||||
self.cpp_info.components["libprotobuf-lite"].requires.extend(["utf8_validity"])
|
||||
|
||||
# TODO: to remove in conan v2 once cmake_find_package* & pkg_config generators removed
|
||||
self.cpp_info.filenames["cmake_find_package"] = "Protobuf"
|
||||
self.cpp_info.filenames["cmake_find_package_multi"] = "protobuf"
|
||||
self.cpp_info.names["pkg_config"] ="protobuf_full_package"
|
||||
for generator in ["cmake_find_package", "cmake_find_package_multi"]:
|
||||
self.cpp_info.components["libprotobuf"].build_modules[generator] = build_modules
|
||||
if self.options.lite:
|
||||
for generator in ["cmake_find_package", "cmake_find_package_multi"]:
|
||||
self.cpp_info.components["libprotobuf-lite"].build_modules[generator] = build_modules
|
||||
self.env_info.PATH.append(os.path.join(self.package_folder, "bin"))
|
||||
70
external/protobuf/patches/protobuf-6.30.1-change-empty-string.patch
vendored
Normal file
70
external/protobuf/patches/protobuf-6.30.1-change-empty-string.patch
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
diff --git a/src/google/protobuf/port.cc b/src/google/protobuf/port.cc
|
||||
index af668e9e2..d60c2b89f 100644
|
||||
--- a/src/google/protobuf/port.cc
|
||||
+++ b/src/google/protobuf/port.cc
|
||||
@@ -97,14 +97,9 @@ void RealDebugCounter::Register(absl::string_view name) {
|
||||
}
|
||||
}
|
||||
|
||||
-#if defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L
|
||||
-PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT const GlobalEmptyString
|
||||
- fixed_address_empty_string{};
|
||||
-#else
|
||||
PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT
|
||||
PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 GlobalEmptyString
|
||||
fixed_address_empty_string{};
|
||||
-#endif
|
||||
|
||||
} // namespace internal
|
||||
} // namespace protobuf
|
||||
diff --git a/src/google/protobuf/port.h b/src/google/protobuf/port.h
|
||||
index 5f9e909a0..386ecc02a 100644
|
||||
--- a/src/google/protobuf/port.h
|
||||
+++ b/src/google/protobuf/port.h
|
||||
@@ -494,20 +494,27 @@ class NoopDebugCounter {
|
||||
// Default empty string object. Don't use this directly. Instead, call
|
||||
// GetEmptyString() to get the reference. This empty string is aligned with a
|
||||
// minimum alignment of 8 bytes to match the requirement of ArenaStringPtr.
|
||||
-#if defined(__cpp_lib_constexpr_string) && __cpp_lib_constexpr_string >= 201907L
|
||||
+
|
||||
// Take advantage of C++20 constexpr support in std::string.
|
||||
-class alignas(8) GlobalEmptyString {
|
||||
+class alignas(8) GlobalEmptyStringConstexpr {
|
||||
public:
|
||||
const std::string& get() const { return value_; }
|
||||
// Nothing to init, or destroy.
|
||||
std::string* Init() const { return nullptr; }
|
||||
|
||||
+ template <typename T = std::string, bool = (T(), true)>
|
||||
+ static constexpr std::true_type HasConstexprDefaultConstructor(int) {
|
||||
+ return {};
|
||||
+ }
|
||||
+ static constexpr std::false_type HasConstexprDefaultConstructor(char) {
|
||||
+ return {};
|
||||
+ }
|
||||
+
|
||||
private:
|
||||
std::string value_;
|
||||
};
|
||||
-PROTOBUF_EXPORT extern const GlobalEmptyString fixed_address_empty_string;
|
||||
-#else
|
||||
-class alignas(8) GlobalEmptyString {
|
||||
+
|
||||
+class alignas(8) GlobalEmptyStringDynamicInit {
|
||||
public:
|
||||
const std::string& get() const {
|
||||
return *reinterpret_cast<const std::string*>(internal::Launder(buffer_));
|
||||
@@ -519,8 +526,12 @@ class alignas(8) GlobalEmptyString {
|
||||
private:
|
||||
alignas(std::string) char buffer_[sizeof(std::string)];
|
||||
};
|
||||
+
|
||||
+using GlobalEmptyString = std::conditional_t<
|
||||
+ GlobalEmptyStringConstexpr::HasConstexprDefaultConstructor(0),
|
||||
+ const GlobalEmptyStringConstexpr, GlobalEmptyStringDynamicInit>;
|
||||
+
|
||||
PROTOBUF_EXPORT extern GlobalEmptyString fixed_address_empty_string;
|
||||
-#endif
|
||||
|
||||
} // namespace internal
|
||||
} // namespace protobuf
|
||||
21
external/protobuf/patches/protobuf-6.30.1-disable-fixed-string-MSVC.patch
vendored
Normal file
21
external/protobuf/patches/protobuf-6.30.1-disable-fixed-string-MSVC.patch
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
diff --git a/src/google/protobuf/port.h b/src/google/protobuf/port.h
|
||||
index 386ecc02a..32d260c42 100644
|
||||
--- a/src/google/protobuf/port.h
|
||||
+++ b/src/google/protobuf/port.h
|
||||
@@ -502,10 +502,16 @@ class alignas(8) GlobalEmptyStringConstexpr {
|
||||
// Nothing to init, or destroy.
|
||||
std::string* Init() const { return nullptr; }
|
||||
|
||||
+ // Disable the optimization for MSVC.
|
||||
+ // There are some builds where the default constructed string can't be used as
|
||||
+ // `constinit` even though the constructor is `constexpr` and can be used
|
||||
+ // during constant evaluation.
|
||||
+#if !defined(_MSC_VER)
|
||||
template <typename T = std::string, bool = (T(), true)>
|
||||
static constexpr std::true_type HasConstexprDefaultConstructor(int) {
|
||||
return {};
|
||||
}
|
||||
+#endif
|
||||
static constexpr std::false_type HasConstexprDefaultConstructor(char) {
|
||||
return {};
|
||||
}
|
||||
25
external/protobuf/protobuf-conan-protoc-target.cmake
vendored
Normal file
25
external/protobuf/protobuf-conan-protoc-target.cmake
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
if(NOT TARGET protobuf::protoc)
|
||||
# Locate protoc executable
|
||||
## Workaround for legacy "cmake" generator in case of cross-build
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
find_program(PROTOC_PROGRAM NAMES protoc PATHS ENV PATH NO_DEFAULT_PATH)
|
||||
endif()
|
||||
## And here this will work fine with "CMakeToolchain" (for native & cross-build)
|
||||
## and legacy "cmake" generator in case of native build
|
||||
if(NOT PROTOC_PROGRAM)
|
||||
find_program(PROTOC_PROGRAM NAMES protoc)
|
||||
endif()
|
||||
## Last resort: we search in package folder directly
|
||||
if(NOT PROTOC_PROGRAM)
|
||||
set(PROTOC_PROGRAM "${CMAKE_CURRENT_LIST_DIR}/../../../bin/protoc${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
endif()
|
||||
get_filename_component(PROTOC_PROGRAM "${PROTOC_PROGRAM}" ABSOLUTE)
|
||||
|
||||
# Give opportunity to users to provide an external protoc executable
|
||||
# (this is a feature of official FindProtobuf.cmake)
|
||||
set(Protobuf_PROTOC_EXECUTABLE ${PROTOC_PROGRAM} CACHE FILEPATH "The protoc compiler")
|
||||
|
||||
# Create executable imported target protobuf::protoc
|
||||
add_executable(protobuf::protoc IMPORTED)
|
||||
set_property(TARGET protobuf::protoc PROPERTY IMPORTED_LOCATION ${Protobuf_PROTOC_EXECUTABLE})
|
||||
endif()
|
||||
28
external/protobuf/test_package/CMakeLists.txt
vendored
Normal file
28
external/protobuf/test_package/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
project(test_package LANGUAGES CXX)
|
||||
|
||||
find_package(protobuf CONFIG REQUIRED)
|
||||
|
||||
add_executable(${PROJECT_NAME} test_package.cpp)
|
||||
|
||||
if(CONAN_TEST_USE_CXXSTD_14)
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14)
|
||||
else()
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_11)
|
||||
endif()
|
||||
|
||||
if (protobuf_LITE)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE protobuf::libprotobuf-lite)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE CONANTEST_PROTOBUF_LITE=1)
|
||||
else()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE protobuf::libprotobuf)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET protobuf::protoc)
|
||||
message(FATAL_ERROR "protoc executable should have been defined as part of find_package(protobuf)")
|
||||
endif()
|
||||
|
||||
if(NOT COMMAND protobuf_generate)
|
||||
message(FATAL_ERROR "protobuf_generate should have been defined as part of find_package(protobuf)")
|
||||
endif()
|
||||
64
external/protobuf/test_package/addressbook.proto
vendored
Normal file
64
external/protobuf/test_package/addressbook.proto
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// See README.txt for information and build instructions.
|
||||
//
|
||||
// Note: START and END tags are used in comments to define sections used in
|
||||
// tutorials. They are not part of the syntax for Protocol Buffers.
|
||||
//
|
||||
// To get an in-depth walkthrough of this file and the related examples, see:
|
||||
// https://developers.google.com/protocol-buffers/docs/tutorials
|
||||
|
||||
// [START declaration]
|
||||
syntax = "proto3";
|
||||
package tutorial;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
// [END declaration]
|
||||
|
||||
// [START java_declaration]
|
||||
option java_package = "com.example.tutorial";
|
||||
option java_outer_classname = "AddressBookProtos";
|
||||
// [END java_declaration]
|
||||
|
||||
// [START csharp_declaration]
|
||||
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
|
||||
// [END csharp_declaration]
|
||||
|
||||
// [START messages]
|
||||
message Person {
|
||||
string name = 1;
|
||||
int32 id = 2; // Unique ID number for this person.
|
||||
string email = 3;
|
||||
|
||||
enum PhoneType {
|
||||
MOBILE = 0;
|
||||
HOME = 1;
|
||||
WORK = 2;
|
||||
}
|
||||
|
||||
message PhoneNumber {
|
||||
string number = 1;
|
||||
PhoneType type = 2;
|
||||
}
|
||||
|
||||
repeated PhoneNumber phones = 4;
|
||||
|
||||
google.protobuf.Timestamp last_updated = 5;
|
||||
}
|
||||
|
||||
// Our address book file is just one of these.
|
||||
message AddressBook {
|
||||
repeated Person people = 1;
|
||||
}
|
||||
// [END messages]
|
||||
|
||||
// Artibrary message with a GID type, to cause
|
||||
// the .pb.h file to define `GID_MAX`, which
|
||||
// may conflict with a macro defined by system
|
||||
// headers on macOS.
|
||||
message FooBar {
|
||||
enum GID {
|
||||
FOO = 0;
|
||||
}
|
||||
enum UID {
|
||||
BAR = 0;
|
||||
}
|
||||
}
|
||||
40
external/protobuf/test_package/conanfile.py
vendored
Normal file
40
external/protobuf/test_package/conanfile.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
from conan import ConanFile
|
||||
from conan.tools.build import can_run
|
||||
from conan.tools.cmake import cmake_layout, CMake, CMakeToolchain
|
||||
from conan.tools.scm import Version
|
||||
import os
|
||||
|
||||
|
||||
class TestPackageConan(ConanFile):
|
||||
settings = "os", "arch", "compiler", "build_type"
|
||||
generators = "CMakeDeps", "VirtualBuildEnv", "VirtualRunEnv"
|
||||
test_type = "explicit"
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
|
||||
def requirements(self):
|
||||
# note `run=True` so that the runenv can find protoc
|
||||
self.requires(self.tested_reference_str, run=True)
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.cache_variables["protobuf_LITE"] = self.dependencies[self.tested_reference_str].options.lite
|
||||
protobuf_version = Version(self.dependencies[self.tested_reference_str].ref.version)
|
||||
tc.cache_variables["CONAN_TEST_USE_CXXSTD_14"] = protobuf_version >= "3.22"
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def test(self):
|
||||
if can_run(self):
|
||||
bin_path = os.path.join(self.cpp.build.bindirs[0], "test_package")
|
||||
self.run(bin_path, env="conanrun")
|
||||
|
||||
# Invoke protoc in the same way CMake would
|
||||
self.run(f"protoc --proto_path={self.source_folder} --cpp_out={self.build_folder} {self.source_folder}/addressbook.proto", env="conanrun")
|
||||
assert os.path.exists(os.path.join(self.build_folder,"addressbook.pb.cc"))
|
||||
assert os.path.exists(os.path.join(self.build_folder,"addressbook.pb.h"))
|
||||
23
external/protobuf/test_package/test_package.cpp
vendored
Normal file
23
external/protobuf/test_package/test_package.cpp
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#if !defined(CONANTEST_PROTOBUF_LITE)
|
||||
#include <google/protobuf/timestamp.pb.h>
|
||||
#include <google/protobuf/util/time_util.h>
|
||||
#else
|
||||
#include <google/protobuf/message_lite.h>
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
#if !defined(CONANTEST_PROTOBUF_LITE)
|
||||
google::protobuf::Timestamp ts;
|
||||
google::protobuf::util::TimeUtil::FromString("1972-01-01T10:00:20.021Z", &ts);
|
||||
const auto nanoseconds = ts.nanos();
|
||||
std::cout << "1972-01-01T10:00:20.021Z in nanoseconds: " << nanoseconds << "\n";
|
||||
#else
|
||||
google::protobuf::ShutdownProtobufLibrary();
|
||||
#endif
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,980 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/Slot.h>
|
||||
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class TestHandler : public reduce_relay::SquelchHandler
|
||||
{
|
||||
public:
|
||||
using squelch_method =
|
||||
std::function<void(PublicKey const&, Peer::id_t, std::uint32_t)>;
|
||||
using squelchAll_method = std::function<
|
||||
void(PublicKey const&, std::uint32_t, std::function<void(Peer::id_t)>)>;
|
||||
using unsquelch_method = std::function<void(PublicKey const&, Peer::id_t)>;
|
||||
|
||||
squelch_method squelch_f_;
|
||||
squelchAll_method squelchAll_f_;
|
||||
unsquelch_method unsquelch_f_;
|
||||
|
||||
TestHandler(
|
||||
squelch_method const& squelch_f,
|
||||
squelchAll_method const& squelchAll_f,
|
||||
unsquelch_method const& unsquelch_f)
|
||||
: squelch_f_(squelch_f)
|
||||
, squelchAll_f_(squelchAll_f)
|
||||
, unsquelch_f_(unsquelch_f)
|
||||
{
|
||||
}
|
||||
|
||||
TestHandler(TestHandler& copy)
|
||||
{
|
||||
squelch_f_ = copy.squelch_f_;
|
||||
squelchAll_f_ = copy.squelchAll_f_;
|
||||
unsquelch_f_ = copy.unsquelch_f_;
|
||||
}
|
||||
|
||||
void
|
||||
squelch(PublicKey const& validator, Peer::id_t peer, std::uint32_t duration)
|
||||
const override
|
||||
{
|
||||
squelch_f_(validator, peer, duration);
|
||||
}
|
||||
|
||||
void
|
||||
squelchAll(
|
||||
PublicKey const& validator,
|
||||
std::uint32_t duration,
|
||||
std::function<void(Peer::id_t)> callback) override
|
||||
{
|
||||
squelchAll_f_(validator, duration, callback);
|
||||
}
|
||||
|
||||
void
|
||||
unsquelch(PublicKey const& validator, Peer::id_t peer) const override
|
||||
{
|
||||
unsquelch_f_(validator, peer);
|
||||
}
|
||||
};
|
||||
|
||||
class EnhancedSquelchingTestSlots : public reduce_relay::Slots
|
||||
{
|
||||
using Slots = reduce_relay::Slots;
|
||||
|
||||
public:
|
||||
EnhancedSquelchingTestSlots(
|
||||
Logs& logs,
|
||||
reduce_relay::SquelchHandler& handler,
|
||||
Config const& config,
|
||||
reduce_relay::Slots::clock_type& clock)
|
||||
: Slots(logs, handler, config, clock)
|
||||
{
|
||||
}
|
||||
|
||||
Slots::slots_map const&
|
||||
getSlots(bool trusted) const
|
||||
{
|
||||
if (trusted)
|
||||
return trustedSlots_;
|
||||
|
||||
return untrustedSlots_;
|
||||
}
|
||||
|
||||
hash_map<PublicKey, ValidatorInfo> const&
|
||||
getConsideredValidators()
|
||||
{
|
||||
return consideredValidators_;
|
||||
}
|
||||
|
||||
std::optional<PublicKey>
|
||||
updateConsideredValidator(PublicKey const& validator, Peer::id_t peerID)
|
||||
{
|
||||
return Slots::updateConsideredValidator(validator, peerID);
|
||||
}
|
||||
|
||||
void
|
||||
squelchValidator(PublicKey const& validatorKey, Peer::id_t peerID)
|
||||
{
|
||||
Slots::registerSquelchedValidator(validatorKey, peerID);
|
||||
}
|
||||
|
||||
bool
|
||||
validatorSquelched(PublicKey const& validatorKey)
|
||||
{
|
||||
return Slots::expireAndIsValidatorSquelched(validatorKey);
|
||||
}
|
||||
|
||||
bool
|
||||
peerSquelched(PublicKey const& validatorKey, Peer::id_t peerID)
|
||||
{
|
||||
return Slots::expireAndIsPeerSquelched(validatorKey, peerID);
|
||||
}
|
||||
};
|
||||
|
||||
class enhanced_squelch_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
TestHandler::squelch_method noop_squelch =
|
||||
[&](PublicKey const&, Peer::id_t, std::uint32_t) {
|
||||
BEAST_EXPECTS(false, "unexpected call to squelch handler");
|
||||
};
|
||||
|
||||
TestHandler::squelchAll_method noop_squelchAll =
|
||||
[&](PublicKey const&, std::uint32_t, std::function<void(Peer::id_t)>) {
|
||||
BEAST_EXPECTS(false, "unexpected call to squelchAll handler");
|
||||
};
|
||||
|
||||
TestHandler::unsquelch_method noop_unsquelch = [&](PublicKey const&,
|
||||
Peer::id_t) {
|
||||
BEAST_EXPECTS(false, "unexpected call to unsquelch handler");
|
||||
};
|
||||
|
||||
// noop_handler is passed as a place holder Handler to slots
|
||||
TestHandler noop_handler = {
|
||||
noop_squelch,
|
||||
noop_squelchAll,
|
||||
noop_unsquelch,
|
||||
};
|
||||
|
||||
jtx::Env env_;
|
||||
|
||||
enhanced_squelch_test() : env_(*this)
|
||||
{
|
||||
env_.app().config().VP_REDUCE_RELAY_ENHANCED_SQUELCH_ENABLE = true;
|
||||
}
|
||||
|
||||
void
|
||||
testConfig()
|
||||
{
|
||||
testcase("Test Config - enabled enhanced squelching");
|
||||
Config c;
|
||||
|
||||
std::string toLoad(R"rippleConfig(
|
||||
[reduce_relay]
|
||||
vp_enhanced_squelch_enable=1
|
||||
)rippleConfig");
|
||||
|
||||
c.loadFromString(toLoad);
|
||||
BEAST_EXPECT(c.VP_REDUCE_RELAY_ENHANCED_SQUELCH_ENABLE == true);
|
||||
|
||||
toLoad = R"rippleConfig(
|
||||
[reduce_relay]
|
||||
vp_enhanced_squelch_enable=0
|
||||
)rippleConfig";
|
||||
|
||||
c.loadFromString(toLoad);
|
||||
BEAST_EXPECT(c.VP_REDUCE_RELAY_ENHANCED_SQUELCH_ENABLE == false);
|
||||
|
||||
toLoad = R"rippleConfig(
|
||||
[reduce_relay]
|
||||
)rippleConfig";
|
||||
|
||||
c.loadFromString(toLoad);
|
||||
BEAST_EXPECT(c.VP_REDUCE_RELAY_ENHANCED_SQUELCH_ENABLE == false);
|
||||
}
|
||||
|
||||
/** Tests tracking for squelched validators and peers */
|
||||
void
|
||||
testSquelchTracking()
|
||||
{
|
||||
testcase("squelchTracking");
|
||||
Peer::id_t const squelchedPeerID = 0;
|
||||
Peer::id_t const newPeerID = 1;
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), noop_handler, env_.app().config(), stopwatch);
|
||||
auto const publicKey = randomKeyPair(KeyType::ed25519).first;
|
||||
|
||||
// a new key should not be squelched
|
||||
BEAST_EXPECTS(
|
||||
!slots.validatorSquelched(publicKey), "validator squelched");
|
||||
|
||||
slots.squelchValidator(publicKey, squelchedPeerID);
|
||||
|
||||
// after squelching a peer, the validator must be squelched
|
||||
BEAST_EXPECTS(
|
||||
slots.validatorSquelched(publicKey), "validator not squelched");
|
||||
|
||||
// the peer must also be squelched
|
||||
BEAST_EXPECTS(
|
||||
slots.peerSquelched(publicKey, squelchedPeerID),
|
||||
"peer not squelched");
|
||||
|
||||
// a new peer must not be squelched
|
||||
BEAST_EXPECTS(
|
||||
!slots.peerSquelched(publicKey, newPeerID), "new peer squelched");
|
||||
|
||||
// advance the manual clock to after expiration
|
||||
stopwatch.advance(
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT +
|
||||
std::chrono::seconds{11});
|
||||
|
||||
// validator squelch should expire
|
||||
BEAST_EXPECTS(
|
||||
!slots.validatorSquelched(publicKey),
|
||||
"validator squelched after expiry");
|
||||
|
||||
// peer squelch should also expire
|
||||
BEAST_EXPECTS(
|
||||
!slots.peerSquelched(publicKey, squelchedPeerID),
|
||||
"validator squelched after expiry");
|
||||
}
|
||||
|
||||
void
|
||||
testUpdateValidatorSlot_newValidator()
|
||||
{
|
||||
testcase("updateValidatorSlot_newValidator");
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), noop_handler, env_.app().config(), stopwatch);
|
||||
|
||||
Peer::id_t const peerID = 1;
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
uint256 const message{0};
|
||||
|
||||
slots.updateUntrustedValidatorSlot(message, validator, peerID);
|
||||
|
||||
// adding untrusted slot does not effect trusted slots
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(true).size() == 0, "trusted slots changed");
|
||||
|
||||
// we expect that the validator was not added to untrusted slots
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(false).size() == 0, "untrusted slot changed");
|
||||
|
||||
// we expect that the validator was added to th consideration list
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().contains(validator),
|
||||
"new validator was not considered");
|
||||
}
|
||||
|
||||
void
|
||||
testUpdateValidatorSlot_squelchedValidator()
|
||||
{
|
||||
testcase("testUpdateValidatorSlot_squelchedValidator");
|
||||
|
||||
Peer::id_t const squelchedPeerID = 0;
|
||||
Peer::id_t const newPeerID = 1;
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
|
||||
TestHandler::squelch_method const squelch_f =
|
||||
[&](PublicKey const& key, Peer::id_t id, std::uint32_t duration) {
|
||||
BEAST_EXPECTS(
|
||||
key == validator,
|
||||
"squelch called for unknown validator key");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
id == newPeerID, "squelch called for the wrong peer");
|
||||
};
|
||||
|
||||
TestHandler handler{squelch_f, noop_squelchAll, noop_unsquelch};
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
|
||||
slots.squelchValidator(validator, squelchedPeerID);
|
||||
|
||||
// this should not trigger squelch assertions, the peer is squelched
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator), validator, squelchedPeerID);
|
||||
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator), validator, newPeerID);
|
||||
|
||||
// the squelched peer remained squelched
|
||||
BEAST_EXPECTS(
|
||||
slots.peerSquelched(validator, squelchedPeerID),
|
||||
"peer not squelched");
|
||||
|
||||
// because the validator was squelched, the new peer was also squelched
|
||||
BEAST_EXPECTS(
|
||||
slots.peerSquelched(validator, newPeerID),
|
||||
"new peer was not squelched");
|
||||
|
||||
// a squelched validator must not be considered
|
||||
BEAST_EXPECTS(
|
||||
!slots.getConsideredValidators().contains(validator),
|
||||
"squelched validator was added for consideration");
|
||||
}
|
||||
|
||||
void
|
||||
testUpdateValidatorSlot_slotsFull()
|
||||
{
|
||||
testcase("updateValidatorSlot_slotsFull");
|
||||
Peer::id_t const peerID = 1;
|
||||
|
||||
// while there are open untrusted slots, no calls should be made to
|
||||
// squelch any validators
|
||||
TestHandler handler{noop_handler};
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
|
||||
// saturate validator slots
|
||||
auto const validators = fillUntrustedSlots(slots);
|
||||
|
||||
// adding untrusted slot does not effect trusted slots
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(true).size() == 0, "trusted slots changed");
|
||||
|
||||
// simulate additional messages from already selected validators
|
||||
for (auto const& validator : validators)
|
||||
for (int i = 0; i < reduce_relay::MAX_MESSAGE_THRESHOLD; ++i)
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator) + static_cast<uint256>(i),
|
||||
validator,
|
||||
peerID);
|
||||
|
||||
// an untrusted slot was added for each validator
|
||||
BEAST_EXPECT(
|
||||
slots.getSlots(false).size() == reduce_relay::MAX_UNTRUSTED_SLOTS);
|
||||
|
||||
for (auto const& validator : validators)
|
||||
BEAST_EXPECTS(
|
||||
!slots.validatorSquelched(validator),
|
||||
"selected validator was squelched");
|
||||
|
||||
auto const newValidator = randomKeyPair(KeyType::ed25519).first;
|
||||
|
||||
// once slots are full squelchAll must be called for new peer/validator
|
||||
handler.squelchAll_f_ = [&](PublicKey const& key,
|
||||
std::uint32_t,
|
||||
std::function<void(Peer::id_t)> callback) {
|
||||
BEAST_EXPECTS(
|
||||
key == newValidator, "unexpected validator squelched");
|
||||
callback(peerID);
|
||||
};
|
||||
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(newValidator), newValidator, peerID);
|
||||
|
||||
// Once the slots are saturated every other validator is squelched
|
||||
BEAST_EXPECTS(
|
||||
slots.validatorSquelched(newValidator),
|
||||
"untrusted validator not squelched");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.peerSquelched(newValidator, peerID),
|
||||
"peer for untrusted validator not squelched");
|
||||
}
|
||||
|
||||
void
|
||||
testDeleteIdlePeers_deleteIdleSlots()
|
||||
{
|
||||
testcase("deleteIdlePeers");
|
||||
TestHandler handler{noop_handler};
|
||||
TestStopwatch stopwatch;
|
||||
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
auto keys = fillUntrustedSlots(slots);
|
||||
|
||||
// verify that squelchAll is called for each idled slot validator
|
||||
handler.squelchAll_f_ = [&](PublicKey const& actualKey,
|
||||
std::uint32_t duration,
|
||||
std::function<void(Peer::id_t)> callback) {
|
||||
for (auto it = keys.begin(); it != keys.end(); ++it)
|
||||
{
|
||||
if (*it == actualKey)
|
||||
{
|
||||
keys.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
BEAST_EXPECTS(false, "unexpected key passed to squelchAll");
|
||||
};
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(false).size() == reduce_relay::MAX_UNTRUSTED_SLOTS,
|
||||
"unexpected number of untrusted slots");
|
||||
|
||||
// advance the manual clock to after slot expiration
|
||||
stopwatch.advance(
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT +
|
||||
std::chrono::seconds{1});
|
||||
|
||||
slots.deleteIdlePeers();
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(false).size() == 0,
|
||||
"unexpected number of untrusted slots");
|
||||
|
||||
BEAST_EXPECTS(keys.empty(), "not all validators were squelched");
|
||||
}
|
||||
|
||||
void
|
||||
testDeleteIdlePeers_deleteIdleUntrustedPeer()
|
||||
{
|
||||
testcase("deleteIdleUntrustedPeer");
|
||||
Peer::id_t const peerID = 1;
|
||||
Peer::id_t const peerID2 = 2;
|
||||
TestStopwatch stopwatch;
|
||||
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), noop_handler, env_.app().config(), stopwatch);
|
||||
|
||||
// fill one untrustd validator slot
|
||||
auto const validator = fillUntrustedSlots(slots, 1)[0];
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(false).size() == 1,
|
||||
"unexpected number of untrusted slots");
|
||||
|
||||
slots.updateSlotAndSquelch(
|
||||
sha512Half(validator) + static_cast<uint256>(100),
|
||||
validator,
|
||||
peerID,
|
||||
false);
|
||||
|
||||
slots.updateSlotAndSquelch(
|
||||
sha512Half(validator) + static_cast<uint256>(100),
|
||||
validator,
|
||||
peerID2,
|
||||
false);
|
||||
|
||||
slots.deletePeer(peerID, true);
|
||||
|
||||
auto const slotPeers = getUntrustedSlotPeers(validator, slots);
|
||||
BEAST_EXPECTS(
|
||||
slotPeers.size() == 1, "untrusted validator slot is missing");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slotPeers.contains(peerID),
|
||||
"peer was not removed from untrusted slots");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slotPeers.contains(peerID2),
|
||||
"peer was removed from untrusted slots");
|
||||
}
|
||||
|
||||
/** Test that untrusted validator slots are correctly updated by
|
||||
* updateSlotAndSquelch
|
||||
*/
|
||||
void
|
||||
testUpdateSlotAndSquelch_untrustedValidator()
|
||||
{
|
||||
testcase("updateUntrsutedValidatorSlot");
|
||||
TestHandler handler{noop_handler};
|
||||
|
||||
handler.squelch_f_ = [](PublicKey const&, Peer::id_t, std::uint32_t) {};
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
|
||||
// peers that will be source of validator messages
|
||||
std::vector<Peer::id_t> peers = {};
|
||||
|
||||
// prepare n+1 peers, we expect the n+1st peer will be squelched
|
||||
for (int i = 0; i <
|
||||
env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS + 1;
|
||||
++i)
|
||||
peers.push_back(i);
|
||||
|
||||
auto const validator = fillUntrustedSlots(slots, 1)[0];
|
||||
|
||||
// Squelching logic resets all counters each time a new peer is added
|
||||
// Therfore we need to populate counters for each peer before sending
|
||||
// new messages
|
||||
for (auto const& peer : peers)
|
||||
{
|
||||
auto const now = stopwatch.now();
|
||||
slots.updateSlotAndSquelch(
|
||||
sha512Half(validator) +
|
||||
static_cast<uint256>(now.time_since_epoch().count()),
|
||||
validator,
|
||||
peer,
|
||||
false);
|
||||
|
||||
stopwatch.advance(std::chrono::milliseconds{10});
|
||||
}
|
||||
|
||||
// simulate new, unique validator messages sent by peers
|
||||
for (auto const& peer : peers)
|
||||
for (int i = 0; i < reduce_relay::MAX_MESSAGE_THRESHOLD + 1; ++i)
|
||||
{
|
||||
auto const now = stopwatch.now();
|
||||
slots.updateSlotAndSquelch(
|
||||
sha512Half(validator) +
|
||||
static_cast<uint256>(now.time_since_epoch().count()),
|
||||
validator,
|
||||
peer,
|
||||
false);
|
||||
|
||||
stopwatch.advance(std::chrono::milliseconds{10});
|
||||
}
|
||||
|
||||
auto const slotPeers = getUntrustedSlotPeers(validator, slots);
|
||||
BEAST_EXPECTS(
|
||||
slotPeers.size() ==
|
||||
env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS +
|
||||
1,
|
||||
"untrusted validator slot is missing");
|
||||
|
||||
int selected = 0;
|
||||
int squelched = 0;
|
||||
for (auto const& [_, info] : slotPeers)
|
||||
{
|
||||
switch (info.state)
|
||||
{
|
||||
case reduce_relay::PeerState::Selected:
|
||||
++selected;
|
||||
break;
|
||||
case reduce_relay::PeerState::Squelched:
|
||||
++squelched;
|
||||
break;
|
||||
case reduce_relay::PeerState::Counting:
|
||||
BEAST_EXPECTS(
|
||||
false, "peer should not be in counting state");
|
||||
}
|
||||
}
|
||||
|
||||
BEAST_EXPECTS(squelched == 1, "expected one squelched peer");
|
||||
BEAST_EXPECTS(
|
||||
selected ==
|
||||
env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS,
|
||||
"wrong number of peers selected");
|
||||
}
|
||||
|
||||
void
|
||||
testUpdateConsideredValidator_new()
|
||||
{
|
||||
testcase("testUpdateConsideredValidator_new");
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), noop_handler, env_.app().config(), stopwatch);
|
||||
|
||||
// insert some random validator key
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
Peer::id_t const peerID = 0;
|
||||
Peer::id_t const peerID2 = 1;
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID),
|
||||
"validator was selected with insufficient number of peers");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().contains(validator),
|
||||
"new validator was not added for consideration");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID),
|
||||
"validator was selected with insufficient number of peers");
|
||||
|
||||
// expect that a peer will be registered once as a message source
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().at(validator).peers.size() == 1,
|
||||
"duplicate peer was registered");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID2),
|
||||
"validator was selected with insufficient number of peers");
|
||||
|
||||
// expect that each distinct peer will be registered
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().at(validator).peers.size() == 2,
|
||||
"distinct peers were not registered");
|
||||
}
|
||||
|
||||
void
|
||||
testUpdateConsideredValidator_idle()
|
||||
{
|
||||
testcase("testUpdateConsideredValidator_idle");
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), noop_handler, env_.app().config(), stopwatch);
|
||||
|
||||
// insert some random validator key
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
Peer::id_t peerID = 0;
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID),
|
||||
"validator was selected with insufficient number of peers");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().contains(validator),
|
||||
"new validator was not added for consideration");
|
||||
|
||||
auto const state = slots.getConsideredValidators().at(validator);
|
||||
|
||||
// simulate a validator sending a new message before the idle timer
|
||||
stopwatch.advance(reduce_relay::PEER_IDLED - std::chrono::seconds(1));
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID),
|
||||
"validator was selected with insufficient number of peers");
|
||||
auto const newState = slots.getConsideredValidators().at(validator);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
state.count + 1 == newState.count,
|
||||
"non-idling validator was updated");
|
||||
|
||||
// simulate a validator idling
|
||||
stopwatch.advance(reduce_relay::PEER_IDLED + std::chrono::seconds(1));
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID),
|
||||
"validator was selected with insufficient number of peers");
|
||||
}
|
||||
|
||||
void
|
||||
testUpdateConsideredValidator_selectQualifying()
|
||||
{
|
||||
testcase("testUpdateConsideredValidator_selectQualifying");
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), noop_handler, env_.app().config(), stopwatch);
|
||||
|
||||
// insert some random validator key
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
Peer::id_t peerID = 0;
|
||||
|
||||
for (int i = 0; i < reduce_relay::MAX_MESSAGE_THRESHOLD - 1; ++i)
|
||||
{
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID),
|
||||
"validator was selected before reaching message threshold");
|
||||
|
||||
stopwatch.advance(
|
||||
reduce_relay::PEER_IDLED - std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
auto const consideredValidator =
|
||||
slots.updateConsideredValidator(validator, peerID);
|
||||
BEAST_EXPECTS(
|
||||
consideredValidator && *consideredValidator == validator,
|
||||
"expected validator was not selected");
|
||||
|
||||
// expect that selected peer was removed
|
||||
BEAST_EXPECTS(
|
||||
!slots.getConsideredValidators().contains(validator),
|
||||
"selected validator was not removed from considered list");
|
||||
}
|
||||
|
||||
void
|
||||
testCleanConsideredValidators_resetIdle()
|
||||
{
|
||||
testcase("testCleanConsideredValidators_resetIdle");
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), noop_handler, env_.app().config(), stopwatch);
|
||||
|
||||
// send enough messages for a slot to meet peer requirements
|
||||
for (int i = 0;
|
||||
i < env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS;
|
||||
++i)
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator) + static_cast<uint256>(i), validator, i);
|
||||
|
||||
// send enough messages from some peer to be one message away from
|
||||
// meeting the selection criteria
|
||||
for (int i = 0; i < reduce_relay::MAX_MESSAGE_THRESHOLD -
|
||||
(env_.app()
|
||||
.config()
|
||||
.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS +
|
||||
1);
|
||||
++i)
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator) + static_cast<uint256>(i), validator, 0);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().at(validator).count ==
|
||||
reduce_relay::MAX_MESSAGE_THRESHOLD - 1,
|
||||
"considered validator information is in an invalid state");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().at(validator).peers.size() ==
|
||||
env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS,
|
||||
"considered validator information is in an invalid state");
|
||||
|
||||
stopwatch.advance(reduce_relay::PEER_IDLED + std::chrono::seconds{1});
|
||||
|
||||
// deleteIdlePeers must reset the progress of a validator that idled
|
||||
slots.deleteIdlePeers();
|
||||
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator) + static_cast<uint256>(1), validator, 0);
|
||||
|
||||
// we expect that the validator was not selected
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(false).size() == 0, "untrusted slot was created");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().at(validator).count == 1,
|
||||
"considered validator information is in an invalid state");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().at(validator).peers.size() == 1,
|
||||
"considered validator information is in an invalid state");
|
||||
}
|
||||
|
||||
void
|
||||
testCleanConsideredValidators_deletePoorlyConnected()
|
||||
{
|
||||
testcase("cleanConsideredValidators_deletePoorlyConnected");
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
Peer::id_t const peerID = 0;
|
||||
TestHandler handler{noop_handler};
|
||||
|
||||
// verify that squelchAll is called for poorly connected validator
|
||||
handler.squelchAll_f_ = [&](PublicKey const& actualKey,
|
||||
std::uint32_t duration,
|
||||
std::function<void(Peer::id_t)> callback) {
|
||||
BEAST_EXPECTS(
|
||||
actualKey == validator, "unexpected key passed to squelchAll");
|
||||
callback(peerID);
|
||||
};
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
|
||||
// send enough messages from a single peer
|
||||
for (int i = 0; i < 2 * reduce_relay::MAX_MESSAGE_THRESHOLD + 1; ++i)
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator) + static_cast<uint256>(i),
|
||||
validator,
|
||||
peerID);
|
||||
|
||||
stopwatch.advance(reduce_relay::PEER_IDLED + std::chrono::seconds{1});
|
||||
|
||||
// deleteIdlePeers must squelch the validator as it failed to reach
|
||||
// peering requirements
|
||||
slots.deleteIdlePeers();
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().size() == 0,
|
||||
"poorly connected validator was not deleted");
|
||||
}
|
||||
|
||||
void
|
||||
testCleanConsideredValidators_deleteSilent()
|
||||
{
|
||||
testcase("cleanConsideredValidators_deleteSilent");
|
||||
// insert some random validator key
|
||||
auto const idleValidator = randomKeyPair(KeyType::ed25519).first;
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
Peer::id_t const peerID = 0;
|
||||
|
||||
TestHandler handler{noop_handler};
|
||||
|
||||
// verify that squelchAll is called for idle validator
|
||||
handler.squelchAll_f_ = [&](PublicKey const& actualKey,
|
||||
std::uint32_t duration,
|
||||
std::function<void(Peer::id_t)> callback) {
|
||||
BEAST_EXPECTS(
|
||||
actualKey == idleValidator,
|
||||
"unexpected key passed to squelchAll");
|
||||
callback(peerID);
|
||||
};
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(idleValidator, peerID),
|
||||
"validator was selected with insufficient number of peers");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().contains(idleValidator),
|
||||
"new validator was not added for consideration");
|
||||
|
||||
// simulate a validator idling
|
||||
stopwatch.advance(
|
||||
reduce_relay::MAX_UNTRUSTED_VALIDATOR_IDLE +
|
||||
std::chrono::seconds(1));
|
||||
BEAST_EXPECTS(
|
||||
!slots.updateConsideredValidator(validator, peerID),
|
||||
"validator was selected with insufficient number of peers");
|
||||
|
||||
slots.deleteIdlePeers();
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.getConsideredValidators().contains(idleValidator),
|
||||
"late validator was not removed");
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().contains(validator),
|
||||
"timely validator was removed");
|
||||
}
|
||||
|
||||
void
|
||||
testSquelchUntrustedValidator_consideredListCleared()
|
||||
{
|
||||
testcase("testSquelchUntrustedValidator");
|
||||
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
Peer::id_t const peerID = 0;
|
||||
|
||||
TestHandler handler{noop_handler};
|
||||
// verify that squelchAll is called for idle validator
|
||||
handler.squelchAll_f_ = [&](PublicKey const& actualKey,
|
||||
std::uint32_t duration,
|
||||
std::function<void(Peer::id_t)> callback) {
|
||||
BEAST_EXPECTS(
|
||||
actualKey == validator, "unexpected key passed to squelchAll");
|
||||
};
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
|
||||
// add the validator to the considered list
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator), validator, peerID);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getConsideredValidators().contains(validator),
|
||||
"validator was not added to considered list");
|
||||
|
||||
slots.squelchUntrustedValidator(validator);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.getConsideredValidators().contains(validator),
|
||||
"validator was not removed from considered list");
|
||||
}
|
||||
|
||||
void
|
||||
testSquelchUntrustedValidator_slotEvicted()
|
||||
{
|
||||
testcase("testSquelchUntrustedValidator_slotEvicted");
|
||||
|
||||
TestHandler handler{noop_handler};
|
||||
TestStopwatch stopwatch;
|
||||
EnhancedSquelchingTestSlots slots(
|
||||
env_.app().logs(), handler, env_.app().config(), stopwatch);
|
||||
|
||||
// assign a slot to the untrusted validator
|
||||
auto const validators = fillUntrustedSlots(slots, 1);
|
||||
|
||||
// verify that squelchAll is called for idle validator
|
||||
handler.squelchAll_f_ = [&](PublicKey const& actualKey,
|
||||
std::uint32_t duration,
|
||||
std::function<void(Peer::id_t)> callback) {
|
||||
BEAST_EXPECTS(
|
||||
actualKey == validators[0],
|
||||
"unexpected key passed to squelchAll");
|
||||
};
|
||||
|
||||
BEAST_EXPECTS(
|
||||
slots.getSlots(false).contains(validators[0]),
|
||||
"a slot was not assigned to a validator");
|
||||
|
||||
slots.squelchUntrustedValidator(validators[0]);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!slots.getSlots(false).contains(validators[0]),
|
||||
"a slot was not evicted");
|
||||
}
|
||||
|
||||
private:
|
||||
/** A helper method to fill untrusted slots of a given Slots instance
|
||||
* with random validator messages*/
|
||||
std::vector<PublicKey>
|
||||
fillUntrustedSlots(
|
||||
EnhancedSquelchingTestSlots& slots,
|
||||
int64_t maxSlots = reduce_relay::MAX_UNTRUSTED_SLOTS)
|
||||
{
|
||||
std::vector<PublicKey> keys;
|
||||
for (int i = 0; i < maxSlots; ++i)
|
||||
{
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
keys.push_back(validator);
|
||||
for (int j = 0; j <
|
||||
env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS;
|
||||
++j)
|
||||
// send enough messages so that a validator slot is selected
|
||||
for (int k = 0; k < reduce_relay::MAX_MESSAGE_THRESHOLD; ++k)
|
||||
slots.updateUntrustedValidatorSlot(
|
||||
sha512Half(validator) + static_cast<uint256>(k),
|
||||
validator,
|
||||
j);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
std::unordered_map<Peer::id_t, reduce_relay::Slot::PeerInfo>
|
||||
getUntrustedSlotPeers(
|
||||
PublicKey const& validator,
|
||||
EnhancedSquelchingTestSlots const& slots)
|
||||
{
|
||||
auto const& it = slots.getSlots(false).find(validator);
|
||||
if (it == slots.getSlots(false).end())
|
||||
return {};
|
||||
|
||||
auto r = std::unordered_map<Peer::id_t, reduce_relay::Slot::PeerInfo>();
|
||||
|
||||
for (auto const& [id, info] : it->second.getPeers())
|
||||
r.emplace(std::make_pair(id, info));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testConfig();
|
||||
testSquelchTracking();
|
||||
testUpdateValidatorSlot_newValidator();
|
||||
testUpdateValidatorSlot_slotsFull();
|
||||
testUpdateValidatorSlot_squelchedValidator();
|
||||
testDeleteIdlePeers_deleteIdleSlots();
|
||||
testDeleteIdlePeers_deleteIdleUntrustedPeer();
|
||||
testUpdateSlotAndSquelch_untrustedValidator();
|
||||
testUpdateConsideredValidator_new();
|
||||
testUpdateConsideredValidator_idle();
|
||||
testUpdateConsideredValidator_selectQualifying();
|
||||
testCleanConsideredValidators_deleteSilent();
|
||||
testCleanConsideredValidators_resetIdle();
|
||||
testCleanConsideredValidators_deletePoorlyConnected();
|
||||
testSquelchUntrustedValidator_consideredListCleared();
|
||||
testSquelchUntrustedValidator_slotEvicted();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(enhanced_squelch, overlay, ripple);
|
||||
|
||||
} // namespace test
|
||||
|
||||
} // namespace ripple
|
||||
@@ -17,22 +17,24 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/Slot.h>
|
||||
#include <xrpld/overlay/SquelchStore.h>
|
||||
#include <xrpld/overlay/Squelch.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/messages.h>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iterator>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
|
||||
@@ -42,21 +44,6 @@ namespace test {
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
template <class Clock>
|
||||
class extended_manual_clock : public beast::manual_clock<Clock>
|
||||
{
|
||||
public:
|
||||
using typename beast::manual_clock<Clock>::duration;
|
||||
using typename beast::manual_clock<Clock>::time_point;
|
||||
|
||||
void
|
||||
randAdvance(std::chrono::milliseconds min, std::chrono::milliseconds max)
|
||||
{
|
||||
auto ms = ripple::rand_int(min.count(), max.count());
|
||||
this->advance(std::chrono::milliseconds(ms));
|
||||
}
|
||||
};
|
||||
|
||||
class Link;
|
||||
|
||||
using MessageSPtr = std::shared_ptr<Message>;
|
||||
@@ -67,7 +54,6 @@ using SquelchCB =
|
||||
std::function<void(PublicKey const&, PeerWPtr const&, std::uint32_t)>;
|
||||
using UnsquelchCB = std::function<void(PublicKey const&, PeerWPtr const&)>;
|
||||
using LinkIterCB = std::function<void(Link&, MessageSPtr)>;
|
||||
using TestStopwatch = extended_manual_clock<std::chrono::steady_clock>;
|
||||
|
||||
static constexpr std::uint32_t MAX_PEERS = 10;
|
||||
static constexpr std::uint32_t MAX_VALIDATORS = 10;
|
||||
@@ -205,6 +191,52 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/** Manually advanced clock. */
|
||||
class ManualClock
|
||||
{
|
||||
public:
|
||||
typedef uint64_t rep;
|
||||
typedef std::milli period;
|
||||
typedef std::chrono::duration<std::uint32_t, period> duration;
|
||||
typedef std::chrono::time_point<ManualClock> time_point;
|
||||
inline static bool const is_steady = false;
|
||||
|
||||
static void
|
||||
advance(duration d) noexcept
|
||||
{
|
||||
now_ += d;
|
||||
}
|
||||
|
||||
static void
|
||||
randAdvance(milliseconds min, milliseconds max)
|
||||
{
|
||||
now_ += randDuration(min, max);
|
||||
}
|
||||
|
||||
static void
|
||||
reset() noexcept
|
||||
{
|
||||
now_ = time_point(seconds(0));
|
||||
}
|
||||
|
||||
static time_point
|
||||
now() noexcept
|
||||
{
|
||||
return now_;
|
||||
}
|
||||
|
||||
static duration
|
||||
randDuration(milliseconds min, milliseconds max)
|
||||
{
|
||||
return duration(milliseconds(rand_int(min.count(), max.count())));
|
||||
}
|
||||
|
||||
explicit ManualClock() = default;
|
||||
|
||||
private:
|
||||
inline static time_point now_ = time_point(seconds(0));
|
||||
};
|
||||
|
||||
/** Simulate server's OverlayImpl */
|
||||
class Overlay
|
||||
{
|
||||
@@ -217,20 +249,12 @@ public:
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t id,
|
||||
SquelchCB f) = 0;
|
||||
SquelchCB f,
|
||||
protocol::MessageType type = protocol::mtVALIDATION) = 0;
|
||||
|
||||
virtual void deleteIdlePeers(UnsquelchCB) = 0;
|
||||
|
||||
virtual void deletePeer(Peer::id_t, UnsquelchCB) = 0;
|
||||
|
||||
TestStopwatch&
|
||||
clock()
|
||||
{
|
||||
return clock_;
|
||||
}
|
||||
|
||||
protected:
|
||||
TestStopwatch clock_;
|
||||
};
|
||||
|
||||
class Validator;
|
||||
@@ -433,39 +457,19 @@ private:
|
||||
std::uint16_t id_ = 0;
|
||||
};
|
||||
|
||||
class BaseSquelchingTestSlots : public reduce_relay::Slots
|
||||
{
|
||||
using Slots = reduce_relay::Slots;
|
||||
|
||||
public:
|
||||
BaseSquelchingTestSlots(
|
||||
Logs& logs,
|
||||
reduce_relay::SquelchHandler& handler,
|
||||
Config const& config,
|
||||
reduce_relay::Slots::clock_type& clock)
|
||||
: Slots(logs, handler, config, clock)
|
||||
{
|
||||
}
|
||||
|
||||
Slots::slots_map const&
|
||||
getSlots() const
|
||||
{
|
||||
return trustedSlots_;
|
||||
}
|
||||
};
|
||||
|
||||
class PeerSim : public PeerPartial, public std::enable_shared_from_this<PeerSim>
|
||||
{
|
||||
public:
|
||||
using id_t = Peer::id_t;
|
||||
PeerSim(Overlay& overlay, beast::Journal journal)
|
||||
: overlay_(overlay), squelchStore_(journal, overlay_.clock())
|
||||
: overlay_(overlay), squelch_(journal)
|
||||
{
|
||||
id_ = sid_++;
|
||||
}
|
||||
|
||||
~PeerSim() = default;
|
||||
|
||||
Peer::id_t
|
||||
id_t
|
||||
id() const override
|
||||
{
|
||||
return id_;
|
||||
@@ -483,7 +487,7 @@ public:
|
||||
{
|
||||
auto validator = m->getValidatorKey();
|
||||
assert(validator);
|
||||
if (squelchStore_.isSquelched(*validator))
|
||||
if (!squelch_.expireSquelch(*validator))
|
||||
return;
|
||||
|
||||
overlay_.updateSlotAndSquelch({}, *validator, id(), f);
|
||||
@@ -495,17 +499,18 @@ public:
|
||||
{
|
||||
auto validator = squelch.validatorpubkey();
|
||||
PublicKey key(Slice(validator.data(), validator.size()));
|
||||
squelchStore_.handleSquelch(
|
||||
key,
|
||||
squelch.squelch(),
|
||||
std::chrono::seconds{squelch.squelchduration()});
|
||||
if (squelch.squelch())
|
||||
squelch_.addSquelch(
|
||||
key, std::chrono::seconds{squelch.squelchduration()});
|
||||
else
|
||||
squelch_.removeSquelch(key);
|
||||
}
|
||||
|
||||
private:
|
||||
inline static Peer::id_t sid_ = 0;
|
||||
Peer::id_t id_;
|
||||
inline static id_t sid_ = 0;
|
||||
id_t id_;
|
||||
Overlay& overlay_;
|
||||
reduce_relay::SquelchStore squelchStore_;
|
||||
reduce_relay::Squelch<ManualClock> squelch_;
|
||||
};
|
||||
|
||||
class OverlaySim : public Overlay, public reduce_relay::SquelchHandler
|
||||
@@ -513,9 +518,10 @@ class OverlaySim : public Overlay, public reduce_relay::SquelchHandler
|
||||
using Peers = std::unordered_map<Peer::id_t, PeerSPtr>;
|
||||
|
||||
public:
|
||||
using clock_type = TestStopwatch;
|
||||
using id_t = Peer::id_t;
|
||||
using clock_type = ManualClock;
|
||||
OverlaySim(Application& app)
|
||||
: slots_(app.logs(), *this, app.config(), clock_), logs_(app.logs())
|
||||
: slots_(app.logs(), *this, app.config()), logs_(app.logs())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -525,21 +531,15 @@ public:
|
||||
clear()
|
||||
{
|
||||
peers_.clear();
|
||||
clock_.advance(hours(1));
|
||||
ManualClock::advance(hours(1));
|
||||
slots_.deleteIdlePeers();
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
inState(PublicKey const& validator, reduce_relay::PeerState state)
|
||||
{
|
||||
auto const& it = slots_.getSlots().find(validator);
|
||||
if (it != slots_.getSlots().end())
|
||||
return std::count_if(
|
||||
it->second.getPeers().begin(),
|
||||
it->second.getPeers().end(),
|
||||
[&](auto const& it) { return (it.second.state == state); });
|
||||
|
||||
return 0;
|
||||
auto res = slots_.inState(validator, state);
|
||||
return res ? *res : 0;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -547,14 +547,15 @@ public:
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t id,
|
||||
SquelchCB f) override
|
||||
SquelchCB f,
|
||||
protocol::MessageType type = protocol::mtVALIDATION) override
|
||||
{
|
||||
squelch_ = f;
|
||||
slots_.updateSlotAndSquelch(key, validator, id, true);
|
||||
slots_.updateSlotAndSquelch(key, validator, id, type);
|
||||
}
|
||||
|
||||
void
|
||||
deletePeer(Peer::id_t id, UnsquelchCB f) override
|
||||
deletePeer(id_t id, UnsquelchCB f) override
|
||||
{
|
||||
unsquelch_ = f;
|
||||
slots_.deletePeer(id, true);
|
||||
@@ -631,55 +632,40 @@ public:
|
||||
bool
|
||||
isCountingState(PublicKey const& validator)
|
||||
{
|
||||
auto const& it = slots_.getSlots().find(validator);
|
||||
if (it != slots_.getSlots().end())
|
||||
return it->second.getState() == reduce_relay::SlotState::Counting;
|
||||
|
||||
return false;
|
||||
return slots_.inState(validator, reduce_relay::SlotState::Counting);
|
||||
}
|
||||
|
||||
std::set<Peer::id_t>
|
||||
std::set<id_t>
|
||||
getSelected(PublicKey const& validator)
|
||||
{
|
||||
auto const& it = slots_.getSlots().find(validator);
|
||||
if (it == slots_.getSlots().end())
|
||||
return {};
|
||||
|
||||
std::set<Peer::id_t> r;
|
||||
for (auto const& [id, info] : it->second.getPeers())
|
||||
if (info.state == reduce_relay::PeerState::Selected)
|
||||
r.insert(id);
|
||||
|
||||
return r;
|
||||
return slots_.getSelected(validator);
|
||||
}
|
||||
|
||||
bool
|
||||
isSelected(PublicKey const& validator, Peer::id_t peer)
|
||||
{
|
||||
auto selected = getSelected(validator);
|
||||
auto selected = slots_.getSelected(validator);
|
||||
return selected.find(peer) != selected.end();
|
||||
}
|
||||
|
||||
Peer::id_t
|
||||
id_t
|
||||
getSelectedPeer(PublicKey const& validator)
|
||||
{
|
||||
auto selected = getSelected(validator);
|
||||
auto selected = slots_.getSelected(validator);
|
||||
assert(selected.size());
|
||||
return *selected.begin();
|
||||
}
|
||||
|
||||
std::unordered_map<Peer::id_t, reduce_relay::Slot::PeerInfo>
|
||||
std::unordered_map<
|
||||
id_t,
|
||||
std::tuple<
|
||||
reduce_relay::PeerState,
|
||||
std::uint16_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t>>
|
||||
getPeers(PublicKey const& validator)
|
||||
{
|
||||
auto const& it = slots_.getSlots().find(validator);
|
||||
if (it == slots_.getSlots().end())
|
||||
return {};
|
||||
|
||||
auto r = std::unordered_map<Peer::id_t, reduce_relay::Slot::PeerInfo>();
|
||||
for (auto const& [id, info] : it->second.getPeers())
|
||||
r.emplace(std::make_pair(id, info));
|
||||
|
||||
return r;
|
||||
return slots_.getPeers(validator);
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
@@ -698,31 +684,17 @@ private:
|
||||
if (auto it = peers_.find(id); it != peers_.end())
|
||||
squelch_(validator, it->second, squelchDuration);
|
||||
}
|
||||
|
||||
void
|
||||
squelchAll(
|
||||
PublicKey const& validator,
|
||||
std::uint32_t duration,
|
||||
std::function<void(Peer::id_t)> callback) override
|
||||
{
|
||||
for (auto const& [id, peer] : peers_)
|
||||
{
|
||||
squelch_(validator, peer, duration);
|
||||
callback(id);
|
||||
}
|
||||
}
|
||||
void
|
||||
unsquelch(PublicKey const& validator, Peer::id_t id) const override
|
||||
{
|
||||
if (auto it = peers_.find(id); it != peers_.end())
|
||||
unsquelch_(validator, it->second);
|
||||
}
|
||||
|
||||
SquelchCB squelch_;
|
||||
UnsquelchCB unsquelch_;
|
||||
Peers peers_;
|
||||
Peers peersCache_;
|
||||
BaseSquelchingTestSlots slots_;
|
||||
reduce_relay::Slots<ManualClock> slots_;
|
||||
Logs& logs_;
|
||||
};
|
||||
|
||||
@@ -854,8 +826,12 @@ public:
|
||||
LinkIterCB link,
|
||||
std::uint16_t nValidators = MAX_VALIDATORS,
|
||||
std::uint32_t nMessages = MAX_MESSAGES,
|
||||
bool purge = true)
|
||||
bool purge = true,
|
||||
bool resetClock = true)
|
||||
{
|
||||
if (resetClock)
|
||||
ManualClock::reset();
|
||||
|
||||
if (purge)
|
||||
{
|
||||
purgePeers();
|
||||
@@ -864,8 +840,7 @@ public:
|
||||
|
||||
for (int m = 0; m < nMessages; ++m)
|
||||
{
|
||||
overlay_.clock().randAdvance(
|
||||
milliseconds(1800), milliseconds(2200));
|
||||
ManualClock::randAdvance(milliseconds(1800), milliseconds(2200));
|
||||
for_rand(0, nValidators, [&](std::uint32_t v) {
|
||||
validators_[v].for_links(link);
|
||||
});
|
||||
@@ -899,7 +874,8 @@ public:
|
||||
for (auto& [_, v] : peers)
|
||||
{
|
||||
(void)_;
|
||||
if (v.state == reduce_relay::PeerState::Squelched)
|
||||
if (std::get<reduce_relay::PeerState>(v) ==
|
||||
reduce_relay::PeerState::Squelched)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -911,9 +887,10 @@ private:
|
||||
std::vector<Validator> validators_;
|
||||
};
|
||||
|
||||
class base_squelch_test : public beast::unit_test::suite
|
||||
class reduce_relay_test : public beast::unit_test::suite
|
||||
{
|
||||
using Slot = reduce_relay::Slot;
|
||||
using Slot = reduce_relay::Slot<ManualClock>;
|
||||
using id_t = Peer::id_t;
|
||||
|
||||
protected:
|
||||
void
|
||||
@@ -924,7 +901,8 @@ protected:
|
||||
<< "num peers " << (int)network_.overlay().getNumPeers()
|
||||
<< std::endl;
|
||||
for (auto& [k, v] : peers)
|
||||
std::cout << k << ":" << to_string(v.state) << " ";
|
||||
std::cout << k << ":" << (int)std::get<reduce_relay::PeerState>(v)
|
||||
<< " ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
@@ -962,7 +940,7 @@ protected:
|
||||
Peer::id_t peer_;
|
||||
std::uint16_t validator_;
|
||||
std::optional<PublicKey> key_;
|
||||
TestStopwatch::time_point time_;
|
||||
time_point<ManualClock> time_;
|
||||
bool handled_ = false;
|
||||
};
|
||||
|
||||
@@ -974,12 +952,12 @@ protected:
|
||||
{
|
||||
std::unordered_map<EventType, Event> events{
|
||||
{LinkDown, {}}, {PeerDisconnected, {}}};
|
||||
auto lastCheck = network_.overlay().clock().now();
|
||||
time_point<ManualClock> lastCheck = ManualClock::now();
|
||||
|
||||
network_.reset();
|
||||
network_.propagate([&](Link& link, MessageSPtr m) {
|
||||
auto& validator = link.validator();
|
||||
auto const now = network_.overlay().clock().now();
|
||||
auto now = ManualClock::now();
|
||||
|
||||
bool squelched = false;
|
||||
std::stringstream str;
|
||||
@@ -1003,8 +981,7 @@ protected:
|
||||
str << s << " ";
|
||||
if (log)
|
||||
std::cout
|
||||
<< (double)std::chrono::duration_cast<milliseconds>(
|
||||
now.time_since_epoch())
|
||||
<< (double)reduce_relay::epoch<milliseconds>(now)
|
||||
.count() /
|
||||
1000.
|
||||
<< " random, squelched, validator: " << validator.id()
|
||||
@@ -1096,17 +1073,10 @@ protected:
|
||||
event.isSelected_ =
|
||||
network_.overlay().isSelected(*event.key_, event.peer_);
|
||||
auto peers = network_.overlay().getPeers(*event.key_);
|
||||
|
||||
auto d =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch())
|
||||
.count() -
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
peers[event.peer_].lastMessage.time_since_epoch())
|
||||
.count();
|
||||
|
||||
auto d = reduce_relay::epoch<milliseconds>(now).count() -
|
||||
std::get<3>(peers[event.peer_]);
|
||||
mustHandle = event.isSelected_ &&
|
||||
d > milliseconds(reduce_relay::PEER_IDLED).count() &&
|
||||
d > milliseconds(reduce_relay::IDLED).count() &&
|
||||
network_.overlay().inState(
|
||||
*event.key_, reduce_relay::PeerState::Squelched) >
|
||||
0 &&
|
||||
@@ -1128,7 +1098,7 @@ protected:
|
||||
}
|
||||
if (event.state_ == State::WaitReset ||
|
||||
(event.state_ == State::On &&
|
||||
(now - event.time_ > (reduce_relay::PEER_IDLED + seconds(2)))))
|
||||
(now - event.time_ > (reduce_relay::IDLED + seconds(2)))))
|
||||
{
|
||||
bool handled =
|
||||
event.state_ == State::WaitReset || !event.handled_;
|
||||
@@ -1158,6 +1128,7 @@ protected:
|
||||
checkCounting(PublicKey const& validator, bool isCountingState)
|
||||
{
|
||||
auto countingState = network_.overlay().isCountingState(validator);
|
||||
BEAST_EXPECT(countingState == isCountingState);
|
||||
return countingState == isCountingState;
|
||||
}
|
||||
|
||||
@@ -1188,7 +1159,7 @@ protected:
|
||||
testPeerUnsquelchedTooSoon(bool log)
|
||||
{
|
||||
doTest("Peer Unsquelched Too Soon", log, [this](bool log) {
|
||||
BEAST_EXPECT(propagateNoSquelch(log, 1, false, false));
|
||||
BEAST_EXPECT(propagateNoSquelch(log, 1, false, false, false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1198,17 +1169,17 @@ protected:
|
||||
void
|
||||
testPeerUnsquelched(bool log)
|
||||
{
|
||||
network_.overlay().clock().advance(seconds(601));
|
||||
ManualClock::advance(seconds(601));
|
||||
doTest("Peer Unsquelched", log, [this](bool log) {
|
||||
BEAST_EXPECT(propagateNoSquelch(log, 2, true, true));
|
||||
BEAST_EXPECT(propagateNoSquelch(log, 2, true, true, false));
|
||||
});
|
||||
}
|
||||
|
||||
/** Propagate enough messages to generate one squelch event */
|
||||
bool
|
||||
propagateAndSquelch(bool log, bool purge = true)
|
||||
propagateAndSquelch(bool log, bool purge = true, bool resetClock = true)
|
||||
{
|
||||
int squelchEvents = 0;
|
||||
int n = 0;
|
||||
network_.propagate(
|
||||
[&](Link& link, MessageSPtr message) {
|
||||
std::uint16_t squelched = 0;
|
||||
@@ -1228,21 +1199,21 @@ protected:
|
||||
env_.app()
|
||||
.config()
|
||||
.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS);
|
||||
squelchEvents++;
|
||||
n++;
|
||||
}
|
||||
},
|
||||
1,
|
||||
reduce_relay::MAX_MESSAGE_THRESHOLD + 2,
|
||||
purge);
|
||||
purge,
|
||||
resetClock);
|
||||
auto selected = network_.overlay().getSelected(network_.validator(0));
|
||||
BEAST_EXPECT(
|
||||
selected.size() ==
|
||||
env_.app().config().VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS);
|
||||
|
||||
BEAST_EXPECT(squelchEvents == 1); // only one selection round
|
||||
BEAST_EXPECT(n == 1); // only one selection round
|
||||
auto res = checkCounting(network_.validator(0), false);
|
||||
BEAST_EXPECT(res);
|
||||
return squelchEvents == 1 && res;
|
||||
return n == 1 && res;
|
||||
}
|
||||
|
||||
/** Send fewer message so that squelch event is not generated */
|
||||
@@ -1251,7 +1222,8 @@ protected:
|
||||
bool log,
|
||||
std::uint16_t nMessages,
|
||||
bool countingState,
|
||||
bool purge = true)
|
||||
bool purge = true,
|
||||
bool resetClock = true)
|
||||
{
|
||||
bool squelched = false;
|
||||
network_.propagate(
|
||||
@@ -1267,9 +1239,9 @@ protected:
|
||||
},
|
||||
1,
|
||||
nMessages,
|
||||
purge);
|
||||
purge,
|
||||
resetClock);
|
||||
auto res = checkCounting(network_.validator(0), countingState);
|
||||
BEAST_EXPECT(res);
|
||||
return !squelched && res;
|
||||
}
|
||||
|
||||
@@ -1280,9 +1252,9 @@ protected:
|
||||
testNewPeer(bool log)
|
||||
{
|
||||
doTest("New Peer", log, [this](bool log) {
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true));
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true, false));
|
||||
network_.addPeer();
|
||||
BEAST_EXPECT(propagateNoSquelch(log, 1, true, false));
|
||||
BEAST_EXPECT(propagateNoSquelch(log, 1, true, false, false));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1292,8 +1264,8 @@ protected:
|
||||
testSelectedPeerDisconnects(bool log)
|
||||
{
|
||||
doTest("Selected Peer Disconnects", log, [this](bool log) {
|
||||
network_.overlay().clock().advance(seconds(601));
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true));
|
||||
ManualClock::advance(seconds(601));
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true, false));
|
||||
auto id = network_.overlay().getSelectedPeer(network_.validator(0));
|
||||
std::uint16_t unsquelched = 0;
|
||||
network_.overlay().deletePeer(
|
||||
@@ -1316,16 +1288,15 @@ protected:
|
||||
testSelectedPeerStopsRelaying(bool log)
|
||||
{
|
||||
doTest("Selected Peer Stops Relaying", log, [this](bool log) {
|
||||
network_.overlay().clock().advance(seconds(601));
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true));
|
||||
network_.overlay().clock().advance(
|
||||
reduce_relay::PEER_IDLED + seconds(1));
|
||||
ManualClock::advance(seconds(601));
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true, false));
|
||||
ManualClock::advance(reduce_relay::IDLED + seconds(1));
|
||||
std::uint16_t unsquelched = 0;
|
||||
network_.overlay().deleteIdlePeers(
|
||||
[&](PublicKey const& key, PeerWPtr const& peer) {
|
||||
unsquelched++;
|
||||
});
|
||||
|
||||
auto peers = network_.overlay().getPeers(network_.validator(0));
|
||||
BEAST_EXPECT(
|
||||
unsquelched ==
|
||||
MAX_PEERS -
|
||||
@@ -1342,11 +1313,12 @@ protected:
|
||||
testSquelchedPeerDisconnects(bool log)
|
||||
{
|
||||
doTest("Squelched Peer Disconnects", log, [this](bool log) {
|
||||
network_.overlay().clock().advance(seconds(601));
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true));
|
||||
ManualClock::advance(seconds(601));
|
||||
BEAST_EXPECT(propagateAndSquelch(log, true, false));
|
||||
auto peers = network_.overlay().getPeers(network_.validator(0));
|
||||
auto it = std::find_if(peers.begin(), peers.end(), [&](auto it) {
|
||||
return it.second.state == reduce_relay::PeerState::Squelched;
|
||||
return std::get<reduce_relay::PeerState>(it.second) ==
|
||||
reduce_relay::PeerState::Squelched;
|
||||
});
|
||||
assert(it != peers.end());
|
||||
std::uint16_t unsquelched = 0;
|
||||
@@ -1497,34 +1469,29 @@ vp_base_squelch_max_selected_peers=2
|
||||
testBaseSquelchReady(bool log)
|
||||
{
|
||||
doTest("BaseSquelchReady", log, [&](bool log) {
|
||||
auto createSlots =
|
||||
[&](bool baseSquelchEnabled,
|
||||
TestStopwatch stopwatch) -> reduce_relay::Slots {
|
||||
ManualClock::reset();
|
||||
auto createSlots = [&](bool baseSquelchEnabled)
|
||||
-> reduce_relay::Slots<ManualClock> {
|
||||
env_.app().config().VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE =
|
||||
baseSquelchEnabled;
|
||||
return reduce_relay::Slots(
|
||||
env_.app().logs(),
|
||||
network_.overlay(),
|
||||
env_.app().config(),
|
||||
stopwatch);
|
||||
return reduce_relay::Slots<ManualClock>(
|
||||
env_.app().logs(), network_.overlay(), env_.app().config());
|
||||
};
|
||||
|
||||
TestStopwatch stopwatch;
|
||||
// base squelching must not be ready if squelching is disabled
|
||||
BEAST_EXPECT(!createSlots(false, stopwatch).baseSquelchReady());
|
||||
BEAST_EXPECT(!createSlots(false).baseSquelchReady());
|
||||
|
||||
// base squelch must not be ready as not enough time passed from
|
||||
// bootup
|
||||
BEAST_EXPECT(!createSlots(true, stopwatch).baseSquelchReady());
|
||||
BEAST_EXPECT(!createSlots(true).baseSquelchReady());
|
||||
|
||||
stopwatch.advance(reduce_relay::WAIT_ON_BOOTUP + minutes{1});
|
||||
ManualClock::advance(reduce_relay::WAIT_ON_BOOTUP + minutes{1});
|
||||
|
||||
// base squelch enabled and bootup time passed
|
||||
BEAST_EXPECT(createSlots(true, stopwatch).baseSquelchReady());
|
||||
BEAST_EXPECT(createSlots(true).baseSquelchReady());
|
||||
|
||||
// even if time passed, base squelching must not be ready if turned
|
||||
// off in the config
|
||||
BEAST_EXPECT(!createSlots(false, stopwatch).baseSquelchReady());
|
||||
BEAST_EXPECT(!createSlots(false).baseSquelchReady());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1547,7 +1514,7 @@ vp_base_squelch_max_selected_peers=2
|
||||
auto peers = network_.overlay().getPeers(network_.validator(0));
|
||||
// first message changes Slot state to Counting and is not counted,
|
||||
// hence '-1'.
|
||||
BEAST_EXPECT(peers[0].count == (nMessages - 1));
|
||||
BEAST_EXPECT(std::get<1>(peers[0]) == (nMessages - 1));
|
||||
// add duplicate
|
||||
uint256 key(nMessages - 1);
|
||||
network_.overlay().updateSlotAndSquelch(
|
||||
@@ -1557,10 +1524,9 @@ vp_base_squelch_max_selected_peers=2
|
||||
[&](PublicKey const&, PeerWPtr, std::uint32_t) {});
|
||||
// confirm the same number of messages
|
||||
peers = network_.overlay().getPeers(network_.validator(0));
|
||||
BEAST_EXPECT(peers[0].count == (nMessages - 1));
|
||||
BEAST_EXPECT(std::get<1>(peers[0]) == (nMessages - 1));
|
||||
// advance the clock
|
||||
network_.overlay().clock().advance(
|
||||
reduce_relay::PEER_IDLED + seconds(1));
|
||||
ManualClock::advance(reduce_relay::IDLED + seconds(1));
|
||||
network_.overlay().updateSlotAndSquelch(
|
||||
key,
|
||||
network_.validator(0),
|
||||
@@ -1568,7 +1534,7 @@ vp_base_squelch_max_selected_peers=2
|
||||
[&](PublicKey const&, PeerWPtr, std::uint32_t) {});
|
||||
peers = network_.overlay().getPeers(network_.validator(0));
|
||||
// confirm message number increased
|
||||
BEAST_EXPECT(peers[0].count == nMessages);
|
||||
BEAST_EXPECT(std::get<1>(peers[0]) == nMessages);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1584,15 +1550,6 @@ vp_base_squelch_max_selected_peers=2
|
||||
if (duration > maxDuration_)
|
||||
maxDuration_ = duration;
|
||||
}
|
||||
|
||||
void
|
||||
squelchAll(
|
||||
PublicKey const&,
|
||||
std::uint32_t,
|
||||
std::function<void(Peer::id_t)>) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
unsquelch(PublicKey const&, Peer::id_t) const override
|
||||
{
|
||||
@@ -1609,11 +1566,8 @@ vp_base_squelch_max_selected_peers=2
|
||||
|
||||
auto run = [&](int npeers) {
|
||||
handler.maxDuration_ = 0;
|
||||
reduce_relay::Slots slots(
|
||||
env_.app().logs(),
|
||||
handler,
|
||||
env_.app().config(),
|
||||
network_.overlay().clock());
|
||||
reduce_relay::Slots<ManualClock> slots(
|
||||
env_.app().logs(), handler, env_.app().config());
|
||||
// 1st message from a new peer switches the slot
|
||||
// to counting state and resets the counts of all peers +
|
||||
// MAX_MESSAGE_THRESHOLD + 1 messages to reach the threshold
|
||||
@@ -1628,11 +1582,14 @@ vp_base_squelch_max_selected_peers=2
|
||||
std::uint64_t mid = m * 1000 + peer;
|
||||
uint256 const message{mid};
|
||||
slots.updateSlotAndSquelch(
|
||||
message, validator, peer, true);
|
||||
message,
|
||||
validator,
|
||||
peer,
|
||||
protocol::MessageType::mtVALIDATION);
|
||||
}
|
||||
}
|
||||
// make Slot's internal hash router expire all messages
|
||||
network_.overlay().clock().advance(hours(1));
|
||||
ManualClock::advance(hours(1));
|
||||
};
|
||||
|
||||
using namespace reduce_relay;
|
||||
@@ -1746,7 +1703,7 @@ vp_base_squelch_max_selected_peers=2
|
||||
Network network_;
|
||||
|
||||
public:
|
||||
base_squelch_test()
|
||||
reduce_relay_test()
|
||||
: env_(*this, jtx::envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE = true;
|
||||
cfg->VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS = 6;
|
||||
@@ -1775,7 +1732,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class base_squelch_simulate_test : public base_squelch_test
|
||||
class reduce_relay_simulate_test : public reduce_relay_test
|
||||
{
|
||||
void
|
||||
testRandom(bool log)
|
||||
@@ -1791,8 +1748,8 @@ class base_squelch_simulate_test : public base_squelch_test
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(base_squelch, ripple_data, ripple);
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(base_squelch_simulate, ripple_data, ripple);
|
||||
BEAST_DEFINE_TESTSUITE(reduce_relay, ripple_data, ripple);
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL(reduce_relay_simulate, ripple_data, ripple);
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/SquelchStore.h>
|
||||
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace test {
|
||||
|
||||
class TestSquelchStore : public reduce_relay::SquelchStore
|
||||
{
|
||||
public:
|
||||
TestSquelchStore(beast::Journal journal, TestStopwatch& clock)
|
||||
: reduce_relay::SquelchStore(journal, clock)
|
||||
{
|
||||
}
|
||||
|
||||
hash_map<PublicKey, TestStopwatch::time_point> const&
|
||||
getSquelched() const
|
||||
{
|
||||
return squelched_;
|
||||
}
|
||||
};
|
||||
|
||||
class squelch_store_test : public beast::unit_test::suite
|
||||
{
|
||||
using seconds = std::chrono::seconds;
|
||||
|
||||
public:
|
||||
jtx::Env env_;
|
||||
|
||||
squelch_store_test() : env_(*this)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
testHandleSquelch()
|
||||
{
|
||||
testcase("SquelchStore handleSquelch");
|
||||
|
||||
TestStopwatch clock;
|
||||
auto store = TestSquelchStore(env_.journal, clock);
|
||||
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
|
||||
// attempt to squelch the peer with a too small duration
|
||||
store.handleSquelch(
|
||||
validator, true, reduce_relay::MIN_UNSQUELCH_EXPIRE - seconds{1});
|
||||
|
||||
// the peer must not be squelched
|
||||
BEAST_EXPECTS(!store.isSquelched(validator), "peer is squelched");
|
||||
|
||||
// attempt to squelch the peer with a too big duration
|
||||
store.handleSquelch(
|
||||
validator,
|
||||
true,
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_PEERS + seconds{1});
|
||||
|
||||
// the peer must not be squelched
|
||||
BEAST_EXPECTS(!store.isSquelched(validator), "peer is squelched");
|
||||
|
||||
// squelch the peer with a good duration
|
||||
store.handleSquelch(
|
||||
validator, true, reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{1});
|
||||
|
||||
// the peer for the validator should be squelched
|
||||
BEAST_EXPECTS(
|
||||
store.isSquelched(validator),
|
||||
"peer and validator are not squelched");
|
||||
|
||||
// unsquelch the validator
|
||||
store.handleSquelch(validator, false, seconds{0});
|
||||
|
||||
BEAST_EXPECTS(!store.isSquelched(validator), "peer is squelched");
|
||||
}
|
||||
|
||||
void
|
||||
testIsSquelched()
|
||||
{
|
||||
testcase("SquelchStore IsSquelched");
|
||||
TestStopwatch clock;
|
||||
auto store = TestSquelchStore(env_.journal, clock);
|
||||
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
auto const duration = reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{1};
|
||||
|
||||
store.handleSquelch(
|
||||
validator, true, reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{1});
|
||||
BEAST_EXPECTS(
|
||||
store.isSquelched(validator),
|
||||
"peer and validator are not squelched");
|
||||
|
||||
clock.advance(duration + seconds{1});
|
||||
|
||||
// the peer with short squelch duration must be not squelched
|
||||
BEAST_EXPECTS(
|
||||
!store.isSquelched(validator), "peer and validator are squelched");
|
||||
}
|
||||
|
||||
void
|
||||
testClearExpiredSquelches()
|
||||
{
|
||||
testcase("SquelchStore testClearExpiredSquelches");
|
||||
TestStopwatch clock;
|
||||
auto store = TestSquelchStore(env_.journal, clock);
|
||||
|
||||
auto const validator = randomKeyPair(KeyType::ed25519).first;
|
||||
auto const duration = reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{1};
|
||||
store.handleSquelch(validator, true, duration);
|
||||
BEAST_EXPECTS(
|
||||
store.getSquelched().size() == 1,
|
||||
"validators were not registered in the store");
|
||||
|
||||
clock.advance(duration + seconds{1});
|
||||
|
||||
auto const validator2 = randomKeyPair(KeyType::ed25519).first;
|
||||
auto const duration2 = reduce_relay::MIN_UNSQUELCH_EXPIRE + seconds{2};
|
||||
store.handleSquelch(validator2, true, duration2);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!store.getSquelched().contains(validator),
|
||||
"expired squelch was not deleted");
|
||||
|
||||
BEAST_EXPECTS(
|
||||
store.getSquelched().contains(validator2),
|
||||
"validators were not registered in the store");
|
||||
}
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testHandleSquelch();
|
||||
testIsSquelched();
|
||||
testClearExpiredSquelches();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(squelch_store, ripple_data, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -254,9 +254,6 @@ public:
|
||||
std::size_t VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS = 5;
|
||||
///////////////// END OF TEMPORARY CODE BLOCK /////////////////////
|
||||
|
||||
// Enable enhanced squelching of unique untrusted validator messages
|
||||
bool VP_REDUCE_RELAY_ENHANCED_SQUELCH_ENABLE = false;
|
||||
|
||||
// Transaction reduce-relay feature
|
||||
bool TX_REDUCE_RELAY_ENABLE = false;
|
||||
// If tx reduce-relay feature is disabled
|
||||
|
||||
@@ -775,9 +775,6 @@ Config::loadFromString(std::string const& fileContents)
|
||||
"greater than or equal to 3");
|
||||
///////////////// !!END OF TEMPORARY CODE BLOCK!! /////////////////////
|
||||
|
||||
VP_REDUCE_RELAY_ENHANCED_SQUELCH_ENABLE =
|
||||
sec.value_or("vp_enhanced_squelch_enable", false);
|
||||
|
||||
TX_REDUCE_RELAY_ENABLE = sec.value_or("tx_enable", false);
|
||||
TX_REDUCE_RELAY_METRICS = sec.value_or("tx_metrics", false);
|
||||
TX_REDUCE_RELAY_MIN_PEERS = sec.value_or("tx_min_peers", 20);
|
||||
|
||||
@@ -72,6 +72,7 @@ Previous-Ledger: q4aKbP7sd5wv+EXArwCmQiWZhq9AwBl2p/hCtpGJNsc=
|
||||
|
||||
##### Example HTTP Upgrade Response (Success)
|
||||
|
||||
|
||||
```
|
||||
HTTP/1.1 101 Switching Protocols
|
||||
Connection: Upgrade
|
||||
@@ -101,9 +102,9 @@ Content-Type: application/json
|
||||
|
||||
#### Standard Fields
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `User-Agent` | :heavy_check_mark: | |
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `User-Agent` | :heavy_check_mark: | |
|
||||
|
||||
The `User-Agent` field indicates the version of the software that the
|
||||
peer that is making the HTTP request is using. No semantic meaning is
|
||||
@@ -112,9 +113,9 @@ specify the version of the software that is used.
|
||||
|
||||
See [RFC2616 §14.43](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43).
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Server` | | :heavy_check_mark: |
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Server` | | :heavy_check_mark: |
|
||||
|
||||
The `Server` field indicates the version of the software that the
|
||||
peer that is processing the HTTP request is using. No semantic meaning is
|
||||
@@ -123,18 +124,18 @@ specify the version of the software that is used.
|
||||
|
||||
See [RFC2616 §14.38](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.38).
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Connection` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Connection` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
The `Connection` field should have a value of `Upgrade` to indicate that a
|
||||
request to upgrade the connection is being performed.
|
||||
|
||||
See [RFC2616 §14.10](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10).
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Upgrade` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Upgrade` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
The `Upgrade` field is part of the standard connection upgrade mechanism and
|
||||
must be present in both requests and responses. It is used to negotiate the
|
||||
@@ -155,11 +156,12 @@ equal to 2 and the minor is greater than or equal to 0.
|
||||
|
||||
See [RFC 2616 §14.42](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.42)
|
||||
|
||||
|
||||
#### Custom Fields
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Connect-As` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Connect-As` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
The mandatory `Connect-As` field is used to specify that type of connection
|
||||
that is being requested.
|
||||
@@ -173,9 +175,10 @@ elements specified in the request. If a server processing a request does not
|
||||
recognize any of the connection types, the request should fail with an
|
||||
appropriate HTTP error code (e.g. by sending an HTTP 400 "Bad Request" response).
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Remote-IP` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Remote-IP` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
The optional `Remote-IP` field contains the string representation of the IP
|
||||
address of the remote end of the connection as seen from the peer that is
|
||||
@@ -184,9 +187,10 @@ sending the field.
|
||||
By observing values of this field from a sufficient number of different
|
||||
servers, a peer making outgoing connections can deduce its own IP address.
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Local-IP` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Local-IP` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
The optional `Local-IP` field contains the string representation of the IP
|
||||
address that the peer sending the field believes to be its own.
|
||||
@@ -194,9 +198,10 @@ address that the peer sending the field believes to be its own.
|
||||
Servers receiving this field can detect IP address mismatches, which may
|
||||
indicate a potential man-in-the-middle attack.
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Network-ID` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Network-ID` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
The optional `Network-ID` can be used to identify to which of several
|
||||
[parallel networks](https://xrpl.org/parallel-networks.html) the server
|
||||
@@ -212,9 +217,10 @@ If a server configured to join one network receives a connection request from a
|
||||
server configured to join another network, the request should fail with an
|
||||
appropriate HTTP error code (e.g. by sending an HTTP 400 "Bad Request" response).
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Network-Time` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Network-Time` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
The optional `Network-Time` field reports the current [time](https://xrpl.org/basic-data-types.html#specifying-time)
|
||||
according to sender's internal clock.
|
||||
@@ -226,18 +232,20 @@ each other with an appropriate HTTP error code (e.g. by sending an HTTP 400
|
||||
It is highly recommended that servers synchronize their clocks using time
|
||||
synchronization software. For more on this topic, please visit [ntp.org](http://www.ntp.org/).
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Public-Key` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Public-Key` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
The mandatory `Public-Key` field identifies the sending server's public key,
|
||||
encoded in base58 using the standard encoding for node public keys.
|
||||
|
||||
See: <https://xrpl.org/base58-encodings.html>
|
||||
See: https://xrpl.org/base58-encodings.html
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Server-Domain` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Server-Domain` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
The optional `Server-Domain` field allows a server to report the domain that
|
||||
it is operating under. The value is configured by the server administrator in
|
||||
@@ -251,9 +259,10 @@ under the specified domain and locating the public key of this server under the
|
||||
|
||||
Sending a malformed domain will prevent a connection from being established.
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Session-Signature` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Session-Signature` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
The `Session-Signature` field is mandatory and is used to secure the peer link
|
||||
against certain types of attack. For more details see "Session Signature" below.
|
||||
@@ -263,35 +272,36 @@ should support both **Base64** and **HEX** encoding for this value.
|
||||
|
||||
For more details on this field, please see **Session Signature** below.
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Crawl` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Crawl` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
The optional `Crawl` field can be used by a server to indicate whether peers
|
||||
should include it in crawl reports.
|
||||
|
||||
The field can take two values:
|
||||
|
||||
- **`Public`**: The server's IP address and port should be included in crawl
|
||||
reports.
|
||||
- **`Private`**: The server's IP address and port should not be included in
|
||||
crawl reports. _This is the default, if the field is omitted._
|
||||
|
||||
For more on the Peer Crawler, please visit <https://xrpl.org/peer-crawler.html>.
|
||||
For more on the Peer Crawler, please visit https://xrpl.org/peer-crawler.html.
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Closed-Ledger` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Closed-Ledger` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
If present, identifies the hash of the last ledger that the sending server
|
||||
considers to be closed.
|
||||
|
||||
The value is encoded as **HEX**, but implementations should support both
|
||||
**Base64** and **HEX** encoding for this value for legacy purposes.
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Previous-Ledger` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
| Field Name | Request | Response |
|
||||
|--------------------- |:-----------------: |:-----------------: |
|
||||
| `Previous-Ledger` | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
If present, identifies the hash of the parent ledger that the sending server
|
||||
considers to be closed.
|
||||
@@ -307,6 +317,7 @@ and values in both requests and responses.
|
||||
Implementations should not reject requests because of the presence of fields
|
||||
that they do not understand.
|
||||
|
||||
|
||||
### Session Signature
|
||||
|
||||
Even for SSL/TLS encrypted connections, it is possible for an attacker to mount
|
||||
@@ -354,52 +365,10 @@ transferred between A and B and will not be able to intelligently tamper with th
|
||||
message stream between Alice and Bob, although she may be still be able to inject
|
||||
delays or terminate the link.
|
||||
|
||||
## Peer-to-Peer Traffic Routing
|
||||
|
||||
### Squelching
|
||||
# Ripple Clustering #
|
||||
|
||||
Validator Squelching is a network feature that reduces redundant message traffic by intelligently selecting a small subset of peers to listen to for each validator. Messages from non-selected peers are temporarily ignored, or "squelched." This process significantly cuts down on processing overhead and provides dynamic fault tolerance by allowing the network to ignore misbehaving peers without a permanent ban. The system continuously re-evaluates peer performance to adapt to changing network conditions.
|
||||
|
||||
### Components
|
||||
|
||||
The squelching architecture is built on five key classes:
|
||||
|
||||
- `SquelchStore.h`: A low-level, timed key-value store that maps a validator to its squelch expiration timestamp.
|
||||
|
||||
- `Slot.h/Slot`: Manages the state for a single validator, tracking all peers that relay its messages. It operates in a Counting state to gather peer performance data and a Selected state after choosing the best peers and squelching the rest. It handles peer disconnections and idleness to keep the selection optimal.
|
||||
|
||||
- `Slot.h/Slots`: The central container that manages all active Slot instances. It applies different policies for trusted and untrusted validators, evaluates candidates for the limited untrusted slots, and runs periodic cleanup routines.
|
||||
|
||||
- `OverlayImpl.h`: Integrates the squelching system with the network, capturing events and dispatching them to the Slots container in a thread-safe manner.
|
||||
|
||||
- `PeerImp.h` - Handles squelch messages, and calls `OverlayImpl.h` when it received proposal or validation messages.
|
||||
|
||||
### Component Dependency
|
||||
|
||||
The component dependencies follow a clear hierarchy from the network layer down to individual peers:
|
||||
|
||||
- `OverlayImpl`: The top-level component that owns a single instance of Slots, and a currently connected Peers.
|
||||
- `Slots`: This central orchestrator owns and manages a collection of many Slot instances.
|
||||
- `Slot`: Each Slot represents a single validator and manages the state of all PeerImp instances that relay messages for it.
|
||||
- `PeerImp`: Represents a connected peer and owns its own instance of SquelchStore to manage its local squelch state for various validators.
|
||||
|
||||
### The Squelching Lifecycle
|
||||
|
||||
When a message from a validator arrives, it is dispatched to the appropriate Slot. The Slot, initially in a Counting state, tracks message volume from each peer. Once enough data is gathered, it triggers a selection, randomly choosing a small number of the best-performing peers. The Slot then instructs a SquelchHandler to squelch all non-selected peers for a calculated duration and transitions to a Selected state. The system continuously monitors for network changes, such as a selected peer disconnecting, which causes the Slot to reset to the Counting state and begin a new evaluation.
|
||||
|
||||
### Trusted vs. Untrusted Validators
|
||||
|
||||
The system applies different policies based on validator trust status:
|
||||
|
||||
- **Trusted Validators**: Are granted a Slot immediately to optimize traffic from known-good sources.
|
||||
|
||||
- **Untrusted Validators**: Are handled more cautiously, especially when the Enhanced Squelching feature is enabled. They must compete for a fixed number of limited slots by first proving their reliability in a "consideration pool." Validators that fail to gain a slot or become idle are aggressively squelched across all peers. This can also be triggered by a network-wide consensus to ignore a specific untrusted validator.
|
||||
|
||||
This dual-policy approach optimizes trusted traffic while robustly protecting the network from potentially malicious or unknown validators.
|
||||
|
||||
## XRP Ledger Clustering
|
||||
|
||||
A cluster consists of more than one XRP Ledger server under common
|
||||
A cluster consists of more than one Ripple server under common
|
||||
administration that share load information, distribute cryptography
|
||||
operations, and provide greater response consistency.
|
||||
|
||||
@@ -409,7 +378,7 @@ Cluster nodes share information about their internal load status. Cluster
|
||||
nodes do not have to verify the cryptographic signatures on messages
|
||||
received from other cluster nodes.
|
||||
|
||||
### Configuration
|
||||
## Configuration ##
|
||||
|
||||
A server's public key can be determined from the output of the `server_info`
|
||||
command. The key is in the `pubkey_node` value, and is a text string
|
||||
@@ -435,7 +404,7 @@ New spokes can be added as follows:
|
||||
- Restart each hub, one by one
|
||||
- Restart the spoke
|
||||
|
||||
### Transaction Behavior
|
||||
## Transaction Behavior ##
|
||||
|
||||
When a transaction is received from a cluster member, several normal checks
|
||||
are bypassed:
|
||||
@@ -451,7 +420,7 @@ does not meet its current relay fee. It is preferable to keep the cluster
|
||||
in agreement and permit confirmation from one cluster member to more
|
||||
reliably indicate the transaction's acceptance by the cluster.
|
||||
|
||||
### Server Load Information
|
||||
## Server Load Information ##
|
||||
|
||||
Cluster members exchange information on their server's load level. The load
|
||||
level is essentially the amount by which the normal fee levels are multiplied
|
||||
@@ -462,7 +431,7 @@ fee, is the highest of its local load level, the network load level, and the
|
||||
cluster load level. The cluster load level is the median load level reported
|
||||
by a cluster member.
|
||||
|
||||
### Gossip
|
||||
## Gossip ##
|
||||
|
||||
Gossip is the mechanism by which cluster members share information about
|
||||
endpoints (typically IPv4 addresses) that are imposing unusually high load
|
||||
@@ -477,7 +446,7 @@ the servers in a cluster. With gossip, if he chooses to use the same IP
|
||||
address to impose load on more than one server, he will find that the amount
|
||||
of load he can impose before getting disconnected is much lower.
|
||||
|
||||
### Monitoring
|
||||
## Monitoring ##
|
||||
|
||||
The `peers` command will report on the status of the cluster. The `cluster`
|
||||
object will contain one entry for each member of the cluster (either configured
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#define RIPPLE_OVERLAY_REDUCERELAYCOMMON_H_INCLUDED
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -40,31 +39,19 @@ static constexpr auto MIN_UNSQUELCH_EXPIRE = std::chrono::seconds{300};
|
||||
static constexpr auto MAX_UNSQUELCH_EXPIRE_DEFAULT = std::chrono::seconds{600};
|
||||
static constexpr auto SQUELCH_PER_PEER = std::chrono::seconds(10);
|
||||
static constexpr auto MAX_UNSQUELCH_EXPIRE_PEERS = std::chrono::seconds{3600};
|
||||
|
||||
// No message received threshold before identifying a peer as idled
|
||||
static constexpr auto PEER_IDLED = std::chrono::seconds{8};
|
||||
|
||||
static constexpr auto IDLED = std::chrono::seconds{8};
|
||||
// Message count threshold to start selecting peers as the source
|
||||
// of messages from the validator. We add peers who reach
|
||||
// MIN_MESSAGE_THRESHOLD to considered pool once MAX_SELECTED_PEERS
|
||||
// reach MAX_MESSAGE_THRESHOLD.
|
||||
static constexpr uint16_t MIN_MESSAGE_THRESHOLD = 19;
|
||||
static constexpr uint16_t MAX_MESSAGE_THRESHOLD = 20;
|
||||
|
||||
// Max selected peers to choose as the source of messages from validator
|
||||
static constexpr uint16_t MAX_SELECTED_PEERS = 5;
|
||||
|
||||
// Max number of untrusted slots the server will maintain
|
||||
static constexpr uint16_t MAX_UNTRUSTED_SLOTS = 30;
|
||||
|
||||
// The maximum of seconds an untrusted validator can go without sending a
|
||||
// validation message. After this, a validator may be squelched
|
||||
static constexpr auto MAX_UNTRUSTED_VALIDATOR_IDLE = std::chrono::seconds{30};
|
||||
|
||||
// Wait before reduce-relay feature is enabled on boot up to let
|
||||
// the server establish peer connections
|
||||
static constexpr auto WAIT_ON_BOOTUP = std::chrono::minutes{10};
|
||||
|
||||
// Maximum size of the aggregated transaction hashes per peer.
|
||||
// Once we get to high tps throughput, this cap will prevent
|
||||
// TMTransactions from exceeding the current protocol message
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
129
src/xrpld/overlay/Squelch.h
Normal file
129
src/xrpld/overlay/Squelch.h
Normal file
@@ -0,0 +1,129 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2020 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_OVERLAY_SQUELCH_H_INCLUDED
|
||||
#define RIPPLE_OVERLAY_SQUELCH_H_INCLUDED
|
||||
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace reduce_relay {
|
||||
|
||||
/** Maintains squelching of relaying messages from validators */
|
||||
template <typename clock_type>
|
||||
class Squelch
|
||||
{
|
||||
using time_point = typename clock_type::time_point;
|
||||
|
||||
public:
|
||||
explicit Squelch(beast::Journal journal) : journal_(journal)
|
||||
{
|
||||
}
|
||||
virtual ~Squelch() = default;
|
||||
|
||||
/** Squelch validation/proposal relaying for the validator
|
||||
* @param validator The validator's public key
|
||||
* @param squelchDuration Squelch duration in seconds
|
||||
* @return false if invalid squelch duration
|
||||
*/
|
||||
bool
|
||||
addSquelch(
|
||||
PublicKey const& validator,
|
||||
std::chrono::seconds const& squelchDuration);
|
||||
|
||||
/** Remove the squelch
|
||||
* @param validator The validator's public key
|
||||
*/
|
||||
void
|
||||
removeSquelch(PublicKey const& validator);
|
||||
|
||||
/** Remove expired squelch
|
||||
* @param validator Validator's public key
|
||||
* @return true if removed or doesn't exist, false if still active
|
||||
*/
|
||||
bool
|
||||
expireSquelch(PublicKey const& validator);
|
||||
|
||||
private:
|
||||
/** Maintains the list of squelched relaying to downstream peers.
|
||||
* Expiration time is included in the TMSquelch message. */
|
||||
hash_map<PublicKey, time_point> squelched_;
|
||||
beast::Journal const journal_;
|
||||
};
|
||||
|
||||
template <typename clock_type>
|
||||
bool
|
||||
Squelch<clock_type>::addSquelch(
|
||||
PublicKey const& validator,
|
||||
std::chrono::seconds const& squelchDuration)
|
||||
{
|
||||
if (squelchDuration >= MIN_UNSQUELCH_EXPIRE &&
|
||||
squelchDuration <= MAX_UNSQUELCH_EXPIRE_PEERS)
|
||||
{
|
||||
squelched_[validator] = clock_type::now() + squelchDuration;
|
||||
return true;
|
||||
}
|
||||
|
||||
JLOG(journal_.error()) << "squelch: invalid squelch duration "
|
||||
<< squelchDuration.count();
|
||||
|
||||
// unsquelch if invalid duration
|
||||
removeSquelch(validator);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename clock_type>
|
||||
void
|
||||
Squelch<clock_type>::removeSquelch(PublicKey const& validator)
|
||||
{
|
||||
squelched_.erase(validator);
|
||||
}
|
||||
|
||||
template <typename clock_type>
|
||||
bool
|
||||
Squelch<clock_type>::expireSquelch(PublicKey const& validator)
|
||||
{
|
||||
auto now = clock_type::now();
|
||||
|
||||
auto const& it = squelched_.find(validator);
|
||||
if (it == squelched_.end())
|
||||
return true;
|
||||
else if (it->second > now)
|
||||
return false;
|
||||
|
||||
// squelch expired
|
||||
squelched_.erase(it);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace reduce_relay
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLED_SQUELCH_H
|
||||
@@ -1,146 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_OVERLAY_SQUELCH_H_INCLUDED
|
||||
#define RIPPLE_OVERLAY_SQUELCH_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace reduce_relay {
|
||||
|
||||
/**
|
||||
* @brief Manages the temporary suppression ("squelching") of validators.
|
||||
*
|
||||
* @details This class provides a mechanism to temporarily ignore messages from
|
||||
* specific validators for a defined duration. It tracks which
|
||||
* validators are currently squelched and handles the
|
||||
* expiration of the squelch period. The use of an
|
||||
* abstract clock allows for deterministic testing of time-based
|
||||
* squelch logic.
|
||||
*/
|
||||
class SquelchStore
|
||||
{
|
||||
using clock_type = beast::abstract_clock<std::chrono::steady_clock>;
|
||||
using time_point = typename clock_type::time_point;
|
||||
|
||||
public:
|
||||
explicit SquelchStore(beast::Journal journal, clock_type& clock)
|
||||
: journal_(journal), clock_(clock)
|
||||
{
|
||||
}
|
||||
virtual ~SquelchStore() = default;
|
||||
|
||||
/**
|
||||
* @brief Manages the squelch status of a validator.
|
||||
*
|
||||
* @details This function acts as the primary public interface for
|
||||
* controlling a validator's squelch state. Based on the `squelch` flag, it
|
||||
* either adds a new squelch entry for the specified duration or removes an
|
||||
* existing one. This function also clears all expired squelches.
|
||||
*
|
||||
* @param validator The public key of the validator to manage.
|
||||
* @param squelch If `true`, the validator will be squelched. If `false`,
|
||||
* any existing squelch will be removed.
|
||||
* @param duration The duration in seconds for the squelch. This value is
|
||||
* only used when `squelch` is `true`.
|
||||
*/
|
||||
void
|
||||
handleSquelch(
|
||||
PublicKey const& validator,
|
||||
bool squelch,
|
||||
std::chrono::seconds duration);
|
||||
|
||||
/**
|
||||
* @brief Checks if a validator is currently squelched.
|
||||
*
|
||||
* @details This function checks if the validator's squelch has expired.
|
||||
*
|
||||
* @param validator The public key of the validator to check.
|
||||
* @return `true` if a non-expired squelch entry exists for the
|
||||
* validator, `false` otherwise.
|
||||
*/
|
||||
bool
|
||||
isSquelched(PublicKey const& validator) const;
|
||||
|
||||
// The following field is protected for unit tests.
|
||||
protected:
|
||||
/**
|
||||
* @brief The core data structure mapping a validator's public key to the
|
||||
* time point when their squelch expires.
|
||||
*/
|
||||
hash_map<PublicKey, time_point> squelched_;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Internal implementation to add or update a squelch entry.
|
||||
*
|
||||
* @details Calculates the expiration time point by adding the duration to
|
||||
* the current time and inserts or overwrites the entry for the validator in
|
||||
* the `squelched_` map.
|
||||
*
|
||||
* @param validator The public key of the validator to squelch.
|
||||
* @param squelchDuration The duration for which the validator should be
|
||||
* squelched.
|
||||
*/
|
||||
void
|
||||
add(PublicKey const& validator,
|
||||
std::chrono::seconds const& squelchDuration);
|
||||
|
||||
/**
|
||||
* @brief Internal implementation to remove a squelch entry.
|
||||
*
|
||||
* @details Erases the squelch entry for the given validator from the
|
||||
* `squelched_` map, effectively unsquelching it.
|
||||
*
|
||||
* @param validator The public key of the validator to unsquelch.
|
||||
*/
|
||||
void
|
||||
remove(PublicKey const& validator);
|
||||
|
||||
/**
|
||||
* @brief Internal implementation to remove all expired squelches.
|
||||
*
|
||||
* @details Erases all squelch entries whose expiration is in the past.
|
||||
*/
|
||||
void
|
||||
removeExpired();
|
||||
|
||||
/**
|
||||
* @brief The logging interface used by this store.
|
||||
*/
|
||||
beast::Journal const journal_;
|
||||
|
||||
/**
|
||||
* @brief A reference to the clock used for all time-based operations,
|
||||
* allowing for deterministic testing via dependency injection.
|
||||
*/
|
||||
clock_type& clock_;
|
||||
};
|
||||
|
||||
} // namespace reduce_relay
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLED_SQUELCH_H
|
||||
@@ -24,12 +24,10 @@
|
||||
#include <xrpld/app/rdb/RelationalDatabase.h>
|
||||
#include <xrpld/app/rdb/Wallet.h>
|
||||
#include <xrpld/overlay/Cluster.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/ConnectAttempt.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/TrafficCount.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
#include <xrpld/overlay/predicates.h>
|
||||
#include <xrpld/peerfinder/make_Manager.h>
|
||||
#include <xrpld/rpc/handlers/GetCounts.h>
|
||||
#include <xrpld/rpc/json_body.h>
|
||||
@@ -41,7 +39,9 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/server/SimpleWriter.h>
|
||||
|
||||
#include <functional>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include "xrpld/overlay/detail/TrafficCount.h"
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -142,7 +142,7 @@ OverlayImpl::OverlayImpl(
|
||||
, m_resolver(resolver)
|
||||
, next_id_(1)
|
||||
, timer_count_(0)
|
||||
, slots_(app.logs(), *this, app.config(), stopwatch())
|
||||
, slots_(app.logs(), *this, app.config())
|
||||
, m_stats(
|
||||
std::bind(&OverlayImpl::collect_metrics, this),
|
||||
collector,
|
||||
@@ -578,22 +578,17 @@ OverlayImpl::stop()
|
||||
void
|
||||
OverlayImpl::onWrite(beast::PropertyStream::Map& stream)
|
||||
{
|
||||
beast::PropertyStream::Set set("traffic", stream);
|
||||
auto const stats = m_traffic.getCounts();
|
||||
for (auto const& pair : stats)
|
||||
{
|
||||
beast::PropertyStream::Set set("traffic", stream);
|
||||
auto const stats = m_traffic.getCounts();
|
||||
for (auto const& pair : stats)
|
||||
{
|
||||
beast::PropertyStream::Map item(set);
|
||||
item["category"] = pair.second.name;
|
||||
item["bytes_in"] = std::to_string(pair.second.bytesIn.load());
|
||||
item["messages_in"] = std::to_string(pair.second.messagesIn.load());
|
||||
item["bytes_out"] = std::to_string(pair.second.bytesOut.load());
|
||||
item["messages_out"] =
|
||||
std::to_string(pair.second.messagesOut.load());
|
||||
}
|
||||
beast::PropertyStream::Map item(set);
|
||||
item["category"] = pair.second.name;
|
||||
item["bytes_in"] = std::to_string(pair.second.bytesIn.load());
|
||||
item["messages_in"] = std::to_string(pair.second.messagesIn.load());
|
||||
item["bytes_out"] = std::to_string(pair.second.bytesOut.load());
|
||||
item["messages_out"] = std::to_string(pair.second.messagesOut.load());
|
||||
}
|
||||
|
||||
slots_.onWrite(stream);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -1415,24 +1410,12 @@ OverlayImpl::squelch(
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::squelchAll(
|
||||
PublicKey const& validator,
|
||||
uint32_t squelchDuration,
|
||||
std::function<void(Peer::id_t)> report)
|
||||
{
|
||||
for_each([&](std::shared_ptr<PeerImp>&& p) {
|
||||
p->send(makeSquelchMessage(validator, true, squelchDuration));
|
||||
report(p->id());
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::updateSlotAndSquelch(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
std::set<Peer::id_t>&& peers,
|
||||
bool isTrusted)
|
||||
protocol::MessageType type)
|
||||
{
|
||||
if (!slots_.baseSquelchReady())
|
||||
return;
|
||||
@@ -1445,18 +1428,14 @@ OverlayImpl::updateSlotAndSquelch(
|
||||
key = key,
|
||||
validator = validator,
|
||||
peers = std::move(peers),
|
||||
isTrusted]() mutable {
|
||||
updateSlotAndSquelch(
|
||||
key, validator, std::move(peers), isTrusted);
|
||||
type]() mutable {
|
||||
updateSlotAndSquelch(key, validator, std::move(peers), type);
|
||||
});
|
||||
|
||||
for (auto id : peers)
|
||||
slots_.updateSlotAndSquelch(
|
||||
key,
|
||||
validator,
|
||||
id,
|
||||
[&]() { reportInboundTraffic(TrafficCount::squelch_ignored, 0); },
|
||||
isTrusted);
|
||||
slots_.updateSlotAndSquelch(key, validator, id, type, [&]() {
|
||||
reportInboundTraffic(TrafficCount::squelch_ignored, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1464,7 +1443,7 @@ OverlayImpl::updateSlotAndSquelch(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t peer,
|
||||
bool isTrusted)
|
||||
protocol::MessageType type)
|
||||
{
|
||||
if (!slots_.baseSquelchReady())
|
||||
return;
|
||||
@@ -1473,64 +1452,15 @@ OverlayImpl::updateSlotAndSquelch(
|
||||
return post(
|
||||
strand_,
|
||||
// Must capture copies of reference parameters (i.e. key, validator)
|
||||
[this, key = key, validator = validator, peer, isTrusted]() {
|
||||
updateSlotAndSquelch(key, validator, peer, isTrusted);
|
||||
[this, key = key, validator = validator, peer, type]() {
|
||||
updateSlotAndSquelch(key, validator, peer, type);
|
||||
});
|
||||
|
||||
slots_.updateSlotAndSquelch(
|
||||
key,
|
||||
validator,
|
||||
peer,
|
||||
[&]() { reportInboundTraffic(TrafficCount::squelch_ignored, 0); },
|
||||
isTrusted);
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::updateUntrustedValidatorSlot(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t peer)
|
||||
{
|
||||
if (!slots_.enhancedSquelchReady())
|
||||
return;
|
||||
|
||||
if (!strand_.running_in_this_thread())
|
||||
return post(
|
||||
strand_,
|
||||
// Must capture copies of reference parameters (i.e. key, validator)
|
||||
[this, key = key, validator = validator, peer]() {
|
||||
updateUntrustedValidatorSlot(key, validator, peer);
|
||||
});
|
||||
|
||||
slots_.updateUntrustedValidatorSlot(key, validator, peer, [&]() {
|
||||
slots_.updateSlotAndSquelch(key, validator, peer, type, [&]() {
|
||||
reportInboundTraffic(TrafficCount::squelch_ignored, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::handleUntrustedSquelch(PublicKey const& validator)
|
||||
{
|
||||
if (!strand_.running_in_this_thread())
|
||||
return post(
|
||||
strand_,
|
||||
std::bind(&OverlayImpl::handleUntrustedSquelch, this, validator));
|
||||
|
||||
auto count = 0;
|
||||
// we can get the total number of peers with size(), however that would have
|
||||
// to acquire another lock on peers. Instead, count the number of peers in
|
||||
// the same loop, as we're already iterating all peers.
|
||||
auto total = 0;
|
||||
for_each([&](std::shared_ptr<PeerImp>&& p) {
|
||||
++total;
|
||||
if (p->isSquelched(validator))
|
||||
++count;
|
||||
});
|
||||
|
||||
// if majority of peers squelched the validator
|
||||
if (count >= total - 1)
|
||||
slots_.squelchUntrustedValidator(validator);
|
||||
}
|
||||
|
||||
void
|
||||
OverlayImpl::deletePeer(Peer::id_t id)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <xrpld/core/Job.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Overlay.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/Slot.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
#include <xrpld/overlay/detail/TrafficCount.h>
|
||||
@@ -49,7 +48,6 @@
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
@@ -105,7 +103,7 @@ private:
|
||||
boost::asio::io_service& io_service_;
|
||||
std::optional<boost::asio::io_service::work> work_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
std::recursive_mutex mutable mutex_; // VFALCO use std::mutex
|
||||
mutable std::recursive_mutex mutex_; // VFALCO use std::mutex
|
||||
std::condition_variable_any cond_;
|
||||
std::weak_ptr<Timer> timer_;
|
||||
boost::container::flat_map<Child*, std::weak_ptr<Child>> list_;
|
||||
@@ -124,7 +122,7 @@ private:
|
||||
std::atomic<uint64_t> peerDisconnects_{0};
|
||||
std::atomic<uint64_t> peerDisconnectsCharges_{0};
|
||||
|
||||
reduce_relay::Slots slots_;
|
||||
reduce_relay::Slots<UptimeClock> slots_;
|
||||
|
||||
// Transaction reduce-relay metrics
|
||||
metrics::TxMetrics txMetrics_;
|
||||
@@ -394,90 +392,35 @@ public:
|
||||
return setup_.networkID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes a message from a validator received via multiple peers.
|
||||
*
|
||||
* @details This function serves as a thread-safe entry point to the
|
||||
* squelching system.
|
||||
*
|
||||
* @param key The unique hash of the message.
|
||||
* @param validator The public key of the validator.
|
||||
* @param peers A set of peer IDs that relayed this message.
|
||||
* @param isTrusted `true` if the message is from a trusted validator.
|
||||
/** Updates message count for validator/peer. Sends TMSquelch if the number
|
||||
* of messages for N peers reaches threshold T. A message is counted
|
||||
* if a peer receives the message for the first time and if
|
||||
* the message has been relayed.
|
||||
* @param key Unique message's key
|
||||
* @param validator Validator's public key
|
||||
* @param peers Peers' id to update the slots for
|
||||
* @param type Received protocol message type
|
||||
*/
|
||||
void
|
||||
updateSlotAndSquelch(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
std::set<Peer::id_t>&& peers,
|
||||
bool isTrusted);
|
||||
protocol::MessageType type);
|
||||
|
||||
/**
|
||||
* @brief Processes a message from a validator received via a single peer.
|
||||
*
|
||||
* @details This function is a thread-safe entry point for handling a
|
||||
* message from a single peer. It ensures the squelching feature is ready
|
||||
* and serializes the call onto the `strand_`. It then invokes the
|
||||
* underlying `Slots::updateSlotAndSquelch` method to process the message.
|
||||
*
|
||||
* @param key The unique hash of the message.
|
||||
* @param validator The public key of the validator.
|
||||
* @param peer The ID of the peer that relayed this message.
|
||||
* @param isTrusted `true` if the message is from a trusted validator.
|
||||
/** Overload to reduce allocation in case of single peer
|
||||
*/
|
||||
void
|
||||
updateSlotAndSquelch(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t peer,
|
||||
bool isTrusted);
|
||||
protocol::MessageType type);
|
||||
|
||||
/**
|
||||
* @brief Processes a message specifically for the untrusted validator slot
|
||||
* logic.
|
||||
*
|
||||
* @details This function is the thread-safe entry point for the enhanced
|
||||
* squelching feature, which manages a limited number of slots for
|
||||
* untrusted validators. It ensures the feature is ready, posts the work to
|
||||
* the `strand_`, and then calls the underlying
|
||||
* `Slots::updateUntrustedValidatorSlot` to handle the slot admission and
|
||||
* evaluation logic.
|
||||
*
|
||||
* @param key The unique hash of the message.
|
||||
* @param validator The public key of the untrusted validator.
|
||||
* @param peer The ID of the peer that relayed this message.
|
||||
*/
|
||||
void
|
||||
updateUntrustedValidatorSlot(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t peer);
|
||||
|
||||
/**
|
||||
* @brief Handles a squelch message for an untrusted validator.
|
||||
*
|
||||
* @details This function is called when this node receives a message
|
||||
* indicating that a peer is squelching an untrusted validator. It
|
||||
* tallies how many of its own connected peers have also squelched the
|
||||
* validator. If a majority of peers agree, this node takes definitive local
|
||||
* action by calling `Slots::squelchUntrustedValidator`, effectively joining
|
||||
* the consensus to silence the validator.
|
||||
*
|
||||
* @param validator The public key of the untrusted validator being
|
||||
* squelched.
|
||||
*/
|
||||
void
|
||||
handleUntrustedSquelch(PublicKey const& validator);
|
||||
|
||||
/**
|
||||
* @brief Handles the deletion of a peer from the overlay network.
|
||||
*
|
||||
* @details This function provides a thread-safe entry point for removing a
|
||||
* peer. It ensures the operation is executed on the correct strand and
|
||||
* then delegates the logic to `Slots::deletePeer`, which notifies all
|
||||
* active slots about the peer's removal.
|
||||
*
|
||||
* @param id The ID of the peer to be deleted.
|
||||
/** Called when the peer is deleted. If the peer was selected to be the
|
||||
* source of messages from the validator then squelched peers have to be
|
||||
* unsquelched.
|
||||
* @param id Peer's id
|
||||
*/
|
||||
void
|
||||
deletePeer(Peer::id_t id);
|
||||
@@ -508,12 +451,6 @@ private:
|
||||
Peer::id_t const id,
|
||||
std::uint32_t squelchDuration) const override;
|
||||
|
||||
void
|
||||
squelchAll(
|
||||
PublicKey const& validator,
|
||||
std::uint32_t squelchDuration,
|
||||
std::function<void(Peer::id_t)>) override;
|
||||
|
||||
void
|
||||
unsquelch(PublicKey const& validator, Peer::id_t id) const override;
|
||||
|
||||
@@ -540,7 +477,7 @@ private:
|
||||
|
||||
/** Handles validator list requests.
|
||||
Using a /vl/<hex-encoded public key> URL, will retrieve the
|
||||
latest validator list (or UNL) that this node has for that
|
||||
latest valdiator list (or UNL) that this node has for that
|
||||
public key, if the node trusts that public key.
|
||||
|
||||
@return true if the request was handled.
|
||||
@@ -618,13 +555,8 @@ private:
|
||||
void
|
||||
sendTxQueue();
|
||||
|
||||
/**
|
||||
* @brief Triggers the cleanup of idle peers and stale slots.
|
||||
*
|
||||
* @details This function is a thread-safe wrapper that executes
|
||||
* `Slots::deleteIdlePeers` to perform the necessary cleanup of inactive
|
||||
* peers, stale slots, and unviable validator candidates.
|
||||
*/
|
||||
/** Check if peers stopped relaying messages
|
||||
* and if slots stopped receiving messages from the validator */
|
||||
void
|
||||
deleteIdlePeers();
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <xrpld/app/misc/ValidatorList.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/overlay/Cluster.h>
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
#include <xrpld/perflog/PerfLog.h>
|
||||
@@ -45,7 +44,6 @@
|
||||
#include <boost/beast/core/ostream.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
@@ -97,7 +95,7 @@ PeerImp::PeerImp(
|
||||
, publicKey_(publicKey)
|
||||
, lastPingTime_(clock_type::now())
|
||||
, creationTime_(clock_type::now())
|
||||
, squelchStore_(app_.journal("SquelchStore"), stopwatch())
|
||||
, squelch_(app_.journal("Squelch"))
|
||||
, usage_(consumer)
|
||||
, fee_{Resource::feeTrivialPeer, ""}
|
||||
, slot_(slot)
|
||||
@@ -248,8 +246,8 @@ PeerImp::send(std::shared_ptr<Message> const& m)
|
||||
if (detaching_)
|
||||
return;
|
||||
|
||||
auto const validator = m->getValidatorKey();
|
||||
if (validator && isSquelched(*validator))
|
||||
auto validator = m->getValidatorKey();
|
||||
if (validator && !squelch_.expireSquelch(*validator))
|
||||
{
|
||||
overlay_.reportOutboundTraffic(
|
||||
TrafficCount::category::squelch_suppressed,
|
||||
@@ -267,7 +265,7 @@ PeerImp::send(std::shared_ptr<Message> const& m)
|
||||
TrafficCount::category::total,
|
||||
static_cast<int>(m->getBuffer(compressionEnabled_).size()));
|
||||
|
||||
auto const sendq_size = send_queue_.size();
|
||||
auto sendq_size = send_queue_.size();
|
||||
|
||||
if (sendq_size < Tuning::targetSendQueue)
|
||||
{
|
||||
@@ -572,12 +570,6 @@ PeerImp::hasRange(std::uint32_t uMin, std::uint32_t uMax)
|
||||
(uMax <= maxLedger_);
|
||||
}
|
||||
|
||||
bool
|
||||
PeerImp::isSquelched(PublicKey const& validator) const
|
||||
{
|
||||
return squelchStore_.isSquelched(validator);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
@@ -1707,6 +1699,21 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMProposeSet> const& m)
|
||||
// suppression for 30 seconds to avoid doing a relatively expensive lookup
|
||||
// every time a spam packet is received
|
||||
PublicKey const publicKey{makeSlice(set.nodepubkey())};
|
||||
auto const isTrusted = app_.validators().trusted(publicKey);
|
||||
|
||||
// If the operator has specified that untrusted proposals be dropped then
|
||||
// this happens here I.e. before further wasting CPU verifying the signature
|
||||
// of an untrusted key
|
||||
if (!isTrusted)
|
||||
{
|
||||
// report untrusted proposal messages
|
||||
overlay_.reportInboundTraffic(
|
||||
TrafficCount::category::proposal_untrusted,
|
||||
Message::messageSize(*m));
|
||||
|
||||
if (app_.config().RELAY_UNTRUSTED_PROPOSALS == -1)
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 const proposeHash{set.currenttxhash()};
|
||||
uint256 const prevLedger{set.previousledger()};
|
||||
@@ -1721,18 +1728,15 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMProposeSet> const& m)
|
||||
publicKey.slice(),
|
||||
sig);
|
||||
|
||||
auto const isTrusted = app_.validators().trusted(publicKey);
|
||||
|
||||
if (auto const& [added, relayed] =
|
||||
if (auto [added, relayed] =
|
||||
app_.getHashRouter().addSuppressionPeerWithStatus(suppression, id_);
|
||||
!added)
|
||||
{
|
||||
// Count unique messages (Slots has it's own 'HashRouter'), which a peer
|
||||
// receives within IDLED seconds since the message has been relayed.
|
||||
if (relayed &&
|
||||
(stopwatch().now() - *relayed) < reduce_relay::PEER_IDLED)
|
||||
if (relayed && (stopwatch().now() - *relayed) < reduce_relay::IDLED)
|
||||
overlay_.updateSlotAndSquelch(
|
||||
suppression, publicKey, id_, isTrusted);
|
||||
suppression, publicKey, id_, protocol::mtPROPOSE_LEDGER);
|
||||
|
||||
// report duplicate proposal messages
|
||||
overlay_.reportInboundTraffic(
|
||||
@@ -1746,16 +1750,6 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMProposeSet> const& m)
|
||||
|
||||
if (!isTrusted)
|
||||
{
|
||||
overlay_.reportInboundTraffic(
|
||||
TrafficCount::category::proposal_untrusted,
|
||||
Message::messageSize(*m));
|
||||
|
||||
// If the operator has specified that untrusted proposals be dropped
|
||||
// then this happens here I.e. before further wasting CPU verifying the
|
||||
// signature of an untrusted key
|
||||
if (app_.config().RELAY_UNTRUSTED_PROPOSALS == -1)
|
||||
return;
|
||||
|
||||
if (tracking_.load() == Tracking::diverged)
|
||||
{
|
||||
JLOG(p_journal_.debug())
|
||||
@@ -2364,6 +2358,20 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMValidation> const& m)
|
||||
auto const isTrusted =
|
||||
app_.validators().trusted(val->getSignerPublic());
|
||||
|
||||
// If the operator has specified that untrusted validations be
|
||||
// dropped then this happens here I.e. before further wasting CPU
|
||||
// verifying the signature of an untrusted key
|
||||
if (!isTrusted)
|
||||
{
|
||||
// increase untrusted validations received
|
||||
overlay_.reportInboundTraffic(
|
||||
TrafficCount::category::validation_untrusted,
|
||||
Message::messageSize(*m));
|
||||
|
||||
if (app_.config().RELAY_UNTRUSTED_VALIDATIONS == -1)
|
||||
return;
|
||||
}
|
||||
|
||||
auto key = sha512Half(makeSlice(m->validation()));
|
||||
|
||||
auto [added, relayed] =
|
||||
@@ -2374,10 +2382,9 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMValidation> const& m)
|
||||
// Count unique messages (Slots has it's own 'HashRouter'), which a
|
||||
// peer receives within IDLED seconds since the message has been
|
||||
// relayed.
|
||||
if (relayed &&
|
||||
(stopwatch().now() - *relayed) < reduce_relay::PEER_IDLED)
|
||||
if (relayed && (stopwatch().now() - *relayed) < reduce_relay::IDLED)
|
||||
overlay_.updateSlotAndSquelch(
|
||||
key, val->getSignerPublic(), id_, isTrusted);
|
||||
key, val->getSignerPublic(), id_, protocol::mtVALIDATION);
|
||||
|
||||
// increase duplicate validations received
|
||||
overlay_.reportInboundTraffic(
|
||||
@@ -2388,25 +2395,6 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMValidation> const& m)
|
||||
return;
|
||||
}
|
||||
|
||||
// at this point the message is guaranteed to be unique
|
||||
if (!isTrusted)
|
||||
{
|
||||
overlay_.reportInboundTraffic(
|
||||
TrafficCount::category::validation_untrusted,
|
||||
Message::messageSize(*m));
|
||||
|
||||
// If the operator has specified that untrusted validations be
|
||||
// dropped then this happens here I.e. before further wasting CPU
|
||||
// verifying the signature of an untrusted key
|
||||
// TODO: Deprecate RELAY_UNTRUSTED_VALIDATIONS config once enhanced
|
||||
// squelching is the defacto routing algorithm.
|
||||
if (app_.config().RELAY_UNTRUSTED_VALIDATIONS == -1)
|
||||
return;
|
||||
|
||||
overlay_.updateUntrustedValidatorSlot(
|
||||
key, val->getSignerPublic(), id_);
|
||||
}
|
||||
|
||||
if (!isTrusted && (tracking_.load() == Tracking::diverged))
|
||||
{
|
||||
JLOG(p_journal_.debug())
|
||||
@@ -2716,7 +2704,6 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMSquelch> const& m)
|
||||
fee_.update(Resource::feeInvalidData, "squelch no pubkey");
|
||||
return;
|
||||
}
|
||||
|
||||
auto validator = m->validatorpubkey();
|
||||
auto const slice{makeSlice(validator)};
|
||||
if (!publicKeyType(slice))
|
||||
@@ -2734,27 +2721,15 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMSquelch> const& m)
|
||||
return;
|
||||
}
|
||||
|
||||
auto duration = std::chrono::seconds{
|
||||
m->has_squelchduration() ? m->squelchduration() : 0};
|
||||
|
||||
if (m->squelch() &&
|
||||
(duration < reduce_relay::MIN_UNSQUELCH_EXPIRE ||
|
||||
duration > reduce_relay::MAX_UNSQUELCH_EXPIRE_PEERS))
|
||||
{
|
||||
std::uint32_t duration =
|
||||
m->has_squelchduration() ? m->squelchduration() : 0;
|
||||
if (!m->squelch())
|
||||
squelch_.removeSquelch(key);
|
||||
else if (!squelch_.addSquelch(key, std::chrono::seconds{duration}))
|
||||
fee_.update(Resource::feeInvalidData, "squelch duration");
|
||||
return;
|
||||
}
|
||||
|
||||
JLOG(p_journal_.debug())
|
||||
<< "onMessage: TMSquelch " << (!m->squelch() ? "un" : "")
|
||||
<< "squelch message; validator: " << slice << "peer: " << id()
|
||||
<< " duration: " << duration.count();
|
||||
|
||||
squelchStore_.handleSquelch(key, m->squelch(), duration);
|
||||
|
||||
// if the squelch is for an untrusted validator
|
||||
if (m->squelch() && !app_.validators().trusted(key))
|
||||
overlay_.handleUntrustedSquelch(key);
|
||||
<< "onMessage: TMSquelch " << slice << " " << id() << " " << duration;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -3038,7 +3013,7 @@ PeerImp::checkPropose(
|
||||
peerPos.suppressionID(),
|
||||
peerPos.publicKey(),
|
||||
std::move(haveMessage),
|
||||
isTrusted);
|
||||
protocol::mtPROPOSE_LEDGER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3074,7 +3049,7 @@ PeerImp::checkValidation(
|
||||
key,
|
||||
val->getSignerPublic(),
|
||||
std::move(haveMessage),
|
||||
val->isTrusted());
|
||||
protocol::mtVALIDATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
#include <xrpld/app/consensus/RCLCxPeerPos.h>
|
||||
#include <xrpld/app/ledger/detail/LedgerReplayMsgHandler.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/SquelchStore.h>
|
||||
#include <xrpld/overlay/Squelch.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/peerfinder/PeerfinderManager.h>
|
||||
@@ -117,7 +116,7 @@ private:
|
||||
clock_type::time_point lastPingTime_;
|
||||
clock_type::time_point const creationTime_;
|
||||
|
||||
reduce_relay::SquelchStore squelchStore_;
|
||||
reduce_relay::Squelch<UptimeClock> squelch_;
|
||||
|
||||
// Notes on thread locking:
|
||||
//
|
||||
@@ -441,13 +440,6 @@ public:
|
||||
return txReduceRelayEnabled_;
|
||||
}
|
||||
|
||||
/** Check if a given validator is squelched.
|
||||
* @param validator Validator's public key
|
||||
* @return true if squelch exists and it is not expired. False otherwise.
|
||||
*/
|
||||
bool
|
||||
isSquelched(PublicKey const& validator) const;
|
||||
|
||||
private:
|
||||
void
|
||||
close();
|
||||
@@ -688,7 +680,7 @@ PeerImp::PeerImp(
|
||||
, publicKey_(publicKey)
|
||||
, lastPingTime_(clock_type::now())
|
||||
, creationTime_(clock_type::now())
|
||||
, squelchStore_(app_.journal("SquelchStore"), stopwatch())
|
||||
, squelch_(app_.journal("Squelch"))
|
||||
, usage_(usage)
|
||||
, fee_{Resource::feeTrivialPeer}
|
||||
, slot_(std::move(slot))
|
||||
|
||||
@@ -1,722 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/Slot.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/container/aged_unordered_map.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace reduce_relay {
|
||||
|
||||
void
|
||||
Slot::deleteIdlePeer(PublicKey const& validator)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto const now = clock_.now();
|
||||
for (auto it = peers_.begin(); it != peers_.end();)
|
||||
{
|
||||
auto const& peer = it->second;
|
||||
auto const id = it->first;
|
||||
++it;
|
||||
if (now - peer.lastMessage > reduce_relay::PEER_IDLED)
|
||||
{
|
||||
JLOG(journal_.trace())
|
||||
<< "deleteIdlePeer: deleting idle peer "
|
||||
<< formatLogMessage(validator, id)
|
||||
<< " peer_state: " << to_string(peer.state)
|
||||
<< " idle for: " << (now - peer.lastMessage).count();
|
||||
deletePeer(validator, id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Slot::update(
|
||||
PublicKey const& validator,
|
||||
Peer::id_t id,
|
||||
ignored_squelch_callback report)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto const now = clock_.now();
|
||||
auto const it = peers_.find(id);
|
||||
|
||||
// First message from this peer
|
||||
if (it == peers_.end())
|
||||
{
|
||||
JLOG(journal_.trace())
|
||||
<< "update: adding new slot" << formatLogMessage(validator, id);
|
||||
peers_.emplace(std::make_pair(
|
||||
id,
|
||||
PeerInfo{
|
||||
.state = PeerState::Counting,
|
||||
.count = 0,
|
||||
.expire = now,
|
||||
.lastMessage = now,
|
||||
.timesSelected = 0}));
|
||||
initCounting();
|
||||
return;
|
||||
}
|
||||
// Message from a peer with expired squelch
|
||||
if (it->second.state == PeerState::Squelched && now > it->second.expire)
|
||||
{
|
||||
JLOG(journal_.trace())
|
||||
<< "update: squelch expired" << formatLogMessage(validator, id);
|
||||
it->second.state = PeerState::Counting;
|
||||
it->second.lastMessage = now;
|
||||
initCounting();
|
||||
return;
|
||||
}
|
||||
|
||||
auto& peer = it->second;
|
||||
|
||||
peer.lastMessage = now;
|
||||
|
||||
// report if we received a message from a squelched peer
|
||||
if (peer.state == PeerState::Squelched)
|
||||
report();
|
||||
|
||||
if (getState() != SlotState::Counting || peer.state == PeerState::Squelched)
|
||||
return;
|
||||
|
||||
if (++peer.count > reduce_relay::MIN_MESSAGE_THRESHOLD)
|
||||
considered_.insert(id);
|
||||
if (peer.count == (reduce_relay::MAX_MESSAGE_THRESHOLD + 1))
|
||||
++reachedThreshold_;
|
||||
|
||||
if (now - lastSelected_ > 2 * reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT)
|
||||
{
|
||||
JLOG(journal_.warn())
|
||||
<< "update: resetting due to inactivity"
|
||||
<< formatLogMessage(validator, id) << " inactive for: "
|
||||
<< duration_cast<seconds>(now - lastSelected_).count();
|
||||
initCounting();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reachedThreshold_ == maxSelectedPeers_)
|
||||
{
|
||||
// Randomly select maxSelectedPeers_ peers from considered.
|
||||
// Exclude peers that have been idling > IDLED -
|
||||
// it's possible that deleteIdlePeer() has not been called yet.
|
||||
// If number of remaining peers != maxSelectedPeers_
|
||||
// then reset the Counting state and let deleteIdlePeer() handle
|
||||
// idled peers.
|
||||
std::unordered_set<Peer::id_t> selected;
|
||||
std::stringstream str;
|
||||
while (selected.size() != maxSelectedPeers_ && considered_.size() != 0)
|
||||
{
|
||||
auto const i =
|
||||
considered_.size() == 1 ? 0 : rand_int(considered_.size() - 1);
|
||||
auto const it = std::next(considered_.begin(), i);
|
||||
auto const id = *it;
|
||||
considered_.erase(it);
|
||||
|
||||
auto const& peersIt = peers_.find(id);
|
||||
if (peersIt == peers_.end())
|
||||
{
|
||||
JLOG(journal_.error()) << "update: peer not found"
|
||||
<< formatLogMessage(validator, id);
|
||||
|
||||
continue;
|
||||
}
|
||||
if (now - peersIt->second.lastMessage < reduce_relay::PEER_IDLED)
|
||||
{
|
||||
selected.insert(id);
|
||||
str << id << " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.size() != maxSelectedPeers_)
|
||||
{
|
||||
JLOG(journal_.error()) << "update: selection failed"
|
||||
<< formatLogMessage(validator, std::nullopt);
|
||||
|
||||
initCounting();
|
||||
return;
|
||||
}
|
||||
|
||||
lastSelected_ = now;
|
||||
|
||||
JLOG(journal_.trace()) << "update: selected peers "
|
||||
<< formatLogMessage(validator, std::nullopt)
|
||||
<< " peers: " << str.str();
|
||||
|
||||
XRPL_ASSERT(
|
||||
peers_.size() >= maxSelectedPeers_,
|
||||
"ripple::reduce_relay::Slot::update : minimum peers");
|
||||
|
||||
// squelch peers which are not selected and
|
||||
// not already squelched
|
||||
str.clear();
|
||||
for (auto& [k, v] : peers_)
|
||||
{
|
||||
v.count = 0;
|
||||
|
||||
if (selected.find(k) != selected.end())
|
||||
{
|
||||
v.state = PeerState::Selected;
|
||||
++v.timesSelected;
|
||||
}
|
||||
|
||||
else if (v.state != PeerState::Squelched)
|
||||
{
|
||||
if (journal_.trace())
|
||||
str << k << " ";
|
||||
v.state = PeerState::Squelched;
|
||||
std::chrono::seconds duration =
|
||||
getSquelchDuration(peers_.size() - maxSelectedPeers_);
|
||||
v.expire = now + duration;
|
||||
handler_.squelch(validator, k, duration.count());
|
||||
}
|
||||
}
|
||||
JLOG(journal_.trace()) << "update: squelched peers "
|
||||
<< formatLogMessage(validator, std::nullopt)
|
||||
<< " peers: " << str.str();
|
||||
considered_.clear();
|
||||
reachedThreshold_ = 0;
|
||||
state_ = SlotState::Selected;
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::seconds
|
||||
Slot::getSquelchDuration(std::size_t npeers) const
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto m = std::max(
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT,
|
||||
seconds{reduce_relay::SQUELCH_PER_PEER * npeers});
|
||||
if (m > reduce_relay::MAX_UNSQUELCH_EXPIRE_PEERS)
|
||||
{
|
||||
m = reduce_relay::MAX_UNSQUELCH_EXPIRE_PEERS;
|
||||
JLOG(journal_.warn())
|
||||
<< "getSquelchDuration: unexpected squelch duration " << npeers;
|
||||
}
|
||||
return seconds{
|
||||
ripple::rand_int(reduce_relay::MIN_UNSQUELCH_EXPIRE / 1s, m / 1s)};
|
||||
}
|
||||
|
||||
void
|
||||
Slot::deletePeer(PublicKey const& validator, Peer::id_t id, bool erase)
|
||||
{
|
||||
auto it = peers_.find(id);
|
||||
if (it == peers_.end())
|
||||
return;
|
||||
|
||||
std::vector<Peer::id_t> toUnsquelch;
|
||||
auto const now = clock_.now();
|
||||
if (it->second.state == PeerState::Selected)
|
||||
{
|
||||
JLOG(journal_.debug())
|
||||
<< "deletePeer: unsquelching selected peer "
|
||||
<< formatLogMessage(validator, id)
|
||||
<< " peer_state: " << to_string(it->second.state)
|
||||
<< " considered: " << (considered_.find(id) != considered_.end())
|
||||
<< " erase: " << erase;
|
||||
|
||||
for (auto& [k, v] : peers_)
|
||||
{
|
||||
if (v.state == PeerState::Squelched)
|
||||
toUnsquelch.push_back(k);
|
||||
v.state = PeerState::Counting;
|
||||
v.count = 0;
|
||||
v.expire = now;
|
||||
}
|
||||
|
||||
considered_.clear();
|
||||
reachedThreshold_ = 0;
|
||||
state_ = SlotState::Counting;
|
||||
}
|
||||
else if (considered_.contains(id))
|
||||
{
|
||||
if (it->second.count > reduce_relay::MAX_MESSAGE_THRESHOLD)
|
||||
--reachedThreshold_;
|
||||
considered_.erase(id);
|
||||
}
|
||||
|
||||
it->second.lastMessage = now;
|
||||
it->second.count = 0;
|
||||
|
||||
if (erase)
|
||||
peers_.erase(it);
|
||||
|
||||
// Must be after peers_.erase(it)
|
||||
for (auto const& k : toUnsquelch)
|
||||
handler_.unsquelch(validator, k);
|
||||
}
|
||||
|
||||
void
|
||||
Slot::onWrite(beast::PropertyStream::Map& stream) const
|
||||
{
|
||||
auto const now = clock_.now();
|
||||
stream["state"] = to_string(getState());
|
||||
stream["reachedThreshold"] = reachedThreshold_;
|
||||
stream["considered"] = considered_.size();
|
||||
stream["lastSelected"] =
|
||||
duration_cast<std::chrono::seconds>(now - lastSelected_).count();
|
||||
stream["isTrusted"] = isTrusted_;
|
||||
|
||||
beast::PropertyStream::Set peers("peers", stream);
|
||||
|
||||
for (auto const& [id, info] : peers_)
|
||||
{
|
||||
beast::PropertyStream::Map item(peers);
|
||||
item["id"] = id;
|
||||
item["count"] = info.count;
|
||||
item["expire"] =
|
||||
duration_cast<std::chrono::seconds>(info.expire - now).count();
|
||||
item["lastMessage"] =
|
||||
duration_cast<std::chrono::seconds>(now - info.lastMessage).count();
|
||||
item["timesSelected"] = info.timesSelected;
|
||||
item["state"] = to_string(info.state);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Slot::initCounting()
|
||||
{
|
||||
state_ = SlotState::Counting;
|
||||
considered_.clear();
|
||||
reachedThreshold_ = 0;
|
||||
for (auto& [_, peer] : peers_)
|
||||
{
|
||||
(void)_;
|
||||
peer.count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
Slot::formatLogMessage(PublicKey const& validator, std::optional<Peer::id_t> id)
|
||||
const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "validator: " << toBase58(TokenType::NodePublic, validator);
|
||||
if (id)
|
||||
ss << " peer: " << *id;
|
||||
ss << " trusted: " << isTrusted_;
|
||||
ss << " slot_state: " << to_string(getState());
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// --------------------------------- Slots --------------------------------- //
|
||||
|
||||
bool
|
||||
Slots::reduceRelayReady()
|
||||
{
|
||||
if (!reduceRelayReady_)
|
||||
reduceRelayReady_ =
|
||||
std::chrono::duration_cast<std::chrono::minutes>(
|
||||
clock_.now().time_since_epoch()) > reduce_relay::WAIT_ON_BOOTUP;
|
||||
|
||||
return reduceRelayReady_;
|
||||
}
|
||||
|
||||
void
|
||||
Slots::registerSquelchedValidator(
|
||||
PublicKey const& validatorKey,
|
||||
Peer::id_t peerID)
|
||||
{
|
||||
peersWithSquelchedValidators_[validatorKey].insert(peerID);
|
||||
}
|
||||
|
||||
bool
|
||||
Slots::expireAndIsValidatorSquelched(PublicKey const& validatorKey)
|
||||
{
|
||||
beast::expire(
|
||||
peersWithSquelchedValidators_,
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT);
|
||||
|
||||
return peersWithSquelchedValidators_.find(validatorKey) !=
|
||||
peersWithSquelchedValidators_.end();
|
||||
}
|
||||
|
||||
bool
|
||||
Slots::expireAndIsPeerSquelched(
|
||||
PublicKey const& validatorKey,
|
||||
Peer::id_t peerID)
|
||||
{
|
||||
beast::expire(
|
||||
peersWithSquelchedValidators_,
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT);
|
||||
|
||||
auto const it = peersWithSquelchedValidators_.find(validatorKey);
|
||||
|
||||
// if validator was not squelched, the peer was also not squelched
|
||||
if (it == peersWithSquelchedValidators_.end())
|
||||
return false;
|
||||
|
||||
// if a peer is found the squelch for it has not expired
|
||||
return it->second.find(peerID) != it->second.end();
|
||||
}
|
||||
|
||||
bool
|
||||
Slots::expireAndIsPeerMessageCached(uint256 const& key, Peer::id_t id)
|
||||
{
|
||||
beast::expire(peersWithMessage_, reduce_relay::PEER_IDLED);
|
||||
|
||||
// return false if the ID was not inserted
|
||||
if (key.isNonZero())
|
||||
return !peersWithMessage_[key].insert(id).second;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
Slots::updateSlotAndSquelch(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t id,
|
||||
typename Slot::ignored_squelch_callback report,
|
||||
bool isTrusted)
|
||||
{
|
||||
if (expireAndIsPeerMessageCached(key, id))
|
||||
return;
|
||||
|
||||
// If we receive a message from a trusted validator either update an
|
||||
// existing slot or insert a new one. If we are not running enhanced
|
||||
// squelching also deduplicate untrusted validator messages
|
||||
if (isTrusted || !enhancedSquelchEnabled_)
|
||||
{
|
||||
// if enhanced squelching is disabled, keep untrusted validator slots
|
||||
// separately from trusted ones
|
||||
auto it = (isTrusted ? trustedSlots_ : untrustedSlots_)
|
||||
.emplace(std::make_pair(
|
||||
validator,
|
||||
Slot(
|
||||
handler_,
|
||||
logs_.journal("Slot"),
|
||||
maxSelectedPeers_,
|
||||
isTrusted,
|
||||
clock_)))
|
||||
.first;
|
||||
|
||||
it->second.update(validator, id, report);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = untrustedSlots_.find(validator);
|
||||
// If we received a message from a validator that is not
|
||||
// selected, and is not squelched, there is nothing to do. It
|
||||
// will be squelched later when `updateValidatorSlot` is called.
|
||||
if (it == untrustedSlots_.end())
|
||||
return;
|
||||
|
||||
it->second.update(validator, id, report);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Slots::updateUntrustedValidatorSlot(
|
||||
uint256 const& key,
|
||||
PublicKey const& validator,
|
||||
Peer::id_t id,
|
||||
typename Slot::ignored_squelch_callback report)
|
||||
{
|
||||
// We received a message from an already selected validator
|
||||
// we can ignore this message
|
||||
if (untrustedSlots_.find(validator) != untrustedSlots_.end())
|
||||
return;
|
||||
|
||||
// Did we receive a message from an already squelched validator?
|
||||
// This could happen in few cases:
|
||||
// 1. It happened so that the squelch for a particular peer expired
|
||||
// before our local squelch.
|
||||
// 2. We receive a message from a new peer that did not receive the
|
||||
// squelch request.
|
||||
// 3. The peer is ignoring our squelch request and we have not sent
|
||||
// the control message in a while.
|
||||
// In all of these cases we can only send them a squelch request again.
|
||||
if (expireAndIsValidatorSquelched(validator))
|
||||
{
|
||||
if (!expireAndIsPeerSquelched(validator, id))
|
||||
{
|
||||
JLOG(journal_.debug())
|
||||
<< "updateUntrustedValidatorSlot: received a message from a "
|
||||
"squelched validator "
|
||||
<< "validator: " << toBase58(TokenType::NodePublic, validator)
|
||||
<< " peer: " << id;
|
||||
|
||||
registerSquelchedValidator(validator, id);
|
||||
handler_.squelch(
|
||||
validator,
|
||||
id,
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT.count());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we have any available slots for additional untrusted validators?
|
||||
// This could happen in few cases:
|
||||
// 1. We received a message from a new untrusted validator, but we
|
||||
// are at capacity.
|
||||
// 2. We received a message from a previously squelched validator.
|
||||
// In all of these cases we send a squelch message to all peers.
|
||||
// The validator may still be considered by the selector. However, it
|
||||
// will be eventually cleaned and squelched
|
||||
if (untrustedSlots_.size() == reduce_relay::MAX_UNTRUSTED_SLOTS)
|
||||
{
|
||||
JLOG(journal_.debug())
|
||||
<< "updateUntrustedValidatorSlot: slots full squelching validator "
|
||||
<< "validator: " << toBase58(TokenType::NodePublic, validator);
|
||||
|
||||
handler_.squelchAll(
|
||||
validator,
|
||||
MAX_UNSQUELCH_EXPIRE_DEFAULT.count(),
|
||||
[&](Peer::id_t id) { registerSquelchedValidator(validator, id); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto const v = updateConsideredValidator(validator, id))
|
||||
{
|
||||
JLOG(journal_.debug())
|
||||
<< "updateUntrustedValidatorSlot: selected untrusted validator "
|
||||
<< "validator: " << toBase58(TokenType::NodePublic, *v);
|
||||
|
||||
untrustedSlots_.emplace(std::make_pair(
|
||||
*v,
|
||||
Slot(
|
||||
handler_,
|
||||
logs_.journal("Slot"),
|
||||
maxSelectedPeers_,
|
||||
false,
|
||||
clock_)));
|
||||
}
|
||||
// When we reach MAX_UNTRUSTED_SLOTS, don't explicitly clean them.
|
||||
// Since we stop updating their counters, they will idle, and will be
|
||||
// removed and squelched.
|
||||
}
|
||||
|
||||
std::optional<PublicKey>
|
||||
Slots::updateConsideredValidator(PublicKey const& validator, Peer::id_t peer)
|
||||
{
|
||||
auto const now = clock_.now();
|
||||
|
||||
auto it = consideredValidators_.find(validator);
|
||||
if (it == consideredValidators_.end())
|
||||
{
|
||||
consideredValidators_.emplace(std::make_pair(
|
||||
validator,
|
||||
ValidatorInfo{
|
||||
.count = 1,
|
||||
.lastMessage = now,
|
||||
.peers = {peer},
|
||||
}));
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
it->second.peers.insert(peer);
|
||||
it->second.lastMessage = now;
|
||||
++it->second.count;
|
||||
|
||||
// if the validator has not met selection criteria yet
|
||||
if (it->second.count < reduce_relay::MAX_MESSAGE_THRESHOLD)
|
||||
return std::nullopt;
|
||||
|
||||
auto const key = it->first;
|
||||
consideredValidators_.erase(it);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void
|
||||
Slots::squelchUntrustedValidator(PublicKey const& validator)
|
||||
{
|
||||
JLOG(journal_.info())
|
||||
<< "squelchUntrustedValidator: squelching untrusted validator: "
|
||||
<< toBase58(TokenType::NodePublic, validator);
|
||||
// to prevent the validator from being reinserted squelch the validator
|
||||
// before removing the validator from consideration and slots
|
||||
handler_.squelchAll(
|
||||
validator, MAX_UNSQUELCH_EXPIRE_DEFAULT.count(), [&](Peer::id_t id) {
|
||||
registerSquelchedValidator(validator, id);
|
||||
});
|
||||
|
||||
consideredValidators_.erase(validator);
|
||||
untrustedSlots_.erase(validator);
|
||||
}
|
||||
|
||||
void
|
||||
Slots::deletePeer(Peer::id_t id, bool erase)
|
||||
{
|
||||
auto const f = [&](slots_map& slots) {
|
||||
for (auto& [validator, slot] : slots)
|
||||
slot.deletePeer(validator, id, erase);
|
||||
};
|
||||
|
||||
f(trustedSlots_);
|
||||
f(untrustedSlots_);
|
||||
}
|
||||
|
||||
void
|
||||
Slots::deleteIdlePeers()
|
||||
{
|
||||
auto const f = [&](slots_map& slots) {
|
||||
auto const now = clock_.now();
|
||||
|
||||
for (auto it = slots.begin(); it != slots.end();)
|
||||
{
|
||||
auto const& validator = it->first;
|
||||
auto& slot = it->second;
|
||||
slot.deleteIdlePeer(validator);
|
||||
|
||||
// delete the slot if the untrusted slot no longer meets the
|
||||
// selection critera or it has not been selected for a while
|
||||
if ((!slot.isTrusted_ &&
|
||||
slot.getPeers().size() < maxSelectedPeers_) ||
|
||||
now - it->second.getLastSelected() >
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT)
|
||||
{
|
||||
JLOG(journal_.trace())
|
||||
<< "deleteIdlePeers: deleting "
|
||||
<< (slot.isTrusted_ ? "trusted" : "untrusted") << " slot "
|
||||
<< toBase58(TokenType::NodePublic, it->first) << " reason: "
|
||||
<< (now - it->second.getLastSelected() >
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT
|
||||
? " inactive "
|
||||
: " insufficient peers");
|
||||
|
||||
// if an untrusted validator slot idled - peers stopped
|
||||
// sending messages for this validator squelch it
|
||||
if (!it->second.isTrusted_)
|
||||
handler_.squelchAll(
|
||||
it->first,
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT.count(),
|
||||
[&](Peer::id_t id) {
|
||||
registerSquelchedValidator(it->first, id);
|
||||
});
|
||||
|
||||
it = slots.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
};
|
||||
|
||||
f(trustedSlots_);
|
||||
f(untrustedSlots_);
|
||||
|
||||
// remove and squelch all validators that the selector deemed unsuitable
|
||||
// there might be some good validators in this set that "lapsed".
|
||||
// However, since these are untrusted validators we're not concerned
|
||||
for (auto const& validator : cleanConsideredValidators())
|
||||
handler_.squelchAll(
|
||||
validator,
|
||||
reduce_relay::MAX_UNSQUELCH_EXPIRE_DEFAULT.count(),
|
||||
[&](Peer::id_t id) { registerSquelchedValidator(validator, id); });
|
||||
}
|
||||
|
||||
std::vector<PublicKey>
|
||||
Slots::cleanConsideredValidators()
|
||||
{
|
||||
auto const now = clock_.now();
|
||||
|
||||
std::vector<PublicKey> keys;
|
||||
std::stringstream ss;
|
||||
for (auto it = consideredValidators_.begin();
|
||||
it != consideredValidators_.end();)
|
||||
{
|
||||
if (now - it->second.lastMessage >
|
||||
reduce_relay::MAX_UNTRUSTED_VALIDATOR_IDLE)
|
||||
{
|
||||
keys.push_back(it->first);
|
||||
ss << " " << toBase58(TokenType::NodePublic, it->first);
|
||||
it = consideredValidators_.erase(it);
|
||||
}
|
||||
// Due to some reason the validator idled, reset their progress
|
||||
else if (now - it->second.lastMessage > reduce_relay::PEER_IDLED)
|
||||
{
|
||||
it->second.reset();
|
||||
++it;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
if (keys.size() > 0)
|
||||
{
|
||||
JLOG(journal_.info())
|
||||
<< "cleanConsideredValidators: removed considered validators "
|
||||
<< ss.str();
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
void
|
||||
Slots::onWrite(beast::PropertyStream::Map& stream) const
|
||||
{
|
||||
auto const writeSlot = [](beast::PropertyStream::Set& set,
|
||||
hash_map<PublicKey, Slot> const& slots) {
|
||||
for (auto const& [validator, slot] : slots)
|
||||
{
|
||||
beast::PropertyStream::Map item(set);
|
||||
item["validator"] = toBase58(TokenType::NodePublic, validator);
|
||||
slot.onWrite(item);
|
||||
}
|
||||
};
|
||||
|
||||
beast::PropertyStream::Map slots("slots", stream);
|
||||
|
||||
{
|
||||
beast::PropertyStream::Set set("trusted", slots);
|
||||
writeSlot(set, trustedSlots_);
|
||||
}
|
||||
|
||||
{
|
||||
beast::PropertyStream::Set set("untrusted", slots);
|
||||
writeSlot(set, untrustedSlots_);
|
||||
}
|
||||
|
||||
{
|
||||
beast::PropertyStream::Set set("considered", slots);
|
||||
|
||||
auto const now = clock_.now();
|
||||
|
||||
for (auto const& [validator, info] : consideredValidators_)
|
||||
{
|
||||
beast::PropertyStream::Map item(set);
|
||||
item["validator"] = toBase58(TokenType::NodePublic, validator);
|
||||
item["lastMessage"] =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now - info.lastMessage)
|
||||
.count();
|
||||
item["messageCount"] = info.count;
|
||||
item["peers"] = info.peers.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reduce_relay
|
||||
|
||||
} // namespace ripple
|
||||
@@ -1,101 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/SquelchStore.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace reduce_relay {
|
||||
|
||||
void
|
||||
SquelchStore::handleSquelch(
|
||||
PublicKey const& validator,
|
||||
bool squelch,
|
||||
std::chrono::seconds duration)
|
||||
{
|
||||
// Remove all expired squelches. This call is here, as it is on the least
|
||||
// critical execution path, that does not require periodic cleanup calls.
|
||||
removeExpired();
|
||||
|
||||
if (squelch)
|
||||
{
|
||||
// This should never trigger. The squelch duration is validated in
|
||||
// PeerImp.onMessage(TMSquelch). However, if somehow invalid duration is
|
||||
// passed, log is as an error
|
||||
if ((duration < reduce_relay::MIN_UNSQUELCH_EXPIRE ||
|
||||
duration > reduce_relay::MAX_UNSQUELCH_EXPIRE_PEERS))
|
||||
{
|
||||
JLOG(journal_.error())
|
||||
<< "SquelchStore: invalid squelch duration validator: "
|
||||
<< Slice(validator) << " duration: " << duration.count();
|
||||
return;
|
||||
}
|
||||
|
||||
add(validator, duration);
|
||||
return;
|
||||
}
|
||||
|
||||
remove(validator);
|
||||
}
|
||||
|
||||
bool
|
||||
SquelchStore::isSquelched(PublicKey const& validator) const
|
||||
{
|
||||
auto const now = clock_.now();
|
||||
|
||||
auto const it = squelched_.find(validator);
|
||||
if (it == squelched_.end())
|
||||
return false;
|
||||
|
||||
return it->second > now;
|
||||
}
|
||||
|
||||
void
|
||||
SquelchStore::add(
|
||||
PublicKey const& validator,
|
||||
std::chrono::seconds const& duration)
|
||||
{
|
||||
squelched_[validator] = clock_.now() + duration;
|
||||
}
|
||||
|
||||
void
|
||||
SquelchStore::remove(PublicKey const& validator)
|
||||
{
|
||||
squelched_.erase(validator);
|
||||
}
|
||||
|
||||
void
|
||||
SquelchStore::removeExpired()
|
||||
{
|
||||
auto const now = clock_.now();
|
||||
std::erase_if(
|
||||
squelched_, [&](auto const& entry) { return entry.second < now; });
|
||||
}
|
||||
|
||||
} // namespace reduce_relay
|
||||
|
||||
} // namespace ripple
|
||||
Reference in New Issue
Block a user