Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dbcd7d07c | |||
| 60c21a8d1d | |||
| feefde9022 | |||
| 645be5fa22 | |||
| ac56717b23 | |||
| ec502f46f0 | |||
| 4a9a5579c4 | |||
| 96180275f8 | |||
| ab20260a2f | |||
| 889d464e98 | |||
| 56206433e6 | |||
| 8e713d43e1 | |||
| 35aa7e338d | |||
| 2a60b2f057 | |||
| dcde4c8df1 | |||
| fcaa32284b | |||
| fa72f90bfa | |||
| 12b9aefa99 |
@@ -21,7 +21,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ arc-ubuntu-20.04 ]
|
||||
platform: [ arc-ubuntu-22.04 ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
|
||||
@@ -4,6 +4,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.13-magura-drift] (2024-11-29)
|
||||
|
||||
- Optimised syncing bandwidth information to storage
|
||||
|
||||
## [2024.13-magura-patched] (2024-11-22)
|
||||
|
||||
- [experimental] allow clients to change between deterministic route selection based on packet headers and a pseudorandom distribution
|
||||
|
||||
Generated
+14
-9
@@ -2428,7 +2428,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.42"
|
||||
version = "1.1.43"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.5.20",
|
||||
@@ -4451,7 +4451,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.46"
|
||||
version = "1.1.47"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4701,7 +4701,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.44"
|
||||
version = "1.1.45"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -4782,7 +4782,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.44"
|
||||
version = "1.1.45"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.20",
|
||||
@@ -5093,7 +5093,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.3"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5113,6 +5113,7 @@ dependencies = [
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-http-api-common",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
@@ -5620,12 +5621,15 @@ dependencies = [
|
||||
"axum-client-ip",
|
||||
"bytes",
|
||||
"colored",
|
||||
"futures",
|
||||
"mime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tower 0.4.13",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5899,7 +5903,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.45"
|
||||
version = "1.1.46"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -5950,7 +5954,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -6306,7 +6310,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.44"
|
||||
version = "1.1.45"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.20",
|
||||
@@ -6886,6 +6890,7 @@ dependencies = [
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"x25519-dalek",
|
||||
@@ -6908,7 +6913,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.44"
|
||||
version = "1.1.45"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.44"
|
||||
version = "1.1.45"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -17,7 +17,7 @@ use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::EcashSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, EcashQueryClient};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct TopologyReadPermit<'a> {
|
||||
permit: RwLockReadGuard<'a, Option<NymTopology>>,
|
||||
}
|
||||
|
||||
impl<'a> Deref for TopologyReadPermit<'a> {
|
||||
impl Deref for TopologyReadPermit<'_> {
|
||||
type Target = Option<NymTopology>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Div<GasPrice> for Coin {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Div<GasPrice> for &'a Coin {
|
||||
impl Div<GasPrice> for &Coin {
|
||||
type Output = Gas;
|
||||
|
||||
fn div(self, rhs: GasPrice) -> Self::Output {
|
||||
|
||||
@@ -13,6 +13,44 @@ use tracing::error;
|
||||
|
||||
pub use cosmrs::abci::MsgResponse;
|
||||
|
||||
pub fn parse_singleton_u32_from_contract_response(b: Vec<u8>) -> Result<u32, NyxdError> {
|
||||
if b.len() != 4 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 4,
|
||||
});
|
||||
}
|
||||
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
pub fn parse_singleton_u64_from_contract_response(b: Vec<u8>) -> Result<u64, NyxdError> {
|
||||
if b.len() != 8 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 8,
|
||||
});
|
||||
}
|
||||
Ok(u64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParsedContractResponse {
|
||||
pub message_index: usize,
|
||||
pub response: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ParsedContractResponse {
|
||||
pub fn parse_singleton_u32_contract_data(self) -> Result<u32, NyxdError> {
|
||||
parse_singleton_u32_from_contract_response(self.response)
|
||||
}
|
||||
|
||||
pub fn parse_singleton_u64_contract_data(self) -> Result<u64, NyxdError> {
|
||||
parse_singleton_u64_from_contract_response(self.response)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_msg_responses(data: Bytes) -> Vec<MsgResponse> {
|
||||
// it seems that currently, on wasmd 0.43 + tendermint-rs 0.37 + cosmrs 0.17.0-pre
|
||||
// the data is left in undecoded base64 form, but I'd imagine this might change so if the decoding fails,
|
||||
@@ -34,35 +72,25 @@ pub fn parse_msg_responses(data: Bytes) -> Vec<MsgResponse> {
|
||||
}
|
||||
|
||||
// requires there's a single response message
|
||||
pub trait ToSingletonContractData: Sized {
|
||||
pub trait ContractResponseData: Sized {
|
||||
fn parse_singleton_u32_contract_data(&self) -> Result<u32, NyxdError> {
|
||||
let b = self.to_singleton_contract_data()?;
|
||||
if b.len() != 4 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 4,
|
||||
});
|
||||
}
|
||||
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
parse_singleton_u32_from_contract_response(b)
|
||||
}
|
||||
|
||||
fn parse_singleton_u64_contract_data(&self) -> Result<u64, NyxdError> {
|
||||
let b = self.to_singleton_contract_data()?;
|
||||
if b.len() != 8 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 8,
|
||||
});
|
||||
}
|
||||
Ok(u64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
parse_singleton_u64_from_contract_response(b)
|
||||
}
|
||||
|
||||
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError>;
|
||||
|
||||
fn to_unchecked_contract_data(&self) -> Result<Vec<Vec<u8>>, NyxdError>;
|
||||
|
||||
fn to_contract_data(&self) -> Result<Vec<ParsedContractResponse>, NyxdError>;
|
||||
}
|
||||
|
||||
impl ToSingletonContractData for ExecuteResult {
|
||||
impl ContractResponseData for ExecuteResult {
|
||||
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError> {
|
||||
if self.msg_responses.len() != 1 {
|
||||
return Err(NyxdError::UnexpectedNumberOfMsgResponses {
|
||||
@@ -72,6 +100,30 @@ impl ToSingletonContractData for ExecuteResult {
|
||||
|
||||
self.msg_responses[0].to_contract_response_data()
|
||||
}
|
||||
|
||||
fn to_unchecked_contract_data(&self) -> Result<Vec<Vec<u8>>, NyxdError> {
|
||||
self.msg_responses
|
||||
.iter()
|
||||
.map(ToContractResponseData::to_contract_response_data)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_contract_data(&self) -> Result<Vec<ParsedContractResponse>, NyxdError> {
|
||||
let mut response = Vec::new();
|
||||
|
||||
for (message_index, msg) in self.msg_responses.iter().enumerate() {
|
||||
// unfortunately `Name` trait has not been derived for `MsgExecuteContractResponse`,
|
||||
// so we have to make an explicit string comparison instead
|
||||
if msg.type_url == "/cosmwasm.wasm.v1.MsgExecuteContractResponse" {
|
||||
response.push(ParsedContractResponse {
|
||||
message_index,
|
||||
response: msg.to_contract_response_data()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToContractResponseData: Sized {
|
||||
|
||||
@@ -23,7 +23,7 @@ use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::query::Query;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest};
|
||||
|
||||
pub use helpers::{ToContractResponseData, ToSingletonContractData};
|
||||
pub use helpers::{ContractResponseData, ToContractResponseData};
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::http_client;
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct GasPrice {
|
||||
pub denom: String,
|
||||
}
|
||||
|
||||
impl<'a> Mul<Gas> for &'a GasPrice {
|
||||
impl Mul<Gas> for &GasPrice {
|
||||
type Output = Coin;
|
||||
|
||||
fn mul(self, gas_limit: Gas) -> Self::Output {
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(crate) mod string_rfc3339_offset_date_time {
|
||||
|
||||
struct Rfc3339OffsetDateTimeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for Rfc3339OffsetDateTimeVisitor {
|
||||
impl Visitor<'_> for Rfc3339OffsetDateTimeVisitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -111,7 +111,7 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
async fn sync_storage_bandwidth(&mut self) -> Result<()> {
|
||||
pub async fn sync_storage_bandwidth(&mut self) -> Result<()> {
|
||||
trace!("syncing client bandwidth with the underlying storage");
|
||||
let updated = self
|
||||
.storage
|
||||
|
||||
@@ -8,8 +8,8 @@ use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_secs(5 * 60); // 5 minutes
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BandwidthFlushingBehaviourConfig {
|
||||
|
||||
@@ -18,7 +18,7 @@ use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::cw3::Status;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
|
||||
@@ -26,9 +26,8 @@ const PARALLEL_RUNS: usize = 32;
|
||||
/// `lambda` ($\lambda$) in the DKG paper
|
||||
const SECURITY_PARAMETER: usize = 256;
|
||||
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
/// ceil(SECURITY_PARAMETER / PARALLEL_RUNS) in the paper
|
||||
const NUM_CHALLENGE_BITS: usize = (SECURITY_PARAMETER + PARALLEL_RUNS - 1) / PARALLEL_RUNS;
|
||||
const NUM_CHALLENGE_BITS: usize = SECURITY_PARAMETER.div_ceil(PARALLEL_RUNS);
|
||||
|
||||
// type alias for ease of use
|
||||
type FirstChallenge = Vec<Vec<Vec<u64>>>;
|
||||
|
||||
@@ -196,7 +196,7 @@ impl<'b> Add<&'b Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add<Polynomial> for &'a Polynomial {
|
||||
impl Add<Polynomial> for &Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: Polynomial) -> Polynomial {
|
||||
@@ -212,10 +212,10 @@ impl Add<Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<&'b Polynomial> for &'a Polynomial {
|
||||
impl<'a> Add<&'a Polynomial> for &Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: &'b Polynomial) -> Self::Output {
|
||||
fn add(self, rhs: &'a Polynomial) -> Self::Output {
|
||||
let len = self.coefficients.len();
|
||||
let rhs_len = rhs.coefficients.len();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl<'a> Future for GatewayHandshake<'a> {
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
|
||||
@@ -15,12 +15,15 @@ axum-client-ip.workspace = true
|
||||
axum.workspace = true
|
||||
bytes = { workspace = true }
|
||||
colored.workspace = true
|
||||
futures = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_yaml = { workspace = true }
|
||||
tower = { workspace = true }
|
||||
tracing.workspace = true
|
||||
utoipa = { workspace = true, optional = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[features]
|
||||
utoipa = ["dep:utoipa"]
|
||||
|
||||
@@ -7,7 +7,7 @@ use axum::Json;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod logging;
|
||||
pub mod middleware;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use axum::http::{header, HeaderValue, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use axum::extract::Request;
|
||||
use axum::http::header::{HOST, USER_AGENT};
|
||||
@@ -11,6 +11,7 @@ use colored::Colorize;
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
|
||||
/// Simple logger for requests
|
||||
pub async fn logger(
|
||||
InsecureClientIp(addr): InsecureClientIp,
|
||||
request: Request,
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod bearer_auth;
|
||||
pub mod logging;
|
||||
@@ -324,18 +324,6 @@ pub fn unchecked_aggregate_indices_signatures(
|
||||
_aggregate_indices_signatures(params, vk, signatures_shares, false)
|
||||
}
|
||||
|
||||
/// Generates parameters for the scheme setup.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `total_coins` - it is the number of coins in a freshly generated wallet. It is the public parameter of the scheme.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Parameters` struct containing group parameters, public key, the number of signatures (`total_coins`),
|
||||
/// and a map of signatures for each index `l`.
|
||||
///
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -264,7 +264,7 @@ impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
|
||||
impl Mul<Scalar> for &VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -984,7 +984,7 @@ pub struct SerialNumberRef<'a> {
|
||||
pub(crate) inner: &'a [G1Projective],
|
||||
}
|
||||
|
||||
impl<'a> SerialNumberRef<'a> {
|
||||
impl SerialNumberRef<'_> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ss_len = self.inner.len();
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
|
||||
|
||||
@@ -206,10 +206,10 @@ impl Deref for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Mul<&'b Scalar> for &'a PublicKey {
|
||||
impl<'a> Mul<&'a Scalar> for &PublicKey {
|
||||
type Output = G1Projective;
|
||||
|
||||
fn mul(self, rhs: &'b Scalar) -> Self::Output {
|
||||
fn mul(self, rhs: &'a Scalar) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKey {
|
||||
impl Mul<Scalar> for &VerificationKey {
|
||||
type Output = VerificationKey;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient {
|
||||
{
|
||||
struct RecipientVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RecipientVisitor {
|
||||
impl Visitor<'_> for RecipientVisitor {
|
||||
type Value = Recipient;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Encodoing and decoding node routing information.
|
||||
//!
|
||||
//! This module is responsible for encoding and decoding node routing information, so that
|
||||
//! they could be later put into an appropriate field in a sphinx header.
|
||||
//! Currently, that routing information is an IP address, but in principle it can be anything
|
||||
//! for as long as it's going to fit in the field.
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH};
|
||||
|
||||
@@ -12,13 +18,6 @@ use thiserror::Error;
|
||||
pub type NodeIdentity = identity::PublicKey;
|
||||
pub const NODE_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH;
|
||||
|
||||
/// Encodoing and decoding node routing information.
|
||||
///
|
||||
/// This module is responsible for encoding and decoding node routing information, so that
|
||||
/// they could be later put into an appropriate field in a sphinx header.
|
||||
/// Currently, that routing information is an IP address, but in principle it can be anything
|
||||
/// for as long as it's going to fit in the field.
|
||||
|
||||
/// MAX_UNPADDED_LEN represents maximum length an unpadded address could have.
|
||||
/// In this case it's an ipv6 socket address (with version prefix)
|
||||
pub const MAX_NODE_ADDRESS_UNPADDED_LEN: usize = 19;
|
||||
|
||||
@@ -56,7 +56,7 @@ impl<'de> Deserialize<'de> for ReplySurb {
|
||||
{
|
||||
struct ReplySurbVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ReplySurbVisitor {
|
||||
impl Visitor<'_> for ReplySurbVisitor {
|
||||
type Value = ReplySurb;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -253,25 +253,15 @@ impl Socks5RequestContent {
|
||||
/// Deserialize the request type, connection id, destination address and port,
|
||||
/// and the request body from bytes.
|
||||
///
|
||||
// TODO: this was already inaccurate
|
||||
// /// Serialized bytes looks like this:
|
||||
// ///
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
// /// request_flag | connection_id | address_length | remote_address_bytes | request_data |
|
||||
// /// 1 | 8 | 2 | address_length | ... |
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
///
|
||||
/// The request_flag tells us whether this is a new connection request (`new_connect`),
|
||||
/// an already-established connection we should send up (`new_send`), or
|
||||
/// a request to close an established connection (`new_close`).
|
||||
|
||||
// connect:
|
||||
// RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || <RETURN_ADDR>
|
||||
//
|
||||
// send:
|
||||
// RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA
|
||||
// where DATA: SEQ || TRUE_DATA
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
|
||||
// each request needs to at least contain flag and ConnectionId
|
||||
if b.is_empty() {
|
||||
|
||||
@@ -26,6 +26,7 @@ log.workspace = true
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-stream = { workspace = true }
|
||||
time = { workspace = true }
|
||||
|
||||
nym-authenticator-requests = { path = "../authenticator-requests" }
|
||||
nym-credential-verification = { path = "../credential-verification" }
|
||||
|
||||
@@ -20,6 +20,7 @@ use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
pub(crate) mod error;
|
||||
pub mod peer_controller;
|
||||
pub mod peer_handle;
|
||||
pub mod peer_storage_manager;
|
||||
|
||||
pub struct WgApiWrapper {
|
||||
inner: WGApi,
|
||||
@@ -118,7 +119,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
storage
|
||||
.insert_wireguard_peer(peer, bandwidth_manager.is_some())
|
||||
.await?;
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager);
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
|
||||
}
|
||||
wg_api.create_interface()?;
|
||||
let interface_config = InterfaceConfiguration {
|
||||
|
||||
@@ -20,9 +20,9 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::peer_handle::PeerHandle;
|
||||
use crate::WgApiWrapper;
|
||||
use crate::{error::Error, peer_handle::SharedBandwidthStorageManager};
|
||||
use crate::{peer_handle::PeerHandle, peer_storage_manager::PeerStorageManager};
|
||||
|
||||
pub enum PeerControlRequest {
|
||||
AddPeer {
|
||||
@@ -79,7 +79,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
storage: St,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
initial_host_information: Host,
|
||||
bw_storage_managers: HashMap<Key, Option<SharedBandwidthStorageManager<St>>>,
|
||||
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager<St>>, Peer)>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
request_rx: mpsc::Receiver<PeerControlRequest>,
|
||||
task_client: nym_task::TaskClient,
|
||||
@@ -88,11 +88,16 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
|
||||
);
|
||||
let host_information = Arc::new(RwLock::new(initial_host_information));
|
||||
for (public_key, bandwidth_storage_manager) in bw_storage_managers.iter() {
|
||||
let mut handle = PeerHandle::new(
|
||||
for (public_key, (bandwidth_storage_manager, peer)) in bw_storage_managers.iter() {
|
||||
let peer_storage_manager = PeerStorageManager::new(
|
||||
storage.clone(),
|
||||
peer.clone(),
|
||||
bandwidth_storage_manager.is_some(),
|
||||
);
|
||||
let mut handle = PeerHandle::new(
|
||||
public_key.clone(),
|
||||
host_information.clone(),
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager.clone(),
|
||||
request_tx.clone(),
|
||||
&task_client,
|
||||
@@ -103,6 +108,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
}
|
||||
});
|
||||
}
|
||||
let bw_storage_managers = bw_storage_managers
|
||||
.into_iter()
|
||||
.map(|(k, (m, _))| (k, m))
|
||||
.collect();
|
||||
|
||||
PeerController {
|
||||
storage,
|
||||
@@ -184,10 +193,15 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
Self::generate_bandwidth_manager(self.storage.clone(), &peer.public_key)
|
||||
.await?
|
||||
.map(|bw_m| Arc::new(RwLock::new(bw_m)));
|
||||
let mut handle = PeerHandle::new(
|
||||
let peer_storage_manager = PeerStorageManager::new(
|
||||
self.storage.clone(),
|
||||
peer.clone(),
|
||||
bandwidth_storage_manager.is_some(),
|
||||
);
|
||||
let mut handle = PeerHandle::new(
|
||||
peer.public_key.clone(),
|
||||
self.host_information.clone(),
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager.clone(),
|
||||
self.request_tx.clone(),
|
||||
&self.task_client,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::peer_controller::PeerControlRequest;
|
||||
use crate::peer_storage_manager::PeerStorageManager;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use defguard_wireguard_rs::{host::Host, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
@@ -21,9 +22,9 @@ pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageM
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
pub struct PeerHandle<St> {
|
||||
storage: St,
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
@@ -33,9 +34,9 @@ pub struct PeerHandle<St> {
|
||||
|
||||
impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
pub fn new(
|
||||
storage: St,
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
task_client: &TaskClient,
|
||||
@@ -46,9 +47,9 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
let mut task_client = task_client.fork(format!("peer-{public_key}"));
|
||||
task_client.disarm();
|
||||
PeerHandle {
|
||||
storage,
|
||||
public_key,
|
||||
host_information,
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager,
|
||||
request_tx,
|
||||
timeout_check_interval,
|
||||
@@ -84,16 +85,19 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
.ok_or(Error::InconsistentConsumedBytes)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::InconsistentConsumedBytes)?;
|
||||
if spent_bandwidth > 0
|
||||
&& bandwidth_manager
|
||||
if spent_bandwidth > 0 {
|
||||
self.peer_storage_manager.update_trx(kernel_peer);
|
||||
if bandwidth_manager
|
||||
.write()
|
||||
.await
|
||||
.try_use_bandwidth(spent_bandwidth)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let success = self.remove_peer().await?;
|
||||
return Ok(!success);
|
||||
{
|
||||
let success = self.remove_peer().await?;
|
||||
self.peer_storage_manager.remove_peer();
|
||||
return Ok(!success);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
|
||||
@@ -132,7 +136,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
// the host information hasn't beed updated yet
|
||||
continue;
|
||||
};
|
||||
let Some(storage_peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else {
|
||||
let Some(storage_peer) = self.peer_storage_manager.get_peer() else {
|
||||
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
};
|
||||
@@ -141,12 +145,18 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
return Ok(());
|
||||
} else {
|
||||
// Update storage values
|
||||
self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?;
|
||||
self.peer_storage_manager.sync_storage_peer().await?;
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.task_client.recv() => {
|
||||
log::trace!("PeerHandle: Received shutdown");
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
if let Err(e) = bandwidth_manager.write().await.sync_storage_bandwidth().await {
|
||||
log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed");
|
||||
}
|
||||
}
|
||||
log::trace!("PeerHandle: Finished shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_gateway_storage::Storage;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
const DEFAULT_PEER_MAX_FLUSHING_RATE: Duration = Duration::from_secs(60 * 60 * 24); // 24h
|
||||
const DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT: u64 = 512 * 1024 * 1024; // 512MB
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PeerFlushingBehaviourConfig {
|
||||
/// Defines maximum delay between peer information being flushed to the persistent storage.
|
||||
pub peer_max_flushing_rate: Duration,
|
||||
|
||||
/// Defines a maximum change in peer before it gets flushed to the persistent storage.
|
||||
pub peer_max_delta_flushing_amount: u64,
|
||||
}
|
||||
|
||||
impl Default for PeerFlushingBehaviourConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
peer_max_flushing_rate: DEFAULT_PEER_MAX_FLUSHING_RATE,
|
||||
peer_max_delta_flushing_amount: DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PeerStorageManager<S> {
|
||||
pub(crate) storage: S,
|
||||
pub(crate) peer_information: Option<PeerInformation>,
|
||||
pub(crate) cfg: PeerFlushingBehaviourConfig,
|
||||
pub(crate) with_client_id: bool,
|
||||
}
|
||||
|
||||
impl<S: Storage + Clone + 'static> PeerStorageManager<S> {
|
||||
pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self {
|
||||
let peer_information = Some(PeerInformation::new(peer));
|
||||
Self {
|
||||
storage,
|
||||
peer_information,
|
||||
cfg: PeerFlushingBehaviourConfig::default(),
|
||||
with_client_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_peer(&self) -> Option<WireguardPeer> {
|
||||
self.peer_information
|
||||
.as_ref()
|
||||
.map(|p| p.peer.clone().into())
|
||||
}
|
||||
|
||||
pub(crate) fn remove_peer(&mut self) {
|
||||
self.peer_information = None;
|
||||
}
|
||||
|
||||
pub(crate) fn update_trx(&mut self, kernel_peer: &Peer) {
|
||||
if let Some(peer_information) = self.peer_information.as_mut() {
|
||||
peer_information.update_trx_bytes(kernel_peer.tx_bytes, kernel_peer.rx_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn sync_storage_peer(&mut self) -> Result<(), Error> {
|
||||
let Some(peer_information) = self.peer_information.as_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
if !peer_information.should_sync(self.cfg) {
|
||||
return Ok(());
|
||||
}
|
||||
if self
|
||||
.storage
|
||||
.get_wireguard_peer(&peer_information.peer().public_key.to_string())
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
self.peer_information = None;
|
||||
return Ok(());
|
||||
}
|
||||
self.storage
|
||||
.insert_wireguard_peer(peer_information.peer(), self.with_client_id)
|
||||
.await?;
|
||||
|
||||
peer_information.resync_peer_with_storage();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PeerInformation {
|
||||
pub(crate) peer: Peer,
|
||||
pub(crate) last_synced: OffsetDateTime,
|
||||
|
||||
pub(crate) bytes_delta_since_sync: u64,
|
||||
}
|
||||
|
||||
impl PeerInformation {
|
||||
pub fn new(peer: Peer) -> PeerInformation {
|
||||
PeerInformation {
|
||||
peer,
|
||||
last_synced: OffsetDateTime::now_utc(),
|
||||
bytes_delta_since_sync: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn should_sync(&self, cfg: PeerFlushingBehaviourConfig) -> bool {
|
||||
if self.bytes_delta_since_sync >= cfg.peer_max_delta_flushing_amount {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.last_synced + cfg.peer_max_flushing_rate < OffsetDateTime::now_utc()
|
||||
&& self.bytes_delta_since_sync != 0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn peer(&self) -> &Peer {
|
||||
&self.peer
|
||||
}
|
||||
|
||||
pub(crate) fn update_trx_bytes(&mut self, tx_bytes: u64, rx_bytes: u64) {
|
||||
self.bytes_delta_since_sync += tx_bytes.saturating_sub(self.peer.tx_bytes)
|
||||
+ rx_bytes.saturating_sub(self.peer.rx_bytes);
|
||||
self.peer.tx_bytes = tx_bytes;
|
||||
self.peer.rx_bytes = rx_bytes;
|
||||
}
|
||||
|
||||
pub(crate) fn resync_peer_with_storage(&mut self) {
|
||||
self.bytes_delta_since_sync = 0;
|
||||
self.last_synced = OffsetDateTime::now_utc();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.42"
|
||||
version = "1.1.43"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ impl GeoIp {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&City<'a>> for Location {
|
||||
impl TryFrom<&City<'_>> for Location {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(city: &City) -> Result<Self, Self::Error> {
|
||||
|
||||
@@ -65,7 +65,7 @@ impl<'r> FromRequest<'r> for Location {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OpenApiFromRequest<'a> for Location {
|
||||
impl OpenApiFromRequest<'_> for Location {
|
||||
fn from_request_input(
|
||||
_gen: &mut OpenApiGenerator,
|
||||
_name: String,
|
||||
|
||||
+46
-12
@@ -5,6 +5,8 @@ use async_trait::async_trait;
|
||||
use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
|
||||
use nym_topology::{gateway, NymTopology, TopologyProvider};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
@@ -17,6 +19,7 @@ pub struct GatewayTopologyProvider {
|
||||
impl GatewayTopologyProvider {
|
||||
pub fn new(
|
||||
gateway_node: gateway::LegacyNode,
|
||||
cache_ttl: Duration,
|
||||
user_agent: UserAgent,
|
||||
nym_api_url: Vec<Url>,
|
||||
) -> GatewayTopologyProvider {
|
||||
@@ -31,6 +34,9 @@ impl GatewayTopologyProvider {
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
Some(user_agent),
|
||||
),
|
||||
cache_ttl,
|
||||
cached_at: OffsetDateTime::UNIX_EPOCH,
|
||||
cached: None,
|
||||
gateway_node,
|
||||
})),
|
||||
}
|
||||
@@ -39,25 +45,53 @@ impl GatewayTopologyProvider {
|
||||
|
||||
struct GatewayTopologyProviderInner {
|
||||
inner: NymApiTopologyProvider,
|
||||
cache_ttl: Duration,
|
||||
cached_at: OffsetDateTime,
|
||||
cached: Option<NymTopology>,
|
||||
gateway_node: gateway::LegacyNode,
|
||||
}
|
||||
|
||||
impl GatewayTopologyProviderInner {
|
||||
fn cached_topology(&self) -> Option<NymTopology> {
|
||||
if let Some(cached_topology) = &self.cached {
|
||||
if self.cached_at + self.cache_ttl > OffsetDateTime::now_utc() {
|
||||
return Some(cached_topology.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn update_cache(&mut self) -> Option<NymTopology> {
|
||||
let updated_cache = match self.inner.get_new_topology().await {
|
||||
None => None,
|
||||
Some(mut base) => {
|
||||
if !base.gateway_exists(&self.gateway_node.identity_key) {
|
||||
debug!(
|
||||
"{} didn't exist in topology. inserting it.",
|
||||
self.gateway_node.identity_key
|
||||
);
|
||||
base.insert_gateway(self.gateway_node.clone());
|
||||
}
|
||||
Some(base)
|
||||
}
|
||||
};
|
||||
|
||||
self.cached_at = OffsetDateTime::now_utc();
|
||||
self.cached = updated_cache.clone();
|
||||
|
||||
updated_cache
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TopologyProvider for GatewayTopologyProvider {
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
let mut guard = self.inner.lock().await;
|
||||
match guard.inner.get_new_topology().await {
|
||||
None => None,
|
||||
Some(mut base) => {
|
||||
if !base.gateway_exists(&guard.gateway_node.identity_key) {
|
||||
debug!(
|
||||
"{} didn't exist in topology. inserting it.",
|
||||
guard.gateway_node.identity_key
|
||||
);
|
||||
base.insert_gateway(guard.gateway_node.clone());
|
||||
}
|
||||
Some(base)
|
||||
}
|
||||
// check the cache
|
||||
if let Some(cached) = guard.cached_topology() {
|
||||
return Some(cached);
|
||||
}
|
||||
guard.update_cache().await
|
||||
}
|
||||
}
|
||||
|
||||
+26
-16
@@ -30,6 +30,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::*;
|
||||
|
||||
pub(crate) mod client_handling;
|
||||
@@ -148,8 +149,11 @@ impl<St> Gateway<St> {
|
||||
}
|
||||
|
||||
fn gateway_topology_provider(&self) -> GatewayTopologyProvider {
|
||||
// TODO: make topology ttl configurable
|
||||
// (to be done in reeses with the final smooshing)
|
||||
GatewayTopologyProvider::new(
|
||||
self.as_topology_node(),
|
||||
Duration::from_secs(5 * 60),
|
||||
self.user_agent.clone(),
|
||||
self.config.gateway.nym_api_urls.clone(),
|
||||
)
|
||||
@@ -231,22 +235,28 @@ impl<St> Gateway<St> {
|
||||
forwarding_channel,
|
||||
router_tx,
|
||||
);
|
||||
let all_peers = self.client_storage.get_all_wireguard_peers().await?;
|
||||
let used_private_network_ips = all_peers
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|wireguard_peer| {
|
||||
defguard_wireguard_rs::host::Peer::try_from(wireguard_peer).map(|mut peer| {
|
||||
peer.allowed_ips
|
||||
.pop()
|
||||
.ok_or(Box::new(GatewayError::InternalWireguardError(format!(
|
||||
"no private IP set for peer {}",
|
||||
peer.public_key
|
||||
))))
|
||||
.map(|p| p.ip)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Result<Vec<_>, _>, _>>()??;
|
||||
let mut used_private_network_ips = vec![];
|
||||
let mut all_peers = vec![];
|
||||
for wireguard_peer in self
|
||||
.client_storage
|
||||
.get_all_wireguard_peers()
|
||||
.await?
|
||||
.into_iter()
|
||||
{
|
||||
let mut peer = defguard_wireguard_rs::host::Peer::try_from(wireguard_peer.clone())?;
|
||||
let Some(peer) = peer.allowed_ips.pop() else {
|
||||
tracing::warn!(
|
||||
"Peer {} has empty allowed ips. It will be removed",
|
||||
peer.public_key
|
||||
);
|
||||
self.client_storage
|
||||
.remove_wireguard_peer(&peer.public_key.to_string())
|
||||
.await?;
|
||||
continue;
|
||||
};
|
||||
used_private_network_ips.push(peer.ip);
|
||||
all_peers.push(wireguard_peer);
|
||||
}
|
||||
|
||||
if let Some(wireguard_data) = self.wireguard_data.take() {
|
||||
let (on_start_tx, on_start_rx) = oneshot::channel();
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.46"
|
||||
version = "1.1.47"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
@@ -121,7 +121,7 @@ nym-types = { path = "../common/types" }
|
||||
nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] }
|
||||
nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] }
|
||||
nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" }
|
||||
nym-statistics-common = {path ="../common/statistics" }
|
||||
nym-statistics-common = { path = "../common/statistics" }
|
||||
|
||||
[features]
|
||||
no-reward = []
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE INDEX IF NOT EXISTS monitor_run_id on monitor_run(id);
|
||||
CREATE INDEX IF NOT EXISTS monitor_run_timestamp on monitor_run(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS testing_route_monitor_run_id on testing_route(monitor_run_id);
|
||||
@@ -64,7 +64,7 @@ pub(crate) mod overengineered_offset_date_time_serde {
|
||||
])),
|
||||
];
|
||||
|
||||
impl<'de> Visitor<'de> for OffsetDateTimeVisitor {
|
||||
impl Visitor<'_> for OffsetDateTimeVisitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::ecash::error::EcashError;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::Path;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -21,28 +21,19 @@ use tracing::trace;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
/// routes with globally aggregated keys, signatures, etc.
|
||||
pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
pub(crate) fn aggregation_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/master-verification-key",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| master_verification_key(epoch_id, ecash_state)
|
||||
}),
|
||||
axum::routing::get(master_verification_key),
|
||||
)
|
||||
.route(
|
||||
"/aggregated-expiration-date-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| expiration_date_signatures(expiration_date, ecash_state)
|
||||
}),
|
||||
axum::routing::get(expiration_date_signatures),
|
||||
)
|
||||
.route(
|
||||
"/aggregated-coin-indices-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| coin_indices_signatures(epoch_id, ecash_state)
|
||||
}),
|
||||
axum::routing::get(coin_indices_signatures),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,8 +49,8 @@ pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppStat
|
||||
)
|
||||
)]
|
||||
async fn master_verification_key(
|
||||
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
|
||||
state: Arc<EcashState>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
) -> AxumResult<Json<VerificationKeyResponse>> {
|
||||
trace!("aggregated_verification_key request");
|
||||
|
||||
@@ -72,7 +63,6 @@ async fn master_verification_key(
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
}
|
||||
@@ -89,8 +79,8 @@ struct ExpirationDateParam {
|
||||
)
|
||||
)]
|
||||
async fn expiration_date_signatures(
|
||||
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
|
||||
state: Arc<EcashState>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
|
||||
) -> AxumResult<Json<AggregatedExpirationDateSignatureResponse>> {
|
||||
trace!("aggregated_expiration_date_signatures request");
|
||||
|
||||
@@ -126,8 +116,8 @@ async fn expiration_date_signatures(
|
||||
)
|
||||
)]
|
||||
async fn coin_indices_signatures(
|
||||
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
|
||||
state: Arc<EcashState>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
) -> AxumResult<Json<AggregatedCoinIndicesSignatureResponse>> {
|
||||
trace!("aggregated_coin_indices_signatures request");
|
||||
|
||||
|
||||
@@ -5,15 +5,13 @@ use crate::ecash::api_routes::aggregation::aggregation_routes;
|
||||
use crate::ecash::api_routes::issued::issued_routes;
|
||||
use crate::ecash::api_routes::partial_signing::partial_signing_routes;
|
||||
use crate::ecash::api_routes::spending::spending_routes;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::Router;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) fn ecash_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
pub(crate) fn ecash_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.merge(aggregation_routes(Arc::clone(&ecash_state)))
|
||||
.merge(issued_routes(Arc::clone(&ecash_state)))
|
||||
.merge(partial_signing_routes(Arc::clone(&ecash_state)))
|
||||
.merge(spending_routes(Arc::clone(&ecash_state)))
|
||||
.merge(aggregation_routes())
|
||||
.merge(issued_routes())
|
||||
.merge(partial_signing_routes())
|
||||
.merge(spending_routes())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#[derive(serde::Deserialize, utoipa::IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
pub(super) struct EpochIdParam {
|
||||
pub(super) epoch_id: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::Path;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::ecash::models::{
|
||||
IssuedTicketbooksChallengeRequest, IssuedTicketbooksChallengeResponse,
|
||||
@@ -17,21 +17,15 @@ use time::Date;
|
||||
use tracing::trace;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
pub(crate) fn issued_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
pub(crate) fn issued_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/issued-ticketbooks-for/:expiration_date",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| issued_ticketbooks_for(expiration_date, ecash_state)
|
||||
}),
|
||||
axum::routing::get(issued_ticketbooks_for),
|
||||
)
|
||||
.route(
|
||||
"/issued-ticketbooks-challenge",
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| issued_ticketbooks_challenge(body, ecash_state)
|
||||
}),
|
||||
axum::routing::post(issued_ticketbooks_challenge),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,8 +52,8 @@ pub(crate) struct ExpirationDatePathParam {
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_for(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksForResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -83,8 +77,8 @@ async fn issued_ticketbooks_for(
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_challenge(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(challenge): Json<IssuedTicketbooksChallengeRequest>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksChallengeResponse>> {
|
||||
trace!("replying to ticketbooks challenge: {:?}", challenge);
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ecash::helpers::blind_sign;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::Query;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
@@ -22,28 +22,16 @@ use time::Date;
|
||||
use tracing::{debug, trace};
|
||||
use utoipa::IntoParams;
|
||||
|
||||
pub(crate) fn partial_signing_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
pub(crate) fn partial_signing_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/blind-sign",
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| post_blind_sign(body, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route("/blind-sign", axum::routing::post(post_blind_sign))
|
||||
.route(
|
||||
"/partial-expiration-date-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state)
|
||||
}),
|
||||
axum::routing::get(partial_expiration_date_signatures),
|
||||
)
|
||||
.route(
|
||||
"/partial-coin-indices-signatures",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state)
|
||||
}),
|
||||
axum::routing::get(partial_coin_indices_signatures),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,8 +47,8 @@ pub(crate) fn partial_signing_routes(ecash_state: Arc<EcashState>) -> Router<App
|
||||
)
|
||||
)]
|
||||
async fn post_blind_sign(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(blind_sign_request_body): Json<BlindSignRequestBody>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<BlindedSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -134,8 +122,8 @@ struct ExpirationDateParam {
|
||||
)
|
||||
)]
|
||||
async fn partial_expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -172,8 +160,8 @@ async fn partial_expiration_date_signatures(
|
||||
)
|
||||
)]
|
||||
async fn partial_coin_indices_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::ecash::error::EcashError;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
|
||||
use nym_api_requests::ecash::models::{
|
||||
@@ -21,28 +22,16 @@ use time::{OffsetDateTime, Time};
|
||||
use tracing::{error, warn};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn spending_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
pub(crate) fn spending_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/verify-ecash-ticket",
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| verify_ticket(body, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route("/verify-ecash-ticket", axum::routing::post(verify_ticket))
|
||||
.route(
|
||||
"/batch-redeem-ecash-tickets",
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| batch_redeem_tickets(body, ecash_state)
|
||||
}),
|
||||
axum::routing::post(batch_redeem_tickets),
|
||||
)
|
||||
.route(
|
||||
"/double-spending-filter-v1",
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|| double_spending_filter_v1(ecash_state)
|
||||
}),
|
||||
axum::routing::get(double_spending_filter_v1),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,9 +56,9 @@ fn reject_ticket(
|
||||
)
|
||||
)]
|
||||
async fn verify_ticket(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
// TODO in the future: make it send binary data rather than json
|
||||
Json(verify_ticket_body): Json<VerifyEcashTicketBody>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -170,9 +159,9 @@ async fn verify_ticket(
|
||||
)
|
||||
)]
|
||||
async fn batch_redeem_tickets(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
// TODO in the future: make it send binary data rather than json
|
||||
Json(batch_redeem_credentials_body): Json<BatchRedeemTicketsBody>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<EcashBatchTicketRedemptionResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -244,8 +233,6 @@ async fn batch_redeem_tickets(
|
||||
)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn double_spending_filter_v1(
|
||||
_state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted"))
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use nym_validator_client::EcashApiClient;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Client {
|
||||
async fn address(&self) -> AccountId;
|
||||
async fn address(&self) -> Result<AccountId>;
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId>;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ impl DkgClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_address(&self) -> AccountId {
|
||||
pub(crate) async fn get_address(&self) -> Result<AccountId, EcashError> {
|
||||
self.inner.address().await
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl DkgClient {
|
||||
|
||||
pub(crate) async fn group_member(&self) -> Result<MemberResponse, EcashError> {
|
||||
self.inner
|
||||
.group_member(self.get_address().await.to_string())
|
||||
.group_member(self.get_address().await?.to_string())
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ impl DkgClient {
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<ContractVKShare>, EcashError> {
|
||||
let address = self.inner.address().await;
|
||||
let address = self.inner.address().await?;
|
||||
self.get_verification_key_share(epoch_id, address).await
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ impl DkgClient {
|
||||
}
|
||||
|
||||
pub(crate) async fn get_vote(&self, proposal_id: u64) -> Result<VoteResponse, EcashError> {
|
||||
let address = self.get_address().await.to_string();
|
||||
let address = self.get_address().await?.to_string();
|
||||
self.inner.get_vote(proposal_id, address).await
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
resharing: bool,
|
||||
) -> Result<(), DealingGenerationError> {
|
||||
let dealing_state = self.state.dealing_exchange_state(epoch_id)?;
|
||||
let address = self.dkg_client.get_address().await.to_string();
|
||||
let address = self.dkg_client.get_address().await?.to_string();
|
||||
|
||||
let status = self
|
||||
.dkg_client
|
||||
@@ -259,7 +259,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
.checked_sub(1)
|
||||
.expect("resharing epoch invariant has been broken");
|
||||
|
||||
let address = self.dkg_client.get_address().await;
|
||||
let address = self.dkg_client.get_address().await?;
|
||||
Ok(self
|
||||
.dkg_client
|
||||
.dealer_in_epoch(previous_epoch_id, address.to_string())
|
||||
|
||||
@@ -494,7 +494,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
// submitted proposals and find the one with our address
|
||||
self.get_validation_proposals()
|
||||
.await?
|
||||
.get(self.dkg_client.get_address().await.as_ref())
|
||||
.get(self.dkg_client.get_address().await?.as_ref())
|
||||
.copied()
|
||||
.ok_or(KeyDerivationError::UnrecoverableProposalId)
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
};
|
||||
|
||||
// if this is our share, obviously vote for yes without spending time on verification
|
||||
if owner.as_ref() == self.dkg_client.get_address().await.as_ref() {
|
||||
if owner.as_ref() == self.dkg_client.get_address().await?.as_ref() {
|
||||
votes.insert(*proposal_id, true);
|
||||
continue;
|
||||
}
|
||||
@@ -313,7 +313,7 @@ mod tests {
|
||||
exchange_dealings(&mut controllers, false).await;
|
||||
derive_keypairs(&mut controllers, false).await;
|
||||
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await;
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await?;
|
||||
|
||||
{
|
||||
let mut guard = chain.lock().unwrap();
|
||||
@@ -365,8 +365,8 @@ mod tests {
|
||||
exchange_dealings(&mut controllers, false).await;
|
||||
derive_keypairs(&mut controllers, false).await;
|
||||
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await;
|
||||
let second_dealer = controllers[1].dkg_client.get_address().await;
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await?;
|
||||
let second_dealer = controllers[1].dkg_client.get_address().await?;
|
||||
|
||||
{
|
||||
let mut guard = chain.lock().unwrap();
|
||||
|
||||
@@ -25,6 +25,9 @@ pub enum EcashError {
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("this instance is running without on-chain signing capabilities so no transactions can be sent")]
|
||||
ChainSignerNotEnabled,
|
||||
|
||||
#[error("this operation couldn't be completed as this nym-api is not an active ecash signer")]
|
||||
NotASigner,
|
||||
|
||||
|
||||
@@ -139,7 +139,9 @@ impl EcashState {
|
||||
.local
|
||||
.active_signer
|
||||
.get_or_init(epoch_id, || async {
|
||||
let address = self.aux.client.address().await;
|
||||
let Ok(address) = self.aux.client.address().await else {
|
||||
return Ok(false);
|
||||
};
|
||||
let ecash_signers = self.aux.comm_channel.ecash_clients(epoch_id).await?;
|
||||
|
||||
// check if any ecash signers for this epoch has the same cosmos address as this api
|
||||
@@ -246,7 +248,7 @@ impl EcashState {
|
||||
let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?;
|
||||
|
||||
// let mut shares = Mutex::new(Vec::with_capacity(all_apis.len()));
|
||||
let cosmos_address = self.aux.client.address().await;
|
||||
let cosmos_address = self.aux.client.address().await.ok();
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
@@ -256,7 +258,7 @@ impl EcashState {
|
||||
|
||||
// check if we're attempting to query ourselves, in that case just get local signature
|
||||
// rather than making the http query
|
||||
let partial = if api.cosmos_address == cosmos_address {
|
||||
let partial = if Some(api.cosmos_address) == cosmos_address {
|
||||
self.partial_coin_index_signatures(Some(epoch_id))
|
||||
.await?
|
||||
.signatures
|
||||
@@ -380,7 +382,7 @@ impl EcashState {
|
||||
let all_apis = self.aux.comm_channel.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?;
|
||||
|
||||
let cosmos_address = self.aux.client.address().await;
|
||||
let cosmos_address = self.aux.client.address().await.ok();
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
@@ -390,7 +392,7 @@ impl EcashState {
|
||||
|
||||
// check if we're attempting to query ourselves, in that case just get local signature
|
||||
// rather than making the http query
|
||||
let partial = if api.cosmos_address == cosmos_address {
|
||||
let partial = if Some(api.cosmos_address) == cosmos_address {
|
||||
self.partial_expiration_date_signatures(expiration_date)
|
||||
.await?
|
||||
.signatures
|
||||
|
||||
@@ -263,7 +263,7 @@ pub(crate) struct TestingDkgController {
|
||||
|
||||
impl TestingDkgController {
|
||||
pub async fn address(&self) -> AccountId {
|
||||
self.dkg_client.get_address().await
|
||||
self.dkg_client.get_address().await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn cw_address(&self) -> Addr {
|
||||
|
||||
@@ -51,7 +51,7 @@ pub(crate) async fn initialise_dkg(controllers: &mut [TestingDkgController], res
|
||||
|
||||
// add every dealer to group contract
|
||||
for controller in controllers.iter() {
|
||||
let address = controller.dkg_client.get_address().await;
|
||||
let address = controller.dkg_client.get_address().await.unwrap();
|
||||
let mut chain = controllers[0].chain_state.lock().unwrap();
|
||||
chain.add_member(address.as_ref(), 10);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ pub(crate) async fn submit_public_keys(controllers: &mut [TestingDkgController],
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let threshold = (2 * controllers.len() as u64 + 3 - 1) / 3;
|
||||
let threshold = (2 * controllers.len() as u64).div_ceil(3);
|
||||
|
||||
let mut guard = controllers[0].chain_state.lock().unwrap();
|
||||
guard.dkg_contract.epoch.state = EpochState::DealingExchange { resharing };
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::handlers::unstable;
|
||||
use crate::node_status_api::NodeStatusCache;
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::status::ApiStatusState;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::config;
|
||||
use crate::support::http::state::{AppState, ForcedRefresh};
|
||||
@@ -524,8 +525,8 @@ impl DummyClient {
|
||||
|
||||
#[async_trait]
|
||||
impl super::client::Client for DummyClient {
|
||||
async fn address(&self) -> AccountId {
|
||||
self.validator_address.clone()
|
||||
async fn address(&self) -> Result<AccountId> {
|
||||
Ok(self.validator_address.clone())
|
||||
}
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId> {
|
||||
@@ -1262,7 +1263,7 @@ struct TestFixture {
|
||||
}
|
||||
|
||||
impl TestFixture {
|
||||
fn build_app_state(storage: NymApiStorage) -> AppState {
|
||||
fn build_app_state(storage: NymApiStorage, ecash_state: EcashState) -> AppState {
|
||||
AppState {
|
||||
forced_refresh: ForcedRefresh::new(true),
|
||||
nym_contract_cache: NymContractCache::new(),
|
||||
@@ -1275,6 +1276,8 @@ impl TestFixture {
|
||||
NymNetworkDetails::new_empty(),
|
||||
),
|
||||
node_info_cache: unstable::NodeInfoCache::default(),
|
||||
api_status: ApiStatusState::new(None),
|
||||
ecash_state: Arc::new(ecash_state),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1337,8 +1340,8 @@ impl TestFixture {
|
||||
TestFixture {
|
||||
axum: TestServer::new(
|
||||
Router::new()
|
||||
.nest("/v1/ecash", ecash_routes(Arc::new(ecash_state)))
|
||||
.with_state(Self::build_app_state(storage.clone())),
|
||||
.nest("/v1/ecash", ecash_routes())
|
||||
.with_state(Self::build_app_state(storage.clone(), ecash_state)),
|
||||
)
|
||||
.unwrap(),
|
||||
storage,
|
||||
|
||||
@@ -11,6 +11,9 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RewardingError {
|
||||
#[error("this instance is running without on-chain signing capabilities so no transactions can be sent")]
|
||||
ChainSignerNotEnabled,
|
||||
|
||||
#[error("Our account ({our_address}) is not permitted to update rewarded set and perform rewarding. The allowed address is {allowed_address}")]
|
||||
Unauthorised {
|
||||
our_address: AccountId,
|
||||
|
||||
@@ -134,9 +134,12 @@ impl EpochAdvancer {
|
||||
|
||||
let epoch_status = self.nyxd_client.get_current_epoch_status().await?;
|
||||
if !epoch_status.is_in_progress() {
|
||||
if epoch_status.being_advanced_by.as_str()
|
||||
!= self.nyxd_client.client_address().await.as_ref()
|
||||
{
|
||||
// SAFETY: before `EpochAdvancer` is started, `ensure_rewarding_permission` is called
|
||||
// which is not allowed to progress if this instance is not using a signing client
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let address = self.nyxd_client.client_address().await.unwrap();
|
||||
|
||||
if epoch_status.being_advanced_by.as_str() != address.as_ref() {
|
||||
// another nym-api is already handling
|
||||
error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by);
|
||||
return Ok(());
|
||||
@@ -318,8 +321,10 @@ impl EpochAdvancer {
|
||||
pub(crate) async fn ensure_rewarding_permission(
|
||||
nyxd_client: &Client,
|
||||
) -> Result<(), RewardingError> {
|
||||
let Some(our_address) = nyxd_client.client_address().await else {
|
||||
return Err(RewardingError::ChainSignerNotEnabled);
|
||||
};
|
||||
let allowed_address = nyxd_client.get_rewarding_validator_address().await?;
|
||||
let our_address = nyxd_client.client_address().await;
|
||||
if allowed_address != our_address {
|
||||
Err(RewardingError::Unauthorised {
|
||||
our_address,
|
||||
|
||||
@@ -59,7 +59,7 @@ impl GatewayClientHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> UnlockedGatewayClientHandle<'a> {
|
||||
impl UnlockedGatewayClientHandle<'_> {
|
||||
pub(crate) fn get_mut_unchecked(
|
||||
&mut self,
|
||||
) -> &mut GatewayClient<nyxd::Client, PersistentStorage> {
|
||||
|
||||
@@ -4,37 +4,20 @@
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::status::ApiStatusState;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse};
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_compact_ecash::Base58;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) fn api_status_routes() -> Router<AppState> {
|
||||
let api_status_state = Arc::new(ApiStatusState::new());
|
||||
|
||||
Router::new()
|
||||
.route(
|
||||
"/health",
|
||||
axum::routing::get({
|
||||
let state = Arc::clone(&api_status_state);
|
||||
|| health(state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/build-information",
|
||||
axum::routing::get({
|
||||
let state = Arc::clone(&api_status_state);
|
||||
|| build_information(state)
|
||||
}),
|
||||
)
|
||||
.route("/health", axum::routing::get(health))
|
||||
.route("/build-information", axum::routing::get(build_information))
|
||||
.route(
|
||||
"/signer-information",
|
||||
axum::routing::get({
|
||||
let state = Arc::clone(&api_status_state);
|
||||
|| signer_information(state)
|
||||
}),
|
||||
axum::routing::get(signer_information),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,7 +29,7 @@ pub(crate) fn api_status_routes() -> Router<AppState> {
|
||||
(status = 200, body = ApiHealthResponse)
|
||||
)
|
||||
)]
|
||||
async fn health(state: Arc<ApiStatusState>) -> Json<ApiHealthResponse> {
|
||||
async fn health(State(state): State<ApiStatusState>) -> Json<ApiHealthResponse> {
|
||||
let uptime = state.startup_time.elapsed();
|
||||
let health = ApiHealthResponse::new_healthy(uptime);
|
||||
Json(health)
|
||||
@@ -60,7 +43,9 @@ async fn health(state: Arc<ApiStatusState>) -> Json<ApiHealthResponse> {
|
||||
(status = 200, body = BinaryBuildInformationOwned)
|
||||
)
|
||||
)]
|
||||
async fn build_information(state: Arc<ApiStatusState>) -> Json<BinaryBuildInformationOwned> {
|
||||
async fn build_information(
|
||||
State(state): State<ApiStatusState>,
|
||||
) -> Json<BinaryBuildInformationOwned> {
|
||||
Json(state.build_information.to_owned())
|
||||
}
|
||||
|
||||
@@ -73,7 +58,7 @@ async fn build_information(state: Arc<ApiStatusState>) -> Json<BinaryBuildInform
|
||||
)
|
||||
)]
|
||||
async fn signer_information(
|
||||
state: Arc<ApiStatusState>,
|
||||
State(state): State<ApiStatusState>,
|
||||
) -> AxumResult<Json<SignerInformationResponse>> {
|
||||
let signer_state = state.signer_information.as_ref().ok_or_else(|| {
|
||||
AxumErrorResponse::internal_msg("this api does not expose zk-nym signing functionalities")
|
||||
|
||||
@@ -4,12 +4,25 @@
|
||||
use crate::ecash;
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_bin_common::build_information::BinaryBuildInformation;
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use tokio::time::Instant;
|
||||
|
||||
pub(crate) mod handlers;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ApiStatusState {
|
||||
inner: Arc<ApiStatusStateInner>,
|
||||
}
|
||||
|
||||
impl Deref for ApiStatusState {
|
||||
type Target = ApiStatusStateInner;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ApiStatusStateInner {
|
||||
startup_time: Instant,
|
||||
build_information: BinaryBuildInformation,
|
||||
signer_information: Option<SignerState>,
|
||||
@@ -27,15 +40,13 @@ pub(crate) struct SignerState {
|
||||
}
|
||||
|
||||
impl ApiStatusState {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(signer_information: Option<SignerState>) -> Self {
|
||||
ApiStatusState {
|
||||
startup_time: Instant::now(),
|
||||
build_information: bin_info!(),
|
||||
signer_information: None,
|
||||
inner: Arc::new(ApiStatusStateInner {
|
||||
startup_time: Instant::now(),
|
||||
build_information: bin_info!(),
|
||||
signer_information,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_zk_nym_signer(&mut self, signer_information: SignerState) {
|
||||
self.signer_information = Some(signer_information)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::ecash::api_routes::handlers::ecash_routes;
|
||||
use crate::ecash::client::Client;
|
||||
use crate::ecash::comm::QueryCommunicationChannel;
|
||||
use crate::ecash::dkg::controller::keys::{
|
||||
@@ -138,8 +137,6 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
let described_nodes_cache = SharedCache::<DescribedNodes>::new();
|
||||
let node_info_cache = unstable::NodeInfoCache::default();
|
||||
|
||||
let mut status_state = ApiStatusState::new();
|
||||
|
||||
let ecash_contract = nyxd_client
|
||||
.get_ecash_contract_address()
|
||||
.await
|
||||
@@ -161,8 +158,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
|
||||
// if ecash signer is enabled, there are additional constraints on the nym-api,
|
||||
// such as having sufficient token balance
|
||||
let router = if config.ecash_signer.enabled {
|
||||
let cosmos_address = nyxd_client.address().await;
|
||||
let signer_information = if config.ecash_signer.enabled {
|
||||
let cosmos_address = nyxd_client.address().await?;
|
||||
|
||||
// make sure we have some tokens to cover multisig fees
|
||||
let balance = nyxd_client.balance(&mix_denom).await?;
|
||||
@@ -177,16 +174,14 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
.clone()
|
||||
.map(|u| u.to_string())
|
||||
.unwrap_or_default();
|
||||
status_state.add_zk_nym_signer(SignerState {
|
||||
Some(SignerState {
|
||||
cosmos_address: cosmos_address.to_string(),
|
||||
identity: encoded_identity,
|
||||
announce_address,
|
||||
ecash_keypair: ecash_keypair_wrapper.clone(),
|
||||
});
|
||||
|
||||
router.nest("/v1/ecash", ecash_routes(Arc::new(ecash_state)))
|
||||
})
|
||||
} else {
|
||||
router
|
||||
None
|
||||
};
|
||||
|
||||
let router = router.with_state(AppState {
|
||||
@@ -200,6 +195,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
described_nodes_cache: described_nodes_cache.clone(),
|
||||
network_details,
|
||||
node_info_cache,
|
||||
api_status: ApiStatusState::new(signer_information),
|
||||
ecash_state: Arc::new(ecash_state),
|
||||
});
|
||||
|
||||
let task_manager = TaskManager::new(TASK_MANAGER_TIMEOUT_S);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::circulating_supply_api::handlers::circulating_supply_routes;
|
||||
use crate::ecash::api_routes::handlers::ecash_routes;
|
||||
use crate::network::handlers::nym_network_routes;
|
||||
use crate::node_status_api::handlers::node_status_routes;
|
||||
use crate::nym_contract_cache::handlers::nym_contract_cache_routes;
|
||||
@@ -16,7 +17,7 @@ use axum::response::Redirect;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use core::net::SocketAddr;
|
||||
use nym_http_api_common::logging::logger;
|
||||
use nym_http_api_common::middleware::logging::logger;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::sync::WaitForCancellationFutureOwned;
|
||||
use tower_http::cors::CorsLayer;
|
||||
@@ -62,6 +63,7 @@ impl RouterBuilder {
|
||||
.nest("/network", nym_network_routes())
|
||||
.nest("/api-status", status::handlers::api_status_routes())
|
||||
.nest("/nym-nodes", nym_node_routes())
|
||||
.nest("/ecash", ecash_routes())
|
||||
.nest("/unstable", unstable_routes()), // CORS layer needs to be "outside" of routes
|
||||
);
|
||||
|
||||
@@ -70,6 +72,7 @@ impl RouterBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn nest(self, path: &str, router: Router<AppState>) -> Self {
|
||||
Self {
|
||||
unfinished_router: self.unfinished_router.nest(path, router),
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::handlers::unstable;
|
||||
use crate::node_status_api::models::AxumErrorResponse;
|
||||
use crate::node_status_api::NodeStatusCache;
|
||||
use crate::nym_contract_cache::cache::{CachedRewardedSet, NymContractCache};
|
||||
use crate::status::ApiStatusState;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::caching::Cache;
|
||||
use crate::support::storage;
|
||||
use axum::extract::FromRef;
|
||||
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_task::TaskManager;
|
||||
@@ -80,6 +83,21 @@ pub(crate) struct AppState {
|
||||
pub(crate) described_nodes_cache: SharedCache<DescribedNodes>,
|
||||
pub(crate) network_details: NetworkDetails,
|
||||
pub(crate) node_info_cache: unstable::NodeInfoCache,
|
||||
pub(crate) api_status: ApiStatusState,
|
||||
// todo: refactor it into inner: Arc<EcashStateInner>
|
||||
pub(crate) ecash_state: Arc<EcashState>,
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for ApiStatusState {
|
||||
fn from_ref(app_state: &AppState) -> Self {
|
||||
app_state.api_status.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for Arc<EcashState> {
|
||||
fn from_ref(app_state: &AppState) -> Self {
|
||||
app_state.ecash_state.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -77,16 +77,6 @@ macro_rules! nyxd_query {
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! nyxd_signing_shared {
|
||||
($self:expr, $($op:tt)*) => {{
|
||||
let guard = $self.inner.read().await;
|
||||
match &*guard {
|
||||
$crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*,
|
||||
$crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! nyxd_signing {
|
||||
($self:expr, $($op:tt)*) => {{
|
||||
let guard = $self.inner.write().await;
|
||||
@@ -140,13 +130,19 @@ impl Client {
|
||||
self.inner.read().await
|
||||
}
|
||||
|
||||
pub(crate) async fn client_address(&self) -> AccountId {
|
||||
nyxd_signing_shared!(self, address())
|
||||
pub(crate) async fn client_address(&self) -> Option<AccountId> {
|
||||
let guard = self.inner.read().await;
|
||||
match &*guard {
|
||||
ClientInner::Signing(client) => Some(client.address()),
|
||||
ClientInner::Query(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn balance<S: Into<String>>(&self, denom: S) -> Result<Coin, NyxdError> {
|
||||
let address = self.client_address().await;
|
||||
let denom = denom.into();
|
||||
let Some(address) = self.client_address().await else {
|
||||
return Ok(Coin::new(0, denom));
|
||||
};
|
||||
let balance = nyxd_query!(self, get_balance(&address, denom.clone()).await?);
|
||||
|
||||
match balance {
|
||||
@@ -394,8 +390,10 @@ impl Client {
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ecash::client::Client for Client {
|
||||
async fn address(&self) -> AccountId {
|
||||
self.client_address().await
|
||||
async fn address(&self) -> Result<AccountId, EcashError> {
|
||||
self.client_address()
|
||||
.await
|
||||
.ok_or(EcashError::ChainSignerNotEnabled)
|
||||
}
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId, EcashError> {
|
||||
@@ -481,7 +479,7 @@ impl crate::ecash::client::Client for Client {
|
||||
async fn get_self_registered_dealer_details(
|
||||
&self,
|
||||
) -> crate::ecash::error::Result<DealerDetailsResponse> {
|
||||
let self_address = &self.address().await;
|
||||
let self_address = &self.address().await?;
|
||||
Ok(nyxd_query!(self, get_dealer_details(self_address).await?))
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58"] }
|
||||
workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["query-types"]
|
||||
query-types = ["nym-http-api-common"]
|
||||
|
||||
@@ -268,6 +268,9 @@ pub struct WebhookTicketbookWalletSharesRequest {
|
||||
pub struct TicketbookObtainQueryParams {
|
||||
pub output: Option<Output>,
|
||||
|
||||
#[serde(default)]
|
||||
pub skip_webhook: bool,
|
||||
|
||||
pub include_master_verification_key: bool,
|
||||
|
||||
pub include_coin_index_signatures: bool,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.3"
|
||||
version = "0.1.6"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -48,6 +48,7 @@ nym-config = { path = "../../common/config" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand", "serde"] }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||
nym-ecash-contract-common = { path = "../../common/cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-http-api-common = { path = "../../common/http-api-common", features = ["utoipa"] }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
@@ -30,6 +30,6 @@ RUN apt update && apt install -yy curl ca-certificates
|
||||
|
||||
WORKDIR /nym
|
||||
|
||||
COPY --from=builder /usr/src/nym/nym-credential-proxy/target/release/nym-credential-proxy ./
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-credential-proxy ./
|
||||
ENTRYPOINT [ "/nym/nym-credential-proxy" ]
|
||||
|
||||
|
||||
@@ -55,6 +55,15 @@ pub struct Cli {
|
||||
)]
|
||||
pub(crate) http_auth_token: String,
|
||||
|
||||
/// Specify the maximum number of deposits the credential proxy can make in a single transaction
|
||||
/// (default: 32)
|
||||
#[clap(
|
||||
long,
|
||||
env = "NYM_CREDENTIAL_PROXY_MAX_CONCURRENT_DEPOSITS",
|
||||
default_value_t = 32
|
||||
)]
|
||||
pub(crate) max_concurrent_deposits: usize,
|
||||
|
||||
#[clap(long, env = "NYM_CREDENTIAL_PROXY_PERSISTENT_STORAGE_STORAGE")]
|
||||
pub(crate) persistent_storage_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposit_maker::{DepositRequest, DepositResponse};
|
||||
use crate::error::VpnApiError;
|
||||
use crate::http::state::ApiState;
|
||||
use crate::storage::models::BlindedShares;
|
||||
@@ -14,21 +15,48 @@ use nym_credentials::IssuanceTicketBook;
|
||||
use nym_credentials_interface::Base58;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::ecash::BlindSignRequestBody;
|
||||
use nym_validator_client::nyxd::contract_traits::EcashSigningClient;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::timeout;
|
||||
use tracing::{debug, error, info, instrument};
|
||||
use tokio::sync::{oneshot, Mutex};
|
||||
use tokio::time::{timeout, Instant};
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
// use the same type alias as our contract without importing the whole thing just for this single line
|
||||
pub type NodeId = u64;
|
||||
|
||||
#[instrument(skip(state), ret, err(Display))]
|
||||
async fn make_deposit(
|
||||
state: &ApiState,
|
||||
pub_key: ed25519::PublicKey,
|
||||
deposit_amount: &Coin,
|
||||
) -> Result<DepositResponse, VpnApiError> {
|
||||
let start = Instant::now();
|
||||
let (on_done_tx, on_done_rx) = oneshot::channel();
|
||||
let request = DepositRequest::new(pub_key, deposit_amount, on_done_tx);
|
||||
state.request_deposit(request).await;
|
||||
|
||||
let time_taken = start.elapsed();
|
||||
let formatted = humantime::format_duration(time_taken);
|
||||
|
||||
let Ok(deposit_response) = on_done_rx.await else {
|
||||
error!("failed to receive deposit response: the corresponding sender channel got dropped by the DepositMaker!");
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
};
|
||||
|
||||
if time_taken > Duration::from_secs(20) {
|
||||
warn!("attempting to resolve deposit request took {formatted}. perhaps the buffer is too small or the process/chain is overloaded?")
|
||||
} else {
|
||||
debug!("attempting to resolve deposit request took {formatted}")
|
||||
}
|
||||
|
||||
deposit_response.ok_or(VpnApiError::DepositFailure)
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
skip(state, request_data, request, requested_on),
|
||||
fields(
|
||||
@@ -59,25 +87,12 @@ pub(crate) async fn try_obtain_wallet_shares(
|
||||
.await?;
|
||||
let ecash_api_clients = state.ecash_clients(epoch).await?.clone();
|
||||
|
||||
let chain_write_permit = state.start_chain_tx().await;
|
||||
let DepositResponse {
|
||||
deposit_id,
|
||||
tx_hash,
|
||||
} = make_deposit(state, *ed25519_keypair.public_key(), &deposit_amount).await?;
|
||||
|
||||
info!("starting the deposit!");
|
||||
// TODO: batch those up
|
||||
// TODO: batch those up
|
||||
let deposit_res = chain_write_permit
|
||||
.make_ticketbook_deposit(
|
||||
ed25519_keypair.public_key().to_base58_string(),
|
||||
deposit_amount.clone(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// explicitly drop it here so other tasks could start using it
|
||||
drop(chain_write_permit);
|
||||
|
||||
let deposit_id = deposit_res.parse_singleton_u32_contract_data()?;
|
||||
let tx_hash = deposit_res.transaction_hash;
|
||||
info!(deposit_id = %deposit_id, tx_hash = %tx_hash, "deposit finished");
|
||||
info!(deposit_id = %deposit_id, "deposit finished");
|
||||
|
||||
// store the deposit information so if we fail, we could perhaps still reuse it for another issuance
|
||||
state
|
||||
@@ -342,6 +357,7 @@ pub(crate) async fn try_obtain_blinded_ticketbook_async(
|
||||
params: TicketbookObtainQueryParams,
|
||||
pending: BlindedShares,
|
||||
) {
|
||||
let skip_webhook = params.skip_webhook;
|
||||
if let Err(err) = try_obtain_blinded_ticketbook_async_inner(
|
||||
&state,
|
||||
request,
|
||||
@@ -352,6 +368,11 @@ pub(crate) async fn try_obtain_blinded_ticketbook_async(
|
||||
)
|
||||
.await
|
||||
{
|
||||
if skip_webhook {
|
||||
info!(uuid = %request,"the webhook is not going to be called for this request");
|
||||
return;
|
||||
}
|
||||
|
||||
// post to the webhook to notify of errors on this side
|
||||
if let Err(webhook_err) = try_trigger_webhook_request_for_error(
|
||||
&state,
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::http::state::ChainClient;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::{Coin, Hash};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DepositResponse {
|
||||
pub tx_hash: Hash,
|
||||
pub deposit_id: DepositId,
|
||||
}
|
||||
|
||||
pub(crate) struct DepositRequest {
|
||||
pubkey: ed25519::PublicKey,
|
||||
deposit_amount: Coin,
|
||||
on_done: oneshot::Sender<Option<DepositResponse>>,
|
||||
}
|
||||
|
||||
impl DepositRequest {
|
||||
pub(crate) fn new(
|
||||
pubkey: ed25519::PublicKey,
|
||||
deposit_amount: &Coin,
|
||||
on_done: oneshot::Sender<Option<DepositResponse>>,
|
||||
) -> Self {
|
||||
DepositRequest {
|
||||
pubkey,
|
||||
deposit_amount: deposit_amount.clone(),
|
||||
on_done,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DepositRequestReceiver = mpsc::Receiver<DepositRequest>;
|
||||
|
||||
pub(crate) fn new_control_channels(
|
||||
max_concurrent_deposits: usize,
|
||||
) -> (DepositRequestSender, DepositRequestReceiver) {
|
||||
let (tx, rx) = mpsc::channel(max_concurrent_deposits);
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DepositRequestSender(mpsc::Sender<DepositRequest>);
|
||||
|
||||
impl From<mpsc::Sender<DepositRequest>> for DepositRequestSender {
|
||||
fn from(inner: mpsc::Sender<DepositRequest>) -> Self {
|
||||
DepositRequestSender(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl DepositRequestSender {
|
||||
pub(crate) async fn request_deposit(&self, request: DepositRequest) {
|
||||
if self.0.send(request).await.is_err() {
|
||||
error!("failed to request deposit: the DepositMaker must have died!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DepositMaker {
|
||||
client: ChainClient,
|
||||
max_concurrent_deposits: usize,
|
||||
deposit_request_sender: DepositRequestSender,
|
||||
deposit_request_receiver: DepositRequestReceiver,
|
||||
short_sha: &'static str,
|
||||
cancellation_token: CancellationToken,
|
||||
}
|
||||
|
||||
impl DepositMaker {
|
||||
pub(crate) fn new(
|
||||
short_sha: &'static str,
|
||||
client: ChainClient,
|
||||
max_concurrent_deposits: usize,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Self {
|
||||
let (deposit_request_sender, deposit_request_receiver) =
|
||||
new_control_channels(max_concurrent_deposits);
|
||||
|
||||
DepositMaker {
|
||||
client,
|
||||
max_concurrent_deposits,
|
||||
deposit_request_sender,
|
||||
deposit_request_receiver,
|
||||
short_sha,
|
||||
cancellation_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deposit_request_sender(&self) -> DepositRequestSender {
|
||||
self.deposit_request_sender.clone()
|
||||
}
|
||||
|
||||
pub(crate) async fn process_deposit_requests(
|
||||
&mut self,
|
||||
requests: Vec<DepositRequest>,
|
||||
) -> Result<(), VpnApiError> {
|
||||
let chain_write_permit = self.client.start_chain_tx().await;
|
||||
|
||||
info!("starting deposits");
|
||||
let mut contents = Vec::new();
|
||||
let mut replies = Vec::new();
|
||||
for request in requests {
|
||||
// check if the channel is still open in case the receiver client has cancelled the request
|
||||
if request.on_done.is_closed() {
|
||||
warn!(
|
||||
"the request for deposit from {} got cancelled",
|
||||
request.pubkey
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
contents.push((request.pubkey.to_base58_string(), request.deposit_amount));
|
||||
replies.push(request.on_done);
|
||||
}
|
||||
|
||||
let deposits_res = chain_write_permit
|
||||
.make_deposits(self.short_sha, contents)
|
||||
.await;
|
||||
let execute_res = match deposits_res {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
// we have to let requesters know the deposit(s) failed
|
||||
for reply in replies {
|
||||
if reply.send(None).is_err() {
|
||||
warn!("one of the deposit requesters has been terminated")
|
||||
}
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
let tx_hash = execute_res.transaction_hash;
|
||||
info!("{} deposits made in transaction: {tx_hash}", replies.len());
|
||||
|
||||
let contract_data = match execute_res.to_contract_data() {
|
||||
Ok(contract_data) => contract_data,
|
||||
Err(err) => {
|
||||
// that one is tricky. deposits technically got made, but we somehow failed to parse response,
|
||||
// in this case terminate the proxy with 0 exit code so it wouldn't get automatically restarted
|
||||
// because it requires some serious MANUAL intervention
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit information from the contract transaction. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually. error was: {err}");
|
||||
self.cancellation_token.cancel();
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
if contract_data.len() != replies.len() {
|
||||
// another critical failure, that one should be quite impossible and thus has to be manually inspected
|
||||
error!("CRITICAL FAILURE: failed to parse out all deposit information from the contract transaction. got {} responses while we sent {} deposits! either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually", contract_data.len(), replies.len());
|
||||
self.cancellation_token.cancel();
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
}
|
||||
|
||||
for (reply_channel, response) in replies.into_iter().zip(contract_data) {
|
||||
let response_index = response.message_index;
|
||||
let deposit_id = match response.parse_singleton_u32_contract_data() {
|
||||
Ok(deposit_id) => deposit_id,
|
||||
Err(err) => {
|
||||
// another impossibility
|
||||
error!("CRITICAL FAILURE: failed to parse out deposit id out of the response at index {response_index}: {err}. either the chain got upgraded and the schema changed or the ecash contract got changed! terminating the process. it has to be inspected manually");
|
||||
self.cancellation_token.cancel();
|
||||
return Err(VpnApiError::DepositFailure);
|
||||
}
|
||||
};
|
||||
|
||||
if reply_channel
|
||||
.send(Some(DepositResponse {
|
||||
deposit_id,
|
||||
tx_hash,
|
||||
}))
|
||||
.is_err()
|
||||
{
|
||||
warn!("one of the deposit requesters has been terminated. deposit {deposit_id} will remain unclaimed!");
|
||||
// this shouldn't happen as the requester task shouldn't be killed, but it's not a critical failure
|
||||
// we just lost some tokens, but it's not an undefined on-chain behaviour
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_forever(mut self) {
|
||||
info!("starting the deposit maker task");
|
||||
loop {
|
||||
let mut receive_buffer = Vec::with_capacity(self.max_concurrent_deposits);
|
||||
tokio::select! {
|
||||
_ = self.cancellation_token.cancelled() => {
|
||||
break
|
||||
}
|
||||
received = self.deposit_request_receiver.recv_many(&mut receive_buffer, self.max_concurrent_deposits) => {
|
||||
debug!("received {received} deposit requests");
|
||||
if let Err(err) = self.process_deposit_requests(receive_buffer).await {
|
||||
error!("failed to process received deposit requests: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,9 @@ pub enum VpnApiError {
|
||||
|
||||
#[error("timed out while attempting to obtain partial wallet from {client_repr}")]
|
||||
EcashApiRequestTimeout { client_repr: String },
|
||||
|
||||
#[error("failed to create deposit")]
|
||||
DepositFailure,
|
||||
}
|
||||
|
||||
impl VpnApiError {
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use axum::{
|
||||
extract::{ConnectInfo, Request},
|
||||
http::{
|
||||
header::{HOST, USER_AGENT},
|
||||
HeaderValue,
|
||||
},
|
||||
middleware::Next,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use colored::*;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::time::Instant;
|
||||
use tracing::info;
|
||||
|
||||
/// Simple logger for requests
|
||||
pub async fn logger(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
req: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
let method = req.method().to_string().green();
|
||||
let uri = req.uri().to_string().blue();
|
||||
let agent = header_map(
|
||||
req.headers().get(USER_AGENT),
|
||||
"Unknown User Agent".to_string(),
|
||||
);
|
||||
|
||||
let host = header_map(req.headers().get(HOST), "Unknown Host".to_string());
|
||||
|
||||
let start = Instant::now();
|
||||
let res = next.run(req).await;
|
||||
let time_taken = start.elapsed();
|
||||
let status = res.status();
|
||||
let print_status = if status.is_client_error() || status.is_server_error() {
|
||||
status.to_string().red()
|
||||
} else if status.is_success() {
|
||||
status.to_string().green()
|
||||
} else {
|
||||
status.to_string().yellow()
|
||||
};
|
||||
|
||||
let taken = "time taken".bold();
|
||||
|
||||
let time_taken = match time_taken.as_millis() {
|
||||
ms if ms > 500 => format!("{taken}: {}", format!("{ms}ms").red()),
|
||||
ms if ms > 200 => format!("{taken}: {}", format!("{ms}ms").yellow()),
|
||||
ms if ms > 50 => format!("{taken}: {}", format!("{ms}ms").bright_yellow()),
|
||||
ms => format!("{taken}: {ms}ms"),
|
||||
};
|
||||
|
||||
let agent_str = "agent".bold();
|
||||
info!("[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn header_map(header: Option<&HeaderValue>, msg: String) -> String {
|
||||
header
|
||||
.map(|x| x.to_str().unwrap_or(&msg).to_string())
|
||||
.unwrap_or(msg)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod auth;
|
||||
pub mod logging;
|
||||
@@ -10,7 +10,6 @@ use tokio_util::sync::CancellationToken;
|
||||
use tracing::info;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod middleware;
|
||||
pub mod router;
|
||||
pub mod state;
|
||||
pub mod types;
|
||||
@@ -22,10 +21,15 @@ pub struct HttpServer {
|
||||
}
|
||||
|
||||
impl HttpServer {
|
||||
pub fn new(bind_address: SocketAddr, state: ApiState, auth_token: String) -> Self {
|
||||
pub fn new(
|
||||
bind_address: SocketAddr,
|
||||
state: ApiState,
|
||||
auth_token: String,
|
||||
cancellation: CancellationToken,
|
||||
) -> Self {
|
||||
HttpServer {
|
||||
bind_address,
|
||||
cancellation: state.cancellation_token(),
|
||||
cancellation,
|
||||
router: build_router(state, auth_token),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
use crate::http::state::ApiState;
|
||||
use axum::Router;
|
||||
use nym_credential_proxy_requests::routes;
|
||||
use nym_http_api_common::middleware::bearer_auth::AuthLayer;
|
||||
|
||||
use crate::http::middleware::auth::AuthLayer;
|
||||
pub(crate) use nym_http_api_common::{Output, OutputParams};
|
||||
|
||||
pub mod v1;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::http::middleware::auth::AuthLayer;
|
||||
use crate::http::state::ApiState;
|
||||
use axum::Router;
|
||||
use nym_credential_proxy_requests::routes::api::v1;
|
||||
use nym_http_api_common::middleware::bearer_auth::AuthLayer;
|
||||
|
||||
// pub mod bandwidth_voucher;
|
||||
// pub mod freepass;
|
||||
pub mod openapi;
|
||||
pub mod ticketbook;
|
||||
|
||||
|
||||
+102
-93
@@ -21,7 +21,7 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
use nym_credential_proxy_requests::routes::api::v1::ticketbook;
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{error, info, span, warn, Level};
|
||||
use tracing::{error, info, span, warn, Instrument, Level};
|
||||
|
||||
pub(crate) mod shares;
|
||||
|
||||
@@ -71,55 +71,58 @@ pub(crate) async fn obtain_ticketbook_shares(
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
|
||||
let span = span!(Level::INFO, "obtain ticketboook", uuid = %uuid);
|
||||
let _entered = span.enter();
|
||||
info!("");
|
||||
async move {
|
||||
info!("");
|
||||
|
||||
let output = params.output.unwrap_or_default();
|
||||
let output = params.output.unwrap_or_default();
|
||||
|
||||
state.ensure_not_in_epoch_transition(Some(uuid)).await?;
|
||||
let epoch_id = state
|
||||
.current_epoch_id()
|
||||
.await
|
||||
.map_err(|err| RequestError::new_server_error(err, uuid))?;
|
||||
state.ensure_not_in_epoch_transition(Some(uuid)).await?;
|
||||
let epoch_id = state
|
||||
.current_epoch_id()
|
||||
.await
|
||||
.map_err(|err| RequestError::new_server_error(err, uuid))?;
|
||||
|
||||
if let Err(err) = ensure_sane_expiration_date(payload.expiration_date) {
|
||||
warn!("failure due to invalid expiration date");
|
||||
return Err(RequestError::new_with_uuid(
|
||||
err.to_string(),
|
||||
uuid,
|
||||
StatusCode::BAD_REQUEST,
|
||||
));
|
||||
}
|
||||
if let Err(err) = ensure_sane_expiration_date(payload.expiration_date) {
|
||||
warn!("failure due to invalid expiration date");
|
||||
return Err(RequestError::new_with_uuid(
|
||||
err.to_string(),
|
||||
uuid,
|
||||
StatusCode::BAD_REQUEST,
|
||||
));
|
||||
}
|
||||
|
||||
// if additional data was requested, grab them first in case there are any cache/network issues
|
||||
let (
|
||||
master_verification_key,
|
||||
aggregated_expiration_date_signatures,
|
||||
aggregated_coin_index_signatures,
|
||||
) = state
|
||||
.response_global_data(
|
||||
params.include_master_verification_key,
|
||||
params.include_expiration_date_signatures,
|
||||
params.include_coin_index_signatures,
|
||||
// if additional data was requested, grab them first in case there are any cache/network issues
|
||||
let (
|
||||
master_verification_key,
|
||||
aggregated_expiration_date_signatures,
|
||||
aggregated_coin_index_signatures,
|
||||
) = state
|
||||
.response_global_data(
|
||||
params.include_master_verification_key,
|
||||
params.include_expiration_date_signatures,
|
||||
params.include_coin_index_signatures,
|
||||
epoch_id,
|
||||
payload.expiration_date,
|
||||
uuid,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let shares = try_obtain_wallet_shares(&state, uuid, requested_on, payload)
|
||||
.await
|
||||
.inspect_err(|err| warn!("request failure: {err}"))
|
||||
.map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
info!("request was successful!");
|
||||
Ok(output.to_response(TicketbookWalletSharesResponse {
|
||||
epoch_id,
|
||||
payload.expiration_date,
|
||||
uuid,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let shares = try_obtain_wallet_shares(&state, uuid, requested_on, payload)
|
||||
.await
|
||||
.inspect_err(|err| warn!("request failure: {err}"))
|
||||
.map_err(|err| RequestError::new(err.to_string(), StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
info!("request was successful!");
|
||||
Ok(output.to_response(TicketbookWalletSharesResponse {
|
||||
epoch_id,
|
||||
shares,
|
||||
master_verification_key,
|
||||
aggregated_coin_index_signatures,
|
||||
aggregated_expiration_date_signatures,
|
||||
}))
|
||||
shares,
|
||||
master_verification_key,
|
||||
aggregated_coin_index_signatures,
|
||||
aggregated_expiration_date_signatures,
|
||||
}))
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Attempt to obtain blinded shares of an ecash ticketbook wallet asynchronously
|
||||
@@ -159,63 +162,69 @@ pub(crate) async fn obtain_ticketbook_shares_async(
|
||||
let requested_on = OffsetDateTime::now_utc();
|
||||
|
||||
let span = span!(Level::INFO, "[async] obtain ticketboook", uuid = %uuid);
|
||||
let _entered = span.enter();
|
||||
info!("");
|
||||
async move {
|
||||
info!("");
|
||||
let output = params.output.unwrap_or_default();
|
||||
|
||||
let output = params.output.unwrap_or_default();
|
||||
// 1. perform basic validation
|
||||
state.ensure_not_in_epoch_transition(Some(uuid)).await?;
|
||||
|
||||
// 1. perform basic validation
|
||||
state.ensure_not_in_epoch_transition(Some(uuid)).await?;
|
||||
|
||||
if let Err(err) = ensure_sane_expiration_date(payload.inner.expiration_date) {
|
||||
warn!("failure due to invalid expiration date");
|
||||
return Err(RequestError::new_with_uuid(
|
||||
err.to_string(),
|
||||
uuid,
|
||||
StatusCode::BAD_REQUEST,
|
||||
));
|
||||
}
|
||||
|
||||
// 2. store the request to retrieve the id
|
||||
let pending = match state
|
||||
.storage()
|
||||
.insert_new_pending_async_shares_request(uuid, &payload.device_id, &payload.credential_id)
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
error!("failed to insert new pending async shares: {err}");
|
||||
if let Err(err) = ensure_sane_expiration_date(payload.inner.expiration_date) {
|
||||
warn!("failure due to invalid expiration date");
|
||||
return Err(RequestError::new_with_uuid(
|
||||
err.to_string(),
|
||||
uuid,
|
||||
StatusCode::CONFLICT,
|
||||
StatusCode::BAD_REQUEST,
|
||||
));
|
||||
}
|
||||
Ok(pending) => pending,
|
||||
};
|
||||
let id = pending.id;
|
||||
|
||||
// 3. try to spawn a new task attempting to resolve the request
|
||||
if state
|
||||
.try_spawn(try_obtain_blinded_ticketbook_async(
|
||||
state.clone(),
|
||||
uuid,
|
||||
requested_on,
|
||||
payload,
|
||||
params,
|
||||
pending,
|
||||
))
|
||||
.is_none()
|
||||
{
|
||||
// we're going through the shutdown
|
||||
return Err(RequestError::new_with_uuid(
|
||||
"server shutdown in progress",
|
||||
uuid,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
// 2. store the request to retrieve the id
|
||||
let pending = match state
|
||||
.storage()
|
||||
.insert_new_pending_async_shares_request(
|
||||
uuid,
|
||||
&payload.device_id,
|
||||
&payload.credential_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
error!("failed to insert new pending async shares: {err}");
|
||||
return Err(RequestError::new_with_uuid(
|
||||
err.to_string(),
|
||||
uuid,
|
||||
StatusCode::CONFLICT,
|
||||
));
|
||||
}
|
||||
Ok(pending) => pending,
|
||||
};
|
||||
let id = pending.id;
|
||||
|
||||
// 3. try to spawn a new task attempting to resolve the request
|
||||
if state
|
||||
.try_spawn(try_obtain_blinded_ticketbook_async(
|
||||
state.clone(),
|
||||
uuid,
|
||||
requested_on,
|
||||
payload,
|
||||
params,
|
||||
pending,
|
||||
))
|
||||
.is_none()
|
||||
{
|
||||
// we're going through the shutdown
|
||||
return Err(RequestError::new_with_uuid(
|
||||
"server shutdown in progress",
|
||||
uuid,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
}
|
||||
|
||||
// 4. in the meantime, return the id to the user
|
||||
Ok(output.to_response(TicketbookWalletSharesAsyncResponse { id, uuid }))
|
||||
}
|
||||
|
||||
// 4. in the meantime, return the id to the user
|
||||
Ok(output.to_response(TicketbookWalletSharesAsyncResponse { id, uuid }))
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Obtain the current value of the bandwidth voucher deposit
|
||||
|
||||
+77
-75
@@ -17,7 +17,7 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
use nym_credential_proxy_requests::routes::api::v1::ticketbook::shares;
|
||||
use nym_http_api_common::OutputParams;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use tracing::{debug, span, Level};
|
||||
use tracing::{debug, span, Instrument, Level};
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn shares_to_response(
|
||||
@@ -100,50 +100,51 @@ pub(crate) async fn query_for_shares_by_id(
|
||||
let uuid = random_uuid();
|
||||
|
||||
let span = span!(Level::INFO, "query shares by id", uuid = %uuid, share_id = %share_id);
|
||||
let _entered = span.enter();
|
||||
debug!("");
|
||||
async move {
|
||||
debug!("");
|
||||
|
||||
// TODO: edge case: this will **NOT** work if shares got created in epoch X,
|
||||
// but this query happened in epoch X+1
|
||||
let shares = match state
|
||||
.storage()
|
||||
.load_wallet_shares_by_shares_id(share_id)
|
||||
.await
|
||||
{
|
||||
Ok(shares) => {
|
||||
if shares.is_empty() {
|
||||
debug!("shares not found");
|
||||
// TODO: edge case: this will **NOT** work if shares got created in epoch X,
|
||||
// but this query happened in epoch X+1
|
||||
let shares = match state
|
||||
.storage()
|
||||
.load_wallet_shares_by_shares_id(share_id)
|
||||
.await
|
||||
{
|
||||
Ok(shares) => {
|
||||
if shares.is_empty() {
|
||||
debug!("shares not found");
|
||||
|
||||
// check for explicit error
|
||||
match state
|
||||
.storage()
|
||||
.load_shares_error_by_shares_id(share_id)
|
||||
.await
|
||||
{
|
||||
Ok(maybe_error_message) => {
|
||||
if let Some(error_message) = maybe_error_message {
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("failed to obtain wallet shares: {error_message} - share_id = {share_id}"),
|
||||
uuid,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
// check for explicit error
|
||||
match state
|
||||
.storage()
|
||||
.load_shares_error_by_shares_id(share_id)
|
||||
.await
|
||||
{
|
||||
Ok(maybe_error_message) => {
|
||||
if let Some(error_message) = maybe_error_message {
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("failed to obtain wallet shares: {error_message} - share_id = {share_id}"),
|
||||
uuid,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
}
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("not found - share_id = {share_id}"),
|
||||
uuid,
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
}
|
||||
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("not found - share_id = {share_id}"),
|
||||
uuid,
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
shares
|
||||
}
|
||||
shares
|
||||
}
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
};
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
};
|
||||
|
||||
shares_to_response(state, uuid, shares, params).await
|
||||
shares_to_response(state, uuid, shares, params).await
|
||||
}.instrument(span).await
|
||||
}
|
||||
|
||||
/// Query by id for blinded wallet shares of a ticketbook
|
||||
@@ -174,50 +175,51 @@ pub(crate) async fn query_for_shares_by_device_id_and_credential_id(
|
||||
let uuid = random_uuid();
|
||||
|
||||
let span = span!(Level::INFO, "query shares by device and credential ids", uuid = %uuid, device_id = %device_id, credential_id = %credential_id);
|
||||
let _entered = span.enter();
|
||||
debug!("");
|
||||
async move {
|
||||
debug!("");
|
||||
|
||||
// TODO: edge case: this will **NOT** work if shares got created in epoch X,
|
||||
// but this query happened in epoch X+1
|
||||
let shares = match state
|
||||
.storage()
|
||||
.load_wallet_shares_by_device_and_credential_id(&device_id, &credential_id)
|
||||
.await
|
||||
{
|
||||
Ok(shares) => {
|
||||
if shares.is_empty() {
|
||||
debug!("shares not found");
|
||||
// TODO: edge case: this will **NOT** work if shares got created in epoch X,
|
||||
// but this query happened in epoch X+1
|
||||
let shares = match state
|
||||
.storage()
|
||||
.load_wallet_shares_by_device_and_credential_id(&device_id, &credential_id)
|
||||
.await
|
||||
{
|
||||
Ok(shares) => {
|
||||
if shares.is_empty() {
|
||||
debug!("shares not found");
|
||||
|
||||
// check for explicit error
|
||||
match state
|
||||
.storage()
|
||||
.load_shares_error_by_device_and_credential_id(&device_id, &credential_id)
|
||||
.await
|
||||
{
|
||||
Ok(maybe_error_message) => {
|
||||
if let Some(error_message) = maybe_error_message {
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("failed to obtain wallet shares: {error_message} - device_id = {device_id}, credential_id = {credential_id}"),
|
||||
uuid,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
// check for explicit error
|
||||
match state
|
||||
.storage()
|
||||
.load_shares_error_by_device_and_credential_id(&device_id, &credential_id)
|
||||
.await
|
||||
{
|
||||
Ok(maybe_error_message) => {
|
||||
if let Some(error_message) = maybe_error_message {
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("failed to obtain wallet shares: {error_message} - device_id = {device_id}, credential_id = {credential_id}"),
|
||||
uuid,
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
}
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("not found - device_id = {device_id}, credential_id = {credential_id}"),
|
||||
uuid,
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
}
|
||||
|
||||
return Err(RequestError::new_with_uuid(
|
||||
format!("not found - device_id = {device_id}, credential_id = {credential_id}"),
|
||||
uuid,
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
shares
|
||||
}
|
||||
shares
|
||||
}
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
};
|
||||
Err(err) => return db_failure(err, uuid),
|
||||
};
|
||||
|
||||
shares_to_response(state, uuid, shares, params).await
|
||||
shares_to_response(state, uuid, shares, params).await
|
||||
}.instrument(span).await
|
||||
}
|
||||
|
||||
pub(crate) fn routes() -> Router<ApiState> {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::http::middleware::auth::AuthLayer;
|
||||
use crate::http::middleware::logging;
|
||||
use crate::http::state::ApiState;
|
||||
use axum::response::Redirect;
|
||||
use axum::routing::{get, MethodRouter};
|
||||
use axum::Router;
|
||||
use nym_credential_proxy_requests::routes;
|
||||
use nym_http_api_common::middleware::bearer_auth::AuthLayer;
|
||||
use nym_http_api_common::middleware::logging;
|
||||
use std::sync::Arc;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::deposit_maker::{DepositRequest, DepositRequestSender};
|
||||
use crate::error::VpnApiError;
|
||||
use crate::helpers::LockTimer;
|
||||
use crate::http::types::RequestError;
|
||||
@@ -28,20 +29,24 @@ use nym_credentials::{
|
||||
AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey,
|
||||
};
|
||||
use nym_credentials_interface::VerificationKeyAuth;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
DkgQueryClient, EcashQueryClient, NymContractsProvider, PagedDkgQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::{Coin, NyxdClient};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use nym_validator_client::nyxd::{Coin, CosmWasmClient, NyxdClient};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, EcashApiClient};
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::Instant;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::{debug, info, warn};
|
||||
@@ -59,36 +64,19 @@ impl ApiState {
|
||||
pub async fn new(
|
||||
storage: VpnApiStorage,
|
||||
zk_nym_web_hook_config: ZkNymWebHookConfig,
|
||||
mnemonic: Mnemonic,
|
||||
client: ChainClient,
|
||||
deposit_requester: DepositRequestSender,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Result<Self, VpnApiError> {
|
||||
let network_details = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?;
|
||||
|
||||
let nyxd_url = network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.ok_or_else(|| VpnApiError::NoNyxEndpointsAvailable)?
|
||||
.nyxd_url
|
||||
.as_str();
|
||||
|
||||
let client = NyxdClient::connect_with_mnemonic(client_config, nyxd_url, mnemonic)?;
|
||||
|
||||
if client.ecash_contract_address().is_none() {
|
||||
return Err(VpnApiError::UnavailableEcashContract);
|
||||
}
|
||||
|
||||
if client.dkg_contract_address().is_none() {
|
||||
return Err(VpnApiError::UnavailableDKGContract);
|
||||
}
|
||||
|
||||
let state = ApiState {
|
||||
inner: Arc::new(ApiStateInner {
|
||||
storage,
|
||||
client: RwLock::new(client),
|
||||
client,
|
||||
ecash_state: EcashState::default(),
|
||||
zk_nym_web_hook_config,
|
||||
task_tracker: TaskTracker::new(),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
deposit_requester,
|
||||
cancellation_token,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -136,10 +124,6 @@ impl ApiState {
|
||||
self.inner.task_tracker.wait().await
|
||||
}
|
||||
|
||||
pub(crate) fn cancellation_token(&self) -> CancellationToken {
|
||||
self.inner.cancellation_token.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn zk_nym_web_hook(&self) -> &ZkNymWebHookConfig {
|
||||
&self.inner.zk_nym_web_hook_config
|
||||
}
|
||||
@@ -220,16 +204,19 @@ impl ApiState {
|
||||
}
|
||||
|
||||
pub(crate) async fn query_chain(&self) -> RwLockReadGuard<DirectSigningHttpRpcNyxdClient> {
|
||||
let _acquire_timer = LockTimer::new("acquire chain query permit");
|
||||
self.inner.client.read().await
|
||||
self.inner.client.query_chain().await
|
||||
}
|
||||
|
||||
pub(crate) async fn start_chain_tx(&self) -> ChainWritePermit {
|
||||
let _acquire_timer = LockTimer::new("acquire exclusive chain write permit");
|
||||
pub(crate) async fn request_deposit(&self, request: DepositRequest) {
|
||||
let start = Instant::now();
|
||||
self.inner.deposit_requester.request_deposit(request).await;
|
||||
|
||||
ChainWritePermit {
|
||||
lock_timer: LockTimer::new("exclusive chain access permit"),
|
||||
inner: self.inner.client.write().await,
|
||||
let time_taken = start.elapsed();
|
||||
let formatted = humantime::format_duration(time_taken);
|
||||
if time_taken > Duration::from_secs(10) {
|
||||
warn!("attempting to push new deposit request onto the queue took {formatted}. perhaps the buffer is too small or the process/chain is overloaded?")
|
||||
} else {
|
||||
debug!("attempting to push new deposit request onto the queue took {formatted}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,10 +591,57 @@ impl ApiState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ChainClient(Arc<RwLock<DirectSigningHttpRpcNyxdClient>>);
|
||||
|
||||
impl ChainClient {
|
||||
pub fn new(mnemonic: Mnemonic) -> Result<Self, VpnApiError> {
|
||||
let network_details = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?;
|
||||
|
||||
let nyxd_url = network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.ok_or_else(|| VpnApiError::NoNyxEndpointsAvailable)?
|
||||
.nyxd_url
|
||||
.as_str();
|
||||
|
||||
let client = NyxdClient::connect_with_mnemonic(client_config, nyxd_url, mnemonic)?;
|
||||
|
||||
if client.ecash_contract_address().is_none() {
|
||||
return Err(VpnApiError::UnavailableEcashContract);
|
||||
}
|
||||
|
||||
if client.dkg_contract_address().is_none() {
|
||||
return Err(VpnApiError::UnavailableDKGContract);
|
||||
}
|
||||
|
||||
Ok(ChainClient(Arc::new(RwLock::new(client))))
|
||||
}
|
||||
|
||||
pub(crate) async fn query_chain(&self) -> ChainReadPermit {
|
||||
let _acquire_timer = LockTimer::new("acquire chain query permit");
|
||||
self.0.read().await
|
||||
}
|
||||
|
||||
pub(crate) async fn start_chain_tx(&self) -> ChainWritePermit {
|
||||
let _acquire_timer = LockTimer::new("acquire exclusive chain write permit");
|
||||
|
||||
ChainWritePermit {
|
||||
lock_timer: LockTimer::new("exclusive chain access permit"),
|
||||
inner: self.0.write().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
struct ApiStateInner {
|
||||
storage: VpnApiStorage,
|
||||
|
||||
client: RwLock<DirectSigningHttpRpcNyxdClient>,
|
||||
client: ChainClient,
|
||||
|
||||
deposit_requester: DepositRequestSender,
|
||||
|
||||
zk_nym_web_hook_config: ZkNymWebHookConfig,
|
||||
|
||||
@@ -666,6 +700,8 @@ pub(crate) struct EcashState {
|
||||
CachedImmutableItems<Date, AggregatedExpirationDateSignatures>,
|
||||
}
|
||||
|
||||
pub(crate) type ChainReadPermit<'a> = RwLockReadGuard<'a, DirectSigningHttpRpcNyxdClient>;
|
||||
|
||||
// explicitly wrap the WriteGuard for extra information regarding time taken
|
||||
pub(crate) struct ChainWritePermit<'a> {
|
||||
// it's not really dead, we only care about it being dropped
|
||||
@@ -674,7 +710,56 @@ pub(crate) struct ChainWritePermit<'a> {
|
||||
inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>,
|
||||
}
|
||||
|
||||
impl<'a> Deref for ChainWritePermit<'a> {
|
||||
impl<'a> ChainWritePermit<'a> {
|
||||
pub(crate) async fn make_deposits(
|
||||
self,
|
||||
short_sha: &'static str,
|
||||
info: Vec<(String, Coin)>,
|
||||
) -> Result<ExecuteResult, VpnApiError> {
|
||||
let address = self.inner.address();
|
||||
let starting_sequence = self.inner.get_sequence(&address).await?.sequence;
|
||||
|
||||
let deposits = info.len();
|
||||
|
||||
let ecash_contract = self
|
||||
.inner
|
||||
.ecash_contract_address()
|
||||
.ok_or(VpnApiError::UnavailableEcashContract)?;
|
||||
let deposit_messages = info
|
||||
.into_iter()
|
||||
.map(|(identity_key, amount)| {
|
||||
(
|
||||
ExecuteMsg::DepositTicketBookFunds { identity_key },
|
||||
vec![amount],
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let res = self
|
||||
.inner
|
||||
.execute_multiple(
|
||||
ecash_contract,
|
||||
deposit_messages,
|
||||
None,
|
||||
format!("cp-{short_sha}: performing {deposits} deposits"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
loop {
|
||||
let updated_sequence = self.inner.get_sequence(&address).await?.sequence;
|
||||
|
||||
if updated_sequence > starting_sequence {
|
||||
break;
|
||||
}
|
||||
warn!("wrong sequence number... waiting before releasing chain lock");
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ChainWritePermit<'_> {
|
||||
type Target = DirectSigningHttpRpcNyxdClient;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -7,19 +7,23 @@
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use crate::cli::Cli;
|
||||
use crate::deposit_maker::DepositMaker;
|
||||
use crate::error::VpnApiError;
|
||||
use crate::http::state::ApiState;
|
||||
use crate::http::state::{ApiState, ChainClient};
|
||||
use crate::http::HttpServer;
|
||||
use crate::storage::VpnApiStorage;
|
||||
use crate::tasks::StoragePruner;
|
||||
use clap::Parser;
|
||||
use nym_bin_common::logging::setup_tracing_logger;
|
||||
use nym_bin_common::{bin_info, bin_info_owned};
|
||||
use nym_network_defaults::setup_env;
|
||||
use tracing::{info, trace};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, trace};
|
||||
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod credentials;
|
||||
mod deposit_maker;
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod http;
|
||||
@@ -50,6 +54,20 @@ pub async fn wait_for_signal() {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_sha_short() -> &'static str {
|
||||
let bin_info = bin_info!();
|
||||
if bin_info.commit_sha.len() < 7 {
|
||||
panic!("unavailable build commit sha")
|
||||
}
|
||||
|
||||
if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" {
|
||||
error!("the binary hasn't been built correctly. it doesn't have a commit sha information");
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
&bin_info.commit_sha[..7]
|
||||
}
|
||||
|
||||
async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
|
||||
// create the tasks
|
||||
let bind_address = cli.bind_address();
|
||||
@@ -58,14 +76,37 @@ async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
|
||||
let mnemonic = cli.mnemonic;
|
||||
let auth_token = cli.http_auth_token;
|
||||
let webhook_cfg = cli.webhook;
|
||||
let api_state = ApiState::new(storage.clone(), webhook_cfg, mnemonic).await?;
|
||||
let http_server = HttpServer::new(bind_address, api_state.clone(), auth_token);
|
||||
let chain_client = ChainClient::new(mnemonic)?;
|
||||
let cancellation_token = CancellationToken::new();
|
||||
|
||||
let storage_pruner = StoragePruner::new(api_state.cancellation_token(), storage);
|
||||
let deposit_maker = DepositMaker::new(
|
||||
build_sha_short(),
|
||||
chain_client.clone(),
|
||||
cli.max_concurrent_deposits,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
|
||||
let deposit_request_sender = deposit_maker.deposit_request_sender();
|
||||
let api_state = ApiState::new(
|
||||
storage.clone(),
|
||||
webhook_cfg,
|
||||
chain_client,
|
||||
deposit_request_sender,
|
||||
cancellation_token.clone(),
|
||||
)
|
||||
.await?;
|
||||
let http_server = HttpServer::new(
|
||||
bind_address,
|
||||
api_state.clone(),
|
||||
auth_token,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
let storage_pruner = StoragePruner::new(cancellation_token, storage);
|
||||
|
||||
// spawn all the tasks
|
||||
api_state.try_spawn(http_server.run_forever());
|
||||
api_state.try_spawn(storage_pruner.run_forever());
|
||||
api_state.try_spawn(deposit_maker.run_forever());
|
||||
|
||||
// wait for cancel signal (SIGINT, SIGTERM or SIGQUIT)
|
||||
wait_for_signal().await;
|
||||
@@ -78,10 +119,10 @@ async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
std::env::set_var(
|
||||
"RUST_LOG",
|
||||
"trace,handlebars=warn,tendermint_rpc=warn,h2=warn,hyper=warn,rustls=warn,reqwest=warn,tungstenite=warn,async_tungstenite=warn,tokio_util=warn,tokio_tungstenite=warn,tokio-util=warn,nym_validator_client=info",
|
||||
);
|
||||
// std::env::set_var(
|
||||
// "RUST_LOG",
|
||||
// "trace,handlebars=warn,tendermint_rpc=warn,h2=warn,hyper=warn,rustls=warn,reqwest=warn,tungstenite=warn,async_tungstenite=warn,tokio_util=warn,tokio_tungstenite=warn,tokio-util=warn,axum=warn,sqlx-core=warn,nym_validator_client=info",
|
||||
// );
|
||||
|
||||
let cli = Cli::parse();
|
||||
cli.webhook.ensure_valid_client_url()?;
|
||||
@@ -90,6 +131,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
setup_env(cli.config_env_file.as_ref());
|
||||
setup_tracing_logger();
|
||||
|
||||
let bin_info = bin_info_owned!();
|
||||
info!("using the following version: {bin_info}");
|
||||
|
||||
run_api(cli).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ use nym_validator_client::nyxd::Coin;
|
||||
use sqlx::ConnectOptions;
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tracing::log::LevelFilter;
|
||||
use tracing::{debug, error, info, instrument};
|
||||
use uuid::Uuid;
|
||||
use zeroize::Zeroizing;
|
||||
@@ -40,9 +42,15 @@ impl VpnApiStorage {
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
.log_statements(LevelFilter::Trace)
|
||||
.log_slow_statements(LevelFilter::Warn, Duration::from_millis(250));
|
||||
|
||||
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
|
||||
let pool_opts = sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.min_connections(5)
|
||||
.max_connections(25)
|
||||
.acquire_timeout(Duration::from_secs(60));
|
||||
|
||||
let connection_pool = match pool_opts.connect_with(opts).await {
|
||||
Ok(db) => db,
|
||||
Err(err) => {
|
||||
error!("Failed to connect to SQLx database: {err}");
|
||||
|
||||
@@ -19,10 +19,11 @@ impl StoragePruner {
|
||||
}
|
||||
|
||||
pub async fn run_forever(self) {
|
||||
while !self.cancellation_token.is_cancelled() {
|
||||
info!("starting the storage pruner task");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.cancellation_token.cancelled() => {
|
||||
// The token was cancelled, task can shut down
|
||||
break
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_secs(60 * 60)) => {
|
||||
match self.storage.prune_old_blinded_shares().await {
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::error::VpnApiError;
|
||||
use clap::Args;
|
||||
use reqwest::header::AUTHORIZATION;
|
||||
use serde::Serialize;
|
||||
use tracing::{debug, error, instrument, span, Level};
|
||||
use tracing::{debug, error, instrument, span, Instrument, Level};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -46,30 +46,33 @@ impl ZkNymWebHookConfig {
|
||||
pub async fn try_trigger<T: Serialize + ?Sized>(&self, original_uuid: Uuid, payload: &T) {
|
||||
let url = self.unchecked_client_url();
|
||||
let span = span!(Level::DEBUG, "webhook", uuid = %original_uuid, url = %url);
|
||||
let _entered = span.enter();
|
||||
|
||||
debug!("🕸️ about to trigger the webhook");
|
||||
async move {
|
||||
debug!("🕸️ about to trigger the webhook");
|
||||
|
||||
match reqwest::Client::new()
|
||||
.post(url.clone())
|
||||
.header(AUTHORIZATION, self.bearer_token())
|
||||
.json(payload)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
if !res.status().is_success() {
|
||||
error!("❌🕸️ failed to call webhook: {res:?}");
|
||||
} else {
|
||||
debug!("✅🕸️ webhook triggered successfully: {res:?}");
|
||||
if let Ok(body) = res.text().await {
|
||||
debug!("body = {body}");
|
||||
match reqwest::Client::new()
|
||||
.post(url.clone())
|
||||
.header(AUTHORIZATION, self.bearer_token())
|
||||
.json(payload)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
if !res.status().is_success() {
|
||||
error!("❌🕸️ failed to call webhook: {res:?}");
|
||||
} else {
|
||||
debug!("✅🕸️ webhook triggered successfully: {res:?}");
|
||||
if let Ok(body) = res.text().await {
|
||||
debug!("body = {body}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to call webhook: {err}")
|
||||
Err(err) => {
|
||||
error!("failed to call webhook: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::net::SocketAddr;
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub mod error;
|
||||
pub mod middleware;
|
||||
pub mod router;
|
||||
pub mod state;
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use axum::{
|
||||
extract::{ConnectInfo, Request},
|
||||
http::{
|
||||
header::{HOST, USER_AGENT},
|
||||
HeaderValue,
|
||||
},
|
||||
middleware::Next,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use colored::*;
|
||||
use std::net::SocketAddr;
|
||||
use tracing::info;
|
||||
|
||||
/// Simple logger for requests
|
||||
pub async fn logger(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
req: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
let method = req.method().to_string().green();
|
||||
let uri = req.uri().to_string().blue();
|
||||
let agent = header_map(
|
||||
req.headers().get(USER_AGENT),
|
||||
"Unknown User Agent".to_string(),
|
||||
);
|
||||
|
||||
let host = header_map(req.headers().get(HOST), "Unknown Host".to_string());
|
||||
|
||||
let res = next.run(req).await;
|
||||
let status = res.status();
|
||||
let print_status = if status.is_client_error() || status.is_server_error() {
|
||||
status.to_string().red()
|
||||
} else if status.is_success() {
|
||||
status.to_string().green()
|
||||
} else {
|
||||
status.to_string().yellow()
|
||||
};
|
||||
|
||||
info!(target: "incoming request", "[{addr} -> {host}] {method} '{uri}': {print_status} / agent: {agent}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn header_map(header: Option<&HeaderValue>, msg: String) -> String {
|
||||
header
|
||||
.map(|x| x.to_str().unwrap_or(&msg).to_string())
|
||||
.unwrap_or(msg)
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod logging;
|
||||
@@ -2,12 +2,12 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::NymNodeHttpError;
|
||||
use crate::middleware::logging;
|
||||
use crate::state::AppState;
|
||||
use crate::NymNodeHTTPServer;
|
||||
use axum::response::Redirect;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_http_api_common::middleware::logging;
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
use nym_node_requests::api::v1::gateway::models::{Gateway, Wireguard};
|
||||
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
|
||||
@@ -36,7 +36,7 @@ impl MixnodeConfig {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
pub struct Verloc {
|
||||
/// Socket address this node will use for binding its verloc API.
|
||||
/// default: `0.0.0.0:1790`
|
||||
|
||||
@@ -11,6 +11,7 @@ use nym_pemstore::store_keypair;
|
||||
use old_configs::old_config_v2::*;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -756,15 +757,16 @@ fn initialise(config: &WireguardV2) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn try_upgrade_config_v1<P: AsRef<Path>>(
|
||||
path: P,
|
||||
prev_config: Option<ConfigV1>,
|
||||
) -> Result<ConfigV2, NymNodeError> {
|
||||
tracing::debug!("Updating from 1.1.2");
|
||||
debug!("attempting to load v1 config...");
|
||||
let old_cfg = if let Some(prev_config) = prev_config {
|
||||
prev_config
|
||||
} else {
|
||||
ConfigV1::read_from_path(&path)?
|
||||
ConfigV1::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))?
|
||||
};
|
||||
let wireguard = WireguardV2 {
|
||||
enabled: old_cfg.wireguard.enabled,
|
||||
|
||||
@@ -16,6 +16,7 @@ use nym_sphinx_acknowledgements::AckKey;
|
||||
use old_configs::old_config_v3::*;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -786,15 +787,16 @@ pub async fn initialise(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn try_upgrade_config_v2<P: AsRef<Path>>(
|
||||
path: P,
|
||||
prev_config: Option<ConfigV2>,
|
||||
) -> Result<ConfigV3, NymNodeError> {
|
||||
tracing::debug!("Updating from 1.1.3");
|
||||
debug!("attempting to load v2 config...");
|
||||
let old_cfg = if let Some(prev_config) = prev_config {
|
||||
prev_config
|
||||
} else {
|
||||
ConfigV2::read_from_path(&path)?
|
||||
ConfigV2::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))?
|
||||
};
|
||||
|
||||
let authenticator_paths = AuthenticatorPathsV3::new(
|
||||
|
||||
@@ -17,6 +17,7 @@ use old_configs::old_config_v4::*;
|
||||
use persistence::*;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -938,15 +939,16 @@ pub async fn initialise(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn try_upgrade_config_v3<P: AsRef<Path>>(
|
||||
path: P,
|
||||
prev_config: Option<ConfigV3>,
|
||||
) -> Result<ConfigV4, NymNodeError> {
|
||||
tracing::debug!("Updating from 1.1.4");
|
||||
debug!("attempting to load v3 config...");
|
||||
let old_cfg = if let Some(prev_config) = prev_config {
|
||||
prev_config
|
||||
} else {
|
||||
ConfigV3::read_from_path(&path)?
|
||||
ConfigV3::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))?
|
||||
};
|
||||
|
||||
let exit_gateway_paths = ExitGatewayPaths::new(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user