mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-25 03:26:59 +00:00
Compare commits
4 Commits
legleux/va
...
mvadari/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
537c520e32 | ||
|
|
310bfc7b94 | ||
|
|
afc0b7ab8c | ||
|
|
556d62a0de |
@@ -206,6 +206,6 @@ CheckOptions:
|
||||
readability-identifier-naming.PublicMemberSuffix: ""
|
||||
readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$"
|
||||
|
||||
HeaderFilterRegex: '^.*/(tests?|xrpl|xrpld|validator-keys-tool)/.*\.(h|hpp|ipp)$'
|
||||
HeaderFilterRegex: '^.*/(tests?|xrpl|xrpld)/.*\.(h|hpp|ipp)$'
|
||||
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$'
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -50,8 +50,7 @@
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON"
|
||||
"arch": ["amd64"]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -59,8 +58,7 @@
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON"
|
||||
"arch": ["amd64"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
9
.github/workflows/on-pr.yml
vendored
9
.github/workflows/on-pr.yml
vendored
@@ -67,7 +67,6 @@ jobs:
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-test-conan-package.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
.clang-tidy
|
||||
.codecov.yml
|
||||
@@ -79,7 +78,6 @@ jobs:
|
||||
include/**
|
||||
src/**
|
||||
tests/**
|
||||
validator-keys-tool/**
|
||||
CMakeLists.txt
|
||||
conanfile.py
|
||||
conan.lock
|
||||
@@ -148,16 +146,10 @@ jobs:
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
test-conan-package:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-test-conan-package.yml
|
||||
|
||||
upload-recipe:
|
||||
needs:
|
||||
- should-run
|
||||
- build-test
|
||||
- test-conan-package
|
||||
# Only run when committing to a PR that targets a release branch.
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release') }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
@@ -189,7 +181,6 @@ jobs:
|
||||
- clang-tidy
|
||||
- build-test
|
||||
- package
|
||||
- test-conan-package
|
||||
- upload-recipe
|
||||
- notify-clio
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
5
.github/workflows/on-tag.yml
vendored
5
.github/workflows/on-tag.yml
vendored
@@ -16,13 +16,8 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
test-conan-package:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-test-conan-package.yml
|
||||
|
||||
upload-recipe:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
needs: test-conan-package
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
|
||||
9
.github/workflows/on-trigger.yml
vendored
9
.github/workflows/on-trigger.yml
vendored
@@ -24,7 +24,6 @@ on:
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-test-conan-package.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
- ".clang-tidy"
|
||||
- ".codecov.yml"
|
||||
@@ -36,7 +35,6 @@ on:
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
- "tests/**"
|
||||
- "validator-keys-tool/**"
|
||||
- "CMakeLists.txt"
|
||||
- "conanfile.py"
|
||||
- "conan.lock"
|
||||
@@ -94,13 +92,8 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
test-conan-package:
|
||||
uses: ./.github/workflows/reusable-test-conan-package.yml
|
||||
|
||||
upload-recipe:
|
||||
needs:
|
||||
- build-test
|
||||
- test-conan-package
|
||||
needs: build-test
|
||||
# Only run when pushing to the develop branch.
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
|
||||
40
.github/workflows/reusable-build-test-config.yml
vendored
40
.github/workflows/reusable-build-test-config.yml
vendored
@@ -227,7 +227,8 @@ jobs:
|
||||
--build . \
|
||||
--config "${BUILD_TYPE}" \
|
||||
--parallel "${BUILD_NPROC}" \
|
||||
--target "${CMAKE_TARGET}"
|
||||
--target "${CMAKE_TARGET}" \
|
||||
2>&1 | tee build.log
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: ${{ inputs.ccache_enabled }}
|
||||
@@ -247,25 +248,6 @@ jobs:
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Check validator-keys binary (Linux)
|
||||
id: validator_keys_binary
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
run: |
|
||||
if [ -x "${BUILD_DIR}/validator-keys" ]; then
|
||||
echo "present=true" >>"${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "present=false" >>"${GITHUB_OUTPUT}"
|
||||
fi
|
||||
|
||||
- name: Upload validator-keys binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' && steps.validator_keys_binary.outputs.present == 'true' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: validator-keys-${{ inputs.config_name }}
|
||||
path: ${{ env.BUILD_DIR }}/validator-keys
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload the test binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
@@ -344,7 +326,7 @@ jobs:
|
||||
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
if: ${{ failure() }}
|
||||
env:
|
||||
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
run: |
|
||||
@@ -355,13 +337,17 @@ jobs:
|
||||
|
||||
cd "${WORKING_DIR}"
|
||||
|
||||
if [ ! -f unittest.log ]; then
|
||||
if [ -f unittest.log ]; then
|
||||
if ! grep -E "failed" unittest.log | grep -vE "^I[0-9]|^[0-9]+> (ERR:|FTL:)"; then
|
||||
echo "unittest.log present but no failure lines found."
|
||||
fi
|
||||
else
|
||||
echo "unittest.log not found; embedded tests may not have run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! grep -E "failed" unittest.log; then
|
||||
echo "Log present but no failure lines found in unittest.log."
|
||||
if [ -f build.log ]; then
|
||||
if ! grep -E "error:" build.log; then
|
||||
echo "build.log present but no compile errors found."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
- name: Debug failure (Linux)
|
||||
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}
|
||||
|
||||
24
.github/workflows/reusable-package.yml
vendored
24
.github/workflows/reusable-package.yml
vendored
@@ -39,23 +39,8 @@ jobs:
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
run: ./generate.py --packaging >>"${GITHUB_OUTPUT}"
|
||||
|
||||
generate-version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
src/libxrpl/protocol/BuildInfo.cpp
|
||||
- name: Generate version
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
|
||||
package:
|
||||
needs: [generate-matrix, generate-version]
|
||||
needs: [generate-matrix]
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -77,19 +62,18 @@ jobs:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binaries executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld" "${BUILD_DIR}/validator-keys"
|
||||
- name: Make binary executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PKG_VERSION: ${{ needs.generate-version.outputs.version }}
|
||||
PKG_RELEASE: ${{ inputs.pkg_release }}
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
|
||||
name: ${{ matrix.artifact_name }}-pkg
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# Build the Conan package and run the consumer test package.
|
||||
name: Test Conan package
|
||||
|
||||
# This workflow can only be triggered by other workflows.
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
test-conan-package:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
- name: Detect build parallelism
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Export Conan package under test
|
||||
run: conan export . --version=head
|
||||
|
||||
- name: Run Conan package test
|
||||
working-directory: tests/conan
|
||||
run: |
|
||||
conan test . xrpl/head \
|
||||
--profile:all ci \
|
||||
--build=missing \
|
||||
--settings:all build_type=Release \
|
||||
--conf:all tools.build:jobs="${{ steps.nproc.outputs.nproc }}"
|
||||
@@ -135,9 +135,9 @@ endif()
|
||||
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplValidatorKeys)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
if(tests)
|
||||
include(CTest)
|
||||
|
||||
@@ -25,23 +25,9 @@ if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET xrpld)
|
||||
message(STATUS "xrpld=ON is required; 'package' target not available")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET validator-keys)
|
||||
message(
|
||||
STATUS
|
||||
"validator_keys=ON is required; 'package' target not available"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(package_env
|
||||
SRC_DIR=${CMAKE_SOURCE_DIR}
|
||||
BUILD_DIR=${CMAKE_BINARY_DIR}
|
||||
PKG_VERSION=${xrpld_version}
|
||||
PKG_RELEASE=${pkg_release}
|
||||
)
|
||||
|
||||
@@ -51,7 +37,7 @@ add_custom_target(
|
||||
${CMAKE_COMMAND} -E env ${package_env}
|
||||
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS xrpld validator-keys
|
||||
DEPENDS xrpld
|
||||
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
option(
|
||||
validator_keys
|
||||
"Enables building of the vendored validator-keys tool as a separate target"
|
||||
"Enables building of validator-keys tool as a separate target (imported via FetchContent)"
|
||||
OFF
|
||||
)
|
||||
|
||||
if(validator_keys)
|
||||
include(GNUInstallDirs)
|
||||
git_branch(current_branch)
|
||||
# default to tracking VK master branch unless we are on release
|
||||
if(NOT (current_branch STREQUAL "release"))
|
||||
set(current_branch "master")
|
||||
endif()
|
||||
message(STATUS "Tracking ValidatorKeys branch: ${current_branch}")
|
||||
|
||||
add_subdirectory(
|
||||
"${CMAKE_SOURCE_DIR}/validator-keys-tool"
|
||||
"${CMAKE_BINARY_DIR}/validator-keys-tool"
|
||||
FetchContent_Declare(
|
||||
validator_keys
|
||||
GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git
|
||||
GIT_TAG "${current_branch}"
|
||||
)
|
||||
FetchContent_MakeAvailable(validator_keys)
|
||||
set_target_properties(
|
||||
validator-keys
|
||||
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
)
|
||||
install(
|
||||
TARGETS validator-keys
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT runtime
|
||||
)
|
||||
install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
@@ -116,10 +116,8 @@ words:
|
||||
- fcontext
|
||||
- finalizers
|
||||
- firewalled
|
||||
- fprofile
|
||||
- fmtdur
|
||||
- fsanitize
|
||||
- ftest
|
||||
- funclets
|
||||
- gcov
|
||||
- gcovr
|
||||
@@ -129,11 +127,9 @@ words:
|
||||
- gpgcheck
|
||||
- gpgkey
|
||||
- hotwallet
|
||||
- hvssbqmgz
|
||||
- hwaddress
|
||||
- hwrap
|
||||
- ifndef
|
||||
- Iiwib
|
||||
- inequation
|
||||
- insuf
|
||||
- insuff
|
||||
@@ -279,7 +275,6 @@ words:
|
||||
- sslws
|
||||
- statsd
|
||||
- STATSDCOLLECTOR
|
||||
- STRINGIZE
|
||||
- stissue
|
||||
- stnum
|
||||
- stobj
|
||||
@@ -306,6 +301,7 @@ words:
|
||||
- txs
|
||||
- ubsan
|
||||
- UBSAN
|
||||
- ufdio
|
||||
- umant
|
||||
- unacquired
|
||||
- unambiguity
|
||||
@@ -340,12 +336,10 @@ words:
|
||||
- writeme
|
||||
- wsrch
|
||||
- wthread
|
||||
- Wsuggest
|
||||
- xbridge
|
||||
- xchain
|
||||
- ximinez
|
||||
- XMACRO
|
||||
- xcrun
|
||||
- xrpkuwait
|
||||
- xrpl
|
||||
- xrpld
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
# Linux Packaging
|
||||
|
||||
This directory contains all files needed to build RPM and Debian packages for
|
||||
`xrpld`. The packages also include the `validator-keys` utility.
|
||||
This directory contains all files needed to build RPM and Debian packages for `xrpld`.
|
||||
|
||||
## Directory layout
|
||||
|
||||
```
|
||||
package/
|
||||
build_pkg.sh Staging and build script (called by CMake targets and CI)
|
||||
build_pkg.sh Staging and build script (called by the CMake `package` target and CI)
|
||||
rpm/
|
||||
xrpld.spec RPM spec (xrpld_version/pkg_release passed via rpmbuild --define)
|
||||
debian/ Debian control files (control, rules, install, links, conffiles, ...)
|
||||
xrpld.spec RPM spec
|
||||
debian/ Debian control files (control, rules, copyright, xrpld.docs, xrpld.links, source/format)
|
||||
shared/
|
||||
xrpld.service systemd unit file (used by both RPM and DEB)
|
||||
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
|
||||
@@ -22,21 +21,19 @@ package/
|
||||
|
||||
Packaging targets and their container images are declared in
|
||||
[`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json)
|
||||
inside `package_configs` configurations. Today only
|
||||
`linux/amd64` is emitted. The package format
|
||||
(deb or rpm) is inferred at build time from the container's package manager
|
||||
(`apt-get` -> deb, `dnf`/`yum` -> rpm). The image tag is composed as
|
||||
`ghcr.io/xrplf/xrpld/packaging-<distro>:sha-<git_sha>` —
|
||||
the same scheme used by `reusable-build-test.yml`. Bump `image_sha` in
|
||||
`linux.json` and both CI and local builds pick up the new image with no
|
||||
workflow edits.
|
||||
under `package_configs`, one entry per distro. Today only `linux/amd64` is
|
||||
emitted. Each entry pins its full container image in an `image` field; to move
|
||||
to a new image, edit that field and both CI and local builds pick it up. The
|
||||
package format (deb or rpm) is inferred at build time from the container's
|
||||
package manager (`apt-get` -> deb, `dnf`/`yum` -> rpm).
|
||||
|
||||
| Package type | Image (derived from `linux.json`) | Tool required |
|
||||
| ------------ | ---------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-<git_sha>` | `rpmbuild` |
|
||||
| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-<git_sha>` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` |
|
||||
| Package type | Image (`package_configs.<distro>[].image` in `linux.json`) | Tools required |
|
||||
| ------------ | ---------------------------------------------------------- | --------------------------------------------------- |
|
||||
| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-<sha>` | `rpmbuild` |
|
||||
| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-<sha>` | `dpkg-buildpackage`, debhelper with compat level 13 |
|
||||
|
||||
To print the exact image tags for the current `linux.json`:
|
||||
To print the full packaging matrix (artifact names and images) for the current
|
||||
`linux.json`:
|
||||
|
||||
```bash
|
||||
./.github/scripts/strategy-matrix/generate.py --packaging
|
||||
@@ -47,37 +44,34 @@ To print the exact image tags for the current `linux.json`:
|
||||
### Via CI
|
||||
|
||||
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
|
||||
`reusable-strategy-matrix.yml` with `mode: packaging` to generate the matrix of
|
||||
`{artifact_name, os}` entries, then fan out to
|
||||
`reusable-package.yml` per entry. That workflow downloads the pre-built binary
|
||||
artifact containing `xrpld` and `validator-keys`, detects the package format
|
||||
from the container, and calls `build_pkg.sh` directly — no CMake configure or
|
||||
`reusable-package.yml`. That workflow generates its own packaging matrix from
|
||||
`package_configs` in `linux.json` (via `generate.py --packaging`) and fans out
|
||||
one job per distro. Each job downloads the pre-built `xrpld` binary artifact and
|
||||
runs in that distro's container, so the package format follows from the
|
||||
container's package manager. The packaging script derives the package version
|
||||
from the downloaded binary's `xrpld --version` output; no CMake configure or
|
||||
build step is needed inside the packaging job.
|
||||
|
||||
### Locally (mirrors CI)
|
||||
|
||||
With `xrpld` and `validator-keys` binaries already built at `build/xrpld` and
|
||||
`build/validator-keys`, run the packaging step inside the same container CI
|
||||
uses. The image tag is derived from `linux.json` so you don't need to hardcode a
|
||||
SHA.
|
||||
With an `xrpld` binary already built at `build/xrpld`, run the packaging step
|
||||
inside the same container CI uses. The image tag is derived from `linux.json`
|
||||
so you don't need to hardcode a SHA.
|
||||
|
||||
```bash
|
||||
# From the repo root. Pick any image flagged with `"package": true` in
|
||||
# linux.json; the package format is inferred from the container's package
|
||||
# manager. Example for the rpm-producing image:
|
||||
IMAGE=$(jq -r '
|
||||
.os | map(select(.package == true))[0] |
|
||||
"ghcr.io/xrplf/ci/\(.distro_name)-\(.distro_version):\(.compiler_name)-\(.compiler_version)-sha-\(.image_sha)"
|
||||
' .github/scripts/strategy-matrix/linux.json)
|
||||
# From the repo root. Each distro's container image is the `image` field of its
|
||||
# package_configs entry in linux.json; the package format is inferred from the
|
||||
# container's package manager. Example for the rpm-producing image (use
|
||||
# .package_configs.debian[0].image for the deb image):
|
||||
IMAGE=$(jq -r '.package_configs.rhel[0].image' .github/scripts/strategy-matrix/linux.json)
|
||||
|
||||
VERSION=2.4.0-local
|
||||
PKG_RELEASE=1
|
||||
|
||||
docker run --rm \
|
||||
-v "$(pwd):/src" \
|
||||
-w /src \
|
||||
"$IMAGE" \
|
||||
./package/build_pkg.sh --pkg-version "$VERSION" --pkg-release "$PKG_RELEASE"
|
||||
"${IMAGE}" \
|
||||
./package/build_pkg.sh --pkg-release "${PKG_RELEASE}"
|
||||
|
||||
# Output:
|
||||
# build/debbuild/*.deb (DEB + dbgsym .ddeb)
|
||||
@@ -93,42 +87,73 @@ needed, but the host toolchain replaces the pinned CI image:
|
||||
```bash
|
||||
cmake \
|
||||
-Dxrpld=ON \
|
||||
-Dvalidator_keys=ON \
|
||||
-Dxrpld_version=2.4.0-local \
|
||||
-Dpkg_release=1 \
|
||||
-Dtests=OFF \
|
||||
..
|
||||
|
||||
cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL
|
||||
```
|
||||
|
||||
The `cmake/XrplPackaging.cmake` module defines the target only if at least one
|
||||
of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then infers the
|
||||
package format from the host's package manager. The packaging script installs
|
||||
to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
|
||||
The `cmake/XrplPackaging.cmake` module defines the `package` target only if at
|
||||
least one of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then
|
||||
infers the package format from the host's package manager. The packaging script
|
||||
installs to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
|
||||
`CMAKE_INSTALL_PREFIX`.
|
||||
|
||||
The package version is not a CMake input on this path: `build_pkg.sh` derives it
|
||||
from the just-built `xrpld` binary's `xrpld --version` output. The package
|
||||
release defaults to 1 and is overridable with `-Dpkg_release=N`.
|
||||
|
||||
## How `build_pkg.sh` works
|
||||
|
||||
`build_pkg.sh` accepts long-form flags, each of which can also be set via an
|
||||
environment variable. Flags override env vars; env vars override the built-in
|
||||
defaults. Run `./package/build_pkg.sh --help` for the same table:
|
||||
`build_pkg.sh` derives the `xrpld` software version from
|
||||
`${BUILD_DIR}/xrpld --version` in both package formats.
|
||||
|
||||
| Flag | Env var | Default | Purpose |
|
||||
| -------------------------- | ------------------- | ----------------------------- | ------------------------------------ |
|
||||
| `--src-dir DIR` | `SRC_DIR` | `$PWD` | repo root |
|
||||
| `--build-dir DIR` | `BUILD_DIR` | `$PWD/build` | directory holding pre-built binaries |
|
||||
| `--pkg-version STR` | `PKG_VERSION` | parsed from `xrpld --version` | version string, e.g. `3.2.0-b1` |
|
||||
| `--pkg-release N` | `PKG_RELEASE` | `1` | package release number |
|
||||
| `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime | reproducibility timestamp |
|
||||
The binary's version is already SemVer-validated by `BuildInfo`.
|
||||
`build_pkg.sh` converts pre-release versions such as `3.2.0-b1` or
|
||||
`3.2.0-rc1` from `-` to `~` for package metadata so pre-releases sort before
|
||||
the final release. If that normalized package version still contains `-`,
|
||||
packaging fails because RPM forbids `-` in `Version`, and Debian uses `-` as
|
||||
the upstream/revision separator.
|
||||
|
||||
`pkg_version` is the normalized package metadata version derived inside
|
||||
`build_pkg.sh` from the binary-reported `xrpld` version (`-` pre-release
|
||||
separator converted to `~`). It is not a separate user input.
|
||||
|
||||
`PKG_RELEASE` is a different value: the package release iteration for that
|
||||
`xrpld` version. RPM receives the normalized `pkg_version` and `PKG_RELEASE` as
|
||||
the `pkg_version` and `pkg_release` macros for its `Version` and `Release`
|
||||
values; DEB writes them as `${pkg_version}-${PKG_RELEASE}` in
|
||||
`debian/changelog`.
|
||||
|
||||
With `PKG_RELEASE=1`, the package metadata becomes:
|
||||
|
||||
| Input version | RPM version/release | Debian version |
|
||||
| ------------------ | ---------------------------- | -------------------- |
|
||||
| `3.2.0` | `3.2.0-1%{?dist}` | `3.2.0-1` |
|
||||
| `3.2.0-b0+abc1234` | `3.2.0~b0+abc1234-1%{?dist}` | `3.2.0~b0+abc1234-1` |
|
||||
| `3.2.0-b1` | `3.2.0~b1-1%{?dist}` | `3.2.0~b1-1` |
|
||||
| `3.2.0-rc1` | `3.2.0~rc1-1%{?dist}` | `3.2.0~rc1-1` |
|
||||
|
||||
The Debian changelog entry carries the repository component: final releases use
|
||||
`stable`, `b0` builds, including `b0+metadata`, use `develop`, and `bN`/`rcN`
|
||||
pre-releases use `unstable`.
|
||||
Build metadata on a final release, such as `3.2.0+abc123`, is rejected.
|
||||
|
||||
The RPM path intentionally uses `~` in `Version`, matching the Debian
|
||||
pre-release ordering convention, so RPM filenames/NVRs begin with forms like
|
||||
`xrpld-3.2.0~b1-...` and `xrpld-3.2.0~rc1-...` instead of encoding
|
||||
pre-releases with an older `0.<release>.<suffix>` RPM `Release` value.
|
||||
|
||||
The package format (`deb` or `rpm`) is inferred from the host's package
|
||||
manager (`apt-get` -> deb, `dnf`/`yum` -> rpm). Hosts without one of those
|
||||
fail early.
|
||||
|
||||
Flags are for explicit invocation; environment variables are intended for
|
||||
CMake/systemd/CI integration. The CI workflow and the CMake `package` target
|
||||
both invoke `build_pkg.sh` with no flags, configuring it entirely via env
|
||||
(see `cmake/XrplPackaging.cmake`).
|
||||
CMake/CI integration. The CI workflow and the CMake `package` target both invoke
|
||||
`build_pkg.sh` with no flags; CMake supplies `SRC_DIR`, `BUILD_DIR`, and
|
||||
`PKG_RELEASE` via env, while CI supplies `BUILD_DIR` and `PKG_RELEASE` via env
|
||||
and lets the script use defaults for the rest.
|
||||
|
||||
It resolves `SRC_DIR` and `BUILD_DIR` to absolute paths, then calls
|
||||
`stage_common()` to copy the binary, config files, and shared support files
|
||||
@@ -137,18 +162,32 @@ into the staging area, and invokes the platform build tool.
|
||||
### RPM
|
||||
|
||||
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
|
||||
2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`.
|
||||
3. Runs `rpmbuild -bb --define "xrpld_version ..." --define "pkg_release ..."`. The spec uses manual `install` commands to place files.
|
||||
2. Copies `xrpld.spec` and all shared source files (binary, configs, service files) into `SOURCES/`.
|
||||
3. Runs `rpmbuild -bb`, passing the normalized package metadata version as the
|
||||
`pkg_version` RPM macro and `PKG_RELEASE` as the `pkg_release` RPM macro.
|
||||
The spec uses manual `install` commands to place files, disables `dwz`, and
|
||||
writes uncompressed RPM payloads while generating debuginfo packages.
|
||||
4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
|
||||
|
||||
The uncompressed RPM payload setting is intentionally unconditional for
|
||||
generated RPMs. It trades larger RPM artifacts for much shorter package
|
||||
build/validation time, which keeps RPM package validation in the same rough time
|
||||
class as Debian package validation.
|
||||
|
||||
RPM upgrades intentionally do not restart a running `xrpld` service. The spec
|
||||
uses `%systemd_postun`, matching Debian's `dh_installsystemd
|
||||
--no-stop-on-upgrade` behavior; operators pick up the new binary on the next
|
||||
service restart.
|
||||
|
||||
### DEB
|
||||
|
||||
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
|
||||
2. Stages the binaries, configs, `README.md`, and `LICENSE.md`.
|
||||
2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
|
||||
3. Copies `package/debian/` control files into `debbuild/source/debian/`.
|
||||
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
|
||||
5. Generates a minimal `debian/changelog` (pre-release versions use `~` instead of `-`).
|
||||
6. Runs `dpkg-buildpackage -b --no-sign`. `debian/rules` uses manual `install` commands.
|
||||
5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`,
|
||||
where `pkg_version` is derived from the binary-reported `xrpld` version.
|
||||
6. Runs `dpkg-buildpackage -b --no-sign -d` (`-d` skips the build-dependency check, since the binary is already built). `debian/rules` uses manual `install` commands.
|
||||
7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package)
|
||||
|
||||
## Post-build verification
|
||||
@@ -164,11 +203,14 @@ rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
|
||||
|
||||
## Reproducibility
|
||||
|
||||
The following environment variables improve build reproducibility. They are not
|
||||
set automatically by `build_pkg.sh`; set them manually if needed:
|
||||
`build_pkg.sh` already defaults `SOURCE_DATE_EPOCH` to the latest git commit
|
||||
time, or the current time outside a git tree, and exports it (override with
|
||||
`--source-date-epoch` / `SOURCE_DATE_EPOCH`); the RPM spec clamps file
|
||||
modification times to it via `%build_mtime_policy`. The remaining variables
|
||||
below further improve reproducibility but are _not_ set by the script — export
|
||||
them yourself if needed:
|
||||
|
||||
```bash
|
||||
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
export TZ=UTC
|
||||
export LC_ALL=C.UTF-8
|
||||
export GZIP=-n
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build an RPM or Debian package from pre-built xrpld and validator-keys
|
||||
# binaries.
|
||||
# Build an RPM or Debian package from a pre-built xrpld binary.
|
||||
#
|
||||
# Flags override env vars; env vars override defaults. Env vars are intended
|
||||
# for CMake/systemd/CI integration; flags are for explicit invocation.
|
||||
# Flags override env vars; env vars override defaults.
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: build_pkg.sh [options]
|
||||
|
||||
Options (each can also be set via the env var shown):
|
||||
--src-dir DIR repo root [SRC_DIR; default: $PWD]
|
||||
--build-dir DIR directory holding binaries [BUILD_DIR; default: $PWD/build]
|
||||
--pkg-version STR version, e.g. 3.2.0-b1 [PKG_VERSION; default: parsed from xrpld --version]
|
||||
--pkg-release N package release number [PKG_RELEASE; default: 1]
|
||||
--source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; default: latest git commit ctime]
|
||||
-h, --help show this help and exit
|
||||
--src-dir DIR repo root [SRC_DIR; default: ${PWD}]
|
||||
--build-dir DIR directory holding xrpld [BUILD_DIR; default: ${PWD}/build]
|
||||
--pkg-release N package release iteration [PKG_RELEASE; default: 1]
|
||||
--source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; latest git ctime; fallback: current time]
|
||||
-h, --help show this help and exit
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -31,8 +28,7 @@ need_arg() {
|
||||
# Seed from env. CLI parsing below overrides these directly.
|
||||
SRC_DIR="${SRC_DIR:-}"
|
||||
BUILD_DIR="${BUILD_DIR:-}"
|
||||
PKG_VERSION="${PKG_VERSION:-}"
|
||||
PKG_RELEASE="${PKG_RELEASE:-}"
|
||||
PKG_RELEASE="${PKG_RELEASE:-1}"
|
||||
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -47,11 +43,6 @@ while [[ $# -gt 0 ]]; do
|
||||
BUILD_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pkg-version)
|
||||
need_arg "$@"
|
||||
PKG_VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pkg-release)
|
||||
need_arg "$@"
|
||||
PKG_RELEASE="$2"
|
||||
@@ -75,19 +66,61 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)"
|
||||
BUILD_DIR="$(cd "${BUILD_DIR:-${PWD}/build}" && pwd)"
|
||||
PKG_RELEASE="${PKG_RELEASE:-1}"
|
||||
|
||||
if [[ -z "${PKG_VERSION}" ]]; then
|
||||
PKG_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR==1 {print $3; exit}')"
|
||||
BUILD_DIR="${BUILD_DIR:-${PWD}/build}"
|
||||
if [[ ! -d "${BUILD_DIR}" ]]; then
|
||||
echo "build_pkg.sh: build directory not found: ${BUILD_DIR}" >&2
|
||||
echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
|
||||
exit 1
|
||||
fi
|
||||
BUILD_DIR="$(cd "${BUILD_DIR}" && pwd)"
|
||||
|
||||
if [[ -z "${PKG_VERSION}" ]]; then
|
||||
echo "PKG_VERSION is empty (not provided and could not be derived)." >&2
|
||||
xrpld_binary="${BUILD_DIR}/xrpld"
|
||||
if [[ ! -x "${xrpld_binary}" ]]; then
|
||||
echo "build_pkg.sh: expected executable xrpld binary at ${xrpld_binary}." >&2
|
||||
echo "Build xrpld before packaging, or set BUILD_DIR to the directory containing xrpld." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${PKG_VERSION}"
|
||||
xrpld_version="$("${xrpld_binary}" --version | awk 'NR == 1 { print $3 }')"
|
||||
|
||||
if [[ -z "${xrpld_version}" ]]; then
|
||||
echo "build_pkg.sh: unable to derive xrpld version from ${xrpld_binary} --version." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The version as the package formats consume it: identical to xrpld_version
|
||||
# except a pre-release uses '~' (3.2.0-b1 -> 3.2.0~b1), which also sorts before
|
||||
# the final 3.2.0; a no-op for a final release. Lowercase = derived internally,
|
||||
# not an input (cf. pkg_type).
|
||||
pkg_version="${xrpld_version}"
|
||||
pre_release=""
|
||||
if [[ "${xrpld_version}" == *-* ]]; then
|
||||
pre_release="${xrpld_version#*-}"
|
||||
pkg_version="${xrpld_version%%-*}~${pre_release}"
|
||||
fi
|
||||
|
||||
# BuildInfo already SemVer-validates the binary's version. Packaging adds one
|
||||
# narrower constraint: after pre-release normalization, the package version must
|
||||
# not contain '-' because RPM forbids it in Version and Debian uses it as the
|
||||
# upstream/revision separator.
|
||||
if [[ "${pkg_version}" == *-* ]]; then
|
||||
echo "build_pkg.sh: unsupported xrpld version '${xrpld_version}'." >&2
|
||||
echo "Package version '${pkg_version}' cannot contain '-'." >&2
|
||||
echo "Use a single-token pre-release like 3.2.0-b1 or 3.2.0-rc2." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${pre_release}" && "${xrpld_version}" == *+* ]]; then
|
||||
echo "build_pkg.sh: unsupported xrpld version '${xrpld_version}'." >&2
|
||||
echo "Build metadata is only supported on bN/rcN pre-releases." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${pre_release}" && ! "${pre_release}" =~ ^(b0|b[1-9][0-9]*|rc[0-9]+)(\+.*)?$ ]]; then
|
||||
echo "build_pkg.sh: unsupported xrpld pre-release '${pre_release}'." >&2
|
||||
echo "Use bN or rcN, e.g. 3.2.0-b1 or 3.2.0-rc2." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
pkg_type=deb
|
||||
@@ -99,32 +132,15 @@ else
|
||||
fi
|
||||
|
||||
if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then
|
||||
if git -C "$SRC_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
SOURCE_DATE_EPOCH="$(git -C "$SRC_DIR" log -1 --format=%ct)"
|
||||
if git -C "${SRC_DIR}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
SOURCE_DATE_EPOCH="$(git -C "${SRC_DIR}" log -1 --format=%ct)"
|
||||
else
|
||||
SOURCE_DATE_EPOCH="$(date +%s)"
|
||||
fi
|
||||
fi
|
||||
|
||||
export SOURCE_DATE_EPOCH
|
||||
CHANGELOG_DATE="$(date -u -R -d "@$SOURCE_DATE_EPOCH")"
|
||||
|
||||
# Split VERSION at the first '-' into base and optional pre-release suffix.
|
||||
# Examples: "3.2.0" -> ("3.2.0", ""); "3.2.0-b1" -> ("3.2.0", "b1").
|
||||
VER_BASE="${VERSION%%-*}"
|
||||
VER_SUFFIX="${VERSION#*-}"
|
||||
[[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX=""
|
||||
|
||||
# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). Neither an
|
||||
# RPM Version nor a Debian upstream version may contain '-' (it's the NVR /
|
||||
# version-revision separator), and the convention here is single-token
|
||||
# suffixes like b1 or rc2. Fail early with a clear message rather than letting
|
||||
# the package tooling blow up or silently mangle dashes.
|
||||
if [[ "${VER_SUFFIX}" == *-* ]]; then
|
||||
echo "build_pkg.sh: multi-segment pre-release in VERSION='${VERSION}' (suffix '${VER_SUFFIX}')." >&2
|
||||
echo "Use single-token suffixes like 3.2.0-b1 or 3.2.0-rc2." >&2
|
||||
exit 1
|
||||
fi
|
||||
CHANGELOG_DATE="$(date -u -R -d "@${SOURCE_DATE_EPOCH}")"
|
||||
|
||||
SHARED="${SRC_DIR}/package/shared"
|
||||
DEBIAN_DIR="${SRC_DIR}/package/debian"
|
||||
@@ -135,7 +151,6 @@ stage_common() {
|
||||
mkdir -p "${dest}"
|
||||
|
||||
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
|
||||
cp "${BUILD_DIR}/validator-keys" "${dest}/validator-keys"
|
||||
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
|
||||
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
|
||||
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"
|
||||
@@ -145,7 +160,6 @@ stage_common() {
|
||||
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
|
||||
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
|
||||
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
|
||||
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
|
||||
}
|
||||
|
||||
build_rpm() {
|
||||
@@ -156,18 +170,11 @@ build_rpm() {
|
||||
cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec"
|
||||
stage_common "${topdir}/SOURCES"
|
||||
|
||||
# Pre-releases use the modern rpm '~' convention (rpm >= 4.10): the suffix
|
||||
# goes in Version (e.g. 3.2.0~b1), which rpmvercmp sorts *before* the final
|
||||
# 3.2.0 — identical semantics to Debian's '~'. Release is just the package
|
||||
# release number. This replaces the older "0.<release>.<suffix>" Release
|
||||
# hack and keeps the RPM and DEB version strings symmetric.
|
||||
local rpm_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}"
|
||||
|
||||
set -x
|
||||
rpmbuild -bb \
|
||||
--define "_topdir ${topdir}" \
|
||||
--define "xrpld_version ${rpm_version}" \
|
||||
--define "xrpld_release ${PKG_RELEASE}" \
|
||||
--define "pkg_version ${pkg_version}" \
|
||||
--define "pkg_release ${PKG_RELEASE}" \
|
||||
"${topdir}/SPECS/xrpld.spec"
|
||||
}
|
||||
|
||||
@@ -184,23 +191,26 @@ build_deb() {
|
||||
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
|
||||
cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
|
||||
|
||||
# Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0.
|
||||
local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}"
|
||||
|
||||
# Derive release channel from the version suffix:
|
||||
# (none) -> stable (tagged release)
|
||||
# b0 -> develop (develop-branch build)
|
||||
# b<N>, rc<N> -> unstable (pre-release)
|
||||
local deb_distribution
|
||||
case "${VER_SUFFIX}" in
|
||||
"") deb_distribution="stable" ;;
|
||||
b0) deb_distribution="develop" ;;
|
||||
*) deb_distribution="unstable" ;;
|
||||
esac
|
||||
# Choose the Debian repository component for this package.
|
||||
# 3.2.0 -> stable, *-b0[+metadata] -> develop,
|
||||
# bN/rcN pre-releases -> unstable.
|
||||
local deb_component
|
||||
if [[ -z "${pre_release}" ]]; then
|
||||
deb_component="stable"
|
||||
elif [[ "${pre_release}" =~ ^b0(\+.*)?$ ]]; then
|
||||
deb_component="develop"
|
||||
elif [[ "${pre_release}" =~ ^(b[1-9][0-9]*|rc[0-9]+)(\+.*)?$ ]]; then
|
||||
deb_component="unstable"
|
||||
else
|
||||
echo "build_pkg.sh: unsupported xrpld pre-release '${pre_release}'." >&2
|
||||
echo "Use bN or rcN, e.g. 3.2.0-b1 or 3.2.0-rc2." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Debian version is <upstream>[~<pre>]-<pkg release>.
|
||||
cat >"${staging}/debian/changelog" <<EOF
|
||||
xrpld (${deb_full_version}) ${deb_distribution}; urgency=medium
|
||||
* Release ${VERSION}.
|
||||
xrpld (${pkg_version}-${PKG_RELEASE}) ${deb_component}; urgency=medium
|
||||
* Release ${xrpld_version}.
|
||||
|
||||
-- XRPL Foundation <contact@xrplf.org> ${CHANGELOG_DATE}
|
||||
EOF
|
||||
|
||||
@@ -18,8 +18,6 @@ Depends:
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: XRP Ledger daemon
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
This package also includes the validator-keys tool for
|
||||
validator key management.
|
||||
Reference implementation of the XRP Ledger protocol.
|
||||
Participates in the peer-to-peer network, processes transactions,
|
||||
and maintains a local ledger copy.
|
||||
|
||||
@@ -18,7 +18,6 @@ override_dh_installsysusers:
|
||||
|
||||
override_dh_install:
|
||||
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
|
||||
install -D -m 0755 validator-keys debian/xrpld/usr/bin/validator-keys
|
||||
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
|
||||
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
%if "%{?pkg_version}" == ""
|
||||
%{error:pkg_version must be defined}
|
||||
%endif
|
||||
|
||||
%if "%{?pkg_release}" == ""
|
||||
%{error:pkg_release must be defined}
|
||||
%endif
|
||||
|
||||
Name: xrpld
|
||||
Version: %{xrpld_version}
|
||||
Release: %{xrpld_release}%{?dist}
|
||||
Version: %{pkg_version}
|
||||
Release: %{pkg_release}%{?dist}
|
||||
Summary: XRP Ledger daemon
|
||||
|
||||
License: ISC
|
||||
@@ -11,6 +19,9 @@ BuildRequires: systemd-rpm-macros
|
||||
|
||||
%undefine _debugsource_packages
|
||||
%debug_package
|
||||
# Intentionally trade larger RPM artifacts for faster package validation.
|
||||
%global _binary_payload w.ufdio
|
||||
%global _find_debuginfo_dwz_opts %{nil}
|
||||
|
||||
%build_mtime_policy clamp_to_source_date_epoch
|
||||
|
||||
@@ -21,8 +32,6 @@ BuildRequires: systemd-rpm-macros
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
This package also includes the validator-keys tool for validator key
|
||||
management.
|
||||
|
||||
%prep
|
||||
:
|
||||
@@ -32,7 +41,6 @@ management.
|
||||
|
||||
%install
|
||||
install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{name}
|
||||
install -Dm0755 %{_sourcedir}/validator-keys %{buildroot}%{_bindir}/validator-keys
|
||||
install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{name}/xrpld.cfg
|
||||
install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{name}/validators.txt
|
||||
|
||||
@@ -40,7 +48,10 @@ install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{
|
||||
install -Dm0644 %{_sourcedir}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service
|
||||
install -Dm0644 %{_sourcedir}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf
|
||||
install -Dm0644 %{_sourcedir}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf
|
||||
install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-xrpld.preset
|
||||
install -Dm0644 /dev/null %{buildroot}%{_presetdir}/50-xrpld.preset
|
||||
cat >%{buildroot}%{_presetdir}/50-xrpld.preset <<'EOF'
|
||||
enable xrpld.service
|
||||
EOF
|
||||
|
||||
# Logrotate config
|
||||
install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
|
||||
@@ -65,7 +76,7 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%systemd_preun xrpld.service
|
||||
|
||||
%postun
|
||||
%systemd_postun_with_restart xrpld.service
|
||||
%systemd_postun xrpld.service
|
||||
|
||||
%files
|
||||
%license %{_docdir}/%{name}/LICENSE.md
|
||||
@@ -74,7 +85,6 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%dir %{_sysconfdir}/%{name}
|
||||
|
||||
%{_bindir}/%{name}
|
||||
%{_bindir}/validator-keys
|
||||
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/xrpld.cfg
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# /usr/lib/systemd/system-preset/50-xrpld.preset
|
||||
enable xrpld.service
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/pay.h>
|
||||
@@ -100,6 +101,12 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
return xrpl::test::jtx::testableAmendments() | fixCleanup3_1_3 | fixCleanup3_2_0;
|
||||
}
|
||||
|
||||
test::jtx::Env
|
||||
makeEnv(FeatureBitset features)
|
||||
{
|
||||
return {*this, test::jtx::envconfig(), features, nullptr, beast::Severity::Disabled};
|
||||
}
|
||||
|
||||
/** Run a specific test case to put the ledger into a state that will be
|
||||
* detected by an invariant. Simulates the actions of a transaction that
|
||||
* would violate an invariant.
|
||||
@@ -128,7 +135,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
TxAccount setTxAccount = TxAccount::None)
|
||||
{
|
||||
doInvariantCheck(
|
||||
test::jtx::Env(*this, defaultAmendments()),
|
||||
makeEnv(defaultAmendments()),
|
||||
expectLogs,
|
||||
precheck,
|
||||
fee,
|
||||
@@ -1405,7 +1412,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
testcase << "PermissionedDomain" + std::string(fixEnabled ? " fix" : "");
|
||||
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain with no rules."}},
|
||||
[](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
return createPermissionedDomain(ac, a1, a2, 0).get();
|
||||
@@ -1418,7 +1425,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
static constexpr auto kTooBig = kMaxPermissionedDomainCredentialsArraySize + 1;
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain bad credentials size " + std::to_string(kTooBig)}},
|
||||
[](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
return !!createPermissionedDomain(ac, a1, a2, kTooBig);
|
||||
@@ -1429,7 +1436,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
testcase << "PermissionedDomain 3";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain credentials aren't sorted"}},
|
||||
[](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
auto slePd = createPermissionedDomain(ac, a1, a2, 0);
|
||||
@@ -1453,7 +1460,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
testcase << "PermissionedDomain 4";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain credentials aren't unique"}},
|
||||
[](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
auto slePd = createPermissionedDomain(ac, a1, a2, 0);
|
||||
@@ -1476,7 +1483,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
testcase << "PermissionedDomain Set 1";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain with no rules."}},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
// create PD
|
||||
@@ -1497,7 +1504,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
testcase << "PermissionedDomain Set 2";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain bad credentials size " + std::to_string(kTooBig)}},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
// create PD
|
||||
@@ -1528,7 +1535,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
testcase << "PermissionedDomain Set 3";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain credentials aren't sorted"}},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
// create PD
|
||||
@@ -1558,7 +1565,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
testcase << "PermissionedDomain Set 4";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"permissioned domain credentials aren't unique"}},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
// create PD
|
||||
@@ -1599,7 +1606,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase << "PermissionedDomain set 2 domains ";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
fixEnabled ? badMoreThan1 : emptyV,
|
||||
[](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
createPermissionedDomain(ac, a1, a2);
|
||||
@@ -1645,7 +1652,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase << "PermissionedDomain set 0 domains ";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
fixEnabled ? badNoDomains : emptyV,
|
||||
[](Account const&, Account const&, ApplyContext&) { return true; },
|
||||
XRPAmount{},
|
||||
@@ -1668,7 +1675,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
env1.close();
|
||||
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
a1,
|
||||
a2,
|
||||
fixEnabled ? badNoDomains : emptyV,
|
||||
@@ -1709,7 +1716,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase << "PermissionedDomain del, create domain ";
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
fixEnabled ? badNotDeleted : emptyV,
|
||||
[](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
createPermissionedDomain(ac, a1, a2);
|
||||
@@ -1889,7 +1896,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
testcase << "PermissionedDEX" + std::string(fixEnabled ? " fix" : "");
|
||||
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"domain doesn't exist"}},
|
||||
[](Account const& a1, Account const&, ApplyContext& ac) {
|
||||
Keylet const offerKey = keylet::offer(a1.id(), 10);
|
||||
@@ -1916,7 +1923,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
|
||||
// missing domain ID in offer object
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
{{"hybrid offer is malformed"}},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
Keylet const offerKey = keylet::offer(a2.id(), 10);
|
||||
@@ -4230,7 +4237,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
};
|
||||
|
||||
doInvariantCheck(
|
||||
Env{*this, defaultAmendments() - fixCleanup3_2_0},
|
||||
makeEnv(defaultAmendments() - fixCleanup3_2_0),
|
||||
{},
|
||||
[](Account const&, Account const&, ApplyContext&) { return true; },
|
||||
XRPAmount{},
|
||||
@@ -4749,7 +4756,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
// sfHighLimit issue, not the keylet currency).
|
||||
testcase << "overwrite: NoXRPTrustLines" + std::string(fixEnabled ? " fix" : "");
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
fixEnabled ? std::vector<std::string>{{"an XRP trust line was created"}}
|
||||
: std::vector<std::string>{},
|
||||
[&insertOrderedTrustLinePair](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
@@ -4777,7 +4784,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
// Regression: bad deep-freeze trust line followed by a valid one.
|
||||
testcase << "overwrite: NoDeepFreeze" + std::string(fixEnabled ? " fix" : "");
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
fixEnabled ? std::vector<std::string>{{"a trust line with deep freeze flag without "
|
||||
"normal freeze was created"}}
|
||||
: std::vector<std::string>{},
|
||||
@@ -4811,7 +4818,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
// still fires ("a MPT issuance was created").
|
||||
testcase << "overwrite: NoZeroEscrow MPT" + std::string(fixEnabled ? " fix" : "");
|
||||
doInvariantCheck(
|
||||
Env(*this, features),
|
||||
makeEnv(features),
|
||||
fixEnabled ? std::vector<std::string>{{"escrow specifies invalid amount"}}
|
||||
: std::vector<std::string>{{"a MPT issuance was created"}},
|
||||
[](Account const& a1, Account const&, ApplyContext& ac) {
|
||||
|
||||
2
tests/conan/.gitignore
vendored
2
tests/conan/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# Conan test_package build output (cmake_layout)
|
||||
/build/
|
||||
@@ -1,21 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
set(name validator-keys-conan-test)
|
||||
set(name example)
|
||||
set(version 0.1.0)
|
||||
|
||||
project(${name} LANGUAGES CXX)
|
||||
project(${name} VERSION ${version} LANGUAGES CXX)
|
||||
|
||||
find_package(xrpl CONFIG REQUIRED)
|
||||
|
||||
# Build the in-repo validator-keys-tool source instead of fetching it from
|
||||
# GitHub. Keep it out of the default build; the test recipe builds the target
|
||||
# explicitly.
|
||||
add_subdirectory(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../validator-keys-tool
|
||||
${CMAKE_BINARY_DIR}/validator-keys-tool
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
validator-keys
|
||||
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
)
|
||||
add_executable(example)
|
||||
target_sources(example PRIVATE src/example.cpp)
|
||||
target_link_libraries(example PRIVATE xrpl::libxrpl)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from conan.tools.build import can_run
|
||||
from conan.tools.cmake import CMake, cmake_layout
|
||||
@@ -6,15 +6,15 @@ from conan.tools.cmake import CMake, cmake_layout
|
||||
from conan import ConanFile
|
||||
|
||||
|
||||
class ValidatorKeysConanTest(ConanFile):
|
||||
name = "validator-keys-conan-test"
|
||||
class Example(ConanFile):
|
||||
name = "example"
|
||||
license = "ISC"
|
||||
author = (
|
||||
"John Freeman <jfreeman08@gmail.com>, Michael Legleux <mlegleux@ripple.com>"
|
||||
)
|
||||
author = "John Freeman <jfreeman08@gmail.com>, Michael Legleux <mlegleux@ripple.com"
|
||||
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
|
||||
requires = ["xrpl/head"]
|
||||
|
||||
default_options = {
|
||||
"xrpl/*:xrpld": False,
|
||||
}
|
||||
@@ -25,20 +25,19 @@ class ValidatorKeysConanTest(ConanFile):
|
||||
if self.version is None:
|
||||
self.version = "0.1.0"
|
||||
|
||||
def requirements(self):
|
||||
# Test whatever reference is being created/tested rather than a
|
||||
# hardcoded version, so this test_package works for any xrpl version.
|
||||
self.requires(self.tested_reference_str)
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build(target="validator-keys")
|
||||
cmake.build()
|
||||
|
||||
def package(self):
|
||||
cmake = CMake(self)
|
||||
cmake.install()
|
||||
|
||||
def test(self):
|
||||
if can_run(self):
|
||||
cmd = os.path.join(self.cpp.build.bindir, "validator-keys")
|
||||
self.run(f'"{cmd}" --unittest', env="conanrun")
|
||||
cmd_path = Path(self.build_folder) / self.cpp.build.bindir / "example"
|
||||
self.run(cmd_path, env="conanrun")
|
||||
|
||||
10
tests/conan/src/example.cpp
Normal file
10
tests/conan/src/example.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <xrpl/protocol/BuildInfo.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int
|
||||
main(int argc, char const** argv)
|
||||
{
|
||||
std::printf("%s\n", xrpl::BuildInfo::getVersionString().c_str());
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# This feature requires Git >= 2.24
|
||||
# To use it by default in git blame:
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
8ae260cb466d4cd0d4db378e5ce0acb8e4432f7c
|
||||
@@ -1,34 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
project(validator-keys-tool)
|
||||
|
||||
#[===========================================[
|
||||
This project is built as part of the xrpld
|
||||
repository's Conan test package. The parent
|
||||
project calls find_package(xrpl) and adds this
|
||||
directory, providing the xrpl::libxrpl target.
|
||||
#]===========================================]
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
if(NOT TARGET xrpl::libxrpl)
|
||||
find_package(xrpl CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
include(KeysSanity)
|
||||
include(KeysCov)
|
||||
include(KeysInterface)
|
||||
|
||||
add_executable(
|
||||
validator-keys
|
||||
src/ValidatorKeys.cpp
|
||||
src/ValidatorKeysTool.cpp
|
||||
# UNIT TESTS:
|
||||
src/test/ValidatorKeys_test.cpp
|
||||
src/test/ValidatorKeysTool_test.cpp
|
||||
)
|
||||
target_include_directories(validator-keys PRIVATE src)
|
||||
target_link_libraries(validator-keys xrpl::libxrpl Keys::opts)
|
||||
|
||||
include(CTest)
|
||||
if(BUILD_TESTING)
|
||||
add_test(test validator-keys --unittest)
|
||||
endif()
|
||||
@@ -1,140 +0,0 @@
|
||||
# validator-keys-tool
|
||||
|
||||
Xrpld validator key generation tool
|
||||
|
||||
## Build
|
||||
|
||||
Use the same build process as xrpld from the repository root, enabling the
|
||||
optional `validator_keys` target and building `validator-keys`:
|
||||
|
||||
```
|
||||
mkdir .build
|
||||
cd .build
|
||||
conan install .. --output-folder . --build=missing --settings=build_type=Release
|
||||
cmake -DCMAKE_POLICY_DEFAULT_CMP0091=NEW \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=conan_toolchain.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-Dvalidator_keys=ON \
|
||||
..
|
||||
cmake --build . --target validator-keys
|
||||
./validator-keys --unittest
|
||||
```
|
||||
|
||||
The Conan test package in `tests/conan` builds the same target against the
|
||||
exported `xrpl` package.
|
||||
|
||||
## Usage Guide
|
||||
|
||||
This guide explains how to set up a validator so its public key does not have to
|
||||
change if the xrpld config and/or server are compromised.
|
||||
|
||||
A validator uses a public/private key pair. The validator is identified by the
|
||||
public key. The private key should be tightly controlled. It is used to:
|
||||
|
||||
- sign tokens authorizing an xrpld server to run as the validator identified by
|
||||
this public key.
|
||||
- sign revocations indicating that the private key has been compromised and the
|
||||
validator public key should no longer be trusted.
|
||||
|
||||
Each new token invalidates all previous tokens for the validator public key. The
|
||||
current token needs to be present in the xrpld config file.
|
||||
|
||||
Servers that trust the validator will adapt automatically when the token changes.
|
||||
|
||||
### Validator Keys
|
||||
|
||||
When first setting up a validator, use the `validator-keys` tool to generate its
|
||||
key pair:
|
||||
|
||||
```
|
||||
$ validator-keys create_keys
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
```
|
||||
Validator keys stored in /home/ubuntu/.ripple/validator-keys.json
|
||||
```
|
||||
|
||||
Keep the key file in a secure but recoverable location, such as an encrypted USB
|
||||
flash drive. Do not modify its contents.
|
||||
|
||||
### Validator Token
|
||||
|
||||
After first creating the [validator keys](#validator-keys) or if the previous
|
||||
token has been compromised, use the `validator-keys` tool to create a new
|
||||
validator token:
|
||||
|
||||
```
|
||||
$ validator-keys create_token
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
```
|
||||
Update xrpld.cfg file with these values:
|
||||
|
||||
# validator public key: nHUtNnLVx7odrz5dnfb2xpIgbEeJPbzJWfdicSkGyVw1eE5GpjQr
|
||||
|
||||
[validator_token]
|
||||
eyJ2YWxpZGF0aW9uX3NlY3J|dF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3NDdiNT
|
||||
QzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYT|kYWY2IiwibWFuaWZl
|
||||
c3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncxL3ZDeE
|
||||
hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4U0tG
|
||||
bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUzZQU2
|
||||
hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZeXd1
|
||||
NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZWRGdj
|
||||
VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0ifQ==
|
||||
```
|
||||
|
||||
For a new validator, add the `[validator_token]` value to the xrpld config file.
|
||||
For a pre-existing validator, replace the old `[validator_token]` value with the
|
||||
newly generated one. A valid config file may only contain one `[validator_token]`
|
||||
value. After the config is updated, restart xrpld.
|
||||
|
||||
There is a hard limit of 4,294,967,293 tokens that can be generated for a given
|
||||
validator key pair.
|
||||
|
||||
### Key Revocation
|
||||
|
||||
If a validator private key is compromised, the key must be revoked permanently.
|
||||
To revoke the validator key, use the `validator-keys` tool to generate a
|
||||
revocation, which indicates to other servers that the key is no longer valid:
|
||||
|
||||
```
|
||||
$ validator-keys revoke_keys
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
```
|
||||
WARNING: This will revoke your validator keys!
|
||||
|
||||
Update xrpld.cfg file with these values and restart xrpld:
|
||||
|
||||
# validator public key: nHUtNnLVx7odrz5dnfb2xpIgbEeJPbzJWfdicSkGyVw1eE5GpjQr
|
||||
|
||||
[validator_key_revocation]
|
||||
JP////9xIe0hvssbqmgzFH4/NDp1z|3ShkmCtFXuC5A0IUocppHopnASQN2MuMD1Puoyjvnr
|
||||
jQ2KJSO/2tsjRhjO6q0QQHppslQsKNSXWxjGQNIEa6nPisBOKlDDcJVZAMP4QcIyNCadzgM=
|
||||
```
|
||||
|
||||
Add the `[validator_key_revocation]` value to this validator's config and
|
||||
restart xrpld. Rename the old key file and generate new
|
||||
[validator keys](#validator-keys) and a corresponding
|
||||
[validator token](#validator-token).
|
||||
|
||||
### Signing
|
||||
|
||||
The `validator-keys` tool can be used to sign arbitrary data with the validator
|
||||
key.
|
||||
|
||||
```
|
||||
$ validator-keys sign "your data to sign"
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
```
|
||||
B91B73536235BBA028D344B81DBCBECF19C1E0034AC21FB51C2351A138C9871162F3193D7C41A49FB7AABBC32BC2B116B1D5701807BE462D8800B5AEA4F0550D
|
||||
```
|
||||
@@ -1,133 +0,0 @@
|
||||
#[===================================================================[
|
||||
coverage report target
|
||||
#]===================================================================]
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
if(NOT coverage)
|
||||
message(STATUS "Coverage disabled")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT (is_clang OR is_gcc))
|
||||
message(STATUS "Coverage: neither clang nor gcc")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(is_clang)
|
||||
if(APPLE)
|
||||
execute_process(
|
||||
COMMAND xcrun -f llvm-profdata
|
||||
OUTPUT_VARIABLE LLVM_PROFDATA
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
else()
|
||||
find_program(LLVM_PROFDATA llvm-profdata)
|
||||
endif()
|
||||
if(NOT LLVM_PROFDATA)
|
||||
message(
|
||||
WARNING
|
||||
"unable to find llvm-profdata - skipping coverage_report target"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
execute_process(
|
||||
COMMAND xcrun -f llvm-cov
|
||||
OUTPUT_VARIABLE LLVM_COV
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
else()
|
||||
find_program(LLVM_COV llvm-cov)
|
||||
endif()
|
||||
if(NOT LLVM_COV)
|
||||
message(
|
||||
WARNING
|
||||
"unable to find llvm-cov - skipping coverage_report target"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(extract_pattern "")
|
||||
if(coverage_core_only)
|
||||
set(extract_pattern "${CMAKE_CURRENT_SOURCE_DIR}/src/")
|
||||
endif()
|
||||
|
||||
if(LLVM_COV AND LLVM_PROFDATA)
|
||||
add_custom_target(
|
||||
coverage_report
|
||||
USES_TERMINAL
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E echo
|
||||
"Generating coverage - results will be in ${CMAKE_BINARY_DIR}/coverage/index.html."
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Running validator-keys tests."
|
||||
COMMAND
|
||||
validator-keys
|
||||
--unittest$<$<BOOL:${coverage_test}>:=${coverage_test}>
|
||||
COMMAND
|
||||
${LLVM_PROFDATA} merge -sparse default.profraw -o rip.profdata
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Summary of coverage:"
|
||||
COMMAND
|
||||
${LLVM_COV} report -instr-profile=rip.profdata
|
||||
$<TARGET_FILE:validator-keys> ${extract_pattern}
|
||||
# generate html report
|
||||
COMMAND
|
||||
${LLVM_COV} show -format=html
|
||||
-output-dir=${CMAKE_BINARY_DIR}/coverage
|
||||
-instr-profile=rip.profdata $<TARGET_FILE:validator-keys>
|
||||
${extract_pattern}
|
||||
BYPRODUCTS coverage/index.html
|
||||
)
|
||||
endif()
|
||||
elseif(is_gcc)
|
||||
find_program(LCOV lcov)
|
||||
if(NOT LCOV)
|
||||
message(WARNING "unable to find lcov - skipping coverage_report target")
|
||||
endif()
|
||||
|
||||
find_program(GENHTML genhtml)
|
||||
if(NOT GENHTML)
|
||||
message(
|
||||
WARNING
|
||||
"unable to find genhtml - skipping coverage_report target"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(extract_pattern "*")
|
||||
if(coverage_core_only)
|
||||
set(extract_pattern "*/src/*")
|
||||
endif()
|
||||
|
||||
if(LCOV AND GENHTML)
|
||||
add_custom_target(
|
||||
coverage_report
|
||||
USES_TERMINAL
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E echo
|
||||
"Generating coverage- results will be in ${CMAKE_BINARY_DIR}/coverage/index.html."
|
||||
# create baseline info file
|
||||
COMMAND
|
||||
${LCOV} --no-external -d "${CMAKE_CURRENT_SOURCE_DIR}" -c -d .
|
||||
-i -o baseline.info | grep -v "ignoring data for external file"
|
||||
# run tests
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E echo
|
||||
"Running validator-keys tests for coverage report."
|
||||
COMMAND
|
||||
validator-keys
|
||||
--unittest$<$<BOOL:${coverage_test}>:=${coverage_test}>
|
||||
# Create test coverage data file
|
||||
COMMAND
|
||||
${LCOV} --no-external -d "${CMAKE_CURRENT_SOURCE_DIR}" -c -d .
|
||||
-o tests.info | grep -v "ignoring data for external file"
|
||||
# Combine baseline and test coverage data
|
||||
COMMAND ${LCOV} -a baseline.info -a tests.info -o lcov-all.info
|
||||
# extract our files
|
||||
COMMAND ${LCOV} -e lcov-all.info "${extract_pattern}" -o lcov.info
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Summary of coverage:"
|
||||
COMMAND ${LCOV} --summary lcov.info
|
||||
# generate HTML report
|
||||
COMMAND ${GENHTML} -o ${CMAKE_BINARY_DIR}/coverage lcov.info
|
||||
BYPRODUCTS coverage/index.html
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,87 +0,0 @@
|
||||
#[===================================================================[
|
||||
rippled compile options/settings via an interface library
|
||||
#]===================================================================]
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
add_library(keys_opts INTERFACE)
|
||||
add_library(Keys::opts ALIAS keys_opts)
|
||||
target_compile_definitions(
|
||||
keys_opts
|
||||
INTERFACE
|
||||
BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
|
||||
$<$<BOOL:${boost_show_deprecated}>:
|
||||
BOOST_ASIO_NO_DEPRECATED
|
||||
BOOST_FILESYSTEM_NO_DEPRECATED
|
||||
>
|
||||
$<$<NOT:$<BOOL:${boost_show_deprecated}>>:
|
||||
BOOST_COROUTINES_NO_DEPRECATION_WARNING
|
||||
BOOST_BEAST_ALLOW_DEPRECATED
|
||||
BOOST_FILESYSTEM_DEPRECATED
|
||||
>
|
||||
$<$<BOOL:${beast_hashers}>:
|
||||
USE_BEAST_HASHER
|
||||
>
|
||||
$<$<BOOL:${beast_no_unit_test_inline}>:BEAST_NO_UNIT_TEST_INLINE=1>
|
||||
$<$<BOOL:${beast_disable_autolink}>:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1>
|
||||
$<$<BOOL:${single_io_service_thread}>:XRPL_SINGLE_IO_SERVICE_THREAD=1>
|
||||
)
|
||||
target_compile_options(
|
||||
keys_opts
|
||||
INTERFACE
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<COMPILE_LANGUAGE:CXX>>:-Wsuggest-override>
|
||||
$<$<BOOL:${perf}>:-fno-omit-frame-pointer>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-fprofile-arcs
|
||||
-ftest-coverage>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-fprofile-instr-generate
|
||||
-fcoverage-mapping>
|
||||
$<$<BOOL:${profile}>:-pg>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
keys_opts
|
||||
INTERFACE
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-fprofile-arcs
|
||||
-ftest-coverage>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-fprofile-instr-generate
|
||||
-fcoverage-mapping>
|
||||
$<$<BOOL:${profile}>:-pg>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>
|
||||
)
|
||||
|
||||
if(jemalloc)
|
||||
if(static)
|
||||
set(JEMALLOC_USE_STATIC ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
find_package(jemalloc REQUIRED)
|
||||
target_compile_definitions(keys_opts INTERFACE PROFILE_JEMALLOC)
|
||||
target_include_directories(
|
||||
keys_opts
|
||||
SYSTEM
|
||||
INTERFACE ${JEMALLOC_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(keys_opts INTERFACE ${JEMALLOC_LIBRARIES})
|
||||
get_filename_component(JEMALLOC_LIB_PATH ${JEMALLOC_LIBRARIES} DIRECTORY)
|
||||
## TODO see if we can use the BUILD_RPATH target property (is it transitive?)
|
||||
set(CMAKE_BUILD_RPATH ${CMAKE_BUILD_RPATH} ${JEMALLOC_LIB_PATH})
|
||||
endif()
|
||||
if(san)
|
||||
target_compile_options(
|
||||
keys_opts
|
||||
INTERFACE
|
||||
# sanitizers recommend minimum of -O1 for reasonable performance
|
||||
$<$<CONFIG:Debug>:-O1>
|
||||
${SAN_FLAG}
|
||||
-fno-omit-frame-pointer
|
||||
)
|
||||
target_compile_definitions(
|
||||
keys_opts
|
||||
INTERFACE
|
||||
$<$<STREQUAL:${san},address>:SANITIZER=ASAN>
|
||||
$<$<STREQUAL:${san},thread>:SANITIZER=TSAN>
|
||||
$<$<STREQUAL:${san},memory>:SANITIZER=MSAN>
|
||||
$<$<STREQUAL:${san},undefined>:SANITIZER=UBSAN>
|
||||
)
|
||||
target_link_libraries(keys_opts INTERFACE ${SAN_FLAG} ${SAN_LIB})
|
||||
endif()
|
||||
@@ -1,107 +0,0 @@
|
||||
#[===================================================================[
|
||||
convenience variables and sanity checks
|
||||
#]===================================================================]
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
get_directory_property(has_parent PARENT_DIRECTORY)
|
||||
if(has_parent)
|
||||
set(is_root_project OFF)
|
||||
else()
|
||||
set(is_root_project ON)
|
||||
endif()
|
||||
|
||||
if(NOT ep_procs)
|
||||
include(ProcessorCount)
|
||||
ProcessorCount(ep_procs)
|
||||
if(ep_procs GREATER 1)
|
||||
# never use more than half of cores for EP builds
|
||||
math(EXPR ep_procs "${ep_procs} / 2")
|
||||
message(STATUS "Using ${ep_procs} cores for ExternalProject builds.")
|
||||
endif()
|
||||
endif()
|
||||
get_property(is_multiconfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(is_multiconfig STREQUAL "NOTFOUND")
|
||||
if(
|
||||
${CMAKE_GENERATOR} STREQUAL "Xcode"
|
||||
OR ${CMAKE_GENERATOR} MATCHES "^Visual Studio"
|
||||
)
|
||||
set(is_multiconfig TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(is_root_project)
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
if(is_root_project AND NOT is_multiconfig)
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
message(STATUS "Build type not specified - defaulting to Release")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "build type" FORCE)
|
||||
elseif(
|
||||
NOT (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL Release)
|
||||
)
|
||||
# for simplicity, these are the only two config types we care about. Limiting
|
||||
# the build types simplifies dealing with external project builds especially
|
||||
message(
|
||||
FATAL_ERROR
|
||||
" *** Only Debug or Release build types are currently supported ***"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_ID}" MATCHES ".*Clang") # both Clang and AppleClang
|
||||
set(is_clang TRUE)
|
||||
if(
|
||||
"${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang"
|
||||
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0
|
||||
)
|
||||
message(FATAL_ERROR "This project requires clang 7 or later")
|
||||
endif()
|
||||
# TODO min AppleClang version check ?
|
||||
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
set(is_gcc TRUE)
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
|
||||
message(FATAL_ERROR "This project requires GCC 7 or later")
|
||||
endif()
|
||||
endif()
|
||||
if(CMAKE_GENERATOR STREQUAL "Xcode")
|
||||
set(is_xcode TRUE)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(is_linux TRUE)
|
||||
else()
|
||||
set(is_linux FALSE)
|
||||
endif()
|
||||
|
||||
if("$ENV{CI}" STREQUAL "true" OR "$ENV{CONTINUOUS_INTEGRATION}" STREQUAL "true")
|
||||
set(is_ci TRUE)
|
||||
else()
|
||||
set(is_ci FALSE)
|
||||
endif()
|
||||
|
||||
# check for in-source build and fail
|
||||
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Builds (in-source) are not allowed in "
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}. Please remove CMakeCache.txt and the CMakeFiles "
|
||||
"directory from ${CMAKE_CURRENT_SOURCE_DIR} and try building in a separate directory."
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MSVC AND CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
|
||||
message(FATAL_ERROR "Visual Studio 32-bit build is not supported.")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"validator-keys requires a 64 bit target architecture.\n"
|
||||
"The most likely cause of this warning is trying to build on a 32-bit OS."
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE AND NOT HOMEBREW)
|
||||
find_program(HOMEBREW brew)
|
||||
endif()
|
||||
@@ -1,360 +0,0 @@
|
||||
#include <ValidatorKeys.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
|
||||
#include <boost/algorithm/clamp.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
writeKeyFile(boost::filesystem::path const& keyFile, std::string const& contents)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::ofstream o(keyFile.string(), std::ios_base::trunc);
|
||||
if (o.fail())
|
||||
throw std::runtime_error("Cannot open key file: " + keyFile.string());
|
||||
|
||||
o << contents;
|
||||
o.close();
|
||||
if (o.fail())
|
||||
throw std::runtime_error("Failed to write key file: " + keyFile.string());
|
||||
#else
|
||||
auto const path = keyFile.string();
|
||||
int fd = ::open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
|
||||
if (fd == -1)
|
||||
throw std::runtime_error("Cannot open key file: " + path);
|
||||
|
||||
auto closeFile = [&path, &fd]() {
|
||||
if (fd != -1 && ::close(fd) == -1)
|
||||
{
|
||||
fd = -1;
|
||||
throw std::runtime_error("Failed to close key file: " + path);
|
||||
}
|
||||
fd = -1;
|
||||
};
|
||||
|
||||
if (::fchmod(fd, S_IRUSR | S_IWUSR) == -1)
|
||||
{
|
||||
auto const error = errno;
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
errno = error;
|
||||
throw std::runtime_error("Cannot set key file permissions: " + path);
|
||||
}
|
||||
|
||||
auto const* data = contents.data();
|
||||
auto bytesLeft = contents.size();
|
||||
while (bytesLeft != 0)
|
||||
{
|
||||
auto const written = ::write(fd, data, bytesLeft);
|
||||
if (written == -1)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
auto const error = errno;
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
errno = error;
|
||||
throw std::runtime_error("Failed to write key file: " + path);
|
||||
}
|
||||
if (written == 0)
|
||||
{
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
throw std::runtime_error("Failed to write key file: " + path);
|
||||
}
|
||||
|
||||
auto const bytesWritten = static_cast<std::size_t>(written);
|
||||
data += bytesWritten;
|
||||
bytesLeft -= bytesWritten;
|
||||
}
|
||||
|
||||
closeFile();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string
|
||||
ValidatorToken::toString() const
|
||||
{
|
||||
json::Value jv;
|
||||
jv["validation_secret_key"] = strHex(secretKey);
|
||||
jv["manifest"] = manifest;
|
||||
|
||||
return xrpl::base64Encode(to_string(jv));
|
||||
}
|
||||
|
||||
ValidatorKeys::ValidatorKeys(KeyType const& keyType)
|
||||
: keyType_(keyType)
|
||||
, tokenSequence_(0)
|
||||
, revoked_(false)
|
||||
, keys_(generateKeyPair(keyType_, randomSeed()))
|
||||
{
|
||||
}
|
||||
|
||||
ValidatorKeys::ValidatorKeys(
|
||||
KeyType const& keyType,
|
||||
SecretKey const& secretKey,
|
||||
std::uint32_t tokenSequence,
|
||||
bool revoked)
|
||||
: keyType_(keyType)
|
||||
, tokenSequence_(tokenSequence)
|
||||
, revoked_(revoked)
|
||||
, keys_({derivePublicKey(keyType_, secretKey), secretKey})
|
||||
{
|
||||
}
|
||||
|
||||
ValidatorKeys
|
||||
ValidatorKeys::make_ValidatorKeys(boost::filesystem::path const& keyFile)
|
||||
{
|
||||
std::ifstream ifsKeys(keyFile.c_str(), std::ios::in);
|
||||
|
||||
if (!ifsKeys)
|
||||
throw std::runtime_error("Failed to open key file: " + keyFile.string());
|
||||
|
||||
json::Reader reader;
|
||||
json::Value jKeys;
|
||||
if (!reader.parse(ifsKeys, jKeys))
|
||||
{
|
||||
throw std::runtime_error("Unable to parse json key file: " + keyFile.string());
|
||||
}
|
||||
|
||||
static std::array<std::string, 4> const requiredFields{
|
||||
{"key_type", "secret_key", "token_sequence", "revoked"}};
|
||||
|
||||
for (auto field : requiredFields)
|
||||
{
|
||||
if (!jKeys.isMember(field))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() + "' is missing \"" + field + "\" field");
|
||||
}
|
||||
}
|
||||
|
||||
auto const keyType = keyTypeFromString(jKeys["key_type"].asString());
|
||||
if (!keyType)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() +
|
||||
"' contains invalid \"key_type\" field: " + jKeys["key_type"].toStyledString());
|
||||
}
|
||||
|
||||
auto const secret =
|
||||
parseBase58<SecretKey>(TokenType::NodePrivate, jKeys["secret_key"].asString());
|
||||
|
||||
if (!secret)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() +
|
||||
"' contains invalid \"secret_key\" field: " + jKeys["secret_key"].toStyledString());
|
||||
}
|
||||
|
||||
std::uint32_t tokenSequence;
|
||||
try
|
||||
{
|
||||
if (!jKeys["token_sequence"].isIntegral())
|
||||
throw std::runtime_error("");
|
||||
|
||||
tokenSequence = jKeys["token_sequence"].asUInt();
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() + "' contains invalid \"token_sequence\" field: " +
|
||||
jKeys["token_sequence"].toStyledString());
|
||||
}
|
||||
|
||||
if (!jKeys["revoked"].isBool())
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() +
|
||||
"' contains invalid \"revoked\" field: " + jKeys["revoked"].toStyledString());
|
||||
|
||||
ValidatorKeys vk(*keyType, *secret, tokenSequence, jKeys["revoked"].asBool());
|
||||
|
||||
if (jKeys.isMember("domain"))
|
||||
{
|
||||
if (!jKeys["domain"].isString())
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() +
|
||||
"' contains invalid \"domain\" field: " + jKeys["domain"].toStyledString());
|
||||
|
||||
vk.domain(jKeys["domain"].asString());
|
||||
}
|
||||
|
||||
if (jKeys.isMember("manifest"))
|
||||
{
|
||||
if (!jKeys["manifest"].isString())
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() +
|
||||
"' contains invalid \"manifest\" field: " + jKeys["manifest"].toStyledString());
|
||||
|
||||
auto ret = strUnHex(jKeys["manifest"].asString());
|
||||
|
||||
if (!ret || ret->size() == 0)
|
||||
throw std::runtime_error(
|
||||
"Key file '" + keyFile.string() +
|
||||
"' contains invalid \"manifest\" field: " + jKeys["manifest"].toStyledString());
|
||||
|
||||
vk.manifest_.clear();
|
||||
vk.manifest_.reserve(ret->size());
|
||||
std::copy(ret->begin(), ret->end(), std::back_inserter(vk.manifest_));
|
||||
}
|
||||
|
||||
return vk;
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorKeys::writeToFile(boost::filesystem::path const& keyFile) const
|
||||
{
|
||||
using namespace boost::filesystem;
|
||||
|
||||
json::Value jv;
|
||||
jv["key_type"] = to_string(keyType_);
|
||||
jv["public_key"] = toBase58(TokenType::NodePublic, keys_.publicKey);
|
||||
jv["secret_key"] = toBase58(TokenType::NodePrivate, keys_.secretKey);
|
||||
jv["token_sequence"] = json::UInt(tokenSequence_);
|
||||
jv["revoked"] = revoked_;
|
||||
if (!domain_.empty())
|
||||
jv["domain"] = domain_;
|
||||
if (!manifest_.empty())
|
||||
jv["manifest"] = strHex(makeSlice(manifest_));
|
||||
|
||||
if (!keyFile.parent_path().empty())
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if (!exists(keyFile.parent_path()))
|
||||
boost::filesystem::create_directories(keyFile.parent_path(), ec);
|
||||
|
||||
if (ec || !is_directory(keyFile.parent_path()))
|
||||
throw std::runtime_error("Cannot create directory: " + keyFile.parent_path().string());
|
||||
}
|
||||
|
||||
writeKeyFile(keyFile, jv.toStyledString());
|
||||
}
|
||||
|
||||
boost::optional<ValidatorToken>
|
||||
ValidatorKeys::createValidatorToken(KeyType const& keyType)
|
||||
{
|
||||
if (revoked() || std::numeric_limits<std::uint32_t>::max() - 1 <= tokenSequence_)
|
||||
return boost::none;
|
||||
|
||||
++tokenSequence_;
|
||||
|
||||
auto const tokenSecret = generateSecretKey(keyType, randomSeed());
|
||||
auto const tokenPublic = derivePublicKey(keyType, tokenSecret);
|
||||
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = tokenSequence_;
|
||||
st[sfPublicKey] = keys_.publicKey;
|
||||
st[sfSigningPubKey] = tokenPublic;
|
||||
|
||||
if (!domain_.empty())
|
||||
st[sfDomain] = makeSlice(domain_);
|
||||
|
||||
xrpl::sign(st, HashPrefix::Manifest, keyType, tokenSecret);
|
||||
xrpl::sign(st, HashPrefix::Manifest, keyType_, keys_.secretKey, sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
manifest_.clear();
|
||||
manifest_.reserve(s.size());
|
||||
std::copy(s.begin(), s.end(), std::back_inserter(manifest_));
|
||||
|
||||
return ValidatorToken{xrpl::base64Encode(manifest_.data(), manifest_.size()), tokenSecret};
|
||||
}
|
||||
|
||||
std::string
|
||||
ValidatorKeys::revoke()
|
||||
{
|
||||
revoked_ = true;
|
||||
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = std::numeric_limits<std::uint32_t>::max();
|
||||
st[sfPublicKey] = keys_.publicKey;
|
||||
|
||||
xrpl::sign(st, HashPrefix::Manifest, keyType_, keys_.secretKey, sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
manifest_.clear();
|
||||
manifest_.reserve(s.size());
|
||||
std::copy(s.begin(), s.end(), std::back_inserter(manifest_));
|
||||
|
||||
return xrpl::base64Encode(manifest_.data(), manifest_.size());
|
||||
}
|
||||
|
||||
std::string
|
||||
ValidatorKeys::sign(std::string const& data) const
|
||||
{
|
||||
return strHex(xrpl::sign(keys_.publicKey, keys_.secretKey, makeSlice(data)));
|
||||
}
|
||||
|
||||
void
|
||||
ValidatorKeys::domain(std::string d)
|
||||
{
|
||||
if (!d.empty())
|
||||
{
|
||||
// A valid domain for a validator must be at least 4 characters
|
||||
// long, should contain at least one . and should not be longer
|
||||
// that 128 characters.
|
||||
if (d.size() < 4 || d.size() > 128)
|
||||
throw std::runtime_error("The domain must be between 4 and 128 characters long.");
|
||||
|
||||
// This regular expression should do a decent job of weeding out
|
||||
// obviously wrong domain names but it isn't perfect. It does not
|
||||
// really support IDNs. If this turns out to be an issue, a more
|
||||
// thorough regex can be used or this check can just be removed.
|
||||
static boost::regex const re(
|
||||
"^" // Beginning of line
|
||||
"(" // Hostname or domain name
|
||||
"(?!-)" // - must not begin with '-'
|
||||
"[a-zA-Z0-9-]{1,63}" // - only alphanumeric and '-'
|
||||
"(?<!-)" // - must not end with '-'
|
||||
"\\." // segment separator
|
||||
")+" // 1 or more segments
|
||||
"[A-Za-z]{2,63}" // TLD
|
||||
"$" // End of line
|
||||
,
|
||||
boost::regex_constants::optimize);
|
||||
|
||||
if (!boost::regex_match(d, re))
|
||||
throw std::runtime_error(
|
||||
"The domain field must use the '[host.][subdomain.]domain.tld' "
|
||||
"format");
|
||||
}
|
||||
|
||||
domain_ = std::move(d);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,161 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace filesystem {
|
||||
class path;
|
||||
}
|
||||
} // namespace boost
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
struct ValidatorToken
|
||||
{
|
||||
std::string const manifest;
|
||||
SecretKey const secretKey;
|
||||
|
||||
/// Returns base64-encoded JSON object
|
||||
std::string
|
||||
toString() const;
|
||||
};
|
||||
|
||||
class ValidatorKeys
|
||||
{
|
||||
private:
|
||||
KeyType keyType_;
|
||||
|
||||
// struct used to contain both public and secret keys
|
||||
struct Keys
|
||||
{
|
||||
PublicKey publicKey;
|
||||
SecretKey secretKey;
|
||||
|
||||
Keys() = delete;
|
||||
Keys(std::pair<PublicKey, SecretKey> p) : publicKey(p.first), secretKey(p.second)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t> manifest_;
|
||||
std::uint32_t tokenSequence_;
|
||||
bool revoked_;
|
||||
std::string domain_;
|
||||
Keys keys_;
|
||||
|
||||
public:
|
||||
explicit ValidatorKeys(KeyType const& keyType);
|
||||
|
||||
ValidatorKeys(
|
||||
KeyType const& keyType,
|
||||
SecretKey const& secretKey,
|
||||
std::uint32_t sequence,
|
||||
bool revoked = false);
|
||||
|
||||
/** Returns ValidatorKeys constructed from JSON file
|
||||
|
||||
@param keyFile Path to JSON key file
|
||||
|
||||
@throws std::runtime_error if file content is invalid
|
||||
*/
|
||||
static ValidatorKeys
|
||||
make_ValidatorKeys(boost::filesystem::path const& keyFile);
|
||||
|
||||
~ValidatorKeys() = default;
|
||||
ValidatorKeys(ValidatorKeys const&) = default;
|
||||
ValidatorKeys&
|
||||
operator=(ValidatorKeys const&) = default;
|
||||
|
||||
inline bool
|
||||
operator==(ValidatorKeys const& rhs) const
|
||||
{
|
||||
// SecretKey::operator== is deleted to discourage non-constant-time
|
||||
// comparison. The public key is derived deterministically from the
|
||||
// secret key, so comparing public keys is equivalent here.
|
||||
return revoked_ == rhs.revoked_ && keyType_ == rhs.keyType_ &&
|
||||
tokenSequence_ == rhs.tokenSequence_ && keys_.publicKey == rhs.keys_.publicKey;
|
||||
}
|
||||
|
||||
/** Write keys to JSON file
|
||||
|
||||
@param keyFile Path to file to write
|
||||
|
||||
@note Overwrites existing key file
|
||||
|
||||
@throws std::runtime_error if unable to create parent directory
|
||||
*/
|
||||
void
|
||||
writeToFile(boost::filesystem::path const& keyFile) const;
|
||||
|
||||
/** Returns validator token for current sequence
|
||||
|
||||
@param keyType Key type for the token keys
|
||||
*/
|
||||
boost::optional<ValidatorToken>
|
||||
createValidatorToken(KeyType const& keyType = KeyType::Secp256k1);
|
||||
|
||||
/** Revokes validator keys
|
||||
|
||||
@return base64-encoded key revocation
|
||||
*/
|
||||
std::string
|
||||
revoke();
|
||||
|
||||
/** Signs string with validator key
|
||||
|
||||
@param data String to sign
|
||||
|
||||
@return hex-encoded signature
|
||||
*/
|
||||
std::string
|
||||
sign(std::string const& data) const;
|
||||
|
||||
/** Returns the public key. */
|
||||
PublicKey const&
|
||||
publicKey() const
|
||||
{
|
||||
return keys_.publicKey;
|
||||
}
|
||||
|
||||
/** Returns true if keys are revoked. */
|
||||
bool
|
||||
revoked() const
|
||||
{
|
||||
return revoked_;
|
||||
}
|
||||
|
||||
/** Returns the domain associated with this key, if any */
|
||||
std::string
|
||||
domain() const
|
||||
{
|
||||
return domain_;
|
||||
}
|
||||
|
||||
/** Sets the domain associated with this key */
|
||||
void
|
||||
domain(std::string d);
|
||||
|
||||
/** Returns the last manifest we generated for this domain, if available. */
|
||||
std::vector<std::uint8_t>
|
||||
manifest() const
|
||||
{
|
||||
return manifest_;
|
||||
}
|
||||
|
||||
/** Returns the sequence number of the last manifest generated. */
|
||||
std::uint32_t
|
||||
sequence() const
|
||||
{
|
||||
return tokenSequence_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,454 +0,0 @@
|
||||
#include <ValidatorKeysTool.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/beast/core/SemanticVersion.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <ValidatorKeys.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// The build version number. You must edit this for each release
|
||||
// and follow the format described at http://semver.org/
|
||||
//--------------------------------------------------------------------------
|
||||
char const* const versionString =
|
||||
"0.3.2"
|
||||
|
||||
#if defined(DEBUG) || defined(SANITIZER)
|
||||
"+"
|
||||
#ifdef DEBUG
|
||||
"DEBUG"
|
||||
#ifdef SANITIZER
|
||||
"."
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SANITIZER
|
||||
BOOST_PP_STRINGIZE(SANITIZER)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
;
|
||||
|
||||
static constexpr std::size_t kConfigLineLength = 72;
|
||||
|
||||
static int
|
||||
runUnitTests()
|
||||
{
|
||||
using namespace beast::unit_test;
|
||||
reporter r;
|
||||
bool const anyFailed = r.runEach(globalSuites());
|
||||
if (anyFailed)
|
||||
return EXIT_FAILURE; // LCOV_EXCL_LINE
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
createKeyFile(boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
if (exists(keyFile))
|
||||
throw std::runtime_error("Refusing to overwrite existing key file: " + keyFile.string());
|
||||
|
||||
ValidatorKeys const keys(KeyType::Ed25519);
|
||||
keys.writeToFile(keyFile);
|
||||
|
||||
std::cout << "Validator keys stored in " << keyFile.string()
|
||||
<< "\n\nThis file should be stored securely and not shared.\n\n";
|
||||
}
|
||||
|
||||
void
|
||||
createToken(boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
auto keys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
|
||||
if (keys.revoked())
|
||||
throw std::runtime_error("Validator keys have been revoked.");
|
||||
|
||||
auto const token = keys.createValidatorToken();
|
||||
|
||||
if (!token)
|
||||
throw std::runtime_error(
|
||||
"Maximum number of tokens have already been generated.\n"
|
||||
"Revoke validator keys if previous token has been compromised.");
|
||||
|
||||
// Update key file with new token sequence
|
||||
keys.writeToFile(keyFile);
|
||||
|
||||
std::cout << "Update xrpld.cfg file with these values and restart xrpld:\n\n";
|
||||
std::cout << "# validator public key: " << toBase58(TokenType::NodePublic, keys.publicKey())
|
||||
<< "\n\n";
|
||||
std::cout << "[validator_token]\n";
|
||||
|
||||
auto const tokenStr = token->toString();
|
||||
for (std::size_t i = 0; i < tokenStr.size(); i += kConfigLineLength)
|
||||
std::cout << tokenStr.substr(i, kConfigLineLength) << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
createRevocation(boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
auto keys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
|
||||
if (keys.revoked())
|
||||
std::cout << "WARNING: Validator keys have already been revoked!\n\n";
|
||||
else
|
||||
std::cout << "WARNING: This will revoke your validator keys!\n\n";
|
||||
|
||||
auto const revocation = keys.revoke();
|
||||
|
||||
// Update key file with new token sequence
|
||||
keys.writeToFile(keyFile);
|
||||
|
||||
std::cout << "Update xrpld.cfg file with these values and restart xrpld:\n\n";
|
||||
std::cout << "# validator public key: " << toBase58(TokenType::NodePublic, keys.publicKey())
|
||||
<< "\n\n";
|
||||
std::cout << "[validator_key_revocation]\n";
|
||||
|
||||
for (std::size_t i = 0; i < revocation.size(); i += kConfigLineLength)
|
||||
std::cout << revocation.substr(i, kConfigLineLength) << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
attestDomain(xrpl::ValidatorKeys const& keys)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
if (keys.domain().empty())
|
||||
{
|
||||
std::cout << "No attestation is necessary if no domain is specified!\n";
|
||||
std::cout << "If you have an attestation in your xrpl-ledger.toml\n";
|
||||
std::cout << "you should remove it at this time.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "The domain attestation for validator "
|
||||
<< toBase58(TokenType::NodePublic, keys.publicKey()) << " is:\n\n";
|
||||
|
||||
std::cout << "attestation=\""
|
||||
<< keys.sign(
|
||||
"[domain-attestation-blob:" + keys.domain() + ":" +
|
||||
toBase58(TokenType::NodePublic, keys.publicKey()) + "]")
|
||||
<< "\"\n\n";
|
||||
|
||||
std::cout << "You should include it in your xrp-ledger.toml file in the\n";
|
||||
std::cout << "section for this validator.\n";
|
||||
}
|
||||
|
||||
void
|
||||
attestDomain(boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
auto keys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
|
||||
if (keys.revoked())
|
||||
throw std::runtime_error("Operation error: The specified master key has been revoked!");
|
||||
|
||||
attestDomain(keys);
|
||||
}
|
||||
|
||||
void
|
||||
setDomain(std::string const& domain, boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
auto keys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
|
||||
if (keys.revoked())
|
||||
throw std::runtime_error("Operation error: The specified master key has been revoked!");
|
||||
|
||||
if (domain == keys.domain())
|
||||
{
|
||||
if (domain.empty())
|
||||
std::cout << "The domain name was already cleared!\n";
|
||||
else
|
||||
std::cout << "The domain name was already set.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the domain and generate a new token
|
||||
keys.domain(domain);
|
||||
auto const token = keys.createValidatorToken();
|
||||
if (!token)
|
||||
throw std::runtime_error(
|
||||
"Maximum number of tokens have already been generated.\n"
|
||||
"Revoke validator keys if previous token has been compromised.");
|
||||
|
||||
// Flush to disk
|
||||
keys.writeToFile(keyFile);
|
||||
|
||||
if (domain.empty())
|
||||
std::cout << "The domain name has been cleared.\n";
|
||||
else
|
||||
std::cout << "The domain name has been set to: " << domain << "\n\n";
|
||||
attestDomain(keys);
|
||||
|
||||
std::cout << "\n";
|
||||
std::cout << "You also need to update the xrpld.cfg file to add a new\n";
|
||||
std::cout << "validator token and restart xrpld:\n\n";
|
||||
std::cout << "# validator public key: " << toBase58(TokenType::NodePublic, keys.publicKey())
|
||||
<< "\n\n";
|
||||
std::cout << "[validator_token]\n";
|
||||
|
||||
auto const tokenStr = token->toString();
|
||||
for (std::size_t i = 0; i < tokenStr.size(); i += kConfigLineLength)
|
||||
std::cout << tokenStr.substr(i, kConfigLineLength) << std::endl;
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void
|
||||
signData(std::string const& data, boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
if (data.empty())
|
||||
throw std::runtime_error("Syntax error: Must specify data string to sign");
|
||||
|
||||
auto keys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
|
||||
if (keys.revoked())
|
||||
std::cout << "WARNING: Validator keys have been revoked!\n\n";
|
||||
|
||||
std::cout << keys.sign(data) << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
generateManifest(std::string const& type, boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace xrpl;
|
||||
|
||||
auto keys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
|
||||
auto const m = keys.manifest();
|
||||
|
||||
if (m.empty())
|
||||
{
|
||||
std::cout << "The last manifest generated is unavailable. You can\n";
|
||||
std::cout << "generate a new one.\n\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "base64")
|
||||
{
|
||||
std::cout << "Manifest #" << keys.sequence() << " (Base64):\n";
|
||||
std::cout << base64Encode(m.data(), m.size()) << "\n\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "hex")
|
||||
{
|
||||
std::cout << "Manifest #" << keys.sequence() << " (Hex):\n";
|
||||
std::cout << strHex(makeSlice(m)) << "\n\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Unknown encoding '" << type << "'\n";
|
||||
}
|
||||
|
||||
int
|
||||
runCommand(
|
||||
std::string const& command,
|
||||
std::vector<std::string> const& args,
|
||||
boost::filesystem::path const& keyFile)
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
static map<string, vector<string>::size_type> const commandArgs = {
|
||||
{"create_keys", 0},
|
||||
{"create_token", 0},
|
||||
{"revoke_keys", 0},
|
||||
{"set_domain", 1},
|
||||
{"clear_domain", 0},
|
||||
{"attest_domain", 0},
|
||||
{"show_manifest", 1},
|
||||
{"sign", 1},
|
||||
};
|
||||
|
||||
auto const iArgs = commandArgs.find(command);
|
||||
|
||||
if (iArgs == commandArgs.end())
|
||||
throw std::runtime_error("Unknown command: " + command);
|
||||
|
||||
if (args.size() != iArgs->second)
|
||||
throw std::runtime_error("Syntax error: Wrong number of arguments");
|
||||
|
||||
if (command == "create_keys")
|
||||
createKeyFile(keyFile);
|
||||
else if (command == "create_token")
|
||||
createToken(keyFile);
|
||||
else if (command == "revoke_keys")
|
||||
createRevocation(keyFile);
|
||||
else if (command == "set_domain")
|
||||
setDomain(args[0], keyFile);
|
||||
else if (command == "clear_domain")
|
||||
setDomain("", keyFile);
|
||||
else if (command == "attest_domain")
|
||||
attestDomain(keyFile);
|
||||
else if (command == "sign")
|
||||
signData(args[0], keyFile);
|
||||
else if (command == "show_manifest")
|
||||
generateManifest(args[0], keyFile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
static std::string
|
||||
getEnvVar(char const* name)
|
||||
{
|
||||
std::string value;
|
||||
|
||||
auto const v = getenv(name);
|
||||
|
||||
if (v != nullptr)
|
||||
value = v;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void
|
||||
printHelp(boost::program_options::options_description const& desc)
|
||||
{
|
||||
std::cerr << "validator-keys [options] <command> [<argument> ...]\n"
|
||||
<< desc << std::endl
|
||||
<< "Commands: \n"
|
||||
" create_keys Generate validator keys.\n"
|
||||
" create_token Generate validator token.\n"
|
||||
" revoke_keys Revoke validator keys.\n"
|
||||
" sign <data> Sign string with validator "
|
||||
"key.\n"
|
||||
" show_manifest [hex|base64] Displays the last generated "
|
||||
"manifest\n"
|
||||
" set_domain <domain> Associate a domain with the "
|
||||
"validator key.\n"
|
||||
" clear_domain Disassociate a domain from a "
|
||||
"validator key.\n"
|
||||
" attest_domain Produce the attestation string "
|
||||
"for a domain.\n";
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
std::string const&
|
||||
getVersionString()
|
||||
{
|
||||
static std::string const value = [] {
|
||||
std::string const s = versionString;
|
||||
beast::SemanticVersion v;
|
||||
if (!v.parse(s) || v.print() != s)
|
||||
throw std::logic_error(s + ": Bad version string"); // LCOV_EXCL_LINE
|
||||
return s;
|
||||
}();
|
||||
return value;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
namespace po = boost::program_options;
|
||||
|
||||
po::variables_map vm;
|
||||
|
||||
// Set up option parsing.
|
||||
//
|
||||
po::options_description general("General Options");
|
||||
general.add_options()("help,h", "Display this message.")(
|
||||
"keyfile", po::value<std::string>(), "Specify the key file.")(
|
||||
"unittest,u", "Perform unit tests.")("version", "Display the build version.");
|
||||
|
||||
po::options_description hidden("Hidden options");
|
||||
hidden.add_options()("command", po::value<std::string>(), "Command.")(
|
||||
"arguments",
|
||||
po::value<std::vector<std::string>>()->default_value(std::vector<std::string>(), "empty"),
|
||||
"Arguments.");
|
||||
po::positional_options_description p;
|
||||
p.add("command", 1).add("arguments", -1);
|
||||
|
||||
po::options_description cmdline_options;
|
||||
cmdline_options.add(general).add(hidden);
|
||||
|
||||
// Parse options, if no error.
|
||||
try
|
||||
{
|
||||
po::store(
|
||||
po::command_line_parser(argc, argv)
|
||||
.options(cmdline_options) // Parse options.
|
||||
.positional(p)
|
||||
.run(),
|
||||
vm);
|
||||
po::notify(vm); // Invoke option notify functions.
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (std::exception const&)
|
||||
{
|
||||
std::cerr << "validator-keys: Incorrect command line syntax." << std::endl;
|
||||
std::cerr << "Use '--help' for a list of options." << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
// Run the unit tests if requested.
|
||||
// The unit tests will exit the application with an appropriate return code.
|
||||
if (vm.count("unittest"))
|
||||
return runUnitTests();
|
||||
|
||||
// LCOV_EXCL_START
|
||||
if (vm.count("version"))
|
||||
{
|
||||
std::cout << "validator-keys version " << getVersionString() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (vm.count("help") || !vm.count("command"))
|
||||
{
|
||||
printHelp(general);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
std::string const homeDir = getEnvVar("HOME");
|
||||
std::string const defaultKeyFile =
|
||||
(homeDir.empty() ? boost::filesystem::current_path().string() : homeDir) +
|
||||
"/.ripple/validator-keys.json";
|
||||
|
||||
try
|
||||
{
|
||||
using namespace boost::filesystem;
|
||||
path keyFile = vm.count("keyfile") ? vm["keyfile"].as<std::string>() : defaultKeyFile;
|
||||
|
||||
return runCommand(
|
||||
vm["command"].as<std::string>(),
|
||||
vm["arguments"].as<std::vector<std::string>>(),
|
||||
keyFile);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
std::cerr << e.what() << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace filesystem {
|
||||
class path;
|
||||
}
|
||||
} // namespace boost
|
||||
|
||||
std::string const&
|
||||
getVersionString();
|
||||
|
||||
void
|
||||
createKeyFile(boost::filesystem::path const& keyFile);
|
||||
|
||||
void
|
||||
createToken(boost::filesystem::path const& keyFile);
|
||||
|
||||
void
|
||||
createRevocation(boost::filesystem::path const& keyFile);
|
||||
|
||||
void
|
||||
signData(std::string const& data, boost::filesystem::path const& keyFile);
|
||||
|
||||
int
|
||||
runCommand(
|
||||
std::string const& command,
|
||||
std::vector<std::string> const& arg,
|
||||
boost::filesystem::path const& keyFile);
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
Write a key file dir and remove when done.
|
||||
*/
|
||||
class KeyFileGuard
|
||||
{
|
||||
private:
|
||||
using Path = boost::filesystem::path;
|
||||
|
||||
beast::unit_test::Suite& test_;
|
||||
Path subDir_;
|
||||
|
||||
void
|
||||
rmDir(Path const& toRm)
|
||||
{
|
||||
if (boost::filesystem::is_directory(toRm))
|
||||
boost::filesystem::remove_all(toRm);
|
||||
else
|
||||
test_.log << "Expected " << toRm.string() << " to be an existing directory."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
public:
|
||||
KeyFileGuard(beast::unit_test::Suite& test, std::string const& subDir)
|
||||
: test_(test), subDir_(subDir)
|
||||
{
|
||||
if (!boost::filesystem::exists(subDir_))
|
||||
boost::filesystem::create_directory(subDir_);
|
||||
else
|
||||
// Cannot run the test. Someone created a file or directory
|
||||
// where we want to put our directory
|
||||
throw std::runtime_error("Cannot create directory: " + subDir_.string());
|
||||
}
|
||||
~KeyFileGuard()
|
||||
{
|
||||
try
|
||||
{
|
||||
rmDir(subDir_);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
// if we throw here, just let it die.
|
||||
test_.log << "Error in ~KeyFileGuard: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,295 +0,0 @@
|
||||
#include <test/KeyFileGuard.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <ValidatorKeys.h>
|
||||
#include <ValidatorKeysTool.h>
|
||||
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace tests {
|
||||
|
||||
class ValidatorKeysTool_test : public beast::unit_test::Suite
|
||||
{
|
||||
private:
|
||||
// Allow cout to be redirected. Destructor restores old cout streambuf.
|
||||
class CoutRedirect
|
||||
{
|
||||
public:
|
||||
CoutRedirect(std::stringstream& sStream) : old_(std::cout.rdbuf(sStream.rdbuf()))
|
||||
{
|
||||
}
|
||||
|
||||
~CoutRedirect()
|
||||
{
|
||||
std::cout.rdbuf(old_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::streambuf* const old_;
|
||||
};
|
||||
|
||||
void
|
||||
testCreateKeyFile()
|
||||
{
|
||||
testcase("Create Key File");
|
||||
|
||||
std::stringstream coutCapture;
|
||||
CoutRedirect coutRedirect{coutCapture};
|
||||
|
||||
using namespace boost::filesystem;
|
||||
|
||||
path const subdir = "test_key_file";
|
||||
KeyFileGuard const g(*this, subdir.string());
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
|
||||
createKeyFile(keyFile);
|
||||
BEAST_EXPECT(exists(keyFile));
|
||||
|
||||
std::string const expectedError =
|
||||
"Refusing to overwrite existing key file: " + keyFile.string();
|
||||
std::string error;
|
||||
try
|
||||
{
|
||||
createKeyFile(keyFile);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
|
||||
void
|
||||
testCreateToken()
|
||||
{
|
||||
testcase("Create Token");
|
||||
|
||||
std::stringstream coutCapture;
|
||||
CoutRedirect coutRedirect{coutCapture};
|
||||
|
||||
using namespace boost::filesystem;
|
||||
|
||||
path const subdir = "test_key_file";
|
||||
KeyFileGuard const g(*this, subdir.string());
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
|
||||
auto testToken = [this](path const& keyFile, std::string const& expectedError) {
|
||||
try
|
||||
{
|
||||
createToken(keyFile);
|
||||
BEAST_EXPECT(expectedError.empty());
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(std::string{e.what()} == expectedError);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
std::string const expectedError = "Failed to open key file: " + keyFile.string();
|
||||
testToken(keyFile, expectedError);
|
||||
}
|
||||
|
||||
createKeyFile(keyFile);
|
||||
|
||||
{
|
||||
std::string const expectedError = "";
|
||||
testToken(keyFile, expectedError);
|
||||
}
|
||||
{
|
||||
auto const keyType = KeyType::Ed25519;
|
||||
auto const kp = generateKeyPair(keyType, randomSeed());
|
||||
|
||||
auto keys =
|
||||
ValidatorKeys(keyType, kp.second, std::numeric_limits<std::uint32_t>::max() - 1);
|
||||
|
||||
keys.writeToFile(keyFile);
|
||||
std::string const expectedError =
|
||||
"Maximum number of tokens have already been generated.\n"
|
||||
"Revoke validator keys if previous token has been compromised.";
|
||||
testToken(keyFile, expectedError);
|
||||
}
|
||||
{
|
||||
createRevocation(keyFile);
|
||||
std::string const expectedError = "Validator keys have been revoked.";
|
||||
testToken(keyFile, expectedError);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCreateRevocation()
|
||||
{
|
||||
testcase("Create Revocation");
|
||||
|
||||
std::stringstream coutCapture;
|
||||
CoutRedirect coutRedirect{coutCapture};
|
||||
|
||||
using namespace boost::filesystem;
|
||||
|
||||
path const subdir = "test_key_file";
|
||||
KeyFileGuard const g(*this, subdir.string());
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
|
||||
auto expectedError = "Failed to open key file: " + keyFile.string();
|
||||
std::string error;
|
||||
try
|
||||
{
|
||||
createRevocation(keyFile);
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
|
||||
createKeyFile(keyFile);
|
||||
BEAST_EXPECT(exists(keyFile));
|
||||
|
||||
createRevocation(keyFile);
|
||||
createRevocation(keyFile);
|
||||
}
|
||||
|
||||
void
|
||||
testSign()
|
||||
{
|
||||
testcase("Sign");
|
||||
|
||||
std::stringstream coutCapture;
|
||||
CoutRedirect coutRedirect{coutCapture};
|
||||
|
||||
using namespace boost::filesystem;
|
||||
|
||||
auto testSign =
|
||||
[this](std::string const& data, path const& keyFile, std::string const& expectedError) {
|
||||
try
|
||||
{
|
||||
signData(data, keyFile);
|
||||
BEAST_EXPECT(expectedError.empty());
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(std::string{e.what()} == expectedError);
|
||||
}
|
||||
};
|
||||
|
||||
std::string const data = "data to sign";
|
||||
|
||||
path const subdir = "test_key_file";
|
||||
KeyFileGuard const g(*this, subdir.string());
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
|
||||
{
|
||||
std::string const expectedError = "Failed to open key file: " + keyFile.string();
|
||||
testSign(data, keyFile, expectedError);
|
||||
}
|
||||
|
||||
createKeyFile(keyFile);
|
||||
BEAST_EXPECT(exists(keyFile));
|
||||
|
||||
{
|
||||
std::string const emptyData = "";
|
||||
std::string const expectedError = "Syntax error: Must specify data string to sign";
|
||||
testSign(emptyData, keyFile, expectedError);
|
||||
}
|
||||
{
|
||||
std::string const expectedError = "";
|
||||
testSign(data, keyFile, expectedError);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRunCommand()
|
||||
{
|
||||
testcase("Run Command");
|
||||
|
||||
std::stringstream coutCapture;
|
||||
CoutRedirect coutRedirect{coutCapture};
|
||||
|
||||
using namespace boost::filesystem;
|
||||
|
||||
path const subdir = "test_key_file";
|
||||
KeyFileGuard g(*this, subdir.string());
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
|
||||
auto testCommand = [this](
|
||||
std::string const& command,
|
||||
std::vector<std::string> const& args,
|
||||
path const& keyFile,
|
||||
std::string const& expectedError) {
|
||||
try
|
||||
{
|
||||
runCommand(command, args, keyFile);
|
||||
BEAST_EXPECT(expectedError.empty());
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(std::string{e.what()} == expectedError);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::string> const noArgs;
|
||||
std::vector<std::string> const oneArg = {"some data"};
|
||||
std::vector<std::string> const twoArgs = {"data", "more data"};
|
||||
std::string const noError = "";
|
||||
std::string const argError = "Syntax error: Wrong number of arguments";
|
||||
{
|
||||
std::string const command = "unknown";
|
||||
std::string const expectedError = "Unknown command: " + command;
|
||||
testCommand(command, noArgs, keyFile, expectedError);
|
||||
testCommand(command, oneArg, keyFile, expectedError);
|
||||
testCommand(command, twoArgs, keyFile, expectedError);
|
||||
}
|
||||
{
|
||||
std::string const command = "create_keys";
|
||||
testCommand(command, noArgs, keyFile, noError);
|
||||
testCommand(command, oneArg, keyFile, argError);
|
||||
testCommand(command, twoArgs, keyFile, argError);
|
||||
}
|
||||
{
|
||||
std::string const command = "create_token";
|
||||
testCommand(command, noArgs, keyFile, noError);
|
||||
testCommand(command, oneArg, keyFile, argError);
|
||||
testCommand(command, twoArgs, keyFile, argError);
|
||||
}
|
||||
{
|
||||
std::string const command = "revoke_keys";
|
||||
testCommand(command, noArgs, keyFile, noError);
|
||||
testCommand(command, oneArg, keyFile, argError);
|
||||
testCommand(command, twoArgs, keyFile, argError);
|
||||
}
|
||||
{
|
||||
std::string const command = "sign";
|
||||
testCommand(command, noArgs, keyFile, argError);
|
||||
testCommand(command, oneArg, keyFile, noError);
|
||||
testCommand(command, twoArgs, keyFile, argError);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
getVersionString();
|
||||
|
||||
testCreateKeyFile();
|
||||
testCreateToken();
|
||||
testCreateRevocation();
|
||||
testSign();
|
||||
testRunCommand();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ValidatorKeysTool, keys, xrpl);
|
||||
|
||||
} // namespace tests
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,408 +0,0 @@
|
||||
#include <test/KeyFileGuard.h>
|
||||
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <ValidatorKeys.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace tests {
|
||||
|
||||
class ValidatorKeys_test : public beast::unit_test::Suite
|
||||
{
|
||||
private:
|
||||
void
|
||||
testKeyFile(
|
||||
boost::filesystem::path const& keyFile,
|
||||
json::Value const& jv,
|
||||
std::string const& expectedError)
|
||||
{
|
||||
{
|
||||
std::ofstream o(keyFile.string(), std::ios_base::trunc);
|
||||
o << jv.toStyledString();
|
||||
o.close();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
BEAST_EXPECT(expectedError.empty());
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(std::string{e.what()} == expectedError);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<KeyType, 2> const keyTypes{{KeyType::Ed25519, KeyType::Secp256k1}};
|
||||
|
||||
#ifndef _WIN32
|
||||
void
|
||||
expectOwnerOnlyKeyFile(boost::filesystem::path const& keyFile)
|
||||
{
|
||||
struct stat fileStatus;
|
||||
BEAST_EXPECT(::stat(keyFile.string().c_str(), &fileStatus) == 0);
|
||||
BEAST_EXPECT((fileStatus.st_mode & 0777) == (S_IRUSR | S_IWUSR));
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
testMakeValidatorKeys()
|
||||
{
|
||||
testcase("Make Validator Keys");
|
||||
|
||||
using namespace boost::filesystem;
|
||||
|
||||
path const subdir = "test_key_file";
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
|
||||
for (auto const keyType : keyTypes)
|
||||
{
|
||||
ValidatorKeys const keys(keyType);
|
||||
|
||||
KeyFileGuard const g(*this, subdir.string());
|
||||
|
||||
keys.writeToFile(keyFile);
|
||||
BEAST_EXPECT(exists(keyFile));
|
||||
|
||||
auto const keys2 = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
BEAST_EXPECT(keys == keys2);
|
||||
}
|
||||
{
|
||||
// Require expected fields
|
||||
KeyFileGuard g(*this, subdir.string());
|
||||
|
||||
auto expectedError = "Failed to open key file: " + keyFile.string();
|
||||
std::string error;
|
||||
try
|
||||
{
|
||||
ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
|
||||
expectedError = "Unable to parse json key file: " + keyFile.string();
|
||||
|
||||
{
|
||||
std::ofstream o(keyFile.string(), std::ios_base::trunc);
|
||||
o << "{{}";
|
||||
o.close();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
|
||||
json::Value jv;
|
||||
jv["dummy"] = "field";
|
||||
expectedError = "Key file '" + keyFile.string() + "' is missing \"key_type\" field";
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["key_type"] = "dummy keytype";
|
||||
expectedError = "Key file '" + keyFile.string() + "' is missing \"secret_key\" field";
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["secret_key"] = "dummy secret";
|
||||
expectedError =
|
||||
"Key file '" + keyFile.string() + "' is missing \"token_sequence\" field";
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["token_sequence"] = "dummy sequence";
|
||||
expectedError = "Key file '" + keyFile.string() + "' is missing \"revoked\" field";
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["revoked"] = "dummy revoked";
|
||||
expectedError = "Key file '" + keyFile.string() +
|
||||
"' contains invalid \"key_type\" field: " + jv["key_type"].toStyledString();
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
auto const keyType = KeyType::Ed25519;
|
||||
jv["key_type"] = to_string(keyType);
|
||||
expectedError = "Key file '" + keyFile.string() +
|
||||
"' contains invalid \"secret_key\" field: " + jv["secret_key"].toStyledString();
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
ValidatorKeys const keys(keyType);
|
||||
{
|
||||
auto const kp = generateKeyPair(keyType, randomSeed());
|
||||
jv["secret_key"] = toBase58(TokenType::NodePrivate, kp.second);
|
||||
}
|
||||
expectedError = "Key file '" + keyFile.string() +
|
||||
"' contains invalid \"token_sequence\" field: " +
|
||||
jv["token_sequence"].toStyledString();
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["token_sequence"] = -1;
|
||||
expectedError = "Key file '" + keyFile.string() +
|
||||
"' contains invalid \"token_sequence\" field: " +
|
||||
jv["token_sequence"].toStyledString();
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["token_sequence"] = json::UInt(std::numeric_limits<std::uint32_t>::max());
|
||||
expectedError = "Key file '" + keyFile.string() +
|
||||
"' contains invalid \"revoked\" field: " + jv["revoked"].toStyledString();
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["revoked"] = false;
|
||||
expectedError = "";
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
|
||||
jv["revoked"] = true;
|
||||
testKeyFile(keyFile, jv, expectedError);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCreateValidatorToken()
|
||||
{
|
||||
testcase("Create Validator Token");
|
||||
|
||||
for (auto const keyType : keyTypes)
|
||||
{
|
||||
ValidatorKeys keys(keyType);
|
||||
std::uint32_t sequence = 0;
|
||||
|
||||
for (auto const tokenKeyType : keyTypes)
|
||||
{
|
||||
auto const token = keys.createValidatorToken(tokenKeyType);
|
||||
|
||||
if (!BEAST_EXPECT(token))
|
||||
continue;
|
||||
|
||||
auto const tokenPublicKey = derivePublicKey(tokenKeyType, token->secretKey);
|
||||
|
||||
STObject st(sfGeneric);
|
||||
auto const manifest = xrpl::base64Decode(token->manifest);
|
||||
SerialIter sit(manifest.data(), manifest.size());
|
||||
st.set(sit);
|
||||
|
||||
auto const seq = get(st, sfSequence);
|
||||
BEAST_EXPECT(seq);
|
||||
BEAST_EXPECT(*seq == ++sequence);
|
||||
|
||||
auto const tpk = get<PublicKey>(st, sfSigningPubKey);
|
||||
BEAST_EXPECT(tpk);
|
||||
BEAST_EXPECT(*tpk == tokenPublicKey);
|
||||
BEAST_EXPECT(verify(st, HashPrefix::Manifest, tokenPublicKey));
|
||||
|
||||
auto const pk = get<PublicKey>(st, sfPublicKey);
|
||||
BEAST_EXPECT(pk);
|
||||
BEAST_EXPECT(*pk == keys.publicKey());
|
||||
BEAST_EXPECT(verify(st, HashPrefix::Manifest, keys.publicKey(), sfMasterSignature));
|
||||
}
|
||||
}
|
||||
|
||||
auto const keyType = KeyType::Ed25519;
|
||||
auto const kp = generateKeyPair(keyType, randomSeed());
|
||||
|
||||
auto keys =
|
||||
ValidatorKeys(keyType, kp.second, std::numeric_limits<std::uint32_t>::max() - 1);
|
||||
|
||||
BEAST_EXPECT(!keys.createValidatorToken(keyType));
|
||||
|
||||
keys.revoke();
|
||||
BEAST_EXPECT(!keys.createValidatorToken(keyType));
|
||||
}
|
||||
|
||||
void
|
||||
testRevoke()
|
||||
{
|
||||
testcase("Revoke");
|
||||
|
||||
for (auto const keyType : keyTypes)
|
||||
{
|
||||
ValidatorKeys keys(keyType);
|
||||
|
||||
auto const revocation = keys.revoke();
|
||||
|
||||
STObject st(sfGeneric);
|
||||
auto const manifest = xrpl::base64Decode(revocation);
|
||||
SerialIter sit(manifest.data(), manifest.size());
|
||||
st.set(sit);
|
||||
|
||||
auto const seq = get(st, sfSequence);
|
||||
BEAST_EXPECT(seq);
|
||||
BEAST_EXPECT(*seq == std::numeric_limits<std::uint32_t>::max());
|
||||
|
||||
auto const pk = get(st, sfPublicKey);
|
||||
BEAST_EXPECT(pk);
|
||||
BEAST_EXPECT(*pk == keys.publicKey());
|
||||
BEAST_EXPECT(verify(st, HashPrefix::Manifest, keys.publicKey(), sfMasterSignature));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSign()
|
||||
{
|
||||
testcase("Sign");
|
||||
|
||||
std::map<KeyType, std::string> expected(
|
||||
{{KeyType::Ed25519,
|
||||
"2EE541D6825791BF5454C571D2B363EAB3F01C73159B1F"
|
||||
"237AC6D38663A82B9D5EAD262D5F776B916E68247A1F082090F3BAE7ABC939"
|
||||
"C8F29B0DC759FD712300"},
|
||||
{KeyType::Secp256k1,
|
||||
"3045022100F142C27BF83D8D4541C7A4E759DE64A672"
|
||||
"51A388A422DFDA6F4B470A2113ABC4022002DA56695F3A805F62B55E7CC8D5"
|
||||
"55438D64A229CD0B4BA2AE33402443B20409"}});
|
||||
|
||||
std::string const data = "data to sign";
|
||||
|
||||
for (auto const keyType : keyTypes)
|
||||
{
|
||||
auto const sk = generateSecretKey(keyType, generateSeed("test"));
|
||||
ValidatorKeys keys(keyType, sk, 1);
|
||||
|
||||
auto const signature = keys.sign(data);
|
||||
BEAST_EXPECT(expected[keyType] == signature);
|
||||
|
||||
auto const ret = strUnHex(signature);
|
||||
BEAST_EXPECT(ret);
|
||||
BEAST_EXPECT(ret->size());
|
||||
BEAST_EXPECT(verify(keys.publicKey(), makeSlice(data), makeSlice(*ret)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWriteToFile()
|
||||
{
|
||||
testcase("Write to File");
|
||||
|
||||
using namespace boost::filesystem;
|
||||
|
||||
auto const keyType = KeyType::Ed25519;
|
||||
ValidatorKeys keys(keyType);
|
||||
|
||||
{
|
||||
path const subdir = "test_key_file";
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
KeyFileGuard g(*this, subdir.string());
|
||||
|
||||
keys.writeToFile(keyFile);
|
||||
BEAST_EXPECT(exists(keyFile));
|
||||
#ifndef _WIN32
|
||||
expectOwnerOnlyKeyFile(keyFile);
|
||||
#endif
|
||||
|
||||
auto fileKeys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
BEAST_EXPECT(keys == fileKeys);
|
||||
|
||||
// Overwrite file with new sequence
|
||||
#ifndef _WIN32
|
||||
BEAST_EXPECT(
|
||||
::chmod(keyFile.string().c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == 0);
|
||||
#endif
|
||||
keys.createValidatorToken(KeyType::Secp256k1);
|
||||
keys.writeToFile(keyFile);
|
||||
#ifndef _WIN32
|
||||
expectOwnerOnlyKeyFile(keyFile);
|
||||
#endif
|
||||
|
||||
fileKeys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
BEAST_EXPECT(keys == fileKeys);
|
||||
}
|
||||
{
|
||||
// Write to key file in current relative directory
|
||||
path const keyFile = "test_validator_keys.json";
|
||||
if (!exists(keyFile))
|
||||
{
|
||||
keys.writeToFile(keyFile);
|
||||
remove(keyFile.string());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot run the test. Someone created a file
|
||||
// where we want to put our key file
|
||||
Throw<std::runtime_error>("Cannot create key file: " + keyFile.string());
|
||||
}
|
||||
}
|
||||
{
|
||||
// Create key file directory
|
||||
path const subdir = "test_key_file";
|
||||
path const keyFile = subdir / "directories/to/create/validator_keys.json";
|
||||
KeyFileGuard g(*this, subdir.string());
|
||||
|
||||
keys.writeToFile(keyFile);
|
||||
BEAST_EXPECT(exists(keyFile));
|
||||
|
||||
auto const fileKeys = ValidatorKeys::make_ValidatorKeys(keyFile);
|
||||
BEAST_EXPECT(keys == fileKeys);
|
||||
}
|
||||
{
|
||||
// Fail if file cannot be opened for write
|
||||
path const subdir = "test_key_file";
|
||||
KeyFileGuard g(*this, subdir.string());
|
||||
|
||||
path const badKeyFile = subdir / ".";
|
||||
auto expectedError = "Cannot open key file: " + badKeyFile.string();
|
||||
std::string error;
|
||||
try
|
||||
{
|
||||
keys.writeToFile(badKeyFile);
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
|
||||
// Fail if parent directory is existing file
|
||||
path const keyFile = subdir / "validator_keys.json";
|
||||
keys.writeToFile(keyFile);
|
||||
path const conflictingPath = keyFile / "validators_keys.json";
|
||||
expectedError = "Cannot create directory: " + conflictingPath.parent_path().string();
|
||||
try
|
||||
{
|
||||
keys.writeToFile(conflictingPath);
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
error = e.what();
|
||||
}
|
||||
BEAST_EXPECT(error == expectedError);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testMakeValidatorKeys();
|
||||
testCreateValidatorToken();
|
||||
testRevoke();
|
||||
testSign();
|
||||
testWriteToFile();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(ValidatorKeys, keys, xrpl);
|
||||
|
||||
} // namespace tests
|
||||
|
||||
} // namespace xrpl
|
||||
Reference in New Issue
Block a user