sybil fix, vanity fix, wallet selection fix
This commit is contained in:
parent
9f3f53ca34
commit
6cd01d1345
|
|
@ -1,11 +1,11 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::from_slice;
|
||||
use blockchain::read;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::wallets::structures::{SavedWallet, Wallet};
|
||||
use blockchain::wallets::structures::SavedWallet;
|
||||
use blockchain::Path;
|
||||
use serde_json::Value;
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ fn display_vanity_address(short_address: &str) -> Option<String> {
|
|||
Some(format!("{trimmed_payload}.{network_suffix}"))
|
||||
}
|
||||
|
||||
async fn persist_local_vanity_address(encryption_key: &str, tx_json: &str) {
|
||||
async fn persist_local_vanity_address(encryption_key: &str, tx_json: &str, wallet_path: &str) {
|
||||
// Only successful vanity transactions update the local saved wallet display field.
|
||||
let Ok(value) = serde_json::from_str::<Value>(tx_json) else {
|
||||
return;
|
||||
|
|
@ -41,7 +41,6 @@ async fn persist_local_vanity_address(encryption_key: &str, tx_json: &str) {
|
|||
};
|
||||
|
||||
// Read the saved wallet and only update it if it matches the vanity transaction sender.
|
||||
let wallet_path = Wallet::get_wallet_path().await;
|
||||
let wallet_bytes = match read(Path::new(&wallet_path)).await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => return,
|
||||
|
|
@ -63,7 +62,7 @@ async fn persist_local_vanity_address(encryption_key: &str, tx_json: &str) {
|
|||
};
|
||||
|
||||
saved_wallet.vanity_address = Some(display_vanity);
|
||||
saved_wallet.save_the_wallet(Path::new(&wallet_path)).await;
|
||||
saved_wallet.save_the_wallet(Path::new(wallet_path)).await;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -81,6 +80,7 @@ async fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
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.",
|
||||
|
|
@ -118,7 +118,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
@ -130,7 +133,7 @@ async fn main() {
|
|||
println!("{trimmed}");
|
||||
// When a vanity tx broadcasts successfully, keep the local wallet display in sync.
|
||||
if trimmed == "successful_broadcast: true" {
|
||||
persist_local_vanity_address(&encryption_key, &json).await;
|
||||
persist_local_vanity_address(&encryption_key, &json, &wallet_path).await;
|
||||
if let Some(hash) = &txid {
|
||||
println!("{hash}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::blocks::burn::{BurnTransaction, UnsignedBurnTransaction};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, 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::types::BURN_FEE;
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
|
|
@ -63,6 +64,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid fee.");
|
||||
let txfee = (txfee_f64 * 100_000_000.0).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -71,7 +73,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Load the wallet that owns the asset being burned.
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use blockchain::blocks::collateral::{
|
||||
CollateralClaimTransaction, UnsignedCollateralClaimTransaction,
|
||||
};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::COLLATERAL_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
|
|
@ -35,6 +36,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid fee");
|
||||
let txfee = (txfee_f64 * 100_000_000.0).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -43,7 +45,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Load the wallet so the transaction can use the saved short address and private key.
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::blocks::issue_token::{IssueTokenTransaction, UnsignedIssueTokenTransaction};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::ISSUE_TOKEN_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
|
|
@ -49,6 +50,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid fee.");
|
||||
let txfee = (txfee_f64 * 100_000_000.0).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -57,7 +59,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Load the creator wallet that signs the additional token issuance.
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use blockchain::blocks::loan_payment::{
|
||||
ContractPaymentTransaction, UnsignedContractPaymentTransaction,
|
||||
};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::BORROWER_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
|
|
@ -53,6 +54,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid fee");
|
||||
let txfee = (txfee_f64 * 100_000_000.0).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -61,7 +63,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Load the wallet so the transaction can use the saved short address and private key.
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use blockchain::blocks::loans::UnsignedLoanContractTransaction;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::LENDER_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::records::wallet_registry::resolve_local_input_short_address;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
use blockchain::{create_dir_all, AsyncWriteExt};
|
||||
|
|
@ -20,11 +21,6 @@ fn display_fee(value: u64) -> f64 {
|
|||
value as f64 / 100_000_000.0
|
||||
}
|
||||
|
||||
fn normalize_short_address_input(address: &str) -> Result<String, String> {
|
||||
// Accept local vanity/short input and resolve it into the real short address.
|
||||
resolve_local_input_short_address(address.trim())
|
||||
}
|
||||
|
||||
fn parse_start_date(input: &str) -> Result<u32, String> {
|
||||
// Loan start dates are entered as calendar dates and stored as local midnight timestamps.
|
||||
let date = NaiveDate::parse_from_str(input.trim(), "%Y-%m-%d")
|
||||
|
|
@ -43,6 +39,23 @@ fn parse_start_date(input: &str) -> Result<u32, String> {
|
|||
Ok(timestamp as u32)
|
||||
}
|
||||
|
||||
async fn load_signing_wallet() -> Option<Wallet> {
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
"Wallet key cannot be empty. Please try again.",
|
||||
)
|
||||
.await;
|
||||
|
||||
match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => Some(wallet),
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Loan contracts use transaction type 7 and are first signed by the lender.
|
||||
|
|
@ -130,12 +143,18 @@ async fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
// Load the lender wallet before resolving vanity inputs; remote vanity lookup needs a signed handshake.
|
||||
let wallet = match load_signing_wallet().await {
|
||||
Some(wallet) => wallet,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Resolve the borrower input before writing it into the loan offer.
|
||||
let borrower = prompt_visible("What is the wallet address of the borrower? ").await;
|
||||
let borrower = match normalize_short_address_input(borrower.trim()) {
|
||||
let borrower = match resolve_wallet_address_input(borrower.trim(), &wallet).await {
|
||||
Ok(address) => address,
|
||||
Err(_) => {
|
||||
println!("borrower wallet invalid");
|
||||
Err(err) => {
|
||||
println!("borrower wallet invalid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -151,20 +170,6 @@ async fn main() {
|
|||
.expect("Please enter a valid fee");
|
||||
let txfee = (txfee_f64 * 100_000_000.0).round() as u64;
|
||||
|
||||
// Load the lender wallet that creates the first loan-contract signature.
|
||||
let decryption_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(decryption_key, None).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let private_key = &wallet.saved.private_key;
|
||||
let lender = &wallet.saved.short_address;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::blocks::marketing::{MarketingTransaction, UnsignedMarketingTransaction};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::MARKETING_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
|
|
@ -75,6 +76,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid value.");
|
||||
let txfee = ((txfee_f32 as f64) * (100000000_f64)).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -82,7 +84,7 @@ async fn main() {
|
|||
)
|
||||
.await;
|
||||
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::CREATE_NFT_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
|
|
@ -60,6 +61,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid value.");
|
||||
let txfee = ((txfee_f32 as f64) * (100000000_f64)).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -67,7 +69,7 @@ async fn main() {
|
|||
)
|
||||
.await;
|
||||
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use blockchain::blocks::swap::UnsignedSwapTransaction;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::SWAP_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::records::wallet_registry::resolve_local_input_short_address;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::Duration;
|
||||
use blockchain::File;
|
||||
|
|
@ -20,10 +21,6 @@ fn display_fee(value: u64) -> f64 {
|
|||
value as f64 / 100_000_000.0
|
||||
}
|
||||
|
||||
fn normalize_short_address_input(address: &str) -> Result<String, String> {
|
||||
resolve_local_input_short_address(address.trim())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// set type and timestampe
|
||||
|
|
@ -75,14 +72,30 @@ async fn main() {
|
|||
.expect("Please enter a valid age");
|
||||
let value2 = (value2_f64 * (100000000_f64)).round() as u64;
|
||||
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_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(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// get user input swapper address
|
||||
let sender2 =
|
||||
prompt_visible("Please enter the wallet address of the account you are swapping with: ")
|
||||
.await;
|
||||
let sender2 = match normalize_short_address_input(&sender2) {
|
||||
let sender2 = match resolve_wallet_address_input(&sender2, &wallet).await {
|
||||
Ok(address) => address,
|
||||
Err(_) => {
|
||||
println!("reciver wallet is not valid");
|
||||
Err(err) => {
|
||||
println!("receiver wallet is not valid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -135,20 +148,6 @@ async fn main() {
|
|||
let expiration_time: u32 = expiration_duration.as_secs() as u32;
|
||||
let expires = timestamp + expiration_time;
|
||||
|
||||
let decryption_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(decryption_key, None).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let private_key = &wallet.saved.private_key;
|
||||
let address = &wallet.saved.short_address;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::CREATE_TOKEN_FEE;
|
||||
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
|
|
@ -61,6 +62,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid value.");
|
||||
let txfee = ((txfee_f32 as f64) * (100000000_f64)).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -68,7 +70,7 @@ async fn main() {
|
|||
)
|
||||
.await;
|
||||
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use blockchain::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, 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::types::{NON_BASE_TRANSFER_MIN_FEE, TRANSFER_FEE};
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::records::wallet_registry::resolve_local_input_short_address;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::File;
|
||||
use blockchain::Utc;
|
||||
|
|
@ -21,10 +22,6 @@ fn display_fee(value: u64) -> f64 {
|
|||
value as f64 / 100_000_000.0
|
||||
}
|
||||
|
||||
fn normalize_short_address_input(address: &str) -> Result<String, String> {
|
||||
resolve_local_input_short_address(address.trim())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let minimum_transfer_fee_percent = TRANSFER_FEE * 100.0;
|
||||
|
|
@ -60,12 +57,28 @@ async fn main() {
|
|||
.expect("Please enter a valid amount.");
|
||||
let value = ((value_f32 as f64) * 100_000_000.0).round() as u64;
|
||||
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_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(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let receiver_input = if args.len() > 3 {
|
||||
args[3].clone()
|
||||
} else {
|
||||
prompt_visible("Please enter the receiver wallet address: ").await
|
||||
};
|
||||
let receiver = match normalize_short_address_input(&receiver_input) {
|
||||
let receiver = match resolve_wallet_address_input(&receiver_input, &wallet).await {
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
let trimmed_receiver = receiver_input.trim();
|
||||
|
|
@ -146,20 +159,6 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid NFT series number.");
|
||||
|
||||
let decryption_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(decryption_key, None).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let private_key = &wallet.saved.private_key;
|
||||
let short_address = &wallet.saved.short_address;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use blockchain::blocks::vanity::{UnsignedVanityAddressTransaction, VanityAddressTransaction};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible};
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_visible, prompt_wallet_path};
|
||||
use blockchain::common::types::{VANITY_ADDRESS_FEE, VANITY_ADDRESS_TYPE};
|
||||
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
|
|
@ -70,6 +71,7 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Please enter a valid fee.");
|
||||
let txfee = ((txfee_f32 as f64) * 100_000_000.0).round() as u64;
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -78,7 +80,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Load the wallet that will own and sign the vanity registration.
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -24,6 +24,7 @@ async fn main() {
|
|||
println!("Please enter a valid 64-character block hash");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -44,7 +45,10 @@ async fn main() {
|
|||
socket_address,
|
||||
block_hash.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -26,6 +26,7 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
};
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -48,7 +49,10 @@ async fn main() {
|
|||
socket_address,
|
||||
payload.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
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::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
|
@ -25,12 +27,26 @@ async fn main() {
|
|||
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;
|
||||
|
|
@ -46,7 +62,10 @@ async fn main() {
|
|||
socket_address,
|
||||
wallet_address.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletParts {
|
||||
long_address: wallet.saved.long_address.clone(),
|
||||
private_key: wallet.saved.private_key.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::encode;
|
||||
use blockchain::env;
|
||||
|
|
@ -25,6 +25,7 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
};
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -46,7 +47,10 @@ async fn main() {
|
|||
socket_address,
|
||||
contract_hash.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -10,6 +10,7 @@ async fn main() {
|
|||
let hashmap_key = generate_uid();
|
||||
|
||||
let _args: Vec<String> = env::args().collect();
|
||||
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.",
|
||||
|
|
@ -32,7 +33,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -16,6 +16,7 @@ async fn main() {
|
|||
println!("Usage: ./request_height");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -40,7 +41,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -22,6 +22,7 @@ async fn main() {
|
|||
println!("Usage: ./large_tx_fee");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -46,7 +47,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ use blockchain::blocks::swap::SwapTransaction;
|
|||
use blockchain::blocks::token::CreateTokenTransaction;
|
||||
use blockchain::blocks::transfer::TransferTransaction;
|
||||
use blockchain::blocks::vanity::VanityAddressTransaction;
|
||||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::common::types::{
|
||||
BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
||||
|
|
@ -19,7 +20,9 @@ use blockchain::env;
|
|||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::rpc::command_maps;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::to_string_pretty;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
|
||||
async fn decode_one_transaction(tx_bytes: &[u8]) -> Option<String> {
|
||||
let txtype = *tx_bytes.first()?;
|
||||
|
|
@ -106,12 +109,26 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
let address = args[1].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 wallet = match Wallet::try_obtain_wallet(encryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let address = match resolve_wallet_address_input(&address, &wallet).await {
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
eprintln!("wallet address is not valid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let connections = get_connections().await;
|
||||
let mut connected = false;
|
||||
|
|
@ -126,7 +143,10 @@ async fn main() {
|
|||
socket_address,
|
||||
address.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletParts {
|
||||
long_address: wallet.saved.long_address.clone(),
|
||||
private_key: wallet.saved.private_key.clone(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ use blockchain::blocks::swap::SwapTransaction;
|
|||
use blockchain::blocks::token::CreateTokenTransaction;
|
||||
use blockchain::blocks::transfer::TransferTransaction;
|
||||
use blockchain::blocks::vanity::VanityAddressTransaction;
|
||||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::common::types::{
|
||||
BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
||||
|
|
@ -87,6 +88,7 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
let signature = args[1].trim().to_string();
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -107,7 +109,10 @@ async fn main() {
|
|||
socket_address,
|
||||
signature.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -15,6 +15,7 @@ async fn main() {
|
|||
println!("Usage: ./lookup_mempool_tx_count");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -36,7 +37,10 @@ async fn main() {
|
|||
socket_address,
|
||||
"".to_string(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
|
|
@ -86,6 +86,7 @@ async fn main() {
|
|||
println!("Usage: ./lookup_network_info");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -108,7 +109,10 @@ async fn main() {
|
|||
socket_address,
|
||||
"".to_string(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use blockchain::common::binary_conversions::binary_to_string;
|
||||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::encode;
|
||||
use blockchain::env;
|
||||
|
|
@ -181,6 +182,7 @@ async fn main() {
|
|||
|
||||
let nft_name = args[1].clone();
|
||||
let item_number = args[2].clone();
|
||||
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.",
|
||||
|
|
@ -203,7 +205,10 @@ async fn main() {
|
|||
socket_address,
|
||||
payload.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use blockchain::common::binary_conversions::binary_to_string;
|
||||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
|
|
@ -65,6 +66,7 @@ async fn main() {
|
|||
println!("Usage: ./nft_list");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -85,7 +87,10 @@ async fn main() {
|
|||
socket_address,
|
||||
"".to_string(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -16,6 +16,7 @@ async fn main() {
|
|||
println!("Usage: ./lookup_node_time");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -37,7 +38,10 @@ async fn main() {
|
|||
socket_address,
|
||||
"".to_string(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::from_str;
|
||||
use blockchain::read_to_string;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
use blockchain::standalone_tools::connections::handshake;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::tilde;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::Value;
|
||||
use rustyline::completion::FilenameCompleter;
|
||||
use rustyline::error::ReadlineError;
|
||||
|
|
@ -37,6 +39,7 @@ fn extract_address(contents: &str) -> Result<String, String> {
|
|||
let address = value
|
||||
.get("short_address")
|
||||
.and_then(|v| v.as_str())
|
||||
.or_else(|| value.get("vanity_address").and_then(|v| v.as_str()))
|
||||
.or_else(|| value.get("long_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());
|
||||
|
|
@ -110,12 +113,26 @@ async fn main() {
|
|||
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;
|
||||
}
|
||||
};
|
||||
|
||||
let json = wallet_address;
|
||||
// Try each configured peer until one returns a parsable balance response or text error.
|
||||
|
|
@ -131,7 +148,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletParts {
|
||||
long_address: wallet.saved.long_address.clone(),
|
||||
private_key: wallet.saved.private_key.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use blockchain::common::binary_conversions::binary_to_string;
|
||||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
|
|
@ -36,6 +37,7 @@ async fn main() {
|
|||
}
|
||||
|
||||
let token_name = args[1].clone();
|
||||
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.",
|
||||
|
|
@ -56,7 +58,10 @@ async fn main() {
|
|||
socket_address,
|
||||
token_name.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use blockchain::common::binary_conversions::binary_to_string;
|
||||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::json;
|
||||
|
|
@ -55,6 +56,7 @@ async fn main() {
|
|||
println!("Usage: ./token_list");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -75,7 +77,10 @@ async fn main() {
|
|||
socket_address,
|
||||
"".to_string(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -32,6 +32,7 @@ async fn main() {
|
|||
};
|
||||
|
||||
// Extract the encryption key from the command-line arguments
|
||||
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.",
|
||||
|
|
@ -52,7 +53,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::common::types::{
|
||||
BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
||||
|
|
@ -20,6 +20,7 @@ async fn main() {
|
|||
println!("Usage: ./total_transactions");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let encryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -46,7 +47,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ use blockchain::blocks::swap::SwapTransaction;
|
|||
use blockchain::blocks::token::CreateTokenTransaction;
|
||||
use blockchain::blocks::transfer::TransferTransaction;
|
||||
use blockchain::blocks::vanity::VanityAddressTransaction;
|
||||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::common::types::{
|
||||
BORROWER_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE, GENESIS_TYPE, LENDER_TYPE,
|
||||
|
|
@ -46,6 +47,7 @@ async fn main() {
|
|||
};
|
||||
|
||||
// Extract the encryption ley from the command-line argument
|
||||
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.",
|
||||
|
|
@ -68,7 +70,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(encryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: encryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::common::skein::skein_256_hash_bytes;
|
||||
use blockchain::env;
|
||||
|
|
@ -18,6 +18,7 @@ async fn main() {
|
|||
println!("Usage: ./register_wallet");
|
||||
return;
|
||||
}
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -26,7 +27,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Load the wallet so both address forms and the signing key come from the same saved file.
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key.clone(), None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key.clone(), Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
@ -73,7 +74,10 @@ async fn main() {
|
|||
socket_address,
|
||||
json.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(decryption_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: decryption_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
hashmap_key,
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -17,6 +17,7 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
let ip = args[1].trim().to_string();
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let wallet_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -25,7 +26,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Server-side verification expects a signature over the exact IP string.
|
||||
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
@ -48,7 +49,10 @@ async fn main() {
|
|||
socket_address,
|
||||
payload.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(wallet_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: wallet_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::network_startup::get_connections;
|
||||
use blockchain::env;
|
||||
use blockchain::records::memory::response_channels::generate_uid;
|
||||
|
|
@ -17,6 +17,7 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
let ip = args[1].trim().to_string();
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let wallet_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -25,7 +26,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Server-side verification expects a signature over the exact IP string.
|
||||
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
@ -48,7 +49,10 @@ async fn main() {
|
|||
socket_address,
|
||||
payload.clone(),
|
||||
rpc_command,
|
||||
handshake::HandshakeWallet::WalletKey(wallet_key.clone()),
|
||||
handshake::HandshakeWallet::WalletKey {
|
||||
encryption_key: wallet_key.clone(),
|
||||
wallet_path: wallet_path.clone(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use blockchain::common::cli_prompts::prompt_hidden_nonempty;
|
||||
use blockchain::common::cli_prompts::{prompt_hidden_nonempty, prompt_wallet_path};
|
||||
use blockchain::common::skein::skein_256_hash_data;
|
||||
use blockchain::env;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
|
|
@ -20,6 +20,7 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
};
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
|
||||
let decryption_key = prompt_hidden_nonempty(
|
||||
"What is your wallet decryption key? ",
|
||||
|
|
@ -28,7 +29,7 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
// Load the wallet whose private key will create the detached signature.
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
use blockchain::blocks::loans::UnsignedLoanContractTransaction;
|
||||
use blockchain::common::cli_prompts::{ask_yes_no_question, prompt_hidden_nonempty};
|
||||
use blockchain::common::cli_prompts::{
|
||||
ask_yes_no_question, prompt_hidden_nonempty, prompt_wallet_path,
|
||||
};
|
||||
use blockchain::env;
|
||||
|
||||
use blockchain::from_str;
|
||||
use blockchain::fs;
|
||||
use blockchain::json;
|
||||
use blockchain::read_to_string;
|
||||
use blockchain::records::wallet_registry::resolve_local_input_short_address;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::to_string_pretty;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::Value;
|
||||
|
|
@ -34,11 +37,6 @@ fn display_start_date(timestamp: u32) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn normalize_short_address_input(address: &str) -> Result<String, String> {
|
||||
// Accept local vanity/short input and resolve it into the real short address.
|
||||
resolve_local_input_short_address(address.trim())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Borrowers use this tool to review a lender-signed loan and add signature2.
|
||||
|
|
@ -67,27 +65,48 @@ async fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
// Load the borrower wallet before resolving vanity inputs; remote vanity lookup needs a signed handshake.
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
let decryption_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(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let private_key = &wallet.saved.private_key;
|
||||
let address = &wallet.saved.short_address;
|
||||
|
||||
// Extract every field that participates in the hash so the borrower signs the same bytes.
|
||||
let txtype = 7;
|
||||
let timestamp = json["timestamp"].as_u64().unwrap_or_default() as u32;
|
||||
let loan_coin = json["loan_coin"].as_str().unwrap_or_default().to_string();
|
||||
let loan_amount = json["loan_amount"].as_u64().unwrap_or_default();
|
||||
let lender = match normalize_short_address_input(json["lender"].as_str().unwrap_or_default()) {
|
||||
Ok(address) => address,
|
||||
Err(_) => {
|
||||
println!("Transaction is not valid. Lender address is not a valid short address.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let lender =
|
||||
match resolve_wallet_address_input(json["lender"].as_str().unwrap_or_default(), &wallet)
|
||||
.await
|
||||
{
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
println!("Transaction is not valid. Lender address is not valid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let collateral = json["collateral"].as_str().unwrap_or_default().to_string();
|
||||
let collateral_amount = json["collateral_amount"].as_u64().unwrap_or_default();
|
||||
let borrower =
|
||||
match normalize_short_address_input(json["borrower"].as_str().unwrap_or_default()) {
|
||||
match resolve_wallet_address_input(json["borrower"].as_str().unwrap_or_default(), &wallet)
|
||||
.await
|
||||
{
|
||||
Ok(address) => address,
|
||||
Err(_) => {
|
||||
println!(
|
||||
"Transaction is not valid. Borrower address is not a valid short address."
|
||||
);
|
||||
Err(err) => {
|
||||
println!("Transaction is not valid. Borrower address is not valid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -176,23 +195,6 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Load the borrower wallet and ensure it matches the borrower address in the offer.
|
||||
let decryption_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(decryption_key, None).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let private_key = &wallet.saved.private_key;
|
||||
let address = &wallet.saved.short_address;
|
||||
|
||||
if borrower.trim() != address.trim() {
|
||||
println!(
|
||||
"Transaction is not valid for your wallet address. Expected {borrower} found {address}"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
use blockchain::blocks::swap::UnsignedSwapTransaction;
|
||||
use blockchain::common::cli_prompts::{ask_yes_no_question, prompt_hidden_nonempty};
|
||||
use blockchain::common::cli_prompts::{
|
||||
ask_yes_no_question, prompt_hidden_nonempty, prompt_wallet_path,
|
||||
};
|
||||
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
||||
|
||||
use blockchain::env;
|
||||
use blockchain::fs;
|
||||
use blockchain::json;
|
||||
use blockchain::read_to_string;
|
||||
use blockchain::records::wallet_registry::resolve_local_input_short_address;
|
||||
use blockchain::standalone_tools::vanity_resolver::resolve_wallet_address_input;
|
||||
use blockchain::wallets::structures::Wallet;
|
||||
use blockchain::Value;
|
||||
|
||||
|
|
@ -16,10 +19,6 @@ fn pad_to_width(input: &str, width: usize) -> String {
|
|||
result
|
||||
}
|
||||
|
||||
fn normalize_short_address_input(address: &str) -> Result<String, String> {
|
||||
resolve_local_input_short_address(address.trim())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Get the filename from the command line arguments
|
||||
|
|
@ -29,13 +28,14 @@ async fn main() {
|
|||
return;
|
||||
}
|
||||
let filename = &args[1];
|
||||
let wallet_path = prompt_wallet_path().await;
|
||||
let decryption_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(decryption_key, None).await {
|
||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
eprintln!("Wallet decryption failed: {err}");
|
||||
|
|
@ -92,28 +92,32 @@ async fn main() {
|
|||
let txfee1_value: u64 = json["txfee1"].as_u64().unwrap_or_default();
|
||||
let tip1_value: u64 = json["tip1"].as_u64().unwrap_or_default();
|
||||
|
||||
let sender1 = match normalize_short_address_input(json["sender1"].as_str().unwrap_or_default())
|
||||
{
|
||||
Ok(address) => address,
|
||||
Err(_) => {
|
||||
println!("sender1 wallet invalid");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let sender1 =
|
||||
match resolve_wallet_address_input(json["sender1"].as_str().unwrap_or_default(), &wallet)
|
||||
.await
|
||||
{
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
println!("sender1 wallet invalid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let txfee2_value: u64 = json["txfee2"].as_u64().unwrap_or_default();
|
||||
let txfee2 = txfee2_value as f64 / 100000000.0;
|
||||
let tip2_value: u64 = json["tip2"].as_u64().unwrap_or_default();
|
||||
let tip2 = tip2_value as f64 / 100000000.0;
|
||||
|
||||
let sender2 = match normalize_short_address_input(json["sender2"].as_str().unwrap_or_default())
|
||||
{
|
||||
Ok(address) => address,
|
||||
Err(_) => {
|
||||
println!("sender2 wallet invalid");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let sender2 =
|
||||
match resolve_wallet_address_input(json["sender2"].as_str().unwrap_or_default(), &wallet)
|
||||
.await
|
||||
{
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
println!("sender2 wallet invalid: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// ensure wallet and sender2 match
|
||||
if sender2 != address.trim() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,52 @@
|
|||
use crate::read_password;
|
||||
use crate::stdout;
|
||||
use crate::AsyncWriteExt;
|
||||
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;
|
||||
use tokio::io::{stdin, AsyncBufReadExt, BufReader};
|
||||
|
||||
#[derive(RustyHelper, Completer, RustyHighlighter, RustyHinter, RustyValidator)]
|
||||
struct PathHelper {
|
||||
#[rustyline(Completer)]
|
||||
completer: FilenameCompleter,
|
||||
}
|
||||
|
||||
pub fn prompt_path(prompt: &str) -> Result<String, String> {
|
||||
// Rustyline gives CLI path prompts filesystem completion, including tab 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(value) => Ok(value.trim().to_string()),
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
Err("Input cancelled".to_string())
|
||||
}
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn prompt_wallet_path() -> String {
|
||||
loop {
|
||||
match prompt_path("Please enter the path to your wallet file: ") {
|
||||
Ok(path) if !path.trim().is_empty() => return path,
|
||||
Ok(_) => println!("Wallet path cannot be empty. Please try again."),
|
||||
Err(err) => println!("Failed to read wallet path: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn prompt_visible_with_default(prompt: &str, default: &str) -> String {
|
||||
// Show the default in brackets so pressing enter keeps the existing value.
|
||||
let full_prompt = format!("{prompt} [{default}]: ");
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ async fn create_genesis_block(
|
|||
|
||||
// Genesis uses the fixed parent hash and launch difficulty.
|
||||
let timestamp = Utc::now().timestamp() as u32;
|
||||
let next_block_difficulty = 3000000000000000_u64;
|
||||
let next_block_difficulty = 1200000000000000_u64;
|
||||
let block_struct = UnminedBlock::new(
|
||||
timestamp,
|
||||
miner,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@ use crate::common::binary_conversions::{binary_to_ip, ip_to_binary};
|
|||
use crate::lazy_static;
|
||||
use crate::log::{info, warn};
|
||||
use crate::records::memory::enums::{ClientType, ConnectionType};
|
||||
use crate::records::memory::network_mapping::monitor::{MONITOR_ACTION_ADD, MONITOR_ACTION_REMOVE};
|
||||
use crate::records::memory::network_mapping::structs::{MonitorAddressParams, SignedMonitorEdit};
|
||||
use crate::records::memory::network_mapping::NodeInfo;
|
||||
use crate::records::memory::response_channels::{delete_entry, reserve_entry, Command};
|
||||
use crate::records::memory::structs::{Connection, StoreConnectionParams};
|
||||
use crate::rpc::client::handshake::connect_and_handshake;
|
||||
use crate::rpc::client::handshake_processing::{bootstrap_peer_discovery, BootstrapParams};
|
||||
use crate::rpc::client::structs::Connect;
|
||||
use crate::rpc::command_maps::RPC_BLOCK_HEIGHT;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
|
|
@ -69,15 +74,63 @@ pub async fn set_reconnect_context(db: Db, wallet: Arc<Wallet>, map: Arc<Mutex<C
|
|||
*context = Some(ReconnectContext { db, wallet, map });
|
||||
}
|
||||
|
||||
async fn reconnect_dropped_outgoing(excluded_ip: &str) {
|
||||
async fn reconnect_replacement_inner(excluded_ip: &str) {
|
||||
// When an outgoing peer disappears, try to replace it with another
|
||||
// active node that is not already connected and is not the failed IP.
|
||||
let context = {
|
||||
let guard = RECONNECT_CONTEXT.lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
|
||||
let Some(context) = context else {
|
||||
warn!("[reconnect] no reconnect context configured");
|
||||
return;
|
||||
};
|
||||
|
||||
let excluded_ip_bytes = ip_to_binary(excluded_ip);
|
||||
let live_connection = {
|
||||
let guard = CONNECTIONS.read().await;
|
||||
guard.as_ref().and_then(|conn| {
|
||||
conn.connection_map.iter().find_map(|(key, info)| {
|
||||
if key.ip == excluded_ip_bytes {
|
||||
return None;
|
||||
}
|
||||
if ClientType::from_bytes(&info.client_type) != Some(ClientType::Miner) {
|
||||
return None;
|
||||
}
|
||||
let ip = binary_to_ip(key.ip.clone());
|
||||
let connections_key = format!("{}:{}", ip, key.port);
|
||||
Some((connections_key, Arc::clone(&info.stream)))
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
let Some((connections_key, stream)) = live_connection else {
|
||||
warn!("[reconnect] no live stream available for bootstrap recovery");
|
||||
return;
|
||||
};
|
||||
|
||||
let bootstrap_params = BootstrapParams {
|
||||
stream,
|
||||
connections_key,
|
||||
wallet: context.wallet,
|
||||
db: context.db,
|
||||
map: context.map,
|
||||
first: false,
|
||||
};
|
||||
|
||||
if let Err(err) = bootstrap_peer_discovery(bootstrap_params).await {
|
||||
warn!("[reconnect] bootstrap recovery failed: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn retry_dropped_outgoing(ip: String, port: u16) {
|
||||
if !try_start_reconnect() {
|
||||
warn!("[reconnect] replacement attempt already in progress, skipping duplicate request");
|
||||
warn!("[reconnect] reconnect attempt already in progress, skipping duplicate request");
|
||||
return;
|
||||
}
|
||||
|
||||
async {
|
||||
// When an outgoing peer disappears, try to replace it with another
|
||||
// active node that is not already connected and is not the failed IP.
|
||||
let context = {
|
||||
let guard = RECONNECT_CONTEXT.lock().await;
|
||||
guard.clone()
|
||||
|
|
@ -88,41 +141,41 @@ async fn reconnect_dropped_outgoing(excluded_ip: &str) {
|
|||
return;
|
||||
};
|
||||
|
||||
let excluded_ip_bytes = ip_to_binary(excluded_ip);
|
||||
let live_connection = {
|
||||
let guard = CONNECTIONS.read().await;
|
||||
guard.as_ref().and_then(|conn| {
|
||||
conn.connection_map.iter().find_map(|(key, info)| {
|
||||
if key.ip == excluded_ip_bytes {
|
||||
return None;
|
||||
}
|
||||
if ClientType::from_bytes(&info.client_type) != Some(ClientType::Miner) {
|
||||
return None;
|
||||
}
|
||||
let ip = binary_to_ip(key.ip.clone());
|
||||
let connections_key = format!("{}:{}", ip, key.port);
|
||||
Some((connections_key, Arc::clone(&info.stream)))
|
||||
})
|
||||
})
|
||||
};
|
||||
let addr_string = format!("{ip}:{port}");
|
||||
for attempt in 1..=3 {
|
||||
sleep(Duration::from_secs(30)).await;
|
||||
|
||||
let Some((connections_key, stream)) = live_connection else {
|
||||
warn!("[reconnect] no live stream available for bootstrap recovery");
|
||||
return;
|
||||
};
|
||||
let socket_addr = match addr_string.parse() {
|
||||
Ok(addr) => addr,
|
||||
Err(err) => {
|
||||
warn!("[reconnect] invalid dropped peer address {addr_string}: {err}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let bootstrap_params = BootstrapParams {
|
||||
stream,
|
||||
connections_key,
|
||||
wallet: context.wallet,
|
||||
db: context.db,
|
||||
map: context.map,
|
||||
first: false,
|
||||
};
|
||||
let connect = Connect {
|
||||
addr: socket_addr,
|
||||
node_ip: addr_string.clone(),
|
||||
wallet: context.wallet.clone(),
|
||||
db: context.db.clone(),
|
||||
map: context.map.clone(),
|
||||
first: false,
|
||||
};
|
||||
|
||||
if let Err(err) = bootstrap_peer_discovery(bootstrap_params).await {
|
||||
warn!("[reconnect] bootstrap recovery failed: {err}");
|
||||
match connect_and_handshake(connect).await {
|
||||
Ok(()) => {
|
||||
info!("[reconnect] reconnected dropped peer {addr_string} on attempt {attempt}");
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"[reconnect] failed to reconnect dropped peer {addr_string} on attempt {attempt}/3: {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reconnect_replacement_inner(&ip).await;
|
||||
}
|
||||
.await;
|
||||
|
||||
|
|
@ -207,11 +260,12 @@ impl Connection {
|
|||
port,
|
||||
stream.clone(),
|
||||
client_type.as_bytes(),
|
||||
address,
|
||||
address.clone(),
|
||||
);
|
||||
self.connection_map.insert(connection_key, connection_info);
|
||||
|
||||
if client_type == ClientType::Miner {
|
||||
spawn_monitor_update(ip.clone(), MONITOR_ACTION_ADD, address.clone(), port);
|
||||
Connection::client_checkup(stream, connection_type, ip, port, command_map);
|
||||
}
|
||||
true
|
||||
|
|
@ -232,6 +286,14 @@ impl Connection {
|
|||
};
|
||||
let removed = self.connection_map.remove(&connection_key);
|
||||
if let Some(connection_info) = removed.as_ref() {
|
||||
if ClientType::from_bytes(&connection_info.client_type) == Some(ClientType::Miner) {
|
||||
spawn_monitor_update(
|
||||
ip.clone(),
|
||||
MONITOR_ACTION_REMOVE,
|
||||
connection_info.wallet_address.clone(),
|
||||
port,
|
||||
);
|
||||
}
|
||||
let stream = Arc::clone(&connection_info.stream);
|
||||
tokio::spawn(async move {
|
||||
let mut stream_guard = stream.lock().await;
|
||||
|
|
@ -344,7 +406,7 @@ impl Connection {
|
|||
}
|
||||
drop(guard);
|
||||
if connection_type == ConnectionType::Outgoing {
|
||||
reconnect_dropped_outgoing(&ip).await;
|
||||
retry_dropped_outgoing(ip.clone(), port).await;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -434,6 +496,26 @@ impl Connection {
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn get_wallet_for_connection_key(key: &str) -> Option<Vec<u8>> {
|
||||
let (ip, port) = split_ip_port_key(key)?;
|
||||
let lock = CONNECTIONS.read().await;
|
||||
let conn = lock.as_ref()?;
|
||||
|
||||
let ip_bytes = ip_to_binary(&ip);
|
||||
conn.connection_map
|
||||
.iter()
|
||||
.find_map(|(connection_key, info)| {
|
||||
if connection_key.ip == ip_bytes
|
||||
&& connection_key.port == port
|
||||
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||
{
|
||||
Some(info.wallet_address.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Build the serialized connection key for a live stream when only
|
||||
// the stream handle is known.
|
||||
pub fn connection_key_for_stream(&self, stream: &Arc<Mutex<TcpStream>>) -> Option<String> {
|
||||
|
|
@ -533,6 +615,57 @@ impl Connection {
|
|||
}
|
||||
}
|
||||
|
||||
fn spawn_monitor_update(ip: String, action: u8, peer_wallet_bytes: Vec<u8>, port: u16) {
|
||||
tokio::spawn(async move {
|
||||
let context = {
|
||||
let guard = RECONNECT_CONTEXT.lock().await;
|
||||
guard.clone()
|
||||
};
|
||||
let Some(context) = context else {
|
||||
return;
|
||||
};
|
||||
let peer_long = Wallet::bytes_to_long_address(peer_wallet_bytes);
|
||||
let Some(monitored_address) = Wallet::normalize_to_short_address(&peer_long) else {
|
||||
return;
|
||||
};
|
||||
let monitoring_address = context.wallet.saved.short_address.clone();
|
||||
if monitored_address == monitoring_address {
|
||||
return;
|
||||
}
|
||||
let timestamp = crate::Utc::now().timestamp_millis() as u64;
|
||||
let signature = NodeInfo::monitor_signature(
|
||||
action,
|
||||
&monitored_address,
|
||||
&monitoring_address,
|
||||
&ip,
|
||||
timestamp,
|
||||
&context.wallet,
|
||||
)
|
||||
.await;
|
||||
let edit = SignedMonitorEdit {
|
||||
action,
|
||||
monitored_address,
|
||||
monitoring_address,
|
||||
target_ip: ip.clone(),
|
||||
modified_timestamp: timestamp,
|
||||
modified_signature: signature,
|
||||
};
|
||||
let params = MonitorAddressParams {
|
||||
map: context.map.clone(),
|
||||
edit,
|
||||
remote_ip: String::new(),
|
||||
db: context.db.clone(),
|
||||
wallet: context.wallet.clone(),
|
||||
connections_key: format!("{ip}:{port}"),
|
||||
};
|
||||
let _ = if action == MONITOR_ACTION_ADD {
|
||||
NodeInfo::add_monitor(params).await
|
||||
} else {
|
||||
NodeInfo::remove_monitor(params).await
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONNECTIONS: Arc<RwLock<Option<Connection>>> = Arc::new(RwLock::new(None));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,8 +82,6 @@ impl NodeInfo {
|
|||
connections_key,
|
||||
} = params;
|
||||
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
||||
let direct_peer_announcement = !remote_ip.is_empty() && edit.ip == remote_ip;
|
||||
|
||||
if !is_public_network_address(&edit.ip) {
|
||||
return RpcResponse::Binary(b"Error: Invalid network address".to_vec());
|
||||
}
|
||||
|
|
@ -129,7 +127,7 @@ impl NodeInfo {
|
|||
let valid_added_by = signer_node
|
||||
.map(|node| {
|
||||
(current_timestamp - node.added_timestamp) >= 3600
|
||||
&& node.deleted_by.is_empty()
|
||||
&& node.deleted_timestamp == 0
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if !valid_added_by {
|
||||
|
|
@ -162,15 +160,14 @@ impl NodeInfo {
|
|||
}
|
||||
|
||||
// Existing deleted entries can be revived in place when the same
|
||||
// address/IP pair is re-announced, otherwise the older record is
|
||||
// discarded and replaced.
|
||||
// address/IP pair is re-announced. Different active wallets on the
|
||||
// same IP are still rejected, but deleted records remain in place
|
||||
// for historical validation.
|
||||
if let Some(existing_node) = address_map.get_mut(&edit.address) {
|
||||
if !existing_node.deleted_by.is_empty() {
|
||||
if existing_node.deleted_timestamp > 0 {
|
||||
if existing_node.ip == edit.ip {
|
||||
existing_node.deleted_by = "".to_string();
|
||||
existing_node.deleted_timestamp = 0_u64;
|
||||
existing_node.deleted_block = 0_u32;
|
||||
existing_node.deleted_signature = "".to_string();
|
||||
return RpcResponse::Binary(b"Success".to_vec());
|
||||
} else {
|
||||
address_map.remove(&edit.address);
|
||||
|
|
@ -190,9 +187,7 @@ impl NodeInfo {
|
|||
}
|
||||
|
||||
if let Some(existing_node) = address_map.values_mut().find(|node| node.ip == edit.ip) {
|
||||
if !existing_node.deleted_by.is_empty() {
|
||||
address_map.retain(|_, node| node.ip != edit.ip);
|
||||
} else if edit.ip != GENESIS_IP {
|
||||
if existing_node.deleted_timestamp == 0 && edit.ip != GENESIS_IP {
|
||||
penalize_duplicate_ip = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -236,19 +231,6 @@ impl NodeInfo {
|
|||
)
|
||||
.await;
|
||||
|
||||
if direct_peer_announcement {
|
||||
// Only direct self-announcements get a ping monitor. Imported or
|
||||
// rebroadcast map records are not guaranteed to have a live stream.
|
||||
Self::ping(PingMonitorParams {
|
||||
map,
|
||||
edit,
|
||||
remote_ip,
|
||||
db,
|
||||
wallet,
|
||||
connections_key,
|
||||
});
|
||||
}
|
||||
|
||||
RpcResponse::Binary(b"Success".to_vec())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,292 +1,8 @@
|
|||
use super::*;
|
||||
|
||||
impl NodeInfo {
|
||||
pub fn ping(params: PingMonitorParams) {
|
||||
tokio::spawn(async move {
|
||||
let PingMonitorParams {
|
||||
map,
|
||||
edit,
|
||||
remote_ip,
|
||||
db,
|
||||
wallet,
|
||||
connections_key,
|
||||
} = params;
|
||||
let task_addr = edit.address.clone();
|
||||
let task_ip = edit.ip.clone();
|
||||
let task_wallet = edit.modified_by.clone();
|
||||
let task_signature = edit.modified_signature.clone();
|
||||
let added_timestamp = edit.modified_timestamp;
|
||||
|
||||
let task_remote_ip = remote_ip;
|
||||
let task_signing_wallet = wallet;
|
||||
let task_connections_key = connections_key;
|
||||
let task_db = db.clone();
|
||||
|
||||
{
|
||||
let mut monitors = PING_MONITORS.lock().await;
|
||||
// A matching signature means this exact monitor is already active
|
||||
// for the node, so do not start a duplicate ping task.
|
||||
if monitors
|
||||
.get(&task_addr)
|
||||
.map(|current| current == &task_signature)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
monitors.insert(task_addr.clone(), task_signature.clone());
|
||||
}
|
||||
|
||||
let mut failures = 0;
|
||||
|
||||
// Periodically ping the node and remove it after repeated
|
||||
// failures, then recursively reattach any inherited children.
|
||||
loop {
|
||||
sleep(Duration::from_secs(120)).await;
|
||||
|
||||
{
|
||||
let monitors = PING_MONITORS.lock().await;
|
||||
// Stop this task if another monitor replaced it.
|
||||
if !monitors
|
||||
.get(&task_addr)
|
||||
.map(|current| current == &task_signature)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let map_lock = ADDRESS_MAP.lock().await;
|
||||
if let Some(node) = map_lock.get(&task_addr) {
|
||||
// Stop monitoring stale node data after the map entry
|
||||
// has been replaced by a newer edit.
|
||||
if node.added_by != task_wallet || node.added_signature != task_signature {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(unlocked_stream) =
|
||||
Connection::get_stream_from_memory(&task_connections_key).await
|
||||
{
|
||||
let command = RPC_BLOCK_HEIGHT;
|
||||
let (ping_key, _ping_tx, ping_rx) = reserve_entry(map.clone()).await;
|
||||
|
||||
// Liveness uses a normal block-height RPC and waits for the
|
||||
// reserved reply channel to receive any response.
|
||||
let mut message = vec![command];
|
||||
message.extend_from_slice(&ping_key);
|
||||
RpcResponse::send_raw(&unlocked_stream, Some(&task_connections_key), &message)
|
||||
.await;
|
||||
|
||||
let response = timeout(Duration::from_secs(10), async {
|
||||
let mut rx = ping_rx.lock().await;
|
||||
rx.recv().await
|
||||
})
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(Some(_buffer)) => {
|
||||
failures = 0;
|
||||
}
|
||||
_ => {
|
||||
failures += 1;
|
||||
warn!("[network_map] ping failure: address={task_addr} ip={task_ip} failures={failures}");
|
||||
if failures >= 3 {
|
||||
warn!("[network_map] deleting node after ping failures: address={task_addr} ip={task_ip} responsible_by={task_wallet}");
|
||||
let _ = Self::delete_address(DeleteAddressParams {
|
||||
map: map.clone(),
|
||||
edit: SignedNodeEdit {
|
||||
address: task_addr.clone(),
|
||||
ip: task_ip.clone(),
|
||||
modified_by: task_wallet.clone(),
|
||||
modified_timestamp: added_timestamp,
|
||||
modified_signature: task_signature.clone(),
|
||||
},
|
||||
remote_ip: task_remote_ip.clone(),
|
||||
db: task_db.clone(),
|
||||
wallet: task_signing_wallet.clone(),
|
||||
connections_key: task_connections_key.clone(),
|
||||
})
|
||||
.await;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The direct socket disappeared before this monitor fired.
|
||||
// Connection cleanup is owned by the connection manager, so
|
||||
// stop this map monitor without marking the node deleted.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Self::release_ping_monitor(&task_addr, &task_signature).await;
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn delete_address(params: DeleteAddressParams) -> RpcResponse {
|
||||
let DeleteAddressParams {
|
||||
map,
|
||||
mut edit,
|
||||
remote_ip,
|
||||
db,
|
||||
wallet,
|
||||
connections_key,
|
||||
} = params;
|
||||
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
||||
|
||||
// Locally initiated deletions are re-signed with fresh metadata
|
||||
// before they are applied and broadcast.
|
||||
if remote_ip.is_empty() {
|
||||
edit.modified_timestamp = current_timestamp;
|
||||
edit.modified_by = wallet.saved.long_address.clone();
|
||||
edit.modified_signature =
|
||||
Self::added_signature(&edit.address, &edit.ip, current_timestamp, &wallet).await;
|
||||
}
|
||||
|
||||
let data = format!(
|
||||
"{}{}{}{}",
|
||||
edit.address, edit.ip, edit.modified_by, edit.modified_timestamp
|
||||
);
|
||||
let hashed_data = skein_256_hash_data(&data);
|
||||
|
||||
if !Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &edit.modified_by)
|
||||
.await
|
||||
{
|
||||
return RpcResponse::Binary(b"Error: Could not validate signature".to_vec());
|
||||
}
|
||||
|
||||
{
|
||||
let mut address_map = ADDRESS_MAP.lock().await;
|
||||
|
||||
if get_height(&db) > 10_000 {
|
||||
// Mature chains only allow established miners to remove nodes.
|
||||
let signer_key = Wallet::normalize_to_short_address(&edit.modified_by)
|
||||
.unwrap_or_else(|| edit.modified_by.clone());
|
||||
let signer_node = address_map.get(&signer_key);
|
||||
let valid_added_by = signer_node
|
||||
.map(|node| {
|
||||
(current_timestamp - node.added_timestamp) >= 3600
|
||||
&& node.deleted_by.is_empty()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if !valid_added_by {
|
||||
return RpcResponse::Binary(
|
||||
b"Error: Address must exist for 60m and not be deleted".to_vec(),
|
||||
);
|
||||
}
|
||||
|
||||
let mined_count = signer_node.map(|node| node.blocks_mined).unwrap_or(0);
|
||||
if mined_count < 100 {
|
||||
return RpcResponse::Binary(
|
||||
b"Error: Must mine 100 blocks to remove nodes".to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let deleted_count_last_hour = address_map
|
||||
.values()
|
||||
.filter(|node| {
|
||||
node.deleted_by == edit.modified_by
|
||||
&& (current_timestamp - node.deleted_timestamp) <= 3600
|
||||
})
|
||||
.count();
|
||||
|
||||
// Rate limit delete events per signer to prevent churn in the
|
||||
// shared network map.
|
||||
if deleted_count_last_hour >= 10 {
|
||||
return RpcResponse::Binary(b"Error: Max 10 deletions in 60m".to_vec());
|
||||
}
|
||||
|
||||
if let Some(existing_node) = address_map.get_mut(&edit.address) {
|
||||
if !existing_node.deleted_by.is_empty() {
|
||||
return RpcResponse::Binary(
|
||||
b"Error: This address has already been deleted".to_vec(),
|
||||
);
|
||||
}
|
||||
|
||||
// Deletion is recorded as metadata rather than immediately
|
||||
// removing the node, preserving historical validation context.
|
||||
existing_node.deleted_by = edit.modified_by.clone();
|
||||
existing_node.deleted_timestamp = current_timestamp;
|
||||
existing_node.deleted_block = get_height(&db) + 1;
|
||||
existing_node.deleted_signature = edit.modified_signature.clone();
|
||||
info!(
|
||||
"[network_map] node marked deleted: address={} ip={} deleted_by={} timestamp={} deleted_block={}",
|
||||
edit.address,
|
||||
edit.ip,
|
||||
edit.modified_by,
|
||||
current_timestamp,
|
||||
existing_node.deleted_block
|
||||
);
|
||||
} else {
|
||||
return RpcResponse::Binary(b"Error: Address not found".to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
// Stop any ping task owned by the deleted node record.
|
||||
Self::release_ping_monitor(&edit.address, &edit.modified_signature).await;
|
||||
|
||||
// Deletions propagate to peers and also tear down any live
|
||||
// outgoing connection so bootstrap can recover a replacement.
|
||||
Self::broadcast_node(
|
||||
map.clone(),
|
||||
&edit,
|
||||
&remote_ip,
|
||||
NodeEditType::Delete,
|
||||
&connections_key,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(port) = CONNECTIONS
|
||||
.read()
|
||||
.await
|
||||
.as_ref()
|
||||
.and_then(|conn| conn.find_outgoing_port(&edit.ip))
|
||||
{
|
||||
let mut writer = CONNECTIONS.write().await;
|
||||
if let Some(conn) = writer.as_mut() {
|
||||
// Drop the live outgoing socket after marking the node deleted.
|
||||
conn.drop_connection(ConnectionType::Outgoing, edit.ip.clone(), port);
|
||||
info!(
|
||||
"[connection_manager] dropped dead outgoing connection: {}:{}",
|
||||
edit.ip, port
|
||||
);
|
||||
}
|
||||
drop(writer);
|
||||
|
||||
let live_connection = {
|
||||
let guard = CONNECTIONS.read().await;
|
||||
guard.as_ref().and_then(|conn| {
|
||||
conn.get_all_streams().first().and_then(|stream| {
|
||||
conn.connection_key_for_stream(stream)
|
||||
.map(|key| (key, Arc::clone(stream)))
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
if let Some((connections_key, new_stream)) = live_connection {
|
||||
// Use any remaining live stream to refresh peer discovery and
|
||||
// replace the removed outgoing connection.
|
||||
let bootstrap_params = BootstrapParams {
|
||||
stream: new_stream,
|
||||
connections_key,
|
||||
wallet: wallet.clone(),
|
||||
db: db.clone(),
|
||||
map: map.clone(),
|
||||
first: false,
|
||||
};
|
||||
spawn_reconnect_bootstrap(bootstrap_params);
|
||||
} else {
|
||||
warn!("[reconnect] No live stream found to bootstrap from");
|
||||
}
|
||||
}
|
||||
|
||||
RpcResponse::Binary(b"Success: Node marked as deleted".to_vec())
|
||||
let _ = params;
|
||||
RpcResponse::Binary(b"Error: explicit node deletion is disabled".to_vec())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
use crate::rpc::command_maps::{RPC_ADD_NETWORK_NODE, RPC_DELETE_NETWORK_NODE};
|
||||
use crate::rpc::command_maps::{
|
||||
RPC_ADD_NETWORK_NODE, RPC_NETWORK_MONITOR_ADD, RPC_NETWORK_MONITOR_REMOVE,
|
||||
};
|
||||
|
||||
// NodeEditType keeps the network membership update type explicit at the call sites.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum NodeEditType {
|
||||
Add,
|
||||
Delete,
|
||||
MonitorAdd,
|
||||
MonitorRemove,
|
||||
}
|
||||
|
||||
impl NodeEditType {
|
||||
|
|
@ -12,7 +15,8 @@ impl NodeEditType {
|
|||
pub fn message_type(self) -> u8 {
|
||||
match self {
|
||||
NodeEditType::Add => RPC_ADD_NETWORK_NODE,
|
||||
NodeEditType::Delete => RPC_DELETE_NETWORK_NODE,
|
||||
NodeEditType::MonitorAdd => RPC_NETWORK_MONITOR_ADD,
|
||||
NodeEditType::MonitorRemove => RPC_NETWORK_MONITOR_REMOVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use super::*;
|
||||
use crate::records::unpack_block::unpack_header::load_block_header;
|
||||
|
||||
impl NodeInfo {
|
||||
pub async fn increment_mined(address: &str) {
|
||||
|
|
@ -39,6 +40,18 @@ impl NodeInfo {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn set_deleted_metadata_from_mapping(
|
||||
address: &str,
|
||||
deleted_timestamp: u64,
|
||||
deleted_block: u32,
|
||||
) {
|
||||
let mut map = ADDRESS_MAP.lock().await;
|
||||
if let Some(node_info) = map.get_mut(address) {
|
||||
node_info.deleted_timestamp = deleted_timestamp;
|
||||
node_info.deleted_block = deleted_block;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rebuild_mined_counts_from_chain(db: &Db) -> Result<(), String> {
|
||||
// Recompute node mined counts directly from saved block headers
|
||||
// so startup and recovery can rebuild memory-only state.
|
||||
|
|
|
|||
|
|
@ -8,24 +8,17 @@ use crate::log::{error, info, warn};
|
|||
use crate::records::block_height::get_block_height::get_height;
|
||||
use crate::records::ip_score::enums::InfractionType;
|
||||
use crate::records::ip_score::score::update_ip_score;
|
||||
use crate::records::memory::connections::{spawn_reconnect_bootstrap, CONNECTIONS};
|
||||
use crate::records::memory::enums::ConnectionType;
|
||||
use crate::records::memory::connections::CONNECTIONS;
|
||||
use crate::records::memory::network_mapping::enums::NodeEditType;
|
||||
use crate::records::memory::network_mapping::structs::{
|
||||
AddAddressParams, DeleteAddressParams, PingMonitorParams, SignedNodeEdit, NODE_RECORD_BYTES,
|
||||
AddAddressParams, DeleteAddressParams, MonitorAddressParams, SignedMonitorEdit, SignedNodeEdit,
|
||||
NODE_RECORD_FIXED_BYTES,
|
||||
};
|
||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||
use crate::records::memory::structs::Connection;
|
||||
use crate::records::unpack_block::unpack_header::load_block_header;
|
||||
use crate::rpc::client::handshake_processing::BootstrapParams;
|
||||
use crate::rpc::command_maps::RPC_BLOCK_HEIGHT;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
use crate::sleep;
|
||||
use crate::timeout;
|
||||
use crate::wallets::structures::Wallet;
|
||||
use crate::Arc;
|
||||
use crate::Duration;
|
||||
use crate::HashMap;
|
||||
use crate::Mutex;
|
||||
use crate::TcpStream;
|
||||
|
|
@ -33,7 +26,6 @@ use crate::Utc;
|
|||
|
||||
lazy_static! {
|
||||
static ref ADDRESS_MAP: Mutex<HashMap<String, NodeInfo>> = Mutex::new(HashMap::new());
|
||||
static ref PING_MONITORS: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -43,24 +35,12 @@ pub struct NodeInfo {
|
|||
added_by: String,
|
||||
added_timestamp: u64,
|
||||
added_signature: String,
|
||||
deleted_by: String,
|
||||
deleted_timestamp: u64,
|
||||
deleted_block: u32,
|
||||
deleted_signature: String,
|
||||
monitoring: Vec<String>,
|
||||
}
|
||||
|
||||
impl NodeInfo {
|
||||
async fn release_ping_monitor(address: &str, signature: &str) {
|
||||
let mut monitors = PING_MONITORS.lock().await;
|
||||
if monitors
|
||||
.get(address)
|
||||
.map(|current| current == signature)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
monitors.remove(address);
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
ip: String,
|
||||
blocks_mined: u8,
|
||||
|
|
@ -74,10 +54,9 @@ impl NodeInfo {
|
|||
added_by,
|
||||
added_timestamp,
|
||||
added_signature,
|
||||
deleted_by: "".to_string(),
|
||||
deleted_timestamp: 0_u64,
|
||||
deleted_block: 0_u32,
|
||||
deleted_signature: "".to_string(),
|
||||
monitoring: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,5 +65,6 @@ mod add;
|
|||
mod delete;
|
||||
pub mod enums;
|
||||
mod mined_counts;
|
||||
pub(crate) mod monitor;
|
||||
mod queries;
|
||||
pub mod structs;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
use super::*;
|
||||
use crate::records::wallet_registry::resolve_pubkey_from_short_address;
|
||||
|
||||
pub const MONITOR_ACTION_ADD: u8 = 1;
|
||||
pub const MONITOR_ACTION_REMOVE: u8 = 2;
|
||||
|
||||
impl NodeInfo {
|
||||
pub async fn monitor_signature(
|
||||
action: u8,
|
||||
monitored_address: &str,
|
||||
monitoring_address: &str,
|
||||
target_ip: &str,
|
||||
current_timestamp: u64,
|
||||
wallet: &Arc<Wallet>,
|
||||
) -> String {
|
||||
let private_key = &wallet.saved.private_key;
|
||||
let data = format!(
|
||||
"{action}{monitored_address}{monitoring_address}{target_ip}{current_timestamp}"
|
||||
);
|
||||
let hashed_data = skein_256_hash_data(&data);
|
||||
Wallet::sign_transaction(&hashed_data, private_key).await
|
||||
}
|
||||
|
||||
pub async fn broadcast_monitor(
|
||||
map: Arc<Mutex<Command>>,
|
||||
edit: &SignedMonitorEdit,
|
||||
remote_ip: &str,
|
||||
edittype: NodeEditType,
|
||||
connections_key: &str,
|
||||
) {
|
||||
let monitored_bytes = match Wallet::short_address_to_bytes(&edit.monitored_address) {
|
||||
Some(bytes) => bytes,
|
||||
None => return,
|
||||
};
|
||||
let monitoring_bytes = match Wallet::short_address_to_bytes(&edit.monitoring_address) {
|
||||
Some(bytes) => bytes,
|
||||
None => return,
|
||||
};
|
||||
let target_ip_bytes = ip_to_binary(&edit.target_ip);
|
||||
let timestamp_bytes = edit.modified_timestamp.to_le_bytes();
|
||||
let signature_bytes = match decode(&edit.modified_signature) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let connections_lock = CONNECTIONS.read().await;
|
||||
let streams = connections_lock
|
||||
.as_ref()
|
||||
.map(|connection| connection.get_all_streams());
|
||||
|
||||
if let Some(streams) = streams {
|
||||
for unlocked_stream in streams {
|
||||
let peer_addr = {
|
||||
let stream = unlocked_stream.lock().await;
|
||||
stream.peer_addr()
|
||||
};
|
||||
let Ok(addr) = peer_addr else {
|
||||
continue;
|
||||
};
|
||||
if !remote_ip.is_empty() && addr.ip().to_string() == remote_ip {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (hashmap_key, _hashmap_tx, hashmap_rx) = reserve_entry(map.clone()).await;
|
||||
let mut message = Vec::new();
|
||||
message.push(edittype.message_type());
|
||||
message.extend_from_slice(&hashmap_key);
|
||||
message.push(edit.action);
|
||||
message.extend_from_slice(&monitored_bytes);
|
||||
message.extend_from_slice(&monitoring_bytes);
|
||||
message.extend_from_slice(&target_ip_bytes);
|
||||
message.extend_from_slice(×tamp_bytes);
|
||||
message.extend_from_slice(&signature_bytes);
|
||||
RpcResponse::send_raw(&unlocked_stream, Some(connections_key), &message).await;
|
||||
let mut rx = hashmap_rx.lock().await;
|
||||
let _ = rx.recv().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_deleted_and_cascade(
|
||||
address_map: &mut HashMap<String, NodeInfo>,
|
||||
deleted_address: &str,
|
||||
current_timestamp: u64,
|
||||
deleted_block: u32,
|
||||
) {
|
||||
let mut stack = vec![deleted_address.to_string()];
|
||||
|
||||
while let Some(address) = stack.pop() {
|
||||
let should_cascade = match address_map.get_mut(&address) {
|
||||
Some(node) if node.deleted_timestamp == 0 && node.monitoring.is_empty() => {
|
||||
node.deleted_timestamp = current_timestamp;
|
||||
node.deleted_block = deleted_block;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !should_cascade {
|
||||
continue;
|
||||
}
|
||||
|
||||
info!(
|
||||
"[network_map] node marked deleted: address={} timestamp={} deleted_block={}",
|
||||
address, current_timestamp, deleted_block
|
||||
);
|
||||
|
||||
let targets: Vec<String> = address_map
|
||||
.iter_mut()
|
||||
.filter_map(|(target, node)| {
|
||||
let before = node.monitoring.len();
|
||||
node.monitoring.retain(|monitor| monitor != &address);
|
||||
if before != node.monitoring.len() && node.monitoring.is_empty() {
|
||||
Some(target.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for target in targets {
|
||||
stack.push(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn verify_monitor_edit(edit: &SignedMonitorEdit, db: &Db) -> bool {
|
||||
let Ok(Some(pubkey)) = resolve_pubkey_from_short_address(db, &edit.monitoring_address)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let signer = Wallet::bytes_to_long_address(pubkey);
|
||||
let data = format!(
|
||||
"{}{}{}{}{}",
|
||||
edit.action,
|
||||
edit.monitored_address,
|
||||
edit.monitoring_address,
|
||||
edit.target_ip,
|
||||
edit.modified_timestamp
|
||||
);
|
||||
let hashed_data = skein_256_hash_data(&data);
|
||||
Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &signer).await
|
||||
}
|
||||
|
||||
pub async fn add_monitor(params: MonitorAddressParams) -> RpcResponse {
|
||||
Self::apply_monitor(params, MONITOR_ACTION_ADD).await
|
||||
}
|
||||
|
||||
pub async fn remove_monitor(params: MonitorAddressParams) -> RpcResponse {
|
||||
Self::apply_monitor(params, MONITOR_ACTION_REMOVE).await
|
||||
}
|
||||
|
||||
async fn apply_monitor(params: MonitorAddressParams, action: u8) -> RpcResponse {
|
||||
let MonitorAddressParams {
|
||||
map,
|
||||
mut edit,
|
||||
remote_ip,
|
||||
db,
|
||||
wallet,
|
||||
connections_key,
|
||||
} = params;
|
||||
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
||||
|
||||
if edit.action != action {
|
||||
return RpcResponse::Binary(b"Error: Invalid monitor action".to_vec());
|
||||
}
|
||||
|
||||
let local_short = wallet.saved.short_address.clone();
|
||||
if remote_ip.is_empty() && edit.monitoring_address == local_short {
|
||||
edit.modified_timestamp = current_timestamp;
|
||||
edit.modified_signature = Self::monitor_signature(
|
||||
action,
|
||||
&edit.monitored_address,
|
||||
&edit.monitoring_address,
|
||||
&edit.target_ip,
|
||||
current_timestamp,
|
||||
&wallet,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if !Self::verify_monitor_edit(&edit, &db).await {
|
||||
return RpcResponse::Binary(b"Error: Could not validate monitor signature".to_vec());
|
||||
}
|
||||
|
||||
{
|
||||
let mut address_map = ADDRESS_MAP.lock().await;
|
||||
let Some(monitored) = address_map.get_mut(&edit.monitored_address) else {
|
||||
return RpcResponse::Binary(b"Error: monitored address not found".to_vec());
|
||||
};
|
||||
if monitored.ip != edit.target_ip {
|
||||
return RpcResponse::Binary(b"Error: monitor target IP mismatch".to_vec());
|
||||
}
|
||||
|
||||
match action {
|
||||
MONITOR_ACTION_ADD => {
|
||||
if !monitored.monitoring.contains(&edit.monitoring_address) {
|
||||
monitored.monitoring.push(edit.monitoring_address.clone());
|
||||
}
|
||||
monitored.deleted_timestamp = 0;
|
||||
monitored.deleted_block = 0;
|
||||
}
|
||||
MONITOR_ACTION_REMOVE => {
|
||||
monitored
|
||||
.monitoring
|
||||
.retain(|monitor| monitor != &edit.monitoring_address);
|
||||
if monitored.monitoring.is_empty() {
|
||||
let deleted_block = get_height(&db) + 1;
|
||||
Self::mark_deleted_and_cascade(
|
||||
&mut address_map,
|
||||
&edit.monitored_address,
|
||||
current_timestamp,
|
||||
deleted_block,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => return RpcResponse::Binary(b"Error: Invalid monitor action".to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
let broadcast_type = if action == MONITOR_ACTION_ADD {
|
||||
NodeEditType::MonitorAdd
|
||||
} else {
|
||||
NodeEditType::MonitorRemove
|
||||
};
|
||||
Self::broadcast_monitor(map, &edit, &remote_ip, broadcast_type, &connections_key).await;
|
||||
|
||||
RpcResponse::Binary(b"Success".to_vec())
|
||||
}
|
||||
|
||||
pub async fn set_monitors_from_mapping(address: &str, monitors: Vec<String>) {
|
||||
let mut address_map = ADDRESS_MAP.lock().await;
|
||||
if let Some(node) = address_map.get_mut(address) {
|
||||
node.monitoring = monitors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ impl NodeInfo {
|
|||
map.values()
|
||||
// Deleted nodes stay in the map for history, but active node
|
||||
// lists should only return bare public IPs.
|
||||
.filter(|node_info| node_info.deleted_by.is_empty())
|
||||
.filter(|node_info| node_info.deleted_timestamp == 0)
|
||||
.map(|node_info| node_info.ip.clone())
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -55,10 +55,10 @@ impl NodeInfo {
|
|||
}
|
||||
|
||||
pub async fn request_valid_nodes() -> RpcResponse {
|
||||
// Serialize the in-memory node map into the fixed binary layout
|
||||
// Serialize the in-memory node map into the binary layout
|
||||
// used by peer bootstrap and node-list synchronization.
|
||||
let map = ADDRESS_MAP.lock().await;
|
||||
let mut data: Vec<u8> = Vec::with_capacity(map.len() * NODE_RECORD_BYTES);
|
||||
let mut data: Vec<u8> = Vec::with_capacity(map.len() * NODE_RECORD_FIXED_BYTES);
|
||||
|
||||
for (address, node_info) in map.iter() {
|
||||
let address_bytes = match Wallet::short_address_to_bytes(address) {
|
||||
|
|
@ -70,22 +70,10 @@ impl NodeInfo {
|
|||
let added_by_bytes = Wallet::long_address_to_bytes(node_info.added_by.to_string());
|
||||
let added_timestamp_bytes = node_info.added_timestamp.to_le_bytes();
|
||||
|
||||
// Empty deletion fields serialize as zero-filled fixed-width values
|
||||
// so every node record stays the same size on the wire.
|
||||
let deleted_by_bytes = if node_info.deleted_by.is_empty() {
|
||||
vec![0u8; Wallet::ADDRESS_BYTES_LENGTH]
|
||||
} else {
|
||||
Wallet::long_address_to_bytes(node_info.deleted_by.to_string())
|
||||
};
|
||||
|
||||
let deleted_timestamp_bytes = node_info.deleted_timestamp.to_le_bytes();
|
||||
let deleted_block_bytes = node_info.deleted_block.to_le_bytes();
|
||||
|
||||
let deleted_signature_bytes = if node_info.deleted_signature.is_empty() {
|
||||
vec![0u8; Wallet::SIGNATURE_LENGTH]
|
||||
} else {
|
||||
decode(node_info.deleted_signature.clone()).unwrap()
|
||||
};
|
||||
let monitor_count = node_info.monitoring.len().min(u16::MAX as usize) as u16;
|
||||
let monitor_count_bytes = monitor_count.to_le_bytes();
|
||||
|
||||
let added_signature_bytes = decode(node_info.added_signature.clone()).unwrap();
|
||||
|
||||
|
|
@ -97,10 +85,16 @@ impl NodeInfo {
|
|||
data.extend_from_slice(&added_by_bytes);
|
||||
data.extend_from_slice(&added_timestamp_bytes);
|
||||
data.extend_from_slice(&added_signature_bytes);
|
||||
data.extend_from_slice(&deleted_by_bytes);
|
||||
data.extend_from_slice(&deleted_timestamp_bytes);
|
||||
data.extend_from_slice(&deleted_block_bytes);
|
||||
data.extend_from_slice(&deleted_signature_bytes);
|
||||
data.extend_from_slice(&monitor_count_bytes);
|
||||
for monitor in node_info.monitoring.iter().take(monitor_count as usize) {
|
||||
if let Some(monitor_bytes) = Wallet::short_address_to_bytes(monitor) {
|
||||
data.extend_from_slice(&monitor_bytes);
|
||||
} else {
|
||||
data.extend_from_slice(&[0u8; Wallet::SHORT_ADDRESS_BYTES_LENGTH]);
|
||||
}
|
||||
}
|
||||
}
|
||||
RpcResponse::Binary(data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub const NODE_IP_BYTES: usize = 16;
|
|||
pub const NODE_BLOCKS_MINED_BYTES: usize = 1;
|
||||
pub const NODE_TIMESTAMP_BYTES: usize = 8;
|
||||
pub const NODE_DELETED_BLOCK_BYTES: usize = 4;
|
||||
pub const NODE_MONITOR_COUNT_BYTES: usize = 2;
|
||||
|
||||
pub const NODE_ADDRESS_OFFSET: usize = 0;
|
||||
pub const NODE_IP_OFFSET: usize = NODE_ADDRESS_OFFSET + Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||
|
|
@ -15,13 +16,11 @@ pub const NODE_BLOCKS_MINED_OFFSET: usize = NODE_IP_OFFSET + NODE_IP_BYTES;
|
|||
pub const NODE_ADDED_BY_OFFSET: usize = NODE_BLOCKS_MINED_OFFSET + NODE_BLOCKS_MINED_BYTES;
|
||||
pub const NODE_ADDED_TIMESTAMP_OFFSET: usize = NODE_ADDED_BY_OFFSET + Wallet::ADDRESS_BYTES_LENGTH;
|
||||
pub const NODE_ADDED_SIGNATURE_OFFSET: usize = NODE_ADDED_TIMESTAMP_OFFSET + NODE_TIMESTAMP_BYTES;
|
||||
pub const NODE_DELETED_BY_OFFSET: usize = NODE_ADDED_SIGNATURE_OFFSET + Wallet::SIGNATURE_LENGTH;
|
||||
pub const NODE_DELETED_TIMESTAMP_OFFSET: usize =
|
||||
NODE_DELETED_BY_OFFSET + Wallet::ADDRESS_BYTES_LENGTH;
|
||||
NODE_ADDED_SIGNATURE_OFFSET + Wallet::SIGNATURE_LENGTH;
|
||||
pub const NODE_DELETED_BLOCK_OFFSET: usize = NODE_DELETED_TIMESTAMP_OFFSET + NODE_TIMESTAMP_BYTES;
|
||||
pub const NODE_DELETED_SIGNATURE_OFFSET: usize =
|
||||
NODE_DELETED_BLOCK_OFFSET + NODE_DELETED_BLOCK_BYTES;
|
||||
pub const NODE_RECORD_BYTES: usize = NODE_DELETED_SIGNATURE_OFFSET + Wallet::SIGNATURE_LENGTH;
|
||||
pub const NODE_MONITOR_COUNT_OFFSET: usize = NODE_DELETED_BLOCK_OFFSET + NODE_DELETED_BLOCK_BYTES;
|
||||
pub const NODE_RECORD_FIXED_BYTES: usize = NODE_MONITOR_COUNT_OFFSET + NODE_MONITOR_COUNT_BYTES;
|
||||
|
||||
// SignedNodeEdit carries the signed node membership payload used by add/delete updates.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -33,6 +32,16 @@ pub struct SignedNodeEdit {
|
|||
pub modified_signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SignedMonitorEdit {
|
||||
pub action: u8,
|
||||
pub monitored_address: String,
|
||||
pub monitoring_address: String,
|
||||
pub target_ip: String,
|
||||
pub modified_timestamp: u64,
|
||||
pub modified_signature: String,
|
||||
}
|
||||
|
||||
// AddAddressParams groups the shared context needed to add a node to the network map.
|
||||
#[derive(Clone)]
|
||||
pub struct AddAddressParams {
|
||||
|
|
@ -56,11 +65,10 @@ pub struct DeleteAddressParams {
|
|||
pub connections_key: String,
|
||||
}
|
||||
|
||||
// PingMonitorParams keeps the background liveness-monitor arguments bundled together.
|
||||
#[derive(Clone)]
|
||||
pub struct PingMonitorParams {
|
||||
pub struct MonitorAddressParams {
|
||||
pub map: Arc<Mutex<Command>>,
|
||||
pub edit: SignedNodeEdit,
|
||||
pub edit: SignedMonitorEdit,
|
||||
pub remote_ip: String,
|
||||
pub db: Db,
|
||||
pub wallet: Arc<Wallet>,
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ async fn block_file_context(db: &Db, mut difficulty: u64) -> (String, u32, u64)
|
|||
let next_number = if genesis_checkup().await {
|
||||
current_height + 1
|
||||
} else {
|
||||
difficulty = 3000000000000000_u64;
|
||||
difficulty = 1200000000000000_u64;
|
||||
0
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ pub const RPC_WALLET_REGISTRY_SYNC: u8 = 39;
|
|||
pub const RPC_VANITY_LOOKUP: u8 = 40;
|
||||
pub const RPC_TORRENT_CANDIDATES: u8 = 41;
|
||||
pub const RPC_BLOCK_HASH_AT_HEIGHT: u8 = 42;
|
||||
pub const RPC_NETWORK_MONITOR_ADD: u8 = 43;
|
||||
pub const RPC_NETWORK_MONITOR_REMOVE: u8 = 44;
|
||||
pub const RPC_VANITY_OWNER_LOOKUP: u8 = 45;
|
||||
pub const RPC_REPLY: u8 = 255;
|
||||
pub const MAX_RPC_REPLY_BYTES: usize = 64 * 1024 * 1024;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub mod largest_tx_fee;
|
|||
pub mod latest_block;
|
||||
pub mod memory_by_signature;
|
||||
pub mod network_info;
|
||||
pub mod network_monitor_add;
|
||||
pub mod network_monitor_remove;
|
||||
pub mod nft_list;
|
||||
pub mod nft_lookup;
|
||||
pub mod random_node;
|
||||
|
|
@ -43,3 +45,4 @@ pub mod validate_torrent;
|
|||
pub mod wallet_register;
|
||||
pub mod wallet_registry_sync;
|
||||
pub mod wallet_vanity_lookup;
|
||||
pub mod wallet_vanity_owner_lookup;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
use crate::records::memory::network_mapping::structs::{MonitorAddressParams, SignedMonitorEdit};
|
||||
use crate::records::memory::network_mapping::NodeInfo;
|
||||
use crate::records::memory::response_channels::Command;
|
||||
use crate::rpc::read_bytes_from_stream;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
use crate::wallets::structures::Wallet;
|
||||
use crate::Arc;
|
||||
use crate::Mutex;
|
||||
use crate::TcpStream;
|
||||
|
||||
pub async fn network_monitor_add(
|
||||
connections_key: &str,
|
||||
stream_locked: Arc<Mutex<TcpStream>>,
|
||||
db: &Db,
|
||||
wallet: Arc<Wallet>,
|
||||
map: Arc<Mutex<Command>>,
|
||||
) -> Result<(u32, RpcResponse), String> {
|
||||
let (uid, _) =
|
||||
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
||||
.await?;
|
||||
let action =
|
||||
read_bytes_from_stream::read_u8_from_stream(connections_key, stream_locked.clone()).await?;
|
||||
let monitored_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||
connections_key,
|
||||
stream_locked.clone(),
|
||||
)
|
||||
.await?;
|
||||
let monitored_address = Wallet::bytes_to_short_address(&monitored_bytes)
|
||||
.ok_or_else(|| "error: Invalid monitored short address bytes".to_string())?;
|
||||
let monitoring_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||
connections_key,
|
||||
stream_locked.clone(),
|
||||
)
|
||||
.await?;
|
||||
let monitoring_address = Wallet::bytes_to_short_address(&monitoring_bytes)
|
||||
.ok_or_else(|| "error: Invalid monitoring short address bytes".to_string())?;
|
||||
let target_ip =
|
||||
read_bytes_from_stream::read_ip_from_stream(connections_key, stream_locked.clone()).await?;
|
||||
let modified_timestamp =
|
||||
read_bytes_from_stream::read_u64_from_stream(connections_key, stream_locked.clone())
|
||||
.await?;
|
||||
let modified_signature =
|
||||
read_bytes_from_stream::read_signature_from_stream(connections_key, stream_locked.clone())
|
||||
.await?;
|
||||
let remote_ip = read_bytes_from_stream::read_caller_ip(stream_locked).await?;
|
||||
|
||||
let result = NodeInfo::add_monitor(MonitorAddressParams {
|
||||
map,
|
||||
edit: SignedMonitorEdit {
|
||||
action,
|
||||
monitored_address,
|
||||
monitoring_address,
|
||||
target_ip,
|
||||
modified_timestamp,
|
||||
modified_signature,
|
||||
},
|
||||
remote_ip,
|
||||
db: db.clone(),
|
||||
wallet,
|
||||
connections_key: connections_key.to_string(),
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok((uid, result))
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
use crate::records::memory::network_mapping::structs::{MonitorAddressParams, SignedMonitorEdit};
|
||||
use crate::records::memory::network_mapping::NodeInfo;
|
||||
use crate::records::memory::response_channels::Command;
|
||||
use crate::rpc::read_bytes_from_stream;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
use crate::wallets::structures::Wallet;
|
||||
use crate::Arc;
|
||||
use crate::Mutex;
|
||||
use crate::TcpStream;
|
||||
|
||||
pub async fn network_monitor_remove(
|
||||
connections_key: &str,
|
||||
stream_locked: Arc<Mutex<TcpStream>>,
|
||||
db: &Db,
|
||||
wallet: Arc<Wallet>,
|
||||
map: Arc<Mutex<Command>>,
|
||||
) -> Result<(u32, RpcResponse), String> {
|
||||
let (uid, _) =
|
||||
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
||||
.await?;
|
||||
let action =
|
||||
read_bytes_from_stream::read_u8_from_stream(connections_key, stream_locked.clone()).await?;
|
||||
let monitored_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||
connections_key,
|
||||
stream_locked.clone(),
|
||||
)
|
||||
.await?;
|
||||
let monitored_address = Wallet::bytes_to_short_address(&monitored_bytes)
|
||||
.ok_or_else(|| "error: Invalid monitored short address bytes".to_string())?;
|
||||
let monitoring_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||
connections_key,
|
||||
stream_locked.clone(),
|
||||
)
|
||||
.await?;
|
||||
let monitoring_address = Wallet::bytes_to_short_address(&monitoring_bytes)
|
||||
.ok_or_else(|| "error: Invalid monitoring short address bytes".to_string())?;
|
||||
let target_ip =
|
||||
read_bytes_from_stream::read_ip_from_stream(connections_key, stream_locked.clone()).await?;
|
||||
let modified_timestamp =
|
||||
read_bytes_from_stream::read_u64_from_stream(connections_key, stream_locked.clone())
|
||||
.await?;
|
||||
let modified_signature =
|
||||
read_bytes_from_stream::read_signature_from_stream(connections_key, stream_locked.clone())
|
||||
.await?;
|
||||
let remote_ip = read_bytes_from_stream::read_caller_ip(stream_locked).await?;
|
||||
|
||||
let result = NodeInfo::remove_monitor(MonitorAddressParams {
|
||||
map,
|
||||
edit: SignedMonitorEdit {
|
||||
action,
|
||||
monitored_address,
|
||||
monitoring_address,
|
||||
target_ip,
|
||||
modified_timestamp,
|
||||
modified_signature,
|
||||
},
|
||||
remote_ip,
|
||||
db: db.clone(),
|
||||
wallet,
|
||||
connections_key: connections_key.to_string(),
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok((uid, result))
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
use crate::records::wallet_registry::resolve_owner_from_vanity_address;
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
|
||||
pub async fn lookup(vanity_address: String, db: &Db) -> RpcResponse {
|
||||
match resolve_owner_from_vanity_address(db, &vanity_address) {
|
||||
Ok(Some(owner_short_address)) => RpcResponse::Binary(owner_short_address.into_bytes()),
|
||||
Ok(None) => RpcResponse::Binary(Vec::new()),
|
||||
Err(err) => RpcResponse::Binary(
|
||||
format!("error: Failed to lookup vanity owner address: {err}").into_bytes(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
@ -813,6 +813,67 @@ pub async fn start_loop(
|
|||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
}
|
||||
43 => {
|
||||
// add a monitor edge in the network map
|
||||
let (uid, result) = commands::network_monitor_add::network_monitor_add(
|
||||
&connections_key,
|
||||
stream_locked.clone(),
|
||||
&db,
|
||||
wallet.clone(),
|
||||
map.clone(),
|
||||
)
|
||||
.await?;
|
||||
result
|
||||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
}
|
||||
44 => {
|
||||
// remove a monitor edge in the network map
|
||||
let (uid, result) = commands::network_monitor_remove::network_monitor_remove(
|
||||
&connections_key,
|
||||
stream_locked.clone(),
|
||||
&db,
|
||||
wallet.clone(),
|
||||
map.clone(),
|
||||
)
|
||||
.await?;
|
||||
result
|
||||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
}
|
||||
45 => {
|
||||
// request the canonical short address that owns a registered vanity address
|
||||
let (uid, _) = read_bytes_from_stream::read_uid_from_stream(
|
||||
&connections_key,
|
||||
stream_locked.clone(),
|
||||
)
|
||||
.await?;
|
||||
let vanity_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||
&connections_key,
|
||||
stream_locked.clone(),
|
||||
)
|
||||
.await?;
|
||||
let vanity_address =
|
||||
match crate::wallets::structures::Wallet::bytes_to_vanity_address(&vanity_bytes)
|
||||
{
|
||||
Some(address) => address,
|
||||
None => {
|
||||
let result = crate::rpc::responses::RpcResponse::Binary(
|
||||
b"error: Invalid vanity address bytes".to_vec(),
|
||||
);
|
||||
result
|
||||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let result =
|
||||
commands::wallet_vanity_owner_lookup::lookup(vanity_address, &db).await;
|
||||
result
|
||||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.await;
|
||||
}
|
||||
255 => {
|
||||
commands::route_reply::route_reply(
|
||||
&connections_key,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ use crate::{AsyncReadExt, AsyncWriteExt};
|
|||
|
||||
pub enum HandshakeWallet {
|
||||
// Most standalone tools only have the wallet password and need to load the saved wallet.
|
||||
WalletKey(String),
|
||||
WalletKey {
|
||||
encryption_key: String,
|
||||
wallet_path: String,
|
||||
},
|
||||
// Wallet recovery tools already have the address and private key before the wallet is saved.
|
||||
WalletParts {
|
||||
long_address: String,
|
||||
|
|
@ -36,8 +39,11 @@ pub async fn connect_and_handshake(
|
|||
) -> Result<Vec<u8>, io::Error> {
|
||||
// Resolve the wallet material before opening the stream so the handshake can sign its challenge.
|
||||
let (long_address, private_key) = match wallet_source {
|
||||
HandshakeWallet::WalletKey(encryption_key) => {
|
||||
let wallet = Wallet::try_obtain_wallet(encryption_key, None)
|
||||
HandshakeWallet::WalletKey {
|
||||
encryption_key,
|
||||
wallet_path,
|
||||
} => {
|
||||
let wallet = Wallet::try_obtain_wallet(encryption_key, Some(&wallet_path))
|
||||
.await
|
||||
.map_err(io::Error::other)?;
|
||||
(wallet.saved.long_address, wallet.saved.private_key)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::rpc::command_maps::{
|
|||
RPC_LOAN_CONTRACT, RPC_MEMPOOL_TX_BY_ADDRESS, RPC_MEMPOOL_TX_BY_SIGNATURE,
|
||||
RPC_MEMPOOL_TX_COUNT, RPC_NETWORK_INFO, RPC_NFT_DETAILS, RPC_NFT_LIST, RPC_REGISTER_WALLET,
|
||||
RPC_TIME, RPC_TOKEN_DETAILS, RPC_TOKEN_LIST, RPC_TORRENT_BY_HEIGHT, RPC_TOTAL_CONFIRMED_TX,
|
||||
RPC_TRANSACTION_BY_TXID, RPC_UNBLOCK_IP, RPC_VANITY_LOOKUP,
|
||||
RPC_TRANSACTION_BY_TXID, RPC_UNBLOCK_IP, RPC_VANITY_LOOKUP, RPC_VANITY_OWNER_LOOKUP,
|
||||
};
|
||||
use crate::standalone_tools::transaction_creator::create_transaction_request;
|
||||
use crate::timeout;
|
||||
|
|
@ -455,6 +455,18 @@ async fn build_request_bytes(
|
|||
bin_msg.extend_from_slice(&hashmap_key);
|
||||
bin_msg.extend_from_slice(&address_bytes);
|
||||
}
|
||||
// Lookup canonical short owner address by registered vanity address.
|
||||
45 => {
|
||||
let command_number: u8 = RPC_VANITY_OWNER_LOOKUP;
|
||||
let address_bytes = Wallet::normalize_to_short_address(&command_input)
|
||||
.and_then(|vanity| Wallet::vanity_address_to_bytes(&vanity))
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::InvalidInput, "Invalid vanity address")
|
||||
})?;
|
||||
bin_msg.extend_from_slice(&command_number.to_le_bytes());
|
||||
bin_msg.extend_from_slice(&hashmap_key);
|
||||
bin_msg.extend_from_slice(&address_bytes);
|
||||
}
|
||||
_ => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// The standalone_tools module holds utility helpers that mirror node serialization and connection behavior.
|
||||
pub mod connections;
|
||||
pub mod transaction_creator;
|
||||
pub mod vanity_resolver;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
use crate::common::network_startup::get_connections;
|
||||
use crate::io;
|
||||
use crate::records::memory::response_channels::generate_uid;
|
||||
use crate::rpc::command_maps::RPC_VANITY_OWNER_LOOKUP;
|
||||
use crate::standalone_tools::connections::handshake::{self, HandshakeWallet};
|
||||
use crate::wallets::structures::Wallet;
|
||||
|
||||
pub async fn resolve_wallet_address_input(
|
||||
address: &str,
|
||||
wallet: &Wallet,
|
||||
) -> Result<String, String> {
|
||||
let normalized = Wallet::normalize_to_short_address(address.trim())
|
||||
.ok_or_else(|| "Invalid wallet address.".to_string())?;
|
||||
|
||||
if Wallet::short_address_to_bytes(&normalized).is_some() {
|
||||
return Ok(normalized);
|
||||
}
|
||||
|
||||
if Wallet::vanity_address_to_bytes(&normalized).is_none() {
|
||||
return Err("Invalid wallet address.".to_string());
|
||||
}
|
||||
|
||||
lookup_vanity_owner(&normalized, wallet).await
|
||||
}
|
||||
|
||||
async fn lookup_vanity_owner(vanity_address: &str, wallet: &Wallet) -> Result<String, String> {
|
||||
let connections = get_connections().await;
|
||||
if connections.is_empty() {
|
||||
return Err("Vanity address lookup requires a reachable node.".to_string());
|
||||
}
|
||||
|
||||
let mut last_error: Option<String> = None;
|
||||
|
||||
for conn in connections {
|
||||
let socket_address = match conn.parse() {
|
||||
Ok(address) => address,
|
||||
Err(err) => {
|
||||
last_error = Some(format!("Invalid configured peer address {conn}: {err}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let result = handshake::connect_and_handshake(
|
||||
socket_address,
|
||||
vanity_address.to_string(),
|
||||
RPC_VANITY_OWNER_LOOKUP as usize,
|
||||
HandshakeWallet::WalletParts {
|
||||
long_address: wallet.saved.long_address.clone(),
|
||||
private_key: wallet.saved.private_key.clone(),
|
||||
},
|
||||
generate_uid(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(response) if response.is_empty() => {
|
||||
last_error = Some("Vanity address is not registered.".to_string());
|
||||
}
|
||||
Ok(response) => {
|
||||
if let Ok(text) = String::from_utf8(response.clone()) {
|
||||
let trimmed = text.trim();
|
||||
if trimmed.starts_with("error:") {
|
||||
last_error = Some(trimmed.to_string());
|
||||
continue;
|
||||
}
|
||||
if Wallet::short_address_to_bytes(trimmed).is_some() {
|
||||
return Ok(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(short_address) = Wallet::bytes_to_short_address(&response) {
|
||||
return Ok(short_address);
|
||||
}
|
||||
|
||||
last_error = Some("Vanity lookup returned an invalid short address.".to_string());
|
||||
}
|
||||
Err(err) if err.kind() == io::ErrorKind::TimedOut => {
|
||||
last_error = Some(format!("Vanity lookup timed out on {conn}."));
|
||||
}
|
||||
Err(err) => {
|
||||
last_error = Some(format!("Vanity lookup failed on {conn}: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error.unwrap_or_else(|| "Vanity address lookup failed.".to_string()))
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
use crate::common::binary_conversions::{binary_to_ip, binary_to_string, ip_to_binary};
|
||||
use crate::common::network_startup::get_ip_and_port;
|
||||
use crate::encode;
|
||||
use crate::records::memory::network_mapping::monitor::MONITOR_ACTION_ADD;
|
||||
use crate::records::memory::network_mapping::structs::{
|
||||
AddAddressParams, DeleteAddressParams, SignedNodeEdit, NODE_ADDED_BY_OFFSET,
|
||||
NODE_ADDED_SIGNATURE_OFFSET, NODE_ADDED_TIMESTAMP_OFFSET, NODE_BLOCKS_MINED_OFFSET,
|
||||
NODE_DELETED_BLOCK_OFFSET, NODE_DELETED_BY_OFFSET, NODE_DELETED_SIGNATURE_OFFSET,
|
||||
NODE_DELETED_TIMESTAMP_OFFSET, NODE_IP_OFFSET, NODE_RECORD_BYTES,
|
||||
AddAddressParams, MonitorAddressParams, SignedMonitorEdit, SignedNodeEdit,
|
||||
NODE_ADDED_BY_OFFSET, NODE_ADDED_SIGNATURE_OFFSET, NODE_ADDED_TIMESTAMP_OFFSET,
|
||||
NODE_BLOCKS_MINED_OFFSET, NODE_DELETED_BLOCK_OFFSET, NODE_DELETED_TIMESTAMP_OFFSET,
|
||||
NODE_IP_OFFSET, NODE_MONITOR_COUNT_OFFSET, NODE_RECORD_FIXED_BYTES,
|
||||
};
|
||||
use crate::records::memory::network_mapping::NodeInfo;
|
||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||
use crate::records::memory::structs::Connection;
|
||||
use crate::rpc::command_maps::{RPC_ADD_NETWORK_NODE, RPC_REQUEST_NODE_LIST};
|
||||
use crate::rpc::responses::RpcResponse;
|
||||
use crate::sled::Db;
|
||||
|
|
@ -18,13 +19,53 @@ use crate::Mutex;
|
|||
use crate::TcpStream;
|
||||
use crate::Utc;
|
||||
|
||||
fn decode_optional_signature(bytes: &[u8]) -> String {
|
||||
// Network mapping records use zero-filled signature fields when no signature exists yet.
|
||||
if bytes.iter().all(|&byte| byte == 0) {
|
||||
String::new()
|
||||
} else {
|
||||
encode(bytes)
|
||||
async fn record_local_monitor_for_peer(
|
||||
connections_key: &str,
|
||||
command_map: Arc<Mutex<Command>>,
|
||||
db: &Db,
|
||||
wallet: Arc<Wallet>,
|
||||
) {
|
||||
let Some(peer_wallet_bytes) = Connection::get_wallet_for_connection_key(connections_key).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let peer_long = Wallet::bytes_to_long_address(peer_wallet_bytes);
|
||||
let Some(monitored_address) = Wallet::normalize_to_short_address(&peer_long) else {
|
||||
return;
|
||||
};
|
||||
let monitoring_address = wallet.saved.short_address.clone();
|
||||
if monitored_address == monitoring_address {
|
||||
return;
|
||||
}
|
||||
let Some((target_ip, _)) = connections_key.rsplit_once(':') else {
|
||||
return;
|
||||
};
|
||||
let timestamp = Utc::now().timestamp_millis() as u64;
|
||||
let signature = NodeInfo::monitor_signature(
|
||||
MONITOR_ACTION_ADD,
|
||||
&monitored_address,
|
||||
&monitoring_address,
|
||||
target_ip,
|
||||
timestamp,
|
||||
&wallet,
|
||||
)
|
||||
.await;
|
||||
let _ = NodeInfo::add_monitor(MonitorAddressParams {
|
||||
map: command_map,
|
||||
edit: SignedMonitorEdit {
|
||||
action: MONITOR_ACTION_ADD,
|
||||
monitored_address,
|
||||
monitoring_address,
|
||||
target_ip: target_ip.to_string(),
|
||||
modified_timestamp: timestamp,
|
||||
modified_signature: signature,
|
||||
},
|
||||
remote_ip: String::new(),
|
||||
db: db.clone(),
|
||||
wallet,
|
||||
connections_key: connections_key.to_string(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn announce_self_to_network(
|
||||
|
|
@ -84,10 +125,11 @@ pub async fn announce_self_to_network(
|
|||
unlocked_stream,
|
||||
command_map.clone(),
|
||||
db,
|
||||
wallet,
|
||||
wallet.clone(),
|
||||
connections_key,
|
||||
)
|
||||
.await
|
||||
.await;
|
||||
record_local_monitor_for_peer(connections_key, command_map, db, wallet).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -110,15 +152,23 @@ pub async fn get_network_mapping(
|
|||
message.push(message_type);
|
||||
message.extend_from_slice(&download_hashmap_key);
|
||||
|
||||
// The remote reply is a concatenated list of fixed-width node records.
|
||||
// The remote reply is a concatenated list of variable-width node records.
|
||||
RpcResponse::send_raw(&unlocked_stream, Some(connections_key), &message).await;
|
||||
|
||||
let mut rx = download_hashmap_rx.lock().await;
|
||||
// each node record is serialized into one fixed-width payload so
|
||||
// startup import can safely drain the response in-place.
|
||||
if let Some(mut buffer) = rx.recv().await {
|
||||
while buffer.len() >= NODE_RECORD_BYTES {
|
||||
let chunk: Vec<u8> = buffer.drain(0..NODE_RECORD_BYTES).collect();
|
||||
while buffer.len() >= NODE_RECORD_FIXED_BYTES {
|
||||
let monitor_count = u16::from_le_bytes(
|
||||
buffer[NODE_MONITOR_COUNT_OFFSET..NODE_RECORD_FIXED_BYTES]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
) as usize;
|
||||
let record_bytes =
|
||||
NODE_RECORD_FIXED_BYTES + (monitor_count * Wallet::SHORT_ADDRESS_BYTES_LENGTH);
|
||||
if buffer.len() < record_bytes {
|
||||
break;
|
||||
}
|
||||
let chunk: Vec<u8> = buffer.drain(0..record_bytes).collect();
|
||||
// The first part of each record describes the advertised node address and IP.
|
||||
let Some(address) = Wallet::bytes_to_short_address(&chunk[0..NODE_IP_OFFSET]) else {
|
||||
continue;
|
||||
|
|
@ -137,8 +187,17 @@ pub async fn get_network_mapping(
|
|||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let added_signature = decode_optional_signature(
|
||||
&chunk[NODE_ADDED_SIGNATURE_OFFSET..NODE_DELETED_BY_OFFSET],
|
||||
let added_signature =
|
||||
crate::encode(&chunk[NODE_ADDED_SIGNATURE_OFFSET..NODE_DELETED_TIMESTAMP_OFFSET]);
|
||||
let deleted_timestamp = u64::from_le_bytes(
|
||||
chunk[NODE_DELETED_TIMESTAMP_OFFSET..NODE_DELETED_BLOCK_OFFSET]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let deleted_block = u32::from_le_bytes(
|
||||
chunk[NODE_DELETED_BLOCK_OFFSET..NODE_MONITOR_COUNT_OFFSET]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let remote_ip = "";
|
||||
// Add records are imported through NodeInfo so local validation/signing rules stay central.
|
||||
|
|
@ -159,44 +218,24 @@ pub async fn get_network_mapping(
|
|||
})
|
||||
.await;
|
||||
|
||||
if !chunk[NODE_DELETED_BY_OFFSET..NODE_DELETED_TIMESTAMP_OFFSET]
|
||||
.iter()
|
||||
.all(|&byte| byte == 0)
|
||||
{
|
||||
// Deleted fields are optional; when present, replay the delete record locally too.
|
||||
let deleted_by = Wallet::bytes_to_long_address(
|
||||
chunk[NODE_DELETED_BY_OFFSET..NODE_DELETED_TIMESTAMP_OFFSET].to_vec(),
|
||||
);
|
||||
let deleted_timestamp = u64::from_le_bytes(
|
||||
chunk[NODE_DELETED_TIMESTAMP_OFFSET..NODE_DELETED_BLOCK_OFFSET]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let deleted_block = u32::from_le_bytes(
|
||||
chunk[NODE_DELETED_BLOCK_OFFSET..NODE_DELETED_SIGNATURE_OFFSET]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let deleted_signature = decode_optional_signature(
|
||||
&chunk[NODE_DELETED_SIGNATURE_OFFSET..NODE_RECORD_BYTES],
|
||||
);
|
||||
let deleted_address = address.clone();
|
||||
let _ = NodeInfo::delete_address(DeleteAddressParams {
|
||||
map: command_map.clone(),
|
||||
edit: SignedNodeEdit {
|
||||
address,
|
||||
ip,
|
||||
modified_by: deleted_by,
|
||||
modified_timestamp: deleted_timestamp,
|
||||
modified_signature: deleted_signature,
|
||||
},
|
||||
remote_ip: remote_ip.to_string(),
|
||||
db: db.clone(),
|
||||
wallet: wallet.clone(),
|
||||
connections_key: connections_key.to_string(),
|
||||
})
|
||||
let mut monitors = Vec::with_capacity(monitor_count);
|
||||
for monitor_index in 0..monitor_count {
|
||||
let start =
|
||||
NODE_RECORD_FIXED_BYTES + monitor_index * Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||
let end = start + Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||
if let Some(monitor) = Wallet::bytes_to_short_address(&chunk[start..end]) {
|
||||
monitors.push(monitor);
|
||||
}
|
||||
}
|
||||
NodeInfo::set_monitors_from_mapping(&address, monitors).await;
|
||||
|
||||
if deleted_timestamp > 0 {
|
||||
NodeInfo::set_deleted_metadata_from_mapping(
|
||||
&address,
|
||||
deleted_timestamp,
|
||||
deleted_block,
|
||||
)
|
||||
.await;
|
||||
NodeInfo::set_deleted_block_from_mapping(&deleted_address, deleted_block).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,3 +15,11 @@ pub fn reserved_base_coins() -> [&'static str; 11] {
|
|||
"BCH ",
|
||||
]
|
||||
}
|
||||
|
||||
pub fn is_reserved_base_coin_ticker(asset_name: &str) -> bool {
|
||||
let normalized = asset_name.trim().to_lowercase();
|
||||
|
||||
reserved_base_coins()
|
||||
.iter()
|
||||
.any(|reserved| normalized == reserved.trim().to_lowercase())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ impl Torrent {
|
|||
}
|
||||
blockchain_data.unmined_block.next_block_difficulty
|
||||
} else {
|
||||
3000000000000000_u64
|
||||
1200000000000000_u64
|
||||
};
|
||||
let torrent_difficulty = self.info.this_block_difficulty;
|
||||
if blockchain_difficulty == torrent_difficulty {
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ impl Block {
|
|||
// compare the reduced header hash against the previous
|
||||
// block's recorded target difficulty threshold
|
||||
if !genesis_checkup().await {
|
||||
let difficulty_target = 3000000000000000_u64;
|
||||
let difficulty_target = 1200000000000000_u64;
|
||||
if hash <= difficulty_target {
|
||||
return Ok(());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::records::wallet_registry::{
|
|||
require_canonical_registered_short_address, resolve_pubkey_from_short_address,
|
||||
};
|
||||
use crate::sled::Db;
|
||||
use crate::verifications::async_funcs::asset_rules::reserved_base_coins;
|
||||
use crate::verifications::async_funcs::asset_rules::is_reserved_base_coin_ticker;
|
||||
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
|
||||
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
|
||||
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
|
||||
|
|
@ -19,6 +19,19 @@ impl CreateNftTransaction {
|
|||
let signature = &self.signature;
|
||||
let hash = &self.unsigned_create_nft.hash().await;
|
||||
|
||||
// NFT names use the same fixed padded asset-name length as tokens.
|
||||
let nft_name_length = self.unsigned_create_nft.nft_name.len();
|
||||
if nft_name_length != COIN_LENGTH {
|
||||
return Err(
|
||||
"Nft Name length must be 15 characters. Consider padding with empty spaces",
|
||||
)
|
||||
.map_err(|s| s.to_string())?;
|
||||
}
|
||||
|
||||
if is_reserved_base_coin_ticker(&self.unsigned_create_nft.nft_name) {
|
||||
return Err("NFT name is reserved for the base coin.".to_string());
|
||||
}
|
||||
|
||||
// Transactions already present in the mempool can short-circuit
|
||||
// the deeper verification path and reuse their stored signature.
|
||||
if memcheck(signature, hash).await {
|
||||
|
|
@ -63,22 +76,6 @@ impl CreateNftTransaction {
|
|||
return Err("A series must contain at least 2 items").map_err(|s| s.to_string())?;
|
||||
}
|
||||
|
||||
// NFT names use the same fixed padded asset-name length as tokens.
|
||||
let nft_name_length = self.unsigned_create_nft.nft_name.len();
|
||||
if nft_name_length != COIN_LENGTH {
|
||||
return Err(
|
||||
"Nft Name length must be 15 characters. Consider padding with empty spaces",
|
||||
)
|
||||
.map_err(|s| s.to_string())?;
|
||||
}
|
||||
|
||||
if reserved_base_coins().iter().any(|reserved| {
|
||||
self.unsigned_create_nft.nft_name.trim().to_lowercase()
|
||||
== reserved.trim().to_lowercase()
|
||||
}) {
|
||||
return Err("NFT name is reserved for the base coin.".to_string());
|
||||
}
|
||||
|
||||
// Series NFTs reserve each numbered item name separately, while
|
||||
// standalone NFTs reserve the base name directly.
|
||||
let tree = db.open_tree("nfts").unwrap();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::records::wallet_registry::{
|
|||
require_canonical_registered_short_address, resolve_pubkey_from_short_address,
|
||||
};
|
||||
use crate::sled::Db;
|
||||
use crate::verifications::async_funcs::asset_rules::reserved_base_coins;
|
||||
use crate::verifications::async_funcs::asset_rules::is_reserved_base_coin_ticker;
|
||||
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
|
||||
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
|
||||
use crate::verifications::async_funcs::checks::verify_db::{
|
||||
|
|
@ -20,6 +20,18 @@ impl CreateTokenTransaction {
|
|||
// the deeper verification path and reuse their stored signature.
|
||||
let hash = self.unsigned_create_token.hash().await;
|
||||
let signature = &self.signature;
|
||||
|
||||
// Token tickers use the fixed padded coin-length format.
|
||||
let ticker_length = self.unsigned_create_token.ticker.len();
|
||||
if ticker_length != COIN_LENGTH {
|
||||
return Err("Ticker length must be 15 characters. Consider padding with empty spaces")
|
||||
.map_err(|s| s.to_string())?;
|
||||
}
|
||||
|
||||
if is_reserved_base_coin_ticker(&self.unsigned_create_token.ticker) {
|
||||
return Err("Ticker is reserved for the base coin.".to_string());
|
||||
}
|
||||
|
||||
if memcheck(signature, &hash).await {
|
||||
return Ok(self.signature.clone());
|
||||
}
|
||||
|
|
@ -52,20 +64,6 @@ impl CreateTokenTransaction {
|
|||
return Err("Ticker already exists exist.".to_string());
|
||||
}
|
||||
|
||||
// Token tickers use the fixed padded coin-length format.
|
||||
let ticker_length = self.unsigned_create_token.ticker.len();
|
||||
if ticker_length != COIN_LENGTH {
|
||||
return Err("Ticker length must be 15 characters. Consider padding with empty spaces")
|
||||
.map_err(|s| s.to_string())?;
|
||||
}
|
||||
|
||||
if reserved_base_coins().iter().any(|reserved| {
|
||||
self.unsigned_create_token.ticker.trim().to_lowercase()
|
||||
== reserved.trim().to_lowercase()
|
||||
}) {
|
||||
return Err("Ticker is reserved for the base coin.".to_string());
|
||||
}
|
||||
|
||||
// Token hard-limit metadata is stored as a single byte so later
|
||||
// issuance checks can enforce either capped or uncapped supply.
|
||||
if self.unsigned_create_token.hard_limit > 1 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue