lp-reg gw flow working-ish
This commit is contained in:
Generated
+5
@@ -5920,6 +5920,7 @@ dependencies = [
|
||||
"clap",
|
||||
"futures",
|
||||
"hex",
|
||||
"nym-api-requests",
|
||||
"nym-authenticator-client",
|
||||
"nym-authenticator-requests",
|
||||
"nym-bandwidth-controller",
|
||||
@@ -5936,6 +5937,9 @@ dependencies = [
|
||||
"nym-ip-packet-client",
|
||||
"nym-ip-packet-requests",
|
||||
"nym-lp",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-node-requests",
|
||||
"nym-node-status-client",
|
||||
"nym-registration-client",
|
||||
"nym-registration-common",
|
||||
@@ -5947,6 +5951,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
|
||||
@@ -39,6 +39,10 @@ impl PrivateKey {
|
||||
Ok(PrivateKey(SphinxPrivateKey::from(bytes)))
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
|
||||
PrivateKey(SphinxPrivateKey::from(*bytes))
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
let public_key = SphinxPublicKey::from(&self.0);
|
||||
PublicKey(public_key)
|
||||
@@ -102,6 +106,21 @@ impl Keypair {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_private_key(private_key: PrivateKey) -> Self {
|
||||
let public_key = private_key.public_key();
|
||||
Self {
|
||||
private_key,
|
||||
public_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_keys(private_key: PrivateKey, public_key: PublicKey) -> Self {
|
||||
Self {
|
||||
private_key,
|
||||
public_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn private_key(&self) -> &PrivateKey {
|
||||
&self.private_key
|
||||
}
|
||||
|
||||
@@ -225,37 +225,37 @@ impl LpStateMachine {
|
||||
// LpState::Closed { reason }
|
||||
LpState::Handshaking { session }
|
||||
} else {
|
||||
// 4. Check if handshake is now complete
|
||||
if session.is_handshake_complete() {
|
||||
result_action = Some(Ok(LpAction::HandshakeComplete));
|
||||
LpState::Transport { session } // Transition to Transport
|
||||
} else {
|
||||
// 5. Check if we need to send the next handshake message
|
||||
match session.prepare_handshake_message() {
|
||||
Some(Ok(message)) => {
|
||||
match session.next_packet(message) {
|
||||
Ok(response_packet) => {
|
||||
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
|
||||
// Check AGAIN if handshake became complete *after preparing*
|
||||
if session.is_handshake_complete() {
|
||||
LpState::Transport { session } // Transition to Transport
|
||||
} else {
|
||||
LpState::Handshaking { session } // Remain Handshaking
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let reason = e.to_string();
|
||||
result_action = Some(Err(e));
|
||||
LpState::Closed { reason }
|
||||
// 4. First check if we need to send a handshake message (before checking completion)
|
||||
match session.prepare_handshake_message() {
|
||||
Some(Ok(message)) => {
|
||||
match session.next_packet(message) {
|
||||
Ok(response_packet) => {
|
||||
result_action = Some(Ok(LpAction::SendPacket(response_packet)));
|
||||
// Check if handshake became complete after preparing message
|
||||
if session.is_handshake_complete() {
|
||||
LpState::Transport { session } // Transition to Transport
|
||||
} else {
|
||||
LpState::Handshaking { session } // Remain Handshaking
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let reason = e.to_string();
|
||||
result_action = Some(Err(e));
|
||||
LpState::Closed { reason }
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
let reason = e.to_string();
|
||||
result_action = Some(Err(e));
|
||||
LpState::Closed { reason }
|
||||
}
|
||||
None => {
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
let reason = e.to_string();
|
||||
result_action = Some(Err(e));
|
||||
LpState::Closed { reason }
|
||||
}
|
||||
None => {
|
||||
// 5. No message to send - check if handshake is complete
|
||||
if session.is_handshake_complete() {
|
||||
result_action = Some(Ok(LpAction::HandshakeComplete));
|
||||
LpState::Transport { session } // Transition to Transport
|
||||
} else {
|
||||
// Handshake stalled unexpectedly
|
||||
let err = LpError::NoiseError(NoiseError::Other(
|
||||
"Handshake stalled unexpectedly".to_string(),
|
||||
|
||||
@@ -164,7 +164,7 @@ pub struct WireguardData {
|
||||
/// Start wireguard device
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn start_wireguard(
|
||||
ecash_manager: Arc<EcashManager>,
|
||||
ecash_manager: Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>,
|
||||
metrics: nym_node_metrics::NymNodeMetrics,
|
||||
peers: Vec<Peer>,
|
||||
upgrade_mode_status: nym_credential_verification::upgrade_mode::UpgradeModeStatus,
|
||||
|
||||
@@ -81,6 +81,8 @@ Ports published to host:
|
||||
- 10001-10004 → Mixnet ports
|
||||
- 20001-20004 → Verloc ports
|
||||
- 30001-30004 → HTTP APIs
|
||||
- 41264 → LP control port (registration)
|
||||
- 51264 → LP data port
|
||||
|
||||
### Startup Flow
|
||||
|
||||
@@ -246,6 +248,41 @@ container logs nym-gateway --follow &
|
||||
curl -L --socks5 localhost:1080 https://nymtech.com
|
||||
```
|
||||
|
||||
### LP (Lewes Protocol) Testing
|
||||
|
||||
The gateway is configured with LP listener enabled and **mock ecash verification** for testing:
|
||||
|
||||
```bash
|
||||
# LP listener ports (exposed on host):
|
||||
# - 41264: LP control port (TCP registration)
|
||||
# - 51264: LP data port
|
||||
|
||||
# Check LP ports are listening
|
||||
nc -zv localhost 41264
|
||||
nc -zv localhost 51264
|
||||
|
||||
# Test LP registration with nym-gateway-probe
|
||||
cargo run -p nym-gateway-probe run-local \
|
||||
--mnemonic "test mnemonic here" \
|
||||
--gateway-ip 'localhost:41264' \
|
||||
--only-lp-registration
|
||||
```
|
||||
|
||||
**Mock Ecash Mode**:
|
||||
- Gateway uses `--lp.use-mock-ecash true` flag
|
||||
- Accepts ANY bandwidth credential without blockchain verification
|
||||
- Perfect for testing LP protocol implementation
|
||||
- **WARNING**: Never use mock ecash in production!
|
||||
|
||||
**Testing without blockchain**:
|
||||
The mock ecash manager allows testing the complete LP registration flow without requiring:
|
||||
- Running nyxd blockchain
|
||||
- Deploying smart contracts
|
||||
- Acquiring real bandwidth credentials
|
||||
- Setting up coconut signers
|
||||
|
||||
This makes localnet perfect for rapid LP protocol development and testing.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
|
||||
@@ -230,6 +230,8 @@ start_gateway() {
|
||||
-p 10004:10004 \
|
||||
-p 20004:20004 \
|
||||
-p 30004:30004 \
|
||||
-p 41264:41264 \
|
||||
-p 51264:51264 \
|
||||
-v "$VOLUME_PATH:/localnet" \
|
||||
-v "$NYM_VOLUME_PATH:/root/.nym" \
|
||||
-d \
|
||||
@@ -250,14 +252,17 @@ start_gateway() {
|
||||
--http-bind-address=0.0.0.0:30004 \
|
||||
--http-access-token=lala \
|
||||
--public-ips $CONTAINER_IP \
|
||||
--enable-lp true \
|
||||
--lp-use-mock-ecash true \
|
||||
--output=json \
|
||||
--wireguard-enabled true \
|
||||
--bonding-information-output="/localnet/gateway.json";
|
||||
|
||||
echo "Waiting for network.json...";
|
||||
while [ ! -f /localnet/network.json ]; do
|
||||
sleep 2;
|
||||
done;
|
||||
echo "Starting gateway...";
|
||||
echo "Starting gateway with LP listener (mock ecash)...";
|
||||
exec nym-node run --id gateway-localnet --unsafe-disable-replay-protection --local
|
||||
'
|
||||
|
||||
@@ -429,13 +434,36 @@ show_status() {
|
||||
|
||||
echo ""
|
||||
log_info "Port status:"
|
||||
for port in 9000 1080 10001 10002 10003 10004; do
|
||||
echo " Mixnet:"
|
||||
for port in 10001 10002 10003 10004; do
|
||||
if nc -z 127.0.0.1 $port 2>/dev/null; then
|
||||
echo -e " ${GREEN}●${NC} Port $port - listening"
|
||||
echo -e " ${GREEN}●${NC} Port $port - listening"
|
||||
else
|
||||
echo -e " ${RED}○${NC} Port $port - not listening"
|
||||
echo -e " ${RED}○${NC} Port $port - not listening"
|
||||
fi
|
||||
done
|
||||
echo " Gateway:"
|
||||
for port in 9000 30004; do
|
||||
if nc -z 127.0.0.1 $port 2>/dev/null; then
|
||||
echo -e " ${GREEN}●${NC} Port $port - listening"
|
||||
else
|
||||
echo -e " ${RED}○${NC} Port $port - not listening"
|
||||
fi
|
||||
done
|
||||
echo " LP (Lewes Protocol):"
|
||||
for port in 41264 51264; do
|
||||
if nc -z 127.0.0.1 $port 2>/dev/null; then
|
||||
echo -e " ${GREEN}●${NC} Port $port - listening"
|
||||
else
|
||||
echo -e " ${RED}○${NC} Port $port - not listening"
|
||||
fi
|
||||
done
|
||||
echo " SOCKS5:"
|
||||
if nc -z 127.0.0.1 1080 2>/dev/null; then
|
||||
echo -e " ${GREEN}●${NC} Port 1080 - listening"
|
||||
else
|
||||
echo -e " ${RED}○${NC} Port 1080 - not listening"
|
||||
fi
|
||||
}
|
||||
|
||||
# Build network topology with container IPs
|
||||
|
||||
@@ -7,7 +7,7 @@ use super::registration::process_registration;
|
||||
use super::LpHandlerState;
|
||||
use crate::error::GatewayError;
|
||||
use nym_lp::{
|
||||
keypair::{Keypair, PublicKey},
|
||||
keypair::{Keypair, PrivateKey as LpPrivateKey, PublicKey},
|
||||
LpMessage, LpPacket, LpSession,
|
||||
};
|
||||
use nym_metrics::{add_histogram_obs, inc};
|
||||
@@ -109,10 +109,21 @@ impl LpConnectionHandler {
|
||||
// 2. Client's public key (will be received during handshake)
|
||||
// 3. PSK (pre-shared key) - for now use a placeholder
|
||||
|
||||
// Generate fresh LP keypair (x25519) for this connection
|
||||
// Using Keypair::default() which generates a new random x25519 keypair
|
||||
// This is secure and simple - each connection gets its own keypair
|
||||
let gateway_keypair = Keypair::default();
|
||||
// Derive LP keypair from gateway's ed25519 identity using proper conversion
|
||||
// This creates a valid x25519 keypair for ECDH operations in Noise protocol
|
||||
let x25519_private = self.state.local_identity.private_key().to_x25519();
|
||||
let x25519_public = self.state.local_identity.public_key().to_x25519()
|
||||
.map_err(|e| GatewayError::LpHandshakeError(
|
||||
format!("Failed to convert ed25519 public key to x25519: {}", e)
|
||||
))?;
|
||||
|
||||
let lp_private = LpPrivateKey::from_bytes(x25519_private.as_bytes());
|
||||
let lp_public = PublicKey::from_bytes(x25519_public.as_bytes())
|
||||
.map_err(|e| GatewayError::LpHandshakeError(
|
||||
format!("Failed to create LP public key: {}", e)
|
||||
))?;
|
||||
|
||||
let gateway_keypair = Keypair::from_keys(lp_private, lp_public);
|
||||
|
||||
// Receive client's public key and salt via ClientHello message
|
||||
// The client initiates by sending ClientHello as first packet
|
||||
@@ -289,11 +300,7 @@ impl LpConnectionHandler {
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("System time before UNIX epoch")
|
||||
.as_secs();
|
||||
if now >= timestamp {
|
||||
now - timestamp
|
||||
} else {
|
||||
timestamp - now
|
||||
}
|
||||
now.abs_diff(timestamp)
|
||||
},
|
||||
self.state.lp_config.timestamp_tolerance_secs
|
||||
);
|
||||
@@ -333,22 +340,20 @@ impl LpConnectionHandler {
|
||||
)));
|
||||
}
|
||||
|
||||
// Extract registration request from LP message
|
||||
match packet.message() {
|
||||
LpMessage::EncryptedData(data) => {
|
||||
// Deserialize registration request
|
||||
bincode::deserialize(&data).map_err(|e| {
|
||||
GatewayError::LpProtocolError(format!(
|
||||
"Failed to deserialize registration request: {}",
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
other => Err(GatewayError::LpProtocolError(format!(
|
||||
"Expected EncryptedData message, got {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
// Decrypt the packet payload using the established session
|
||||
let decrypted_bytes = session
|
||||
.decrypt_data(packet.message())
|
||||
.map_err(|e| {
|
||||
GatewayError::LpProtocolError(format!("Failed to decrypt registration request: {}", e))
|
||||
})?;
|
||||
|
||||
// Deserialize the decrypted bytes into LpRegistrationRequest
|
||||
bincode::deserialize(&decrypted_bytes).map_err(|e| {
|
||||
GatewayError::LpProtocolError(format!(
|
||||
"Failed to deserialize registration request: {}",
|
||||
e
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Send registration response after processing
|
||||
|
||||
@@ -108,17 +108,29 @@ pub struct LpConfig {
|
||||
/// Recommended: 30-60 seconds
|
||||
#[serde(default = "default_timestamp_tolerance_secs")]
|
||||
pub timestamp_tolerance_secs: u64,
|
||||
|
||||
/// Use mock ecash manager for testing (default: false)
|
||||
///
|
||||
/// When enabled, the LP listener will use a mock ecash verifier that
|
||||
/// accepts any credential without blockchain verification. This is
|
||||
/// useful for testing the LP protocol implementation without requiring
|
||||
/// a full blockchain/contract setup.
|
||||
///
|
||||
/// WARNING: Only use this for local testing! Never enable in production.
|
||||
#[serde(default = "default_use_mock_ecash")]
|
||||
pub use_mock_ecash: bool,
|
||||
}
|
||||
|
||||
impl Default for LpConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
bind_address: default_bind_address(),
|
||||
control_port: default_control_port(),
|
||||
data_port: default_data_port(),
|
||||
max_connections: default_max_connections(),
|
||||
timestamp_tolerance_secs: default_timestamp_tolerance_secs(),
|
||||
use_mock_ecash: default_use_mock_ecash(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,6 +155,10 @@ fn default_timestamp_tolerance_secs() -> u64 {
|
||||
30 // 30 seconds - balances security vs clock skew tolerance
|
||||
}
|
||||
|
||||
fn default_use_mock_ecash() -> bool {
|
||||
false // Always default to real ecash for security
|
||||
}
|
||||
|
||||
/// Shared state for LP connection handlers
|
||||
#[derive(Clone)]
|
||||
pub struct LpHandlerState {
|
||||
|
||||
@@ -315,8 +315,8 @@ async fn register_wg_peer(
|
||||
.unwrap_or_else(|_| SocketAddr::from_str("0.0.0.0:51820").unwrap()),
|
||||
);
|
||||
peer.allowed_ips = vec![
|
||||
format!("{}/32", client_ipv4).parse().unwrap(),
|
||||
format!("{}/128", client_ipv6).parse().unwrap(),
|
||||
format!("{client_ipv4}/32").parse().unwrap(),
|
||||
format!("{client_ipv6}/128").parse().unwrap(),
|
||||
];
|
||||
peer.persistent_keepalive_interval = Some(25);
|
||||
|
||||
|
||||
+21
-9
@@ -11,7 +11,7 @@ use crate::node::internal_service_providers::{
|
||||
use crate::node::stale_data_cleaner::StaleMessagesCleaner;
|
||||
use futures::channel::oneshot;
|
||||
use nym_credential_verification::ecash::{
|
||||
credential_sender::CredentialHandlerConfig, EcashManager,
|
||||
credential_sender::CredentialHandlerConfig, EcashManager, MockEcashManager,
|
||||
};
|
||||
use nym_credential_verification::upgrade_mode::{
|
||||
UpgradeModeCheckConfig, UpgradeModeDetails, UpgradeModeState,
|
||||
@@ -106,7 +106,8 @@ pub struct GatewayTasksBuilder {
|
||||
shutdown_tracker: ShutdownTracker,
|
||||
|
||||
// populated and cached as necessary
|
||||
ecash_manager: Option<Arc<EcashManager>>,
|
||||
ecash_manager:
|
||||
Option<Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>>,
|
||||
|
||||
wireguard_peers: Option<Vec<defguard_wireguard_rs::host::Peer>>,
|
||||
|
||||
@@ -213,7 +214,21 @@ impl GatewayTasksBuilder {
|
||||
Ok(nyxd_client)
|
||||
}
|
||||
|
||||
async fn build_ecash_manager(&self) -> Result<Arc<EcashManager>, GatewayError> {
|
||||
async fn build_ecash_manager(
|
||||
&self,
|
||||
) -> Result<
|
||||
Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>,
|
||||
GatewayError,
|
||||
> {
|
||||
// Check if we should use mock ecash for testing
|
||||
if self.config.lp.use_mock_ecash {
|
||||
info!("Using MockEcashManager for LP testing (credentials NOT verified)");
|
||||
let mock_manager = MockEcashManager::new(Box::new(self.storage.clone()));
|
||||
return Ok(Arc::new(mock_manager)
|
||||
as Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>);
|
||||
}
|
||||
|
||||
// Production path: use real EcashManager with blockchain verification
|
||||
let handler_config = CredentialHandlerConfig {
|
||||
revocation_bandwidth_penalty: self
|
||||
.config
|
||||
@@ -246,7 +261,8 @@ impl GatewayTasksBuilder {
|
||||
"EcashCredentialHandler",
|
||||
);
|
||||
|
||||
Ok(Arc::new(ecash_manager))
|
||||
Ok(Arc::new(ecash_manager)
|
||||
as Arc<dyn nym_credential_verification::ecash::traits::EcashManager + Send + Sync>)
|
||||
}
|
||||
|
||||
async fn ecash_manager(
|
||||
@@ -303,11 +319,7 @@ impl GatewayTasksBuilder {
|
||||
active_clients_store: ActiveClientsStore,
|
||||
) -> Result<lp_listener::LpListener, GatewayError> {
|
||||
// Get WireGuard peer controller if available
|
||||
let wg_peer_controller = if let Some(wg_data) = &self.wireguard_data {
|
||||
Some(wg_data.inner.peer_tx().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let wg_peer_controller = self.wireguard_data.as_ref().map(|wg_data| wg_data.inner.peer_tx().clone());
|
||||
|
||||
let handler_state = lp_listener::LpHandlerState {
|
||||
ecash_verifier: self.ecash_manager().await?,
|
||||
|
||||
@@ -41,6 +41,7 @@ x25519-dalek = { workspace = true, features = [
|
||||
"static_secrets",
|
||||
] }
|
||||
|
||||
nym-api-requests = { path = "../nym-api/nym-api-requests" }
|
||||
nym-authenticator-requests = { path = "../common/authenticator-requests" }
|
||||
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
|
||||
nym-bin-common = { path = "../common/bin-common" }
|
||||
@@ -59,9 +60,13 @@ nym-credentials = { path = "../common/credentials" }
|
||||
nym-http-api-client-macro = { path = "../common/http-api-client-macro" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-node-status-client = { path = "../nym-node-status-api/nym-node-status-client" }
|
||||
nym-node-requests = { path = "../nym-node/nym-node-requests" }
|
||||
nym-registration-client = { path = "../nym-registration-client" }
|
||||
nym-lp = { path = "../common/nym-lp" }
|
||||
nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-registration-common = { path = "../common/registration" }
|
||||
time = { workspace = true }
|
||||
|
||||
# TEMP: REMOVE BEFORE PR
|
||||
nym-topology = { path = "../common/topology" }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use anyhow::{Context, bail};
|
||||
use nym_bandwidth_controller::error::BandwidthControllerError;
|
||||
use nym_client_core::client::base_client::storage::OnDiskPersistent;
|
||||
use nym_credentials::CredentialSpendingData;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_node_status_client::models::AttachedTicketMaterials;
|
||||
use nym_sdk::bandwidth::BandwidthImporter;
|
||||
@@ -155,3 +156,87 @@ pub(crate) async fn acquire_bandwidth(
|
||||
|
||||
bail!("failed to acquire bandwidth after {MAX_RETRIES} attempts")
|
||||
}
|
||||
|
||||
/// Create a dummy credential for mock ecash testing
|
||||
///
|
||||
/// Gateway with --lp-use-mock-ecash accepts any credential without verification,
|
||||
/// so we only need to provide properly structured data with correct types.
|
||||
///
|
||||
/// This is useful for local testing without requiring blockchain access or funded accounts.
|
||||
///
|
||||
/// This uses a pre-serialized test credential from the wireguard tests - since MockEcashManager
|
||||
/// doesn't verify anything, any valid CredentialSpendingData structure will work.
|
||||
pub(crate) fn create_dummy_credential(
|
||||
_gateway_identity: &[u8; 32],
|
||||
_ticket_type: TicketType,
|
||||
) -> CredentialSpendingData {
|
||||
// This is a valid serialized CredentialSpendingData taken from integration tests
|
||||
// See: common/wireguard-private-metadata/tests/src/lib.rs:CREDENTIAL_BYTES
|
||||
const CREDENTIAL_BYTES: [u8; 1245] = [
|
||||
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
|
||||
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
|
||||
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
|
||||
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
|
||||
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
|
||||
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
|
||||
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
|
||||
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
|
||||
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
|
||||
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
|
||||
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
|
||||
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
|
||||
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
|
||||
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
|
||||
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
|
||||
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
|
||||
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
|
||||
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
|
||||
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
|
||||
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
|
||||
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
|
||||
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
|
||||
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
|
||||
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
|
||||
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
|
||||
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
|
||||
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
|
||||
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
|
||||
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
|
||||
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
|
||||
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
|
||||
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
|
||||
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
|
||||
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
|
||||
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
|
||||
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
|
||||
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
|
||||
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
|
||||
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
|
||||
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
|
||||
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
|
||||
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
|
||||
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
|
||||
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
|
||||
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
|
||||
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
|
||||
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
|
||||
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
|
||||
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
|
||||
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
|
||||
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
|
||||
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
|
||||
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
|
||||
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
|
||||
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
|
||||
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
|
||||
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
|
||||
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
|
||||
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
|
||||
0, 0, 0, 0, 0, 1,
|
||||
];
|
||||
|
||||
CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES)
|
||||
.expect("Failed to deserialize test credential - this is a bug in the test harness")
|
||||
}
|
||||
|
||||
+209
-24
@@ -60,7 +60,7 @@ pub mod nodes;
|
||||
mod types;
|
||||
|
||||
use crate::bandwidth_helpers::{acquire_bandwidth, import_bandwidth};
|
||||
use crate::nodes::NymApiDirectory;
|
||||
use crate::nodes::{DirectoryNode, NymApiDirectory};
|
||||
use nym_node_status_client::models::AttachedTicketMaterials;
|
||||
pub use types::{IpPingReplies, ProbeOutcome, ProbeResult};
|
||||
|
||||
@@ -155,6 +155,8 @@ pub struct Probe {
|
||||
amnezia_args: String,
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
/// Pre-queried gateway node (used when --gateway-ip is specified)
|
||||
direct_gateway_node: Option<DirectoryNode>,
|
||||
}
|
||||
|
||||
impl Probe {
|
||||
@@ -170,8 +172,28 @@ impl Probe {
|
||||
amnezia_args: "".into(),
|
||||
netstack_args,
|
||||
credentials_args,
|
||||
direct_gateway_node: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a probe with a pre-queried gateway node (for direct IP mode)
|
||||
pub fn new_with_gateway(
|
||||
entrypoint: NodeIdentity,
|
||||
tested_node: TestedNode,
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
gateway_node: DirectoryNode,
|
||||
) -> Self {
|
||||
Self {
|
||||
entrypoint,
|
||||
tested_node,
|
||||
amnezia_args: "".into(),
|
||||
netstack_args,
|
||||
credentials_args,
|
||||
direct_gateway_node: Some(gateway_node),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_amnezia(&mut self, args: &str) -> &Self {
|
||||
self.amnezia_args = args.to_string();
|
||||
self
|
||||
@@ -179,10 +201,11 @@ impl Probe {
|
||||
|
||||
pub async fn probe(
|
||||
self,
|
||||
directory: NymApiDirectory,
|
||||
directory: Option<NymApiDirectory>,
|
||||
nyxd_url: Url,
|
||||
ignore_egress_epoch_role: bool,
|
||||
only_wireguard: bool,
|
||||
only_lp_registration: bool,
|
||||
min_mixnet_performance: Option<u8>,
|
||||
) -> anyhow::Result<ProbeResult> {
|
||||
let tickets_materials = self.credentials_args.decode_attached_ticket_materials()?;
|
||||
@@ -218,6 +241,8 @@ impl Probe {
|
||||
nyxd_url,
|
||||
tested_entry,
|
||||
only_wireguard,
|
||||
only_lp_registration,
|
||||
false, // Not using mock ecash in regular probe mode
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -226,13 +251,23 @@ impl Probe {
|
||||
pub async fn probe_run_locally(
|
||||
self,
|
||||
config_dir: &PathBuf,
|
||||
mnemonic: &str,
|
||||
directory: NymApiDirectory,
|
||||
mnemonic: Option<&str>,
|
||||
directory: Option<NymApiDirectory>,
|
||||
nyxd_url: Url,
|
||||
ignore_egress_epoch_role: bool,
|
||||
only_wireguard: bool,
|
||||
only_lp_registration: bool,
|
||||
min_mixnet_performance: Option<u8>,
|
||||
use_mock_ecash: bool,
|
||||
) -> anyhow::Result<ProbeResult> {
|
||||
// If only testing LP registration, use the dedicated LP-only path
|
||||
// This skips mixnet setup entirely and allows testing local gateways
|
||||
if only_lp_registration {
|
||||
return self
|
||||
.probe_lp_only(config_dir, directory, nyxd_url, use_mock_ecash)
|
||||
.await;
|
||||
}
|
||||
|
||||
let tested_entry = self.tested_node.is_same_as_entry();
|
||||
let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?;
|
||||
|
||||
@@ -278,7 +313,11 @@ impl Probe {
|
||||
|
||||
info!("Credential store contains {} ticketbooks", ticketbook_count);
|
||||
|
||||
if ticketbook_count < 1 {
|
||||
// Only acquire real bandwidth if not using mock ecash
|
||||
if ticketbook_count < 1 && !use_mock_ecash {
|
||||
let mnemonic = mnemonic.ok_or_else(|| {
|
||||
anyhow::anyhow!("mnemonic is required when not using mock ecash (--use-mock-ecash)")
|
||||
})?;
|
||||
for ticketbook_type in [
|
||||
TicketType::V1MixnetEntry,
|
||||
TicketType::V1WireguardEntry,
|
||||
@@ -286,6 +325,8 @@ impl Probe {
|
||||
] {
|
||||
acquire_bandwidth(mnemonic, &disconnected_mixnet_client, ticketbook_type).await?;
|
||||
}
|
||||
} else if use_mock_ecash {
|
||||
info!("Using mock ecash mode - skipping bandwidth acquisition");
|
||||
}
|
||||
|
||||
let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await;
|
||||
@@ -298,14 +339,113 @@ impl Probe {
|
||||
nyxd_url,
|
||||
tested_entry,
|
||||
only_wireguard,
|
||||
only_lp_registration,
|
||||
use_mock_ecash,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Probe LP registration only, skipping all mixnet tests
|
||||
/// This is useful for testing local dev gateways that aren't registered in nym-api
|
||||
pub async fn probe_lp_only(
|
||||
self,
|
||||
config_dir: &PathBuf,
|
||||
directory: Option<NymApiDirectory>,
|
||||
nyxd_url: Url,
|
||||
use_mock_ecash: bool,
|
||||
) -> anyhow::Result<ProbeResult> {
|
||||
let tested_entry = self.tested_node.is_same_as_entry();
|
||||
let (mixnet_entry_gateway_id, node_info) = self.lookup_gateway(&directory).await?;
|
||||
|
||||
if config_dir.is_file() {
|
||||
bail!("provided configuration directory is a file");
|
||||
}
|
||||
|
||||
if !config_dir.exists() {
|
||||
std::fs::create_dir_all(config_dir)?;
|
||||
}
|
||||
|
||||
let storage_paths = StoragePaths::new_from_dir(config_dir)?;
|
||||
let storage = storage_paths
|
||||
.initialise_default_persistent_storage()
|
||||
.await?;
|
||||
|
||||
let key_store = storage.key_store();
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Generate client keys if they don't exist
|
||||
if key_store.load_keys().await.is_err() {
|
||||
tracing::log::debug!("Generating new client keys");
|
||||
nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?;
|
||||
}
|
||||
|
||||
// Check if node has LP address
|
||||
let (lp_address, ip_address) = match (node_info.lp_address, node_info.ip_address) {
|
||||
(Some(lp_addr), Some(ip_addr)) => (lp_addr, ip_addr),
|
||||
_ => {
|
||||
bail!("Gateway does not have LP address configured");
|
||||
}
|
||||
};
|
||||
|
||||
info!("Testing LP registration for gateway {}", node_info.identity);
|
||||
|
||||
// Create bandwidth controller for credential preparation
|
||||
let config = nym_validator_client::nyxd::Config::try_from_nym_network_details(
|
||||
&NymNetworkDetails::new_from_env(),
|
||||
)?;
|
||||
let client = nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?;
|
||||
let bw_controller =
|
||||
nym_bandwidth_controller::BandwidthController::new(storage.credential_store().clone(), client);
|
||||
|
||||
// Run LP registration probe
|
||||
let lp_outcome = lp_registration_probe(
|
||||
node_info.identity,
|
||||
lp_address,
|
||||
ip_address,
|
||||
&bw_controller,
|
||||
use_mock_ecash,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
// Return result with only LP outcome
|
||||
Ok(ProbeResult {
|
||||
node: node_info.identity.to_string(),
|
||||
used_entry: mixnet_entry_gateway_id.to_string(),
|
||||
outcome: types::ProbeOutcome {
|
||||
as_entry: types::Entry::NotTested,
|
||||
as_exit: if tested_entry {
|
||||
None
|
||||
} else {
|
||||
Some(types::Exit::fail_to_connect())
|
||||
},
|
||||
wg: None,
|
||||
lp: Some(lp_outcome),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn lookup_gateway(
|
||||
&self,
|
||||
directory: &NymApiDirectory,
|
||||
directory: &Option<NymApiDirectory>,
|
||||
) -> anyhow::Result<(NodeIdentity, TestedNodeDetails)> {
|
||||
// If we have a pre-queried gateway node (direct IP mode), use that
|
||||
if let Some(direct_node) = &self.direct_gateway_node {
|
||||
info!("Using pre-queried gateway node from direct IP query");
|
||||
let node_info = direct_node.to_testable_node()?;
|
||||
info!("connecting to entry gateway: {}", direct_node.identity());
|
||||
debug!(
|
||||
"authenticator version: {:?}",
|
||||
node_info.authenticator_version
|
||||
);
|
||||
return Ok((self.entrypoint, node_info));
|
||||
}
|
||||
|
||||
// Otherwise, use the directory (original behavior)
|
||||
let directory = directory
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Directory is required when not using --gateway-ip"))?;
|
||||
|
||||
// Setup the entry gateways
|
||||
let entry_gateway = directory.entry_gateway(&self.entrypoint)?;
|
||||
|
||||
@@ -341,6 +481,8 @@ impl Probe {
|
||||
nyxd_url: Url,
|
||||
tested_entry: bool,
|
||||
only_wireguard: bool,
|
||||
only_lp_registration: bool,
|
||||
use_mock_ecash: bool,
|
||||
) -> anyhow::Result<ProbeResult>
|
||||
where
|
||||
T: MixnetClientStorage + Clone + 'static,
|
||||
@@ -375,7 +517,7 @@ impl Probe {
|
||||
info!("Our nym address: {nym_address}");
|
||||
|
||||
// Now that we have a connected mixnet client, we can start pinging
|
||||
let (outcome, mixnet_client) = if only_wireguard {
|
||||
let (outcome, mixnet_client) = if only_wireguard || only_lp_registration {
|
||||
(
|
||||
Ok(ProbeOutcome {
|
||||
as_entry: if tested_entry {
|
||||
@@ -399,7 +541,10 @@ impl Probe {
|
||||
.await
|
||||
};
|
||||
|
||||
let wg_outcome = if let (Some(authenticator), Some(ip_address)) =
|
||||
let wg_outcome = if only_lp_registration {
|
||||
// Skip WireGuard test when only testing LP registration
|
||||
WgProbeResults::default()
|
||||
} else if let (Some(authenticator), Some(ip_address)) =
|
||||
(node_info.authenticator_address, node_info.ip_address)
|
||||
{
|
||||
// Start the mixnet listener that the auth clients use to receive messages.
|
||||
@@ -474,6 +619,7 @@ impl Probe {
|
||||
lp_address,
|
||||
ip_address,
|
||||
&bw_controller,
|
||||
use_mock_ecash,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
@@ -684,6 +830,7 @@ async fn lp_registration_probe<St>(
|
||||
nym_validator_client::nyxd::NyxdClient<nym_validator_client::HttpRpcClient>,
|
||||
St,
|
||||
>,
|
||||
use_mock_ecash: bool,
|
||||
) -> anyhow::Result<types::LpProbeResults>
|
||||
where
|
||||
St: nym_sdk::mixnet::CredentialStorage + Clone + Send + Sync + 'static,
|
||||
@@ -699,11 +846,21 @@ where
|
||||
// Generate LP keypair for this connection
|
||||
let client_lp_keypair = std::sync::Arc::new(LpKeypair::default());
|
||||
|
||||
// Derive gateway LP public key from gateway identity (as done in registration-client)
|
||||
let gateway_lp_key = match LpPublicKey::from_bytes(&gateway_identity.to_bytes()) {
|
||||
// Derive gateway LP public key from gateway identity using proper ed25519→x25519 conversion
|
||||
let gateway_x25519_pub = match gateway_identity.to_x25519() {
|
||||
Ok(key) => key,
|
||||
Err(e) => {
|
||||
let error_msg = format!("Failed to derive gateway LP key: {}", e);
|
||||
let error_msg = format!("Failed to convert gateway ed25519 key to x25519: {}", e);
|
||||
error!("{}", error_msg);
|
||||
lp_outcome.error = Some(error_msg);
|
||||
return Ok(lp_outcome);
|
||||
}
|
||||
};
|
||||
|
||||
let gateway_lp_key = match LpPublicKey::from_bytes(gateway_x25519_pub.as_bytes()) {
|
||||
Ok(key) => key,
|
||||
Err(e) => {
|
||||
let error_msg = format!("Failed to create LP key from x25519 bytes: {}", e);
|
||||
error!("{}", error_msg);
|
||||
lp_outcome.error = Some(error_msg);
|
||||
return Ok(lp_outcome);
|
||||
@@ -766,20 +923,48 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
match client.send_registration_request(
|
||||
&wg_keypair,
|
||||
&gateway_ed25519_pubkey,
|
||||
bandwidth_controller,
|
||||
TicketType::V1WireguardEntry,
|
||||
).await {
|
||||
Ok(_) => {
|
||||
info!("LP registration request sent successfully");
|
||||
// Generate credential based on mode
|
||||
let ticket_type = TicketType::V1WireguardEntry;
|
||||
if use_mock_ecash {
|
||||
info!("Using mock ecash credential for LP registration");
|
||||
let credential = crate::bandwidth_helpers::create_dummy_credential(
|
||||
&gateway_ed25519_pubkey.to_bytes(),
|
||||
ticket_type,
|
||||
);
|
||||
|
||||
match client.send_registration_request_with_credential(
|
||||
&wg_keypair,
|
||||
&gateway_ed25519_pubkey,
|
||||
credential,
|
||||
ticket_type,
|
||||
).await {
|
||||
Ok(_) => {
|
||||
info!("LP registration request sent successfully with mock ecash");
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("Failed to send LP registration request: {}", e);
|
||||
error!("{}", error_msg);
|
||||
lp_outcome.error = Some(error_msg);
|
||||
return Ok(lp_outcome);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("Failed to send LP registration request: {}", e);
|
||||
error!("{}", error_msg);
|
||||
lp_outcome.error = Some(error_msg);
|
||||
return Ok(lp_outcome);
|
||||
} else {
|
||||
info!("Using real bandwidth controller for LP registration");
|
||||
match client.send_registration_request(
|
||||
&wg_keypair,
|
||||
&gateway_ed25519_pubkey,
|
||||
bandwidth_controller,
|
||||
ticket_type,
|
||||
).await {
|
||||
Ok(_) => {
|
||||
info!("LP registration request sent successfully with real ecash");
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("Failed to send LP registration request: {}", e);
|
||||
error!("{}", error_msg);
|
||||
lp_outcome.error = Some(error_msg);
|
||||
return Ok(lp_outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,25 @@
|
||||
|
||||
use crate::TestedNodeDetails;
|
||||
use anyhow::{Context, anyhow, bail};
|
||||
use nym_api_requests::models::{
|
||||
AuthenticatorDetails, DeclaredRoles, DescribedNodeType, HostInformation,
|
||||
IpPacketRouterDetails, NetworkRequesterDetails, NymNodeData, OffsetDateTimeJsonSchemaWrapper,
|
||||
WebSockets, WireguardDetails,
|
||||
};
|
||||
use nym_authenticator_requests::AuthenticatorVersion;
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT;
|
||||
use nym_node_requests::api::client::NymNodeApiClientExt;
|
||||
use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails;
|
||||
use nym_sdk::mixnet::NodeIdentity;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use rand::prelude::IteratorRandom;
|
||||
use std::collections::HashMap;
|
||||
use tracing::{debug, info};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{debug, info, warn};
|
||||
use url::Url;
|
||||
|
||||
// in the old behaviour we were getting all skimmed nodes to retrieve performance
|
||||
@@ -133,6 +144,172 @@ impl DirectoryNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Query a gateway directly by address using its self-described HTTP API endpoints.
|
||||
/// This bypasses the need for directory service lookup.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `address` - The address of the gateway (IP, IP:PORT, or HOST:PORT format)
|
||||
///
|
||||
/// # Returns
|
||||
/// A `DirectoryNode` containing all gateway metadata, or an error if the query fails
|
||||
pub async fn query_gateway_by_ip(address: String) -> anyhow::Result<DirectoryNode> {
|
||||
info!("Querying gateway directly at address: {}", address);
|
||||
|
||||
// Parse the address to check if it contains a port
|
||||
let addresses_to_try = if address.contains(':') {
|
||||
// Address already has port specified, use it directly
|
||||
vec![
|
||||
format!("http://{}", address),
|
||||
format!("https://{}", address),
|
||||
]
|
||||
} else {
|
||||
// No port specified, try multiple ports in order of likelihood
|
||||
vec![
|
||||
format!("http://{}:{}", address, DEFAULT_NYM_NODE_HTTP_PORT), // Standard port 8080
|
||||
format!("https://{}", address), // HTTPS proxy (443)
|
||||
format!("http://{}", address), // HTTP proxy (80)
|
||||
]
|
||||
};
|
||||
|
||||
let user_agent: UserAgent = nym_bin_common::bin_info_local_vergen!().into();
|
||||
let mut last_error = None;
|
||||
|
||||
for address in addresses_to_try {
|
||||
debug!("Trying to connect to gateway at: {}", address);
|
||||
|
||||
// Build client with timeout
|
||||
let client = match nym_node_requests::api::Client::builder(address.clone()) {
|
||||
Ok(builder) => match builder
|
||||
.with_timeout(Duration::from_secs(5))
|
||||
.no_hickory_dns()
|
||||
.with_user_agent(user_agent.clone())
|
||||
.build()
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!("Failed to build client for {}: {}", address, e);
|
||||
last_error = Some(e.into());
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("Failed to create client builder for {}: {}", address, e);
|
||||
last_error = Some(e.into());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the node is up
|
||||
match client.get_health().await {
|
||||
Ok(health) if health.status.is_up() => {
|
||||
info!("Successfully connected to gateway at {}", address);
|
||||
|
||||
// Query all required metadata concurrently
|
||||
let host_info_result = client.get_host_information().await;
|
||||
let roles_result = client.get_roles().await;
|
||||
let build_info_result = client.get_build_information().await;
|
||||
let aux_details_result = client.get_auxiliary_details().await;
|
||||
let websockets_result = client.get_mixnet_websockets().await;
|
||||
|
||||
// These are optional, so we use ok() to ignore errors
|
||||
let ipr_result = client.get_ip_packet_router().await.ok();
|
||||
let authenticator_result = client.get_authenticator().await.ok();
|
||||
let wireguard_result = client.get_wireguard().await.ok();
|
||||
|
||||
// Check required fields
|
||||
let host_info = host_info_result.context("Failed to get host information")?;
|
||||
let roles = roles_result.context("Failed to get roles")?;
|
||||
let build_info = build_info_result.context("Failed to get build information")?;
|
||||
let aux_details: NodeAuxiliaryDetails = aux_details_result.unwrap_or_default();
|
||||
let websockets = websockets_result.context("Failed to get websocket info")?;
|
||||
|
||||
// Verify node signature
|
||||
if !host_info.verify_host_information() {
|
||||
bail!("Gateway host information signature verification failed");
|
||||
}
|
||||
|
||||
// Verify it's actually a gateway
|
||||
if !roles.gateway_enabled {
|
||||
bail!("Node at {} is not configured as an entry gateway", address);
|
||||
}
|
||||
|
||||
// Convert to our internal types
|
||||
let network_requester: Option<NetworkRequesterDetails> = None; // Not needed for LP testing
|
||||
let ip_packet_router: Option<IpPacketRouterDetails> =
|
||||
ipr_result.map(|ipr| IpPacketRouterDetails {
|
||||
address: ipr.address,
|
||||
});
|
||||
let authenticator: Option<AuthenticatorDetails> =
|
||||
authenticator_result.map(|auth| AuthenticatorDetails {
|
||||
address: auth.address,
|
||||
});
|
||||
let wireguard: Option<WireguardDetails> = wireguard_result.map(|wg| WireguardDetails {
|
||||
port: wg.port,
|
||||
tunnel_port: wg.tunnel_port,
|
||||
metadata_port: wg.metadata_port,
|
||||
public_key: wg.public_key,
|
||||
});
|
||||
|
||||
// Construct NymNodeData
|
||||
let node_data = NymNodeData {
|
||||
last_polled: OffsetDateTimeJsonSchemaWrapper(OffsetDateTime::now_utc()),
|
||||
host_information: HostInformation {
|
||||
ip_address: host_info.data.ip_address,
|
||||
hostname: host_info.data.hostname,
|
||||
keys: host_info.data.keys.into(),
|
||||
},
|
||||
declared_role: DeclaredRoles {
|
||||
mixnode: roles.mixnode_enabled,
|
||||
entry: roles.gateway_enabled,
|
||||
exit_nr: roles.network_requester_enabled,
|
||||
exit_ipr: roles.ip_packet_router_enabled,
|
||||
},
|
||||
auxiliary_details: aux_details,
|
||||
build_information: BinaryBuildInformationOwned {
|
||||
binary_name: build_info.binary_name,
|
||||
build_timestamp: build_info.build_timestamp,
|
||||
build_version: build_info.build_version,
|
||||
commit_sha: build_info.commit_sha,
|
||||
commit_timestamp: build_info.commit_timestamp,
|
||||
commit_branch: build_info.commit_branch,
|
||||
rustc_version: build_info.rustc_version,
|
||||
rustc_channel: build_info.rustc_channel,
|
||||
cargo_triple: build_info.cargo_triple,
|
||||
cargo_profile: build_info.cargo_profile,
|
||||
},
|
||||
network_requester,
|
||||
ip_packet_router,
|
||||
authenticator,
|
||||
wireguard,
|
||||
mixnet_websockets: WebSockets {
|
||||
ws_port: websockets.ws_port,
|
||||
wss_port: websockets.wss_port,
|
||||
},
|
||||
};
|
||||
|
||||
// Create NymNodeDescription
|
||||
let described = NymNodeDescription {
|
||||
node_id: 0, // We don't have a node_id from direct query
|
||||
contract_node_type: DescribedNodeType::NymNode, // All new nodes are NymNode type
|
||||
description: node_data,
|
||||
};
|
||||
|
||||
return Ok(DirectoryNode { described });
|
||||
}
|
||||
Ok(_) => {
|
||||
warn!("Gateway at {} is not healthy", address);
|
||||
last_error = Some(anyhow!("Gateway is not healthy"));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Health check failed for {}: {}", address, e);
|
||||
last_error = Some(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error.unwrap_or_else(|| anyhow!("Failed to connect to gateway at {}", address)))
|
||||
}
|
||||
|
||||
pub struct NymApiDirectory {
|
||||
// nodes: HashMap<NodeIdentity, DescribedNodeWithPerformance>,
|
||||
nodes: HashMap<NodeIdentity, DirectoryNode>,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_config::defaults::setup_env;
|
||||
use nym_gateway_probe::nodes::NymApiDirectory;
|
||||
use nym_gateway_probe::nodes::{query_gateway_by_ip, NymApiDirectory};
|
||||
use nym_gateway_probe::{CredentialArgs, NetstackArgs, ProbeResult, TestedNode};
|
||||
use nym_sdk::mixnet::NodeIdentity;
|
||||
use std::path::Path;
|
||||
@@ -37,6 +37,11 @@ struct CliArgs {
|
||||
#[arg(long, short = 'g', alias = "gateway", global = true)]
|
||||
entry_gateway: Option<String>,
|
||||
|
||||
/// The address of the gateway to probe directly (bypasses directory lookup)
|
||||
/// Supports formats: IP (192.168.66.5), IP:PORT (192.168.66.5:8080), HOST:PORT (localhost:30004)
|
||||
#[arg(long, global = true)]
|
||||
gateway_ip: Option<String>,
|
||||
|
||||
/// Identity of the node to test
|
||||
#[arg(long, short, value_parser = validate_node_identity, global = true)]
|
||||
node: Option<NodeIdentity>,
|
||||
@@ -50,6 +55,9 @@ struct CliArgs {
|
||||
#[arg(long, global = true)]
|
||||
only_wireguard: bool,
|
||||
|
||||
#[arg(long, global = true)]
|
||||
only_lp_registration: bool,
|
||||
|
||||
/// Disable logging during probe
|
||||
#[arg(long, global = true)]
|
||||
ignore_egress_epoch_role: bool,
|
||||
@@ -76,12 +84,16 @@ const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/";
|
||||
enum Commands {
|
||||
/// Run the probe locally
|
||||
RunLocal {
|
||||
/// Provide a mnemonic to get credentials
|
||||
/// Provide a mnemonic to get credentials (optional when using --use-mock-ecash)
|
||||
#[arg(long)]
|
||||
mnemonic: String,
|
||||
mnemonic: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
config_dir: Option<PathBuf>,
|
||||
|
||||
/// Use mock ecash credentials for testing (requires gateway with --lp-use-mock-ecash)
|
||||
#[arg(long)]
|
||||
use_mock_ecash: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -116,18 +128,42 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
.first()
|
||||
.map(|ep| ep.nyxd_url())
|
||||
.ok_or(anyhow::anyhow!("missing nyxd url"))?;
|
||||
let api_url = network
|
||||
.endpoints
|
||||
.first()
|
||||
.and_then(|ep| ep.api_url())
|
||||
.ok_or(anyhow::anyhow!("missing nyxd url"))?;
|
||||
// If gateway IP is provided, query it directly without using the directory
|
||||
let (entry, directory, gateway_node) = if let Some(gateway_ip) = args.gateway_ip {
|
||||
info!("Using direct IP query mode for gateway: {}", gateway_ip);
|
||||
let gateway_node = query_gateway_by_ip(gateway_ip).await?;
|
||||
let identity = gateway_node.identity();
|
||||
|
||||
let directory = NymApiDirectory::new(api_url).await?;
|
||||
// Still create the directory for potential secondary lookups,
|
||||
// but only if API URL is available
|
||||
let directory = if let Some(api_url) = network
|
||||
.endpoints
|
||||
.first()
|
||||
.and_then(|ep| ep.api_url())
|
||||
{
|
||||
Some(NymApiDirectory::new(api_url).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let entry = if let Some(gateway) = &args.entry_gateway {
|
||||
NodeIdentity::from_base58_string(gateway)?
|
||||
(identity, directory, Some(gateway_node))
|
||||
} else {
|
||||
directory.random_exit_with_ipr()?
|
||||
// Original behavior: use directory service
|
||||
let api_url = network
|
||||
.endpoints
|
||||
.first()
|
||||
.and_then(|ep| ep.api_url())
|
||||
.ok_or(anyhow::anyhow!("missing api url"))?;
|
||||
|
||||
let directory = NymApiDirectory::new(api_url).await?;
|
||||
|
||||
let entry = if let Some(gateway) = &args.entry_gateway {
|
||||
NodeIdentity::from_base58_string(gateway)?
|
||||
} else {
|
||||
directory.random_exit_with_ipr()?
|
||||
};
|
||||
|
||||
(entry, Some(directory), None)
|
||||
};
|
||||
|
||||
let test_point = if let Some(node) = args.node {
|
||||
@@ -136,8 +172,18 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
TestedNode::SameAsEntry
|
||||
};
|
||||
|
||||
let mut trial =
|
||||
nym_gateway_probe::Probe::new(entry, test_point, args.netstack_args, args.credential_args);
|
||||
let mut trial = if let Some(gw_node) = gateway_node {
|
||||
nym_gateway_probe::Probe::new_with_gateway(
|
||||
entry,
|
||||
test_point,
|
||||
args.netstack_args,
|
||||
args.credential_args,
|
||||
gw_node,
|
||||
)
|
||||
} else {
|
||||
nym_gateway_probe::Probe::new(entry, test_point, args.netstack_args, args.credential_args)
|
||||
};
|
||||
|
||||
if let Some(awg_args) = args.amnezia_args {
|
||||
trial.with_amnezia(&awg_args);
|
||||
}
|
||||
@@ -146,6 +192,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
Some(Commands::RunLocal {
|
||||
mnemonic,
|
||||
config_dir,
|
||||
use_mock_ecash,
|
||||
}) => {
|
||||
let config_dir = config_dir
|
||||
.clone()
|
||||
@@ -158,12 +205,14 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
|
||||
Box::pin(trial.probe_run_locally(
|
||||
&config_dir,
|
||||
mnemonic,
|
||||
mnemonic.as_deref(),
|
||||
directory,
|
||||
nyxd_url,
|
||||
args.ignore_egress_epoch_role,
|
||||
args.only_wireguard,
|
||||
args.only_lp_registration,
|
||||
args.min_gateway_mixnet_performance,
|
||||
*use_mock_ecash,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@@ -173,6 +222,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
nyxd_url,
|
||||
args.ignore_egress_epoch_role,
|
||||
args.only_wireguard,
|
||||
args.only_lp_registration,
|
||||
args.min_gateway_mixnet_performance,
|
||||
))
|
||||
.await
|
||||
|
||||
@@ -446,6 +446,23 @@ pub(crate) struct EntryGatewayArgs {
|
||||
)]
|
||||
#[zeroize(skip)]
|
||||
pub(crate) upgrade_mode_attester_public_key: Option<ed25519::PublicKey>,
|
||||
|
||||
/// Enable LP (Lewes Protocol) listener for client registration.
|
||||
/// LP provides an alternative registration protocol with improved security features.
|
||||
#[clap(
|
||||
long,
|
||||
env = NYMNODE_ENABLE_LP_ARG
|
||||
)]
|
||||
pub(crate) enable_lp: Option<bool>,
|
||||
|
||||
/// Use mock ecash manager for LP testing.
|
||||
/// WARNING: Only use this for local testing! Never enable in production.
|
||||
/// When enabled, the LP listener will accept any credential without blockchain verification.
|
||||
#[clap(
|
||||
long,
|
||||
env = NYMNODE_LP_USE_MOCK_ECASH_ARG
|
||||
)]
|
||||
pub(crate) lp_use_mock_ecash: Option<bool>,
|
||||
}
|
||||
|
||||
impl EntryGatewayArgs {
|
||||
@@ -479,6 +496,12 @@ impl EntryGatewayArgs {
|
||||
if let Some(upgrade_mode_attester_public_key) = self.upgrade_mode_attester_public_key {
|
||||
section.upgrade_mode.attester_public_key = upgrade_mode_attester_public_key
|
||||
}
|
||||
if let Some(enable_lp) = self.enable_lp {
|
||||
section.lp.enabled = enable_lp
|
||||
}
|
||||
if let Some(use_mock_ecash) = self.lp_use_mock_ecash {
|
||||
section.lp.use_mock_ecash = use_mock_ecash
|
||||
}
|
||||
|
||||
section
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ pub mod vars {
|
||||
"NYMNODE_UPGRADE_MODE_ATTESTATION_URL";
|
||||
pub const NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY_ARG: &str =
|
||||
"NYMNODE_UPGRADE_MODE_ATTESTER_PUBKEY";
|
||||
pub const NYMNODE_ENABLE_LP_ARG: &str = "NYMNODE_ENABLE_LP";
|
||||
pub const NYMNODE_LP_USE_MOCK_ECASH_ARG: &str = "NYMNODE_LP_USE_MOCK_ECASH";
|
||||
|
||||
// exit gateway:
|
||||
pub const NYMNODE_UPSTREAM_EXIT_POLICY_ARG: &str = "NYMNODE_UPSTREAM_EXIT_POLICY";
|
||||
|
||||
@@ -286,6 +286,7 @@ pub enum BuilderConfigError {
|
||||
///
|
||||
/// This provides a more convenient way to construct a `BuilderConfig` compared to the
|
||||
/// `new()` constructor with many arguments.
|
||||
#[derive(Default)]
|
||||
pub struct BuilderConfigBuilder {
|
||||
entry_node: Option<NymNodeWithKeys>,
|
||||
exit_node: Option<NymNodeWithKeys>,
|
||||
@@ -300,24 +301,6 @@ pub struct BuilderConfigBuilder {
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl Default for BuilderConfigBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entry_node: None,
|
||||
exit_node: None,
|
||||
data_path: None,
|
||||
mixnet_client_config: None,
|
||||
mode: None,
|
||||
user_agent: None,
|
||||
custom_topology_provider: None,
|
||||
network_env: None,
|
||||
cancel_token: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuilderConfigBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
|
||||
@@ -171,29 +171,46 @@ impl RegistrationClient {
|
||||
tracing::debug!("Entry gateway LP address: {}", entry_lp_address);
|
||||
tracing::debug!("Exit gateway LP address: {}", exit_lp_address);
|
||||
|
||||
// For now, use gateway identities as LP public keys
|
||||
// TODO(nym-87): Implement proper key derivation
|
||||
let entry_gateway_lp_key =
|
||||
LpPublicKey::from_bytes(&self.config.entry.node.identity.to_bytes()).map_err(|e| {
|
||||
RegistrationClientError::LpRegistrationNotPossible {
|
||||
node_id: format!(
|
||||
"{}: invalid LP key: {}",
|
||||
self.config.entry.node.identity.to_base58_string(),
|
||||
e
|
||||
),
|
||||
}
|
||||
})?;
|
||||
// Convert gateway ed25519 identities to x25519 LP public keys using proper conversion
|
||||
let entry_x25519_pub = self.config.entry.node.identity.to_x25519().map_err(|e| {
|
||||
RegistrationClientError::LpRegistrationNotPossible {
|
||||
node_id: format!(
|
||||
"{}: failed to convert ed25519 to x25519: {}",
|
||||
self.config.entry.node.identity.to_base58_string(),
|
||||
e
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
let exit_gateway_lp_key =
|
||||
LpPublicKey::from_bytes(&self.config.exit.node.identity.to_bytes()).map_err(|e| {
|
||||
RegistrationClientError::LpRegistrationNotPossible {
|
||||
node_id: format!(
|
||||
"{}: invalid LP key: {}",
|
||||
self.config.exit.node.identity.to_base58_string(),
|
||||
e
|
||||
),
|
||||
}
|
||||
})?;
|
||||
let entry_gateway_lp_key = LpPublicKey::from_bytes(entry_x25519_pub.as_bytes()).map_err(|e| {
|
||||
RegistrationClientError::LpRegistrationNotPossible {
|
||||
node_id: format!(
|
||||
"{}: invalid LP key: {}",
|
||||
self.config.entry.node.identity.to_base58_string(),
|
||||
e
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
let exit_x25519_pub = self.config.exit.node.identity.to_x25519().map_err(|e| {
|
||||
RegistrationClientError::LpRegistrationNotPossible {
|
||||
node_id: format!(
|
||||
"{}: failed to convert ed25519 to x25519: {}",
|
||||
self.config.exit.node.identity.to_base58_string(),
|
||||
e
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
let exit_gateway_lp_key = LpPublicKey::from_bytes(exit_x25519_pub.as_bytes()).map_err(|e| {
|
||||
RegistrationClientError::LpRegistrationNotPossible {
|
||||
node_id: format!(
|
||||
"{}: invalid LP key: {}",
|
||||
self.config.exit.node.identity.to_base58_string(),
|
||||
e
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
// Generate LP keypairs for this connection
|
||||
let client_lp_keypair = Arc::new(LpKeypair::default());
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::error::{LpClientError, Result};
|
||||
use super::transport::LpTransport;
|
||||
use bytes::BytesMut;
|
||||
use nym_bandwidth_controller::{BandwidthTicketProvider, DEFAULT_TICKETS_TO_SPEND};
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_credentials_interface::{CredentialSpendingData, TicketType};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_lp::LpPacket;
|
||||
use nym_lp::codec::{parse_lp_packet, serialize_lp_packet};
|
||||
@@ -267,7 +267,7 @@ impl LpRegistrationClient {
|
||||
// Step 4: Create state machine as initiator with derived PSK
|
||||
let mut state_machine = LpStateMachine::new(
|
||||
true, // is_initiator
|
||||
&*self.local_keypair,
|
||||
&self.local_keypair,
|
||||
&self.gateway_public_key,
|
||||
&psk,
|
||||
)?;
|
||||
@@ -300,6 +300,13 @@ impl LpRegistrationClient {
|
||||
LpAction::SendPacket(response_packet) => {
|
||||
tracing::trace!("Sending handshake response packet");
|
||||
Self::send_packet(stream, &response_packet).await?;
|
||||
|
||||
// Check if handshake completed after sending this packet
|
||||
// (e.g., initiator completes after sending final message)
|
||||
if state_machine.session()?.is_handshake_complete() {
|
||||
tracing::info!("LP handshake completed after sending packet");
|
||||
break;
|
||||
}
|
||||
}
|
||||
LpAction::HandshakeComplete => {
|
||||
tracing::info!("LP handshake completed successfully");
|
||||
@@ -500,6 +507,80 @@ impl LpRegistrationClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends LP registration request with a pre-generated credential.
|
||||
/// This is useful for testing with mock ecash credentials.
|
||||
///
|
||||
/// This implements the LP registration request sending:
|
||||
/// 1. Uses pre-provided bandwidth credential (skips acquisition)
|
||||
/// 2. Constructs LpRegistrationRequest with dVPN mode
|
||||
/// 3. Serializes request to bytes using bincode
|
||||
/// 4. Encrypts via LP state machine (LpInput::SendData)
|
||||
/// 5. Sends encrypted packet to gateway
|
||||
pub async fn send_registration_request_with_credential(
|
||||
&mut self,
|
||||
wg_keypair: &x25519::KeyPair,
|
||||
_gateway_identity: &ed25519::PublicKey,
|
||||
credential: CredentialSpendingData,
|
||||
ticket_type: TicketType,
|
||||
) -> Result<()> {
|
||||
// Ensure we have a TCP connection
|
||||
let stream = self.tcp_stream.as_mut().ok_or_else(|| {
|
||||
LpClientError::Transport("Cannot send registration: not connected".to_string())
|
||||
})?;
|
||||
|
||||
// Ensure handshake is complete (state machine exists and is in Transport state)
|
||||
let state_machine = self.state_machine.as_mut().ok_or_else(|| {
|
||||
LpClientError::Transport(
|
||||
"Cannot send registration: handshake not completed".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::debug!("Using pre-generated credential for registration");
|
||||
|
||||
// Build registration request with pre-generated credential
|
||||
let wg_public_key = PeerPublicKey::new(wg_keypair.public_key().to_bytes().into());
|
||||
let request =
|
||||
LpRegistrationRequest::new_dvpn(wg_public_key, credential, ticket_type, self.client_ip);
|
||||
|
||||
tracing::trace!("Built registration request: {:?}", request);
|
||||
|
||||
// Serialize the request
|
||||
let request_bytes = bincode::serialize(&request).map_err(|e| {
|
||||
LpClientError::SendRegistrationRequest(format!("Failed to serialize request: {}", e))
|
||||
})?;
|
||||
|
||||
tracing::debug!(
|
||||
"Sending registration request ({} bytes)",
|
||||
request_bytes.len()
|
||||
);
|
||||
|
||||
// Encrypt and prepare packet via state machine
|
||||
let action = state_machine
|
||||
.process_input(LpInput::SendData(request_bytes))
|
||||
.ok_or_else(|| {
|
||||
LpClientError::Transport("State machine returned no action".to_string())
|
||||
})?
|
||||
.map_err(|e| {
|
||||
LpClientError::SendRegistrationRequest(format!(
|
||||
"Failed to encrypt registration request: {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
// Send the encrypted packet
|
||||
match action {
|
||||
LpAction::SendPacket(packet) => {
|
||||
Self::send_packet(stream, &packet).await?;
|
||||
tracing::info!("Successfully sent registration request to gateway");
|
||||
Ok(())
|
||||
}
|
||||
other => Err(LpClientError::Transport(format!(
|
||||
"Unexpected action when sending registration data: {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives and processes the registration response from the gateway.
|
||||
///
|
||||
/// This must be called after sending a registration request. The method:
|
||||
|
||||
Reference in New Issue
Block a user