Skip to content

Commit df0ee43

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

File tree

3 files changed

+619
-23
lines changed

3 files changed

+619
-23
lines changed

examples/transport_security.rs

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

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)