Skip to content

Commit d0dfb32

Browse files
feat: add tls
Signed-off-by: venilinvasilev <venilin.vasilev@gmail.com>
1 parent f9e9189 commit d0dfb32

File tree

3 files changed

+597
-21
lines changed

3 files changed

+597
-21
lines changed

examples/transport_security.rs

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
use clap::Parser;
4+
use hedera::{
5+
AccountCreateTransaction,
6+
AccountId,
7+
Client,
8+
Hbar,
9+
PrivateKey,
10+
};
11+
12+
#[derive(Parser, Debug)]
13+
struct Args {
14+
#[clap(long, env)]
15+
operator_account_id: AccountId,
16+
17+
#[clap(long, env)]
18+
operator_key: PrivateKey,
19+
20+
#[clap(long, env, default_value = "testnet")]
21+
hedera_network: String,
22+
}
23+
24+
#[tokio::main]
25+
async fn main() -> anyhow::Result<()> {
26+
let _ = dotenvy::dotenv();
27+
let Args {
28+
operator_account_id,
29+
operator_key,
30+
hedera_network,
31+
} = Args::parse();
32+
33+
let network_display = hedera_network.to_uppercase();
34+
println!("╔════════════════════════════════════════════════════════════╗");
35+
println!("║ Hedera {} - TLS Transport Security ║", network_display);
36+
println!("╚════════════════════════════════════════════════════════════╝");
37+
println!("\nOperator Details:");
38+
println!(" Account ID: {}", operator_account_id);
39+
println!(" Public Key: {}", operator_key.public_key());
40+
println!(" Network: {}", hedera_network);
41+
42+
// Create a client for the specified network
43+
let client = Client::for_name(&hedera_network)?;
44+
client.set_operator(operator_account_id, operator_key.clone());
45+
46+
// First, test without TLS to verify basic connectivity
47+
println!("\n╔════════════════════════════════════════════════════════════╗");
48+
println!("║ Step 1: Verify Connectivity (without TLS) ║");
49+
println!("╚════════════════════════════════════════════════════════════╝");
50+
51+
println!("\nNetwork Configuration (Before Request):");
52+
let network_before = client.network();
53+
println!(" TLS Enabled: {}", client.transport_security());
54+
println!(" Network has {} nodes", network_before.len());
55+
56+
if network_before.is_empty() {
57+
println!("\nWARNING: Network is EMPTY!");
58+
println!(" This will cause all requests to fail.");
59+
println!(" Check if Client::for_name() is working correctly.");
60+
anyhow::bail!("Network configuration is empty - cannot proceed");
61+
}
62+
63+
println!(" Showing first 5 nodes:");
64+
for (i, (address, account_id)) in network_before.iter().take(5).enumerate() {
65+
println!(" {}. {} -> {}", i + 1, address, account_id);
66+
}
67+
if network_before.len() > 5 {
68+
println!(" ... and {} more nodes", network_before.len() - 5);
69+
}
70+
71+
// Verify addresses look correct
72+
println!("\nNetwork Validation:");
73+
let has_correct_port = network_before.iter().any(|(addr, _)| addr.contains(":50211"));
74+
println!(" Contains port 50211 addresses: {}", has_correct_port);
75+
76+
if !has_correct_port {
77+
println!(" WARNING: No addresses with port 50211 found!");
78+
println!(" This may indicate a network configuration issue.");
79+
}
80+
81+
println!("\nTesting basic connectivity on port 50211 (plaintext)...");
82+
println!(" Creating a new account with 1 tinybar initial balance...");
83+
println!(" Starting request...");
84+
println!(" (This may take up to 30 seconds if the connection fails)");
85+
86+
// Generate a key for the new account
87+
let new_account_key = PrivateKey::generate_ed25519();
88+
println!(" Generated account key: {}", new_account_key.public_key());
89+
90+
// Add timeout to first request too
91+
let plaintext_result = tokio::time::timeout(
92+
tokio::time::Duration::from_secs(30),
93+
AccountCreateTransaction::new()
94+
.set_key_without_alias(new_account_key.public_key())
95+
.initial_balance(Hbar::from_tinybars(1))
96+
.execute(&client)
97+
).await;
98+
99+
match plaintext_result {
100+
Ok(Ok(response)) => {
101+
println!(" Basic connectivity works!");
102+
println!(" Transaction ID: {}", response.transaction_id);
103+
println!(" Waiting for receipt...");
104+
let receipt = response.get_receipt(&client).await?;
105+
let new_account_id = receipt.account_id.unwrap();
106+
println!(" Receipt Status: {:?}", receipt.status);
107+
println!(" Created Account ID: {}", new_account_id);
108+
}
109+
Ok(Err(e)) => {
110+
println!("\n Plaintext Connection Failed!");
111+
println!(" Error: {:?}", e);
112+
println!("\n Possible Issues:");
113+
println!(" 1. Network connectivity problem");
114+
println!(" 2. Account {} may not exist", operator_account_id);
115+
println!(" 3. Network configuration issue");
116+
println!(" 4. Firewall blocking connections");
117+
anyhow::bail!("Plaintext connection failed: {:?}", e);
118+
}
119+
Err(_timeout) => {
120+
println!("\n Plaintext Connection TIMED OUT (30 seconds)");
121+
println!("\n This suggests:");
122+
println!(" 1. Network connectivity issue");
123+
println!(" 2. All nodes are unreachable");
124+
println!(" 3. Firewall blocking port 50211");
125+
println!(" 4. Network configuration problem");
126+
anyhow::bail!("Plaintext connection timed out - check network connectivity");
127+
}
128+
}
129+
130+
// Now enable TLS and try
131+
println!("\n╔════════════════════════════════════════════════════════════╗");
132+
println!("║ Step 2: Testing TLS on Port 50212 ║");
133+
println!("╚════════════════════════════════════════════════════════════╝");
134+
135+
println!("\nEnabling TLS...");
136+
client.set_transport_security(true);
137+
println!(" TLS Enabled: {}", client.transport_security());
138+
println!(" Connection: Encrypted (port 50212)");
139+
println!(" Certificate Verification: Enabled");
140+
141+
// Log the network configuration
142+
println!("\nNetwork Configuration (After Enabling TLS):");
143+
let network = client.network();
144+
println!(" TLS Enabled: {}", client.transport_security());
145+
println!(" Network has {} nodes:", network.len());
146+
println!(" NOTE: Addresses shown are from the network map (port 50211)");
147+
println!(" The TLS implementation will internally convert these to port 50212");
148+
println!();
149+
150+
// Show first 5 nodes as examples
151+
let sample_nodes: Vec<_> = network.iter().take(5).collect();
152+
for (i, (address, account_id)) in sample_nodes.iter().enumerate() {
153+
println!(" {}. {} -> {}", i + 1, address, account_id);
154+
// Show what the TLS address would be
155+
let tls_address = address.replace(":50211", ":50212");
156+
println!(" → TLS: {}", tls_address);
157+
}
158+
if network.len() > 5 {
159+
println!(" ... and {} more nodes", network.len() - 5);
160+
}
161+
162+
println!("\nTLS Load Balancing:");
163+
println!(" The SDK uses ALL available TLS nodes with round-robin load balancing.");
164+
println!(" Requests are distributed across all {} TLS-enabled nodes.", network.len());
165+
166+
println!("\nCreating account with TLS...");
167+
println!(" Creating a new account with 1 tinybar initial balance...");
168+
println!(" This will use load balancing across all TLS nodes:");
169+
for (i, (addr, account_id)) in sample_nodes.iter().enumerate() {
170+
let tls_addr = addr.replace(":50211", ":50212");
171+
println!(" {}. {} ({})", i + 1, tls_addr, account_id);
172+
}
173+
if network.len() > 5 {
174+
println!(" ... and {} more TLS nodes", network.len() - 5);
175+
}
176+
println!(" Starting TLS request...");
177+
println!(" (This may take up to 30 seconds if the connection fails)");
178+
179+
// Generate a key for the new account
180+
let new_account_key_tls = PrivateKey::generate_ed25519();
181+
println!(" Generated account key: {}", new_account_key_tls.public_key());
182+
183+
// Use timeout to prevent indefinite hanging
184+
let tls_result = tokio::time::timeout(
185+
tokio::time::Duration::from_secs(30),
186+
AccountCreateTransaction::new()
187+
.set_key_without_alias(new_account_key_tls.public_key())
188+
.initial_balance(Hbar::from_tinybars(1))
189+
.execute(&client)
190+
).await;
191+
192+
match tls_result {
193+
Ok(Ok(response_tls)) => {
194+
println!("\nTLS Account Creation Completed Successfully!");
195+
println!(" Transaction ID: {}", response_tls.transaction_id);
196+
println!(" Waiting for receipt...");
197+
let receipt_tls = response_tls.get_receipt(&client).await?;
198+
let new_account_id_tls = receipt_tls.account_id.unwrap();
199+
println!(" Receipt Status: {:?}", receipt_tls.status);
200+
println!(" Created Account ID: {}", new_account_id_tls);
201+
202+
println!("\n╔════════════════════════════════════════════════════════════╗");
203+
println!("║ Success! ║");
204+
println!("╚════════════════════════════════════════════════════════════╝");
205+
println!("\nTLS transport security works on {}!", hedera_network);
206+
println!(" {} supports TLS with valid certificates", network_display);
207+
println!(" Encrypted connection on port 50212");
208+
println!(" Certificate verification passed");
209+
println!("\nCost: ~1 HBAR per account creation (1 tinybar initial balance + fees)");
210+
}
211+
Ok(Err(e)) => {
212+
println!("\nTLS Connection Failed!");
213+
println!(" Error: {:?}", e);
214+
215+
println!("\n╔════════════════════════════════════════════════════════════╗");
216+
println!("║ Analysis ║");
217+
println!("╚════════════════════════════════════════════════════════════╝");
218+
println!("\nTLS Issue Detected:");
219+
println!(" • Plaintext (port 50211): Works");
220+
println!(" • TLS (port 50212): Failed");
221+
222+
println!("\nRoot Cause Analysis:");
223+
println!(" Possible issues with TLS connection:");
224+
println!();
225+
println!(" 1. Port Conversion:");
226+
println!(" • Network map uses port 50211 addresses");
227+
println!(" • TLS internally replaces :50211 → :50212");
228+
println!(" • This happens in NodeConnection::channel_with_tls_conversion()");
229+
println!();
230+
println!(" 2. Load Balancing:");
231+
println!(" • SDK uses round-robin across ALL TLS nodes");
232+
println!(" • Certificates are retrieved from all nodes");
233+
println!(" • Requests are distributed for redundancy");
234+
println!();
235+
println!(" 3. Certificate Verification:");
236+
println!(" • Uses SslVerifyMode::PEER (strict verification)");
237+
println!(" • Requires valid, signed certificates");
238+
println!(" • Self-signed certificates will fail");
239+
println!(" • Certificates are dynamically retrieved from each node");
240+
println!();
241+
println!(" 4. Possible Issues:");
242+
println!(" • TLS nodes may not have TLS enabled on port 50212");
243+
println!(" • Port 50212 may be firewalled");
244+
println!(" • Certificate verification may be failing");
245+
println!(" • Network may block outbound port 50212");
246+
247+
println!("\nRecommendations:");
248+
println!(" 1. Use plaintext connections (default) for now");
249+
println!(" 2. TLS may be for private/enterprise deployments only");
250+
println!(" 3. Check if testnet/mainnet nodes support TLS on port 50212");
251+
}
252+
Err(_timeout) => {
253+
println!("\nTLS Connection TIMED OUT (30 seconds)");
254+
255+
println!("\n╔════════════════════════════════════════════════════════════╗");
256+
println!("║ Timeout Analysis ║");
257+
println!("╚════════════════════════════════════════════════════════════╝");
258+
println!("\nConnection Timeout Detected:");
259+
println!(" • Plaintext (port 50211): Works");
260+
println!(" • TLS (port 50212): Timeout (no response)");
261+
262+
println!("\nWhat This Means:");
263+
println!(" The timeout strongly suggests:");
264+
println!();
265+
println!(" 1. Port 50212 is NOT open/listening:");
266+
println!(" • {} consensus nodes don't have TLS enabled", network_display);
267+
println!(" • Port 50212 is filtered/firewalled");
268+
println!(" • The service is not running on that port");
269+
println!();
270+
println!(" 2. Mirror Node vs Consensus Node:");
271+
println!(" • Mirror nodes ≠ Consensus nodes");
272+
println!(" • This example tests CONSENSUS nodes (for transactions)");
273+
println!(" • Mirror nodes use different ports/protocols (REST API)");
274+
println!();
275+
println!(" 3. Expected Behavior:");
276+
println!(" • If port was open but cert failed: immediate error");
277+
println!(" • If port is closed/filtered: timeout (what we see)");
278+
println!(" • This confirms port 50212 is NOT available");
279+
280+
println!("\nConclusion:");
281+
println!(" Hedera {} CONSENSUS nodes do NOT support TLS on port 50212.", network_display);
282+
println!(" The TLS feature may be:");
283+
println!(" • Only for local/private networks");
284+
println!(" • For future use when networks enable it");
285+
println!(" • Incomplete implementation");
286+
287+
println!("\nRecommendation:");
288+
println!(" Continue using plaintext connections (port 50211) for {}.", hedera_network);
289+
println!(" This is the standard and supported configuration.");
290+
}
291+
}
292+
293+
Ok(())
294+
}
295+

src/client/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,27 @@ impl Client {
356356
self.net().0.load().addresses()
357357
}
358358

359+
/// Returns whether TLS transport security is enabled.
360+
///
361+
/// When enabled, connections will use port 50212 with TLS encryption.
362+
/// When disabled, connections will use port 50211 with plaintext.
363+
#[must_use]
364+
pub fn transport_security(&self) -> bool {
365+
self.net().0.load().transport_security()
366+
}
367+
368+
/// Enable or disable TLS transport security.
369+
///
370+
/// When enabled, addresses with port 50211 will be converted to port 50212 for TLS connections.
371+
/// When disabled, connections will use plaintext on port 50211.
372+
///
373+
/// # Note
374+
/// This setting affects how addresses are converted when creating connections.
375+
/// The actual TLS implementation uses dynamic certificate retrieval (like the JavaScript SDK).
376+
pub fn set_transport_security(&self, enabled: bool) {
377+
self.net().0.load().set_transport_security(enabled);
378+
}
379+
359380
/// Returns the max number of times a node can be retried before removing it from the network.
360381
pub fn max_node_attempts(&self) -> Option<NonZeroUsize> {
361382
self.net().0.load().max_node_attempts()

0 commit comments

Comments
 (0)