cli tool fixes
This commit is contained in:
parent
4fad309fc2
commit
12916c0b11
|
|
@ -847,7 +847,7 @@ Expected reply:
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
### lookup_contract_by_hash
|
### lookup_loan_by_hash
|
||||||
|
|
||||||
Looks up a loan contract by contract hash.
|
Looks up a loan contract by contract hash.
|
||||||
|
|
||||||
|
|
@ -860,16 +860,20 @@ Requires:
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
lookup_contract_by_hash <contract_hash>
|
lookup_loan_by_hash <loan_hash>
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected reply:
|
Expected reply:
|
||||||
|
|
||||||
```text
|
```json
|
||||||
<contract data or peer error>
|
{
|
||||||
|
"contract": "<loan hash>",
|
||||||
|
"status": "active",
|
||||||
|
"payments": []
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### lookup_contract_by_address
|
### lookup_loan_by_address
|
||||||
|
|
||||||
Looks up loan contracts by wallet address.
|
Looks up loan contracts by wallet address.
|
||||||
|
|
||||||
|
|
@ -882,13 +886,17 @@ Requires:
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
lookup_contract_by_address <wallet_address>
|
lookup_loan_by_address <wallet_address>
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected reply:
|
Expected reply:
|
||||||
|
|
||||||
```text
|
```json
|
||||||
<contract data or peer error>
|
{
|
||||||
|
"address": "<wallet address>",
|
||||||
|
"contracts": 1,
|
||||||
|
"loans": []
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### lookup_remote_balance
|
### lookup_remote_balance
|
||||||
|
|
|
||||||
|
|
@ -139,5 +139,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,5 +115,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,5 +123,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,5 +137,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -256,5 +256,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,5 +174,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,5 +156,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -237,5 +237,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,5 +143,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -246,5 +246,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,5 +169,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
use blockchain::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
@ -72,7 +73,11 @@ async fn main() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if !response.is_empty() {
|
} else if !response.is_empty() {
|
||||||
println!("{}", hex::encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
use blockchain::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
@ -76,7 +77,11 @@ async fn main() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if !response.is_empty() {
|
} else if !response.is_empty() {
|
||||||
println!("{}", hex::encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::encode;
|
use blockchain::encode;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||||
|
|
@ -79,7 +80,10 @@ async fn main() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if !response.is_empty() {
|
} else if !response.is_empty() {
|
||||||
println!("{}", encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::encode;
|
use blockchain::encode;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
||||||
|
|
@ -64,7 +65,10 @@ async fn main() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if !response.is_empty() {
|
} else if !response.is_empty() {
|
||||||
println!("{}", encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
||||||
|
|
@ -45,7 +46,10 @@ async fn main() {
|
||||||
// The difficulty reply carries 4 bytes of header plus an 8-byte u64 difficulty.
|
// The difficulty reply carries 4 bytes of header plus an 8-byte u64 difficulty.
|
||||||
if response.len() == 12 {
|
if response.len() == 12 {
|
||||||
let difficulty = u64::from_le_bytes(response[4..12].try_into().unwrap());
|
let difficulty = u64::from_le_bytes(response[4..12].try_into().unwrap());
|
||||||
println!("{difficulty}");
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "difficulty": difficulty })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
} else {
|
} else {
|
||||||
let response_text = String::from_utf8_lossy(&response);
|
let response_text = String::from_utf8_lossy(&response);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
||||||
|
|
@ -53,7 +54,10 @@ async fn main() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if response.len() == 4 {
|
if response.len() == 4 {
|
||||||
let height = u32::from_le_bytes(response[..4].try_into().unwrap());
|
let height = u32::from_le_bytes(response[..4].try_into().unwrap());
|
||||||
println!("{height}");
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "height": height })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
} else {
|
} else {
|
||||||
let response_text = String::from_utf8_lossy(&response);
|
let response_text = String::from_utf8_lossy(&response);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
||||||
|
|
@ -58,15 +59,21 @@ async fn main() {
|
||||||
match result {
|
match result {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if response.is_empty() {
|
if response.is_empty() {
|
||||||
println!("0");
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "largest_tx_fee": "0.00000000" }))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if response.len() == 8 {
|
} else if response.len() == 8 {
|
||||||
let fee = u64::from_le_bytes(response[0..8].try_into().unwrap());
|
let fee = u64::from_le_bytes(response[0..8].try_into().unwrap());
|
||||||
if fee == 0 {
|
println!(
|
||||||
println!("0");
|
"{}",
|
||||||
} else {
|
serde_json::to_string_pretty(
|
||||||
println!("{}", format_balance(fee));
|
&json!({ "largest_tx_fee": format_balance(fee) })
|
||||||
}
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
} else {
|
} else {
|
||||||
let response_text = String::from_utf8_lossy(&response);
|
let response_text = String::from_utf8_lossy(&response);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
|
use blockchain::common::network_startup::get_connections;
|
||||||
|
use blockchain::encode;
|
||||||
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
use blockchain::standalone_tools::loan_lookup_json::parse_loan_lookup_by_address;
|
||||||
|
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||||
|
use blockchain::wallets::structures::Wallet;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Command 37 asks a peer for contract records tied to a wallet address.
|
||||||
|
let hashmap_key = generate_uid();
|
||||||
|
let rpc_command = 37;
|
||||||
|
|
||||||
|
// The address can be a long, short, or vanity address; request encoding normalizes it later.
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
if args.len() != 2 {
|
||||||
|
println!("Usage: ./lookup_loan_by_address <wallet_address>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet_address = match args[1].parse::<String>() {
|
||||||
|
Ok(address) => address,
|
||||||
|
Err(_) => {
|
||||||
|
println!("Please enter a wallet address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let wallet_path = prompt_wallet_path().await;
|
||||||
|
let encryption_key = prompt_hidden_nonempty(
|
||||||
|
"What is your wallet decryption key? ",
|
||||||
|
"Wallet key cannot be empty. Please try again.",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let wallet = match Wallet::try_obtain_wallet(encryption_key, Some(&wallet_path)).await {
|
||||||
|
Ok(wallet) => wallet,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Wallet decryption failed: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let wallet_address = match resolve_wallet_address_input(&wallet_address, &wallet).await {
|
||||||
|
Ok(address) => address,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("wallet address is not valid: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try each configured peer until one returns a response.
|
||||||
|
let connections = get_connections().await;
|
||||||
|
let mut connected = false;
|
||||||
|
|
||||||
|
for conn in connections {
|
||||||
|
if connected {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let socket_address = conn.parse().expect("Failed to parse the socket address");
|
||||||
|
let result = handshake::connect_and_handshake(
|
||||||
|
socket_address,
|
||||||
|
wallet_address.clone(),
|
||||||
|
rpc_command,
|
||||||
|
handshake::HandshakeWallet::WalletParts {
|
||||||
|
public_key: wallet.saved.public_key.clone(),
|
||||||
|
private_key: wallet.saved.private_key.clone(),
|
||||||
|
},
|
||||||
|
hashmap_key,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(response) => {
|
||||||
|
// Loan lookups return a key/value text summary; print successful data as JSON.
|
||||||
|
let response_text = String::from_utf8_lossy(&response);
|
||||||
|
let trimmed = response_text.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
if trimmed.starts_with("error:") {
|
||||||
|
println!("{trimmed}");
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&parse_loan_lookup_by_address(trimmed))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
connected = true;
|
||||||
|
} else if !response.is_empty() {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||||
|
);
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connected {
|
||||||
|
eprintln!("failed to connect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
|
use blockchain::common::network_startup::get_connections;
|
||||||
|
use blockchain::encode;
|
||||||
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
use blockchain::standalone_tools::loan_lookup_json::parse_loan_summary;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Command 33 asks a peer for one loan contract by its 32-byte hash.
|
||||||
|
let hashmap_key = generate_uid();
|
||||||
|
let rpc_command = 33;
|
||||||
|
|
||||||
|
// The hash is passed as text here and encoded into binary request bytes later.
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
if args.len() != 2 {
|
||||||
|
println!("Usage: ./lookup_loan_by_hash <loan_hash>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contract_hash = match args[1].parse::<String>() {
|
||||||
|
Ok(hash) => hash,
|
||||||
|
Err(_) => {
|
||||||
|
println!("Please enter a contract hash");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let wallet_path = prompt_wallet_path().await;
|
||||||
|
|
||||||
|
let encryption_key = prompt_hidden_nonempty(
|
||||||
|
"What is your wallet decryption key? ",
|
||||||
|
"Wallet key cannot be empty. Please try again.",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Try each configured peer until one returns a response.
|
||||||
|
let connections = get_connections().await;
|
||||||
|
let mut connected = false;
|
||||||
|
|
||||||
|
for conn in connections {
|
||||||
|
if connected {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let socket_address = conn.parse().expect("Failed to parse the socket address");
|
||||||
|
let result = handshake::connect_and_handshake(
|
||||||
|
socket_address,
|
||||||
|
contract_hash.clone(),
|
||||||
|
rpc_command,
|
||||||
|
handshake::HandshakeWallet::WalletKey {
|
||||||
|
encryption_key: encryption_key.clone(),
|
||||||
|
wallet_path: wallet_path.clone(),
|
||||||
|
},
|
||||||
|
hashmap_key,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(response) => {
|
||||||
|
// Loan lookups return a key/value text summary; print successful data as JSON.
|
||||||
|
let response_text = String::from_utf8_lossy(&response);
|
||||||
|
let trimmed = response_text.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
if trimmed.starts_with("error:") {
|
||||||
|
println!("{trimmed}");
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&parse_loan_summary(trimmed)).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
connected = true;
|
||||||
|
} else if !response.is_empty() {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": encode(response) })).unwrap()
|
||||||
|
);
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connected {
|
||||||
|
eprintln!("failed to connect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::fs;
|
use blockchain::fs;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::tilde;
|
use blockchain::tilde;
|
||||||
use rustyline::completion::FilenameCompleter;
|
use rustyline::completion::FilenameCompleter;
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
|
|
@ -85,5 +86,11 @@ async fn main() {
|
||||||
buffer.copy_from_slice(&bytes[..8]);
|
buffer.copy_from_slice(&bytes[..8]);
|
||||||
let value = u64::from_le_bytes(buffer);
|
let value = u64::from_le_bytes(buffer);
|
||||||
|
|
||||||
println!("{}", format_balance(value));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({
|
||||||
|
"balance": format_balance(value)
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use blockchain::common::types::{
|
||||||
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||||
};
|
};
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::rpc::command_maps;
|
use blockchain::rpc::command_maps;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
@ -154,12 +155,23 @@ async fn main() {
|
||||||
match result {
|
match result {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if response.is_empty() {
|
if response.is_empty() {
|
||||||
println!("[]");
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "transactions": [] })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if let Some(transactions) = decode_mempool_transactions(&response).await {
|
} else if let Some(transactions) = decode_mempool_transactions(&response).await {
|
||||||
|
let mut output = Vec::new();
|
||||||
for transaction in transactions {
|
for transaction in transactions {
|
||||||
println!("{transaction}");
|
match serde_json::from_str::<serde_json::Value>(&transaction) {
|
||||||
|
Ok(value) => output.push(value),
|
||||||
|
Err(_) => output.push(json!(transaction)),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "transactions": output })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
} else {
|
} else {
|
||||||
// Text errors are printed directly; unknown binary is shown as hex for inspection.
|
// Text errors are printed directly; unknown binary is shown as hex for inspection.
|
||||||
|
|
@ -168,7 +180,11 @@ async fn main() {
|
||||||
if !trimmed.is_empty() {
|
if !trimmed.is_empty() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
} else {
|
} else {
|
||||||
println!("{}", hex::encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use blockchain::common::types::{
|
||||||
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||||
};
|
};
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
use blockchain::to_string_pretty;
|
use blockchain::to_string_pretty;
|
||||||
|
|
@ -132,7 +133,11 @@ async fn main() {
|
||||||
if !trimmed.is_empty() {
|
if !trimmed.is_empty() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
} else {
|
} else {
|
||||||
println!("{}", hex::encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
||||||
|
|
@ -49,7 +50,10 @@ async fn main() {
|
||||||
Ok(response) if response.len() >= 4 => {
|
Ok(response) if response.len() >= 4 => {
|
||||||
// The mempool count command returns a 4-byte little-endian integer.
|
// The mempool count command returns a 4-byte little-endian integer.
|
||||||
let count = u32::from_le_bytes(response[0..4].try_into().unwrap());
|
let count = u32::from_le_bytes(response[0..4].try_into().unwrap());
|
||||||
println!("{count}");
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "mempool_tx_count": count })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
use blockchain::{TimeZone, Utc};
|
use blockchain::{TimeZone, Utc};
|
||||||
|
|
@ -54,8 +55,14 @@ async fn main() {
|
||||||
.single()
|
.single()
|
||||||
.map(|datetime| datetime.format("%H:%M:%S UTC").to_string())
|
.map(|datetime| datetime.format("%H:%M:%S UTC").to_string())
|
||||||
.unwrap_or_else(|| "invalid UTC timestamp".to_string());
|
.unwrap_or_else(|| "invalid UTC timestamp".to_string());
|
||||||
println!("timestamp: {timestamp}");
|
println!(
|
||||||
println!("time: {time}");
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"time": time
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::from_str;
|
use blockchain::from_str;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::read_to_string;
|
use blockchain::read_to_string;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
|
|
@ -157,23 +158,31 @@ async fn main() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if response.is_empty() {
|
if response.is_empty() {
|
||||||
connected = true;
|
connected = true;
|
||||||
println!("[]");
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "balances": [] })).unwrap()
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Balance rows are 15 bytes asset name, 4 bytes NFT series, and 8 bytes balance.
|
// Balance rows are 15 bytes asset name, 4 bytes NFT series, and 8 bytes balance.
|
||||||
if response.len() % 27 == 0 {
|
if response.len() % 27 == 0 {
|
||||||
|
let mut balances = Vec::new();
|
||||||
for entry in response.chunks_exact(27) {
|
for entry in response.chunks_exact(27) {
|
||||||
let name_bytes = &entry[..15];
|
let name_bytes = &entry[..15];
|
||||||
let nft_series = u32::from_le_bytes(entry[15..19].try_into().unwrap());
|
let nft_series = u32::from_le_bytes(entry[15..19].try_into().unwrap());
|
||||||
let balance = u64::from_le_bytes(entry[19..27].try_into().unwrap());
|
let balance = u64::from_le_bytes(entry[19..27].try_into().unwrap());
|
||||||
let asset_name = String::from_utf8_lossy(name_bytes).trim_end().to_string();
|
let asset_name = String::from_utf8_lossy(name_bytes).trim_end().to_string();
|
||||||
let formatted_balance = format_balance(balance);
|
let formatted_balance = format_balance(balance);
|
||||||
if nft_series > 0 {
|
balances.push(json!({
|
||||||
println!("{asset_name}:{nft_series} = {formatted_balance}");
|
"asset": asset_name,
|
||||||
} else {
|
"nft_series": nft_series,
|
||||||
println!("{asset_name} = {formatted_balance}");
|
"balance": formatted_balance
|
||||||
}
|
}));
|
||||||
}
|
}
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "balances": balances })).unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
} else {
|
} else {
|
||||||
let response_text = String::from_utf8_lossy(&response);
|
let response_text = String::from_utf8_lossy(&response);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_startup::get_connections;
|
use blockchain::common::network_startup::get_connections;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
use blockchain::to_string_pretty;
|
use blockchain::to_string_pretty;
|
||||||
|
|
@ -79,7 +80,11 @@ async fn main() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if !response.is_empty() {
|
} else if !response.is_empty() {
|
||||||
println!("{}", hex::encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use blockchain::common::types::{
|
||||||
MARKETING_TYPE, REWARDS_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
MARKETING_TYPE, REWARDS_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||||
};
|
};
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
|
use blockchain::json;
|
||||||
use blockchain::records::memory::response_channels::generate_uid;
|
use blockchain::records::memory::response_channels::generate_uid;
|
||||||
use blockchain::standalone_tools::connections::handshake;
|
use blockchain::standalone_tools::connections::handshake;
|
||||||
use blockchain::to_string_pretty;
|
use blockchain::to_string_pretty;
|
||||||
|
|
@ -89,7 +90,11 @@ async fn main() {
|
||||||
println!("{trimmed}");
|
println!("{trimmed}");
|
||||||
connected = true;
|
connected = true;
|
||||||
} else if !response.is_empty() {
|
} else if !response.is_empty() {
|
||||||
println!("{}", hex::encode(response));
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string_pretty(&json!({ "hex": hex::encode(response) }))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
connected = true;
|
connected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,5 +168,10 @@ async fn decode_transaction_to_json(response: &[u8]) -> Option<String> {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(format!("block: {block_number}\n{json}"))
|
let transaction = serde_json::from_str::<serde_json::Value>(&json).ok()?;
|
||||||
|
serde_json::to_string_pretty(&json!({
|
||||||
|
"block": block_number,
|
||||||
|
"transaction": transaction
|
||||||
|
}))
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use blockchain::common::binary_conversions::hex_to_u64;
|
use blockchain::common::binary_conversions::hex_to_u64;
|
||||||
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||||
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use blockchain::common::skein::{skein_128_hash_bytes, skein_256_hash_data};
|
use blockchain::common::skein::skein_128_hash_bytes;
|
||||||
use blockchain::encode;
|
use blockchain::encode;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::records::unpack_block::unpack_header::load_block_header;
|
use blockchain::records::unpack_block::unpack_header::load_block_header;
|
||||||
use blockchain::records::wallet_registry::resolve_pubkey_from_short_address;
|
use blockchain::standalone_tools::wallet_registry_lookup::lookup_pubkey_from_live_registry;
|
||||||
use blockchain::torrent::structs::Torrent;
|
use blockchain::torrent::structs::Torrent;
|
||||||
use blockchain::wallets::structures::Wallet;
|
use blockchain::wallets::structures::Wallet;
|
||||||
use blockchain::{AsyncReadExt, File};
|
use blockchain::{AsyncReadExt, File};
|
||||||
|
|
@ -15,8 +16,8 @@ use std::process;
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Validate that a local block file, block header, and torrent metadata agree.
|
// Validate that a local block file, block header, and torrent metadata agree.
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
if args.len() != 2 {
|
if !(2..=3).contains(&args.len()) {
|
||||||
eprintln!("Usage: {} <block_number>", args[0]);
|
eprintln!("Usage: {} <block_number> [miner_public_key_hex]", args[0]);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,12 +56,25 @@ async fn main() {
|
||||||
let header = load_block_header(block_number).await.unwrap();
|
let header = load_block_header(block_number).await.unwrap();
|
||||||
let block_hash = header.hash().await;
|
let block_hash = header.hash().await;
|
||||||
let block_difficulty = hex_to_u64(&block_hash).await.unwrap();
|
let block_difficulty = hex_to_u64(&block_hash).await.unwrap();
|
||||||
let miner_pubkey = resolve_pubkey_from_short_address(
|
let miner_pubkey = if let Some(public_key_hex) = args.get(2) {
|
||||||
&blockchain::startup::initialize_startup::open_chain_state().await,
|
Wallet::normalize_saved_public_key_bytes(public_key_hex).unwrap_or_else(|| {
|
||||||
&header.unmined_block.miner,
|
eprintln!("Error: supplied miner public key is invalid.");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let wallet_path = prompt_wallet_path().await;
|
||||||
|
let encryption_key = prompt_hidden_nonempty(
|
||||||
|
"What is your wallet decryption key? ",
|
||||||
|
"Wallet key cannot be empty. Please try again.",
|
||||||
)
|
)
|
||||||
.unwrap_or(None)
|
.await;
|
||||||
.unwrap_or_default();
|
lookup_pubkey_from_live_registry(&header.unmined_block.miner, &wallet_path, &encryption_key)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
eprintln!("Error: {err}");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
};
|
||||||
let miner_pubkey_hex = encode(&miner_pubkey);
|
let miner_pubkey_hex = encode(&miner_pubkey);
|
||||||
|
|
||||||
// Load the raw block bytes for file-size, info-hash, and piece-hash checks.
|
// Load the raw block bytes for file-size, info-hash, and piece-hash checks.
|
||||||
|
|
@ -73,9 +87,9 @@ async fn main() {
|
||||||
|
|
||||||
let info_hash_computed = skein_128_hash_bytes(&block_data);
|
let info_hash_computed = skein_128_hash_bytes(&block_data);
|
||||||
|
|
||||||
// Rebuild the unmined-block hash used by the miner proof signature.
|
// Rebuild the unmined-block hash using the same canonical path as mining
|
||||||
let unmined_json = serde_json::to_string(&header.unmined_block).unwrap();
|
// and live block verification.
|
||||||
let unmined_hash = skein_256_hash_data(&unmined_json);
|
let unmined_hash = header.unmined_block.hash().await;
|
||||||
let signature_ok =
|
let signature_ok =
|
||||||
Wallet::verify_transaction_with_public_key(&unmined_hash, &header.proof, &miner_pubkey_hex)
|
Wallet::verify_transaction_with_public_key(&unmined_hash, &header.proof, &miner_pubkey_hex)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
||||||
|
|
@ -1,105 +1,25 @@
|
||||||
|
use blockchain::common::cli_prompts::prompt_visible;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::from_str;
|
|
||||||
use blockchain::read_to_string;
|
|
||||||
use blockchain::tilde;
|
|
||||||
use blockchain::wallets::structures::Wallet;
|
use blockchain::wallets::structures::Wallet;
|
||||||
use blockchain::Value;
|
|
||||||
use rustyline::completion::FilenameCompleter;
|
|
||||||
use rustyline::error::ReadlineError;
|
|
||||||
use rustyline::{history::DefaultHistory, CompletionType, Config, Editor};
|
|
||||||
use rustyline_derive::Completer;
|
|
||||||
use rustyline_derive::Helper as RustyHelper;
|
|
||||||
use rustyline_derive::Highlighter as RustyHighlighter;
|
|
||||||
use rustyline_derive::Hinter as RustyHinter;
|
|
||||||
use rustyline_derive::Validator as RustyValidator;
|
|
||||||
|
|
||||||
async fn read_address_file(path: &str) -> Result<String, String> {
|
|
||||||
// Allow ~ in paths and then extract either a raw address or an address from wallet JSON.
|
|
||||||
let expanded_path = tilde(path).to_string();
|
|
||||||
read_to_string(&expanded_path)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to read {expanded_path}: {e}"))
|
|
||||||
.and_then(|contents| extract_address(&contents))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_address(contents: &str) -> Result<String, String> {
|
|
||||||
// Address files may be a plain address string or a saved wallet JSON object.
|
|
||||||
let trimmed = contents.trim();
|
|
||||||
if trimmed.is_empty() {
|
|
||||||
return Err("Address file is empty".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if trimmed.starts_with('{') {
|
|
||||||
// Wallet JSON address validation uses the canonical short address.
|
|
||||||
let value: Value =
|
|
||||||
from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?;
|
|
||||||
let address = value
|
|
||||||
.get("short_address")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?;
|
|
||||||
return Ok(address.trim().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(trimmed.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(RustyHelper, Completer, RustyHinter, RustyHighlighter, RustyValidator)]
|
|
||||||
struct PathHelper {
|
|
||||||
#[rustyline(Completer)]
|
|
||||||
completer: FilenameCompleter,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_for_path(prompt: &str) -> Result<String, String> {
|
|
||||||
// Rustyline gives the interactive prompt filesystem completion.
|
|
||||||
let config = Config::builder()
|
|
||||||
.completion_type(CompletionType::List)
|
|
||||||
.build();
|
|
||||||
let mut editor =
|
|
||||||
Editor::<PathHelper, DefaultHistory>::with_config(config).map_err(|e| e.to_string())?;
|
|
||||||
editor.set_helper(Some(PathHelper {
|
|
||||||
completer: FilenameCompleter::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
match editor.readline(prompt) {
|
|
||||||
Ok(line) => Ok(line.trim().to_string()),
|
|
||||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
|
||||||
Err("Input cancelled".to_string())
|
|
||||||
}
|
|
||||||
Err(err) => Err(format!("Failed to read address file path: {err}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Accept the address file path as an arg or prompt interactively.
|
// Accept a directly pasted address as an arg or prompt interactively.
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
if args.len() > 1 && args.len() != 2 {
|
if args.len() > 1 && args.len() != 2 {
|
||||||
println!("Usage: ./verify_address <address_file>");
|
println!("Usage: ./verify_address <wallet_address>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let address_path = if args.len() == 2 {
|
let address = if args.len() == 2 {
|
||||||
args[1].clone()
|
args[1].trim().to_string()
|
||||||
} else {
|
} else {
|
||||||
match prompt_for_path("Please enter the path to the file containing the wallet address: ") {
|
prompt_visible("Please enter the wallet address: ")
|
||||||
Ok(path) => path,
|
.await
|
||||||
Err(err) => {
|
.trim()
|
||||||
eprintln!("{err}");
|
.to_string()
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let address = match read_address_file(&address_path).await {
|
println!("{}", Wallet::short_address_validation(&address));
|
||||||
Ok(address) => address,
|
|
||||||
Err(err) => {
|
|
||||||
println!("{err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let message_hash = Wallet::short_address_validation(address.trim());
|
|
||||||
|
|
||||||
println!("{message_hash}");
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,17 @@
|
||||||
use blockchain::common::cli_prompts::prompt_visible;
|
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||||
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
|
||||||
use blockchain::common::skein::skein_256_hash_data;
|
use blockchain::common::skein::skein_256_hash_data;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::from_str;
|
use blockchain::standalone_tools::wallet_registry_lookup::lookup_pubkey_from_live_registry;
|
||||||
use blockchain::read_to_string;
|
|
||||||
use blockchain::records::wallet_registry::resolve_pubkey_from_short_address;
|
|
||||||
use blockchain::sled;
|
|
||||||
use blockchain::tilde;
|
|
||||||
use blockchain::wallets::structures::Wallet;
|
use blockchain::wallets::structures::Wallet;
|
||||||
use blockchain::Value;
|
|
||||||
use rustyline::completion::FilenameCompleter;
|
|
||||||
use rustyline::error::ReadlineError;
|
|
||||||
use rustyline::{history::DefaultHistory, CompletionType, Config, Editor};
|
|
||||||
use rustyline_derive::Completer;
|
|
||||||
use rustyline_derive::Helper as RustyHelper;
|
|
||||||
use rustyline_derive::Highlighter as RustyHighlighter;
|
|
||||||
use rustyline_derive::Hinter as RustyHinter;
|
|
||||||
use rustyline_derive::Validator as RustyValidator;
|
|
||||||
|
|
||||||
async fn read_input_file(path: &str) -> Result<String, String> {
|
|
||||||
// Signature input files are plain text and are trimmed before verification.
|
|
||||||
let expanded_path = tilde(path).to_string();
|
|
||||||
read_to_string(&expanded_path)
|
|
||||||
.await
|
|
||||||
.map(|s| s.trim().to_string())
|
|
||||||
.map_err(|e| format!("Failed to read {expanded_path}: {e}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_address_file(path: &str) -> Result<String, String> {
|
|
||||||
// Allow ~ in paths and then extract either a raw address or an address from wallet JSON.
|
|
||||||
let expanded_path = tilde(path).to_string();
|
|
||||||
read_to_string(&expanded_path)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to read {expanded_path}: {e}"))
|
|
||||||
.and_then(|contents| extract_address(&contents))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_address(contents: &str) -> Result<String, String> {
|
|
||||||
// Address files may be a plain address string or a saved wallet JSON object.
|
|
||||||
let trimmed = contents.trim();
|
|
||||||
if trimmed.is_empty() {
|
|
||||||
return Err("Address file is empty".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if trimmed.starts_with('{') {
|
|
||||||
// Signature verification uses the canonical short address.
|
|
||||||
let value: Value =
|
|
||||||
from_str(trimmed).map_err(|e| format!("Failed to parse wallet JSON: {e}"))?;
|
|
||||||
let address = value
|
|
||||||
.get("short_address")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.ok_or_else(|| "Wallet JSON does not contain a usable address field".to_string())?;
|
|
||||||
return Ok(address.trim().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(trimmed.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(RustyHelper, Completer, RustyHinter, RustyHighlighter, RustyValidator)]
|
|
||||||
struct PathHelper {
|
|
||||||
#[rustyline(Completer)]
|
|
||||||
completer: FilenameCompleter,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_for_path(prompt: &str) -> Result<String, String> {
|
|
||||||
// Rustyline gives the interactive prompt filesystem completion.
|
|
||||||
let config = Config::builder()
|
|
||||||
.completion_type(CompletionType::List)
|
|
||||||
.build();
|
|
||||||
let mut editor =
|
|
||||||
Editor::<PathHelper, DefaultHistory>::with_config(config).map_err(|e| e.to_string())?;
|
|
||||||
editor.set_helper(Some(PathHelper {
|
|
||||||
completer: FilenameCompleter::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
match editor.readline(prompt) {
|
|
||||||
Ok(line) => Ok(line.trim().to_string()),
|
|
||||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
|
||||||
Err("Input cancelled".to_string())
|
|
||||||
}
|
|
||||||
Err(err) => Err(format!("Failed to read file path: {err}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Accept all verification inputs as args or prompt interactively.
|
// Accept direct pasted values. Verification needs a live peer registry
|
||||||
|
// lookup because the local sled database may already be locked by a node.
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let address_path: String;
|
|
||||||
let signature_path: String;
|
|
||||||
|
|
||||||
if args.len() > 1 && args.len() != 4 {
|
if args.len() > 1 && args.len() != 4 {
|
||||||
println!("Usage: ./verify_message \"<message to verify>\" <address_file> <signature_file>");
|
println!("Usage: ./verify_message \"<message to verify>\" <wallet_address> <signature>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,74 +21,46 @@ async fn main() {
|
||||||
prompt_visible("Please enter the message to verify: ").await
|
prompt_visible("Please enter the message to verify: ").await
|
||||||
};
|
};
|
||||||
|
|
||||||
if args.len() == 4 {
|
let address = if args.len() == 4 {
|
||||||
address_path = args[2].clone();
|
args[2].trim().to_string()
|
||||||
signature_path = args[3].clone();
|
|
||||||
} else {
|
} else {
|
||||||
address_path = match prompt_for_path(
|
prompt_visible("Please enter the wallet address: ")
|
||||||
"Please enter the path to the file containing the wallet address: ",
|
|
||||||
) {
|
|
||||||
Ok(path) => path,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("{err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
signature_path =
|
|
||||||
match prompt_for_path("Please enter the path to the file containing the signature: ") {
|
|
||||||
Ok(path) => path,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("{err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let address = match read_address_file(&address_path).await {
|
|
||||||
Ok(address) => address,
|
|
||||||
Err(err) => {
|
|
||||||
println!("{err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let signature = match read_input_file(&signature_path).await {
|
|
||||||
Ok(signature) => signature,
|
|
||||||
Err(err) => {
|
|
||||||
println!("{err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hash the message exactly as sign_message does before verifying the wallet signature.
|
|
||||||
let message_hash = skein_256_hash_data(message.as_str());
|
|
||||||
let (
|
|
||||||
_network_name,
|
|
||||||
_padded_base_coin,
|
|
||||||
_suffix,
|
|
||||||
_torrent_path,
|
|
||||||
_wallet_path,
|
|
||||||
_block_path,
|
|
||||||
db_path,
|
|
||||||
_balance_path,
|
|
||||||
_log_path,
|
|
||||||
) = block_extension_and_paths();
|
|
||||||
let db = match sled::open(&db_path) {
|
|
||||||
Ok(db) => db,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Failed to open wallet registry database: {err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let signature = match resolve_pubkey_from_short_address(&db, address.trim()) {
|
|
||||||
Ok(Some(pubkey)) => {
|
|
||||||
Wallet::verify_transaction_with_public_key_bytes(&message_hash, &signature, &pubkey)
|
|
||||||
.await
|
.await
|
||||||
}
|
.trim()
|
||||||
_ => false,
|
.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
if signature {
|
let signature = if args.len() == 4 {
|
||||||
|
args[3].trim().to_string()
|
||||||
|
} else {
|
||||||
|
prompt_visible("Please enter the signature: ")
|
||||||
|
.await
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let wallet_path = prompt_wallet_path().await;
|
||||||
|
let encryption_key = prompt_hidden_nonempty(
|
||||||
|
"What is your wallet decryption key? ",
|
||||||
|
"Wallet key cannot be empty. Please try again.",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let pubkey =
|
||||||
|
match lookup_pubkey_from_live_registry(&address, &wallet_path, &encryption_key).await {
|
||||||
|
Ok(pubkey) => pubkey,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("{err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hash the message exactly as sign_message does before verifying the detached signature.
|
||||||
|
let message_hash = skein_256_hash_data(message.as_str());
|
||||||
|
let valid =
|
||||||
|
Wallet::verify_transaction_with_public_key_bytes(&message_hash, &signature, &pubkey).await;
|
||||||
|
|
||||||
|
if valid {
|
||||||
println!("valid signature");
|
println!("valid signature");
|
||||||
} else {
|
} else {
|
||||||
println!("invalid signature");
|
println!("invalid signature");
|
||||||
|
|
|
||||||
|
|
@ -279,5 +279,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,5 +250,5 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Transaction: {output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::rpc::command_maps::{
|
||||||
RPC_TIME, RPC_TOKEN_CATALOG, RPC_TOKEN_DETAILS, RPC_TOKEN_LIST, RPC_TORRENT_BY_HEIGHT,
|
RPC_TIME, RPC_TOKEN_CATALOG, RPC_TOKEN_DETAILS, RPC_TOKEN_LIST, RPC_TORRENT_BY_HEIGHT,
|
||||||
RPC_TOTAL_CONFIRMED_TX, RPC_TRANSACTION_BY_TXID, RPC_UNBLOCK_IP, RPC_VALIDATE_MESSAGE,
|
RPC_TOTAL_CONFIRMED_TX, RPC_TRANSACTION_BY_TXID, RPC_UNBLOCK_IP, RPC_VALIDATE_MESSAGE,
|
||||||
RPC_VANITY_LOOKUP, RPC_VANITY_OWNER_LOOKUP, RPC_WALLET_REGISTRATION_STATUS,
|
RPC_VANITY_LOOKUP, RPC_VANITY_OWNER_LOOKUP, RPC_WALLET_REGISTRATION_STATUS,
|
||||||
|
RPC_WALLET_REGISTRY_SYNC,
|
||||||
};
|
};
|
||||||
use crate::standalone_tools::transaction_creator::create_transaction_request;
|
use crate::standalone_tools::transaction_creator::create_transaction_request;
|
||||||
use crate::timeout;
|
use crate::timeout;
|
||||||
|
|
@ -486,6 +487,12 @@ async fn build_request_bytes(
|
||||||
bin_msg.extend_from_slice(&public_key_bytes);
|
bin_msg.extend_from_slice(&public_key_bytes);
|
||||||
bin_msg.extend_from_slice(&signature_bytes);
|
bin_msg.extend_from_slice(&signature_bytes);
|
||||||
}
|
}
|
||||||
|
// Request the deterministic wallet registry snapshot.
|
||||||
|
39 => {
|
||||||
|
let command_number: u8 = RPC_WALLET_REGISTRY_SYNC;
|
||||||
|
bin_msg.extend_from_slice(&command_number.to_le_bytes());
|
||||||
|
bin_msg.extend_from_slice(&hashmap_key);
|
||||||
|
}
|
||||||
// Lookup remote balance by long, short, or vanity address.
|
// Lookup remote balance by long, short, or vanity address.
|
||||||
40 => {
|
40 => {
|
||||||
let command_number: u8 = RPC_VANITY_LOOKUP;
|
let command_number: u8 = RPC_VANITY_LOOKUP;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
use crate::{json, Map, Value};
|
||||||
|
|
||||||
|
fn scalar_value(value: &str) -> Value {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
return Value::String(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
if trimmed.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
|
if let Ok(number) = trimmed.parse::<u64>() {
|
||||||
|
return json!(number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if trimmed.contains('.')
|
||||||
|
&& trimmed
|
||||||
|
.chars()
|
||||||
|
.all(|ch| ch.is_ascii_digit() || ch == '.' || ch == '-')
|
||||||
|
{
|
||||||
|
if let Ok(number) = trimmed.parse::<f64>() {
|
||||||
|
return json!(number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::String(trimmed.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_payment_field(payments: &mut Vec<Map<String, Value>>, key: &str, value: &str) -> bool {
|
||||||
|
let Some(rest) = key.strip_prefix("payment_") else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some((index_text, field)) = rest.split_once('_') else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Ok(index) = index_text.parse::<usize>() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if index == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while payments.len() < index {
|
||||||
|
payments.push(Map::new());
|
||||||
|
}
|
||||||
|
payments[index - 1].insert(field.to_string(), scalar_value(value));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_loan_summary(text: &str) -> Value {
|
||||||
|
let mut loan = Map::new();
|
||||||
|
let mut payments = Vec::<Map<String, Value>>::new();
|
||||||
|
|
||||||
|
for line in text.lines().map(str::trim).filter(|line| !line.is_empty()) {
|
||||||
|
let Some((key, value)) = line.split_once('=') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if insert_payment_field(&mut payments, key.trim(), value) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
loan.insert(key.trim().to_string(), scalar_value(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
loan.insert(
|
||||||
|
"payments".to_string(),
|
||||||
|
Value::Array(payments.into_iter().map(Value::Object).collect()),
|
||||||
|
);
|
||||||
|
Value::Object(loan)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_loan_lookup_by_address(text: &str) -> Value {
|
||||||
|
let mut address = String::new();
|
||||||
|
let mut declared_contracts = None;
|
||||||
|
let mut contracts = Vec::<Value>::new();
|
||||||
|
let mut current_contract = Vec::<String>::new();
|
||||||
|
|
||||||
|
for line in text.lines().map(str::trim) {
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = line.strip_prefix("address=") {
|
||||||
|
address = value.trim().to_string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = line.strip_prefix("contracts=") {
|
||||||
|
declared_contracts = value.trim().parse::<u64>().ok();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.starts_with("contract_") && line.ends_with(':') {
|
||||||
|
if !current_contract.is_empty() {
|
||||||
|
contracts.push(parse_loan_summary(¤t_contract.join("\n")));
|
||||||
|
current_contract.clear();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_contract.push(line.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current_contract.is_empty() {
|
||||||
|
contracts.push(parse_loan_summary(¤t_contract.join("\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"address": address,
|
||||||
|
"contracts": declared_contracts.unwrap_or(contracts.len() as u64),
|
||||||
|
"loans": contracts
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
// The standalone_tools module holds utility helpers that mirror node serialization and connection behavior.
|
// The standalone_tools module holds utility helpers that mirror node serialization and connection behavior.
|
||||||
pub mod connections;
|
pub mod connections;
|
||||||
|
pub mod loan_lookup_json;
|
||||||
pub mod transaction_creator;
|
pub mod transaction_creator;
|
||||||
pub mod vanity_resolver;
|
pub mod vanity_resolver;
|
||||||
|
pub mod wallet_registry_lookup;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::common::network_startup::get_connections;
|
||||||
|
use crate::records::memory::response_channels::generate_uid;
|
||||||
|
use crate::rpc::commands::wallet_registry_sync::WALLET_REGISTRY_RECORD_BYTES;
|
||||||
|
use crate::standalone_tools::connections::handshake;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
|
pub async fn lookup_pubkey_from_live_registry(
|
||||||
|
address: &str,
|
||||||
|
wallet_path: &str,
|
||||||
|
encryption_key: &str,
|
||||||
|
) -> Result<Vec<u8>, String> {
|
||||||
|
let short_address = Wallet::normalize_to_short_address(address)
|
||||||
|
.ok_or_else(|| format!("Invalid wallet address: {address}"))?;
|
||||||
|
let short_address_bytes = Wallet::short_address_to_bytes(&short_address)
|
||||||
|
.ok_or_else(|| format!("Invalid short wallet address: {short_address}"))?;
|
||||||
|
|
||||||
|
for conn in get_connections().await {
|
||||||
|
let Ok(socket_address) = conn.parse() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = handshake::connect_and_handshake(
|
||||||
|
socket_address,
|
||||||
|
String::new(),
|
||||||
|
39,
|
||||||
|
handshake::HandshakeWallet::WalletKey {
|
||||||
|
encryption_key: encryption_key.to_string(),
|
||||||
|
wallet_path: wallet_path.to_string(),
|
||||||
|
},
|
||||||
|
generate_uid(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let Ok(response) = result else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for chunk in response.chunks_exact(WALLET_REGISTRY_RECORD_BYTES) {
|
||||||
|
let registered_address = &chunk[..Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
|
if registered_address == short_address_bytes.as_slice() {
|
||||||
|
let public_key = chunk[Wallet::SHORT_ADDRESS_BYTES_LENGTH..].to_vec();
|
||||||
|
if Wallet::has_valid_public_key_bytes(&public_key) {
|
||||||
|
return Ok(public_key);
|
||||||
|
}
|
||||||
|
return Err(format!(
|
||||||
|
"Registry returned an invalid public key for {short_address}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!(
|
||||||
|
"Could not find registered public key for {short_address} from any configured peer."
|
||||||
|
))
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue