forked from bitcoinafterlife/bal-server
Compare commits
6 Commits
v0.3.0
...
7fe5fd3139
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fe5fd3139 | |||
| b46f85f436 | |||
|
190cac929e
|
|||
|
5e1d0d7c54
|
|||
|
9081e08785
|
|||
|
06db6f1d48
|
@@ -34,7 +34,7 @@ zmq = { version = "0.10.0" }
|
|||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "bal-server"
|
name = "bal-server"
|
||||||
path = "src/bin/bal-server-actix.rs"
|
path = "src/bin/bal-server.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "bal-pusher"
|
name = "bal-pusher"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ WORKDIR /build
|
|||||||
# Cache dependencies: copy Cargo.toml first, create dummy src to build deps
|
# Cache dependencies: copy Cargo.toml first, create dummy src to build deps
|
||||||
COPY Cargo.toml Cargo.lock* ./
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
RUN mkdir -p src/bin && \
|
RUN mkdir -p src/bin && \
|
||||||
echo 'fn main() {}' > src/bin/bal-server-actix.rs && \
|
echo 'fn main() {}' > src/bin/bal-server.rs && \
|
||||||
echo 'fn main() {}' > src/bin/bal-pusher.rs && \
|
echo 'fn main() {}' > src/bin/bal-pusher.rs && \
|
||||||
echo '' > src/lib.rs && \
|
echo '' > src/lib.rs && \
|
||||||
echo '' > src/db.rs && \
|
echo '' > src/db.rs && \
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ WELIST_URL=https://welist.example.com/api/stats
|
|||||||
- `BAL_SSL_KEY_PATH`: The path to the Ed25519 private key (`private_key.pem`) used to sign the statistics payload before sending it to the `welist` server. This is a critical secret.
|
- `BAL_SSL_KEY_PATH`: The path to the Ed25519 private key (`private_key.pem`) used to sign the statistics payload before sending it to the `welist` server. This is a critical secret.
|
||||||
- `SEND_STATS`: A boolean flag to enable the reporting of statistics to the remote `welist` server.
|
- `SEND_STATS`: A boolean flag to enable the reporting of statistics to the remote `welist` server.
|
||||||
- `WELIST_URL`: The URL to which the statistics are sent. If `SEND_STATS` is `true`, this URL must be reachable. If the server is unreachable, the pusher will log an error but might not crash (see `08_security_audit.md` for DoS analysis).
|
- `WELIST_URL`: The URL to which the statistics are sent. If `SEND_STATS` is `true`, this URL must be reachable. If the server is unreachable, the pusher will log an error but might not crash (see `08_security_audit.md` for DoS analysis).
|
||||||
|
- `BAL_PUSHER_PREFER_IPV6`: Optional boolean flag (default `false`). When set to `true`, the pusher resolves the `welist` host itself and pins the HTTP connection to its first IPv6 (AAAA) address, still using the hostname for the `Host` header and TLS SNI. This works around networks where the IPv4 route to the `welist` host is broken while IPv6 works — the default connector may otherwise pick the unreachable family and the request would stall. Leave unset unless you hit this specific connectivity problem.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -133,12 +134,13 @@ This script builds a release binary, creates a Git tag, and uploads the release
|
|||||||
```bash
|
```bash
|
||||||
# WARNING: This script contains a hardcoded secret token. Do not use it as-is for production.
|
# WARNING: This script contains a hardcoded secret token. Do not use it as-is for production.
|
||||||
```
|
```
|
||||||
- **Release Assets:** It generates a `.tar.gz` archive with the binaries, a `.sha256` checksum file, and a `.sig` GPG detached signature.
|
- **Release Assets:** It generates a `.tar.gz` archive with the binaries, a `.sha256` checksum file, and both a `.sig` GPG detached binary signature and a `.asc` ASCII-armored version.
|
||||||
- **Signature:** The release tarball is signed with the GPG key `Svātantrya <svatantrya@bitcoin-after.life>`. The script verifies that `gpg`, `sha256sum`, and `jq` are installed before proceeding.
|
- **Signature:** The release tarball is signed with the GPG key `Svātantrya <svatantrya@bitcoin-after.life>`. The script verifies that `gpg`, `sha256sum`, and `jq` are installed before proceeding.
|
||||||
- **Verification:** The release body includes instructions for verifying the checksum and signature:
|
- **Verification:** The release body includes instructions for verifying the checksum and signature (binary or ASCII-armored):
|
||||||
```bash
|
```bash
|
||||||
sha256sum -c <release>.tar.gz.sha256
|
sha256sum -c <release>.tar.gz.sha256
|
||||||
gpg --verify <release>.tar.gz.sig <release>.tar.gz
|
gpg --verify <release>.tar.gz.sig <release>.tar.gz
|
||||||
|
gpg --verify <release>.tar.gz.asc <release>.tar.gz
|
||||||
```
|
```
|
||||||
- **Security:** It also builds and uploads the binaries. The binaries should be built and signed on a separate, clean build machine, not on the production server.
|
- **Security:** It also builds and uploads the binaries. The binaries should be built and signed on a separate, clean build machine, not on the production server.
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ Regression tests: `tests/panic_regression_tests.rs` (2 tests).
|
|||||||
- Rate limiting: `actix-governor` middleware with token-bucket — configurable via `BAL_SERVER_ACTIX_PUSHTXS_PER_SEC`/`BURST` (default 1 req/s per IP with burst 5)
|
- Rate limiting: `actix-governor` middleware with token-bucket — configurable via `BAL_SERVER_ACTIX_PUSHTXS_PER_SEC`/`BURST` (default 1 req/s per IP with burst 5)
|
||||||
- Connection limits: `workers(4)` and `max_connections(100)` — configurable via `BAL_SERVER_ACTIX_WORKERS`/`MAX_CONNECTIONS`
|
- Connection limits: `workers(4)` and `max_connections(100)` — configurable via `BAL_SERVER_ACTIX_WORKERS`/`MAX_CONNECTIONS`
|
||||||
- Body timeout: configurable via `BAL_SERVER_ACTIX_TIMEOUT_SECS` (default 30s)
|
- Body timeout: configurable via `BAL_SERVER_ACTIX_TIMEOUT_SECS` (default 30s)
|
||||||
**Migration:** Server replaced `hyper` custom server with `actix-web` (see `src/bin/bal-server-actix.rs`). All handlers migrated with `Arc<Mutex<Connection>>` shared DB. Old `bal-server.rs` (Hyper) removed. `bal-pusher` enhanced with ZMQ timeout (`ZMQ_RCVTIMEO` 5000ms) and RPC retry logic. **Priority:** High. (Mitigated)
|
**Migration:** Server replaced `hyper` custom server with `actix-web` (see `src/bin/bal-server.rs`). All handlers migrated with `Arc<Mutex<Connection>>` shared DB. Old `bal-server.rs` (Hyper) removed. `bal-pusher` enhanced with ZMQ timeout (`ZMQ_RCVTIMEO` 5000ms) and RPC retry logic. **Priority:** High. (Mitigated)
|
||||||
|
|
||||||
### 5. SSRF / Network Abuse via `reqwest` (MEDIUM)
|
### 5. SSRF / Network Abuse via `reqwest` (MEDIUM)
|
||||||
**Location:** `src/bin/bal-pusher.rs`.
|
**Location:** `src/bin/bal-pusher.rs`.
|
||||||
@@ -121,7 +121,7 @@ Regression tests: `tests/panic_regression_tests.rs` (2 tests).
|
|||||||
- Rejects symlinks and non-regular files (directories, devices, etc.).
|
- Rejects symlinks and non-regular files (directories, devices, etc.).
|
||||||
- If validation fails, the function returns `Err(String)` instead of panicking, preventing crashes or accidental access to system files.
|
- If validation fails, the function returns `Err(String)` instead of panicking, preventing crashes or accidental access to system files.
|
||||||
- ✅ **WAL mode:** `db::open_db` automatically executes `PRAGMA journal_mode = WAL;` and `PRAGMA synchronous = NORMAL;` on every connection. This is a best practice for safe concurrent access when `bal-server` and `bal-pusher` share the same database file.
|
- ✅ **WAL mode:** `db::open_db` automatically executes `PRAGMA journal_mode = WAL;` and `PRAGMA synchronous = NORMAL;` on every connection. This is a best practice for safe concurrent access when `bal-server` and `bal-pusher` share the same database file.
|
||||||
- ✅ **Replaced `unwrap`:** In `src/bin/bal-server-actix.rs` and `src/bin/bal-pusher.rs`, `sqlite::open(...).unwrap()` was replaced with `db::open_db(...)` with safe error handling (return `Err` in the server, `std::process::exit(1)` in the pusher with a log error).
|
- ✅ **Replaced `unwrap`:** In `src/bin/bal-server.rs` and `src/bin/bal-pusher.rs`, `sqlite::open(...).unwrap()` was replaced with `db::open_db(...)` with safe error handling (return `Err` in the server, `std::process::exit(1)` in the pusher with a log error).
|
||||||
- **Remaining (ops):** Ensure the database file is owned by the `bal` user and not writable by any other user (`chmod 600`). The database file should not reside on a shared or network drive.
|
- **Remaining (ops):** Ensure the database file is owned by the `bal` user and not writable by any other user (`chmod 600`). The database file should not reside on a shared or network drive.
|
||||||
**Regression tests:** `tests/db_path_validation.rs` (5 tests covering traversal, forbidden absolute paths, symlink, WAL pragma, and valid relative paths). All passing.
|
**Regression tests:** `tests/db_path_validation.rs` (5 tests covering traversal, forbidden absolute paths, symlink, WAL pragma, and valid relative paths). All passing.
|
||||||
**Status:** Fixed. **Priority:** Medium.
|
**Status:** Fixed. **Priority:** Medium.
|
||||||
|
|||||||
189
make_release.sh
189
make_release.sh
@@ -1,189 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#author: <your-name>
|
|
||||||
|
|
||||||
source lib.sh
|
|
||||||
usage() {
|
|
||||||
echo_w "./make_release <version> <message>"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -n "$1" ]; then release=$1; else usage; exit; fi
|
|
||||||
|
|
||||||
if [ -n "$2" ]; then message=$2; else
|
|
||||||
# Create temporary file using mktemp
|
|
||||||
TEMPFILE=$(mktemp)
|
|
||||||
vi $TEMPFILE
|
|
||||||
message=$(cat $TEMPFILE)
|
|
||||||
rm $TEMPFILE
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo_i $message
|
|
||||||
|
|
||||||
# Load secrets from .env file (not committed to git)
|
|
||||||
if [ -f .env ]; then
|
|
||||||
export $(grep -v '^#' .env | xargs)
|
|
||||||
fi
|
|
||||||
|
|
||||||
TOKEN="${GITEA_API_TOKEN:-${TOKEN_GITEA}}"
|
|
||||||
if [ -z "$TOKEN" ]; then
|
|
||||||
echo_e "Error: GITEA_API_TOKEN (or TOKEN_GITEA) is not set in .env file."
|
|
||||||
echo_e "Please create a .env file with: GITEA_API_TOKEN=your_token_here"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for cmd in gpg sha256sum jq; do
|
|
||||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
||||||
echo_e "Error: '$cmd' is required but not installed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
SIGNER_KEY="svatantrya@bitcoin-after.life"
|
|
||||||
ASSET_SHA256="$ASSET_PATH.sha256"
|
|
||||||
ASSET_SIG="$ASSET_PATH.sig"
|
|
||||||
OWNER="bitcoinafterlife"
|
|
||||||
basename=$(basename $(pwd))
|
|
||||||
REPO=$basename
|
|
||||||
TAG="v$release"
|
|
||||||
binpath="target/release/$basename"
|
|
||||||
release_name="$basename-$release"
|
|
||||||
dest="releases/$release"
|
|
||||||
arch=$(uname -m)
|
|
||||||
platform="linux-gnu"
|
|
||||||
destbin="$dest/$arch"
|
|
||||||
destsrc="$dest/src"
|
|
||||||
assetname="$release_name""_$arch""_$platform"
|
|
||||||
|
|
||||||
asset_tar_gz="$assetname.tar.gz"
|
|
||||||
ASSET_PATH="$destbin/$assetname.tar.gz"
|
|
||||||
|
|
||||||
giteahost="https://bitcoin-after.life/gitea"
|
|
||||||
url_releases="$giteahost/api/v1/repos/$OWNER/$REPO/releases"
|
|
||||||
|
|
||||||
echo_i() {
|
|
||||||
echo -e "\033[1m==> $1\033[0m"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo_e() {
|
|
||||||
echo -e "\033[31;1m$1\033[0m"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo_s() {
|
|
||||||
echo -e "\033[32;1m$1\033[0m"
|
|
||||||
}
|
|
||||||
echo_w() {
|
|
||||||
echo -e "\033[33;1m$1\033[0m"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
prepare_release(){
|
|
||||||
mkdir -p "$destbin/$assetname"
|
|
||||||
if ! cargo build --release; then
|
|
||||||
echo_w "error building release"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
ls -l $binpath
|
|
||||||
cp target/release/bal-server \
|
|
||||||
target/release/bal-pusher \
|
|
||||||
README.md \
|
|
||||||
"$destbin/$assetname"
|
|
||||||
(
|
|
||||||
cd "$destbin"
|
|
||||||
echo_w $ASSET_PATH
|
|
||||||
echo "ls $(pwd)"
|
|
||||||
ls
|
|
||||||
echo "ls $(pwd)/$assetname"
|
|
||||||
ls "$(pwd)/$assetname"
|
|
||||||
ls $assetname
|
|
||||||
tar -czf "$asset_tar_gz" "$assetname"
|
|
||||||
|
|
||||||
sha256sum "$asset_tar_gz" > "$asset_tar_gz.sha256"
|
|
||||||
|
|
||||||
if ! gpg --batch --yes --detach-sign --local-user "$SIGNER_KEY" "$asset_tar_gz"; then
|
|
||||||
echo_e "error signing release tarball"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
push_tag() {
|
|
||||||
git commit -am"release: $release_name"
|
|
||||||
git push
|
|
||||||
#git tag -a "$TAG" -m"release: $release_name"
|
|
||||||
#git push origin --tags
|
|
||||||
}
|
|
||||||
|
|
||||||
# Configurazioni
|
|
||||||
post_release() {
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo_e "no data to release"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "data: $1"
|
|
||||||
fi
|
|
||||||
echo "token:$TOKEN"
|
|
||||||
echo url_releases: $url_releases
|
|
||||||
RELEASE="$(curl -s -X POST \
|
|
||||||
-H "accept: application/json" \
|
|
||||||
-H "Authorization: token $TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$1" \
|
|
||||||
$url_releases
|
|
||||||
)"
|
|
||||||
echo $RELEASE
|
|
||||||
}
|
|
||||||
|
|
||||||
add_asset_release() {
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo_e "error add_asset_release"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local assets=("$ASSET_PATH" "$ASSET_SHA256" "$ASSET_SIG")
|
|
||||||
for asset in "${assets[@]}"; do
|
|
||||||
if [ ! -f "$asset" ]; then
|
|
||||||
echo_e "missing asset: $asset"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Uploading: $asset"
|
|
||||||
ls -l "$asset"
|
|
||||||
curl -X POST \
|
|
||||||
-H "accept: application/json" \
|
|
||||||
-H "Authorization: token $TOKEN" \
|
|
||||||
-H "Content-Type: multipart/form-data" \
|
|
||||||
-F "attachment=@$asset" \
|
|
||||||
"$url_releases/$1/assets"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
release_body=$(printf '%s' "Release: $release_name enjoy
|
|
||||||
|
|
||||||
$message
|
|
||||||
|
|
||||||
---
|
|
||||||
Verification Instructions:
|
|
||||||
|
|
||||||
SHA256 Checksum:
|
|
||||||
sha256sum -c $assetname.tar.gz.sha256
|
|
||||||
|
|
||||||
GPG Signature:
|
|
||||||
gpg --verify $assetname.tar.gz.sig $assetname.tar.gz
|
|
||||||
|
|
||||||
Signed by Svātantrya (svatantrya@bitcoin-after.life)")
|
|
||||||
|
|
||||||
release_data=$(jq -n -c \
|
|
||||||
--arg tag_name "$TAG" \
|
|
||||||
--arg name "$release_name" \
|
|
||||||
--arg body "$release_body" \
|
|
||||||
'{tag_name: $tag_name, name: $name, body: $body}')
|
|
||||||
|
|
||||||
prepare_release
|
|
||||||
echo_s "prepare release done"
|
|
||||||
push_tag
|
|
||||||
echo_s "push tag done"
|
|
||||||
echo "$release_data"
|
|
||||||
post_release "$release_data"
|
|
||||||
echo_s "prepare release done"
|
|
||||||
echo $RELEASE
|
|
||||||
id_release=
|
|
||||||
add_asset_release $(echo $RELEASE | jq .id)
|
|
||||||
echo_s "done"
|
|
||||||
@@ -29,7 +29,9 @@ use openssl::sign::Signer;
|
|||||||
use openssl::sign::Verifier;
|
use openssl::sign::Verifier;
|
||||||
use reqwest::Client as rClient;
|
use reqwest::Client as rClient;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
const LOCKTIME_THRESHOLD: i64 = 5000000;
|
const LOCKTIME_THRESHOLD: i64 = 5000000;
|
||||||
const VERSION: &str = "0.0.2";
|
const VERSION: &str = "0.0.2";
|
||||||
@@ -331,8 +333,14 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<(
|
|||||||
stmt.bind((2, Value::String(txid.clone()))).unwrap();
|
stmt.bind((2, Value::String(txid.clone()))).unwrap();
|
||||||
let _ = stmt.next();
|
let _ = stmt.next();
|
||||||
}
|
}
|
||||||
let _ = send_stats_report(cfg, bcinfo).await;
|
if let Err(e) = send_stats_report(cfg, bcinfo).await {
|
||||||
let _ = calculate_stats(&db, network_params.db_field.clone()).await;
|
// Never discard silently: a failing report is otherwise
|
||||||
|
// invisible in the logs and can go unnoticed for a long time.
|
||||||
|
warn!("send_stats_report failed: {e}");
|
||||||
|
}
|
||||||
|
if let Err(e) = calculate_stats(&db, network_params.db_field.clone()).await {
|
||||||
|
warn!("calculate_stats failed: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(erx) => {
|
Err(erx) => {
|
||||||
error!("impossible to get client: {}, retrying on next block", erx);
|
error!("impossible to get client: {}, retrying on next block", erx);
|
||||||
@@ -422,6 +430,76 @@ ON CONFLICT(chain) DO UPDATE SET
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
/// Parse the `(host, port)` pair from a base URL like `https://host[:port]`.
|
||||||
|
///
|
||||||
|
/// Falls back to the scheme's well-known default port (443 for `https`,
|
||||||
|
/// 80 for plain `http`), or to 443 when the scheme is unknown.
|
||||||
|
fn parse_host_port(base_url: &str) -> Option<(String, u16)> {
|
||||||
|
let url = Url::parse(base_url).ok()?;
|
||||||
|
let host = url
|
||||||
|
.host_str()?
|
||||||
|
.trim_start_matches('[')
|
||||||
|
.trim_end_matches(']')
|
||||||
|
.to_string();
|
||||||
|
let port = url.port_or_known_default().unwrap_or(443);
|
||||||
|
Some((host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve `host:port` and return the first IPv6 (AAAA) address, if any.
|
||||||
|
///
|
||||||
|
/// Returns `None` when the host has no IPv6 address.
|
||||||
|
async fn resolve_first_ipv6(host: &str, port: u16) -> Option<SocketAddr> {
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
|
let host = host.to_string();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
format!("{}:{}", host, port)
|
||||||
|
.to_socket_addrs()
|
||||||
|
.ok()
|
||||||
|
.and_then(|mut addrs| addrs.find(|a| a.is_ipv6()))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the HTTP client used for welist reports.
|
||||||
|
///
|
||||||
|
/// When `BAL_PUSHER_PREFER_IPV6` is truthy, the welist host is resolved and
|
||||||
|
/// the client is pinned to its first IPv6 address (the original hostname is
|
||||||
|
/// still used for the `Host` header and TLS SNI). This works around networks
|
||||||
|
/// where the IPv4 route to the welist host is broken while IPv6 works: the
|
||||||
|
/// default connector may otherwise pick the broken family and the request
|
||||||
|
/// stalls. When the variable is unset (the default), behavior is unchanged.
|
||||||
|
async fn welist_http_client(welist_url: &str) -> rClient {
|
||||||
|
let prefer_ipv6 = env::var("BAL_PUSHER_PREFER_IPV6")
|
||||||
|
.unwrap_or("false".to_string())
|
||||||
|
.parse::<bool>()
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !prefer_ipv6 {
|
||||||
|
return rClient::new();
|
||||||
|
}
|
||||||
|
let (host, port) = match parse_host_port(welist_url) {
|
||||||
|
Some(hp) => hp,
|
||||||
|
None => {
|
||||||
|
warn!("BAL_PUSHER_PREFER_IPV6: cannot parse '{welist_url}', using default resolver");
|
||||||
|
return rClient::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match resolve_first_ipv6(&host, port).await {
|
||||||
|
Some(addr) => {
|
||||||
|
debug!("BAL_PUSHER_PREFER_IPV6: pinning {host} to {addr}");
|
||||||
|
rClient::builder()
|
||||||
|
.resolve(&host, addr)
|
||||||
|
.build()
|
||||||
|
.unwrap_or_else(|_| rClient::new())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
debug!("BAL_PUSHER_PREFER_IPV6: no IPv6 address for {host}, using default resolver");
|
||||||
|
rClient::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_stats_report(
|
async fn send_stats_report(
|
||||||
cfg: &MyConfig,
|
cfg: &MyConfig,
|
||||||
bcinfo: GetBlockchainInfoResult,
|
bcinfo: GetBlockchainInfoResult,
|
||||||
@@ -437,7 +515,7 @@ async fn send_stats_report(
|
|||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let client = rClient::new();
|
let client = welist_http_client(&welist_url).await;
|
||||||
let url = format!("{}/ping", welist_url);
|
let url = format!("{}/ping", welist_url);
|
||||||
debug!("welist url: {}", url);
|
debug!("welist url: {}", url);
|
||||||
let chain = bcinfo.chain.to_string().to_lowercase();
|
let chain = bcinfo.chain.to_string().to_lowercase();
|
||||||
@@ -724,3 +802,45 @@ fn seq_to_str(seq: &Vec<u8>) -> String {
|
|||||||
}
|
}
|
||||||
"Unknown".to_string()
|
"Unknown".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_host_port_https_default_port() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_host_port("https://welist.bitcoin-after.life"),
|
||||||
|
Some(("welist.bitcoin-after.life".to_string(), 443))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_host_port_explicit_port_and_path() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_host_port("https://example.com:8443/ping"),
|
||||||
|
Some(("example.com".to_string(), 8443))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_host_port_http_default_port() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_host_port("http://example.com"),
|
||||||
|
Some(("example.com".to_string(), 80))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_host_port_ipv6_literal_brackets_stripped() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_host_port("https://[2a13:2c0::1]:443"),
|
||||||
|
Some(("2a13:2c0::1".to_string(), 443))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_host_port_invalid_url() {
|
||||||
|
assert_eq!(parse_host_port("not a url"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user