Fix staged torrent candidate race
This commit is contained in:
parent
f92823ac90
commit
61a64cf538
|
|
@ -11,10 +11,7 @@ use blockchain::{create_dir_all, AsyncWriteExt};
|
||||||
// Pad the asset name so it matches the 15-byte on-chain asset format.
|
// Pad the asset name so it matches the 15-byte on-chain asset format.
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width);
|
let mut result = String::with_capacity(width);
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@ use blockchain::{create_dir_all, AsyncWriteExt};
|
||||||
// Pad the ticker so it matches the fixed-width 15-byte on-chain format.
|
// Pad the ticker so it matches the fixed-width 15-byte on-chain format.
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width);
|
let mut result = String::with_capacity(width);
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,7 @@ use blockchain::{Local, LocalResult, NaiveDate, NaiveTime, TimeZone};
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
// Asset names are fixed-width fields in the loan transaction bytes.
|
// Asset names are fixed-width fields in the loan transaction bytes.
|
||||||
let mut result = String::with_capacity(width);
|
let mut result = String::with_capacity(width);
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@ use blockchain::{create_dir_all, AsyncWriteExt};
|
||||||
// pad the coin to ensure 15 characters
|
// pad the coin to ensure 15 characters
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,7 @@ async fn main() {
|
||||||
// Refuse to overwrite an existing wallet file.
|
// Refuse to overwrite an existing wallet file.
|
||||||
if let Ok(metadata) = metadata(&wallet_path).await {
|
if let Ok(metadata) = metadata(&wallet_path).await {
|
||||||
if metadata.is_file() {
|
if metadata.is_file() {
|
||||||
eprintln!(
|
eprintln!("Error: Wallet already exists at the specified path: {wallet_path:?}");
|
||||||
"Error: Wallet already exists at the specified path: {wallet_path:?}"
|
|
||||||
);
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@ use blockchain::{create_dir_all, AsyncWriteExt};
|
||||||
// pad the coin to ensure 15 characters
|
// pad the coin to ensure 15 characters
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,7 @@ use blockchain::{create_dir_all, AsyncWriteExt};
|
||||||
// pad the coin to ensure 15 characters
|
// pad the coin to ensure 15 characters
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@ use blockchain::{create_dir_all, AsyncWriteExt};
|
||||||
// pad the coin to ensure 15 characters
|
// pad the coin to ensure 15 characters
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,7 @@ use blockchain::{create_dir_all, AsyncWriteExt};
|
||||||
// pad the coin to ensure 15 characters
|
// pad the coin to ensure 15 characters
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
|
||||||
format_args!("{input:<width$}"),
|
|
||||||
);
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,50 @@ async fn decode_one_transaction(tx_bytes: &[u8]) -> Option<String> {
|
||||||
let body = &tx_bytes[1..];
|
let body = &tx_bytes[1..];
|
||||||
|
|
||||||
match txtype {
|
match txtype {
|
||||||
TRANSFER_TYPE => to_string_pretty(&TransferTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
TRANSFER_TYPE => {
|
||||||
CREATE_TOKEN_TYPE => to_string_pretty(&CreateTokenTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
to_string_pretty(&TransferTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
CREATE_NFT_TYPE => to_string_pretty(&CreateNftTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
}
|
||||||
MARKETING_TYPE => to_string_pretty(&MarketingTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
CREATE_TOKEN_TYPE => to_string_pretty(
|
||||||
|
&CreateTokenTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
CREATE_NFT_TYPE => {
|
||||||
|
to_string_pretty(&CreateNftTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
|
}
|
||||||
|
MARKETING_TYPE => {
|
||||||
|
to_string_pretty(&MarketingTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
|
}
|
||||||
SWAP_TYPE => to_string_pretty(&SwapTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
SWAP_TYPE => to_string_pretty(&SwapTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
||||||
LENDER_TYPE => to_string_pretty(&LoanContractTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
LENDER_TYPE => to_string_pretty(
|
||||||
BORROWER_TYPE => to_string_pretty(&ContractPaymentTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
&LoanContractTransaction::from_bytes(txtype, body)
|
||||||
COLLATERAL_TYPE => to_string_pretty(&CollateralClaimTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
BORROWER_TYPE => to_string_pretty(
|
||||||
|
&ContractPaymentTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
COLLATERAL_TYPE => to_string_pretty(
|
||||||
|
&CollateralClaimTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
BURN_TYPE => to_string_pretty(&BurnTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
BURN_TYPE => to_string_pretty(&BurnTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
||||||
ISSUE_TOKEN_TYPE => to_string_pretty(&IssueTokenTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
ISSUE_TOKEN_TYPE => {
|
||||||
VANITY_ADDRESS_TYPE => to_string_pretty(&VanityAddressTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
to_string_pretty(&IssueTokenTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
|
}
|
||||||
|
VANITY_ADDRESS_TYPE => to_string_pretty(
|
||||||
|
&VanityAddressTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,50 @@ async fn decode_mempool_transaction(response: &[u8]) -> Option<String> {
|
||||||
let body = &response[1..];
|
let body = &response[1..];
|
||||||
|
|
||||||
match txtype {
|
match txtype {
|
||||||
TRANSFER_TYPE => to_string_pretty(&TransferTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
TRANSFER_TYPE => {
|
||||||
CREATE_TOKEN_TYPE => to_string_pretty(&CreateTokenTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
to_string_pretty(&TransferTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
CREATE_NFT_TYPE => to_string_pretty(&CreateNftTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
}
|
||||||
MARKETING_TYPE => to_string_pretty(&MarketingTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
CREATE_TOKEN_TYPE => to_string_pretty(
|
||||||
|
&CreateTokenTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
CREATE_NFT_TYPE => {
|
||||||
|
to_string_pretty(&CreateNftTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
|
}
|
||||||
|
MARKETING_TYPE => {
|
||||||
|
to_string_pretty(&MarketingTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
|
}
|
||||||
SWAP_TYPE => to_string_pretty(&SwapTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
SWAP_TYPE => to_string_pretty(&SwapTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
||||||
LENDER_TYPE => to_string_pretty(&LoanContractTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
LENDER_TYPE => to_string_pretty(
|
||||||
BORROWER_TYPE => to_string_pretty(&ContractPaymentTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
&LoanContractTransaction::from_bytes(txtype, body)
|
||||||
COLLATERAL_TYPE => to_string_pretty(&CollateralClaimTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
BORROWER_TYPE => to_string_pretty(
|
||||||
|
&ContractPaymentTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
COLLATERAL_TYPE => to_string_pretty(
|
||||||
|
&CollateralClaimTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
BURN_TYPE => to_string_pretty(&BurnTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
BURN_TYPE => to_string_pretty(&BurnTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
||||||
ISSUE_TOKEN_TYPE => to_string_pretty(&IssueTokenTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
ISSUE_TOKEN_TYPE => {
|
||||||
VANITY_ADDRESS_TYPE => to_string_pretty(&VanityAddressTransaction::from_bytes(txtype, body).await.ok()?).ok(),
|
to_string_pretty(&IssueTokenTransaction::from_bytes(txtype, body).await.ok()?).ok()
|
||||||
|
}
|
||||||
|
VANITY_ADDRESS_TYPE => to_string_pretty(
|
||||||
|
&VanityAddressTransaction::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.ok()?,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,9 @@ fn decode_network_info(response: &[u8]) -> Option<String> {
|
||||||
|
|
||||||
// Mainnet uses CLC and testnet uses CLTC, so the prefix length is
|
// Mainnet uses CLC and testnet uses CLTC, so the prefix length is
|
||||||
// inferred from the total payload size instead of hard-coded.
|
// inferred from the total payload size instead of hard-coded.
|
||||||
let wallet_prefix =
|
let wallet_prefix = String::from_utf8_lossy(response.get(offset..offset + wallet_prefix_len)?)
|
||||||
String::from_utf8_lossy(response.get(offset..offset + wallet_prefix_len)?)
|
.trim()
|
||||||
.trim()
|
.to_string();
|
||||||
.to_string();
|
|
||||||
offset += wallet_prefix_len;
|
offset += wallet_prefix_len;
|
||||||
|
|
||||||
let height = read_u32(response, &mut offset)?;
|
let height = read_u32(response, &mut offset)?;
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,7 @@ async fn main() {
|
||||||
let address_file_path = if args.len() == 2 {
|
let address_file_path = if args.len() == 2 {
|
||||||
args[1].clone()
|
args[1].clone()
|
||||||
} else {
|
} else {
|
||||||
match prompt_for_path(
|
match prompt_for_path("Please enter the path to the file containing the wallet address: ") {
|
||||||
"Please enter the path to the file containing the wallet address: ",
|
|
||||||
) {
|
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use blockchain::common::skein::{
|
use blockchain::common::skein::{
|
||||||
skein_256_hash_data, skein_256_hash_bytes, skein_128_hash_bytes, skein_128_hash_data,
|
skein_128_hash_bytes, skein_128_hash_data, skein_256_hash_bytes, skein_256_hash_data,
|
||||||
};
|
};
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::File;
|
use blockchain::File;
|
||||||
|
|
|
||||||
|
|
@ -1,189 +1,189 @@
|
||||||
use blockchain::common::binary_conversions::hex_to_u64;
|
use blockchain::common::binary_conversions::hex_to_u64;
|
||||||
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use blockchain::common::skein::{skein_256_hash_data, skein_128_hash_bytes};
|
use blockchain::common::skein::{skein_128_hash_bytes, skein_256_hash_data};
|
||||||
use blockchain::encode;
|
use blockchain::encode;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::records::unpack_block::unpack_header::load_block_header;
|
use blockchain::records::unpack_block::unpack_header::load_block_header;
|
||||||
use blockchain::records::wallet_registry::resolve_pubkey_from_short_address;
|
use blockchain::records::wallet_registry::resolve_pubkey_from_short_address;
|
||||||
use blockchain::torrent::structs::Torrent;
|
use blockchain::torrent::structs::Torrent;
|
||||||
use blockchain::wallets::structures::Wallet;
|
use blockchain::wallets::structures::Wallet;
|
||||||
use blockchain::{AsyncReadExt, File};
|
use blockchain::{AsyncReadExt, File};
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Validate that a local block file, block header, and torrent metadata agree.
|
// Validate that a local block file, block header, and torrent metadata agree.
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
if args.len() != 2 {
|
if args.len() != 2 {
|
||||||
eprintln!("Usage: {} <block_number>", args[0]);
|
eprintln!("Usage: {} <block_number>", args[0]);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let block_number: u32 = match args[1].parse() {
|
let block_number: u32 = match args[1].parse() {
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
eprintln!("Block number must be an integer.");
|
eprintln!("Block number must be an integer.");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
block_ext,
|
block_ext,
|
||||||
torrent_path,
|
torrent_path,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
block_path,
|
block_path,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let block_filename = format!("{block_path}/{block_number}.{block_ext}");
|
let block_filename = format!("{block_path}/{block_number}.{block_ext}");
|
||||||
let torrent_filename = format!("{torrent_path}/{block_number}.torrent");
|
let torrent_filename = format!("{torrent_path}/{block_number}.torrent");
|
||||||
|
|
||||||
// Load and decode the torrent metadata first because later checks compare against it.
|
// Load and decode the torrent metadata first because later checks compare against it.
|
||||||
let mut torrent_bytes = Vec::new();
|
let mut torrent_bytes = Vec::new();
|
||||||
let mut torrent_file = File::open(&torrent_filename).await.unwrap_or_else(|_| {
|
let mut torrent_file = File::open(&torrent_filename).await.unwrap_or_else(|_| {
|
||||||
eprintln!("Error: cannot open torrent file '{torrent_filename}'");
|
eprintln!("Error: cannot open torrent file '{torrent_filename}'");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
torrent_file.read_to_end(&mut torrent_bytes).await.unwrap();
|
torrent_file.read_to_end(&mut torrent_bytes).await.unwrap();
|
||||||
let torrent = Torrent::from_bytes(&torrent_bytes).await.unwrap();
|
let torrent = Torrent::from_bytes(&torrent_bytes).await.unwrap();
|
||||||
|
|
||||||
// Load the local header and resolve the miner public key for signature/VRF checks.
|
// Load the local header and resolve the miner public key for signature/VRF checks.
|
||||||
let header = load_block_header(block_number).await.unwrap();
|
let header = load_block_header(block_number).await.unwrap();
|
||||||
let block_hash = header.hash().await;
|
let block_hash = header.hash().await;
|
||||||
let block_difficulty = hex_to_u64(&block_hash).await.unwrap();
|
let block_difficulty = hex_to_u64(&block_hash).await.unwrap();
|
||||||
let miner_pubkey = resolve_pubkey_from_short_address(
|
let miner_pubkey = resolve_pubkey_from_short_address(
|
||||||
&blockchain::startup::initialize_startup::open_chain_state().await,
|
&blockchain::startup::initialize_startup::open_chain_state().await,
|
||||||
&header.unmined_block.miner,
|
&header.unmined_block.miner,
|
||||||
)
|
)
|
||||||
.unwrap_or(None)
|
.unwrap_or(None)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let miner_pubkey_hex = encode(&miner_pubkey);
|
let miner_pubkey_hex = encode(&miner_pubkey);
|
||||||
|
|
||||||
// Load the raw block bytes for file-size, info-hash, and piece-hash checks.
|
// Load the raw block bytes for file-size, info-hash, and piece-hash checks.
|
||||||
let mut block_data = Vec::new();
|
let mut block_data = Vec::new();
|
||||||
let mut block_file = File::open(&block_filename).await.unwrap_or_else(|_| {
|
let mut block_file = File::open(&block_filename).await.unwrap_or_else(|_| {
|
||||||
eprintln!("Error: cannot open block file '{block_filename}'");
|
eprintln!("Error: cannot open block file '{block_filename}'");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
block_file.read_to_end(&mut block_data).await.unwrap();
|
block_file.read_to_end(&mut block_data).await.unwrap();
|
||||||
|
|
||||||
let info_hash_computed = skein_128_hash_bytes(&block_data);
|
let info_hash_computed = skein_128_hash_bytes(&block_data);
|
||||||
|
|
||||||
// Rebuild the unmined-block hash used by the miner proof signature.
|
// Rebuild the unmined-block hash used by the miner proof signature.
|
||||||
let unmined_json = serde_json::to_string(&header.unmined_block).unwrap();
|
let unmined_json = serde_json::to_string(&header.unmined_block).unwrap();
|
||||||
let unmined_hash = skein_256_hash_data(&unmined_json);
|
let unmined_hash = skein_256_hash_data(&unmined_json);
|
||||||
let signature_ok =
|
let signature_ok =
|
||||||
Wallet::verify_transaction_with_public_key(&unmined_hash, &header.proof, &miner_pubkey_hex)
|
Wallet::verify_transaction_with_public_key(&unmined_hash, &header.proof, &miner_pubkey_hex)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let passed = "[PASSED]".green();
|
let passed = "[PASSED]".green();
|
||||||
let failed = "[FAILED]".red();
|
let failed = "[FAILED]".red();
|
||||||
|
|
||||||
// Compare every header field that is duplicated inside the torrent metadata.
|
// Compare every header field that is duplicated inside the torrent metadata.
|
||||||
if header.unmined_block.timestamp == torrent.info.timestamp {
|
if header.unmined_block.timestamp == torrent.info.timestamp {
|
||||||
println!("timestamp match: {:>90}", format!("{passed}"));
|
println!("timestamp match: {:>90}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("timestamp match: {:>90}", format!("{failed}"));
|
println!("timestamp match: {:>90}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if header.unmined_block.nonce == torrent.info.nonce {
|
if header.unmined_block.nonce == torrent.info.nonce {
|
||||||
println!("nonce match: {:>94}", format!("{passed}"));
|
println!("nonce match: {:>94}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("nonce match: {:>94}", format!("{failed}"));
|
println!("nonce match: {:>94}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if header.unmined_block.miner == torrent.mined_by {
|
if header.unmined_block.miner == torrent.mined_by {
|
||||||
println!("wallet address match: {:>85}", format!("{passed}"));
|
println!("wallet address match: {:>85}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("wallet address match: {:>85}", format!("{failed}"));
|
println!("wallet address match: {:>85}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if block_difficulty < torrent.info.this_block_difficulty {
|
if block_difficulty < torrent.info.this_block_difficulty {
|
||||||
println!("block difficulty check: {:>83}", format!("{passed}"));
|
println!("block difficulty check: {:>83}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("block difficulty check: {:>83}", format!("{failed}"));
|
println!("block difficulty check: {:>83}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if header.vrf == torrent.info.vrf {
|
if header.vrf == torrent.info.vrf {
|
||||||
println!("VRF match: {:>96}", format!("{passed}"));
|
println!("VRF match: {:>96}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("VRF match: {:>96}", format!("{failed}"));
|
println!("VRF match: {:>96}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if block_data.len() == torrent.info.length as usize {
|
if block_data.len() == torrent.info.length as usize {
|
||||||
println!("file size check: {:>90}", format!("{passed}"));
|
println!("file size check: {:>90}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("file size check: {:>90}", format!("{failed}"));
|
println!("file size check: {:>90}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if block_hash == torrent.info.block_hash {
|
if block_hash == torrent.info.block_hash {
|
||||||
println!("block header hash check: {:>82}", format!("{passed}"));
|
println!("block header hash check: {:>82}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("block header hash check: {:>82}", format!("{failed}"));
|
println!("block header hash check: {:>82}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let vrf_ok = Wallet::vrf_verify_with_public_key(
|
let vrf_ok = Wallet::vrf_verify_with_public_key(
|
||||||
header.vrf,
|
header.vrf,
|
||||||
&unmined_hash,
|
&unmined_hash,
|
||||||
&miner_pubkey_hex,
|
&miner_pubkey_hex,
|
||||||
&header.proof,
|
&header.proof,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if vrf_ok {
|
if vrf_ok {
|
||||||
println!("VRF validation check: {:>85}", format!("{passed}"));
|
println!("VRF validation check: {:>85}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("VRF validation check: {:>85}", format!("{failed}"));
|
println!("VRF validation check: {:>85}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if signature_ok {
|
if signature_ok {
|
||||||
println!("VRF Proof check: {:>90}", format!("{passed}"));
|
println!("VRF Proof check: {:>90}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("VRF Proof check: {:>90}", format!("{failed}"));
|
println!("VRF Proof check: {:>90}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut all_pieces_passed = true;
|
let mut all_pieces_passed = true;
|
||||||
// Piece hashes prove the torrent metadata still matches every block-file chunk.
|
// Piece hashes prove the torrent metadata still matches every block-file chunk.
|
||||||
for piece in &torrent.info.pieces {
|
for piece in &torrent.info.pieces {
|
||||||
for (index, expected_hash) in piece.iter() {
|
for (index, expected_hash) in piece.iter() {
|
||||||
let idx = *index as usize;
|
let idx = *index as usize;
|
||||||
let start = (idx - 1) * torrent.info.piece_length as usize;
|
let start = (idx - 1) * torrent.info.piece_length as usize;
|
||||||
let end = std::cmp::min(start + torrent.info.piece_length as usize, block_data.len());
|
let end = std::cmp::min(start + torrent.info.piece_length as usize, block_data.len());
|
||||||
let slice = &block_data[start..end];
|
let slice = &block_data[start..end];
|
||||||
let hash = skein_128_hash_bytes(slice);
|
let hash = skein_128_hash_bytes(slice);
|
||||||
|
|
||||||
if hash == *expected_hash {
|
if hash == *expected_hash {
|
||||||
println!("piece {} hash check: {:>87}", idx, format!("{passed}"));
|
println!("piece {} hash check: {:>87}", idx, format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
all_pieces_passed = false;
|
all_pieces_passed = false;
|
||||||
println!("piece {} hash check: {:>87}", idx, format!("{failed}"));
|
println!("piece {} hash check: {:>87}", idx, format!("{failed}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if info_hash_computed == torrent.info.info_hash {
|
if info_hash_computed == torrent.info.info_hash {
|
||||||
println!("block hash check: {:>89}", format!("{passed}"));
|
println!("block hash check: {:>89}", format!("{passed}"));
|
||||||
} else {
|
} else {
|
||||||
println!("block hash check: {:>89}", format!("{failed}"));
|
println!("block hash check: {:>89}", format!("{failed}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The final pass/fail summary requires every individual validation to pass.
|
// The final pass/fail summary requires every individual validation to pass.
|
||||||
if header.unmined_block.nonce == torrent.info.nonce
|
if header.unmined_block.nonce == torrent.info.nonce
|
||||||
&& header.unmined_block.miner == torrent.mined_by
|
&& header.unmined_block.miner == torrent.mined_by
|
||||||
&& header.unmined_block.timestamp == torrent.info.timestamp
|
&& header.unmined_block.timestamp == torrent.info.timestamp
|
||||||
&& block_difficulty < torrent.info.this_block_difficulty
|
&& block_difficulty < torrent.info.this_block_difficulty
|
||||||
&& header.vrf == torrent.info.vrf
|
&& header.vrf == torrent.info.vrf
|
||||||
&& block_data.len() == torrent.info.length as usize
|
&& block_data.len() == torrent.info.length as usize
|
||||||
&& all_pieces_passed
|
&& all_pieces_passed
|
||||||
&& block_hash == torrent.info.block_hash
|
&& block_hash == torrent.info.block_hash
|
||||||
&& signature_ok
|
&& signature_ok
|
||||||
&& vrf_ok
|
&& vrf_ok
|
||||||
{
|
{
|
||||||
println!("\nBlock {block_number} fully validated.");
|
println!("\nBlock {block_number} fully validated.");
|
||||||
} else {
|
} else {
|
||||||
println!("\nBlock {block_number} FAILED validation.");
|
println!("\nBlock {block_number} FAILED validation.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,7 @@ async fn main() {
|
||||||
let address_path = if args.len() == 2 {
|
let address_path = if args.len() == 2 {
|
||||||
args[1].clone()
|
args[1].clone()
|
||||||
} else {
|
} else {
|
||||||
match prompt_for_path(
|
match prompt_for_path("Please enter the path to the file containing the wallet address: ") {
|
||||||
"Please enter the path to the file containing the wallet address: ",
|
|
||||||
) {
|
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,8 @@ async fn main() {
|
||||||
"Do you agree that payments should be made {}?",
|
"Do you agree that payments should be made {}?",
|
||||||
display_payment_period(&payment_period)
|
display_payment_period(&payment_period)
|
||||||
);
|
);
|
||||||
let question4 = format!(
|
let question4 =
|
||||||
"Do you agree that this loan requires {payment_number} total payments?"
|
format!("Do you agree that this loan requires {payment_number} total payments?");
|
||||||
);
|
|
||||||
let question5 = format!(
|
let question5 = format!(
|
||||||
"Do you agree that each payment should be {} {}?",
|
"Do you agree that each payment should be {} {}?",
|
||||||
display_amount(payment_amount),
|
display_amount(payment_amount),
|
||||||
|
|
|
||||||
|
|
@ -1,249 +1,242 @@
|
||||||
use blockchain::blocks::swap::UnsignedSwapTransaction;
|
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};
|
||||||
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
use blockchain::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use blockchain::env;
|
use blockchain::env;
|
||||||
use blockchain::fs;
|
use blockchain::fs;
|
||||||
use blockchain::json;
|
use blockchain::json;
|
||||||
use blockchain::read_to_string;
|
use blockchain::read_to_string;
|
||||||
use blockchain::records::wallet_registry::resolve_local_input_short_address;
|
use blockchain::records::wallet_registry::resolve_local_input_short_address;
|
||||||
use blockchain::wallets::structures::Wallet;
|
use blockchain::wallets::structures::Wallet;
|
||||||
use blockchain::Value;
|
use blockchain::Value;
|
||||||
|
|
||||||
// padd the coin to ensure 15 characters
|
// padd the coin to ensure 15 characters
|
||||||
fn pad_to_width(input: &str, width: usize) -> String {
|
fn pad_to_width(input: &str, width: usize) -> String {
|
||||||
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
let mut result = String::with_capacity(width); // Pre-allocate string with capacity
|
||||||
let _ = std::fmt::write(
|
let _ = std::fmt::write(&mut result, format_args!("{input:<width$}"));
|
||||||
&mut result,
|
result
|
||||||
format_args!("{input:<width$}"),
|
}
|
||||||
);
|
|
||||||
result
|
fn normalize_short_address_input(address: &str) -> Result<String, String> {
|
||||||
}
|
resolve_local_input_short_address(address.trim())
|
||||||
|
}
|
||||||
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
|
||||||
#[tokio::main]
|
let args: Vec<String> = env::args().collect();
|
||||||
async fn main() {
|
if args.len() != 2 {
|
||||||
// Get the filename from the command line arguments
|
println!("Usage:./sign_swap <path/to/file.json>");
|
||||||
let args: Vec<String> = env::args().collect();
|
return;
|
||||||
if args.len() != 2 {
|
}
|
||||||
println!("Usage:./sign_swap <path/to/file.json>");
|
let filename = &args[1];
|
||||||
return;
|
let decryption_key = prompt_hidden_nonempty(
|
||||||
}
|
"What is your wallet decryption key? ",
|
||||||
let filename = &args[1];
|
"Wallet key cannot be empty. Please try again.",
|
||||||
let decryption_key = prompt_hidden_nonempty(
|
)
|
||||||
"What is your wallet decryption key? ",
|
.await;
|
||||||
"Wallet key cannot be empty. Please try again.",
|
|
||||||
)
|
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
||||||
.await;
|
Ok(wallet) => wallet,
|
||||||
|
Err(err) => {
|
||||||
let wallet = match Wallet::try_obtain_wallet(decryption_key, None).await {
|
eprintln!("Wallet decryption failed: {err}");
|
||||||
Ok(wallet) => wallet,
|
return;
|
||||||
Err(err) => {
|
}
|
||||||
eprintln!("Wallet decryption failed: {err}");
|
};
|
||||||
return;
|
|
||||||
}
|
let private_key = &wallet.saved.private_key;
|
||||||
};
|
let address = &wallet.saved.short_address;
|
||||||
|
|
||||||
let private_key = &wallet.saved.private_key;
|
// Read the contents of the file
|
||||||
let address = &wallet.saved.short_address;
|
let contents = match read_to_string(filename).await {
|
||||||
|
Ok(contents) => contents,
|
||||||
// Read the contents of the file
|
Err(_) => {
|
||||||
let contents = match read_to_string(filename).await {
|
println!("Error reading file: {filename}");
|
||||||
Ok(contents) => contents,
|
return;
|
||||||
Err(_) => {
|
}
|
||||||
println!("Error reading file: {filename}");
|
};
|
||||||
return;
|
|
||||||
}
|
// Parse the JSON
|
||||||
};
|
let json: Result<Value, _> = serde_json::from_str(&contents);
|
||||||
|
let json = match json {
|
||||||
// Parse the JSON
|
Ok(json) => json,
|
||||||
let json: Result<Value, _> = serde_json::from_str(&contents);
|
Err(_) => {
|
||||||
let json = match json {
|
println!("Error parsing JSON in file: {filename}");
|
||||||
Ok(json) => json,
|
return;
|
||||||
Err(_) => {
|
}
|
||||||
println!("Error parsing JSON in file: {filename}");
|
};
|
||||||
return;
|
|
||||||
}
|
let txtype = 6;
|
||||||
};
|
|
||||||
|
let timestamp = json["timestamp"].as_u64().unwrap_or_default() as u32;
|
||||||
let txtype = 6;
|
let offer_expiration = json["offer_expiration"].as_u64().unwrap_or_default() as u32;
|
||||||
|
|
||||||
let timestamp = json["timestamp"].as_u64().unwrap_or_default() as u32;
|
// get values from transaction json
|
||||||
let offer_expiration = json["offer_expiration"].as_u64().unwrap_or_default() as u32;
|
let token_name1 = json["ticker1"]
|
||||||
|
.as_str()
|
||||||
// get values from transaction json
|
.unwrap_or_default()
|
||||||
let token_name1 = json["ticker1"]
|
.trim()
|
||||||
.as_str()
|
.to_lowercase();
|
||||||
.unwrap_or_default()
|
let nft_series1: u32 = json["nft_series1"].as_u64().unwrap_or_default() as u32;
|
||||||
.trim()
|
let value1: u64 = json["value1"].as_u64().unwrap_or_default();
|
||||||
.to_lowercase();
|
let receive_amount = value1 as f64 / 100000000.0;
|
||||||
let nft_series1: u32 = json["nft_series1"].as_u64().unwrap_or_default() as u32;
|
|
||||||
let value1: u64 = json["value1"].as_u64().unwrap_or_default();
|
let token_name2 = json["ticker2"]
|
||||||
let receive_amount = value1 as f64 / 100000000.0;
|
.as_str()
|
||||||
|
.unwrap_or_default()
|
||||||
let token_name2 = json["ticker2"]
|
.trim()
|
||||||
.as_str()
|
.to_lowercase();
|
||||||
.unwrap_or_default()
|
let nft_series2: u32 = json["nft_series2"].as_u64().unwrap_or_default() as u32;
|
||||||
.trim()
|
let value2: u64 = json["value2"].as_u64().unwrap_or_default();
|
||||||
.to_lowercase();
|
let send_amount = value2 as f64 / 100000000.0;
|
||||||
let nft_series2: u32 = json["nft_series2"].as_u64().unwrap_or_default() as u32;
|
|
||||||
let value2: u64 = json["value2"].as_u64().unwrap_or_default();
|
let txfee1_value: u64 = json["txfee1"].as_u64().unwrap_or_default();
|
||||||
let send_amount = value2 as f64 / 100000000.0;
|
let tip1_value: u64 = json["tip1"].as_u64().unwrap_or_default();
|
||||||
|
|
||||||
let txfee1_value: u64 = json["txfee1"].as_u64().unwrap_or_default();
|
let sender1 = match normalize_short_address_input(json["sender1"].as_str().unwrap_or_default())
|
||||||
let tip1_value: u64 = json["tip1"].as_u64().unwrap_or_default();
|
{
|
||||||
|
Ok(address) => address,
|
||||||
let sender1 = match normalize_short_address_input(json["sender1"].as_str().unwrap_or_default())
|
Err(_) => {
|
||||||
{
|
println!("sender1 wallet invalid");
|
||||||
Ok(address) => address,
|
return;
|
||||||
Err(_) => {
|
}
|
||||||
println!("sender1 wallet invalid");
|
};
|
||||||
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 txfee2_value: u64 = json["txfee2"].as_u64().unwrap_or_default();
|
let tip2 = tip2_value as f64 / 100000000.0;
|
||||||
let txfee2 = txfee2_value as f64 / 100000000.0;
|
|
||||||
let tip2_value: u64 = json["tip2"].as_u64().unwrap_or_default();
|
let sender2 = match normalize_short_address_input(json["sender2"].as_str().unwrap_or_default())
|
||||||
let tip2 = tip2_value as f64 / 100000000.0;
|
{
|
||||||
|
Ok(address) => address,
|
||||||
let sender2 = match normalize_short_address_input(json["sender2"].as_str().unwrap_or_default())
|
Err(_) => {
|
||||||
{
|
println!("sender2 wallet invalid");
|
||||||
Ok(address) => address,
|
return;
|
||||||
Err(_) => {
|
}
|
||||||
println!("sender2 wallet invalid");
|
};
|
||||||
return;
|
|
||||||
}
|
// ensure wallet and sender2 match
|
||||||
};
|
if sender2 != address.trim() {
|
||||||
|
println!(
|
||||||
// ensure wallet and sender2 match
|
"Transaction is not valid for your wallet address. Expected {sender2} found {address}"
|
||||||
if sender2 != address.trim() {
|
);
|
||||||
println!(
|
return;
|
||||||
"Transaction is not valid for your wallet address. Expected {sender2} found {address}"
|
}
|
||||||
);
|
|
||||||
return;
|
let (
|
||||||
}
|
_network_name,
|
||||||
|
network_coin,
|
||||||
let (
|
_suffix,
|
||||||
_network_name,
|
_torrent_path,
|
||||||
network_coin,
|
_wallet_path,
|
||||||
_suffix,
|
_blockpath,
|
||||||
_torrent_path,
|
_db_path,
|
||||||
_wallet_path,
|
_balance_path,
|
||||||
_blockpath,
|
_log_path,
|
||||||
_db_path,
|
) = block_extension_and_paths();
|
||||||
_balance_path,
|
// setup validation questions
|
||||||
_log_path,
|
let question1 = format!("Are you expecting to receive {receive_amount} {token_name1}?");
|
||||||
) = block_extension_and_paths();
|
let question2 = format!("Are you expecting to send {send_amount} {token_name2}?");
|
||||||
// setup validation questions
|
let question3 = format!("Are you willing to spend {txfee2} {network_coin} in fees?");
|
||||||
let question1 = format!(
|
let question4 = format!("Are you willing to tip {tip2} {token_name2}?");
|
||||||
"Are you expecting to receive {receive_amount} {token_name1}?"
|
|
||||||
);
|
// ask validation questions
|
||||||
let question2 = format!("Are you expecting to send {send_amount} {token_name2}?");
|
if !ask_yes_no_question(&question1).await {
|
||||||
let question3 = format!(
|
println!("Transaction is not valid");
|
||||||
"Are you willing to spend {txfee2} {network_coin} in fees?"
|
return;
|
||||||
);
|
}
|
||||||
let question4 = format!("Are you willing to tip {tip2} {token_name2}?");
|
if !ask_yes_no_question(&question2).await {
|
||||||
|
println!("Transaction is not valid");
|
||||||
// ask validation questions
|
return;
|
||||||
if !ask_yes_no_question(&question1).await {
|
}
|
||||||
println!("Transaction is not valid");
|
if !ask_yes_no_question(&question3).await {
|
||||||
return;
|
println!("Transaction is not valid");
|
||||||
}
|
return;
|
||||||
if !ask_yes_no_question(&question2).await {
|
}
|
||||||
println!("Transaction is not valid");
|
if !ask_yes_no_question(&question4).await {
|
||||||
return;
|
println!("Transaction is not valid");
|
||||||
}
|
return;
|
||||||
if !ask_yes_no_question(&question3).await {
|
}
|
||||||
println!("Transaction is not valid");
|
|
||||||
return;
|
let padded_token_name1 = pad_to_width(&token_name1, 15);
|
||||||
}
|
let padded_token_name2 = pad_to_width(&token_name2, 15);
|
||||||
if !ask_yes_no_question(&question4).await {
|
|
||||||
println!("Transaction is not valid");
|
let unsigned_swap = UnsignedSwapTransaction::new(
|
||||||
return;
|
txtype,
|
||||||
}
|
timestamp,
|
||||||
|
offer_expiration,
|
||||||
let padded_token_name1 = pad_to_width(&token_name1, 15);
|
&padded_token_name1,
|
||||||
let padded_token_name2 = pad_to_width(&token_name2, 15);
|
nft_series1,
|
||||||
|
value1,
|
||||||
let unsigned_swap = UnsignedSwapTransaction::new(
|
&padded_token_name2,
|
||||||
txtype,
|
nft_series2,
|
||||||
timestamp,
|
value2,
|
||||||
offer_expiration,
|
&sender1,
|
||||||
&padded_token_name1,
|
address.trim(),
|
||||||
nft_series1,
|
tip1_value,
|
||||||
value1,
|
tip2_value,
|
||||||
&padded_token_name2,
|
txfee1_value,
|
||||||
nft_series2,
|
txfee2_value,
|
||||||
value2,
|
)
|
||||||
&sender1,
|
.await;
|
||||||
address.trim(),
|
|
||||||
tip1_value,
|
let hashed_data = unsigned_swap.hash().await;
|
||||||
tip2_value,
|
|
||||||
txfee1_value,
|
let original_hash = &json["hash"].as_str().unwrap_or_default().trim();
|
||||||
txfee2_value,
|
let signature1 = &json["signature1"].as_str().unwrap_or_default().trim();
|
||||||
)
|
|
||||||
.await;
|
let signature2 = if hashed_data == *original_hash.to_string() {
|
||||||
|
match unsigned_swap.hash_and_sign(&private_key.to_string()).await {
|
||||||
let hashed_data = unsigned_swap.hash().await;
|
Ok(signature) => signature,
|
||||||
|
Err(err) => {
|
||||||
let original_hash = &json["hash"].as_str().unwrap_or_default().trim();
|
println!("Signing transaction failed: {err}");
|
||||||
let signature1 = &json["signature1"].as_str().unwrap_or_default().trim();
|
return;
|
||||||
|
}
|
||||||
let signature2 = if hashed_data == *original_hash.to_string() {
|
}
|
||||||
match unsigned_swap.hash_and_sign(&private_key.to_string()).await {
|
} else {
|
||||||
Ok(signature) => signature,
|
println!("Signing transaction failed. The included hash was incorrect.");
|
||||||
Err(err) => {
|
return;
|
||||||
println!("Signing transaction failed: {err}");
|
};
|
||||||
return;
|
|
||||||
}
|
let output = json!({
|
||||||
}
|
"txtype": txtype,
|
||||||
} else {
|
"timestamp": timestamp,
|
||||||
println!("Signing transaction failed. The included hash was incorrect.");
|
"offer_expiration": offer_expiration,
|
||||||
return;
|
"ticker1": padded_token_name1,
|
||||||
};
|
"nft_series1": nft_series1,
|
||||||
|
"value1": value1,
|
||||||
let output = json!({
|
"ticker2": padded_token_name2,
|
||||||
"txtype": txtype,
|
"nft_series2": nft_series2,
|
||||||
"timestamp": timestamp,
|
"value2": value2,
|
||||||
"offer_expiration": offer_expiration,
|
"sender1": sender1,
|
||||||
"ticker1": padded_token_name1,
|
"sender2": address.trim(),
|
||||||
"nft_series1": nft_series1,
|
"tip1": tip1_value,
|
||||||
"value1": value1,
|
"tip2": tip2_value,
|
||||||
"ticker2": padded_token_name2,
|
"txfee1": txfee1_value,
|
||||||
"nft_series2": nft_series2,
|
"txfee2": txfee2_value,
|
||||||
"value2": value2,
|
"hash": hashed_data,
|
||||||
"sender1": sender1,
|
"signature1": signature1,
|
||||||
"sender2": address.trim(),
|
"signature2": signature2
|
||||||
"tip1": tip1_value,
|
});
|
||||||
"tip2": tip2_value,
|
let output_str = serde_json::to_string_pretty(&output).expect("Failed to serialize JSON");
|
||||||
"txfee1": txfee1_value,
|
|
||||||
"txfee2": txfee2_value,
|
// Define the directory path
|
||||||
"hash": hashed_data,
|
let dir_path = "./transactions";
|
||||||
"signature1": signature1,
|
|
||||||
"signature2": signature2
|
// Create the directory if it doesn't exist
|
||||||
});
|
if let Err(e) = fs::create_dir_all(dir_path) {
|
||||||
let output_str = serde_json::to_string_pretty(&output).expect("Failed to serialize JSON");
|
eprintln!("Failed to create directory: {e}");
|
||||||
|
return;
|
||||||
// Define the directory path
|
}
|
||||||
let dir_path = "./transactions";
|
|
||||||
|
// Define the file path
|
||||||
// Create the directory if it doesn't exist
|
let file_path = format!("{dir_path}/{hashed_data}.json");
|
||||||
if let Err(e) = fs::create_dir_all(dir_path) {
|
|
||||||
eprintln!("Failed to create directory: {e}");
|
// Write the JSON string to the file
|
||||||
return;
|
if let Err(e) = fs::write(&file_path, &output_str) {
|
||||||
}
|
eprintln!("Failed to write file: {e}");
|
||||||
|
return;
|
||||||
// Define the file path
|
}
|
||||||
let file_path = format!("{dir_path}/{hashed_data}.json");
|
|
||||||
|
println!("Transaction: {output_str}");
|
||||||
// Write the JSON string to the file
|
}
|
||||||
if let Err(e) = fs::write(&file_path, &output_str) {
|
|
||||||
eprintln!("Failed to write file: {e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Transaction: {output_str}");
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -185,10 +185,8 @@ impl VrfBlock {
|
||||||
cursor
|
cursor
|
||||||
.write_all(&self.unmined_block.timestamp.to_le_bytes())
|
.write_all(&self.unmined_block.timestamp.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let miner_bytes =
|
let miner_bytes = Wallet::short_address_to_bytes(&self.unmined_block.miner)
|
||||||
Wallet::short_address_to_bytes(&self.unmined_block.miner).ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid short miner address"))?;
|
||||||
tokio::io::Error::other("Invalid short miner address")
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&miner_bytes).await?;
|
cursor.write_all(&miner_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(&decode(&self.unmined_block.previous_hash).unwrap())
|
.write_all(&decode(&self.unmined_block.previous_hash).unwrap())
|
||||||
|
|
@ -207,8 +205,7 @@ impl VrfBlock {
|
||||||
pub async fn from_bytes(bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// A VRF header must be exactly the fixed header byte length.
|
// A VRF header must be exactly the fixed header byte length.
|
||||||
if bytes.len() != VRF_BLOCK_BYTES {
|
if bytes.len() != VRF_BLOCK_BYTES {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count for Block",
|
return Err(tokio::io::Error::other("Invalid Byte Count for Block"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from the fixed-width VRF header bytes.
|
// Read from the fixed-width VRF header bytes.
|
||||||
|
|
@ -219,9 +216,8 @@ impl VrfBlock {
|
||||||
|
|
||||||
let mut miner_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut miner_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut miner_bytes).await?;
|
cursor.read_exact(&mut miner_bytes).await?;
|
||||||
let miner = Wallet::bytes_to_short_address(&miner_bytes).ok_or_else(|| {
|
let miner = Wallet::bytes_to_short_address(&miner_bytes)
|
||||||
tokio::io::Error::other("Invalid short miner address")
|
.ok_or_else(|| tokio::io::Error::other("Invalid short miner address"))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode parent hash, difficulty, nonce, VRF number, and proof.
|
// Decode parent hash, difficulty, nonce, VRF number, and proof.
|
||||||
let mut prev_hash_bytes = vec![0; 32];
|
let mut prev_hash_bytes = vec![0; 32];
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,7 @@ impl BurnTransaction {
|
||||||
.write_all(&self.unsigned_burn.time.to_le_bytes())
|
.write_all(&self.unsigned_burn.time.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_burn.address)
|
let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_burn.address)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid burn short address"))?;
|
||||||
tokio::io::Error::other("Invalid burn short address")
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&address_bytes).await?;
|
cursor.write_all(&address_bytes).await?;
|
||||||
cursor.write_all(self.unsigned_burn.coin.as_bytes()).await?;
|
cursor.write_all(self.unsigned_burn.coin.as_bytes()).await?;
|
||||||
cursor
|
cursor
|
||||||
|
|
@ -126,8 +124,7 @@ impl BurnTransaction {
|
||||||
|
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cursor = Cursor::new(bytes);
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
@ -136,10 +133,8 @@ impl BurnTransaction {
|
||||||
|
|
||||||
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut address_bytes).await?;
|
cursor.read_exact(&mut address_bytes).await?;
|
||||||
let address = Wallet::bytes_to_short_address(&address_bytes).ok_or_else(|| {
|
let address = Wallet::bytes_to_short_address(&address_bytes)
|
||||||
tokio::io::Error::other("Invalid burn short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid burn short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut coin_bytes = vec![0; 15];
|
let mut coin_bytes = vec![0; 15];
|
||||||
cursor.read_exact(&mut coin_bytes).await?;
|
cursor.read_exact(&mut coin_bytes).await?;
|
||||||
|
|
|
||||||
|
|
@ -110,10 +110,7 @@ impl CollateralClaimTransaction {
|
||||||
.write_all(&decode(&self.unsigned_collateral_claim.contract_hash).unwrap())
|
.write_all(&decode(&self.unsigned_collateral_claim.contract_hash).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_collateral_claim.address)
|
let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_collateral_claim.address)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid collateral claimant short address"))?;
|
||||||
tokio::io::Error::other("Invalid collateral claimant short address",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&address_bytes).await?;
|
cursor.write_all(&address_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(&self.unsigned_collateral_claim.txfee.to_le_bytes())
|
.write_all(&self.unsigned_collateral_claim.txfee.to_le_bytes())
|
||||||
|
|
@ -126,8 +123,7 @@ impl CollateralClaimTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width collateral-claim bytes.
|
// Read the remaining fixed-width collateral-claim bytes.
|
||||||
|
|
@ -144,8 +140,7 @@ impl CollateralClaimTransaction {
|
||||||
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut address_bytes).await?;
|
cursor.read_exact(&mut address_bytes).await?;
|
||||||
let address = Wallet::bytes_to_short_address(&address_bytes).ok_or_else(|| {
|
let address = Wallet::bytes_to_short_address(&address_bytes).ok_or_else(|| {
|
||||||
tokio::io::Error::other("Invalid collateral claimant short address bytes",
|
tokio::io::Error::other("Invalid collateral claimant short address bytes")
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let txfee = cursor.read_u64_le().await?;
|
let txfee = cursor.read_u64_le().await?;
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,7 @@ impl GenesisTransaction {
|
||||||
// The transaction type is read by the block parser, so this
|
// The transaction type is read by the block parser, so this
|
||||||
// function receives only the remaining genesis bytes.
|
// function receives only the remaining genesis bytes.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from the remaining fixed-width transaction bytes.
|
// Read from the remaining fixed-width transaction bytes.
|
||||||
|
|
|
||||||
|
|
@ -104,10 +104,7 @@ impl IssueTokenTransaction {
|
||||||
.write_all(&self.unsigned_issue_token.time.to_le_bytes())
|
.write_all(&self.unsigned_issue_token.time.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let creator_bytes = Wallet::short_address_to_bytes(&self.unsigned_issue_token.creator)
|
let creator_bytes = Wallet::short_address_to_bytes(&self.unsigned_issue_token.creator)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid issue-token creator short address"))?;
|
||||||
tokio::io::Error::other("Invalid issue-token creator short address",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&creator_bytes).await?;
|
cursor.write_all(&creator_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(self.unsigned_issue_token.ticker.as_bytes())
|
.write_all(self.unsigned_issue_token.ticker.as_bytes())
|
||||||
|
|
@ -126,8 +123,7 @@ impl IssueTokenTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cursor = Cursor::new(bytes);
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
@ -138,8 +134,7 @@ impl IssueTokenTransaction {
|
||||||
let mut creator_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut creator_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut creator_bytes).await?;
|
cursor.read_exact(&mut creator_bytes).await?;
|
||||||
let creator = Wallet::bytes_to_short_address(&creator_bytes).ok_or_else(|| {
|
let creator = Wallet::bytes_to_short_address(&creator_bytes).ok_or_else(|| {
|
||||||
tokio::io::Error::other("Invalid issue-token creator short address bytes",
|
tokio::io::Error::other("Invalid issue-token creator short address bytes")
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Decode token ticker, issued amount, fee, and signature.
|
// Decode token ticker, issued amount, fee, and signature.
|
||||||
|
|
|
||||||
|
|
@ -123,9 +123,7 @@ impl ContractPaymentTransaction {
|
||||||
.write_all(&decode(&self.unsigned_contract_payment.contract_hash).unwrap())
|
.write_all(&decode(&self.unsigned_contract_payment.contract_hash).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_contract_payment.address)
|
let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_contract_payment.address)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid payer short address"))?;
|
||||||
tokio::io::Error::other("Invalid payer short address")
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&address_bytes).await?;
|
cursor.write_all(&address_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(&self.unsigned_contract_payment.tip.to_le_bytes())
|
.write_all(&self.unsigned_contract_payment.tip.to_le_bytes())
|
||||||
|
|
@ -142,8 +140,7 @@ impl ContractPaymentTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width loan-payment bytes.
|
// Read the remaining fixed-width loan-payment bytes.
|
||||||
|
|
@ -160,10 +157,8 @@ impl ContractPaymentTransaction {
|
||||||
// Decode payer short address, miner tip, fee, txid hash, and signature.
|
// Decode payer short address, miner tip, fee, txid hash, and signature.
|
||||||
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut address_bytes).await?;
|
cursor.read_exact(&mut address_bytes).await?;
|
||||||
let address = Wallet::bytes_to_short_address(&address_bytes).ok_or_else(|| {
|
let address = Wallet::bytes_to_short_address(&address_bytes)
|
||||||
tokio::io::Error::other("Invalid payer short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid payer short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let tip = cursor.read_u64_le().await?;
|
let tip = cursor.read_u64_le().await?;
|
||||||
let txfee = cursor.read_u64_le().await?;
|
let txfee = cursor.read_u64_le().await?;
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ pub struct UnsignedLoanContractTransaction {
|
||||||
pub payment_period: String, // 1 byte d, w, or m for days, weeks, or months
|
pub payment_period: String, // 1 byte d, w, or m for days, weeks, or months
|
||||||
pub payment_number: u8, // 1 byte total number of payments
|
pub payment_number: u8, // 1 byte total number of payments
|
||||||
pub payment_amount: u64, // 8 bytes amount due for each payment
|
pub payment_amount: u64, // 8 bytes amount due for each payment
|
||||||
pub grace_period: u8, // 1 byte missed payments before collateral claim is allowed
|
pub grace_period: u8, // 1 byte missed payments before collateral claim is allowed
|
||||||
pub max_late_value: u64, // 8 bytes max overdue value before collateral claim is allowed
|
pub max_late_value: u64, // 8 bytes max overdue value before collateral claim is allowed
|
||||||
pub txfee: u64, // 8 bytes transaction fee paid by the lender
|
pub txfee: u64, // 8 bytes transaction fee paid by the lender
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)] // 1486 bytes
|
#[derive(Debug, Serialize, Clone)] // 1486 bytes
|
||||||
|
|
@ -35,9 +35,23 @@ pub struct LoanContractTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoanContractTransaction {
|
impl LoanContractTransaction {
|
||||||
pub const BYTE_LENGTH: usize =
|
pub const BYTE_LENGTH: usize = 1
|
||||||
1 + 4 + 15 + 8 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 15 + 8 + Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
+ 4
|
||||||
+ 1 + 1 + 8 + 1 + 8 + 8 + 32 + Wallet::SIGNATURE_LENGTH + Wallet::SIGNATURE_LENGTH;
|
+ 15
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ 15
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ 1
|
||||||
|
+ 1
|
||||||
|
+ 8
|
||||||
|
+ 1
|
||||||
|
+ 8
|
||||||
|
+ 8
|
||||||
|
+ 32
|
||||||
|
+ Wallet::SIGNATURE_LENGTH
|
||||||
|
+ Wallet::SIGNATURE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsignedLoanContractTransaction {
|
impl UnsignedLoanContractTransaction {
|
||||||
|
|
@ -163,9 +177,7 @@ impl LoanContractTransaction {
|
||||||
.write_all(&self.unsigned_loan_contract.loan_amount.to_le_bytes())
|
.write_all(&self.unsigned_loan_contract.loan_amount.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let lender_bytes = Wallet::short_address_to_bytes(&self.unsigned_loan_contract.lender)
|
let lender_bytes = Wallet::short_address_to_bytes(&self.unsigned_loan_contract.lender)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid lender short address"))?;
|
||||||
tokio::io::Error::other("Invalid lender short address")
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&lender_bytes).await?;
|
cursor.write_all(&lender_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(self.unsigned_loan_contract.collateral.as_bytes())
|
.write_all(self.unsigned_loan_contract.collateral.as_bytes())
|
||||||
|
|
@ -174,10 +186,7 @@ impl LoanContractTransaction {
|
||||||
.write_all(&self.unsigned_loan_contract.collateral_amount.to_le_bytes())
|
.write_all(&self.unsigned_loan_contract.collateral_amount.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let borrower_bytes = Wallet::short_address_to_bytes(&self.unsigned_loan_contract.borrower)
|
let borrower_bytes = Wallet::short_address_to_bytes(&self.unsigned_loan_contract.borrower)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid borrower short address"))?;
|
||||||
tokio::io::Error::other("Invalid borrower short address",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&borrower_bytes).await?;
|
cursor.write_all(&borrower_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(self.unsigned_loan_contract.payment_period.as_bytes())
|
.write_all(self.unsigned_loan_contract.payment_period.as_bytes())
|
||||||
|
|
@ -207,8 +216,7 @@ impl LoanContractTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width loan-contract bytes.
|
// Read the remaining fixed-width loan-contract bytes.
|
||||||
|
|
@ -226,10 +234,8 @@ impl LoanContractTransaction {
|
||||||
// Decode lender short address.
|
// Decode lender short address.
|
||||||
let mut lender_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut lender_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut lender_bytes).await?;
|
cursor.read_exact(&mut lender_bytes).await?;
|
||||||
let lender = Wallet::bytes_to_short_address(&lender_bytes).ok_or_else(|| {
|
let lender = Wallet::bytes_to_short_address(&lender_bytes)
|
||||||
tokio::io::Error::other("Invalid lender short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid lender short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode collateral asset and amount.
|
// Decode collateral asset and amount.
|
||||||
let mut collateral_bytes = vec![0; 15];
|
let mut collateral_bytes = vec![0; 15];
|
||||||
|
|
@ -241,10 +247,8 @@ impl LoanContractTransaction {
|
||||||
// Decode borrower short address.
|
// Decode borrower short address.
|
||||||
let mut borrower_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut borrower_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut borrower_bytes).await?;
|
cursor.read_exact(&mut borrower_bytes).await?;
|
||||||
let borrower = Wallet::bytes_to_short_address(&borrower_bytes).ok_or_else(|| {
|
let borrower = Wallet::bytes_to_short_address(&borrower_bytes)
|
||||||
tokio::io::Error::other("Invalid borrower short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid borrower short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode payment schedule and late-payment rules.
|
// Decode payment schedule and late-payment rules.
|
||||||
let mut payment_period_bytes = vec![0; 1];
|
let mut payment_period_bytes = vec![0; 1];
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,19 @@ pub struct MarketingTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MarketingTransaction {
|
impl MarketingTransaction {
|
||||||
pub const BYTE_LENGTH: usize =
|
pub const BYTE_LENGTH: usize = 1
|
||||||
1 + 4 + 8 + 6 + 40 + 100 + 1 + 1 + 2 + 2 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 8 + Wallet::SIGNATURE_LENGTH;
|
+ 4
|
||||||
|
+ 8
|
||||||
|
+ 6
|
||||||
|
+ 40
|
||||||
|
+ 100
|
||||||
|
+ 1
|
||||||
|
+ 1
|
||||||
|
+ 2
|
||||||
|
+ 2
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SIGNATURE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsignedMarketingTransaction {
|
impl UnsignedMarketingTransaction {
|
||||||
|
|
@ -152,10 +163,7 @@ impl MarketingTransaction {
|
||||||
.write_all(&self.unsigned_marketing.click_value.to_le_bytes())
|
.write_all(&self.unsigned_marketing.click_value.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let advertiser_bytes = Wallet::short_address_to_bytes(&self.unsigned_marketing.advertiser)
|
let advertiser_bytes = Wallet::short_address_to_bytes(&self.unsigned_marketing.advertiser)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid advertiser short address"))?;
|
||||||
tokio::io::Error::other("Invalid advertiser short address",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&advertiser_bytes).await?;
|
cursor.write_all(&advertiser_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(&self.unsigned_marketing.txfee.to_le_bytes())
|
.write_all(&self.unsigned_marketing.txfee.to_le_bytes())
|
||||||
|
|
@ -168,8 +176,7 @@ impl MarketingTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width marketing bytes.
|
// Read the remaining fixed-width marketing bytes.
|
||||||
|
|
@ -201,10 +208,8 @@ impl MarketingTransaction {
|
||||||
// Decode advertiser short address, fee, and signature.
|
// Decode advertiser short address, fee, and signature.
|
||||||
let mut advertiser_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut advertiser_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut advertiser_bytes).await?;
|
cursor.read_exact(&mut advertiser_bytes).await?;
|
||||||
let advertiser = Wallet::bytes_to_short_address(&advertiser_bytes).ok_or_else(|| {
|
let advertiser = Wallet::bytes_to_short_address(&advertiser_bytes)
|
||||||
tokio::io::Error::other("Invalid advertiser short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid advertiser short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let txfee = cursor.read_u64_le().await?;
|
let txfee = cursor.read_u64_le().await?;
|
||||||
let mut signature_bytes = vec![0; Wallet::SIGNATURE_LENGTH];
|
let mut signature_bytes = vec![0; Wallet::SIGNATURE_LENGTH];
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,15 @@ use crate::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)] // 255 bytes
|
#[derive(Debug, Serialize, Clone)] // 255 bytes
|
||||||
pub struct UnsignedCreateNftTransaction {
|
pub struct UnsignedCreateNftTransaction {
|
||||||
pub txtype: u8, // 1 byte transaction type, should be 4
|
pub txtype: u8, // 1 byte transaction type, should be 4
|
||||||
pub time: u32, // 4 bytes transaction timestamp
|
pub time: u32, // 4 bytes transaction timestamp
|
||||||
pub creator: String, // 22 bytes creator short address
|
pub creator: String, // 22 bytes creator short address
|
||||||
pub series: u8, // 1 byte 0 for single NFT, 1 for series
|
pub series: u8, // 1 byte 0 for single NFT, 1 for series
|
||||||
pub nft_name: String, // 15 bytes NFT or collection name padded with spaces
|
pub nft_name: String, // 15 bytes NFT or collection name padded with spaces
|
||||||
pub item_ipfs: String, // 100 bytes padded CID string
|
pub item_ipfs: String, // 100 bytes padded CID string
|
||||||
pub count: u32, // 4 bytes 1 for single NFT, otherwise series item count
|
pub count: u32, // 4 bytes 1 for single NFT, otherwise series item count
|
||||||
pub desc: String, // 100 bytes description padded with spaces
|
pub desc: String, // 100 bytes description padded with spaces
|
||||||
pub txfee: u64, // 8 bytes transaction fee
|
pub txfee: u64, // 8 bytes transaction fee
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)] // 921 bytes
|
#[derive(Debug, Serialize, Clone)] // 921 bytes
|
||||||
|
|
@ -28,8 +28,16 @@ pub struct CreateNftTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateNftTransaction {
|
impl CreateNftTransaction {
|
||||||
pub const BYTE_LENGTH: usize =
|
pub const BYTE_LENGTH: usize = 1
|
||||||
1 + 4 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 1 + 15 + 100 + 4 + 100 + 8 + Wallet::SIGNATURE_LENGTH;
|
+ 4
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ 1
|
||||||
|
+ 15
|
||||||
|
+ 100
|
||||||
|
+ 4
|
||||||
|
+ 100
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SIGNATURE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsignedCreateNftTransaction {
|
impl UnsignedCreateNftTransaction {
|
||||||
|
|
@ -119,9 +127,7 @@ impl CreateNftTransaction {
|
||||||
.write_all(&self.unsigned_create_nft.time.to_le_bytes())
|
.write_all(&self.unsigned_create_nft.time.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let creator_bytes = Wallet::short_address_to_bytes(&self.unsigned_create_nft.creator)
|
let creator_bytes = Wallet::short_address_to_bytes(&self.unsigned_create_nft.creator)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid creator short address"))?;
|
||||||
tokio::io::Error::other("Invalid creator short address")
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&creator_bytes).await?;
|
cursor.write_all(&creator_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(&self.unsigned_create_nft.series.to_le_bytes())
|
.write_all(&self.unsigned_create_nft.series.to_le_bytes())
|
||||||
|
|
@ -149,8 +155,7 @@ impl CreateNftTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width NFT-creation bytes.
|
// Read the remaining fixed-width NFT-creation bytes.
|
||||||
|
|
@ -161,10 +166,8 @@ impl CreateNftTransaction {
|
||||||
|
|
||||||
let mut creator_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut creator_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut creator_bytes).await?;
|
cursor.read_exact(&mut creator_bytes).await?;
|
||||||
let creator = Wallet::bytes_to_short_address(&creator_bytes).ok_or_else(|| {
|
let creator = Wallet::bytes_to_short_address(&creator_bytes)
|
||||||
tokio::io::Error::other("Invalid creator short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid creator short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode series flag, name, CID, count, description, fee, and signature.
|
// Decode series flag, name, CID, count, description, fee, and signature.
|
||||||
let series = cursor.read_u8().await?;
|
let series = cursor.read_u8().await?;
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,7 @@ impl RewardsTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed reward bytes.
|
// Read the remaining fixed reward bytes.
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,17 @@ pub struct UnsignedSwapTransaction {
|
||||||
pub timestamp: u32, // 4 bytes offer creation timestamp
|
pub timestamp: u32, // 4 bytes offer creation timestamp
|
||||||
pub offer_expiration: u32, // 4 bytes offer expiration timestamp
|
pub offer_expiration: u32, // 4 bytes offer expiration timestamp
|
||||||
pub ticker1: String, // 15 bytes asset offered by sender1
|
pub ticker1: String, // 15 bytes asset offered by sender1
|
||||||
pub nft_series1: u32, // 4 bytes 0 for fungible assets, otherwise NFT series item
|
pub nft_series1: u32, // 4 bytes 0 for fungible assets, otherwise NFT series item
|
||||||
pub value1: u64, // 8 bytes amount offered by sender1
|
pub value1: u64, // 8 bytes amount offered by sender1
|
||||||
pub ticker2: String, // 15 bytes asset offered by sender2
|
pub ticker2: String, // 15 bytes asset offered by sender2
|
||||||
pub nft_series2: u32, // 4 bytes 0 for fungible assets, otherwise NFT series item
|
pub nft_series2: u32, // 4 bytes 0 for fungible assets, otherwise NFT series item
|
||||||
pub value2: u64, // 8 bytes amount offered by sender2
|
pub value2: u64, // 8 bytes amount offered by sender2
|
||||||
pub sender1: String, // 22 bytes sender1 short address
|
pub sender1: String, // 22 bytes sender1 short address
|
||||||
pub sender2: String, // 22 bytes sender2 short address
|
pub sender2: String, // 22 bytes sender2 short address
|
||||||
pub tip1: u64, // 8 bytes miner tip paid in ticker1
|
pub tip1: u64, // 8 bytes miner tip paid in ticker1
|
||||||
pub tip2: u64, // 8 bytes miner tip paid in ticker2
|
pub tip2: u64, // 8 bytes miner tip paid in ticker2
|
||||||
pub txfee1: u64, // 8 bytes sender1 fee
|
pub txfee1: u64, // 8 bytes sender1 fee
|
||||||
pub txfee2: u64, // 8 bytes sender2 fee
|
pub txfee2: u64, // 8 bytes sender2 fee
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)] // 1471 bytes
|
#[derive(Debug, Serialize, Clone)] // 1471 bytes
|
||||||
|
|
@ -35,9 +35,23 @@ pub struct SwapTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwapTransaction {
|
impl SwapTransaction {
|
||||||
pub const BYTE_LENGTH: usize =
|
pub const BYTE_LENGTH: usize = 1
|
||||||
1 + 4 + 4 + 15 + 4 + 8 + 15 + 4 + 8 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
+ 4
|
||||||
+ 8 + 8 + 8 + 8 + Wallet::SIGNATURE_LENGTH + Wallet::SIGNATURE_LENGTH;
|
+ 4
|
||||||
|
+ 15
|
||||||
|
+ 4
|
||||||
|
+ 8
|
||||||
|
+ 15
|
||||||
|
+ 4
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ 8
|
||||||
|
+ 8
|
||||||
|
+ 8
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SIGNATURE_LENGTH
|
||||||
|
+ Wallet::SIGNATURE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsignedSwapTransaction {
|
impl UnsignedSwapTransaction {
|
||||||
|
|
@ -178,13 +192,9 @@ impl SwapTransaction {
|
||||||
.write_all(&self.unsigned_swap.value2.to_le_bytes())
|
.write_all(&self.unsigned_swap.value2.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let sender1_bytes = Wallet::short_address_to_bytes(&self.unsigned_swap.sender1)
|
let sender1_bytes = Wallet::short_address_to_bytes(&self.unsigned_swap.sender1)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender1 short address"))?;
|
||||||
tokio::io::Error::other("Invalid sender1 short address")
|
|
||||||
})?;
|
|
||||||
let sender2_bytes = Wallet::short_address_to_bytes(&self.unsigned_swap.sender2)
|
let sender2_bytes = Wallet::short_address_to_bytes(&self.unsigned_swap.sender2)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender2 short address"))?;
|
||||||
tokio::io::Error::other("Invalid sender2 short address")
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&sender1_bytes).await?;
|
cursor.write_all(&sender1_bytes).await?;
|
||||||
cursor.write_all(&sender2_bytes).await?;
|
cursor.write_all(&sender2_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
|
|
@ -208,8 +218,7 @@ impl SwapTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width swap bytes.
|
// Read the remaining fixed-width swap bytes.
|
||||||
|
|
@ -238,17 +247,13 @@ impl SwapTransaction {
|
||||||
// Decode both sender short addresses.
|
// Decode both sender short addresses.
|
||||||
let mut sender1_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut sender1_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut sender1_bytes).await?;
|
cursor.read_exact(&mut sender1_bytes).await?;
|
||||||
let sender1 = Wallet::bytes_to_short_address(&sender1_bytes).ok_or_else(|| {
|
let sender1 = Wallet::bytes_to_short_address(&sender1_bytes)
|
||||||
tokio::io::Error::other("Invalid sender1 short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender1 short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut sender2_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut sender2_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut sender2_bytes).await?;
|
cursor.read_exact(&mut sender2_bytes).await?;
|
||||||
let sender2 = Wallet::bytes_to_short_address(&sender2_bytes).ok_or_else(|| {
|
let sender2 = Wallet::bytes_to_short_address(&sender2_bytes)
|
||||||
tokio::io::Error::other("Invalid sender2 short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender2 short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode miner tips, fees, and both signatures.
|
// Decode miner tips, fees, and both signatures.
|
||||||
let tip1 = cursor.read_u64_le().await?;
|
let tip1 = cursor.read_u64_le().await?;
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,7 @@ impl CreateTokenTransaction {
|
||||||
.write_all(&self.unsigned_create_token.time.to_le_bytes())
|
.write_all(&self.unsigned_create_token.time.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let creator_bytes = Wallet::short_address_to_bytes(&self.unsigned_create_token.creator)
|
let creator_bytes = Wallet::short_address_to_bytes(&self.unsigned_create_token.creator)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid creator short address"))?;
|
||||||
tokio::io::Error::other("Invalid creator short address")
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&creator_bytes).await?;
|
cursor.write_all(&creator_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
.write_all(self.unsigned_create_token.ticker.as_bytes())
|
.write_all(self.unsigned_create_token.ticker.as_bytes())
|
||||||
|
|
@ -139,8 +137,7 @@ impl CreateTokenTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width token-creation bytes.
|
// Read the remaining fixed-width token-creation bytes.
|
||||||
|
|
@ -152,10 +149,8 @@ impl CreateTokenTransaction {
|
||||||
// Decode the creator short address.
|
// Decode the creator short address.
|
||||||
let mut creator_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut creator_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut creator_bytes).await?;
|
cursor.read_exact(&mut creator_bytes).await?;
|
||||||
let creator = Wallet::bytes_to_short_address(&creator_bytes).ok_or_else(|| {
|
let creator = Wallet::bytes_to_short_address(&creator_bytes)
|
||||||
tokio::io::Error::other("Invalid creator short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid creator short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode the fixed 15-byte token ticker.
|
// Decode the fixed 15-byte token ticker.
|
||||||
let mut ticker_bytes = vec![0; 15];
|
let mut ticker_bytes = vec![0; 15];
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ pub struct UnsignedTransferTransaction {
|
||||||
pub txtype: u8, // 1 byte transaction type, should be 2
|
pub txtype: u8, // 1 byte transaction type, should be 2
|
||||||
pub time: u32, // 4 bytes transaction timestamp
|
pub time: u32, // 4 bytes transaction timestamp
|
||||||
pub value: u64, // 8 bytes number of coins or tokens to send
|
pub value: u64, // 8 bytes number of coins or tokens to send
|
||||||
pub coin: String, // 15 bytes base coin, token ticker, or NFT name, padded with spaces
|
pub coin: String, // 15 bytes base coin, token ticker, or NFT name, padded with spaces
|
||||||
pub nft_series: u32, // 4 bytes 0 for coins/tokens/1-of-1 NFTs, otherwise NFT series item
|
pub nft_series: u32, // 4 bytes 0 for coins/tokens/1-of-1 NFTs, otherwise NFT series item
|
||||||
pub sender: String, // 22 bytes sender short address
|
pub sender: String, // 22 bytes sender short address
|
||||||
pub receiver: String, // 22 bytes receiver short address
|
pub receiver: String, // 22 bytes receiver short address
|
||||||
pub txfee: u64, // 8 bytes transaction fee
|
pub txfee: u64, // 8 bytes transaction fee
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)] // 750 bytes
|
#[derive(Debug, Serialize, Clone)] // 750 bytes
|
||||||
|
|
@ -28,8 +28,15 @@ pub struct TransferTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransferTransaction {
|
impl TransferTransaction {
|
||||||
pub const BYTE_LENGTH: usize =
|
pub const BYTE_LENGTH: usize = 1
|
||||||
1 + 4 + 8 + 15 + 4 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 8 + Wallet::SIGNATURE_LENGTH;
|
+ 4
|
||||||
|
+ 8
|
||||||
|
+ 15
|
||||||
|
+ 4
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SIGNATURE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsignedTransferTransaction {
|
impl UnsignedTransferTransaction {
|
||||||
|
|
@ -126,14 +133,9 @@ impl TransferTransaction {
|
||||||
.write_all(&self.unsigned_transfer.nft_series.to_le_bytes())
|
.write_all(&self.unsigned_transfer.nft_series.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
let sender_bytes = Wallet::short_address_to_bytes(&self.unsigned_transfer.sender)
|
let sender_bytes = Wallet::short_address_to_bytes(&self.unsigned_transfer.sender)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender short address"))?;
|
||||||
tokio::io::Error::other("Invalid sender short address")
|
|
||||||
})?;
|
|
||||||
let receiver_bytes = Wallet::short_address_to_bytes(&self.unsigned_transfer.receiver)
|
let receiver_bytes = Wallet::short_address_to_bytes(&self.unsigned_transfer.receiver)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| tokio::io::Error::other("Invalid receiver short address"))?;
|
||||||
tokio::io::Error::other("Invalid receiver short address",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
cursor.write_all(&sender_bytes).await?;
|
cursor.write_all(&sender_bytes).await?;
|
||||||
cursor.write_all(&receiver_bytes).await?;
|
cursor.write_all(&receiver_bytes).await?;
|
||||||
cursor
|
cursor
|
||||||
|
|
@ -147,8 +149,7 @@ impl TransferTransaction {
|
||||||
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
pub async fn from_bytes(txtype: u8, bytes: &[u8]) -> tokio::io::Result<Self> {
|
||||||
// The block parser already consumed the transaction type byte.
|
// The block parser already consumed the transaction type byte.
|
||||||
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
if bytes.len() != Self::BYTE_LENGTH - 1 {
|
||||||
return Err(tokio::io::Error::other("Invalid Byte Count",
|
return Err(tokio::io::Error::other("Invalid Byte Count"));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the remaining fixed-width transfer bytes.
|
// Read the remaining fixed-width transfer bytes.
|
||||||
|
|
@ -168,18 +169,14 @@ impl TransferTransaction {
|
||||||
// Decode the sender short address.
|
// Decode the sender short address.
|
||||||
let mut sender_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut sender_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut sender_bytes).await?;
|
cursor.read_exact(&mut sender_bytes).await?;
|
||||||
let sender = Wallet::bytes_to_short_address(&sender_bytes).ok_or_else(|| {
|
let sender = Wallet::bytes_to_short_address(&sender_bytes)
|
||||||
tokio::io::Error::other("Invalid sender short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode the receiver short address.
|
// Decode the receiver short address.
|
||||||
let mut receiver_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut receiver_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut receiver_bytes).await?;
|
cursor.read_exact(&mut receiver_bytes).await?;
|
||||||
let receiver = Wallet::bytes_to_short_address(&receiver_bytes).ok_or_else(|| {
|
let receiver = Wallet::bytes_to_short_address(&receiver_bytes)
|
||||||
tokio::io::Error::other("Invalid receiver short address bytes",
|
.ok_or_else(|| tokio::io::Error::other("Invalid receiver short address bytes"))?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Decode fee and signature.
|
// Decode fee and signature.
|
||||||
let txfee = cursor.read_u64_le().await?;
|
let txfee = cursor.read_u64_le().await?;
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ use crate::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)] // 57 bytes
|
#[derive(Debug, Serialize, Clone)] // 57 bytes
|
||||||
pub struct UnsignedVanityAddressTransaction {
|
pub struct UnsignedVanityAddressTransaction {
|
||||||
pub txtype: u8, // 1 byte transaction type, should be 12
|
pub txtype: u8, // 1 byte transaction type, should be 12
|
||||||
pub timestamp: u32, // 4 bytes transaction timestamp
|
pub timestamp: u32, // 4 bytes transaction timestamp
|
||||||
pub address: String, // 22 bytes real short address receiving the vanity mapping
|
pub address: String, // 22 bytes real short address receiving the vanity mapping
|
||||||
pub vanity_address: String, // 22 bytes vanity short address being registered
|
pub vanity_address: String, // 22 bytes vanity short address being registered
|
||||||
pub txfee: u64, // 8 bytes fee paid for vanity registration
|
pub txfee: u64, // 8 bytes fee paid for vanity registration
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)] // 723 bytes
|
#[derive(Debug, Serialize, Clone)] // 723 bytes
|
||||||
|
|
@ -23,8 +23,12 @@ pub struct VanityAddressTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VanityAddressTransaction {
|
impl VanityAddressTransaction {
|
||||||
pub const BYTE_LENGTH: usize =
|
pub const BYTE_LENGTH: usize = 1
|
||||||
1 + 4 + Wallet::SHORT_ADDRESS_BYTES_LENGTH + Wallet::SHORT_ADDRESS_BYTES_LENGTH + 8 + Wallet::SIGNATURE_LENGTH;
|
+ 4
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ 8
|
||||||
|
+ Wallet::SIGNATURE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsignedVanityAddressTransaction {
|
impl UnsignedVanityAddressTransaction {
|
||||||
|
|
@ -99,10 +103,9 @@ impl VanityAddressTransaction {
|
||||||
.write_all(&self.unsigned_vanity_address.timestamp.to_le_bytes())
|
.write_all(&self.unsigned_vanity_address.timestamp.to_le_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let address_bytes = Wallet::short_address_to_bytes(&self.unsigned_vanity_address.address)
|
let address_bytes =
|
||||||
.ok_or_else(|| {
|
Wallet::short_address_to_bytes(&self.unsigned_vanity_address.address)
|
||||||
tokio::io::Error::other("Invalid sender short address")
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender short address"))?;
|
||||||
})?;
|
|
||||||
// Vanity addresses use the same 22-byte width as short addresses
|
// Vanity addresses use the same 22-byte width as short addresses
|
||||||
// but are encoded through the vanity-specific byte conversion.
|
// but are encoded through the vanity-specific byte conversion.
|
||||||
let vanity_bytes =
|
let vanity_bytes =
|
||||||
|
|
@ -135,15 +138,13 @@ impl VanityAddressTransaction {
|
||||||
|
|
||||||
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut address_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut address_bytes).await?;
|
cursor.read_exact(&mut address_bytes).await?;
|
||||||
let address = Wallet::bytes_to_short_address(&address_bytes).ok_or_else(|| {
|
let address = Wallet::bytes_to_short_address(&address_bytes)
|
||||||
tokio::io::Error::other("Invalid sender short address bytes")
|
.ok_or_else(|| tokio::io::Error::other("Invalid sender short address bytes"))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut vanity_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
let mut vanity_bytes = vec![0; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
cursor.read_exact(&mut vanity_bytes).await?;
|
cursor.read_exact(&mut vanity_bytes).await?;
|
||||||
let vanity_address = Wallet::bytes_to_vanity_address(&vanity_bytes).ok_or_else(|| {
|
let vanity_address = Wallet::bytes_to_vanity_address(&vanity_bytes)
|
||||||
tokio::io::Error::other("Invalid vanity short address bytes")
|
.ok_or_else(|| tokio::io::Error::other("Invalid vanity short address bytes"))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let txfee = cursor.read_u64_le().await?;
|
let txfee = cursor.read_u64_le().await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,7 @@ pub async fn hex_to_u64(hex_string: &str) -> Result<u64, String> {
|
||||||
pub fn binary_to_string(binary_data: Vec<u8>) -> String {
|
pub fn binary_to_string(binary_data: Vec<u8>) -> String {
|
||||||
match String::from_utf8(binary_data.clone()) {
|
match String::from_utf8(binary_data.clone()) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => {
|
Err(_) => "Invalid UTF-8 Data".to_string(),
|
||||||
"Invalid UTF-8 Data".to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::encode;
|
use crate::encode;
|
||||||
|
use crate::ripemd::Digest as RipemdDigest;
|
||||||
use crate::Digest;
|
use crate::Digest;
|
||||||
use crate::Output;
|
use crate::Output;
|
||||||
use crate::Ripemd160;
|
use crate::Ripemd160;
|
||||||
use crate::Skein256;
|
use crate::Skein256;
|
||||||
use crate::Skein512;
|
use crate::Skein512;
|
||||||
use crate::ripemd::Digest as RipemdDigest;
|
|
||||||
|
|
||||||
pub fn skein_128_hash_bytes(data: &[u8]) -> String {
|
pub fn skein_128_hash_bytes(data: &[u8]) -> String {
|
||||||
// Contractless 128-bit hashes are Skein256 hashes reduced to 16 bytes.
|
// Contractless 128-bit hashes are Skein256 hashes reduced to 16 bytes.
|
||||||
|
|
|
||||||
|
|
@ -159,8 +159,7 @@ impl Settings {
|
||||||
config_dir,
|
config_dir,
|
||||||
),
|
),
|
||||||
log_path: Self::expand(
|
log_path: Self::expand(
|
||||||
conf.get_from(Some("Paths"), "LOG_PATH")
|
conf.get_from(Some("Paths"), "LOG_PATH").unwrap_or("./logs"),
|
||||||
.unwrap_or("./logs"),
|
|
||||||
config_dir,
|
config_dir,
|
||||||
),
|
),
|
||||||
log_level: conf
|
log_level: conf
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,22 @@ use crate::blocks::block::{Block, UnminedBlock};
|
||||||
use crate::blocks::genesis::{GenesisTransaction, UnsignedGenesisTransaction};
|
use crate::blocks::genesis::{GenesisTransaction, UnsignedGenesisTransaction};
|
||||||
use crate::common::check_genesis::genesis_checkup;
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
use crate::common::types::{Transaction, GENESIS_BLOCK_HASH};
|
use crate::common::types::{Transaction, GENESIS_BLOCK_HASH};
|
||||||
use crate::miner::flag::{is_mining_running, is_mining_stop_requested, is_normal_mode, set_mining_state, MiningState};
|
use crate::log::{error, info};
|
||||||
|
use crate::miner::flag::{
|
||||||
|
is_mining_running, is_mining_stop_requested, is_normal_mode, set_mining_state, MiningState,
|
||||||
|
};
|
||||||
use crate::records::memory::connections::outgoing_connection_count;
|
use crate::records::memory::connections::outgoing_connection_count;
|
||||||
use crate::records::memory::response_channels::Command;
|
use crate::records::memory::response_channels::Command;
|
||||||
use crate::records::record_chain::save::save_block;
|
use crate::records::record_chain::save::save_block;
|
||||||
use crate::records::record_chain::structs::{SaveBlockParams, SaveType};
|
use crate::records::record_chain::structs::{SaveBlockParams, SaveType};
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::sleep;
|
||||||
use crate::verifications::verification_service::VerificationService;
|
use crate::verifications::verification_service::VerificationService;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::log::{error, info};
|
|
||||||
use crate::sled::Db;
|
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::sleep;
|
|
||||||
use crate::Utc;
|
use crate::Utc;
|
||||||
|
|
||||||
pub async fn create_genesis_transaction(
|
pub async fn create_genesis_transaction(
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::common::check_genesis::genesis_checkup;
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::timeout;
|
||||||
|
use crate::torrent::structs::Torrent;
|
||||||
use crate::torrent::torrenting_system::torrent_requests::{
|
use crate::torrent::torrenting_system::torrent_requests::{
|
||||||
handle_response_and_save_torrent, send_request_torrent_message,
|
handle_response_and_save_torrent, send_request_torrent_message,
|
||||||
};
|
};
|
||||||
use crate::sled::Db;
|
|
||||||
use crate::torrent::structs::Torrent;
|
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
use crate::timeout;
|
|
||||||
|
|
||||||
pub async fn create_genesis_block(
|
pub async fn create_genesis_block(
|
||||||
local_height: u32,
|
local_height: u32,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,24 @@
|
||||||
use crate::log::info;
|
use crate::common::skein::skein_128_hash_bytes;
|
||||||
|
use crate::log::{info, warn};
|
||||||
use crate::miner::flag::begin_reorg_lock;
|
use crate::miner::flag::begin_reorg_lock;
|
||||||
|
use crate::orphans::replay_errors::should_retry_staged_candidate;
|
||||||
use crate::orphans::structs::{OrphanCheckup, UndoTransactions};
|
use crate::orphans::structs::{OrphanCheckup, UndoTransactions};
|
||||||
use crate::orphans::undo_block_transactions::undo_transactions;
|
use crate::orphans::undo_block_transactions::undo_transactions;
|
||||||
use crate::records::memory::torrent_status::{
|
use crate::records::memory::torrent_status::{
|
||||||
get_torrent_status, set_torrent_status, TorrentStatus,
|
get_torrent_status, set_torrent_status, TorrentStatus,
|
||||||
};
|
};
|
||||||
use crate::torrent::structs::Torrent;
|
use crate::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
||||||
|
use crate::records::unpack_block::unpack_header::load_block_header;
|
||||||
|
use crate::torrent::structs::{DownloadSave, Torrent};
|
||||||
|
use crate::torrent::torrenting_system::create_file::combine_pieces;
|
||||||
|
use crate::torrent::torrenting_system::download_pieces::download_block_pieces;
|
||||||
use crate::torrent::torrenting_system::save_torrent::{
|
use crate::torrent::torrenting_system::save_torrent::{
|
||||||
list_staged_torrents_for_height, read_staged_torrent,
|
list_staged_torrents_for_height, read_staged_torrent,
|
||||||
};
|
};
|
||||||
|
use crate::torrent::torrenting_system::temp_database_storage::remove_block_pieces_from_db;
|
||||||
|
use crate::torrent::torrenting_system::torrent_map::create_torrent_map;
|
||||||
use crate::torrent::unpack_local_torrent::load_torrent;
|
use crate::torrent::unpack_local_torrent::load_torrent;
|
||||||
|
use crate::verifications::verification_service::global_verification_service;
|
||||||
|
|
||||||
async fn staged_candidates_for_height(height: u32) -> Vec<Torrent> {
|
async fn staged_candidates_for_height(height: u32) -> Vec<Torrent> {
|
||||||
let mut candidates = Vec::new();
|
let mut candidates = Vec::new();
|
||||||
|
|
@ -42,12 +51,12 @@ fn torrent_beats(left: &Torrent, right: &Torrent) -> bool {
|
||||||
.is_lt()
|
.is_lt()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn best_competing_candidate(
|
async fn ordered_competing_candidates(
|
||||||
height: u32,
|
height: u32,
|
||||||
local_torrent: &Torrent,
|
local_torrent: &Torrent,
|
||||||
candidates: &[Torrent],
|
candidates: &[Torrent],
|
||||||
) -> Option<Torrent> {
|
) -> Vec<Torrent> {
|
||||||
let mut preferred: Option<Torrent> = None;
|
let mut ordered = Vec::new();
|
||||||
|
|
||||||
for torrent in candidates {
|
for torrent in candidates {
|
||||||
// Identical info hashes are the same block candidate, not a competing fork.
|
// Identical info hashes are the same block candidate, not a competing fork.
|
||||||
|
|
@ -64,19 +73,83 @@ async fn best_competing_candidate(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &preferred {
|
ordered.push(torrent.clone());
|
||||||
Some(current_torrent) => {
|
}
|
||||||
// Among candidates that have not been ruled out, choose the
|
|
||||||
// same deterministic winner the block fight uses.
|
// Among candidates that have not been ruled out, choose the same
|
||||||
if torrent_beats(torrent, current_torrent) {
|
// deterministic order the block fight uses.
|
||||||
preferred = Some(torrent.clone());
|
ordered.sort_by(|a, b| {
|
||||||
}
|
a.info
|
||||||
}
|
.timestamp
|
||||||
None => preferred = Some(torrent.clone()),
|
.cmp(&b.info.timestamp)
|
||||||
|
.then(a.info.nonce.cmp(&b.info.nonce))
|
||||||
|
.then(a.info.vrf.cmp(&b.info.vrf))
|
||||||
|
});
|
||||||
|
ordered
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cleanup_candidate_pieces(db: &crate::sled::Db, height: u32, torrent: &Torrent) {
|
||||||
|
let _ = remove_block_pieces_from_db(db, height, &torrent.info.info_hash).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn candidate_attaches_before_rollback(
|
||||||
|
params: &OrphanCheckup,
|
||||||
|
height: u32,
|
||||||
|
torrent: &Torrent,
|
||||||
|
wallet_key: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
// Metadata may choose a candidate, but only downloaded block bytes can
|
||||||
|
// prove the rollback is safe.
|
||||||
|
torrent.verify(height, ¶ms.db, wallet_key).await?;
|
||||||
|
|
||||||
|
let verification_service = global_verification_service()
|
||||||
|
.ok_or_else(|| "Verification service not initialized".to_string())?;
|
||||||
|
let torrent_map = create_torrent_map(torrent).await?;
|
||||||
|
let download_save_params = DownloadSave {
|
||||||
|
torrent_map,
|
||||||
|
torrent: torrent.clone(),
|
||||||
|
staged_path: String::new(),
|
||||||
|
block_number: height,
|
||||||
|
allow_during_reorg: true,
|
||||||
|
allow_historical: true,
|
||||||
|
db: params.db.clone(),
|
||||||
|
verification_service: std::sync::Arc::new(verification_service),
|
||||||
|
map: params.map.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
download_block_pieces(download_save_params).await?;
|
||||||
|
let result = combine_pieces(¶ms.db, height, &torrent.info.info_hash).await?;
|
||||||
|
|
||||||
|
if result.len() != torrent.info.length as usize {
|
||||||
|
cleanup_candidate_pieces(¶ms.db, height, torrent).await;
|
||||||
|
return Err("Downloaded candidate length does not match torrent metadata.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if skein_128_hash_bytes(&result) != torrent.info.info_hash {
|
||||||
|
cleanup_candidate_pieces(¶ms.db, height, torrent).await;
|
||||||
|
return Err("Hash validation failed for complete block".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let loaded_block = load_block_from_binary(&result)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to load block from binary: {err}"))?;
|
||||||
|
let header_hash = loaded_block.vrf_block.hash().await;
|
||||||
|
if header_hash != torrent.info.block_hash {
|
||||||
|
cleanup_candidate_pieces(¶ms.db, height, torrent).await;
|
||||||
|
return Err("Candidate header hash does not match torrent metadata.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if height > 0 {
|
||||||
|
let parent_height = height - 1;
|
||||||
|
let parent_header = load_block_header(parent_height).await?;
|
||||||
|
let parent_hash = parent_header.hash().await;
|
||||||
|
if loaded_block.vrf_block.unmined_block.previous_hash != parent_hash {
|
||||||
|
cleanup_candidate_pieces(¶ms.db, height, torrent).await;
|
||||||
|
return Err("Incorrect previous_block_hash.".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preferred
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn checkup(params: OrphanCheckup, wallet_key: &str) -> Result<(), String> {
|
pub async fn checkup(params: OrphanCheckup, wallet_key: &str) -> Result<(), String> {
|
||||||
|
|
@ -89,41 +162,69 @@ pub async fn checkup(params: OrphanCheckup, wallet_key: &str) -> Result<(), Stri
|
||||||
let local_torrent = load_torrent(¶ms.db, height).await?;
|
let local_torrent = load_torrent(¶ms.db, height).await?;
|
||||||
let staged_candidates = staged_candidates_for_height(height).await;
|
let staged_candidates = staged_candidates_for_height(height).await;
|
||||||
|
|
||||||
if let Some(competing_torrent) =
|
let ordered_candidates =
|
||||||
best_competing_candidate(height, &local_torrent, &staged_candidates).await
|
ordered_competing_candidates(height, &local_torrent, &staged_candidates).await;
|
||||||
{
|
|
||||||
let competing_info_hash = competing_torrent.info.info_hash.clone();
|
|
||||||
// If the best staged torrent wins this height, rollback starts
|
|
||||||
// here and replay rebuilds forward from staged candidates.
|
|
||||||
let undo_transactions_params = UndoTransactions {
|
|
||||||
start_height: height,
|
|
||||||
db: params.db.clone(),
|
|
||||||
stream: params.stream.clone(),
|
|
||||||
map: params.map.clone(),
|
|
||||||
node_syncing: params.node_syncing,
|
|
||||||
connections_key: params.connections_key.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if torrent_beats(&competing_torrent, &local_torrent) {
|
for competing_torrent in ordered_candidates {
|
||||||
set_torrent_status(height, &competing_info_hash, TorrentStatus::Valid).await;
|
let competing_info_hash = competing_torrent.info.info_hash.clone();
|
||||||
if !params.node_syncing {
|
|
||||||
begin_reorg_lock().await;
|
if !torrent_beats(&competing_torrent, &local_torrent) {
|
||||||
|
// The local block remains the winner at this height. Since
|
||||||
|
// candidates are sorted best-first, every remaining staged
|
||||||
|
// competitor has also lost to the local block.
|
||||||
|
for staged_torrent in &staged_candidates {
|
||||||
|
if staged_torrent.info.info_hash != local_torrent.info.info_hash {
|
||||||
|
set_torrent_status(
|
||||||
|
height,
|
||||||
|
&staged_torrent.info.info_hash,
|
||||||
|
TorrentStatus::Invalid,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
info!("[orphan] adopting competing staged chain from height {height}");
|
break;
|
||||||
undo_transactions(undo_transactions_params, wallet_key).await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The local block remains the winner at this height, so every
|
match candidate_attaches_before_rollback(
|
||||||
// staged competitor for the same height has now been checked.
|
¶ms,
|
||||||
for staged_torrent in staged_candidates {
|
height,
|
||||||
if staged_torrent.info.info_hash != local_torrent.info.info_hash {
|
&competing_torrent,
|
||||||
set_torrent_status(
|
wallet_key,
|
||||||
height,
|
)
|
||||||
&staged_torrent.info.info_hash,
|
.await
|
||||||
TorrentStatus::Invalid,
|
{
|
||||||
)
|
Ok(()) => {
|
||||||
.await;
|
let undo_transactions_params = UndoTransactions {
|
||||||
|
start_height: height,
|
||||||
|
db: params.db.clone(),
|
||||||
|
stream: params.stream.clone(),
|
||||||
|
map: params.map.clone(),
|
||||||
|
node_syncing: params.node_syncing,
|
||||||
|
connections_key: params.connections_key.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
set_torrent_status(height, &competing_info_hash, TorrentStatus::Valid).await;
|
||||||
|
if !params.node_syncing {
|
||||||
|
begin_reorg_lock().await;
|
||||||
|
}
|
||||||
|
info!("[orphan] adopting proven staged chain from height {height}");
|
||||||
|
undo_transactions(undo_transactions_params, wallet_key).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let status = if should_retry_staged_candidate(&err) {
|
||||||
|
TorrentStatus::Pending
|
||||||
|
} else {
|
||||||
|
TorrentStatus::Invalid
|
||||||
|
};
|
||||||
|
set_torrent_status(height, &competing_info_hash, status).await;
|
||||||
|
warn!(
|
||||||
|
"[orphan] staged candidate failed pre-rollback proof: height={height} err={err}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if status == TorrentStatus::Pending {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,10 @@ pub fn should_retry_staged_candidate(error: &str) -> bool {
|
||||||
|| error.contains("piece not found")
|
|| error.contains("piece not found")
|
||||||
|| error.contains("Requested candidate not found")
|
|| error.contains("Requested candidate not found")
|
||||||
|| error.contains("Block not found")
|
|| error.contains("Block not found")
|
||||||
|
|| (error.contains("Block ") && error.contains(" not found"))
|
||||||
|| error.contains("Timed out waiting for piece")
|
|| error.contains("Timed out waiting for piece")
|
||||||
|| error.contains("Timed out waiting for replacement torrent")
|
|| error.contains("Timed out waiting for replacement torrent")
|
||||||
|| error.contains("No replacement torrent received")
|
|| error.contains("No replacement torrent received")
|
||||||
|| error.contains("Piece reply channel closed")
|
|| error.contains("Piece reply channel closed")
|
||||||
|
|| error.contains("Replay waiting for block pieces")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::orphans::structs::UndoTransactions;
|
|
||||||
use crate::orphans::replay_errors::should_retry_staged_candidate;
|
use crate::orphans::replay_errors::should_retry_staged_candidate;
|
||||||
|
use crate::orphans::structs::UndoTransactions;
|
||||||
|
use crate::orphans::torrent_candidates::hydrate_torrent_candidates;
|
||||||
use crate::records::block_height::get_block_height::get_height;
|
use crate::records::block_height::get_block_height::get_height;
|
||||||
use crate::records::memory::response_channels::reserve_entry;
|
use crate::records::memory::response_channels::reserve_entry;
|
||||||
use crate::records::memory::torrent_status::{
|
use crate::records::memory::torrent_status::{
|
||||||
|
|
@ -13,6 +14,7 @@ use crate::torrent::torrenting_system::torrent_requests::{
|
||||||
handle_response_and_save_torrent, send_request_torrent_message,
|
handle_response_and_save_torrent, send_request_torrent_message,
|
||||||
};
|
};
|
||||||
use crate::{timeout, Duration};
|
use crate::{timeout, Duration};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
pub async fn save_new_blocks(
|
pub async fn save_new_blocks(
|
||||||
params: &UndoTransactions,
|
params: &UndoTransactions,
|
||||||
|
|
@ -22,6 +24,7 @@ pub async fn save_new_blocks(
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// after rollback, request and save each remote block from the
|
// after rollback, request and save each remote block from the
|
||||||
// divergence point up to the height we need to restore
|
// divergence point up to the height we need to restore
|
||||||
|
let mut hydrated_heights = HashSet::new();
|
||||||
loop {
|
loop {
|
||||||
let mut resolved_from_staging = false;
|
let mut resolved_from_staging = false;
|
||||||
let staged_candidates = list_staged_torrents_for_height(true_start_height).await?;
|
let staged_candidates = list_staged_torrents_for_height(true_start_height).await?;
|
||||||
|
|
@ -101,12 +104,7 @@ pub async fn save_new_blocks(
|
||||||
} else {
|
} else {
|
||||||
TorrentStatus::Invalid
|
TorrentStatus::Invalid
|
||||||
};
|
};
|
||||||
set_torrent_status(
|
set_torrent_status(true_start_height, &torrent_info_hash, status).await;
|
||||||
true_start_height,
|
|
||||||
&torrent_info_hash,
|
|
||||||
status,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,6 +122,22 @@ pub async fn save_new_blocks(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hydrated_heights.insert(true_start_height) {
|
||||||
|
let imported = hydrate_torrent_candidates(
|
||||||
|
params.stream.clone(),
|
||||||
|
params.map.clone(),
|
||||||
|
params.connections_key.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if imported > 0 {
|
||||||
|
// Peer candidate hydration can add staged torrents that
|
||||||
|
// are not canonical on the peer yet. Restart this height
|
||||||
|
// so those candidates are tried before canonical fallback.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// No staged candidate worked, so request the replacement torrent
|
// No staged candidate worked, so request the replacement torrent
|
||||||
// directly from the connected peer.
|
// directly from the connected peer.
|
||||||
let (hashmap_key, _save_tx, save_rx) = reserve_entry(params.map.clone()).await;
|
let (hashmap_key, _save_tx, save_rx) = reserve_entry(params.map.clone()).await;
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,8 @@ pub async fn update_snapshot(db: &Db, current_height: u32) -> Result<(), String>
|
||||||
let hash = header.hash().await;
|
let hash = header.hash().await;
|
||||||
let value = format!("{snapshot_height}:{hash}");
|
let value = format!("{snapshot_height}:{hash}");
|
||||||
let key = b"snapshot";
|
let key = b"snapshot";
|
||||||
db.insert(key, value.as_bytes()).map_err(|e| {
|
db.insert(key, value.as_bytes())
|
||||||
format!(
|
.map_err(|e| format!("Failed to store snapshot at height {snapshot_height}: {e}"))?;
|
||||||
"Failed to store snapshot at height {snapshot_height}: {e}"
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +94,9 @@ pub async fn snapshot_verified(params: UndoTransactions, wallet_key: &str) -> bo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unable to verify remote snapshot torrent at height {snap_height}");
|
error!(
|
||||||
|
"Unable to verify remote snapshot torrent at height {snap_height}"
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@ async fn replay_staged_torrents(params: &OrphanCheckup2, wallet_key: &str) -> Re
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut advanced_height = false;
|
let mut advanced_height = false;
|
||||||
|
let mut retryable_pending = false;
|
||||||
for torrent in ordered_candidates {
|
for torrent in ordered_candidates {
|
||||||
let torrent_info_hash = torrent.info.info_hash.clone();
|
let torrent_info_hash = torrent.info.info_hash.clone();
|
||||||
|
|
||||||
|
|
@ -137,6 +138,7 @@ async fn replay_staged_torrents(params: &OrphanCheckup2, wallet_key: &str) -> Re
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if should_retry_staged_candidate(&err) {
|
if should_retry_staged_candidate(&err) {
|
||||||
|
retryable_pending = true;
|
||||||
// Piece availability is not proof that the candidate
|
// Piece availability is not proof that the candidate
|
||||||
// lost the block fight; leave it pending so a later
|
// lost the block fight; leave it pending so a later
|
||||||
// orphan pass can retry after more peers stage it.
|
// orphan pass can retry after more peers stage it.
|
||||||
|
|
@ -163,6 +165,11 @@ async fn replay_staged_torrents(params: &OrphanCheckup2, wallet_key: &str) -> Re
|
||||||
if !advanced_height {
|
if !advanced_height {
|
||||||
// Every staged candidate for the current expected height was
|
// Every staged candidate for the current expected height was
|
||||||
// exhausted without extending the chain, so stop replay here.
|
// exhausted without extending the chain, so stop replay here.
|
||||||
|
if retryable_pending {
|
||||||
|
return Err(format!(
|
||||||
|
"Replay waiting for block pieces at height {expected_height}"
|
||||||
|
));
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,9 +203,16 @@ pub async fn sync_checkup(params: OrphanCheckup2, wallet_key: &str) -> Result<()
|
||||||
if !snapshot_verified(undo_transactions_params, wallet_key).await {
|
if !snapshot_verified(undo_transactions_params, wallet_key).await {
|
||||||
// A snapshot rollback already happened, so replay staged torrents and
|
// A snapshot rollback already happened, so replay staged torrents and
|
||||||
// exit instead of running the near-tip rules against stale heights.
|
// exit instead of running the near-tip rules against stale heights.
|
||||||
|
let mut replay_waiting = false;
|
||||||
match replay_staged_torrents(¶ms, wallet_key).await {
|
match replay_staged_torrents(¶ms, wallet_key).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) => error!("[orphan] staged torrent replay error: {err}"),
|
Err(err) => {
|
||||||
|
replay_waiting = should_retry_staged_candidate(&err);
|
||||||
|
error!("[orphan] staged torrent replay error: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if replay_waiting && !params.node_syncing {
|
||||||
|
return Err("orphan replay is waiting for block data".to_string());
|
||||||
}
|
}
|
||||||
if !params.node_syncing {
|
if !params.node_syncing {
|
||||||
end_reorg_lock();
|
end_reorg_lock();
|
||||||
|
|
@ -218,14 +232,33 @@ pub async fn sync_checkup(params: OrphanCheckup2, wallet_key: &str) -> Result<()
|
||||||
};
|
};
|
||||||
deep_sync_rollback(checkup_params.clone(), wallet_key).await;
|
deep_sync_rollback(checkup_params.clone(), wallet_key).await;
|
||||||
|
|
||||||
|
let mut replay_waiting = false;
|
||||||
|
let height_before_window_check = get_height(¶ms.db);
|
||||||
match orphan_window_check(checkup_params, wallet_key).await {
|
match orphan_window_check(checkup_params, wallet_key).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) => error!("[orphan] orphan window check error: {err}"),
|
Err(err) => {
|
||||||
|
if should_retry_staged_candidate(&err)
|
||||||
|
&& get_height(¶ms.db) < height_before_window_check
|
||||||
|
{
|
||||||
|
replay_waiting = true;
|
||||||
|
}
|
||||||
|
error!("[orphan] orphan window check error: {err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let height_before_replay = get_height(¶ms.db);
|
||||||
match replay_staged_torrents(¶ms, wallet_key).await {
|
match replay_staged_torrents(¶ms, wallet_key).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) => error!("[orphan] staged torrent replay error: {err}"),
|
Err(err) => {
|
||||||
|
replay_waiting |= should_retry_staged_candidate(&err);
|
||||||
|
error!("[orphan] staged torrent replay error: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if get_height(¶ms.db) > height_before_replay {
|
||||||
|
replay_waiting = false;
|
||||||
|
}
|
||||||
|
if replay_waiting && !params.node_syncing {
|
||||||
|
return Err("orphan replay is waiting for block data".to_string());
|
||||||
}
|
}
|
||||||
if !params.node_syncing {
|
if !params.node_syncing {
|
||||||
end_reorg_lock();
|
end_reorg_lock();
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,17 @@ pub async fn undo_transactions(params: UndoTransactions, wallet_key: &str) -> Re
|
||||||
for transaction in transactions.into_iter().rev() {
|
for transaction in transactions.into_iter().rev() {
|
||||||
match transaction {
|
match transaction {
|
||||||
Transaction::Rewards(rewards_tx) => {
|
Transaction::Rewards(rewards_tx) => {
|
||||||
undo_rewards_transaction(rewards_tx, &mining_receiver, ¶ms.db, current_height).await
|
undo_rewards_transaction(
|
||||||
|
rewards_tx,
|
||||||
|
&mining_receiver,
|
||||||
|
¶ms.db,
|
||||||
|
current_height,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
Transaction::Transfer(transfer_tx) => {
|
Transaction::Transfer(transfer_tx) => {
|
||||||
undo_transfer_transaction(transfer_tx.clone(), &mining_receiver, ¶ms.db).await;
|
undo_transfer_transaction(transfer_tx.clone(), &mining_receiver, ¶ms.db)
|
||||||
|
.await;
|
||||||
rolled_back_transactions.push(Transaction::Transfer(transfer_tx));
|
rolled_back_transactions.push(Transaction::Transfer(transfer_tx));
|
||||||
}
|
}
|
||||||
Transaction::Burn(burn_tx) => {
|
Transaction::Burn(burn_tx) => {
|
||||||
|
|
@ -50,20 +57,35 @@ pub async fn undo_transactions(params: UndoTransactions, wallet_key: &str) -> Re
|
||||||
rolled_back_transactions.push(Transaction::Burn(burn_tx));
|
rolled_back_transactions.push(Transaction::Burn(burn_tx));
|
||||||
}
|
}
|
||||||
Transaction::Token(create_token_tx) => {
|
Transaction::Token(create_token_tx) => {
|
||||||
undo_create_token_transaction(create_token_tx.clone(), &mining_receiver, ¶ms.db)
|
undo_create_token_transaction(
|
||||||
.await;
|
create_token_tx.clone(),
|
||||||
|
&mining_receiver,
|
||||||
|
¶ms.db,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
rolled_back_transactions.push(Transaction::Token(create_token_tx));
|
rolled_back_transactions.push(Transaction::Token(create_token_tx));
|
||||||
}
|
}
|
||||||
Transaction::IssueToken(issue_token_tx) => {
|
Transaction::IssueToken(issue_token_tx) => {
|
||||||
undo_issue_token_transaction(issue_token_tx.clone(), &mining_receiver, ¶ms.db).await;
|
undo_issue_token_transaction(
|
||||||
|
issue_token_tx.clone(),
|
||||||
|
&mining_receiver,
|
||||||
|
¶ms.db,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
rolled_back_transactions.push(Transaction::IssueToken(issue_token_tx));
|
rolled_back_transactions.push(Transaction::IssueToken(issue_token_tx));
|
||||||
}
|
}
|
||||||
Transaction::Nft(create_nft_tx) => {
|
Transaction::Nft(create_nft_tx) => {
|
||||||
undo_create_nft_transaction(create_nft_tx.clone(), &mining_receiver, ¶ms.db).await;
|
undo_create_nft_transaction(
|
||||||
|
create_nft_tx.clone(),
|
||||||
|
&mining_receiver,
|
||||||
|
¶ms.db,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
rolled_back_transactions.push(Transaction::Nft(create_nft_tx));
|
rolled_back_transactions.push(Transaction::Nft(create_nft_tx));
|
||||||
}
|
}
|
||||||
Transaction::Marketing(marketing_tx) => {
|
Transaction::Marketing(marketing_tx) => {
|
||||||
undo_marketing_transaction(marketing_tx.clone(), &mining_receiver, ¶ms.db).await;
|
undo_marketing_transaction(marketing_tx.clone(), &mining_receiver, ¶ms.db)
|
||||||
|
.await;
|
||||||
rolled_back_transactions.push(Transaction::Marketing(marketing_tx));
|
rolled_back_transactions.push(Transaction::Marketing(marketing_tx));
|
||||||
}
|
}
|
||||||
Transaction::Swap(swap_tx) => {
|
Transaction::Swap(swap_tx) => {
|
||||||
|
|
@ -71,15 +93,22 @@ pub async fn undo_transactions(params: UndoTransactions, wallet_key: &str) -> Re
|
||||||
rolled_back_transactions.push(Transaction::Swap(swap_tx));
|
rolled_back_transactions.push(Transaction::Swap(swap_tx));
|
||||||
}
|
}
|
||||||
Transaction::Lender(loan_tx) => {
|
Transaction::Lender(loan_tx) => {
|
||||||
undo_loan_creation_transaction(loan_tx.clone(), &mining_receiver, ¶ms.db).await;
|
undo_loan_creation_transaction(loan_tx.clone(), &mining_receiver, ¶ms.db)
|
||||||
|
.await;
|
||||||
rolled_back_transactions.push(Transaction::Lender(loan_tx));
|
rolled_back_transactions.push(Transaction::Lender(loan_tx));
|
||||||
}
|
}
|
||||||
Transaction::Borrower(borrower_tx) => {
|
Transaction::Borrower(borrower_tx) => {
|
||||||
undo_borrower_transaction(borrower_tx.clone(), &mining_receiver, ¶ms.db).await?;
|
undo_borrower_transaction(borrower_tx.clone(), &mining_receiver, ¶ms.db)
|
||||||
|
.await?;
|
||||||
rolled_back_transactions.push(Transaction::Borrower(borrower_tx));
|
rolled_back_transactions.push(Transaction::Borrower(borrower_tx));
|
||||||
}
|
}
|
||||||
Transaction::Collateral(collateral_tx) => {
|
Transaction::Collateral(collateral_tx) => {
|
||||||
undo_collateral_transaction(collateral_tx.clone(), &mining_receiver, ¶ms.db).await?;
|
undo_collateral_transaction(
|
||||||
|
collateral_tx.clone(),
|
||||||
|
&mining_receiver,
|
||||||
|
¶ms.db,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
rolled_back_transactions.push(Transaction::Collateral(collateral_tx));
|
rolled_back_transactions.push(Transaction::Collateral(collateral_tx));
|
||||||
}
|
}
|
||||||
Transaction::Genesis(_) => {
|
Transaction::Genesis(_) => {
|
||||||
|
|
@ -87,10 +116,11 @@ pub async fn undo_transactions(params: UndoTransactions, wallet_key: &str) -> Re
|
||||||
// the requested rollback boundary is invalid.
|
// the requested rollback boundary is invalid.
|
||||||
return Err(
|
return Err(
|
||||||
"Genesis transaction cannot be undone by orphan rollback".to_string()
|
"Genesis transaction cannot be undone by orphan rollback".to_string()
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
Transaction::Vanity(vanity_tx) => {
|
Transaction::Vanity(vanity_tx) => {
|
||||||
undo_vanity_transaction(vanity_tx.clone(), &mining_receiver, ¶ms.db).await?;
|
undo_vanity_transaction(vanity_tx.clone(), &mining_receiver, ¶ms.db)
|
||||||
|
.await?;
|
||||||
rolled_back_transactions.push(Transaction::Vanity(vanity_tx));
|
rolled_back_transactions.push(Transaction::Vanity(vanity_tx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ use crate::blocks::transfer::TransferTransaction;
|
||||||
use crate::blocks::vanity::VanityAddressTransaction;
|
use crate::blocks::vanity::VanityAddressTransaction;
|
||||||
use crate::common::nft_assets::nft_asset_name;
|
use crate::common::nft_assets::nft_asset_name;
|
||||||
use crate::common::types::Transaction;
|
use crate::common::types::Transaction;
|
||||||
|
use crate::decode;
|
||||||
use crate::records::memory::mempool::{restore_processed_by_signatures, BASECOIN};
|
use crate::records::memory::mempool::{restore_processed_by_signatures, BASECOIN};
|
||||||
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
|
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
|
||||||
use crate::decode;
|
|
||||||
|
|
||||||
async fn restore_if_spendable<F, Fut>(signatures: &[String], spendable: bool, insert: F)
|
async fn restore_if_spendable<F, Fut>(signatures: &[String], spendable: bool, insert: F)
|
||||||
where
|
where
|
||||||
|
|
@ -107,8 +107,14 @@ pub async fn restore_create_nft(transaction: &CreateNftTransaction, db: &Db) {
|
||||||
|
|
||||||
pub async fn restore_marketing(transaction: &MarketingTransaction, db: &Db) {
|
pub async fn restore_marketing(transaction: &MarketingTransaction, db: &Db) {
|
||||||
let marketing = &transaction.unsigned_marketing;
|
let marketing = &transaction.unsigned_marketing;
|
||||||
let spendable =
|
let spendable = balance_checkup(
|
||||||
balance_checkup(db, 0, marketing.txfee, BASECOIN.clone(), &marketing.advertiser).await;
|
db,
|
||||||
|
0,
|
||||||
|
marketing.txfee,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
&marketing.advertiser,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let signature = transaction.signature.clone();
|
let signature = transaction.signature.clone();
|
||||||
|
|
||||||
restore_if_spendable(&[signature], spendable, || async {
|
restore_if_spendable(&[signature], spendable, || async {
|
||||||
|
|
@ -123,31 +129,53 @@ pub async fn restore_swap(transaction: &SwapTransaction, db: &Db) {
|
||||||
let asset2 = nft_asset_name(&swap.ticker2, swap.nft_series2);
|
let asset2 = nft_asset_name(&swap.ticker2, swap.nft_series2);
|
||||||
let value1 = swap.value1.saturating_add(swap.tip1);
|
let value1 = swap.value1.saturating_add(swap.tip1);
|
||||||
let value2 = swap.value2.saturating_add(swap.tip2);
|
let value2 = swap.value2.saturating_add(swap.tip2);
|
||||||
let sender1_spendable =
|
let sender1_spendable = balance_checkup(db, value1, swap.txfee1, asset1, &swap.sender1).await;
|
||||||
balance_checkup(db, value1, swap.txfee1, asset1, &swap.sender1).await;
|
let sender2_spendable = balance_checkup(db, value2, swap.txfee2, asset2, &swap.sender2).await;
|
||||||
let sender2_spendable =
|
let signatures = vec![
|
||||||
balance_checkup(db, value2, swap.txfee2, asset2, &swap.sender2).await;
|
transaction.signature1.clone(),
|
||||||
let signatures = vec![transaction.signature1.clone(), transaction.signature2.clone()];
|
transaction.signature2.clone(),
|
||||||
|
];
|
||||||
|
|
||||||
restore_if_spendable(&signatures, sender1_spendable && sender2_spendable, || async {
|
restore_if_spendable(
|
||||||
let _ = transaction.add_to_memory().await;
|
&signatures,
|
||||||
})
|
sender1_spendable && sender2_spendable,
|
||||||
|
|| async {
|
||||||
|
let _ = transaction.add_to_memory().await;
|
||||||
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore_loan_creation(transaction: &LoanContractTransaction, db: &Db) {
|
pub async fn restore_loan_creation(transaction: &LoanContractTransaction, db: &Db) {
|
||||||
let loan = &transaction.unsigned_loan_contract;
|
let loan = &transaction.unsigned_loan_contract;
|
||||||
let lender_spendable =
|
let lender_spendable = balance_checkup(
|
||||||
balance_checkup(db, loan.loan_amount, loan.txfee, loan.loan_coin.clone(), &loan.lender)
|
db,
|
||||||
.await;
|
loan.loan_amount,
|
||||||
let borrower_spendable =
|
loan.txfee,
|
||||||
balance_checkup(db, loan.collateral_amount, 0, loan.collateral.clone(), &loan.borrower)
|
loan.loan_coin.clone(),
|
||||||
.await;
|
&loan.lender,
|
||||||
let signatures = vec![transaction.signature1.clone(), transaction.signature2.clone()];
|
)
|
||||||
|
.await;
|
||||||
|
let borrower_spendable = balance_checkup(
|
||||||
|
db,
|
||||||
|
loan.collateral_amount,
|
||||||
|
0,
|
||||||
|
loan.collateral.clone(),
|
||||||
|
&loan.borrower,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let signatures = vec![
|
||||||
|
transaction.signature1.clone(),
|
||||||
|
transaction.signature2.clone(),
|
||||||
|
];
|
||||||
|
|
||||||
restore_if_spendable(&signatures, lender_spendable && borrower_spendable, || async {
|
restore_if_spendable(
|
||||||
let _ = transaction.add_to_memory().await;
|
&signatures,
|
||||||
})
|
lender_spendable && borrower_spendable,
|
||||||
|
|| async {
|
||||||
|
let _ = transaction.add_to_memory().await;
|
||||||
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,8 +196,14 @@ pub async fn restore_borrower(transaction: &ContractPaymentTransaction, db: &Db)
|
||||||
|
|
||||||
pub async fn restore_collateral(transaction: &CollateralClaimTransaction, db: &Db) {
|
pub async fn restore_collateral(transaction: &CollateralClaimTransaction, db: &Db) {
|
||||||
let collateral = &transaction.unsigned_collateral_claim;
|
let collateral = &transaction.unsigned_collateral_claim;
|
||||||
let spendable =
|
let spendable = balance_checkup(
|
||||||
balance_checkup(db, 0, collateral.txfee, BASECOIN.clone(), &collateral.address).await;
|
db,
|
||||||
|
0,
|
||||||
|
collateral.txfee,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
&collateral.address,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let signature = transaction.signature.clone();
|
let signature = transaction.signature.clone();
|
||||||
|
|
||||||
restore_if_spendable(&[signature], spendable, || async {
|
restore_if_spendable(&[signature], spendable, || async {
|
||||||
|
|
|
||||||
|
|
@ -1,93 +1,92 @@
|
||||||
use crate::blocks::burn::BurnTransaction;
|
use crate::blocks::burn::BurnTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::common::nft_assets::nft_asset_name;
|
use crate::common::nft_assets::nft_asset_name;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
||||||
use crate::records::record_chain::token_provenance::remove_token_history_entry;
|
use crate::records::record_chain::token_provenance::remove_token_history_entry;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_burn_transaction(transaction: BurnTransaction, mining_receiver: &str, db: &Db) {
|
pub async fn undo_burn_transaction(transaction: BurnTransaction, mining_receiver: &str, db: &Db) {
|
||||||
// Reverse the burn fee and burned-asset balance movement before
|
// Reverse the burn fee and burned-asset balance movement before
|
||||||
// restoring the live token or NFT state back into the active chain.
|
// restoring the live token or NFT state back into the active chain.
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let burned_asset = nft_asset_name(
|
let burned_asset = nft_asset_name(
|
||||||
&transaction.unsigned_burn.coin,
|
&transaction.unsigned_burn.coin,
|
||||||
transaction.unsigned_burn.nft_series,
|
transaction.unsigned_burn.nft_series,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove the miner fee, refund the burner fee, and return the burned asset
|
// Remove the miner fee, refund the burner fee, and return the burned asset
|
||||||
// to the burner balance.
|
// to the burner balance.
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
transaction.unsigned_burn.txfee,
|
transaction.unsigned_burn.txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
&transaction.unsigned_burn.address,
|
&transaction.unsigned_burn.address,
|
||||||
transaction.unsigned_burn.txfee,
|
transaction.unsigned_burn.txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_addition,
|
operand_addition,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
&transaction.unsigned_burn.address,
|
&transaction.unsigned_burn.address,
|
||||||
transaction.unsigned_burn.value,
|
transaction.unsigned_burn.value,
|
||||||
&burned_asset,
|
&burned_asset,
|
||||||
operand_addition,
|
operand_addition,
|
||||||
);
|
);
|
||||||
|
|
||||||
let hash_binary = decode(&transaction.unsigned_burn.hash().await).unwrap();
|
let hash_binary = decode(&transaction.unsigned_burn.hash().await).unwrap();
|
||||||
|
|
||||||
// Delete the txid lookup inserted when the burn was saved.
|
// Delete the txid lookup inserted when the burn was saved.
|
||||||
let txid_tree = db.open_tree("txid").unwrap();
|
let txid_tree = db.open_tree("txid").unwrap();
|
||||||
txid_tree.remove(hash_binary.clone()).unwrap();
|
txid_tree.remove(hash_binary.clone()).unwrap();
|
||||||
|
|
||||||
// Restore NFT rows directly, or add the burned amount back into the
|
// Restore NFT rows directly, or add the burned amount back into the
|
||||||
// fungible token supply if this burn targeted a token asset.
|
// fungible token supply if this burn targeted a token asset.
|
||||||
let nft_tree = db.open_tree("nfts").unwrap();
|
let nft_tree = db.open_tree("nfts").unwrap();
|
||||||
let nft_origin_tree = db.open_tree("nft_origins").unwrap();
|
let nft_origin_tree = db.open_tree("nft_origins").unwrap();
|
||||||
if nft_origin_tree
|
if nft_origin_tree
|
||||||
.contains_key(burned_asset.as_bytes())
|
.contains_key(burned_asset.as_bytes())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
// NFT burns remove the live NFT row; rollback restores the row and
|
// NFT burns remove the live NFT row; rollback restores the row and
|
||||||
// removes the burn from NFT history.
|
// removes the burn from NFT history.
|
||||||
let _ = remove_nft_history_entry(db, &burned_asset, &hash_binary);
|
let _ = remove_nft_history_entry(db, &burned_asset, &hash_binary);
|
||||||
let _ = nft_tree.insert(burned_asset.as_bytes(), b"1");
|
let _ = nft_tree.insert(burned_asset.as_bytes(), b"1");
|
||||||
} else {
|
} else {
|
||||||
// Token burns reduce supply; rollback adds the burned amount back.
|
// Token burns reduce supply; rollback adds the burned amount back.
|
||||||
let _ = remove_token_history_entry(db, &transaction.unsigned_burn.coin, &hash_binary);
|
let _ = remove_token_history_entry(db, &transaction.unsigned_burn.coin, &hash_binary);
|
||||||
let token_tree = db.open_tree("tokens").unwrap();
|
let token_tree = db.open_tree("tokens").unwrap();
|
||||||
let current_supply = token_tree
|
let current_supply = token_tree
|
||||||
.get(transaction.unsigned_burn.coin.as_bytes())
|
.get(transaction.unsigned_burn.coin.as_bytes())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|bytes| {
|
.map(|bytes| {
|
||||||
let mut supply_bytes = [0u8; 8];
|
let mut supply_bytes = [0u8; 8];
|
||||||
supply_bytes.copy_from_slice(bytes.as_ref());
|
supply_bytes.copy_from_slice(bytes.as_ref());
|
||||||
u64::from_le_bytes(supply_bytes)
|
u64::from_le_bytes(supply_bytes)
|
||||||
})
|
})
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let restored_supply = current_supply.saturating_add(transaction.unsigned_burn.value);
|
let restored_supply = current_supply.saturating_add(transaction.unsigned_burn.value);
|
||||||
let _ = token_tree.insert(
|
let _ = token_tree.insert(
|
||||||
transaction.unsigned_burn.coin.as_bytes(),
|
transaction.unsigned_burn.coin.as_bytes(),
|
||||||
&restored_supply.to_le_bytes(),
|
&restored_supply.to_le_bytes(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,80 @@
|
||||||
use crate::blocks::nft::CreateNftTransaction;
|
use crate::blocks::nft::CreateNftTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::common::nft_assets::nft_asset_name;
|
use crate::common::nft_assets::nft_asset_name;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::nft_provenance::{remove_nft_history_entry, remove_nft_origin};
|
use crate::records::record_chain::nft_provenance::{remove_nft_history_entry, remove_nft_origin};
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
const NFT_UNIT: u64 = 100_000_000;
|
const NFT_UNIT: u64 = 100_000_000;
|
||||||
|
|
||||||
pub async fn undo_create_nft_transaction(
|
pub async fn undo_create_nft_transaction(
|
||||||
transaction: CreateNftTransaction,
|
transaction: CreateNftTransaction,
|
||||||
mining_receiver: &str,
|
mining_receiver: &str,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
) {
|
) {
|
||||||
// remove the created nft state and restore the creator balances
|
// remove the created nft state and restore the creator balances
|
||||||
// when a create-nft transaction is rolled back
|
// when a create-nft transaction is rolled back
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let (txfee, creator) = (
|
let (txfee, creator) = (
|
||||||
&transaction.unsigned_create_nft.txfee,
|
&transaction.unsigned_create_nft.txfee,
|
||||||
&transaction.unsigned_create_nft.creator,
|
&transaction.unsigned_create_nft.creator,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove the miner fee and refund the creator's base-coin fee.
|
// Remove the miner fee and refund the creator's base-coin fee.
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee,
|
*txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(db, creator, *txfee, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, creator, *txfee, &type_str, operand_addition);
|
||||||
let hash_binary = decode(&transaction.unsigned_create_nft.hash().await).unwrap();
|
let hash_binary = decode(&transaction.unsigned_create_nft.hash().await).unwrap();
|
||||||
|
|
||||||
// Remove the create-NFT transaction lookup from the txid tree.
|
// Remove the create-NFT transaction lookup from the txid tree.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let key = hash_binary.clone();
|
let key = hash_binary.clone();
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
|
|
||||||
// remove each created nft item and clear the
|
// remove each created nft item and clear the
|
||||||
// associated provenance entries
|
// associated provenance entries
|
||||||
let tree = db.open_tree("nfts").unwrap();
|
let tree = db.open_tree("nfts").unwrap();
|
||||||
if transaction.unsigned_create_nft.series == 1 {
|
if transaction.unsigned_create_nft.series == 1 {
|
||||||
// Series creation mints numbered items, so each item is removed
|
// Series creation mints numbered items, so each item is removed
|
||||||
// individually from balances, provenance, origins, and the NFT tree.
|
// individually from balances, provenance, origins, and the NFT tree.
|
||||||
for item_number in 1..=transaction.unsigned_create_nft.count {
|
for item_number in 1..=transaction.unsigned_create_nft.count {
|
||||||
let nft_name = nft_asset_name(&transaction.unsigned_create_nft.nft_name, item_number);
|
let nft_name = nft_asset_name(&transaction.unsigned_create_nft.nft_name, item_number);
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
creator,
|
creator,
|
||||||
NFT_UNIT,
|
NFT_UNIT,
|
||||||
&nft_name,
|
&nft_name,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = remove_nft_history_entry(db, &nft_name, &hash_binary);
|
let _ = remove_nft_history_entry(db, &nft_name, &hash_binary);
|
||||||
let _ = remove_nft_origin(db, &nft_name);
|
let _ = remove_nft_origin(db, &nft_name);
|
||||||
tree.remove(nft_name.as_bytes()).unwrap();
|
tree.remove(nft_name.as_bytes()).unwrap();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Single NFT creation only writes the base NFT name.
|
// Single NFT creation only writes the base NFT name.
|
||||||
let nft_name = &transaction.unsigned_create_nft.nft_name;
|
let nft_name = &transaction.unsigned_create_nft.nft_name;
|
||||||
let _ =
|
let _ =
|
||||||
balance_sheet_operation_with_db(db, creator, NFT_UNIT, nft_name, operand_subtraction);
|
balance_sheet_operation_with_db(db, creator, NFT_UNIT, nft_name, operand_subtraction);
|
||||||
let _ = remove_nft_history_entry(db, nft_name, &hash_binary);
|
let _ = remove_nft_history_entry(db, nft_name, &hash_binary);
|
||||||
let _ = remove_nft_origin(db, nft_name);
|
let _ = remove_nft_origin(db, nft_name);
|
||||||
tree.remove(nft_name.as_bytes()).unwrap();
|
tree.remove(nft_name.as_bytes()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,68 @@
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::token_provenance::clear_token_history;
|
use crate::records::record_chain::token_provenance::clear_token_history;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_create_token_transaction(
|
pub async fn undo_create_token_transaction(
|
||||||
transaction: CreateTokenTransaction,
|
transaction: CreateTokenTransaction,
|
||||||
mining_receiver: &str,
|
mining_receiver: &str,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
) {
|
) {
|
||||||
// remove the created token state and restore the creator balances
|
// remove the created token state and restore the creator balances
|
||||||
// when a create-token transaction is rolled back
|
// when a create-token transaction is rolled back
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let (txfee, creator, ticker, number) = (
|
let (txfee, creator, ticker, number) = (
|
||||||
&transaction.unsigned_create_token.txfee,
|
&transaction.unsigned_create_token.txfee,
|
||||||
&transaction.unsigned_create_token.creator,
|
&transaction.unsigned_create_token.creator,
|
||||||
&transaction.unsigned_create_token.ticker,
|
&transaction.unsigned_create_token.ticker,
|
||||||
&transaction.unsigned_create_token.number,
|
&transaction.unsigned_create_token.number,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove the miner fee, refund the creator fee, and remove the created
|
// Remove the miner fee, refund the creator fee, and remove the created
|
||||||
// supply from the creator balance.
|
// supply from the creator balance.
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee,
|
*txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(db, creator, *txfee, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, creator, *txfee, &type_str, operand_addition);
|
||||||
let _ = balance_sheet_operation_with_db(db, creator, *number, ticker, operand_subtraction);
|
let _ = balance_sheet_operation_with_db(db, creator, *number, ticker, operand_subtraction);
|
||||||
|
|
||||||
let ticker_binary = &transaction.unsigned_create_token.ticker.as_bytes();
|
let ticker_binary = &transaction.unsigned_create_token.ticker.as_bytes();
|
||||||
let hash_binary = decode(&transaction.unsigned_create_token.hash().await).unwrap();
|
let hash_binary = decode(&transaction.unsigned_create_token.hash().await).unwrap();
|
||||||
|
|
||||||
// Remove the create-token transaction lookup from the txid tree.
|
// Remove the create-token transaction lookup from the txid tree.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let key = hash_binary.clone();
|
let key = hash_binary.clone();
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
|
|
||||||
// remove the token definition and origin entry
|
// remove the token definition and origin entry
|
||||||
// created when the token was first saved
|
// created when the token was first saved
|
||||||
let tree = db.open_tree("tokens").unwrap();
|
let tree = db.open_tree("tokens").unwrap();
|
||||||
let key = ticker_binary;
|
let key = ticker_binary;
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
|
|
||||||
let origin_tree = db.open_tree("token_origins").unwrap();
|
let origin_tree = db.open_tree("token_origins").unwrap();
|
||||||
origin_tree.remove(key).unwrap();
|
origin_tree.remove(key).unwrap();
|
||||||
|
|
||||||
let limit_tree = db.open_tree("token_limits").unwrap();
|
let limit_tree = db.open_tree("token_limits").unwrap();
|
||||||
limit_tree.remove(key).unwrap();
|
limit_tree.remove(key).unwrap();
|
||||||
// Token history is cleared because the token itself no longer exists.
|
// Token history is cleared because the token itself no longer exists.
|
||||||
let _ = clear_token_history(db, ticker);
|
let _ = clear_token_history(db, ticker);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,61 @@
|
||||||
use crate::blocks::issue_token::IssueTokenTransaction;
|
use crate::blocks::issue_token::IssueTokenTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::token_provenance::remove_token_history_entry;
|
use crate::records::record_chain::token_provenance::remove_token_history_entry;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_issue_token_transaction(
|
pub async fn undo_issue_token_transaction(
|
||||||
transaction: IssueTokenTransaction,
|
transaction: IssueTokenTransaction,
|
||||||
mining_receiver: &str,
|
mining_receiver: &str,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
) {
|
) {
|
||||||
// Reverse the issued supply and fee movements so rollback restores
|
// Reverse the issued supply and fee movements so rollback restores
|
||||||
// both the creator balance and the stored token supply.
|
// both the creator balance and the stored token supply.
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let txfee = &transaction.unsigned_issue_token.txfee;
|
let txfee = &transaction.unsigned_issue_token.txfee;
|
||||||
let creator = &transaction.unsigned_issue_token.creator;
|
let creator = &transaction.unsigned_issue_token.creator;
|
||||||
let ticker = &transaction.unsigned_issue_token.ticker;
|
let ticker = &transaction.unsigned_issue_token.ticker;
|
||||||
let number = &transaction.unsigned_issue_token.number;
|
let number = &transaction.unsigned_issue_token.number;
|
||||||
|
|
||||||
// Remove the miner fee, refund the issuer fee, and take the issued amount
|
// Remove the miner fee, refund the issuer fee, and take the issued amount
|
||||||
// back out of the issuer balance.
|
// back out of the issuer balance.
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee,
|
*txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(db, creator, *txfee, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, creator, *txfee, &type_str, operand_addition);
|
||||||
let _ = balance_sheet_operation_with_db(db, creator, *number, ticker, operand_subtraction);
|
let _ = balance_sheet_operation_with_db(db, creator, *number, ticker, operand_subtraction);
|
||||||
|
|
||||||
let hash_binary = decode(&transaction.unsigned_issue_token.hash().await).unwrap();
|
let hash_binary = decode(&transaction.unsigned_issue_token.hash().await).unwrap();
|
||||||
|
|
||||||
// Delete the issued-token transaction lookup and provenance record.
|
// Delete the issued-token transaction lookup and provenance record.
|
||||||
let txid_tree = db.open_tree("txid").unwrap();
|
let txid_tree = db.open_tree("txid").unwrap();
|
||||||
txid_tree.remove(hash_binary.clone()).unwrap();
|
txid_tree.remove(hash_binary.clone()).unwrap();
|
||||||
let _ = remove_token_history_entry(db, ticker, &hash_binary);
|
let _ = remove_token_history_entry(db, ticker, &hash_binary);
|
||||||
|
|
||||||
// Restore the previous live token supply by subtracting the issued amount.
|
// Restore the previous live token supply by subtracting the issued amount.
|
||||||
let token_tree = db.open_tree("tokens").unwrap();
|
let token_tree = db.open_tree("tokens").unwrap();
|
||||||
if let Ok(Some(existing_supply)) = token_tree.get(ticker.as_bytes()) {
|
if let Ok(Some(existing_supply)) = token_tree.get(ticker.as_bytes()) {
|
||||||
let mut supply_bytes = [0u8; 8];
|
let mut supply_bytes = [0u8; 8];
|
||||||
supply_bytes.copy_from_slice(existing_supply.as_ref());
|
supply_bytes.copy_from_slice(existing_supply.as_ref());
|
||||||
let current_supply = u64::from_le_bytes(supply_bytes);
|
let current_supply = u64::from_le_bytes(supply_bytes);
|
||||||
let restored_supply = current_supply.saturating_sub(*number);
|
let restored_supply = current_supply.saturating_sub(*number);
|
||||||
let _ = token_tree.insert(ticker.as_bytes(), &restored_supply.to_le_bytes());
|
let _ = token_tree.insert(ticker.as_bytes(), &restored_supply.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,93 +1,92 @@
|
||||||
use crate::blocks::loans::LoanContractTransaction;
|
use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_loan_creation_transaction(
|
pub async fn undo_loan_creation_transaction(
|
||||||
transaction: LoanContractTransaction,
|
transaction: LoanContractTransaction,
|
||||||
mining_receiver: &str,
|
mining_receiver: &str,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
) {
|
) {
|
||||||
// remove a loan contract and restore both sides of the
|
// remove a loan contract and restore both sides of the
|
||||||
// contract balances when the block is rolled back
|
// contract balances when the block is rolled back
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let (txfee, loan_coin, loan_amount, lender, collateral, collateral_amount, borrower, hash) = (
|
let (txfee, loan_coin, loan_amount, lender, collateral, collateral_amount, borrower, hash) = (
|
||||||
&transaction.unsigned_loan_contract.txfee,
|
&transaction.unsigned_loan_contract.txfee,
|
||||||
&transaction.unsigned_loan_contract.loan_coin.clone(),
|
&transaction.unsigned_loan_contract.loan_coin.clone(),
|
||||||
&transaction.unsigned_loan_contract.loan_amount,
|
&transaction.unsigned_loan_contract.loan_amount,
|
||||||
&transaction.unsigned_loan_contract.lender.clone(),
|
&transaction.unsigned_loan_contract.lender.clone(),
|
||||||
&transaction.unsigned_loan_contract.collateral.clone(),
|
&transaction.unsigned_loan_contract.collateral.clone(),
|
||||||
&transaction.unsigned_loan_contract.collateral_amount,
|
&transaction.unsigned_loan_contract.collateral_amount,
|
||||||
&transaction.unsigned_loan_contract.borrower.clone(),
|
&transaction.unsigned_loan_contract.borrower.clone(),
|
||||||
&transaction.hash.clone(),
|
&transaction.hash.clone(),
|
||||||
);
|
);
|
||||||
let collateral_wallet = format!("collateral_{hash}");
|
let collateral_wallet = format!("collateral_{hash}");
|
||||||
|
|
||||||
// undo the fee, loan distribution, and collateral
|
// undo the fee, loan distribution, and collateral
|
||||||
// movement that were applied when the contract was saved
|
// movement that were applied when the contract was saved
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee,
|
*txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(db, lender, *txfee, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, lender, *txfee, &type_str, operand_addition);
|
||||||
let _ =
|
let _ =
|
||||||
balance_sheet_operation_with_db(db, borrower, *loan_amount, loan_coin, operand_subtraction);
|
balance_sheet_operation_with_db(db, borrower, *loan_amount, loan_coin, operand_subtraction);
|
||||||
let _ = balance_sheet_operation_with_db(db, lender, *loan_amount, loan_coin, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, lender, *loan_amount, loan_coin, operand_addition);
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
&collateral_wallet,
|
&collateral_wallet,
|
||||||
*collateral_amount,
|
*collateral_amount,
|
||||||
collateral,
|
collateral,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
borrower,
|
borrower,
|
||||||
*collateral_amount,
|
*collateral_amount,
|
||||||
collateral,
|
collateral,
|
||||||
operand_addition,
|
operand_addition,
|
||||||
);
|
);
|
||||||
|
|
||||||
let hash_binary = decode(hash).unwrap();
|
let hash_binary = decode(hash).unwrap();
|
||||||
|
|
||||||
// delete the txid and remove the active loan record
|
// delete the txid and remove the active loan record
|
||||||
// so the contract no longer exists on-chain
|
// so the contract no longer exists on-chain
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let key = hash_binary.clone();
|
let key = hash_binary.clone();
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
|
|
||||||
let tree = db.open_tree("loan").unwrap();
|
let tree = db.open_tree("loan").unwrap();
|
||||||
let loan_key = decode(&transaction.unsigned_loan_contract.hash().await).unwrap();
|
let loan_key = decode(&transaction.unsigned_loan_contract.hash().await).unwrap();
|
||||||
tree.remove(loan_key).unwrap();
|
tree.remove(loan_key).unwrap();
|
||||||
|
|
||||||
let nft_tree = db.open_tree("nfts").unwrap();
|
let nft_tree = db.open_tree("nfts").unwrap();
|
||||||
// Loan creation can move NFT collateral or NFT loan assets, so clear
|
// Loan creation can move NFT collateral or NFT loan assets, so clear
|
||||||
// provenance entries for whichever side is NFT-backed.
|
// provenance entries for whichever side is NFT-backed.
|
||||||
if nft_tree
|
if nft_tree
|
||||||
.contains_key(collateral.as_bytes())
|
.contains_key(collateral.as_bytes())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
let _ = remove_nft_history_entry(db, collateral, &hash_binary);
|
let _ = remove_nft_history_entry(db, collateral, &hash_binary);
|
||||||
}
|
}
|
||||||
if nft_tree.contains_key(loan_coin.as_bytes()).unwrap_or(false) {
|
if nft_tree.contains_key(loan_coin.as_bytes()).unwrap_or(false) {
|
||||||
let _ = remove_nft_history_entry(db, loan_coin, &hash_binary);
|
let _ = remove_nft_history_entry(db, loan_coin, &hash_binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,48 @@
|
||||||
use crate::blocks::marketing::MarketingTransaction;
|
use crate::blocks::marketing::MarketingTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_marketing_transaction(
|
pub async fn undo_marketing_transaction(
|
||||||
transaction: MarketingTransaction,
|
transaction: MarketingTransaction,
|
||||||
mining_receiver: &str,
|
mining_receiver: &str,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
) {
|
) {
|
||||||
// reverse the fee payment and remove the marketing txid
|
// reverse the fee payment and remove the marketing txid
|
||||||
// when a marketing transaction is rolled back
|
// when a marketing transaction is rolled back
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let (txfee, advertiser) = (
|
let (txfee, advertiser) = (
|
||||||
&transaction.unsigned_marketing.txfee,
|
&transaction.unsigned_marketing.txfee,
|
||||||
&transaction.unsigned_marketing.advertiser,
|
&transaction.unsigned_marketing.advertiser,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove the miner fee and refund the advertiser fee.
|
// Remove the miner fee and refund the advertiser fee.
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee,
|
*txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(db, advertiser, *txfee, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, advertiser, *txfee, &type_str, operand_addition);
|
||||||
|
|
||||||
let hash = decode(&transaction.unsigned_marketing.hash().await).unwrap();
|
let hash = decode(&transaction.unsigned_marketing.hash().await).unwrap();
|
||||||
|
|
||||||
// Remove the marketing transaction lookup from the txid tree.
|
// Remove the marketing transaction lookup from the txid tree.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let key = hash;
|
let key = hash;
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::blocks::rewards::RewardsTransaction;
|
use crate::blocks::rewards::RewardsTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::rewards_tx::{remove_reward_credit_marker, reward_credit_applied};
|
use crate::records::record_chain::rewards_tx::{
|
||||||
|
remove_reward_credit_marker, reward_credit_applied,
|
||||||
|
};
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_rewards_transaction(
|
pub async fn undo_rewards_transaction(
|
||||||
|
|
@ -11,34 +13,34 @@ pub async fn undo_rewards_transaction(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
block_height: u32,
|
block_height: u32,
|
||||||
) {
|
) {
|
||||||
// remove the miner reward and delete the reward txid
|
// remove the miner reward and delete the reward txid
|
||||||
// when the block that minted it is rolled back
|
// when the block that minted it is rolled back
|
||||||
let operand = "subtraction";
|
let operand = "subtraction";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
|
|
||||||
let value = transaction.unsigned.value;
|
let value = transaction.unsigned.value;
|
||||||
|
|
||||||
// Rewards are only spendable after finalization, so rollback subtracts
|
// Rewards are only spendable after finalization, so rollback subtracts
|
||||||
// them only when that delayed credit has actually been applied.
|
// them only when that delayed credit has actually been applied.
|
||||||
if reward_credit_applied(db, block_height) {
|
if reward_credit_applied(db, block_height) {
|
||||||
let _ = balance_sheet_operation_with_db(db, mining_receiver, value, &type_str, operand);
|
let _ = balance_sheet_operation_with_db(db, mining_receiver, value, &type_str, operand);
|
||||||
remove_reward_credit_marker(db, block_height);
|
remove_reward_credit_marker(db, block_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash = decode(transaction.unsigned.hash().await).unwrap();
|
let hash = decode(transaction.unsigned.hash().await).unwrap();
|
||||||
|
|
||||||
// Remove the reward transaction lookup from the txid tree.
|
// Remove the reward transaction lookup from the txid tree.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let key = hash;
|
let key = hash;
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,102 @@
|
||||||
use crate::blocks::swap::SwapTransaction;
|
use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::common::nft_assets::nft_asset_name;
|
use crate::common::nft_assets::nft_asset_name;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_swap_transaction(transaction: SwapTransaction, mining_receiver: &str, db: &Db) {
|
pub async fn undo_swap_transaction(transaction: SwapTransaction, mining_receiver: &str, db: &Db) {
|
||||||
// reverse both sides of the asset exchange and remove the
|
// reverse both sides of the asset exchange and remove the
|
||||||
// swap transaction from chain state during rollback
|
// swap transaction from chain state during rollback
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let (
|
let (
|
||||||
txfee1,
|
txfee1,
|
||||||
txfee2,
|
txfee2,
|
||||||
value1,
|
value1,
|
||||||
value2,
|
value2,
|
||||||
ticker1,
|
ticker1,
|
||||||
ticker2,
|
ticker2,
|
||||||
nft_series1,
|
nft_series1,
|
||||||
nft_series2,
|
nft_series2,
|
||||||
sender1,
|
sender1,
|
||||||
sender2,
|
sender2,
|
||||||
tip1,
|
tip1,
|
||||||
tip2,
|
tip2,
|
||||||
) = (
|
) = (
|
||||||
&transaction.unsigned_swap.txfee1,
|
&transaction.unsigned_swap.txfee1,
|
||||||
&transaction.unsigned_swap.txfee2,
|
&transaction.unsigned_swap.txfee2,
|
||||||
&transaction.unsigned_swap.value1,
|
&transaction.unsigned_swap.value1,
|
||||||
&transaction.unsigned_swap.value2,
|
&transaction.unsigned_swap.value2,
|
||||||
&transaction.unsigned_swap.ticker1,
|
&transaction.unsigned_swap.ticker1,
|
||||||
&transaction.unsigned_swap.ticker2,
|
&transaction.unsigned_swap.ticker2,
|
||||||
&transaction.unsigned_swap.nft_series1,
|
&transaction.unsigned_swap.nft_series1,
|
||||||
&transaction.unsigned_swap.nft_series2,
|
&transaction.unsigned_swap.nft_series2,
|
||||||
&transaction.unsigned_swap.sender1,
|
&transaction.unsigned_swap.sender1,
|
||||||
&transaction.unsigned_swap.sender2,
|
&transaction.unsigned_swap.sender2,
|
||||||
&transaction.unsigned_swap.tip1,
|
&transaction.unsigned_swap.tip1,
|
||||||
&transaction.unsigned_swap.tip2,
|
&transaction.unsigned_swap.tip2,
|
||||||
);
|
);
|
||||||
let asset1 = nft_asset_name(ticker1, *nft_series1);
|
let asset1 = nft_asset_name(ticker1, *nft_series1);
|
||||||
let asset2 = nft_asset_name(ticker2, *nft_series2);
|
let asset2 = nft_asset_name(ticker2, *nft_series2);
|
||||||
|
|
||||||
// Refund both base-coin fees and remove those fees from the miner balance.
|
// Refund both base-coin fees and remove those fees from the miner balance.
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee1,
|
*txfee1,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender1, *txfee1, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender1, *txfee1, &type_str, operand_addition);
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee2,
|
*txfee2,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender2, *txfee2, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender2, *txfee2, &type_str, operand_addition);
|
||||||
// Tips are paid in the swapped assets, so they must be reversed per asset.
|
// Tips are paid in the swapped assets, so they must be reversed per asset.
|
||||||
let _ =
|
let _ =
|
||||||
balance_sheet_operation_with_db(db, mining_receiver, *tip1, &asset1, operand_subtraction);
|
balance_sheet_operation_with_db(db, mining_receiver, *tip1, &asset1, operand_subtraction);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender1, *tip1, &asset1, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender1, *tip1, &asset1, operand_addition);
|
||||||
let _ =
|
let _ =
|
||||||
balance_sheet_operation_with_db(db, mining_receiver, *tip2, &asset2, operand_subtraction);
|
balance_sheet_operation_with_db(db, mining_receiver, *tip2, &asset2, operand_subtraction);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender2, *tip2, &asset2, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender2, *tip2, &asset2, operand_addition);
|
||||||
// Reverse the actual two-sided asset exchange.
|
// Reverse the actual two-sided asset exchange.
|
||||||
let _ = balance_sheet_operation_with_db(db, sender1, *value2, &asset2, operand_subtraction);
|
let _ = balance_sheet_operation_with_db(db, sender1, *value2, &asset2, operand_subtraction);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender2, *value2, &asset2, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender2, *value2, &asset2, operand_addition);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender2, *value1, &asset1, operand_subtraction);
|
let _ = balance_sheet_operation_with_db(db, sender2, *value1, &asset1, operand_subtraction);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender1, *value1, &asset1, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender1, *value1, &asset1, operand_addition);
|
||||||
|
|
||||||
// Convert the txid hash back to bytes for tree lookup/removal.
|
// Convert the txid hash back to bytes for tree lookup/removal.
|
||||||
let hash = decode(&transaction.unsigned_swap.hash().await).unwrap();
|
let hash = decode(&transaction.unsigned_swap.hash().await).unwrap();
|
||||||
|
|
||||||
// Remove the txid lookup for the rolled-back swap.
|
// Remove the txid lookup for the rolled-back swap.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let key = hash.clone();
|
let key = hash.clone();
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
|
|
||||||
let nft_tree = db.open_tree("nfts").unwrap();
|
let nft_tree = db.open_tree("nfts").unwrap();
|
||||||
// If either side of the swap was an NFT, remove this swap from that asset's
|
// If either side of the swap was an NFT, remove this swap from that asset's
|
||||||
// provenance history as well.
|
// provenance history as well.
|
||||||
if nft_tree.contains_key(asset1.as_bytes()).unwrap_or(false) {
|
if nft_tree.contains_key(asset1.as_bytes()).unwrap_or(false) {
|
||||||
let _ = remove_nft_history_entry(db, &asset1, &hash);
|
let _ = remove_nft_history_entry(db, &asset1, &hash);
|
||||||
}
|
}
|
||||||
if nft_tree.contains_key(asset2.as_bytes()).unwrap_or(false) {
|
if nft_tree.contains_key(asset2.as_bytes()).unwrap_or(false) {
|
||||||
let _ = remove_nft_history_entry(db, &asset2, &hash);
|
let _ = remove_nft_history_entry(db, &asset2, &hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,71 +1,70 @@
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::common::nft_assets::nft_asset_name;
|
use crate::common::nft_assets::nft_asset_name;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
pub async fn undo_transfer_transaction(
|
pub async fn undo_transfer_transaction(
|
||||||
transaction: TransferTransaction,
|
transaction: TransferTransaction,
|
||||||
mining_receiver: &str,
|
mining_receiver: &str,
|
||||||
db: &Db,
|
db: &Db,
|
||||||
) {
|
) {
|
||||||
// reverse the transfer and fee movements, then remove the
|
// reverse the transfer and fee movements, then remove the
|
||||||
// transfer transaction from chain state during rollback
|
// transfer transaction from chain state during rollback
|
||||||
let operand_subtraction = "subtraction";
|
let operand_subtraction = "subtraction";
|
||||||
let operand_addition = "addition";
|
let operand_addition = "addition";
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
type_str,
|
type_str,
|
||||||
_torrentpath,
|
_torrentpath,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_blockpath,
|
_blockpath,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let (coin, nft_series, receiver, sender, value, txfee) = (
|
let (coin, nft_series, receiver, sender, value, txfee) = (
|
||||||
&transaction.unsigned_transfer.coin,
|
&transaction.unsigned_transfer.coin,
|
||||||
&transaction.unsigned_transfer.nft_series,
|
&transaction.unsigned_transfer.nft_series,
|
||||||
&transaction.unsigned_transfer.receiver,
|
&transaction.unsigned_transfer.receiver,
|
||||||
&transaction.unsigned_transfer.sender,
|
&transaction.unsigned_transfer.sender,
|
||||||
&transaction.unsigned_transfer.value,
|
&transaction.unsigned_transfer.value,
|
||||||
&transaction.unsigned_transfer.txfee,
|
&transaction.unsigned_transfer.txfee,
|
||||||
);
|
);
|
||||||
let transfer_asset = nft_asset_name(coin, *nft_series);
|
let transfer_asset = nft_asset_name(coin, *nft_series);
|
||||||
|
|
||||||
// Remove the miner fee, return the transferred asset to the sender, and
|
// Remove the miner fee, return the transferred asset to the sender, and
|
||||||
// refund the sender's base-coin fee.
|
// refund the sender's base-coin fee.
|
||||||
let _ = balance_sheet_operation_with_db(
|
let _ = balance_sheet_operation_with_db(
|
||||||
db,
|
db,
|
||||||
mining_receiver,
|
mining_receiver,
|
||||||
*txfee,
|
*txfee,
|
||||||
&type_str,
|
&type_str,
|
||||||
operand_subtraction,
|
operand_subtraction,
|
||||||
);
|
);
|
||||||
let _ =
|
let _ =
|
||||||
balance_sheet_operation_with_db(db, receiver, *value, &transfer_asset, operand_subtraction);
|
balance_sheet_operation_with_db(db, receiver, *value, &transfer_asset, operand_subtraction);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender, *value, &transfer_asset, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender, *value, &transfer_asset, operand_addition);
|
||||||
let _ = balance_sheet_operation_with_db(db, sender, *txfee, &type_str, operand_addition);
|
let _ = balance_sheet_operation_with_db(db, sender, *txfee, &type_str, operand_addition);
|
||||||
|
|
||||||
let hash = decode(&transaction.unsigned_transfer.hash().await).unwrap();
|
let hash = decode(&transaction.unsigned_transfer.hash().await).unwrap();
|
||||||
|
|
||||||
// Remove the txid lookup so the rolled-back transfer no longer resolves as
|
// Remove the txid lookup so the rolled-back transfer no longer resolves as
|
||||||
// an on-chain transaction.
|
// an on-chain transaction.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let key = hash.clone();
|
let key = hash.clone();
|
||||||
tree.remove(key).unwrap();
|
tree.remove(key).unwrap();
|
||||||
|
|
||||||
// NFT transfers also write provenance, so remove this transfer from the
|
// NFT transfers also write provenance, so remove this transfer from the
|
||||||
// asset history if the transferred asset is an NFT.
|
// asset history if the transferred asset is an NFT.
|
||||||
let nft_tree = db.open_tree("nfts").unwrap();
|
let nft_tree = db.open_tree("nfts").unwrap();
|
||||||
if nft_tree
|
if nft_tree
|
||||||
.contains_key(transfer_asset.as_bytes())
|
.contains_key(transfer_asset.as_bytes())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
let _ = remove_nft_history_entry(db, &transfer_asset, &hash);
|
let _ = remove_nft_history_entry(db, &transfer_asset, &hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,7 @@ pub async fn get_balance_with_db(
|
||||||
// Balance queries accept vanity/registered aliases, but storage always
|
// Balance queries accept vanity/registered aliases, but storage always
|
||||||
// resolves back to the canonical short address.
|
// resolves back to the canonical short address.
|
||||||
let canonical_address = resolve_canonical_registered_short_address(db, address)
|
let canonical_address = resolve_canonical_registered_short_address(db, address)
|
||||||
.map_err(|err| {
|
.map_err(|err| io::Error::other(format!("Wallet registry lookup failed: {err}")))?
|
||||||
io::Error::other(
|
|
||||||
format!("Wallet registry lookup failed: {err}"),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.unwrap_or_else(|| address.to_string());
|
.unwrap_or_else(|| address.to_string());
|
||||||
|
|
||||||
get_balance(&canonical_address, coin_type).await
|
get_balance(&canonical_address, coin_type).await
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,7 @@ pub fn balance_sheet_operation(
|
||||||
let mut file_balance = if file_exists {
|
let mut file_balance = if file_exists {
|
||||||
// Existing balances are stored as a single little-endian u64.
|
// Existing balances are stored as a single little-endian u64.
|
||||||
file.read_exact(&mut buffer).map_err(|e| {
|
file.read_exact(&mut buffer).map_err(|e| {
|
||||||
eprintln!(
|
eprintln!("Error reading file balance_sheet address {address}: {e}");
|
||||||
"Error reading file balance_sheet address {address}: {e}"
|
|
||||||
);
|
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
u64::from_le_bytes(buffer)
|
u64::from_le_bytes(buffer)
|
||||||
|
|
@ -142,11 +140,7 @@ pub fn balance_sheet_operation_with_db(
|
||||||
// Vanity or alternate registered addresses resolve to the canonical short
|
// Vanity or alternate registered addresses resolve to the canonical short
|
||||||
// address before the filesystem balance is updated.
|
// address before the filesystem balance is updated.
|
||||||
let canonical_address = resolve_canonical_registered_short_address(db, address)
|
let canonical_address = resolve_canonical_registered_short_address(db, address)
|
||||||
.map_err(|err| {
|
.map_err(|err| io::Error::other(format!("Wallet registry lookup failed: {err}")))?
|
||||||
io::Error::other(
|
|
||||||
format!("Wallet registry lookup failed: {err}"),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.unwrap_or_else(|| address.to_string());
|
.unwrap_or_else(|| address.to_string());
|
||||||
|
|
||||||
balance_sheet_operation(&canonical_address, balance, coin_type, operand)
|
balance_sheet_operation(&canonical_address, balance, coin_type, operand)
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,97 @@
|
||||||
use crate::common::binary_conversions::{binary_to_ip, ip_to_binary};
|
use crate::common::binary_conversions::{binary_to_ip, ip_to_binary};
|
||||||
use crate::lazy_static;
|
use crate::lazy_static;
|
||||||
use crate::log::{info, warn};
|
use crate::log::{info, warn};
|
||||||
use crate::records::memory::enums::{ClientType, ConnectionType};
|
use crate::records::memory::enums::{ClientType, ConnectionType};
|
||||||
use crate::records::memory::response_channels::{delete_entry, reserve_entry, Command};
|
use crate::records::memory::response_channels::{delete_entry, reserve_entry, Command};
|
||||||
use crate::records::memory::structs::{Connection, StoreConnectionParams};
|
use crate::records::memory::structs::{Connection, StoreConnectionParams};
|
||||||
use crate::rpc::client::handshake_processing::{bootstrap_peer_discovery, BootstrapParams};
|
use crate::rpc::client::handshake_processing::{bootstrap_peer_discovery, BootstrapParams};
|
||||||
use crate::rpc::command_maps::RPC_BLOCK_HEIGHT;
|
use crate::rpc::command_maps::RPC_BLOCK_HEIGHT;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::sleep;
|
use crate::sleep;
|
||||||
use crate::thread_rng;
|
use crate::thread_rng;
|
||||||
use crate::timeout;
|
use crate::timeout;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::AsyncWriteExt;
|
use crate::AsyncWriteExt;
|
||||||
use crate::AtomicBool;
|
use crate::AtomicBool;
|
||||||
use crate::AtomicOrdering;
|
use crate::AtomicOrdering;
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
use crate::IteratorRandom;
|
use crate::IteratorRandom;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::RwLock;
|
use crate::RwLock;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
|
|
||||||
fn split_ip_port_key(value: &str) -> Option<(String, u16)> {
|
fn split_ip_port_key(value: &str) -> Option<(String, u16)> {
|
||||||
// Connection keys are stored as ip:port strings; IPv6 addresses may arrive
|
// Connection keys are stored as ip:port strings; IPv6 addresses may arrive
|
||||||
// bracketed, so strip brackets before parsing the port.
|
// bracketed, so strip brackets before parsing the port.
|
||||||
let (ip_part, port_part) = value.rsplit_once(':')?;
|
let (ip_part, port_part) = value.rsplit_once(':')?;
|
||||||
let ip = ip_part
|
let ip = ip_part
|
||||||
.strip_prefix('[')
|
.strip_prefix('[')
|
||||||
.and_then(|inner| inner.strip_suffix(']'))
|
.and_then(|inner| inner.strip_suffix(']'))
|
||||||
.unwrap_or(ip_part)
|
.unwrap_or(ip_part)
|
||||||
.to_string();
|
.to_string();
|
||||||
let port = port_part.parse::<u16>().ok()?;
|
let port = port_part.parse::<u16>().ok()?;
|
||||||
Some((ip, port))
|
Some((ip, port))
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::records::memory::structs::{ConnectionInfo, ConnectionKey};
|
use crate::records::memory::structs::{ConnectionInfo, ConnectionKey};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ReconnectContext {
|
struct ReconnectContext {
|
||||||
db: Db,
|
db: Db,
|
||||||
wallet_key: String,
|
wallet_key: String,
|
||||||
map: Arc<Mutex<Command>>,
|
map: Arc<Mutex<Command>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RECONNECT_CONTEXT: Mutex<Option<ReconnectContext>> = Mutex::new(None);
|
static ref RECONNECT_CONTEXT: Mutex<Option<ReconnectContext>> = Mutex::new(None);
|
||||||
static ref RECONNECT_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
|
static ref RECONNECT_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_start_reconnect() -> bool {
|
fn try_start_reconnect() -> bool {
|
||||||
// Only one reconnect path should run at a time, whether it came from
|
// Only one reconnect path should run at a time, whether it came from
|
||||||
// liveness failure or bootstrap recovery.
|
// liveness failure or bootstrap recovery.
|
||||||
RECONNECT_IN_PROGRESS
|
RECONNECT_IN_PROGRESS
|
||||||
.compare_exchange(false, true, AtomicOrdering::SeqCst, AtomicOrdering::SeqCst)
|
.compare_exchange(false, true, AtomicOrdering::SeqCst, AtomicOrdering::SeqCst)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_reconnect() {
|
fn finish_reconnect() {
|
||||||
// Release the reconnect gate after the async reconnect attempt finishes.
|
// Release the reconnect gate after the async reconnect attempt finishes.
|
||||||
RECONNECT_IN_PROGRESS.store(false, AtomicOrdering::SeqCst);
|
RECONNECT_IN_PROGRESS.store(false, AtomicOrdering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_reconnect_context(
|
pub async fn set_reconnect_context(db: Db, wallet_key: String, map: Arc<Mutex<Command>>) {
|
||||||
db: Db,
|
let mut context = RECONNECT_CONTEXT.lock().await;
|
||||||
wallet_key: String,
|
// Store enough state for later liveness checks to reconnect without
|
||||||
map: Arc<Mutex<Command>>,
|
// needing the original startup stack.
|
||||||
) {
|
*context = Some(ReconnectContext {
|
||||||
let mut context = RECONNECT_CONTEXT.lock().await;
|
db,
|
||||||
// Store enough state for later liveness checks to reconnect without
|
wallet_key,
|
||||||
// needing the original startup stack.
|
map,
|
||||||
*context = Some(ReconnectContext {
|
});
|
||||||
db,
|
}
|
||||||
wallet_key,
|
|
||||||
map,
|
async fn reconnect_dropped_outgoing(excluded_ip: &str) {
|
||||||
});
|
if !try_start_reconnect() {
|
||||||
}
|
warn!("[reconnect] replacement attempt already in progress, skipping duplicate request");
|
||||||
|
return;
|
||||||
async fn reconnect_dropped_outgoing(excluded_ip: &str) {
|
}
|
||||||
if !try_start_reconnect() {
|
|
||||||
warn!("[reconnect] replacement attempt already in progress, skipping duplicate request");
|
async {
|
||||||
return;
|
// 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 = {
|
||||||
async {
|
let guard = RECONNECT_CONTEXT.lock().await;
|
||||||
// When an outgoing peer disappears, try to replace it with another
|
guard.clone()
|
||||||
// active node that is not already connected and is not the failed IP.
|
};
|
||||||
let context = {
|
|
||||||
let guard = RECONNECT_CONTEXT.lock().await;
|
let Some(context) = context else {
|
||||||
guard.clone()
|
warn!("[reconnect] no reconnect context configured");
|
||||||
};
|
return;
|
||||||
|
};
|
||||||
let Some(context) = context else {
|
|
||||||
warn!("[reconnect] no reconnect context configured");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let excluded_ip_bytes = ip_to_binary(excluded_ip);
|
let excluded_ip_bytes = ip_to_binary(excluded_ip);
|
||||||
let live_connection = {
|
let live_connection = {
|
||||||
let guard = CONNECTIONS.read().await;
|
let guard = CONNECTIONS.read().await;
|
||||||
|
|
@ -131,36 +127,36 @@ async fn reconnect_dropped_outgoing(excluded_ip: &str) {
|
||||||
if let Err(err) = bootstrap_peer_discovery(bootstrap_params).await {
|
if let Err(err) = bootstrap_peer_discovery(bootstrap_params).await {
|
||||||
warn!("[reconnect] bootstrap recovery failed: {err}");
|
warn!("[reconnect] bootstrap recovery failed: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
finish_reconnect();
|
finish_reconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_reconnect_bootstrap(params: BootstrapParams) {
|
pub fn spawn_reconnect_bootstrap(params: BootstrapParams) {
|
||||||
if !try_start_reconnect() {
|
if !try_start_reconnect() {
|
||||||
warn!("[reconnect] bootstrap recovery already in progress, skipping duplicate request");
|
warn!("[reconnect] bootstrap recovery already in progress, skipping duplicate request");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bootstrap discovery can perform network requests, so it runs detached
|
// Bootstrap discovery can perform network requests, so it runs detached
|
||||||
// from the caller that noticed the connection problem.
|
// from the caller that noticed the connection problem.
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(err) = bootstrap_peer_discovery(params).await {
|
if let Err(err) = bootstrap_peer_discovery(params).await {
|
||||||
warn!("[reconnect] bootstrap recovery failed: {err}");
|
warn!("[reconnect] bootstrap recovery failed: {err}");
|
||||||
}
|
}
|
||||||
finish_reconnect();
|
finish_reconnect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
// Initialize the in-memory connection manager state.
|
// Initialize the in-memory connection manager state.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store a live socket in memory along with its role, peer identity,
|
// Store a live socket in memory along with its role, peer identity,
|
||||||
// and session metadata used by the RPC and peer-management paths.
|
// and session metadata used by the RPC and peer-management paths.
|
||||||
pub fn store_connection(&mut self, params: StoreConnectionParams) -> bool {
|
pub fn store_connection(&mut self, params: StoreConnectionParams) -> bool {
|
||||||
let StoreConnectionParams {
|
let StoreConnectionParams {
|
||||||
connection_type,
|
connection_type,
|
||||||
|
|
@ -172,101 +168,101 @@ impl Connection {
|
||||||
command_map,
|
command_map,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let ip_bytes = ip_to_binary(&ip);
|
let ip_bytes = ip_to_binary(&ip);
|
||||||
let connection_key = ConnectionKey {
|
let connection_key = ConnectionKey {
|
||||||
connection_type: connection_type.as_bytes(),
|
connection_type: connection_type.as_bytes(),
|
||||||
ip: ip_bytes.clone(),
|
ip: ip_bytes.clone(),
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
|
|
||||||
let connection_key2 = ConnectionKey {
|
let connection_key2 = ConnectionKey {
|
||||||
connection_type: connection_type.opposite().as_bytes(),
|
connection_type: connection_type.opposite().as_bytes(),
|
||||||
ip: ip_bytes.clone(),
|
ip: ip_bytes.clone(),
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Miner nodes are identified by IP, not by port. A second node
|
// Miner nodes are identified by IP, not by port. A second node
|
||||||
// announcing the same IP is rejected even if it uses another
|
// announcing the same IP is rejected even if it uses another
|
||||||
// socket port.
|
// socket port.
|
||||||
if client_type == ClientType::Miner
|
if client_type == ClientType::Miner
|
||||||
&& self.connection_map.iter().any(|(key, info)| {
|
&& self.connection_map.iter().any(|(key, info)| {
|
||||||
key.ip == ip_bytes
|
key.ip == ip_bytes
|
||||||
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-miner RPC clients still use the full socket key so short
|
// Non-miner RPC clients still use the full socket key so short
|
||||||
// request/response connections do not collide unnecessarily.
|
// request/response connections do not collide unnecessarily.
|
||||||
if self.connection_map.contains_key(&connection_key)
|
if self.connection_map.contains_key(&connection_key)
|
||||||
|| self.connection_map.contains_key(&connection_key2)
|
|| self.connection_map.contains_key(&connection_key2)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let address = Wallet::long_address_to_bytes(wallet_address);
|
let address = Wallet::long_address_to_bytes(wallet_address);
|
||||||
if address.len() != Wallet::ADDRESS_BYTES_LENGTH {
|
if address.len() != Wallet::ADDRESS_BYTES_LENGTH {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let connection_info = ConnectionInfo::new(
|
let connection_info = ConnectionInfo::new(
|
||||||
connection_type.as_bytes(),
|
connection_type.as_bytes(),
|
||||||
ip_bytes,
|
ip_bytes,
|
||||||
port,
|
port,
|
||||||
stream.clone(),
|
stream.clone(),
|
||||||
client_type.as_bytes(),
|
client_type.as_bytes(),
|
||||||
address,
|
address,
|
||||||
);
|
);
|
||||||
self.connection_map.insert(connection_key, connection_info);
|
self.connection_map.insert(connection_key, connection_info);
|
||||||
|
|
||||||
if client_type == ClientType::Miner {
|
if client_type == ClientType::Miner {
|
||||||
Connection::client_checkup(stream, connection_type, ip, port, command_map);
|
Connection::client_checkup(stream, connection_type, ip, port, command_map);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a specific connection entry by direction, IP, and port.
|
// Remove a specific connection entry by direction, IP, and port.
|
||||||
pub fn drop_connection(
|
pub fn drop_connection(
|
||||||
&mut self,
|
&mut self,
|
||||||
connection_type: ConnectionType,
|
connection_type: ConnectionType,
|
||||||
ip: String,
|
ip: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
) -> Option<ConnectionInfo> {
|
) -> Option<ConnectionInfo> {
|
||||||
let ip_bytes = ip_to_binary(&ip);
|
let ip_bytes = ip_to_binary(&ip);
|
||||||
let connection_key = ConnectionKey {
|
let connection_key = ConnectionKey {
|
||||||
connection_type: connection_type.as_bytes(),
|
connection_type: connection_type.as_bytes(),
|
||||||
ip: ip_bytes,
|
ip: ip_bytes,
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
let removed = self.connection_map.remove(&connection_key);
|
let removed = self.connection_map.remove(&connection_key);
|
||||||
if let Some(connection_info) = removed.as_ref() {
|
if let Some(connection_info) = removed.as_ref() {
|
||||||
let stream = Arc::clone(&connection_info.stream);
|
let stream = Arc::clone(&connection_info.stream);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stream_guard = stream.lock().await;
|
let mut stream_guard = stream.lock().await;
|
||||||
let _ = stream_guard.shutdown().await;
|
let _ = stream_guard.shutdown().await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let Some(connection_info) = removed.as_ref() {
|
if let Some(connection_info) = removed.as_ref() {
|
||||||
let client_role = ClientType::from_bytes(&connection_info.client_type)
|
let client_role = ClientType::from_bytes(&connection_info.client_type)
|
||||||
.map(|client_type| client_type.as_str())
|
.map(|client_type| client_type.as_str())
|
||||||
.unwrap_or("unknown");
|
.unwrap_or("unknown");
|
||||||
info!(
|
info!(
|
||||||
"[connection_manager] connection dropped: role={} direction={} peer={}:{}",
|
"[connection_manager] connection dropped: role={} direction={} peer={}:{}",
|
||||||
client_role,
|
client_role,
|
||||||
connection_type.as_str(),
|
connection_type.as_str(),
|
||||||
ip,
|
ip,
|
||||||
port
|
port
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
removed
|
removed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn client_checkup(
|
pub fn client_checkup(
|
||||||
stream: Arc<Mutex<TcpStream>>,
|
stream: Arc<Mutex<TcpStream>>,
|
||||||
connection_type: ConnectionType,
|
connection_type: ConnectionType,
|
||||||
ip: String,
|
ip: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
command_map: Arc<Mutex<Command>>,
|
command_map: Arc<Mutex<Command>>,
|
||||||
) {
|
) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -274,39 +270,39 @@ impl Connection {
|
||||||
let still_registered = {
|
let still_registered = {
|
||||||
let guard = CONNECTIONS.read().await;
|
let guard = CONNECTIONS.read().await;
|
||||||
guard
|
guard
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|conn| {
|
.map(|conn| {
|
||||||
let connection_key = ConnectionKey {
|
let connection_key = ConnectionKey {
|
||||||
connection_type: connection_type.as_bytes(),
|
connection_type: connection_type.as_bytes(),
|
||||||
ip: ip_to_binary(&ip),
|
ip: ip_to_binary(&ip),
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
conn.connection_map.contains_key(&connection_key)
|
conn.connection_map.contains_key(&connection_key)
|
||||||
})
|
})
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !still_registered {
|
if !still_registered {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message_type = RPC_BLOCK_HEIGHT; // Block-height request used as a lightweight checkup ping.
|
let message_type = RPC_BLOCK_HEIGHT; // Block-height request used as a lightweight checkup ping.
|
||||||
let (checkup_key, _checkup_tx, checkup_rx_mutex) =
|
let (checkup_key, _checkup_tx, checkup_rx_mutex) =
|
||||||
reserve_entry(command_map.clone()).await;
|
reserve_entry(command_map.clone()).await;
|
||||||
|
|
||||||
// Send a lightweight ping message and wait for the reply
|
// Send a lightweight ping message and wait for the reply
|
||||||
// routed back through the shared response hashmap.
|
// routed back through the shared response hashmap.
|
||||||
let mut message: Vec<u8> = Vec::with_capacity(4);
|
let mut message: Vec<u8> = Vec::with_capacity(4);
|
||||||
message.push(message_type);
|
message.push(message_type);
|
||||||
message.extend_from_slice(&checkup_key);
|
message.extend_from_slice(&checkup_key);
|
||||||
|
|
||||||
RpcResponse::send_raw(&stream, None, &message).await;
|
RpcResponse::send_raw(&stream, None, &message).await;
|
||||||
|
|
||||||
let response_result = {
|
let response_result = {
|
||||||
let mut checkup_rx = checkup_rx_mutex.lock().await;
|
let mut checkup_rx = checkup_rx_mutex.lock().await;
|
||||||
timeout(Duration::from_secs(30), checkup_rx.recv()).await
|
timeout(Duration::from_secs(30), checkup_rx.recv()).await
|
||||||
};
|
};
|
||||||
|
|
||||||
match response_result {
|
match response_result {
|
||||||
Ok(Some(_reply)) => {
|
Ok(Some(_reply)) => {
|
||||||
info!(
|
info!(
|
||||||
|
|
@ -314,26 +310,26 @@ impl Connection {
|
||||||
connection_type.as_str(),
|
connection_type.as_str(),
|
||||||
ip,
|
ip,
|
||||||
port
|
port
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let still_registered = {
|
let still_registered = {
|
||||||
let guard = CONNECTIONS.read().await;
|
let guard = CONNECTIONS.read().await;
|
||||||
guard
|
guard
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|conn| {
|
.map(|conn| {
|
||||||
let connection_key = ConnectionKey {
|
let connection_key = ConnectionKey {
|
||||||
connection_type: connection_type.as_bytes(),
|
connection_type: connection_type.as_bytes(),
|
||||||
ip: ip_to_binary(&ip),
|
ip: ip_to_binary(&ip),
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
conn.connection_map.contains_key(&connection_key)
|
conn.connection_map.contains_key(&connection_key)
|
||||||
})
|
})
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !still_registered {
|
if !still_registered {
|
||||||
delete_entry(command_map.clone(), checkup_key).await;
|
delete_entry(command_map.clone(), checkup_key).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,77 +346,75 @@ impl Connection {
|
||||||
if let Some(conn) = guard.as_mut() {
|
if let Some(conn) = guard.as_mut() {
|
||||||
conn.drop_connection(connection_type, ip.clone(), port);
|
conn.drop_connection(connection_type, ip.clone(), port);
|
||||||
}
|
}
|
||||||
drop(guard);
|
drop(guard);
|
||||||
if connection_type == ConnectionType::Outgoing {
|
if connection_type == ConnectionType::Outgoing {
|
||||||
reconnect_dropped_outgoing(&ip).await;
|
reconnect_dropped_outgoing(&ip).await;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count active incoming peer connections.
|
// Count active incoming peer connections.
|
||||||
pub fn count_incoming_connections(&self) -> usize {
|
pub fn count_incoming_connections(&self) -> usize {
|
||||||
self.connection_map
|
self.connection_map
|
||||||
.values()
|
.values()
|
||||||
.filter(|info| {
|
.filter(|info| {
|
||||||
ConnectionType::from_bytes(&info.connection_type) == Some(ConnectionType::Incoming)
|
ConnectionType::from_bytes(&info.connection_type) == Some(ConnectionType::Incoming)
|
||||||
})
|
})
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count active outgoing peer connections.
|
// Count active outgoing peer connections.
|
||||||
pub fn count_outgoing_connections(&self) -> usize {
|
pub fn count_outgoing_connections(&self) -> usize {
|
||||||
self.connection_map
|
self.connection_map
|
||||||
.values()
|
.values()
|
||||||
.filter(|info| {
|
.filter(|info| {
|
||||||
ConnectionType::from_bytes(&info.connection_type) == Some(ConnectionType::Outgoing)
|
ConnectionType::from_bytes(&info.connection_type) == Some(ConnectionType::Outgoing)
|
||||||
})
|
})
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return all live peer streams so broadcast-style paths can fan out
|
// Return all live peer streams so broadcast-style paths can fan out
|
||||||
// messages without caring whether a peer was incoming or outgoing.
|
// messages without caring whether a peer was incoming or outgoing.
|
||||||
pub fn get_all_streams(&self) -> Vec<Arc<Mutex<TcpStream>>> {
|
pub fn get_all_streams(&self) -> Vec<Arc<Mutex<TcpStream>>> {
|
||||||
self.connection_map
|
self.connection_map
|
||||||
.values()
|
.values()
|
||||||
.filter(|connection_info| {
|
.filter(|connection_info| {
|
||||||
ClientType::from_bytes(&connection_info.client_type) == Some(ClientType::Miner)
|
ClientType::from_bytes(&connection_info.client_type) == Some(ClientType::Miner)
|
||||||
})
|
})
|
||||||
.map(|connection_info| Arc::clone(&connection_info.stream))
|
.map(|connection_info| Arc::clone(&connection_info.stream))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return all non-client peer streams so network-wide broadcasts can
|
// Return all non-client peer streams so network-wide broadcasts can
|
||||||
// reach every reachable chain peer.
|
// reach every reachable chain peer.
|
||||||
pub fn get_all_peer_streams(&self) -> Vec<Arc<Mutex<TcpStream>>> {
|
pub fn get_all_peer_streams(&self) -> Vec<Arc<Mutex<TcpStream>>> {
|
||||||
self.connection_map
|
self.connection_map
|
||||||
.values()
|
.values()
|
||||||
.filter(|connection_info| {
|
.filter(|connection_info| {
|
||||||
ClientType::from_bytes(&connection_info.client_type) == Some(ClientType::Miner)
|
ClientType::from_bytes(&connection_info.client_type) == Some(ClientType::Miner)
|
||||||
})
|
})
|
||||||
.map(|connection_info| Arc::clone(&connection_info.stream))
|
.map(|connection_info| Arc::clone(&connection_info.stream))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve a stored outgoing node connection back to its live stream.
|
// Resolve a stored outgoing node connection back to its live stream.
|
||||||
pub fn get_stream_for_outgoing(&self, ip: &str, port: u16) -> Option<Arc<Mutex<TcpStream>>> {
|
pub fn get_stream_for_outgoing(&self, ip: &str, port: u16) -> Option<Arc<Mutex<TcpStream>>> {
|
||||||
let ip_bytes = ip_to_binary(ip);
|
let ip_bytes = ip_to_binary(ip);
|
||||||
let connection_key = ConnectionKey {
|
let connection_key = ConnectionKey {
|
||||||
connection_type: ConnectionType::Outgoing.as_bytes(),
|
connection_type: ConnectionType::Outgoing.as_bytes(),
|
||||||
ip: ip_bytes,
|
ip: ip_bytes,
|
||||||
port,
|
port,
|
||||||
};
|
};
|
||||||
self.connection_map
|
self.connection_map
|
||||||
.get(&connection_key)
|
.get(&connection_key)
|
||||||
.filter(|info| {
|
.filter(|info| ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner))
|
||||||
ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
.map(|info| Arc::clone(&info.stream))
|
||||||
})
|
}
|
||||||
.map(|info| Arc::clone(&info.stream))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up a live miner stream by the exact ip:port connection key.
|
// Look up a live miner stream by the exact ip:port connection key.
|
||||||
// Network-map records only store bare IPs, so they must not be used
|
// Network-map records only store bare IPs, so they must not be used
|
||||||
// to select an arbitrary live socket.
|
// to select an arbitrary live socket.
|
||||||
|
|
@ -430,16 +424,18 @@ impl Connection {
|
||||||
let conn = lock.as_ref()?;
|
let conn = lock.as_ref()?;
|
||||||
|
|
||||||
let ip_bytes = ip_to_binary(&ip);
|
let ip_bytes = ip_to_binary(&ip);
|
||||||
conn.connection_map.iter().find_map(|(connection_key, info)| {
|
conn.connection_map
|
||||||
if connection_key.ip == ip_bytes
|
.iter()
|
||||||
&& connection_key.port == port
|
.find_map(|(connection_key, info)| {
|
||||||
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
if connection_key.ip == ip_bytes
|
||||||
{
|
&& connection_key.port == port
|
||||||
Some(Arc::clone(&info.stream))
|
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||||
} else {
|
{
|
||||||
None
|
Some(Arc::clone(&info.stream))
|
||||||
}
|
} else {
|
||||||
})
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the serialized connection key for a live stream when only
|
// Build the serialized connection key for a live stream when only
|
||||||
|
|
@ -456,101 +452,101 @@ impl Connection {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the first stored connection record for the requested IP.
|
// Find the first stored connection record for the requested IP.
|
||||||
pub fn find_connection_info(&self, ip: &str) -> Option<(ConnectionType, u16)> {
|
pub fn find_connection_info(&self, ip: &str) -> Option<(ConnectionType, u16)> {
|
||||||
let ip_bytes = ip_to_binary(ip);
|
let ip_bytes = ip_to_binary(ip);
|
||||||
|
|
||||||
for (key, _info) in self.connection_map.iter() {
|
for (key, _info) in self.connection_map.iter() {
|
||||||
if key.ip == ip_bytes {
|
if key.ip == ip_bytes {
|
||||||
let connection_type = ConnectionType::from_bytes(&key.connection_type)?;
|
let connection_type = ConnectionType::from_bytes(&key.connection_type)?;
|
||||||
return Some((connection_type, key.port));
|
return Some((connection_type, key.port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a stored connection by IP, constrained to a specific client role.
|
// Find a stored connection by IP, constrained to a specific client role.
|
||||||
pub fn find_connection_info_by_client_type(
|
pub fn find_connection_info_by_client_type(
|
||||||
&self,
|
&self,
|
||||||
ip: &str,
|
ip: &str,
|
||||||
client_type: ClientType,
|
client_type: ClientType,
|
||||||
) -> Option<(ConnectionType, u16)> {
|
) -> Option<(ConnectionType, u16)> {
|
||||||
let ip_bytes = ip_to_binary(ip);
|
let ip_bytes = ip_to_binary(ip);
|
||||||
let client_type_bytes = client_type.as_bytes();
|
let client_type_bytes = client_type.as_bytes();
|
||||||
|
|
||||||
for (key, info) in self.connection_map.iter() {
|
for (key, info) in self.connection_map.iter() {
|
||||||
if key.ip == ip_bytes && info.client_type == client_type_bytes {
|
if key.ip == ip_bytes && info.client_type == client_type_bytes {
|
||||||
let connection_type = ConnectionType::from_bytes(&key.connection_type)?;
|
let connection_type = ConnectionType::from_bytes(&key.connection_type)?;
|
||||||
return Some((connection_type, key.port));
|
return Some((connection_type, key.port));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the stored outgoing port for a peer IP so reconnect and
|
// Find the stored outgoing port for a peer IP so reconnect and
|
||||||
// cleanup logic can target the correct connection entry.
|
// cleanup logic can target the correct connection entry.
|
||||||
pub fn find_outgoing_port(&self, ip: &str) -> Option<u16> {
|
pub fn find_outgoing_port(&self, ip: &str) -> Option<u16> {
|
||||||
let ip_bytes = ip_to_binary(ip);
|
let ip_bytes = ip_to_binary(ip);
|
||||||
self.connection_map
|
self.connection_map
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(key, _)| {
|
.find(|(key, _)| {
|
||||||
key.connection_type == ConnectionType::Outgoing.as_bytes() && key.ip == ip_bytes
|
key.connection_type == ConnectionType::Outgoing.as_bytes() && key.ip == ip_bytes
|
||||||
})
|
})
|
||||||
.map(|(key, _)| key.port)
|
.map(|(key, _)| key.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer a random incoming node connection, falling back to an
|
// Prefer a random incoming node connection, falling back to an
|
||||||
// outgoing node connection when no incoming peer is available.
|
// outgoing node connection when no incoming peer is available.
|
||||||
pub fn get_random_connection(&self, excluded_key: Option<&str>) -> Option<(Vec<u8>, u16)> {
|
pub fn get_random_connection(&self, excluded_key: Option<&str>) -> Option<(Vec<u8>, u16)> {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let excluded = excluded_key.and_then(split_ip_port_key);
|
let excluded = excluded_key.and_then(split_ip_port_key);
|
||||||
|
|
||||||
if let Some((key, _info)) = self
|
if let Some((key, _info)) = self
|
||||||
.connection_map
|
.connection_map
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(key, info)| {
|
.filter(|(key, info)| {
|
||||||
ConnectionType::from_bytes(&key.connection_type) == Some(ConnectionType::Incoming)
|
ConnectionType::from_bytes(&key.connection_type) == Some(ConnectionType::Incoming)
|
||||||
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||||
&& excluded
|
&& excluded
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|(ip, _)| key.ip != ip_to_binary(ip))
|
.map(|(ip, _)| key.ip != ip_to_binary(ip))
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
})
|
})
|
||||||
.choose(&mut rng)
|
.choose(&mut rng)
|
||||||
{
|
{
|
||||||
return Some((key.ip.clone(), key.port));
|
return Some((key.ip.clone(), key.port));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((key, _info)) = self
|
if let Some((key, _info)) = self
|
||||||
.connection_map
|
.connection_map
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(key, info)| {
|
.filter(|(key, info)| {
|
||||||
ConnectionType::from_bytes(&key.connection_type) == Some(ConnectionType::Outgoing)
|
ConnectionType::from_bytes(&key.connection_type) == Some(ConnectionType::Outgoing)
|
||||||
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||||
&& excluded
|
&& excluded
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|(ip, _)| key.ip != ip_to_binary(ip))
|
.map(|(ip, _)| key.ip != ip_to_binary(ip))
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
})
|
})
|
||||||
.choose(&mut rng)
|
.choose(&mut rng)
|
||||||
{
|
{
|
||||||
return Some((key.ip.clone(), key.port));
|
return Some((key.ip.clone(), key.port));
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CONNECTIONS: Arc<RwLock<Option<Connection>>> = Arc::new(RwLock::new(None));
|
pub static ref CONNECTIONS: Arc<RwLock<Option<Connection>>> = Arc::new(RwLock::new(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initialize_connection() {
|
pub async fn initialize_connection() {
|
||||||
// Lazily create the singleton connection manager the first time the
|
// Lazily create the singleton connection manager the first time the
|
||||||
// node starts accepting or opening peer connections.
|
// node starts accepting or opening peer connections.
|
||||||
let mut connection_instance = CONNECTIONS.write().await;
|
let mut connection_instance = CONNECTIONS.write().await;
|
||||||
if connection_instance.is_none() {
|
if connection_instance.is_none() {
|
||||||
*connection_instance = Some(Connection::new());
|
*connection_instance = Some(Connection::new());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -565,19 +561,19 @@ pub async fn outgoing_connection_count() -> usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_client_type_from_memory(key: &str) -> Option<ClientType> {
|
pub async fn get_client_type_from_memory(key: &str) -> Option<ClientType> {
|
||||||
// Recover the stored client role from the serialized connection key
|
// Recover the stored client role from the serialized connection key
|
||||||
// used throughout the RPC layer.
|
// used throughout the RPC layer.
|
||||||
let (ip, port) = split_ip_port_key(key)?;
|
let (ip, port) = split_ip_port_key(key)?;
|
||||||
let ip_bytes = ip_to_binary(&ip);
|
let ip_bytes = ip_to_binary(&ip);
|
||||||
|
|
||||||
let guard = CONNECTIONS.read().await;
|
let guard = CONNECTIONS.read().await;
|
||||||
let conn = guard.as_ref()?;
|
let conn = guard.as_ref()?;
|
||||||
|
|
||||||
for (connection_key, info) in conn.connection_map.iter() {
|
for (connection_key, info) in conn.connection_map.iter() {
|
||||||
if connection_key.ip == ip_bytes && connection_key.port == port {
|
if connection_key.ip == ip_bytes && connection_key.port == port {
|
||||||
return ClientType::from_bytes(&info.client_type);
|
return ClientType::from_bytes(&info.client_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,89 +1,89 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub async fn signature_exists(signature: &str, hash: &str) -> Result<bool> {
|
pub async fn signature_exists(signature: &str, hash: &str) -> Result<bool> {
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
|
|
||||||
// Check every mempool table because the signature column names differ by
|
// Check every mempool table because the signature column names differ by
|
||||||
// transaction type, especially for two-party swaps and loans.
|
// transaction type, especially for two-party swaps and loans.
|
||||||
let row = client
|
let row = client
|
||||||
.query_one(
|
.query_one(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
CASE
|
||||||
WHEN EXISTS (SELECT 1 FROM transfer a WHERE a.signature = $1 AND a.hash = $2 AND a.processed = false)
|
WHEN EXISTS (SELECT 1 FROM transfer a WHERE a.signature = $1 AND a.hash = $2 AND a.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM token b WHERE b.signature = $1 AND b.hash = $2 AND b.processed = false)
|
OR EXISTS (SELECT 1 FROM token b WHERE b.signature = $1 AND b.hash = $2 AND b.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM issue_token c WHERE c.signature = $1 AND c.hash = $2 AND c.processed = false)
|
OR EXISTS (SELECT 1 FROM issue_token c WHERE c.signature = $1 AND c.hash = $2 AND c.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM burn d WHERE d.signature = $1 AND d.hash = $2 AND d.processed = false)
|
OR EXISTS (SELECT 1 FROM burn d WHERE d.signature = $1 AND d.hash = $2 AND d.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM nft e WHERE e.signature = $1 AND e.hash = $2 AND e.processed = false)
|
OR EXISTS (SELECT 1 FROM nft e WHERE e.signature = $1 AND e.hash = $2 AND e.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM marketing f WHERE f.signature = $1 AND f.hash = $2 AND f.processed = false)
|
OR EXISTS (SELECT 1 FROM marketing f WHERE f.signature = $1 AND f.hash = $2 AND f.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM vanity_address va WHERE va.signature = $1 AND va.hash = $2 AND va.processed = false)
|
OR EXISTS (SELECT 1 FROM vanity_address va WHERE va.signature = $1 AND va.hash = $2 AND va.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM swap g WHERE g.signature1 = $1 AND g.hash = $2 AND g.processed = false)
|
OR EXISTS (SELECT 1 FROM swap g WHERE g.signature1 = $1 AND g.hash = $2 AND g.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM swap h WHERE h.signature2 = $1 AND h.hash = $2 AND h.processed = false)
|
OR EXISTS (SELECT 1 FROM swap h WHERE h.signature2 = $1 AND h.hash = $2 AND h.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM loan_contract i WHERE i.signature1 = $1 AND i.hash = $2 AND i.processed = false)
|
OR EXISTS (SELECT 1 FROM loan_contract i WHERE i.signature1 = $1 AND i.hash = $2 AND i.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM loan_contract j WHERE j.signature2 = $1 AND j.hash = $2 AND j.processed = false)
|
OR EXISTS (SELECT 1 FROM loan_contract j WHERE j.signature2 = $1 AND j.hash = $2 AND j.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM loan_payment k WHERE k.signature = $1 AND k.hash = $2 AND k.processed = false)
|
OR EXISTS (SELECT 1 FROM loan_payment k WHERE k.signature = $1 AND k.hash = $2 AND k.processed = false)
|
||||||
OR EXISTS (SELECT 1 FROM collateral_claim l WHERE l.signature = $1 AND l.hash = $2 AND l.processed = false)
|
OR EXISTS (SELECT 1 FROM collateral_claim l WHERE l.signature = $1 AND l.hash = $2 AND l.processed = false)
|
||||||
THEN 1
|
THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END AS signature_found;
|
END AS signature_found;
|
||||||
"#,
|
"#,
|
||||||
&[&signature, &hash],
|
&[&signature, &hash],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let found: i32 = row.get(0);
|
let found: i32 = row.get(0);
|
||||||
Ok(found == 1)
|
Ok(found == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn transaction_by_signature(signature: &str) -> RpcResponse {
|
pub async fn transaction_by_signature(signature: &str) -> RpcResponse {
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
|
|
||||||
// Return the original serialized transaction bytes, not a reconstructed
|
// Return the original serialized transaction bytes, not a reconstructed
|
||||||
// row, so RPC callers receive the same payload that would enter a block.
|
// row, so RPC callers receive the same payload that would enter a block.
|
||||||
let result = client
|
let result = client
|
||||||
.query_opt(
|
.query_opt(
|
||||||
r#"
|
r#"
|
||||||
SELECT original FROM (
|
SELECT original FROM (
|
||||||
SELECT original FROM transfer WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM transfer WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM token WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM token WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM issue_token WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM issue_token WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM burn WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM burn WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM nft WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM nft WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM marketing WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM marketing WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM vanity_address WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM vanity_address WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM swap WHERE signature1 = $1 AND processed = false LIMIT 1
|
SELECT original FROM swap WHERE signature1 = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM swap WHERE signature2 = $1 AND processed = false LIMIT 1
|
SELECT original FROM swap WHERE signature2 = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM loan_contract WHERE signature1 = $1 AND processed = false LIMIT 1
|
SELECT original FROM loan_contract WHERE signature1 = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM loan_contract WHERE signature2 = $1 AND processed = false LIMIT 1
|
SELECT original FROM loan_contract WHERE signature2 = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM loan_payment WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM loan_payment WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM collateral_claim WHERE signature = $1 AND processed = false LIMIT 1
|
SELECT original FROM collateral_claim WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
) AS subquery LIMIT 1
|
) AS subquery LIMIT 1
|
||||||
"#,
|
"#,
|
||||||
&[&signature],
|
&[&signature],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(row)) => {
|
Ok(Some(row)) => {
|
||||||
let bytes: Vec<u8> = row.get(0);
|
let bytes: Vec<u8> = row.get(0);
|
||||||
RpcResponse::Binary(bytes)
|
RpcResponse::Binary(bytes)
|
||||||
}
|
}
|
||||||
_ => RpcResponse::Binary(Vec::new()),
|
_ => RpcResponse::Binary(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn transactions_by_address(db: &Db, address: &str) -> RpcResponse {
|
pub async fn transactions_by_address(db: &Db, address: &str) -> RpcResponse {
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
// Canonicalize vanity aliases before querying pending rows.
|
// Canonicalize vanity aliases before querying pending rows.
|
||||||
|
|
@ -92,97 +92,97 @@ pub async fn transactions_by_address(db: &Db, address: &str) -> RpcResponse {
|
||||||
// Concatenate original transaction bytes; the RPC/bin caller can split the
|
// Concatenate original transaction bytes; the RPC/bin caller can split the
|
||||||
// stream by transaction type and fixed byte length.
|
// stream by transaction type and fixed byte length.
|
||||||
let rows = match client
|
let rows = match client
|
||||||
.query(
|
.query(
|
||||||
r#"
|
r#"
|
||||||
SELECT original FROM (
|
SELECT original FROM (
|
||||||
SELECT original FROM transfer WHERE receiver = ANY($1) AND processed = false
|
SELECT original FROM transfer WHERE receiver = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM token WHERE creator = ANY($1) AND processed = false
|
SELECT original FROM token WHERE creator = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM issue_token WHERE creator = ANY($1) AND processed = false
|
SELECT original FROM issue_token WHERE creator = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM burn WHERE address = ANY($1) AND processed = false
|
SELECT original FROM burn WHERE address = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM nft WHERE creator = ANY($1) AND processed = false
|
SELECT original FROM nft WHERE creator = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM marketing WHERE advertiser = ANY($1) AND processed = false
|
SELECT original FROM marketing WHERE advertiser = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM vanity_address WHERE address = ANY($1) AND processed = false
|
SELECT original FROM vanity_address WHERE address = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM swap WHERE sender1 = ANY($1) AND processed = false
|
SELECT original FROM swap WHERE sender1 = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM swap WHERE sender2 = ANY($1) AND processed = false
|
SELECT original FROM swap WHERE sender2 = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM loan_contract WHERE lender = ANY($1) AND processed = false
|
SELECT original FROM loan_contract WHERE lender = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM loan_contract WHERE borrower = ANY($1) AND processed = false
|
SELECT original FROM loan_contract WHERE borrower = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM loan_payment WHERE address = ANY($1) AND processed = false
|
SELECT original FROM loan_payment WHERE address = ANY($1) AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM collateral_claim WHERE address = ANY($1) AND processed = false
|
SELECT original FROM collateral_claim WHERE address = ANY($1) AND processed = false
|
||||||
) AS subquery;
|
) AS subquery;
|
||||||
"#,
|
"#,
|
||||||
&[&addresses],
|
&[&addresses],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_) => return RpcResponse::Binary(Vec::new()),
|
Err(_) => return RpcResponse::Binary(Vec::new()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
for row in rows {
|
for row in rows {
|
||||||
let chunk: Vec<u8> = row.get(0);
|
let chunk: Vec<u8> = row.get(0);
|
||||||
bytes.extend(chunk);
|
bytes.extend(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcResponse::Binary(bytes)
|
RpcResponse::Binary(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn largest_fee() -> RpcResponse {
|
pub async fn largest_fee() -> RpcResponse {
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
|
|
||||||
// Swaps have two possible fees, so both sides are included in the max.
|
// Swaps have two possible fees, so both sides are included in the max.
|
||||||
let row = match client
|
let row = match client
|
||||||
.query_one(
|
.query_one(
|
||||||
r#"
|
r#"
|
||||||
SELECT MAX(fee) AS largest_txid FROM (
|
SELECT MAX(fee) AS largest_txid FROM (
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM transfer
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM transfer
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM token
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM token
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM issue_token
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM issue_token
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM burn
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM burn
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM nft
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM nft
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM marketing
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM marketing
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM vanity_address
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM vanity_address
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee1) AS BIGINT) AS fee FROM swap
|
SELECT CAST(MAX(fee1) AS BIGINT) AS fee FROM swap
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee2) AS BIGINT) AS fee FROM swap
|
SELECT CAST(MAX(fee2) AS BIGINT) AS fee FROM swap
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM loan_contract
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM loan_contract
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM loan_payment
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM loan_payment
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM collateral_claim
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM collateral_claim
|
||||||
) AS combined_max_txids;
|
) AS combined_max_txids;
|
||||||
"#,
|
"#,
|
||||||
&[],
|
&[],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_) => return RpcResponse::Binary(0u32.to_le_bytes().to_vec()),
|
Err(_) => return RpcResponse::Binary(0u32.to_le_bytes().to_vec()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_fee: Option<i64> = row.get(0);
|
let max_fee: Option<i64> = row.get(0);
|
||||||
let fee = (max_fee.unwrap_or(0) as u64).to_le_bytes().to_vec();
|
let fee = (max_fee.unwrap_or(0) as u64).to_le_bytes().to_vec();
|
||||||
|
|
||||||
RpcResponse::Binary(fee)
|
RpcResponse::Binary(fee)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,49 +242,49 @@ async fn pending_saved_loan_payment_balance(
|
||||||
pub async fn get_coin_balance(
|
pub async fn get_coin_balance(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
address: &str,
|
address: &str,
|
||||||
coin: &str,
|
coin: &str,
|
||||||
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
// Pending-balance checks use canonical addresses so vanity and short
|
// Pending-balance checks use canonical addresses so vanity and short
|
||||||
// address inputs see the same outgoing obligations.
|
// address inputs see the same outgoing obligations.
|
||||||
let addresses = canonical_mempool_addresses(db, address);
|
let addresses = canonical_mempool_addresses(db, address);
|
||||||
let (asset_name, nft_series) = nft_asset_parts(coin);
|
let (asset_name, nft_series) = nft_asset_parts(coin);
|
||||||
let nft_series = nft_series as i32;
|
let nft_series = nft_series as i32;
|
||||||
|
|
||||||
let row = client
|
let row = client
|
||||||
.query_one(
|
.query_one(
|
||||||
r#"
|
r#"
|
||||||
SELECT CAST((
|
SELECT CAST((
|
||||||
COALESCE((SELECT SUM(t.value)
|
COALESCE((SELECT SUM(t.value)
|
||||||
FROM transfer t
|
FROM transfer t
|
||||||
WHERE t.sender = ANY($1) AND t.coin = $2 AND t.nft_series = $3 AND t.processed = false), 0)
|
WHERE t.sender = ANY($1) AND t.coin = $2 AND t.nft_series = $3 AND t.processed = false), 0)
|
||||||
|
|
||||||
+ COALESCE((SELECT SUM(tok.number)
|
+ COALESCE((SELECT SUM(tok.number)
|
||||||
FROM token tok
|
FROM token tok
|
||||||
WHERE tok.creator = ANY($1) AND tok.ticker = $2 AND tok.processed = false), 0)
|
WHERE tok.creator = ANY($1) AND tok.ticker = $2 AND tok.processed = false), 0)
|
||||||
|
|
||||||
+ COALESCE((SELECT SUM(it.number)
|
+ COALESCE((SELECT SUM(it.number)
|
||||||
FROM issue_token it
|
FROM issue_token it
|
||||||
WHERE it.creator = ANY($1) AND it.ticker = $2 AND it.processed = false), 0)
|
WHERE it.creator = ANY($1) AND it.ticker = $2 AND it.processed = false), 0)
|
||||||
|
|
||||||
+ COALESCE((SELECT SUM(b.value)
|
+ COALESCE((SELECT SUM(b.value)
|
||||||
FROM burn b
|
FROM burn b
|
||||||
WHERE b.address = ANY($1) AND b.coin = $2 AND b.nft_series = $3 AND b.processed = false), 0)
|
WHERE b.address = ANY($1) AND b.coin = $2 AND b.nft_series = $3 AND b.processed = false), 0)
|
||||||
|
|
||||||
+ COALESCE((SELECT SUM(s.value1)
|
+ COALESCE((SELECT SUM(s.value1)
|
||||||
FROM swap s
|
FROM swap s
|
||||||
WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.nft_series1 = $3 AND s.processed = false), 0)
|
WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.nft_series1 = $3 AND s.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(s.tip1)
|
+ COALESCE((SELECT SUM(s.tip1)
|
||||||
FROM swap s
|
FROM swap s
|
||||||
WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.nft_series1 = $3 AND s.processed = false), 0)
|
WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.nft_series1 = $3 AND s.processed = false), 0)
|
||||||
|
|
||||||
+ COALESCE((SELECT SUM(s.value2)
|
+ COALESCE((SELECT SUM(s.value2)
|
||||||
FROM swap s
|
FROM swap s
|
||||||
WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.nft_series2 = $3 AND s.processed = false), 0)
|
WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.nft_series2 = $3 AND s.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(s.tip2)
|
+ COALESCE((SELECT SUM(s.tip2)
|
||||||
FROM swap s
|
FROM swap s
|
||||||
WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.nft_series2 = $3 AND s.processed = false), 0)
|
WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.nft_series2 = $3 AND s.processed = false), 0)
|
||||||
|
|
||||||
+ COALESCE((SELECT SUM(lc.loan_amount)
|
+ COALESCE((SELECT SUM(lc.loan_amount)
|
||||||
FROM loan_contract lc
|
FROM loan_contract lc
|
||||||
WHERE lc.lender = ANY($1) AND lc.loan_coin = $2 AND lc.processed = false), 0)
|
WHERE lc.lender = ANY($1) AND lc.loan_coin = $2 AND lc.processed = false), 0)
|
||||||
|
|
@ -292,25 +292,25 @@ pub async fn get_coin_balance(
|
||||||
+ COALESCE((SELECT SUM(lc.collateral_amount)
|
+ COALESCE((SELECT SUM(lc.collateral_amount)
|
||||||
FROM loan_contract lc
|
FROM loan_contract lc
|
||||||
WHERE lc.borrower = ANY($1) AND lc.collateral = $2 AND lc.processed = false), 0)
|
WHERE lc.borrower = ANY($1) AND lc.collateral = $2 AND lc.processed = false), 0)
|
||||||
|
|
||||||
+ COALESCE((
|
+ COALESCE((
|
||||||
SELECT SUM(lp.payback_amount)
|
SELECT SUM(lp.payback_amount)
|
||||||
FROM loan_payment lp
|
FROM loan_payment lp
|
||||||
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
||||||
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
||||||
), 0)
|
), 0)
|
||||||
|
|
||||||
+ COALESCE((
|
+ COALESCE((
|
||||||
SELECT SUM(lp.tip)
|
SELECT SUM(lp.tip)
|
||||||
FROM loan_payment lp
|
FROM loan_payment lp
|
||||||
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
||||||
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
||||||
), 0)
|
), 0)
|
||||||
|
|
||||||
) AS BIGINT) AS total
|
) AS BIGINT) AS total
|
||||||
"#,
|
"#,
|
||||||
&[&addresses, &asset_name, &nft_series],
|
&[&addresses, &asset_name, &nft_series],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Negative projections are clamped because callers only need the amount
|
// Negative projections are clamped because callers only need the amount
|
||||||
|
|
@ -319,10 +319,10 @@ pub async fn get_coin_balance(
|
||||||
let chain_loan_payments = pending_saved_loan_payment_balance(db, &addresses, coin).await?;
|
let chain_loan_payments = pending_saved_loan_payment_balance(db, &addresses, coin).await?;
|
||||||
Ok((total.max(0) as u64).saturating_add(chain_loan_payments))
|
Ok((total.max(0) as u64).saturating_add(chain_loan_payments))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_basecoin_balance(
|
pub async fn get_basecoin_balance(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
address: &str,
|
address: &str,
|
||||||
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
let addresses = canonical_mempool_addresses(db, address);
|
let addresses = canonical_mempool_addresses(db, address);
|
||||||
|
|
@ -330,9 +330,9 @@ pub async fn get_basecoin_balance(
|
||||||
// Base coin projection includes direct base transfers plus all fees and
|
// Base coin projection includes direct base transfers plus all fees and
|
||||||
// any pending loan/swap movements denominated in the base coin.
|
// any pending loan/swap movements denominated in the base coin.
|
||||||
let row = client
|
let row = client
|
||||||
.query_one(
|
.query_one(
|
||||||
r#"
|
r#"
|
||||||
SELECT CAST((
|
SELECT CAST((
|
||||||
COALESCE((SELECT SUM(t.value)
|
COALESCE((SELECT SUM(t.value)
|
||||||
FROM transfer t
|
FROM transfer t
|
||||||
WHERE t.sender = ANY($1) AND t.coin = $2 AND t.processed = false), 0)
|
WHERE t.sender = ANY($1) AND t.coin = $2 AND t.processed = false), 0)
|
||||||
|
|
@ -341,50 +341,49 @@ pub async fn get_basecoin_balance(
|
||||||
FROM burn b
|
FROM burn b
|
||||||
WHERE b.address = ANY($1) AND b.coin = $2 AND b.processed = false), 0)
|
WHERE b.address = ANY($1) AND b.coin = $2 AND b.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(x.fee) FROM token x WHERE x.creator = ANY($1) AND x.processed = false), 0)
|
+ COALESCE((SELECT SUM(x.fee) FROM token x WHERE x.creator = ANY($1) AND x.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(it.fee) FROM issue_token it WHERE it.creator = ANY($1) AND it.processed = false), 0)
|
+ COALESCE((SELECT SUM(it.fee) FROM issue_token it WHERE it.creator = ANY($1) AND it.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(b.fee) FROM burn b WHERE b.address = ANY($1) AND b.processed = false), 0)
|
+ COALESCE((SELECT SUM(b.fee) FROM burn b WHERE b.address = ANY($1) AND b.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(n.fee) FROM nft n WHERE n.creator = ANY($1) AND n.processed = false), 0)
|
+ COALESCE((SELECT SUM(n.fee) FROM nft n WHERE n.creator = ANY($1) AND n.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(m.fee) FROM marketing m WHERE m.advertiser = ANY($1) AND m.processed = false), 0)
|
+ COALESCE((SELECT SUM(m.fee) FROM marketing m WHERE m.advertiser = ANY($1) AND m.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(v.fee) FROM vanity_address v WHERE v.address = ANY($1) AND v.processed = false), 0)
|
+ COALESCE((SELECT SUM(v.fee) FROM vanity_address v WHERE v.address = ANY($1) AND v.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(cc.fee) FROM collateral_claim cc WHERE cc.address = ANY($1) AND cc.processed = false), 0)
|
+ COALESCE((SELECT SUM(cc.fee) FROM collateral_claim cc WHERE cc.address = ANY($1) AND cc.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(lc.fee) FROM loan_contract lc WHERE lc.lender = ANY($1) AND lc.processed = false), 0)
|
+ COALESCE((SELECT SUM(lc.fee) FROM loan_contract lc WHERE lc.lender = ANY($1) AND lc.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(lp.fee) FROM loan_payment lp WHERE lp.address = ANY($1) AND lp.processed = false), 0)
|
+ COALESCE((SELECT SUM(lp.fee) FROM loan_payment lp WHERE lp.address = ANY($1) AND lp.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(lc.loan_amount)
|
+ COALESCE((SELECT SUM(lc.loan_amount)
|
||||||
FROM loan_contract lc
|
FROM loan_contract lc
|
||||||
WHERE lc.lender = ANY($1) AND lc.loan_coin = $2 AND lc.processed = false), 0)
|
WHERE lc.lender = ANY($1) AND lc.loan_coin = $2 AND lc.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(lc.collateral_amount)
|
+ COALESCE((SELECT SUM(lc.collateral_amount)
|
||||||
FROM loan_contract lc
|
FROM loan_contract lc
|
||||||
WHERE lc.borrower = ANY($1) AND lc.collateral = $2 AND lc.processed = false), 0)
|
WHERE lc.borrower = ANY($1) AND lc.collateral = $2 AND lc.processed = false), 0)
|
||||||
+ COALESCE((
|
+ COALESCE((
|
||||||
SELECT SUM(lp.payback_amount)
|
SELECT SUM(lp.payback_amount)
|
||||||
FROM loan_payment lp
|
FROM loan_payment lp
|
||||||
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
||||||
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
||||||
), 0)
|
), 0)
|
||||||
+ COALESCE((
|
+ COALESCE((
|
||||||
SELECT SUM(lp.tip)
|
SELECT SUM(lp.tip)
|
||||||
FROM loan_payment lp
|
FROM loan_payment lp
|
||||||
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
JOIN loan_contract lc ON lc.txid = lp.contract_hash
|
||||||
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
WHERE lp.address = ANY($1) AND lc.loan_coin = $2 AND lp.processed = false AND lc.processed = false
|
||||||
), 0)
|
), 0)
|
||||||
+ COALESCE((SELECT SUM(s.fee1) FROM swap s WHERE s.sender1 = ANY($1) AND s.processed = false), 0)
|
+ COALESCE((SELECT SUM(s.fee1) FROM swap s WHERE s.sender1 = ANY($1) AND s.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(s.fee2) FROM swap s WHERE s.sender2 = ANY($1) AND s.processed = false), 0)
|
+ COALESCE((SELECT SUM(s.fee2) FROM swap s WHERE s.sender2 = ANY($1) AND s.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(s.value1) FROM swap s WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.processed = false), 0)
|
+ COALESCE((SELECT SUM(s.value1) FROM swap s WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(s.tip1) FROM swap s WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.processed = false), 0)
|
+ COALESCE((SELECT SUM(s.tip1) FROM swap s WHERE s.sender1 = ANY($1) AND s.ticker1 = $2 AND s.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(s.value2) FROM swap s WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.processed = false), 0)
|
+ COALESCE((SELECT SUM(s.value2) FROM swap s WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.processed = false), 0)
|
||||||
+ COALESCE((SELECT SUM(s.tip2) FROM swap s WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.processed = false), 0)
|
+ COALESCE((SELECT SUM(s.tip2) FROM swap s WHERE s.sender2 = ANY($1) AND s.ticker2 = $2 AND s.processed = false), 0)
|
||||||
) AS BIGINT) AS total
|
) AS BIGINT) AS total
|
||||||
"#,
|
"#,
|
||||||
&[&addresses, &*BASECOIN],
|
&[&addresses, &*BASECOIN],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let total: i64 = row.get(0);
|
let total: i64 = row.get(0);
|
||||||
let chain_loan_payments =
|
let chain_loan_payments = pending_saved_loan_payment_balance(db, &addresses, &BASECOIN).await?;
|
||||||
pending_saved_loan_payment_balance(db, &addresses, &BASECOIN).await?;
|
|
||||||
Ok((total.max(0) as u64).saturating_add(chain_loan_payments))
|
Ok((total.max(0) as u64).saturating_add(chain_loan_payments))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_pending_payments_for_contract(
|
pub async fn get_pending_payments_for_contract(
|
||||||
contract_hash: &str,
|
contract_hash: &str,
|
||||||
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
@ -393,63 +392,62 @@ pub async fn get_pending_payments_for_contract(
|
||||||
// Loan verification uses this to prevent pending payments from exceeding
|
// Loan verification uses this to prevent pending payments from exceeding
|
||||||
// what the contract still owes.
|
// what the contract still owes.
|
||||||
let row = client
|
let row = client
|
||||||
.query_one(
|
.query_one(
|
||||||
r#"
|
r#"
|
||||||
SELECT CAST(COALESCE(SUM(lp.payback_amount), 0) AS BIGINT) AS total
|
SELECT CAST(COALESCE(SUM(lp.payback_amount), 0) AS BIGINT) AS total
|
||||||
FROM loan_payment lp
|
FROM loan_payment lp
|
||||||
WHERE lp.contract_hash = $1
|
WHERE lp.contract_hash = $1
|
||||||
AND lp.processed = false
|
AND lp.processed = false
|
||||||
"#,
|
"#,
|
||||||
&[&contract_hash],
|
&[&contract_hash],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let total: i64 = row.get(0);
|
let total: i64 = row.get(0);
|
||||||
Ok(total.max(0) as u64)
|
Ok(total.max(0) as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn total_transactions() -> RpcResponse {
|
pub async fn total_transactions() -> RpcResponse {
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
// Count rows across all mempool tables, including processed rows that may
|
// Count rows across all mempool tables, including processed rows that may
|
||||||
// still be retained briefly for orphan rollback.
|
// still be retained briefly for orphan rollback.
|
||||||
let row = match client
|
let row = match client
|
||||||
.query_one(
|
.query_one(
|
||||||
r#"
|
r#"
|
||||||
SELECT CAST(SUM(row_count) AS BIGINT) AS total_rows FROM (
|
SELECT CAST(SUM(row_count) AS BIGINT) AS total_rows FROM (
|
||||||
SELECT COUNT(*) AS row_count FROM transfer
|
SELECT COUNT(*) AS row_count FROM transfer
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM token
|
SELECT COUNT(*) AS row_count FROM token
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM issue_token
|
SELECT COUNT(*) AS row_count FROM issue_token
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM burn
|
SELECT COUNT(*) AS row_count FROM burn
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM nft
|
SELECT COUNT(*) AS row_count FROM nft
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM marketing
|
SELECT COUNT(*) AS row_count FROM marketing
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM vanity_address
|
SELECT COUNT(*) AS row_count FROM vanity_address
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM swap
|
SELECT COUNT(*) AS row_count FROM swap
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM loan_contract
|
SELECT COUNT(*) AS row_count FROM loan_contract
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM loan_payment
|
SELECT COUNT(*) AS row_count FROM loan_payment
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT COUNT(*) AS row_count FROM collateral_claim
|
SELECT COUNT(*) AS row_count FROM collateral_claim
|
||||||
) AS combined;
|
) AS combined;
|
||||||
"#,
|
"#,
|
||||||
&[],
|
&[],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(_) => return RpcResponse::Binary(vec![0; 8]),
|
Err(_) => return RpcResponse::Binary(vec![0; 8]),
|
||||||
};
|
};
|
||||||
|
|
||||||
let total: Option<i64> = row.get(0);
|
let total: Option<i64> = row.get(0);
|
||||||
let result = (total.unwrap_or(0) as u32).to_le_bytes().to_vec();
|
let result = (total.unwrap_or(0) as u32).to_le_bytes().to_vec();
|
||||||
|
|
||||||
RpcResponse::Binary(result)
|
RpcResponse::Binary(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,330 +1,326 @@
|
||||||
use crate::blocks::loans::LoanContractTransaction;
|
use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::common::binary_conversions::binary_to_string;
|
use crate::common::binary_conversions::binary_to_string;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::common::nft_assets::{nft_asset_name, nft_asset_parts};
|
use crate::common::nft_assets::{nft_asset_name, nft_asset_parts};
|
||||||
use crate::config::SETTINGS;
|
use crate::config::SETTINGS;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::lazy_static;
|
use crate::lazy_static;
|
||||||
use crate::records::memory::structs::BalanceKey;
|
use crate::records::memory::structs::BalanceKey;
|
||||||
use crate::records::wallet_registry::{
|
use crate::records::wallet_registry::resolve_canonical_registered_short_address;
|
||||||
resolve_canonical_registered_short_address,
|
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
||||||
|
use crate::rpc::responses::RpcResponse;
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
use crate::HashMap;
|
||||||
|
use crate::NoTls;
|
||||||
|
use crate::{task, AtomicBool};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use tokio_postgres::Client;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref BASECOIN: String = {
|
||||||
|
let (
|
||||||
|
_network_name,
|
||||||
|
base_coin,
|
||||||
|
_suffix,
|
||||||
|
_torrentpath,
|
||||||
|
_wallet_path,
|
||||||
|
_blockpath,
|
||||||
|
_db_path,
|
||||||
|
_balance_path,
|
||||||
|
_log_path,
|
||||||
|
) = block_extension_and_paths();
|
||||||
|
format!("{base_coin:<15}")
|
||||||
|
};
|
||||||
|
static ref CLEANUP_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static DB: OnceCell<Client> = OnceCell::new();
|
||||||
|
|
||||||
|
pub const EPOCH_ROW_CAP: i64 = 100_000;
|
||||||
|
const NFT_UNIT: i64 = 100_000_000;
|
||||||
|
const CLEANUP_DEPTH: u32 = 10;
|
||||||
|
const CLEANUP_BATCH_LIMIT: i64 = 1000;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum SelectedMempoolTransaction {
|
||||||
|
// These variants hold the minimal fields needed to score, mark, and
|
||||||
|
// later apply selected mempool transactions into a saved block.
|
||||||
|
Transfer {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
sender: String,
|
||||||
|
value: i64,
|
||||||
|
coin: String,
|
||||||
|
nft_series: i32,
|
||||||
|
receiver: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Token {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
creator: String,
|
||||||
|
number: i64,
|
||||||
|
ticker: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
IssueToken {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
creator: String,
|
||||||
|
number: i64,
|
||||||
|
ticker: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Burn {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
address: String,
|
||||||
|
coin: String,
|
||||||
|
nft_series: i32,
|
||||||
|
value: i64,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Nft {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
creator: String,
|
||||||
|
nft_name: String,
|
||||||
|
series: i16,
|
||||||
|
count: i64,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Marketing {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
advertiser: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Vanity {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
address: String,
|
||||||
|
vanity_address: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Swap {
|
||||||
|
id: i64,
|
||||||
|
fee1: i64,
|
||||||
|
fee2: i64,
|
||||||
|
ticker1: String,
|
||||||
|
nft_series1: i32,
|
||||||
|
ticker2: String,
|
||||||
|
nft_series2: i32,
|
||||||
|
value1: i64,
|
||||||
|
value2: i64,
|
||||||
|
sender1: String,
|
||||||
|
tip1: i64,
|
||||||
|
tip2: i64,
|
||||||
|
sender2: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Lender {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
loan_coin: String,
|
||||||
|
loan_amount: i64,
|
||||||
|
lender: String,
|
||||||
|
collateral: String,
|
||||||
|
collateral_amount: i64,
|
||||||
|
borrower: String,
|
||||||
|
txid: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Borrower {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
payback_amount: i64,
|
||||||
|
contract_hash: String,
|
||||||
|
address: String,
|
||||||
|
tip: i64,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
Collateral {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
address: String,
|
||||||
|
contract_hash: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct SelectedMempoolBatch {
|
||||||
|
// The selected transaction view is kept separate from the original
|
||||||
|
// serialized bytes so save paths can stream the original payloads.
|
||||||
|
transactions: Vec<SelectedMempoolTransaction>,
|
||||||
|
originals: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectedMempoolTransaction {
|
||||||
|
fn table_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SelectedMempoolTransaction::Transfer { .. } => "transfer",
|
||||||
|
SelectedMempoolTransaction::Token { .. } => "token",
|
||||||
|
SelectedMempoolTransaction::IssueToken { .. } => "issue_token",
|
||||||
|
SelectedMempoolTransaction::Burn { .. } => "burn",
|
||||||
|
SelectedMempoolTransaction::Nft { .. } => "nft",
|
||||||
|
SelectedMempoolTransaction::Marketing { .. } => "marketing",
|
||||||
|
SelectedMempoolTransaction::Vanity { .. } => "vanity_address",
|
||||||
|
SelectedMempoolTransaction::Swap { .. } => "swap",
|
||||||
|
SelectedMempoolTransaction::Lender { .. } => "loan_contract",
|
||||||
|
SelectedMempoolTransaction::Borrower { .. } => "loan_payment",
|
||||||
|
SelectedMempoolTransaction::Collateral { .. } => "collateral_claim",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> i64 {
|
||||||
|
match self {
|
||||||
|
SelectedMempoolTransaction::Transfer { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Token { id, .. }
|
||||||
|
| SelectedMempoolTransaction::IssueToken { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Burn { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Nft { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Marketing { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Vanity { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Swap { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Lender { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Borrower { id, .. }
|
||||||
|
| SelectedMempoolTransaction::Collateral { id, .. } => *id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectedMempoolBatch {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.transactions.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod lookups;
|
||||||
|
mod processing;
|
||||||
|
mod schema;
|
||||||
|
mod selection;
|
||||||
|
|
||||||
|
pub use lookups::{
|
||||||
|
get_basecoin_balance, get_coin_balance, get_pending_payments_for_contract, largest_fee,
|
||||||
|
signature_exists, total_transactions, transaction_by_signature, transactions_by_address,
|
||||||
};
|
};
|
||||||
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
|
||||||
use crate::rpc::responses::RpcResponse;
|
|
||||||
use crate::sled::Db;
|
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::HashMap;
|
|
||||||
use crate::NoTls;
|
|
||||||
use crate::{task, AtomicBool};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
|
||||||
use tokio_postgres::Client;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref BASECOIN: String = {
|
|
||||||
let (
|
|
||||||
_network_name,
|
|
||||||
base_coin,
|
|
||||||
_suffix,
|
|
||||||
_torrentpath,
|
|
||||||
_wallet_path,
|
|
||||||
_blockpath,
|
|
||||||
_db_path,
|
|
||||||
_balance_path,
|
|
||||||
_log_path,
|
|
||||||
) = block_extension_and_paths();
|
|
||||||
format!("{base_coin:<15}")
|
|
||||||
};
|
|
||||||
static ref CLEANUP_RUNNING: AtomicBool = AtomicBool::new(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static DB: OnceCell<Client> = OnceCell::new();
|
|
||||||
|
|
||||||
pub const EPOCH_ROW_CAP: i64 = 100_000;
|
|
||||||
const NFT_UNIT: i64 = 100_000_000;
|
|
||||||
const CLEANUP_DEPTH: u32 = 10;
|
|
||||||
const CLEANUP_BATCH_LIMIT: i64 = 1000;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
enum SelectedMempoolTransaction {
|
|
||||||
// These variants hold the minimal fields needed to score, mark, and
|
|
||||||
// later apply selected mempool transactions into a saved block.
|
|
||||||
Transfer {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
sender: String,
|
|
||||||
value: i64,
|
|
||||||
coin: String,
|
|
||||||
nft_series: i32,
|
|
||||||
receiver: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Token {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
creator: String,
|
|
||||||
number: i64,
|
|
||||||
ticker: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
IssueToken {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
creator: String,
|
|
||||||
number: i64,
|
|
||||||
ticker: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Burn {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
address: String,
|
|
||||||
coin: String,
|
|
||||||
nft_series: i32,
|
|
||||||
value: i64,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Nft {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
creator: String,
|
|
||||||
nft_name: String,
|
|
||||||
series: i16,
|
|
||||||
count: i64,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Marketing {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
advertiser: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Vanity {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
address: String,
|
|
||||||
vanity_address: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Swap {
|
|
||||||
id: i64,
|
|
||||||
fee1: i64,
|
|
||||||
fee2: i64,
|
|
||||||
ticker1: String,
|
|
||||||
nft_series1: i32,
|
|
||||||
ticker2: String,
|
|
||||||
nft_series2: i32,
|
|
||||||
value1: i64,
|
|
||||||
value2: i64,
|
|
||||||
sender1: String,
|
|
||||||
tip1: i64,
|
|
||||||
tip2: i64,
|
|
||||||
sender2: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Lender {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
loan_coin: String,
|
|
||||||
loan_amount: i64,
|
|
||||||
lender: String,
|
|
||||||
collateral: String,
|
|
||||||
collateral_amount: i64,
|
|
||||||
borrower: String,
|
|
||||||
txid: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Borrower {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
payback_amount: i64,
|
|
||||||
contract_hash: String,
|
|
||||||
address: String,
|
|
||||||
tip: i64,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
Collateral {
|
|
||||||
id: i64,
|
|
||||||
fee: i64,
|
|
||||||
address: String,
|
|
||||||
contract_hash: String,
|
|
||||||
hash: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct SelectedMempoolBatch {
|
|
||||||
// The selected transaction view is kept separate from the original
|
|
||||||
// serialized bytes so save paths can stream the original payloads.
|
|
||||||
transactions: Vec<SelectedMempoolTransaction>,
|
|
||||||
originals: Vec<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectedMempoolTransaction {
|
|
||||||
fn table_name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
SelectedMempoolTransaction::Transfer { .. } => "transfer",
|
|
||||||
SelectedMempoolTransaction::Token { .. } => "token",
|
|
||||||
SelectedMempoolTransaction::IssueToken { .. } => "issue_token",
|
|
||||||
SelectedMempoolTransaction::Burn { .. } => "burn",
|
|
||||||
SelectedMempoolTransaction::Nft { .. } => "nft",
|
|
||||||
SelectedMempoolTransaction::Marketing { .. } => "marketing",
|
|
||||||
SelectedMempoolTransaction::Vanity { .. } => "vanity_address",
|
|
||||||
SelectedMempoolTransaction::Swap { .. } => "swap",
|
|
||||||
SelectedMempoolTransaction::Lender { .. } => "loan_contract",
|
|
||||||
SelectedMempoolTransaction::Borrower { .. } => "loan_payment",
|
|
||||||
SelectedMempoolTransaction::Collateral { .. } => "collateral_claim",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn id(&self) -> i64 {
|
|
||||||
match self {
|
|
||||||
SelectedMempoolTransaction::Transfer { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Token { id, .. }
|
|
||||||
| SelectedMempoolTransaction::IssueToken { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Burn { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Nft { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Marketing { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Vanity { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Swap { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Lender { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Borrower { id, .. }
|
|
||||||
| SelectedMempoolTransaction::Collateral { id, .. } => *id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectedMempoolBatch {
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.transactions.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mod lookups;
|
|
||||||
mod processing;
|
|
||||||
mod schema;
|
|
||||||
mod selection;
|
|
||||||
|
|
||||||
pub use lookups::{
|
|
||||||
get_basecoin_balance, get_coin_balance, get_pending_payments_for_contract, largest_fee,
|
|
||||||
signature_exists, total_transactions, transaction_by_signature, transactions_by_address,
|
|
||||||
};
|
|
||||||
pub use processing::{
|
pub use processing::{
|
||||||
mark_processed_by_signatures, mark_selected_transactions_processed,
|
delete_by_signatures, mark_processed_by_signatures, mark_selected_transactions_processed,
|
||||||
restore_processed_by_signatures, restore_selected_transactions_processed,
|
restore_processed_by_signatures, restore_selected_transactions_processed,
|
||||||
spawn_processed_cleanup, delete_by_signatures,
|
spawn_processed_cleanup,
|
||||||
};
|
};
|
||||||
pub use schema::{clear_mempool, init_db, setup_mempool};
|
pub use schema::{clear_mempool, init_db, setup_mempool};
|
||||||
pub use selection::{
|
pub use selection::{
|
||||||
apply_selected_transaction_math, clear_selected_transaction_sql, delete_selected_transactions,
|
apply_selected_transaction_math, clear_selected_transaction_sql, delete_selected_transactions,
|
||||||
select_transactions_for_block, stream_selected_transaction_originals,
|
select_transactions_for_block, stream_selected_transaction_originals,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn required_string(row: &tokio_postgres::Row, column: &str) -> Result<String> {
|
fn required_string(row: &tokio_postgres::Row, column: &str) -> Result<String> {
|
||||||
row.try_get::<_, Option<String>>(column)?
|
row.try_get::<_, Option<String>>(column)?
|
||||||
.ok_or_else(|| anyhow!("Missing required column {column}"))
|
.ok_or_else(|| anyhow!("Missing required column {column}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_balance_change(
|
fn add_balance_change(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
balance_changes: &mut HashMap<BalanceKey, i64>,
|
balance_changes: &mut HashMap<BalanceKey, i64>,
|
||||||
address: &str,
|
address: &str,
|
||||||
coin: &str,
|
coin: &str,
|
||||||
delta: i64,
|
delta: i64,
|
||||||
) {
|
) {
|
||||||
add_balance_change_bytes(
|
add_balance_change_bytes(
|
||||||
balance_changes,
|
balance_changes,
|
||||||
address_key_bytes(db, address),
|
address_key_bytes(db, address),
|
||||||
coin.as_bytes().to_vec(),
|
coin.as_bytes().to_vec(),
|
||||||
delta,
|
delta,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_balance_change_bytes(
|
fn add_balance_change_bytes(
|
||||||
balance_changes: &mut HashMap<BalanceKey, i64>,
|
balance_changes: &mut HashMap<BalanceKey, i64>,
|
||||||
address: Vec<u8>,
|
address: Vec<u8>,
|
||||||
coin: Vec<u8>,
|
coin: Vec<u8>,
|
||||||
delta: i64,
|
delta: i64,
|
||||||
) {
|
) {
|
||||||
*balance_changes
|
*balance_changes
|
||||||
.entry(BalanceKey { address, coin })
|
.entry(BalanceKey { address, coin })
|
||||||
.or_insert(0) += delta;
|
.or_insert(0) += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn address_key_bytes(db: &Db, address: &str) -> Vec<u8> {
|
fn address_key_bytes(db: &Db, address: &str) -> Vec<u8> {
|
||||||
resolve_canonical_registered_short_address(db, address)
|
resolve_canonical_registered_short_address(db, address)
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
.or_else(|| Wallet::normalize_to_short_address(address))
|
.or_else(|| Wallet::normalize_to_short_address(address))
|
||||||
.map(|normalized| normalized.as_bytes().to_vec())
|
.map(|normalized| normalized.as_bytes().to_vec())
|
||||||
.unwrap_or_else(|| address.as_bytes().to_vec())
|
.unwrap_or_else(|| address.as_bytes().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonical_mempool_addresses(db: &Db, address: &str) -> Vec<String> {
|
fn canonical_mempool_addresses(db: &Db, address: &str) -> Vec<String> {
|
||||||
let canonical = resolve_canonical_registered_short_address(db, address)
|
let canonical = resolve_canonical_registered_short_address(db, address)
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
.or_else(|| Wallet::normalize_to_short_address(address))
|
.or_else(|| Wallet::normalize_to_short_address(address))
|
||||||
.unwrap_or_else(|| address.to_string());
|
.unwrap_or_else(|| address.to_string());
|
||||||
|
|
||||||
vec![canonical]
|
vec![canonical]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_loan_details(db: &Db, contract_hash: &str) -> Result<(Vec<u8>, Vec<u8>)> {
|
async fn resolve_loan_details(db: &Db, contract_hash: &str) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||||
let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, decode(contract_hash)?).await;
|
let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, decode(contract_hash)?).await;
|
||||||
if bytes.is_empty() || bytes[0] != 7 {
|
if bytes.is_empty() || bytes[0] != 7 {
|
||||||
return Ok((Vec::new(), Vec::new()));
|
return Ok((Vec::new(), Vec::new()));
|
||||||
}
|
}
|
||||||
match LoanContractTransaction::from_bytes(7, &bytes[1..]).await {
|
match LoanContractTransaction::from_bytes(7, &bytes[1..]).await {
|
||||||
Ok(loan) => Ok((
|
Ok(loan) => Ok((
|
||||||
loan.unsigned_loan_contract.loan_coin.as_bytes().to_vec(),
|
loan.unsigned_loan_contract.loan_coin.as_bytes().to_vec(),
|
||||||
address_key_bytes(db, &loan.unsigned_loan_contract.lender),
|
address_key_bytes(db, &loan.unsigned_loan_contract.lender),
|
||||||
)),
|
)),
|
||||||
Err(_) => Ok((Vec::new(), Vec::new())),
|
Err(_) => Ok((Vec::new(), Vec::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_collateral_details(db: &Db, contract_hash: &str) -> Result<(Vec<u8>, i64)> {
|
async fn resolve_collateral_details(db: &Db, contract_hash: &str) -> Result<(Vec<u8>, i64)> {
|
||||||
let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, decode(contract_hash)?).await;
|
let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, decode(contract_hash)?).await;
|
||||||
if bytes.is_empty() || bytes[0] != 7 {
|
if bytes.is_empty() || bytes[0] != 7 {
|
||||||
return Ok((Vec::new(), 0));
|
return Ok((Vec::new(), 0));
|
||||||
}
|
}
|
||||||
match LoanContractTransaction::from_bytes(7, &bytes[1..]).await {
|
match LoanContractTransaction::from_bytes(7, &bytes[1..]).await {
|
||||||
Ok(loan) => Ok((
|
Ok(loan) => Ok((
|
||||||
loan.unsigned_loan_contract.collateral.as_bytes().to_vec(),
|
loan.unsigned_loan_contract.collateral.as_bytes().to_vec(),
|
||||||
loan.unsigned_loan_contract.collateral_amount as i64,
|
loan.unsigned_loan_contract.collateral_amount as i64,
|
||||||
)),
|
)),
|
||||||
Err(_) => Ok((Vec::new(), 0)),
|
Err(_) => Ok((Vec::new(), 0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ids_for_table(batch: &SelectedMempoolBatch, table: &str) -> Vec<i64> {
|
fn ids_for_table(batch: &SelectedMempoolBatch, table: &str) -> Vec<i64> {
|
||||||
batch
|
batch
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|tx| tx.table_name() == table)
|
.filter(|tx| tx.table_name() == table)
|
||||||
.map(SelectedMempoolTransaction::id)
|
.map(SelectedMempoolTransaction::id)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mark_rows_by_ids(
|
async fn mark_rows_by_ids(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
table: &str,
|
table: &str,
|
||||||
ids: &[i64],
|
ids: &[i64],
|
||||||
block_number: i32,
|
block_number: i32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if ids.is_empty() {
|
if ids.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let statement = format!(
|
let statement =
|
||||||
"UPDATE {table} SET processed=true, processed_block_number=$1 WHERE id = ANY($2)"
|
format!("UPDATE {table} SET processed=true, processed_block_number=$1 WHERE id = ANY($2)");
|
||||||
);
|
client.execute(&statement, &[&block_number, &ids]).await?;
|
||||||
client.execute(&statement, &[&block_number, &ids]).await?;
|
Ok(())
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unmark_rows_by_ids(client: &Client, table: &str, ids: &[i64]) -> Result<()> {
|
async fn unmark_rows_by_ids(client: &Client, table: &str, ids: &[i64]) -> Result<()> {
|
||||||
|
|
@ -332,75 +328,76 @@ async fn unmark_rows_by_ids(client: &Client, table: &str, ids: &[i64]) -> Result
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let statement =
|
let statement = format!(
|
||||||
format!("UPDATE {table} SET processed=false, processed_block_number=NULL WHERE id = ANY($1)");
|
"UPDATE {table} SET processed=false, processed_block_number=NULL WHERE id = ANY($1)"
|
||||||
|
);
|
||||||
client.execute(&statement, &[&ids]).await?;
|
client.execute(&statement, &[&ids]).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_rows(client: &Client, table: &str, ids: &[i64]) -> Result<()> {
|
async fn delete_rows(client: &Client, table: &str, ids: &[i64]) -> Result<()> {
|
||||||
if ids.is_empty() {
|
if ids.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let statement = format!("DELETE FROM {table} WHERE id = ANY($1)");
|
let statement = format!("DELETE FROM {table} WHERE id = ANY($1)");
|
||||||
client.execute(&statement, &[&ids]).await?;
|
client.execute(&statement, &[&ids]).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unmark_by_signatures(
|
async fn unmark_by_signatures(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
table: &str,
|
table: &str,
|
||||||
signature_column: &str,
|
signature_column: &str,
|
||||||
signatures: &[String],
|
signatures: &[String],
|
||||||
) -> Result<u64> {
|
) -> Result<u64> {
|
||||||
let statement = format!(
|
let statement = format!(
|
||||||
"UPDATE {table} SET processed=false, processed_block_number=NULL WHERE {signature_column} = ANY($1) AND processed = true"
|
"UPDATE {table} SET processed=false, processed_block_number=NULL WHERE {signature_column} = ANY($1) AND processed = true"
|
||||||
);
|
);
|
||||||
Ok(client.execute(&statement, &[&signatures]).await?)
|
Ok(client.execute(&statement, &[&signatures]).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_processed_before_or_at(block_number: u32, limit: i64) -> Result<()> {
|
async fn delete_processed_before_or_at(block_number: u32, limit: i64) -> Result<()> {
|
||||||
// Periodic cleanup deletes processed mempool rows in bounded batches
|
// Periodic cleanup deletes processed mempool rows in bounded batches
|
||||||
// so long-lived nodes do not accumulate infinite processed history.
|
// so long-lived nodes do not accumulate infinite processed history.
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
let bn = block_number as i32;
|
let bn = block_number as i32;
|
||||||
|
|
||||||
delete_processed_rows_limited(client, "transfer", bn, limit).await?;
|
delete_processed_rows_limited(client, "transfer", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "token", bn, limit).await?;
|
delete_processed_rows_limited(client, "token", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "issue_token", bn, limit).await?;
|
delete_processed_rows_limited(client, "issue_token", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "burn", bn, limit).await?;
|
delete_processed_rows_limited(client, "burn", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "nft", bn, limit).await?;
|
delete_processed_rows_limited(client, "nft", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "marketing", bn, limit).await?;
|
delete_processed_rows_limited(client, "marketing", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "vanity_address", bn, limit).await?;
|
delete_processed_rows_limited(client, "vanity_address", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "swap", bn, limit).await?;
|
delete_processed_rows_limited(client, "swap", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "loan_contract", bn, limit).await?;
|
delete_processed_rows_limited(client, "loan_contract", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "loan_payment", bn, limit).await?;
|
delete_processed_rows_limited(client, "loan_payment", bn, limit).await?;
|
||||||
delete_processed_rows_limited(client, "collateral_claim", bn, limit).await?;
|
delete_processed_rows_limited(client, "collateral_claim", bn, limit).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_processed_rows_limited(
|
async fn delete_processed_rows_limited(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
table: &str,
|
table: &str,
|
||||||
block_number: i32,
|
block_number: i32,
|
||||||
limit: i64,
|
limit: i64,
|
||||||
) -> Result<u64> {
|
) -> Result<u64> {
|
||||||
let statement = format!(
|
let statement = format!(
|
||||||
r#"
|
r#"
|
||||||
DELETE FROM {table}
|
DELETE FROM {table}
|
||||||
WHERE id IN (
|
WHERE id IN (
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM {table}
|
FROM {table}
|
||||||
WHERE processed = true
|
WHERE processed = true
|
||||||
AND processed_block_number IS NOT NULL
|
AND processed_block_number IS NOT NULL
|
||||||
AND processed_block_number <= $1
|
AND processed_block_number <= $1
|
||||||
ORDER BY processed_block_number ASC, id ASC
|
ORDER BY processed_block_number ASC, id ASC
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
)
|
)
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(client.execute(&statement, &[&block_number, &limit]).await?)
|
Ok(client.execute(&statement, &[&block_number, &limit]).await?)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,57 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub async fn mark_selected_transactions_processed(
|
pub async fn mark_selected_transactions_processed(
|
||||||
batch: &SelectedMempoolBatch,
|
batch: &SelectedMempoolBatch,
|
||||||
block_number: u32,
|
block_number: u32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Mark each selected mempool row as processed under the saved block
|
// Mark each selected mempool row as processed under the saved block
|
||||||
// number so it can be cleaned up or restored later if needed.
|
// number so it can be cleaned up or restored later if needed.
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
let bn = block_number as i32;
|
let bn = block_number as i32;
|
||||||
|
|
||||||
// Selected batches are grouped by table, then marked with one UPDATE per
|
// Selected batches are grouped by table, then marked with one UPDATE per
|
||||||
// table instead of touching rows one at a time.
|
// table instead of touching rows one at a time.
|
||||||
mark_rows_by_ids(client, "transfer", &ids_for_table(batch, "transfer"), bn).await?;
|
mark_rows_by_ids(client, "transfer", &ids_for_table(batch, "transfer"), bn).await?;
|
||||||
mark_rows_by_ids(client, "token", &ids_for_table(batch, "token"), bn).await?;
|
mark_rows_by_ids(client, "token", &ids_for_table(batch, "token"), bn).await?;
|
||||||
mark_rows_by_ids(
|
mark_rows_by_ids(
|
||||||
client,
|
client,
|
||||||
"issue_token",
|
"issue_token",
|
||||||
&ids_for_table(batch, "issue_token"),
|
&ids_for_table(batch, "issue_token"),
|
||||||
bn,
|
bn,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
mark_rows_by_ids(client, "burn", &ids_for_table(batch, "burn"), bn).await?;
|
mark_rows_by_ids(client, "burn", &ids_for_table(batch, "burn"), bn).await?;
|
||||||
mark_rows_by_ids(client, "nft", &ids_for_table(batch, "nft"), bn).await?;
|
mark_rows_by_ids(client, "nft", &ids_for_table(batch, "nft"), bn).await?;
|
||||||
mark_rows_by_ids(client, "marketing", &ids_for_table(batch, "marketing"), bn).await?;
|
mark_rows_by_ids(client, "marketing", &ids_for_table(batch, "marketing"), bn).await?;
|
||||||
mark_rows_by_ids(
|
mark_rows_by_ids(
|
||||||
client,
|
client,
|
||||||
"vanity_address",
|
"vanity_address",
|
||||||
&ids_for_table(batch, "vanity_address"),
|
&ids_for_table(batch, "vanity_address"),
|
||||||
bn,
|
bn,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
mark_rows_by_ids(client, "swap", &ids_for_table(batch, "swap"), bn).await?;
|
mark_rows_by_ids(client, "swap", &ids_for_table(batch, "swap"), bn).await?;
|
||||||
mark_rows_by_ids(
|
mark_rows_by_ids(
|
||||||
client,
|
client,
|
||||||
"loan_contract",
|
"loan_contract",
|
||||||
&ids_for_table(batch, "loan_contract"),
|
&ids_for_table(batch, "loan_contract"),
|
||||||
bn,
|
bn,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
mark_rows_by_ids(
|
mark_rows_by_ids(
|
||||||
client,
|
client,
|
||||||
"loan_payment",
|
"loan_payment",
|
||||||
&ids_for_table(batch, "loan_payment"),
|
&ids_for_table(batch, "loan_payment"),
|
||||||
bn,
|
bn,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
mark_rows_by_ids(
|
mark_rows_by_ids(
|
||||||
client,
|
client,
|
||||||
"collateral_claim",
|
"collateral_claim",
|
||||||
&ids_for_table(batch, "collateral_claim"),
|
&ids_for_table(batch, "collateral_claim"),
|
||||||
bn,
|
bn,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -63,12 +63,7 @@ pub async fn restore_selected_transactions_processed(batch: &SelectedMempoolBatc
|
||||||
|
|
||||||
unmark_rows_by_ids(client, "transfer", &ids_for_table(batch, "transfer")).await?;
|
unmark_rows_by_ids(client, "transfer", &ids_for_table(batch, "transfer")).await?;
|
||||||
unmark_rows_by_ids(client, "token", &ids_for_table(batch, "token")).await?;
|
unmark_rows_by_ids(client, "token", &ids_for_table(batch, "token")).await?;
|
||||||
unmark_rows_by_ids(
|
unmark_rows_by_ids(client, "issue_token", &ids_for_table(batch, "issue_token")).await?;
|
||||||
client,
|
|
||||||
"issue_token",
|
|
||||||
&ids_for_table(batch, "issue_token"),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
unmark_rows_by_ids(client, "burn", &ids_for_table(batch, "burn")).await?;
|
unmark_rows_by_ids(client, "burn", &ids_for_table(batch, "burn")).await?;
|
||||||
unmark_rows_by_ids(client, "nft", &ids_for_table(batch, "nft")).await?;
|
unmark_rows_by_ids(client, "nft", &ids_for_table(batch, "nft")).await?;
|
||||||
unmark_rows_by_ids(client, "marketing", &ids_for_table(batch, "marketing")).await?;
|
unmark_rows_by_ids(client, "marketing", &ids_for_table(batch, "marketing")).await?;
|
||||||
|
|
@ -102,233 +97,231 @@ pub async fn restore_selected_transactions_processed(batch: &SelectedMempoolBatc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore_processed_by_signatures(signatures: &[String]) -> Result<bool> {
|
pub async fn restore_processed_by_signatures(signatures: &[String]) -> Result<bool> {
|
||||||
// Orphan correction can revive recently processed mempool rows by
|
// Orphan correction can revive recently processed mempool rows by
|
||||||
// signature when a saved block is rolled back out of the chain.
|
// signature when a saved block is rolled back out of the chain.
|
||||||
if signatures.is_empty() {
|
if signatures.is_empty() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
let mut restored = 0_u64;
|
let mut restored = 0_u64;
|
||||||
|
|
||||||
// Each table keeps its own signature columns, so rollback unmarks every
|
// Each table keeps its own signature columns, so rollback unmarks every
|
||||||
// column that could contain one of the rolled-back signatures.
|
// column that could contain one of the rolled-back signatures.
|
||||||
restored += unmark_by_signatures(client, "transfer", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "transfer", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "token", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "token", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "issue_token", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "issue_token", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "burn", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "burn", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "nft", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "nft", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "marketing", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "marketing", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "vanity_address", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "vanity_address", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "swap", "signature1", signatures).await?;
|
restored += unmark_by_signatures(client, "swap", "signature1", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "swap", "signature2", signatures).await?;
|
restored += unmark_by_signatures(client, "swap", "signature2", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "loan_contract", "signature1", signatures).await?;
|
restored += unmark_by_signatures(client, "loan_contract", "signature1", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "loan_contract", "signature2", signatures).await?;
|
restored += unmark_by_signatures(client, "loan_contract", "signature2", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "loan_payment", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "loan_payment", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures(client, "collateral_claim", "signature", signatures).await?;
|
restored += unmark_by_signatures(client, "collateral_claim", "signature", signatures).await?;
|
||||||
|
|
||||||
Ok(restored > 0)
|
Ok(restored > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_processed_cleanup(saved_block_number: u32) {
|
pub fn spawn_processed_cleanup(saved_block_number: u32) {
|
||||||
// Cleanup trails the chain tip by a small depth so recent processed
|
// Cleanup trails the chain tip by a small depth so recent processed
|
||||||
// mempool rows can still be restored during short orphan events.
|
// mempool rows can still be restored during short orphan events.
|
||||||
if saved_block_number <= CLEANUP_DEPTH {
|
if saved_block_number <= CLEANUP_DEPTH {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if CLEANUP_RUNNING
|
if CLEANUP_RUNNING
|
||||||
.compare_exchange(
|
.compare_exchange(
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
crate::AtomicOrdering::SeqCst,
|
crate::AtomicOrdering::SeqCst,
|
||||||
crate::AtomicOrdering::SeqCst,
|
crate::AtomicOrdering::SeqCst,
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
task::spawn(async move {
|
task::spawn(async move {
|
||||||
let safe_block = saved_block_number.saturating_sub(CLEANUP_DEPTH);
|
let safe_block = saved_block_number.saturating_sub(CLEANUP_DEPTH);
|
||||||
// Cleanup is deliberately delayed behind the tip so short reorgs can
|
// Cleanup is deliberately delayed behind the tip so short reorgs can
|
||||||
// still restore recently processed rows.
|
// still restore recently processed rows.
|
||||||
if let Err(err) = delete_processed_before_or_at(safe_block, CLEANUP_BATCH_LIMIT).await {
|
if let Err(err) = delete_processed_before_or_at(safe_block, CLEANUP_BATCH_LIMIT).await {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[mempool_cleanup] failed: saved_block={saved_block_number} safe_block={safe_block} err={err}"
|
"[mempool_cleanup] failed: saved_block={saved_block_number} safe_block={safe_block} err={err}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CLEANUP_RUNNING.store(false, crate::AtomicOrdering::SeqCst);
|
CLEANUP_RUNNING.store(false, crate::AtomicOrdering::SeqCst);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn mark_processed_by_signatures(signatures: &[String], block_number: u32) -> Result<()> {
|
||||||
pub async fn mark_processed_by_signatures(signatures: &[String], block_number: u32) -> Result<()> {
|
// Synced blocks arrive with signatures instead of selected-row IDs,
|
||||||
// Synced blocks arrive with signatures instead of selected-row IDs,
|
// so processed marking on the updating path works by signature.
|
||||||
// so processed marking on the updating path works by signature.
|
if signatures.is_empty() {
|
||||||
if signatures.is_empty() {
|
return Ok(());
|
||||||
return Ok(());
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
let bn = block_number as i32;
|
let bn = block_number as i32;
|
||||||
|
|
||||||
// Remote/synced blocks do not know local row IDs, so they mark by
|
// Remote/synced blocks do not know local row IDs, so they mark by
|
||||||
// transaction signatures instead.
|
// transaction signatures instead.
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE transfer SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE transfer SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE token SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE token SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE issue_token SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE issue_token SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE burn SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE burn SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE nft SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE nft SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE marketing SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE marketing SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE vanity_address SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE vanity_address SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE swap SET processed=true, processed_block_number=$1 WHERE signature1 = ANY($2)",
|
"UPDATE swap SET processed=true, processed_block_number=$1 WHERE signature1 = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE swap SET processed=true, processed_block_number=$1 WHERE signature2 = ANY($2)",
|
"UPDATE swap SET processed=true, processed_block_number=$1 WHERE signature2 = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE loan_contract SET processed=true, processed_block_number=$1 WHERE signature1 = ANY($2)",
|
"UPDATE loan_contract SET processed=true, processed_block_number=$1 WHERE signature1 = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE loan_contract SET processed=true, processed_block_number=$1 WHERE signature2 = ANY($2)",
|
"UPDATE loan_contract SET processed=true, processed_block_number=$1 WHERE signature2 = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE loan_payment SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE loan_payment SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE collateral_claim SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
"UPDATE collateral_claim SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
&[&bn, &signatures],
|
&[&bn, &signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_by_signatures(signatures: &[String]) -> Result<()> {
|
pub async fn delete_by_signatures(signatures: &[String]) -> Result<()> {
|
||||||
// Some validation failures need to remove mempool rows directly by
|
// Some validation failures need to remove mempool rows directly by
|
||||||
// signature regardless of which table the transaction lives in.
|
// signature regardless of which table the transaction lives in.
|
||||||
if signatures.is_empty() {
|
if signatures.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
|
|
||||||
// Failed validation removes every matching pending row no matter which
|
// Failed validation removes every matching pending row no matter which
|
||||||
// transaction table currently owns the signature.
|
// transaction table currently owns the signature.
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM transfer WHERE signature = ANY($1)",
|
"DELETE FROM transfer WHERE signature = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM token WHERE signature = ANY($1)",
|
"DELETE FROM token WHERE signature = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM issue_token WHERE signature = ANY($1)",
|
"DELETE FROM issue_token WHERE signature = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute("DELETE FROM burn WHERE signature = ANY($1)", &[&signatures])
|
.execute("DELETE FROM burn WHERE signature = ANY($1)", &[&signatures])
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute("DELETE FROM nft WHERE signature = ANY($1)", &[&signatures])
|
.execute("DELETE FROM nft WHERE signature = ANY($1)", &[&signatures])
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM marketing WHERE signature = ANY($1)",
|
"DELETE FROM marketing WHERE signature = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM vanity_address WHERE signature = ANY($1)",
|
"DELETE FROM vanity_address WHERE signature = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM swap WHERE signature1 = ANY($1) OR signature2 = ANY($1)",
|
"DELETE FROM swap WHERE signature1 = ANY($1) OR signature2 = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM loan_contract WHERE signature1 = ANY($1) OR signature2 = ANY($1)",
|
"DELETE FROM loan_contract WHERE signature1 = ANY($1) OR signature2 = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM loan_payment WHERE signature = ANY($1)",
|
"DELETE FROM loan_payment WHERE signature = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM collateral_claim WHERE signature = ANY($1)",
|
"DELETE FROM collateral_claim WHERE signature = ANY($1)",
|
||||||
&[&signatures],
|
&[&signatures],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub async fn init_db() -> Result<()> {
|
pub async fn init_db() -> Result<()> {
|
||||||
// Initialize the shared Postgres client used by the mempool tables.
|
// Initialize the shared Postgres client used by the mempool tables.
|
||||||
if DB.get().is_some() {
|
if DB.get().is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let password = SETTINGS
|
let password = SETTINGS
|
||||||
.pg_password
|
.pg_password
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.expect("Postgres password must be set in settings.ini");
|
.expect("Postgres password must be set in settings.ini");
|
||||||
|
|
||||||
let conn_str = format!(
|
let conn_str = format!(
|
||||||
"host={} port={} user={} password={} dbname={}",
|
"host={} port={} user={} password={} dbname={}",
|
||||||
SETTINGS.pg_host, SETTINGS.pg_port, SETTINGS.pg_user, password, SETTINGS.pg_dbname
|
SETTINGS.pg_host, SETTINGS.pg_port, SETTINGS.pg_user, password, SETTINGS.pg_dbname
|
||||||
);
|
);
|
||||||
|
|
||||||
let (client, connection) = tokio_postgres::connect(&conn_str, NoTls)
|
let (client, connection) = tokio_postgres::connect(&conn_str, NoTls)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| anyhow!("Failed to connect to Postgres: {err}"))?;
|
.map_err(|err| anyhow!("Failed to connect to Postgres: {err}"))?;
|
||||||
|
|
@ -23,228 +23,228 @@ pub async fn init_db() -> Result<()> {
|
||||||
// Keep the Postgres connection driver alive in the background for the
|
// Keep the Postgres connection driver alive in the background for the
|
||||||
// lifetime of the shared client.
|
// lifetime of the shared client.
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = connection.await {
|
if let Err(e) = connection.await {
|
||||||
eprintln!("Postgres connection error: {e}");
|
eprintln!("Postgres connection error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
DB.set(client)
|
DB.set(client)
|
||||||
.map_err(|_| anyhow!("DB already initialized"))?;
|
.map_err(|_| anyhow!("DB already initialized"))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn setup_mempool() -> Result<()> {
|
pub async fn setup_mempool() -> Result<()> {
|
||||||
// Create or migrate the mempool schema, deduplicate any stale rows,
|
// Create or migrate the mempool schema, deduplicate any stale rows,
|
||||||
// add the selection indexes, and start from an empty live mempool.
|
// add the selection indexes, and start from an empty live mempool.
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
|
|
||||||
let schema = r#"
|
let schema = r#"
|
||||||
CREATE TABLE IF NOT EXISTS transfer (
|
CREATE TABLE IF NOT EXISTS transfer (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
sender TEXT,
|
sender TEXT,
|
||||||
value BIGINT,
|
value BIGINT,
|
||||||
coin VARCHAR(15),
|
coin VARCHAR(15),
|
||||||
nft_series INTEGER NOT NULL DEFAULT 0,
|
nft_series INTEGER NOT NULL DEFAULT 0,
|
||||||
receiver TEXT NOT NULL,
|
receiver TEXT NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS token (
|
CREATE TABLE IF NOT EXISTS token (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
creator TEXT NOT NULL,
|
creator TEXT NOT NULL,
|
||||||
number BIGINT NOT NULL,
|
number BIGINT NOT NULL,
|
||||||
hard_limit SMALLINT NOT NULL DEFAULT 1,
|
hard_limit SMALLINT NOT NULL DEFAULT 1,
|
||||||
ticker VARCHAR(15) NOT NULL,
|
ticker VARCHAR(15) NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS issue_token (
|
CREATE TABLE IF NOT EXISTS issue_token (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
creator TEXT NOT NULL,
|
creator TEXT NOT NULL,
|
||||||
number BIGINT NOT NULL,
|
number BIGINT NOT NULL,
|
||||||
ticker VARCHAR(15) NOT NULL,
|
ticker VARCHAR(15) NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS burn (
|
CREATE TABLE IF NOT EXISTS burn (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
coin VARCHAR(15) NOT NULL,
|
coin VARCHAR(15) NOT NULL,
|
||||||
nft_series INTEGER NOT NULL DEFAULT 0,
|
nft_series INTEGER NOT NULL DEFAULT 0,
|
||||||
value BIGINT NOT NULL,
|
value BIGINT NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS nft (
|
CREATE TABLE IF NOT EXISTS nft (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
creator TEXT NOT NULL,
|
creator TEXT NOT NULL,
|
||||||
nft_name VARCHAR(15),
|
nft_name VARCHAR(15),
|
||||||
series SMALLINT NOT NULL,
|
series SMALLINT NOT NULL,
|
||||||
count BIGINT NOT NULL DEFAULT 1,
|
count BIGINT NOT NULL DEFAULT 1,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS marketing (
|
CREATE TABLE IF NOT EXISTS marketing (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
advertiser TEXT NOT NULL,
|
advertiser TEXT NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS vanity_address (
|
CREATE TABLE IF NOT EXISTS vanity_address (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
vanity_address TEXT NOT NULL,
|
vanity_address TEXT NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS swap (
|
CREATE TABLE IF NOT EXISTS swap (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
fee1 BIGINT NOT NULL,
|
fee1 BIGINT NOT NULL,
|
||||||
fee2 BIGINT NOT NULL,
|
fee2 BIGINT NOT NULL,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
ticker1 VARCHAR(15),
|
ticker1 VARCHAR(15),
|
||||||
nft_series1 INTEGER NOT NULL DEFAULT 0,
|
nft_series1 INTEGER NOT NULL DEFAULT 0,
|
||||||
ticker2 VARCHAR(15),
|
ticker2 VARCHAR(15),
|
||||||
nft_series2 INTEGER NOT NULL DEFAULT 0,
|
nft_series2 INTEGER NOT NULL DEFAULT 0,
|
||||||
value1 BIGINT NOT NULL,
|
value1 BIGINT NOT NULL,
|
||||||
value2 BIGINT NOT NULL,
|
value2 BIGINT NOT NULL,
|
||||||
sender1 TEXT NOT NULL,
|
sender1 TEXT NOT NULL,
|
||||||
tip1 BIGINT NOT NULL,
|
tip1 BIGINT NOT NULL,
|
||||||
tip2 BIGINT NOT NULL,
|
tip2 BIGINT NOT NULL,
|
||||||
sender2 TEXT NOT NULL,
|
sender2 TEXT NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature1 TEXT NOT NULL,
|
signature1 TEXT NOT NULL,
|
||||||
signature2 TEXT NOT NULL,
|
signature2 TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS loan_contract (
|
CREATE TABLE IF NOT EXISTS loan_contract (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
loan_coin VARCHAR(15),
|
loan_coin VARCHAR(15),
|
||||||
loan_amount BIGINT NOT NULL,
|
loan_amount BIGINT NOT NULL,
|
||||||
lender TEXT NOT NULL,
|
lender TEXT NOT NULL,
|
||||||
collateral VARCHAR(15),
|
collateral VARCHAR(15),
|
||||||
collateral_amount BIGINT NOT NULL,
|
collateral_amount BIGINT NOT NULL,
|
||||||
borrower TEXT NOT NULL,
|
borrower TEXT NOT NULL,
|
||||||
txid VARCHAR(64) NOT NULL,
|
txid VARCHAR(64) NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature1 TEXT NOT NULL,
|
signature1 TEXT NOT NULL,
|
||||||
signature2 TEXT NOT NULL,
|
signature2 TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS loan_payment (
|
CREATE TABLE IF NOT EXISTS loan_payment (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
payback_amount BIGINT NOT NULL,
|
payback_amount BIGINT NOT NULL,
|
||||||
contract_hash VARCHAR(64) NOT NULL,
|
contract_hash VARCHAR(64) NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
tip BIGINT NOT NULL,
|
tip BIGINT NOT NULL,
|
||||||
txid VARCHAR(64),
|
txid VARCHAR(64),
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS collateral_claim (
|
CREATE TABLE IF NOT EXISTS collateral_claim (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
time INTEGER NOT NULL,
|
time INTEGER NOT NULL,
|
||||||
fee BIGINT NOT NULL,
|
fee BIGINT NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
contract_hash VARCHAR(64) NOT NULL,
|
contract_hash VARCHAR(64) NOT NULL,
|
||||||
hash VARCHAR(64) NOT NULL,
|
hash VARCHAR(64) NOT NULL,
|
||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
processed bool DEFAULT false,
|
processed bool DEFAULT false,
|
||||||
processed_block_number INTEGER DEFAULT NULL,
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
ALTER TABLE loan_payment ADD COLUMN IF NOT EXISTS txid VARCHAR(64);
|
ALTER TABLE loan_payment ADD COLUMN IF NOT EXISTS txid VARCHAR(64);
|
||||||
ALTER TABLE transfer ADD COLUMN IF NOT EXISTS nft_series INTEGER NOT NULL DEFAULT 0;
|
ALTER TABLE transfer ADD COLUMN IF NOT EXISTS nft_series INTEGER NOT NULL DEFAULT 0;
|
||||||
ALTER TABLE nft ADD COLUMN IF NOT EXISTS count BIGINT NOT NULL DEFAULT 1;
|
ALTER TABLE nft ADD COLUMN IF NOT EXISTS count BIGINT NOT NULL DEFAULT 1;
|
||||||
ALTER TABLE token ADD COLUMN IF NOT EXISTS hard_limit SMALLINT NOT NULL DEFAULT 1;
|
ALTER TABLE token ADD COLUMN IF NOT EXISTS hard_limit SMALLINT NOT NULL DEFAULT 1;
|
||||||
ALTER TABLE swap ADD COLUMN IF NOT EXISTS nft_series1 INTEGER NOT NULL DEFAULT 0;
|
ALTER TABLE swap ADD COLUMN IF NOT EXISTS nft_series1 INTEGER NOT NULL DEFAULT 0;
|
||||||
ALTER TABLE swap ADD COLUMN IF NOT EXISTS nft_series2 INTEGER NOT NULL DEFAULT 0;
|
ALTER TABLE swap ADD COLUMN IF NOT EXISTS nft_series2 INTEGER NOT NULL DEFAULT 0;
|
||||||
ALTER TABLE transfer ALTER COLUMN sender TYPE TEXT;
|
ALTER TABLE transfer ALTER COLUMN sender TYPE TEXT;
|
||||||
ALTER TABLE transfer ALTER COLUMN receiver TYPE TEXT;
|
ALTER TABLE transfer ALTER COLUMN receiver TYPE TEXT;
|
||||||
ALTER TABLE transfer ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE transfer ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE token ALTER COLUMN creator TYPE TEXT;
|
ALTER TABLE token ALTER COLUMN creator TYPE TEXT;
|
||||||
ALTER TABLE token ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE token ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE issue_token ALTER COLUMN creator TYPE TEXT;
|
ALTER TABLE issue_token ALTER COLUMN creator TYPE TEXT;
|
||||||
ALTER TABLE issue_token ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE issue_token ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE burn ALTER COLUMN address TYPE TEXT;
|
ALTER TABLE burn ALTER COLUMN address TYPE TEXT;
|
||||||
ALTER TABLE burn ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE burn ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE nft ALTER COLUMN creator TYPE TEXT;
|
ALTER TABLE nft ALTER COLUMN creator TYPE TEXT;
|
||||||
ALTER TABLE nft ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE nft ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE marketing ALTER COLUMN advertiser TYPE TEXT;
|
ALTER TABLE marketing ALTER COLUMN advertiser TYPE TEXT;
|
||||||
ALTER TABLE marketing ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE marketing ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE vanity_address ALTER COLUMN address TYPE TEXT;
|
ALTER TABLE vanity_address ALTER COLUMN address TYPE TEXT;
|
||||||
ALTER TABLE vanity_address ALTER COLUMN vanity_address TYPE TEXT;
|
ALTER TABLE vanity_address ALTER COLUMN vanity_address TYPE TEXT;
|
||||||
ALTER TABLE vanity_address ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE vanity_address ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE swap ALTER COLUMN sender1 TYPE TEXT;
|
ALTER TABLE swap ALTER COLUMN sender1 TYPE TEXT;
|
||||||
ALTER TABLE swap ALTER COLUMN sender2 TYPE TEXT;
|
ALTER TABLE swap ALTER COLUMN sender2 TYPE TEXT;
|
||||||
ALTER TABLE swap ALTER COLUMN signature1 TYPE TEXT;
|
ALTER TABLE swap ALTER COLUMN signature1 TYPE TEXT;
|
||||||
ALTER TABLE swap ALTER COLUMN signature2 TYPE TEXT;
|
ALTER TABLE swap ALTER COLUMN signature2 TYPE TEXT;
|
||||||
ALTER TABLE loan_contract ALTER COLUMN lender TYPE TEXT;
|
ALTER TABLE loan_contract ALTER COLUMN lender TYPE TEXT;
|
||||||
ALTER TABLE loan_contract ALTER COLUMN borrower TYPE TEXT;
|
ALTER TABLE loan_contract ALTER COLUMN borrower TYPE TEXT;
|
||||||
ALTER TABLE loan_contract ALTER COLUMN signature1 TYPE TEXT;
|
ALTER TABLE loan_contract ALTER COLUMN signature1 TYPE TEXT;
|
||||||
ALTER TABLE loan_contract ALTER COLUMN signature2 TYPE TEXT;
|
ALTER TABLE loan_contract ALTER COLUMN signature2 TYPE TEXT;
|
||||||
ALTER TABLE loan_payment ALTER COLUMN address TYPE TEXT;
|
ALTER TABLE loan_payment ALTER COLUMN address TYPE TEXT;
|
||||||
ALTER TABLE loan_payment ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE loan_payment ALTER COLUMN signature TYPE TEXT;
|
||||||
ALTER TABLE collateral_claim ALTER COLUMN address TYPE TEXT;
|
ALTER TABLE collateral_claim ALTER COLUMN address TYPE TEXT;
|
||||||
ALTER TABLE collateral_claim ALTER COLUMN signature TYPE TEXT;
|
ALTER TABLE collateral_claim ALTER COLUMN signature TYPE TEXT;
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
// The schema block creates fresh installs and also carries small migrations
|
// The schema block creates fresh installs and also carries small migrations
|
||||||
|
|
@ -252,57 +252,57 @@ pub async fn setup_mempool() -> Result<()> {
|
||||||
client.batch_execute(schema).await?;
|
client.batch_execute(schema).await?;
|
||||||
|
|
||||||
let dedupe = r#"
|
let dedupe = r#"
|
||||||
DELETE FROM transfer a
|
DELETE FROM transfer a
|
||||||
USING transfer b
|
USING transfer b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM token a
|
DELETE FROM token a
|
||||||
USING token b
|
USING token b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM issue_token a
|
DELETE FROM issue_token a
|
||||||
USING issue_token b
|
USING issue_token b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM burn a
|
DELETE FROM burn a
|
||||||
USING burn b
|
USING burn b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM nft a
|
DELETE FROM nft a
|
||||||
USING nft b
|
USING nft b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM marketing a
|
DELETE FROM marketing a
|
||||||
USING marketing b
|
USING marketing b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM vanity_address a
|
DELETE FROM vanity_address a
|
||||||
USING vanity_address b
|
USING vanity_address b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM swap a
|
DELETE FROM swap a
|
||||||
USING swap b
|
USING swap b
|
||||||
WHERE a.id > b.id AND a.signature1 = b.signature1;
|
WHERE a.id > b.id AND a.signature1 = b.signature1;
|
||||||
|
|
||||||
DELETE FROM swap a
|
DELETE FROM swap a
|
||||||
USING swap b
|
USING swap b
|
||||||
WHERE a.id > b.id AND a.signature2 = b.signature2;
|
WHERE a.id > b.id AND a.signature2 = b.signature2;
|
||||||
|
|
||||||
DELETE FROM loan_contract a
|
DELETE FROM loan_contract a
|
||||||
USING loan_contract b
|
USING loan_contract b
|
||||||
WHERE a.id > b.id AND a.signature1 = b.signature1;
|
WHERE a.id > b.id AND a.signature1 = b.signature1;
|
||||||
|
|
||||||
DELETE FROM loan_contract a
|
DELETE FROM loan_contract a
|
||||||
USING loan_contract b
|
USING loan_contract b
|
||||||
WHERE a.id > b.id AND a.signature2 = b.signature2;
|
WHERE a.id > b.id AND a.signature2 = b.signature2;
|
||||||
|
|
||||||
DELETE FROM loan_payment a
|
DELETE FROM loan_payment a
|
||||||
USING loan_payment b
|
USING loan_payment b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
DELETE FROM collateral_claim a
|
DELETE FROM collateral_claim a
|
||||||
USING collateral_claim b
|
USING collateral_claim b
|
||||||
WHERE a.id > b.id AND a.signature = b.signature;
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
// Remove duplicate rows before unique indexes are created, otherwise stale
|
// Remove duplicate rows before unique indexes are created, otherwise stale
|
||||||
|
|
@ -310,53 +310,53 @@ pub async fn setup_mempool() -> Result<()> {
|
||||||
client.batch_execute(dedupe).await?;
|
client.batch_execute(dedupe).await?;
|
||||||
|
|
||||||
let indexes = r#"
|
let indexes = r#"
|
||||||
CREATE INDEX IF NOT EXISTS transfer_pick_idx ON transfer (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS transfer_pick_idx ON transfer (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS transfer_sig_idx ON transfer (signature);
|
CREATE INDEX IF NOT EXISTS transfer_sig_idx ON transfer (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS transfer_sig_unique_idx ON transfer (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS transfer_sig_unique_idx ON transfer (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS token_pick_idx ON token (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS token_pick_idx ON token (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS token_sig_idx ON token (signature);
|
CREATE INDEX IF NOT EXISTS token_sig_idx ON token (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS token_sig_unique_idx ON token (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS token_sig_unique_idx ON token (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS issue_token_pick_idx ON issue_token (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS issue_token_pick_idx ON issue_token (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS issue_token_sig_idx ON issue_token (signature);
|
CREATE INDEX IF NOT EXISTS issue_token_sig_idx ON issue_token (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS issue_token_sig_unique_idx ON issue_token (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS issue_token_sig_unique_idx ON issue_token (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS burn_pick_idx ON burn (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS burn_pick_idx ON burn (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS burn_sig_idx ON burn (signature);
|
CREATE INDEX IF NOT EXISTS burn_sig_idx ON burn (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS burn_sig_unique_idx ON burn (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS burn_sig_unique_idx ON burn (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS nft_pick_idx ON nft (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS nft_pick_idx ON nft (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS nft_sig_idx ON nft (signature);
|
CREATE INDEX IF NOT EXISTS nft_sig_idx ON nft (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS nft_sig_unique_idx ON nft (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS nft_sig_unique_idx ON nft (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS marketing_pick_idx ON marketing (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS marketing_pick_idx ON marketing (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS marketing_sig_idx ON marketing (signature);
|
CREATE INDEX IF NOT EXISTS marketing_sig_idx ON marketing (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS marketing_sig_unique_idx ON marketing (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS marketing_sig_unique_idx ON marketing (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS vanity_address_pick_idx ON vanity_address (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS vanity_address_pick_idx ON vanity_address (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS vanity_address_sig_idx ON vanity_address (signature);
|
CREATE INDEX IF NOT EXISTS vanity_address_sig_idx ON vanity_address (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS vanity_address_sig_unique_idx ON vanity_address (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS vanity_address_sig_unique_idx ON vanity_address (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS swap_pick_idx ON swap (processed, GREATEST(fee1, fee2) DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS swap_pick_idx ON swap (processed, GREATEST(fee1, fee2) DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS swap_sig1_idx ON swap (signature1);
|
CREATE INDEX IF NOT EXISTS swap_sig1_idx ON swap (signature1);
|
||||||
CREATE INDEX IF NOT EXISTS swap_sig2_idx ON swap (signature2);
|
CREATE INDEX IF NOT EXISTS swap_sig2_idx ON swap (signature2);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS swap_sig1_unique_idx ON swap (signature1);
|
CREATE UNIQUE INDEX IF NOT EXISTS swap_sig1_unique_idx ON swap (signature1);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS swap_sig2_unique_idx ON swap (signature2);
|
CREATE UNIQUE INDEX IF NOT EXISTS swap_sig2_unique_idx ON swap (signature2);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS loan_contract_pick_idx ON loan_contract (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS loan_contract_pick_idx ON loan_contract (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS loan_contract_sig1_idx ON loan_contract (signature1);
|
CREATE INDEX IF NOT EXISTS loan_contract_sig1_idx ON loan_contract (signature1);
|
||||||
CREATE INDEX IF NOT EXISTS loan_contract_sig2_idx ON loan_contract (signature2);
|
CREATE INDEX IF NOT EXISTS loan_contract_sig2_idx ON loan_contract (signature2);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS loan_contract_sig1_unique_idx ON loan_contract (signature1);
|
CREATE UNIQUE INDEX IF NOT EXISTS loan_contract_sig1_unique_idx ON loan_contract (signature1);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS loan_contract_sig2_unique_idx ON loan_contract (signature2);
|
CREATE UNIQUE INDEX IF NOT EXISTS loan_contract_sig2_unique_idx ON loan_contract (signature2);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS loan_payment_pick_idx ON loan_payment (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS loan_payment_pick_idx ON loan_payment (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS loan_payment_sig_idx ON loan_payment (signature);
|
CREATE INDEX IF NOT EXISTS loan_payment_sig_idx ON loan_payment (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS loan_payment_sig_unique_idx ON loan_payment (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS loan_payment_sig_unique_idx ON loan_payment (signature);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS collateral_claim_pick_idx ON collateral_claim (processed, fee DESC, time ASC, id ASC);
|
CREATE INDEX IF NOT EXISTS collateral_claim_pick_idx ON collateral_claim (processed, fee DESC, time ASC, id ASC);
|
||||||
CREATE INDEX IF NOT EXISTS collateral_claim_sig_idx ON collateral_claim (signature);
|
CREATE INDEX IF NOT EXISTS collateral_claim_sig_idx ON collateral_claim (signature);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS collateral_claim_sig_unique_idx ON collateral_claim (signature);
|
CREATE UNIQUE INDEX IF NOT EXISTS collateral_claim_sig_unique_idx ON collateral_claim (signature);
|
||||||
"#;
|
"#;
|
||||||
// Pick indexes speed up block selection; signature indexes enforce one
|
// Pick indexes speed up block selection; signature indexes enforce one
|
||||||
// pending copy of each transaction.
|
// pending copy of each transaction.
|
||||||
|
|
@ -364,35 +364,34 @@ pub async fn setup_mempool() -> Result<()> {
|
||||||
|
|
||||||
// Live mempool data is not restored across startup.
|
// Live mempool data is not restored across startup.
|
||||||
clear_mempool().await?;
|
clear_mempool().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear_mempool() -> Result<()> {
|
pub async fn clear_mempool() -> Result<()> {
|
||||||
// Startup clears any leftover mempool rows so a node restart begins
|
// Startup clears any leftover mempool rows so a node restart begins
|
||||||
// from a clean pending-transaction state.
|
// from a clean pending-transaction state.
|
||||||
let client = DB.get().expect("DB not initialized");
|
let client = DB.get().expect("DB not initialized");
|
||||||
|
|
||||||
client
|
client
|
||||||
.batch_execute(
|
.batch_execute(
|
||||||
r#"
|
r#"
|
||||||
TRUNCATE TABLE
|
TRUNCATE TABLE
|
||||||
transfer,
|
transfer,
|
||||||
token,
|
token,
|
||||||
issue_token,
|
issue_token,
|
||||||
burn,
|
burn,
|
||||||
nft,
|
nft,
|
||||||
marketing,
|
marketing,
|
||||||
vanity_address,
|
vanity_address,
|
||||||
swap,
|
swap,
|
||||||
loan_contract,
|
loan_contract,
|
||||||
loan_payment,
|
loan_payment,
|
||||||
collateral_claim
|
collateral_claim
|
||||||
RESTART IDENTITY;
|
RESTART IDENTITY;
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,9 +3,9 @@
|
||||||
pub mod averages;
|
pub mod averages;
|
||||||
pub mod connections;
|
pub mod connections;
|
||||||
pub mod enums;
|
pub mod enums;
|
||||||
pub mod response_channels;
|
|
||||||
pub mod mempool;
|
pub mod mempool;
|
||||||
pub mod network_mapping;
|
pub mod network_mapping;
|
||||||
|
pub mod response_channels;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
pub mod torrent_status;
|
pub mod torrent_status;
|
||||||
pub mod torrentmap;
|
pub mod torrentmap;
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,30 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl NodeInfo {
|
impl NodeInfo {
|
||||||
pub async fn broadcast_node(
|
pub async fn broadcast_node(
|
||||||
map: Arc<Mutex<Command>>,
|
map: Arc<Mutex<Command>>,
|
||||||
edit: &SignedNodeEdit,
|
edit: &SignedNodeEdit,
|
||||||
remote_ip: &str,
|
remote_ip: &str,
|
||||||
edittype: NodeEditType,
|
edittype: NodeEditType,
|
||||||
connections_key: &str,
|
connections_key: &str,
|
||||||
) {
|
) {
|
||||||
// Re-broadcast signed node-map edits to connected peers while
|
// Re-broadcast signed node-map edits to connected peers while
|
||||||
// skipping the source peer that already sent the update.
|
// skipping the source peer that already sent the update.
|
||||||
let message_type = edittype.message_type();
|
let message_type = edittype.message_type();
|
||||||
let ip_bytes = ip_to_binary(&edit.ip);
|
let ip_bytes = ip_to_binary(&edit.ip);
|
||||||
let address_bytes = match Wallet::short_address_to_bytes(&edit.address) {
|
let address_bytes = match Wallet::short_address_to_bytes(&edit.address) {
|
||||||
Some(bytes) => bytes,
|
Some(bytes) => bytes,
|
||||||
None => {
|
None => {
|
||||||
warn!(
|
warn!(
|
||||||
"[network_map] skipping broadcast for invalid short node address {}",
|
"[network_map] skipping broadcast for invalid short node address {}",
|
||||||
edit.address
|
edit.address
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let modified_by_bytes = Wallet::long_address_to_bytes(edit.modified_by.clone());
|
let modified_by_bytes = Wallet::long_address_to_bytes(edit.modified_by.clone());
|
||||||
let modified_timestamp_bytes = edit.modified_timestamp.to_le_bytes();
|
let modified_timestamp_bytes = edit.modified_timestamp.to_le_bytes();
|
||||||
let modified_signature_bytes = decode(&edit.modified_signature).unwrap();
|
let modified_signature_bytes = decode(&edit.modified_signature).unwrap();
|
||||||
let connections_lock = CONNECTIONS.read().await;
|
let connections_lock = CONNECTIONS.read().await;
|
||||||
let streams: Option<Vec<Arc<Mutex<TcpStream>>>> = connections_lock
|
let streams: Option<Vec<Arc<Mutex<TcpStream>>>> = connections_lock
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -35,12 +35,12 @@ impl NodeInfo {
|
||||||
let (hashmap_key, _hashmap_tx, hashmap_rx) = reserve_entry(map.clone()).await;
|
let (hashmap_key, _hashmap_tx, hashmap_rx) = reserve_entry(map.clone()).await;
|
||||||
let mut message: Vec<u8> = Vec::new();
|
let mut message: Vec<u8> = Vec::new();
|
||||||
message.push(message_type);
|
message.push(message_type);
|
||||||
message.extend_from_slice(&hashmap_key);
|
message.extend_from_slice(&hashmap_key);
|
||||||
message.extend_from_slice(&address_bytes);
|
message.extend_from_slice(&address_bytes);
|
||||||
message.extend_from_slice(&ip_bytes);
|
message.extend_from_slice(&ip_bytes);
|
||||||
message.extend_from_slice(&modified_by_bytes);
|
message.extend_from_slice(&modified_by_bytes);
|
||||||
message.extend_from_slice(&modified_timestamp_bytes);
|
message.extend_from_slice(&modified_timestamp_bytes);
|
||||||
message.extend_from_slice(&modified_signature_bytes);
|
message.extend_from_slice(&modified_signature_bytes);
|
||||||
let peer_addr = {
|
let peer_addr = {
|
||||||
let stream = unlocked_stream.lock().await;
|
let stream = unlocked_stream.lock().await;
|
||||||
stream.peer_addr()
|
stream.peer_addr()
|
||||||
|
|
@ -65,182 +65,182 @@ impl NodeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
warn!("No active connections found.");
|
warn!("No active connections found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_address(params: AddAddressParams) -> RpcResponse {
|
pub async fn add_address(params: AddAddressParams) -> RpcResponse {
|
||||||
let AddAddressParams {
|
let AddAddressParams {
|
||||||
map,
|
map,
|
||||||
mut edit,
|
mut edit,
|
||||||
mut blocks_mined,
|
mut blocks_mined,
|
||||||
remote_ip,
|
remote_ip,
|
||||||
db,
|
db,
|
||||||
wallet_key,
|
wallet_key,
|
||||||
connections_key,
|
connections_key,
|
||||||
} = params;
|
} = params;
|
||||||
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
||||||
let direct_peer_announcement = !remote_ip.is_empty() && edit.ip == remote_ip;
|
let direct_peer_announcement = !remote_ip.is_empty() && edit.ip == remote_ip;
|
||||||
|
|
||||||
if !is_public_network_address(&edit.ip) {
|
if !is_public_network_address(&edit.ip) {
|
||||||
return RpcResponse::Binary(b"Error: Invalid network address".to_vec());
|
return RpcResponse::Binary(b"Error: Invalid network address".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locally initiated edits are re-signed with the local wallet and
|
// Locally initiated edits are re-signed with the local wallet and
|
||||||
// current timestamp so they can be propagated as fresh node events.
|
// current timestamp so they can be propagated as fresh node events.
|
||||||
if edit.ip == remote_ip {
|
if edit.ip == remote_ip {
|
||||||
edit.modified_timestamp = current_timestamp;
|
edit.modified_timestamp = current_timestamp;
|
||||||
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), None).await {
|
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), None).await {
|
||||||
Ok(wallet) => wallet,
|
Ok(wallet) => wallet,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Wallet decryption failed while adding node address: {err}");
|
error!("Wallet decryption failed while adding node address: {err}");
|
||||||
return RpcResponse::Binary(b"Error: Wallet decryption failed".to_vec());
|
return RpcResponse::Binary(b"Error: Wallet decryption failed".to_vec());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
edit.modified_by = wallet.saved.long_address;
|
edit.modified_by = wallet.saved.long_address;
|
||||||
edit.modified_signature =
|
edit.modified_signature =
|
||||||
Self::added_signature(&edit.address, &edit.ip, current_timestamp, &wallet_key)
|
Self::added_signature(&edit.address, &edit.ip, current_timestamp, &wallet_key)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !remote_ip.is_empty() {
|
if !remote_ip.is_empty() {
|
||||||
blocks_mined = 0;
|
blocks_mined = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = format!(
|
let data = format!(
|
||||||
"{}{}{}{}",
|
"{}{}{}{}",
|
||||||
edit.address, edit.ip, edit.modified_by, edit.modified_timestamp
|
edit.address, edit.ip, edit.modified_by, edit.modified_timestamp
|
||||||
);
|
);
|
||||||
let hashed_data = skein_256_hash_data(&data);
|
let hashed_data = skein_256_hash_data(&data);
|
||||||
|
|
||||||
// Every add/delete edit is signed, so the network map accepts
|
// Every add/delete edit is signed, so the network map accepts
|
||||||
// only node changes backed by a valid wallet signature.
|
// only node changes backed by a valid wallet signature.
|
||||||
if !Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &edit.modified_by)
|
if !Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &edit.modified_by)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return RpcResponse::Binary(b"Error: Could not validate signature".to_vec());
|
return RpcResponse::Binary(b"Error: Could not validate signature".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut penalize_duplicate_ip = false;
|
let mut penalize_duplicate_ip = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut address_map = ADDRESS_MAP.lock().await;
|
let mut address_map = ADDRESS_MAP.lock().await;
|
||||||
|
|
||||||
// Once the chain is mature, adding nodes is restricted to older
|
// Once the chain is mature, adding nodes is restricted to older
|
||||||
// active participants with sufficient mined history.
|
// active participants with sufficient mined history.
|
||||||
if get_height(&db) > 10000 {
|
if get_height(&db) > 10000 {
|
||||||
let signer_key = Wallet::normalize_to_short_address(&edit.modified_by)
|
let signer_key = Wallet::normalize_to_short_address(&edit.modified_by)
|
||||||
.unwrap_or_else(|| edit.modified_by.clone());
|
.unwrap_or_else(|| edit.modified_by.clone());
|
||||||
let signer_node = address_map.get(&signer_key);
|
let signer_node = address_map.get(&signer_key);
|
||||||
let valid_added_by = signer_node
|
let valid_added_by = signer_node
|
||||||
.map(|node| {
|
.map(|node| {
|
||||||
(current_timestamp - node.added_timestamp) >= 3600
|
(current_timestamp - node.added_timestamp) >= 3600
|
||||||
&& node.deleted_by.is_empty()
|
&& node.deleted_by.is_empty()
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !valid_added_by {
|
if !valid_added_by {
|
||||||
return RpcResponse::Binary(
|
return RpcResponse::Binary(
|
||||||
b"Error: This address cannot add nodes. It must exist for at least 60 minutes and not be marked for deletion"
|
b"Error: This address cannot add nodes. It must exist for at least 60 minutes and not be marked for deletion"
|
||||||
.to_vec(),
|
.to_vec(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mined_count = signer_node.map(|node| node.blocks_mined).unwrap_or(0);
|
let mined_count = signer_node.map(|node| node.blocks_mined).unwrap_or(0);
|
||||||
if mined_count < 100 {
|
if mined_count < 100 {
|
||||||
return RpcResponse::Binary(
|
return RpcResponse::Binary(
|
||||||
b"Error: This address cannot add nodes. It must mined 100 blocks before adding new nodes to the network"
|
b"Error: This address cannot add nodes. It must mined 100 blocks before adding new nodes to the network"
|
||||||
.to_vec(),
|
.to_vec(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let added_by_count_in_last_hour = address_map
|
let added_by_count_in_last_hour = address_map
|
||||||
.values()
|
.values()
|
||||||
.filter(|node| {
|
.filter(|node| {
|
||||||
node.added_by == edit.modified_by
|
node.added_by == edit.modified_by
|
||||||
&& (current_timestamp - node.added_timestamp) <= 3600
|
&& (current_timestamp - node.added_timestamp) <= 3600
|
||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
if added_by_count_in_last_hour >= 10 {
|
if added_by_count_in_last_hour >= 10 {
|
||||||
return RpcResponse::Binary(
|
return RpcResponse::Binary(
|
||||||
b"Error: Cannot add more than 10 nodes in 60 minutes".to_vec(),
|
b"Error: Cannot add more than 10 nodes in 60 minutes".to_vec(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existing deleted entries can be revived in place when the same
|
// Existing deleted entries can be revived in place when the same
|
||||||
// address/IP pair is re-announced, otherwise the older record is
|
// address/IP pair is re-announced, otherwise the older record is
|
||||||
// discarded and replaced.
|
// discarded and replaced.
|
||||||
if let Some(existing_node) = address_map.get_mut(&edit.address) {
|
if let Some(existing_node) = address_map.get_mut(&edit.address) {
|
||||||
if !existing_node.deleted_by.is_empty() {
|
if !existing_node.deleted_by.is_empty() {
|
||||||
if existing_node.ip == edit.ip {
|
if existing_node.ip == edit.ip {
|
||||||
existing_node.deleted_by = "".to_string();
|
existing_node.deleted_by = "".to_string();
|
||||||
existing_node.deleted_timestamp = 0_u64;
|
existing_node.deleted_timestamp = 0_u64;
|
||||||
existing_node.deleted_block = 0_u32;
|
existing_node.deleted_block = 0_u32;
|
||||||
existing_node.deleted_signature = "".to_string();
|
existing_node.deleted_signature = "".to_string();
|
||||||
return RpcResponse::Binary(b"Success".to_vec());
|
return RpcResponse::Binary(b"Success".to_vec());
|
||||||
} else {
|
} else {
|
||||||
address_map.remove(&edit.address);
|
address_map.remove(&edit.address);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if edit.modified_timestamp < existing_node.added_timestamp {
|
if edit.modified_timestamp < existing_node.added_timestamp {
|
||||||
*existing_node = NodeInfo::new(
|
*existing_node = NodeInfo::new(
|
||||||
edit.ip.clone(),
|
edit.ip.clone(),
|
||||||
blocks_mined,
|
blocks_mined,
|
||||||
edit.modified_by.clone(),
|
edit.modified_by.clone(),
|
||||||
edit.modified_timestamp,
|
edit.modified_timestamp,
|
||||||
edit.modified_signature.clone(),
|
edit.modified_signature.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return RpcResponse::Binary(b"Success".to_vec());
|
return RpcResponse::Binary(b"Success".to_vec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(existing_node) = address_map.values_mut().find(|node| node.ip == edit.ip) {
|
if let Some(existing_node) = address_map.values_mut().find(|node| node.ip == edit.ip) {
|
||||||
if !existing_node.deleted_by.is_empty() {
|
if !existing_node.deleted_by.is_empty() {
|
||||||
address_map.retain(|_, node| node.ip != edit.ip);
|
address_map.retain(|_, node| node.ip != edit.ip);
|
||||||
} else if edit.ip != GENESIS_IP {
|
} else if edit.ip != GENESIS_IP {
|
||||||
penalize_duplicate_ip = true;
|
penalize_duplicate_ip = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !penalize_duplicate_ip {
|
if !penalize_duplicate_ip {
|
||||||
// Persist the new node locally. Network-map entries are bare
|
// Persist the new node locally. Network-map entries are bare
|
||||||
// IP membership records, separate from live socket keys.
|
// IP membership records, separate from live socket keys.
|
||||||
address_map.insert(
|
address_map.insert(
|
||||||
edit.address.clone(),
|
edit.address.clone(),
|
||||||
NodeInfo::new(
|
NodeInfo::new(
|
||||||
edit.ip.clone(),
|
edit.ip.clone(),
|
||||||
blocks_mined,
|
blocks_mined,
|
||||||
edit.modified_by.clone(),
|
edit.modified_by.clone(),
|
||||||
edit.modified_timestamp,
|
edit.modified_timestamp,
|
||||||
edit.modified_signature.clone(),
|
edit.modified_signature.clone(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if penalize_duplicate_ip {
|
if penalize_duplicate_ip {
|
||||||
let now = Utc::now().timestamp() as u32;
|
let now = Utc::now().timestamp() as u32;
|
||||||
let _ = update_ip_score(
|
let _ = update_ip_score(
|
||||||
&remote_ip,
|
&remote_ip,
|
||||||
"miner",
|
"miner",
|
||||||
InfractionType::BadMinerIpUpdate,
|
InfractionType::BadMinerIpUpdate,
|
||||||
now,
|
now,
|
||||||
&db,
|
&db,
|
||||||
&wallet_key,
|
&wallet_key,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
return RpcResponse::Binary(b"Error: Ip Already exists.".to_vec());
|
return RpcResponse::Binary(b"Error: Ip Already exists.".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::broadcast_node(
|
Self::broadcast_node(
|
||||||
map.clone(),
|
map.clone(),
|
||||||
&edit,
|
&edit,
|
||||||
&remote_ip,
|
&remote_ip,
|
||||||
NodeEditType::Add,
|
NodeEditType::Add,
|
||||||
&connections_key,
|
&connections_key,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|
@ -259,5 +259,4 @@ impl NodeInfo {
|
||||||
|
|
||||||
RpcResponse::Binary(b"Success".to_vec())
|
RpcResponse::Binary(b"Success".to_vec())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl NodeInfo {
|
impl NodeInfo {
|
||||||
pub fn ping(params: PingMonitorParams) {
|
pub fn ping(params: PingMonitorParams) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
|
@ -37,37 +37,37 @@ impl NodeInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut failures = 0;
|
let mut failures = 0;
|
||||||
|
|
||||||
// Periodically ping the node and remove it after repeated
|
// Periodically ping the node and remove it after repeated
|
||||||
// failures, then recursively reattach any inherited children.
|
// failures, then recursively reattach any inherited children.
|
||||||
loop {
|
loop {
|
||||||
sleep(Duration::from_secs(120)).await;
|
sleep(Duration::from_secs(120)).await;
|
||||||
|
|
||||||
{
|
{
|
||||||
let monitors = PING_MONITORS.lock().await;
|
let monitors = PING_MONITORS.lock().await;
|
||||||
// Stop this task if another monitor replaced it.
|
// Stop this task if another monitor replaced it.
|
||||||
if !monitors
|
if !monitors
|
||||||
.get(&task_addr)
|
.get(&task_addr)
|
||||||
.map(|current| current == &task_signature)
|
.map(|current| current == &task_signature)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let map_lock = ADDRESS_MAP.lock().await;
|
let map_lock = ADDRESS_MAP.lock().await;
|
||||||
if let Some(node) = map_lock.get(&task_addr) {
|
if let Some(node) = map_lock.get(&task_addr) {
|
||||||
// Stop monitoring stale node data after the map entry
|
// Stop monitoring stale node data after the map entry
|
||||||
// has been replaced by a newer edit.
|
// has been replaced by a newer edit.
|
||||||
if node.added_by != task_wallet || node.added_signature != task_signature {
|
if node.added_by != task_wallet || node.added_signature != task_signature {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(unlocked_stream) =
|
if let Some(unlocked_stream) =
|
||||||
Connection::get_stream_from_memory(&task_connections_key).await
|
Connection::get_stream_from_memory(&task_connections_key).await
|
||||||
{
|
{
|
||||||
|
|
@ -77,45 +77,45 @@ impl NodeInfo {
|
||||||
// Liveness uses a normal block-height RPC and waits for the
|
// Liveness uses a normal block-height RPC and waits for the
|
||||||
// reserved reply channel to receive any response.
|
// reserved reply channel to receive any response.
|
||||||
let mut message = vec![command];
|
let mut message = vec![command];
|
||||||
message.extend_from_slice(&ping_key);
|
message.extend_from_slice(&ping_key);
|
||||||
RpcResponse::send_raw(&unlocked_stream, Some(&task_connections_key), &message)
|
RpcResponse::send_raw(&unlocked_stream, Some(&task_connections_key), &message)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let response = timeout(Duration::from_secs(10), async {
|
let response = timeout(Duration::from_secs(10), async {
|
||||||
let mut rx = ping_rx.lock().await;
|
let mut rx = ping_rx.lock().await;
|
||||||
rx.recv().await
|
rx.recv().await
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(Some(_buffer)) => {
|
Ok(Some(_buffer)) => {
|
||||||
failures = 0;
|
failures = 0;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
failures += 1;
|
failures += 1;
|
||||||
warn!("[network_map] ping failure: address={task_addr} ip={task_ip} failures={failures}");
|
warn!("[network_map] ping failure: address={task_addr} ip={task_ip} failures={failures}");
|
||||||
if failures >= 3 {
|
if failures >= 3 {
|
||||||
warn!("[network_map] deleting node after ping failures: address={task_addr} ip={task_ip} responsible_by={task_wallet}");
|
warn!("[network_map] deleting node after ping failures: address={task_addr} ip={task_ip} responsible_by={task_wallet}");
|
||||||
let _ = Self::delete_address(DeleteAddressParams {
|
let _ = Self::delete_address(DeleteAddressParams {
|
||||||
map: map.clone(),
|
map: map.clone(),
|
||||||
edit: SignedNodeEdit {
|
edit: SignedNodeEdit {
|
||||||
address: task_addr.clone(),
|
address: task_addr.clone(),
|
||||||
ip: task_ip.clone(),
|
ip: task_ip.clone(),
|
||||||
modified_by: task_wallet.clone(),
|
modified_by: task_wallet.clone(),
|
||||||
modified_timestamp: added_timestamp,
|
modified_timestamp: added_timestamp,
|
||||||
modified_signature: task_signature.clone(),
|
modified_signature: task_signature.clone(),
|
||||||
},
|
},
|
||||||
remote_ip: task_remote_ip.clone(),
|
remote_ip: task_remote_ip.clone(),
|
||||||
db: task_db.clone(),
|
db: task_db.clone(),
|
||||||
wallet_key: task_wallet_key.clone(),
|
wallet_key: task_wallet_key.clone(),
|
||||||
connections_key: task_connections_key.clone(),
|
connections_key: task_connections_key.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The direct socket disappeared before this monitor fired.
|
// The direct socket disappeared before this monitor fired.
|
||||||
// Connection cleanup is owned by the connection manager, so
|
// Connection cleanup is owned by the connection manager, so
|
||||||
|
|
@ -123,151 +123,151 @@ impl NodeInfo {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::release_ping_monitor(&task_addr, &task_signature).await;
|
Self::release_ping_monitor(&task_addr, &task_signature).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_address(params: DeleteAddressParams) -> RpcResponse {
|
pub async fn delete_address(params: DeleteAddressParams) -> RpcResponse {
|
||||||
let DeleteAddressParams {
|
let DeleteAddressParams {
|
||||||
map,
|
map,
|
||||||
mut edit,
|
mut edit,
|
||||||
remote_ip,
|
remote_ip,
|
||||||
db,
|
db,
|
||||||
wallet_key,
|
wallet_key,
|
||||||
connections_key,
|
connections_key,
|
||||||
} = params;
|
} = params;
|
||||||
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
let current_timestamp = Utc::now().timestamp_millis() as u64;
|
||||||
|
|
||||||
// Locally initiated deletions are re-signed with fresh metadata
|
// Locally initiated deletions are re-signed with fresh metadata
|
||||||
// before they are applied and broadcast.
|
// before they are applied and broadcast.
|
||||||
if remote_ip.is_empty() {
|
if remote_ip.is_empty() {
|
||||||
edit.modified_timestamp = current_timestamp;
|
edit.modified_timestamp = current_timestamp;
|
||||||
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), None).await {
|
let wallet = match Wallet::try_obtain_wallet(wallet_key.clone(), None).await {
|
||||||
Ok(wallet) => wallet,
|
Ok(wallet) => wallet,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Wallet decryption failed while deleting node address: {err}");
|
error!("Wallet decryption failed while deleting node address: {err}");
|
||||||
return RpcResponse::Binary(b"Error: Wallet decryption failed".to_vec());
|
return RpcResponse::Binary(b"Error: Wallet decryption failed".to_vec());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
edit.modified_by = wallet.saved.long_address;
|
edit.modified_by = wallet.saved.long_address;
|
||||||
edit.modified_signature =
|
edit.modified_signature =
|
||||||
Self::added_signature(&edit.address, &edit.ip, current_timestamp, &wallet_key)
|
Self::added_signature(&edit.address, &edit.ip, current_timestamp, &wallet_key)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = format!(
|
let data = format!(
|
||||||
"{}{}{}{}",
|
"{}{}{}{}",
|
||||||
edit.address, edit.ip, edit.modified_by, edit.modified_timestamp
|
edit.address, edit.ip, edit.modified_by, edit.modified_timestamp
|
||||||
);
|
);
|
||||||
let hashed_data = skein_256_hash_data(&data);
|
let hashed_data = skein_256_hash_data(&data);
|
||||||
|
|
||||||
if !Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &edit.modified_by)
|
if !Wallet::verify_transaction(&hashed_data, &edit.modified_signature, &edit.modified_by)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return RpcResponse::Binary(b"Error: Could not validate signature".to_vec());
|
return RpcResponse::Binary(b"Error: Could not validate signature".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut address_map = ADDRESS_MAP.lock().await;
|
let mut address_map = ADDRESS_MAP.lock().await;
|
||||||
|
|
||||||
if get_height(&db) > 10_000 {
|
if get_height(&db) > 10_000 {
|
||||||
// Mature chains only allow established miners to remove nodes.
|
// Mature chains only allow established miners to remove nodes.
|
||||||
let signer_key = Wallet::normalize_to_short_address(&edit.modified_by)
|
let signer_key = Wallet::normalize_to_short_address(&edit.modified_by)
|
||||||
.unwrap_or_else(|| edit.modified_by.clone());
|
.unwrap_or_else(|| edit.modified_by.clone());
|
||||||
let signer_node = address_map.get(&signer_key);
|
let signer_node = address_map.get(&signer_key);
|
||||||
let valid_added_by = signer_node
|
let valid_added_by = signer_node
|
||||||
.map(|node| {
|
.map(|node| {
|
||||||
(current_timestamp - node.added_timestamp) >= 3600
|
(current_timestamp - node.added_timestamp) >= 3600
|
||||||
&& node.deleted_by.is_empty()
|
&& node.deleted_by.is_empty()
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if !valid_added_by {
|
if !valid_added_by {
|
||||||
return RpcResponse::Binary(
|
return RpcResponse::Binary(
|
||||||
b"Error: Address must exist for 60m and not be deleted".to_vec(),
|
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);
|
let mined_count = signer_node.map(|node| node.blocks_mined).unwrap_or(0);
|
||||||
if mined_count < 100 {
|
if mined_count < 100 {
|
||||||
return RpcResponse::Binary(
|
return RpcResponse::Binary(
|
||||||
b"Error: Must mine 100 blocks to remove nodes".to_vec(),
|
b"Error: Must mine 100 blocks to remove nodes".to_vec(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let deleted_count_last_hour = address_map
|
let deleted_count_last_hour = address_map
|
||||||
.values()
|
.values()
|
||||||
.filter(|node| {
|
.filter(|node| {
|
||||||
node.deleted_by == edit.modified_by
|
node.deleted_by == edit.modified_by
|
||||||
&& (current_timestamp - node.deleted_timestamp) <= 3600
|
&& (current_timestamp - node.deleted_timestamp) <= 3600
|
||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
// Rate limit delete events per signer to prevent churn in the
|
// Rate limit delete events per signer to prevent churn in the
|
||||||
// shared network map.
|
// shared network map.
|
||||||
if deleted_count_last_hour >= 10 {
|
if deleted_count_last_hour >= 10 {
|
||||||
return RpcResponse::Binary(b"Error: Max 10 deletions in 60m".to_vec());
|
return RpcResponse::Binary(b"Error: Max 10 deletions in 60m".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(existing_node) = address_map.get_mut(&edit.address) {
|
if let Some(existing_node) = address_map.get_mut(&edit.address) {
|
||||||
if !existing_node.deleted_by.is_empty() {
|
if !existing_node.deleted_by.is_empty() {
|
||||||
return RpcResponse::Binary(
|
return RpcResponse::Binary(
|
||||||
b"Error: This address has already been deleted".to_vec(),
|
b"Error: This address has already been deleted".to_vec(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletion is recorded as metadata rather than immediately
|
// Deletion is recorded as metadata rather than immediately
|
||||||
// removing the node, preserving historical validation context.
|
// removing the node, preserving historical validation context.
|
||||||
existing_node.deleted_by = edit.modified_by.clone();
|
existing_node.deleted_by = edit.modified_by.clone();
|
||||||
existing_node.deleted_timestamp = current_timestamp;
|
existing_node.deleted_timestamp = current_timestamp;
|
||||||
existing_node.deleted_block = get_height(&db) + 1;
|
existing_node.deleted_block = get_height(&db) + 1;
|
||||||
existing_node.deleted_signature = edit.modified_signature.clone();
|
existing_node.deleted_signature = edit.modified_signature.clone();
|
||||||
info!(
|
info!(
|
||||||
"[network_map] node marked deleted: address={} ip={} deleted_by={} timestamp={} deleted_block={}",
|
"[network_map] node marked deleted: address={} ip={} deleted_by={} timestamp={} deleted_block={}",
|
||||||
edit.address,
|
edit.address,
|
||||||
edit.ip,
|
edit.ip,
|
||||||
edit.modified_by,
|
edit.modified_by,
|
||||||
current_timestamp,
|
current_timestamp,
|
||||||
existing_node.deleted_block
|
existing_node.deleted_block
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return RpcResponse::Binary(b"Error: Address not found".to_vec());
|
return RpcResponse::Binary(b"Error: Address not found".to_vec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop any ping task owned by the deleted node record.
|
// Stop any ping task owned by the deleted node record.
|
||||||
Self::release_ping_monitor(&edit.address, &edit.modified_signature).await;
|
Self::release_ping_monitor(&edit.address, &edit.modified_signature).await;
|
||||||
|
|
||||||
// Deletions propagate to peers and also tear down any live
|
// Deletions propagate to peers and also tear down any live
|
||||||
// outgoing connection so bootstrap can recover a replacement.
|
// outgoing connection so bootstrap can recover a replacement.
|
||||||
Self::broadcast_node(
|
Self::broadcast_node(
|
||||||
map.clone(),
|
map.clone(),
|
||||||
&edit,
|
&edit,
|
||||||
&remote_ip,
|
&remote_ip,
|
||||||
NodeEditType::Delete,
|
NodeEditType::Delete,
|
||||||
&connections_key,
|
&connections_key,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Some(port) = CONNECTIONS
|
if let Some(port) = CONNECTIONS
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|conn| conn.find_outgoing_port(&edit.ip))
|
.and_then(|conn| conn.find_outgoing_port(&edit.ip))
|
||||||
{
|
{
|
||||||
let mut writer = CONNECTIONS.write().await;
|
let mut writer = CONNECTIONS.write().await;
|
||||||
if let Some(conn) = writer.as_mut() {
|
if let Some(conn) = writer.as_mut() {
|
||||||
// Drop the live outgoing socket after marking the node deleted.
|
// Drop the live outgoing socket after marking the node deleted.
|
||||||
conn.drop_connection(ConnectionType::Outgoing, edit.ip.clone(), port);
|
conn.drop_connection(ConnectionType::Outgoing, edit.ip.clone(), port);
|
||||||
info!(
|
info!(
|
||||||
"[connection_manager] dropped dead outgoing connection: {}:{}",
|
"[connection_manager] dropped dead outgoing connection: {}:{}",
|
||||||
edit.ip, port
|
edit.ip, port
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
drop(writer);
|
drop(writer);
|
||||||
|
|
||||||
let live_connection = {
|
let live_connection = {
|
||||||
let guard = CONNECTIONS.read().await;
|
let guard = CONNECTIONS.read().await;
|
||||||
guard.as_ref().and_then(|conn| {
|
guard.as_ref().and_then(|conn| {
|
||||||
|
|
@ -287,15 +287,14 @@ impl NodeInfo {
|
||||||
wallet_key: wallet_key.clone(),
|
wallet_key: wallet_key.clone(),
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
map: map.clone(),
|
map: map.clone(),
|
||||||
first: false,
|
first: false,
|
||||||
};
|
};
|
||||||
spawn_reconnect_bootstrap(bootstrap_params);
|
spawn_reconnect_bootstrap(bootstrap_params);
|
||||||
} else {
|
} else {
|
||||||
warn!("[reconnect] No live stream found to bootstrap from");
|
warn!("[reconnect] No live stream found to bootstrap from");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcResponse::Binary(b"Success: Node marked as deleted".to_vec())
|
RpcResponse::Binary(b"Success: Node marked as deleted".to_vec())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,50 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl NodeInfo {
|
impl NodeInfo {
|
||||||
pub async fn increment_mined(address: &str) {
|
pub async fn increment_mined(address: &str) {
|
||||||
let mut map = ADDRESS_MAP.lock().await;
|
let mut map = ADDRESS_MAP.lock().await;
|
||||||
if let Some(node_info) = map.get_mut(address) {
|
if let Some(node_info) = map.get_mut(address) {
|
||||||
// Counts are capped at u8-safe policy maximum used by node rules.
|
// Counts are capped at u8-safe policy maximum used by node rules.
|
||||||
if node_info.blocks_mined < 250 {
|
if node_info.blocks_mined < 250 {
|
||||||
node_info.blocks_mined += 1;
|
node_info.blocks_mined += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn decrement_mined(address: &str) {
|
pub async fn decrement_mined(address: &str) {
|
||||||
let mut map = ADDRESS_MAP.lock().await;
|
let mut map = ADDRESS_MAP.lock().await;
|
||||||
if let Some(node_info) = map.get_mut(address) {
|
if let Some(node_info) = map.get_mut(address) {
|
||||||
// Rollback can undo mined credit, but never below zero.
|
// Rollback can undo mined credit, but never below zero.
|
||||||
if node_info.blocks_mined > 0 {
|
if node_info.blocks_mined > 0 {
|
||||||
node_info.blocks_mined -= 1;
|
node_info.blocks_mined -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_mined_count(address: &str) -> u8 {
|
pub async fn get_mined_count(address: &str) -> u8 {
|
||||||
let map = ADDRESS_MAP.lock().await;
|
let map = ADDRESS_MAP.lock().await;
|
||||||
if let Some(node_info) = map.get(address) {
|
if let Some(node_info) = map.get(address) {
|
||||||
node_info.blocks_mined
|
node_info.blocks_mined
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_deleted_block_from_mapping(address: &str, deleted_block: u32) {
|
pub async fn set_deleted_block_from_mapping(address: &str, deleted_block: u32) {
|
||||||
let mut map = ADDRESS_MAP.lock().await;
|
let mut map = ADDRESS_MAP.lock().await;
|
||||||
if let Some(node_info) = map.get_mut(address) {
|
if let Some(node_info) = map.get_mut(address) {
|
||||||
// The deletion height is filled in once the chain knows the block
|
// The deletion height is filled in once the chain knows the block
|
||||||
// where the delete action becomes active.
|
// where the delete action becomes active.
|
||||||
node_info.deleted_block = deleted_block;
|
node_info.deleted_block = deleted_block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn rebuild_mined_counts_from_chain(db: &Db) -> Result<(), String> {
|
pub async fn rebuild_mined_counts_from_chain(db: &Db) -> Result<(), String> {
|
||||||
// Recompute node mined counts directly from saved block headers
|
// Recompute node mined counts directly from saved block headers
|
||||||
// so startup and recovery can rebuild memory-only state.
|
// so startup and recovery can rebuild memory-only state.
|
||||||
let current_height = get_height(db);
|
let current_height = get_height(db);
|
||||||
let mut mined_counts: HashMap<String, u8> = HashMap::new();
|
let mut mined_counts: HashMap<String, u8> = HashMap::new();
|
||||||
|
|
||||||
let start_height = if current_height > 0 { 1 } else { 0 };
|
let start_height = if current_height > 0 { 1 } else { 0 };
|
||||||
for block_number in start_height..=current_height {
|
for block_number in start_height..=current_height {
|
||||||
let header = load_block_header(block_number).await?;
|
let header = load_block_header(block_number).await?;
|
||||||
|
|
@ -52,26 +52,25 @@ impl NodeInfo {
|
||||||
let entry = mined_counts.entry(miner).or_insert(0);
|
let entry = mined_counts.entry(miner).or_insert(0);
|
||||||
// Keep the rebuilt value under the same cap as live increments.
|
// Keep the rebuilt value under the same cap as live increments.
|
||||||
if *entry < 250 {
|
if *entry < 250 {
|
||||||
*entry += 1;
|
*entry += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut map = ADDRESS_MAP.lock().await;
|
let mut map = ADDRESS_MAP.lock().await;
|
||||||
|
|
||||||
for node_info in map.values_mut() {
|
for node_info in map.values_mut() {
|
||||||
// Clear memory-only counts before applying the rebuilt chain
|
// Clear memory-only counts before applying the rebuilt chain
|
||||||
// totals so removed or inactive miners do not keep stale data.
|
// totals so removed or inactive miners do not keep stale data.
|
||||||
node_info.blocks_mined = 0;
|
node_info.blocks_mined = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (address, mined_count) in mined_counts {
|
for (address, mined_count) in mined_counts {
|
||||||
if let Some(node_info) = map.get_mut(&address) {
|
if let Some(node_info) = map.get_mut(&address) {
|
||||||
node_info.blocks_mined = mined_count;
|
node_info.blocks_mined = mined_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,83 @@
|
||||||
use crate::common::binary_conversions::ip_to_binary;
|
use crate::common::binary_conversions::ip_to_binary;
|
||||||
use crate::common::network_startup::is_public_network_address;
|
use crate::common::network_startup::is_public_network_address;
|
||||||
use crate::common::skein::skein_256_hash_data;
|
use crate::common::skein::skein_256_hash_data;
|
||||||
use crate::common::types::GENESIS_IP;
|
use crate::common::types::GENESIS_IP;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::lazy_static;
|
use crate::lazy_static;
|
||||||
use crate::log::{error, info, warn};
|
use crate::log::{error, info, warn};
|
||||||
use crate::records::block_height::get_block_height::get_height;
|
use crate::records::block_height::get_block_height::get_height;
|
||||||
use crate::records::ip_score::enums::InfractionType;
|
use crate::records::ip_score::enums::InfractionType;
|
||||||
use crate::records::ip_score::score::update_ip_score;
|
use crate::records::ip_score::score::update_ip_score;
|
||||||
use crate::records::memory::connections::{spawn_reconnect_bootstrap, CONNECTIONS};
|
use crate::records::memory::connections::{spawn_reconnect_bootstrap, CONNECTIONS};
|
||||||
use crate::records::memory::enums::ConnectionType;
|
use crate::records::memory::enums::ConnectionType;
|
||||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
|
||||||
use crate::records::memory::network_mapping::enums::NodeEditType;
|
use crate::records::memory::network_mapping::enums::NodeEditType;
|
||||||
use crate::records::memory::network_mapping::structs::{
|
use crate::records::memory::network_mapping::structs::{
|
||||||
AddAddressParams, DeleteAddressParams, PingMonitorParams, SignedNodeEdit, NODE_RECORD_BYTES,
|
AddAddressParams, DeleteAddressParams, PingMonitorParams, SignedNodeEdit, NODE_RECORD_BYTES,
|
||||||
};
|
};
|
||||||
use crate::records::memory::structs::Connection;
|
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||||
use crate::records::unpack_block::unpack_header::load_block_header;
|
use crate::records::memory::structs::Connection;
|
||||||
use crate::rpc::client::handshake_processing::BootstrapParams;
|
use crate::records::unpack_block::unpack_header::load_block_header;
|
||||||
use crate::rpc::command_maps::RPC_BLOCK_HEIGHT;
|
use crate::rpc::client::handshake_processing::BootstrapParams;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::command_maps::RPC_BLOCK_HEIGHT;
|
||||||
use crate::sled::Db;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::sleep;
|
use crate::sled::Db;
|
||||||
use crate::timeout;
|
use crate::sleep;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::timeout;
|
||||||
use crate::Arc;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::Duration;
|
use crate::Arc;
|
||||||
use crate::HashMap;
|
use crate::Duration;
|
||||||
use crate::Mutex;
|
use crate::HashMap;
|
||||||
use crate::TcpStream;
|
use crate::Mutex;
|
||||||
use crate::Utc;
|
use crate::TcpStream;
|
||||||
|
use crate::Utc;
|
||||||
lazy_static! {
|
|
||||||
static ref ADDRESS_MAP: Mutex<HashMap<String, NodeInfo>> = Mutex::new(HashMap::new());
|
lazy_static! {
|
||||||
static ref PING_MONITORS: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
|
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)]
|
|
||||||
pub struct NodeInfo {
|
#[derive(Debug)]
|
||||||
ip: String,
|
pub struct NodeInfo {
|
||||||
blocks_mined: u8,
|
ip: String,
|
||||||
added_by: String,
|
blocks_mined: u8,
|
||||||
added_timestamp: u64,
|
added_by: String,
|
||||||
added_signature: String,
|
added_timestamp: u64,
|
||||||
deleted_by: String,
|
added_signature: String,
|
||||||
deleted_timestamp: u64,
|
deleted_by: String,
|
||||||
deleted_block: u32,
|
deleted_timestamp: u64,
|
||||||
deleted_signature: String,
|
deleted_block: u32,
|
||||||
}
|
deleted_signature: String,
|
||||||
|
}
|
||||||
impl NodeInfo {
|
|
||||||
async fn release_ping_monitor(address: &str, signature: &str) {
|
impl NodeInfo {
|
||||||
let mut monitors = PING_MONITORS.lock().await;
|
async fn release_ping_monitor(address: &str, signature: &str) {
|
||||||
if monitors
|
let mut monitors = PING_MONITORS.lock().await;
|
||||||
.get(address)
|
if monitors
|
||||||
.map(|current| current == signature)
|
.get(address)
|
||||||
.unwrap_or(false)
|
.map(|current| current == signature)
|
||||||
{
|
.unwrap_or(false)
|
||||||
monitors.remove(address);
|
{
|
||||||
}
|
monitors.remove(address);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn new(
|
|
||||||
ip: String,
|
fn new(
|
||||||
blocks_mined: u8,
|
ip: String,
|
||||||
added_by: String,
|
blocks_mined: u8,
|
||||||
added_timestamp: u64,
|
added_by: String,
|
||||||
added_signature: String,
|
added_timestamp: u64,
|
||||||
) -> Self {
|
added_signature: String,
|
||||||
NodeInfo {
|
) -> Self {
|
||||||
ip,
|
NodeInfo {
|
||||||
blocks_mined,
|
ip,
|
||||||
added_by,
|
blocks_mined,
|
||||||
added_timestamp,
|
added_by,
|
||||||
added_signature,
|
added_timestamp,
|
||||||
deleted_by: "".to_string(),
|
added_signature,
|
||||||
deleted_timestamp: 0_u64,
|
deleted_by: "".to_string(),
|
||||||
deleted_block: 0_u32,
|
deleted_timestamp: 0_u64,
|
||||||
deleted_signature: "".to_string(),
|
deleted_block: 0_u32,
|
||||||
|
deleted_signature: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,4 +88,3 @@ pub mod enums;
|
||||||
mod mined_counts;
|
mod mined_counts;
|
||||||
mod queries;
|
mod queries;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,23 @@ impl NodeInfo {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn find_address_by_ip(ip: &str) -> Option<String> {
|
pub async fn find_address_by_ip(ip: &str) -> Option<String> {
|
||||||
let map = ADDRESS_MAP.lock().await;
|
let map = ADDRESS_MAP.lock().await;
|
||||||
for (address, node_info) in map.iter() {
|
for (address, node_info) in map.iter() {
|
||||||
// Reverse lookup is needed when a peer is identified by socket IP
|
// Reverse lookup is needed when a peer is identified by socket IP
|
||||||
// but the node map is keyed by wallet short address.
|
// but the node map is keyed by wallet short address.
|
||||||
if node_info.ip == ip {
|
if node_info.ip == ip {
|
||||||
return Some(address.clone());
|
return Some(address.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_ip_by_address(address: &str) -> Option<String> {
|
pub async fn find_ip_by_address(address: &str) -> Option<String> {
|
||||||
let map = ADDRESS_MAP.lock().await;
|
let map = ADDRESS_MAP.lock().await;
|
||||||
map.get(address).map(|node_info| node_info.ip.clone())
|
map.get(address).map(|node_info| node_info.ip.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn active_node_ips() -> Vec<String> {
|
pub async fn active_node_ips() -> Vec<String> {
|
||||||
let map = ADDRESS_MAP.lock().await;
|
let map = ADDRESS_MAP.lock().await;
|
||||||
map.values()
|
map.values()
|
||||||
|
|
@ -37,8 +36,8 @@ impl NodeInfo {
|
||||||
.filter(|node_info| node_info.deleted_by.is_empty())
|
.filter(|node_info| node_info.deleted_by.is_empty())
|
||||||
.map(|node_info| node_info.ip.clone())
|
.map(|node_info| node_info.ip.clone())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_deleted_addresses() -> Vec<u8> {
|
pub async fn get_deleted_addresses() -> Vec<u8> {
|
||||||
let map = ADDRESS_MAP.lock().await;
|
let map = ADDRESS_MAP.lock().await;
|
||||||
map.iter()
|
map.iter()
|
||||||
|
|
@ -46,25 +45,25 @@ impl NodeInfo {
|
||||||
// The RPC response is a packed list of deleted short-address
|
// The RPC response is a packed list of deleted short-address
|
||||||
// bytes, so invalid address keys are skipped.
|
// bytes, so invalid address keys are skipped.
|
||||||
if node_info.deleted_timestamp > 0 {
|
if node_info.deleted_timestamp > 0 {
|
||||||
Wallet::short_address_to_bytes(address)
|
Wallet::short_address_to_bytes(address)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_valid_nodes() -> RpcResponse {
|
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 fixed binary layout
|
||||||
// used by peer bootstrap and node-list synchronization.
|
// used by peer bootstrap and node-list synchronization.
|
||||||
let map = ADDRESS_MAP.lock().await;
|
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_BYTES);
|
||||||
|
|
||||||
for (address, node_info) in map.iter() {
|
for (address, node_info) in map.iter() {
|
||||||
let address_bytes = match Wallet::short_address_to_bytes(address) {
|
let address_bytes = match Wallet::short_address_to_bytes(address) {
|
||||||
Some(bytes) => bytes,
|
Some(bytes) => bytes,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
let ip_bytes = ip_to_binary(&node_info.ip);
|
let ip_bytes = ip_to_binary(&node_info.ip);
|
||||||
let blocks_mined = node_info.blocks_mined;
|
let blocks_mined = node_info.blocks_mined;
|
||||||
|
|
@ -74,58 +73,58 @@ impl NodeInfo {
|
||||||
// Empty deletion fields serialize as zero-filled fixed-width values
|
// Empty deletion fields serialize as zero-filled fixed-width values
|
||||||
// so every node record stays the same size on the wire.
|
// so every node record stays the same size on the wire.
|
||||||
let deleted_by_bytes = if node_info.deleted_by.is_empty() {
|
let deleted_by_bytes = if node_info.deleted_by.is_empty() {
|
||||||
vec![0u8; Wallet::ADDRESS_BYTES_LENGTH]
|
vec![0u8; Wallet::ADDRESS_BYTES_LENGTH]
|
||||||
} else {
|
} else {
|
||||||
Wallet::long_address_to_bytes(node_info.deleted_by.to_string())
|
Wallet::long_address_to_bytes(node_info.deleted_by.to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
let deleted_timestamp_bytes = node_info.deleted_timestamp.to_le_bytes();
|
let deleted_timestamp_bytes = node_info.deleted_timestamp.to_le_bytes();
|
||||||
let deleted_block_bytes = node_info.deleted_block.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() {
|
let deleted_signature_bytes = if node_info.deleted_signature.is_empty() {
|
||||||
vec![0u8; Wallet::SIGNATURE_LENGTH]
|
vec![0u8; Wallet::SIGNATURE_LENGTH]
|
||||||
} else {
|
} else {
|
||||||
decode(node_info.deleted_signature.clone()).unwrap()
|
decode(node_info.deleted_signature.clone()).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let added_signature_bytes = decode(node_info.added_signature.clone()).unwrap();
|
let added_signature_bytes = decode(node_info.added_signature.clone()).unwrap();
|
||||||
|
|
||||||
// Field order here must match the parser used by node-list
|
// Field order here must match the parser used by node-list
|
||||||
// synchronization.
|
// synchronization.
|
||||||
data.extend_from_slice(&address_bytes);
|
data.extend_from_slice(&address_bytes);
|
||||||
data.extend_from_slice(&ip_bytes);
|
data.extend_from_slice(&ip_bytes);
|
||||||
data.push(blocks_mined);
|
data.push(blocks_mined);
|
||||||
data.extend_from_slice(&added_by_bytes);
|
data.extend_from_slice(&added_by_bytes);
|
||||||
data.extend_from_slice(&added_timestamp_bytes);
|
data.extend_from_slice(&added_timestamp_bytes);
|
||||||
data.extend_from_slice(&added_signature_bytes);
|
data.extend_from_slice(&added_signature_bytes);
|
||||||
data.extend_from_slice(&deleted_by_bytes);
|
data.extend_from_slice(&deleted_by_bytes);
|
||||||
data.extend_from_slice(&deleted_timestamp_bytes);
|
data.extend_from_slice(&deleted_timestamp_bytes);
|
||||||
data.extend_from_slice(&deleted_block_bytes);
|
data.extend_from_slice(&deleted_block_bytes);
|
||||||
data.extend_from_slice(&deleted_signature_bytes);
|
data.extend_from_slice(&deleted_signature_bytes);
|
||||||
}
|
}
|
||||||
RpcResponse::Binary(data)
|
RpcResponse::Binary(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn added_signature(
|
pub async fn added_signature(
|
||||||
address: &str,
|
address: &str,
|
||||||
ip: &str,
|
ip: &str,
|
||||||
current_timestamp: u64,
|
current_timestamp: u64,
|
||||||
wallet_key: &str,
|
wallet_key: &str,
|
||||||
) -> String {
|
) -> String {
|
||||||
// Node edits are signed over address, IP, signer, and timestamp
|
// Node edits are signed over address, IP, signer, and timestamp
|
||||||
// so peers can independently verify the advertised change.
|
// so peers can independently verify the advertised change.
|
||||||
let wallet = match Wallet::try_obtain_wallet(wallet_key.to_string(), None).await {
|
let wallet = match Wallet::try_obtain_wallet(wallet_key.to_string(), None).await {
|
||||||
Ok(wallet) => wallet,
|
Ok(wallet) => wallet,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Wallet decryption failed while signing node edit: {err}");
|
error!("Wallet decryption failed while signing node edit: {err}");
|
||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let added_by = wallet.saved.long_address;
|
let added_by = wallet.saved.long_address;
|
||||||
let private_key = wallet.saved.private_key;
|
let private_key = wallet.saved.private_key;
|
||||||
|
|
||||||
let data = format!("{address}{ip}{added_by}{current_timestamp}");
|
let data = format!("{address}{ip}{added_by}{current_timestamp}");
|
||||||
let hashed_data = skein_256_hash_data(&data);
|
let hashed_data = skein_256_hash_data(&data);
|
||||||
Wallet::sign_transaction(&hashed_data, &private_key).await
|
Wallet::sign_transaction(&hashed_data, &private_key).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ fn random_3_byte_number() -> [u8; 3] {
|
||||||
let num: u32 = rng.gen_range(0..=0xFFFFFF);
|
let num: u32 = rng.gen_range(0..=0xFFFFFF);
|
||||||
// The protocol UID is three bytes on the wire, so the random u32 is sliced
|
// The protocol UID is three bytes on the wire, so the random u32 is sliced
|
||||||
// down to the same fixed-width little-endian layout used by requests.
|
// down to the same fixed-width little-endian layout used by requests.
|
||||||
|
|
||||||
num.to_le_bytes()[1..4].try_into().unwrap()
|
num.to_le_bytes()[1..4].try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,21 @@ pub async fn process_lender(
|
||||||
pending_effects.append_tree_if_key_exists(
|
pending_effects.append_tree_if_key_exists(
|
||||||
"nfts",
|
"nfts",
|
||||||
"nft_history",
|
"nft_history",
|
||||||
transaction.unsigned_loan_contract.collateral.as_bytes().to_vec(),
|
transaction
|
||||||
|
.unsigned_loan_contract
|
||||||
|
.collateral
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
txhash_bytes.clone(),
|
txhash_bytes.clone(),
|
||||||
);
|
);
|
||||||
pending_effects.append_tree_if_key_exists(
|
pending_effects.append_tree_if_key_exists(
|
||||||
"nfts",
|
"nfts",
|
||||||
"nft_history",
|
"nft_history",
|
||||||
transaction.unsigned_loan_contract.loan_coin.as_bytes().to_vec(),
|
transaction
|
||||||
|
.unsigned_loan_contract
|
||||||
|
.loan_coin
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
txhash_bytes,
|
txhash_bytes,
|
||||||
);
|
);
|
||||||
Ok(binary_data)
|
Ok(binary_data)
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,16 @@ pub async fn process_nft(
|
||||||
BalanceOperand::Addition,
|
BalanceOperand::Addition,
|
||||||
);
|
);
|
||||||
pending_effects.set_tree("nfts", nft_save_name.as_bytes().to_vec(), b"1".to_vec());
|
pending_effects.set_tree("nfts", nft_save_name.as_bytes().to_vec(), b"1".to_vec());
|
||||||
pending_effects.set_tree("nft_origins", nft_save_name.as_bytes().to_vec(), txhash_bytes.clone());
|
pending_effects.set_tree(
|
||||||
pending_effects.append_tree("nft_history", nft_save_name.into_bytes(), txhash_bytes.clone());
|
"nft_origins",
|
||||||
|
nft_save_name.as_bytes().to_vec(),
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
);
|
||||||
|
pending_effects.append_tree(
|
||||||
|
"nft_history",
|
||||||
|
nft_save_name.into_bytes(),
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let nft_save_name = transaction.unsigned_create_nft.nft_name.clone();
|
let nft_save_name = transaction.unsigned_create_nft.nft_name.clone();
|
||||||
|
|
@ -72,8 +80,16 @@ pub async fn process_nft(
|
||||||
BalanceOperand::Addition,
|
BalanceOperand::Addition,
|
||||||
);
|
);
|
||||||
pending_effects.set_tree("nfts", nft_save_name.as_bytes().to_vec(), b"1".to_vec());
|
pending_effects.set_tree("nfts", nft_save_name.as_bytes().to_vec(), b"1".to_vec());
|
||||||
pending_effects.set_tree("nft_origins", nft_save_name.as_bytes().to_vec(), txhash_bytes.clone());
|
pending_effects.set_tree(
|
||||||
pending_effects.append_tree("nft_history", nft_save_name.into_bytes(), txhash_bytes.clone());
|
"nft_origins",
|
||||||
|
nft_save_name.as_bytes().to_vec(),
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
);
|
||||||
|
pending_effects.append_tree(
|
||||||
|
"nft_history",
|
||||||
|
nft_save_name.into_bytes(),
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record the txid location so RPC lookups can resolve the saved
|
// Record the txid location so RPC lookups can resolve the saved
|
||||||
|
|
|
||||||
|
|
@ -373,12 +373,8 @@ fn apply_effect(db: &Db, effect: &PendingEffect) -> Result<AppliedEffect, String
|
||||||
contract_id,
|
contract_id,
|
||||||
payment,
|
payment,
|
||||||
} => {
|
} => {
|
||||||
let previous = append_tree_value(
|
let previous =
|
||||||
db,
|
append_tree_value(db, "contract_payments", contract_id, &payment.to_le_bytes())?;
|
||||||
"contract_payments",
|
|
||||||
contract_id,
|
|
||||||
&payment.to_le_bytes(),
|
|
||||||
)?;
|
|
||||||
Ok(AppliedEffect::TreeMutation {
|
Ok(AppliedEffect::TreeMutation {
|
||||||
tree: "contract_payments",
|
tree: "contract_payments",
|
||||||
key: contract_id.clone(),
|
key: contract_id.clone(),
|
||||||
|
|
@ -400,8 +396,10 @@ fn rollback_effect(db: &Db, effect: AppliedEffect) -> Result<(), String> {
|
||||||
amount,
|
amount,
|
||||||
coin,
|
coin,
|
||||||
operand,
|
operand,
|
||||||
} => balance_sheet_operation_with_db(db, &address, amount, &coin, operand.inverse().as_str())
|
} => {
|
||||||
.map_err(|err| format!("Failed to roll back balance effect: {err}")),
|
balance_sheet_operation_with_db(db, &address, amount, &coin, operand.inverse().as_str())
|
||||||
|
.map_err(|err| format!("Failed to roll back balance effect: {err}"))
|
||||||
|
}
|
||||||
AppliedEffect::TreeMutation {
|
AppliedEffect::TreeMutation {
|
||||||
tree,
|
tree,
|
||||||
key,
|
key,
|
||||||
|
|
@ -428,7 +426,12 @@ fn rollback_effect(db: &Db, effect: AppliedEffect) -> Result<(), String> {
|
||||||
previous_rollback,
|
previous_rollback,
|
||||||
} => {
|
} => {
|
||||||
restore_vanity_mapping(db, &owner_address, previous_vanity)?;
|
restore_vanity_mapping(db, &owner_address, previous_vanity)?;
|
||||||
restore_tree_value(db, WALLET_VANITY_ROLLBACK_TREE, &rollback_key, previous_rollback)
|
restore_tree_value(
|
||||||
|
db,
|
||||||
|
WALLET_VANITY_ROLLBACK_TREE,
|
||||||
|
&rollback_key,
|
||||||
|
previous_rollback,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
AppliedEffect::Noop => Ok(()),
|
AppliedEffect::Noop => Ok(()),
|
||||||
}
|
}
|
||||||
|
|
@ -581,8 +584,8 @@ fn apply_vanity_effect(
|
||||||
) -> Result<AppliedEffect, String> {
|
) -> Result<AppliedEffect, String> {
|
||||||
let previous_vanity = get_registered_vanity_for_owner(db, owner_address)
|
let previous_vanity = get_registered_vanity_for_owner(db, owner_address)
|
||||||
.map_err(|err| format!("Could not read existing vanity mapping: {err}"))?;
|
.map_err(|err| format!("Could not read existing vanity mapping: {err}"))?;
|
||||||
let rollback_key =
|
let rollback_key = crate::decode(txhash)
|
||||||
crate::decode(txhash).map_err(|_| "Could not decode vanity transaction hash".to_string())?;
|
.map_err(|_| "Could not decode vanity transaction hash".to_string())?;
|
||||||
let rollback_tree = db
|
let rollback_tree = db
|
||||||
.open_tree(WALLET_VANITY_ROLLBACK_TREE)
|
.open_tree(WALLET_VANITY_ROLLBACK_TREE)
|
||||||
.map_err(|err| format!("Could not open vanity rollback tree: {err}"))?;
|
.map_err(|err| format!("Could not open vanity rollback tree: {err}"))?;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
use crate::common::check_genesis::genesis_checkup;
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::miner::flag::{is_mining_running, is_normal_mode, is_reorganizing_mode, is_syncing_mode};
|
use crate::decode;
|
||||||
|
use crate::fs;
|
||||||
|
use crate::log::{error, info};
|
||||||
|
use crate::miner::flag::{
|
||||||
|
is_mining_running, is_normal_mode, is_reorganizing_mode, is_syncing_mode,
|
||||||
|
};
|
||||||
use crate::orphans::snapshot_check::{snapshot_height, update_snapshot};
|
use crate::orphans::snapshot_check::{snapshot_height, update_snapshot};
|
||||||
use crate::records::block_height::get_block_height::get_height;
|
use crate::records::block_height::get_block_height::get_height;
|
||||||
use crate::records::block_height::increase_block_height::increase_height;
|
use crate::records::block_height::increase_block_height::increase_height;
|
||||||
|
|
@ -8,8 +13,8 @@ use crate::records::memory::averages::{calculate_averages, update_block_data};
|
||||||
use crate::records::memory::mempool::{
|
use crate::records::memory::mempool::{
|
||||||
apply_selected_transaction_math, mark_processed_by_signatures,
|
apply_selected_transaction_math, mark_processed_by_signatures,
|
||||||
mark_selected_transactions_processed, restore_processed_by_signatures,
|
mark_selected_transactions_processed, restore_processed_by_signatures,
|
||||||
restore_selected_transactions_processed, select_transactions_for_block, spawn_processed_cleanup,
|
restore_selected_transactions_processed, select_transactions_for_block,
|
||||||
stream_selected_transaction_originals,
|
spawn_processed_cleanup, stream_selected_transaction_originals,
|
||||||
};
|
};
|
||||||
use crate::records::memory::network_mapping::NodeInfo;
|
use crate::records::memory::network_mapping::NodeInfo;
|
||||||
use crate::records::memory::torrent_status::prune_torrent_statuses_through_height;
|
use crate::records::memory::torrent_status::prune_torrent_statuses_through_height;
|
||||||
|
|
@ -25,10 +30,7 @@ use crate::records::unpack_block::unpack_header::load_block_header;
|
||||||
use crate::torrent::create_metadata::{broadcast_new_torrent_to_peers, metadata_from_file};
|
use crate::torrent::create_metadata::{broadcast_new_torrent_to_peers, metadata_from_file};
|
||||||
use crate::torrent::torrenting_system::save_torrent::prune_staged_torrents;
|
use crate::torrent::torrenting_system::save_torrent::prune_staged_torrents;
|
||||||
use crate::torrent::torrenting_system::torrent_cache::prune_recent_torrents;
|
use crate::torrent::torrenting_system::torrent_cache::prune_recent_torrents;
|
||||||
use crate::log::{error, info};
|
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::decode;
|
|
||||||
use crate::fs;
|
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::PathBuf;
|
use crate::PathBuf;
|
||||||
use crate::Utc;
|
use crate::Utc;
|
||||||
|
|
@ -337,7 +339,9 @@ async fn save_binary_data_with_mempool_stream(
|
||||||
let _ = update_snapshot(db, next_number).await;
|
let _ = update_snapshot(db, next_number).await;
|
||||||
if let Some(snapshot_height) = snapshot_height(db).await {
|
if let Some(snapshot_height) = snapshot_height(db).await {
|
||||||
if let Err(err) = finalize_rewards_through_height(db, snapshot_height).await {
|
if let Err(err) = finalize_rewards_through_height(db, snapshot_height).await {
|
||||||
error!("Failed to finalize rewards through snapshot height {snapshot_height}: {err}");
|
error!(
|
||||||
|
"Failed to finalize rewards through snapshot height {snapshot_height}: {err}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
prune_recent_torrents(snapshot_height).await;
|
prune_recent_torrents(snapshot_height).await;
|
||||||
prune_torrent_statuses_through_height(snapshot_height).await;
|
prune_torrent_statuses_through_height(snapshot_height).await;
|
||||||
|
|
@ -465,7 +469,9 @@ async fn save_binary_data(params: SaveBinaryDataParams<'_>) -> Result<(), String
|
||||||
let _ = update_snapshot(db, next_number).await;
|
let _ = update_snapshot(db, next_number).await;
|
||||||
if let Some(snapshot_height) = snapshot_height(db).await {
|
if let Some(snapshot_height) = snapshot_height(db).await {
|
||||||
if let Err(err) = finalize_rewards_through_height(db, snapshot_height).await {
|
if let Err(err) = finalize_rewards_through_height(db, snapshot_height).await {
|
||||||
error!("Failed to finalize rewards through snapshot height {snapshot_height}: {err}");
|
error!(
|
||||||
|
"Failed to finalize rewards through snapshot height {snapshot_height}: {err}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
prune_recent_torrents(snapshot_height).await;
|
prune_recent_torrents(snapshot_height).await;
|
||||||
prune_torrent_statuses_through_height(snapshot_height).await;
|
prune_torrent_statuses_through_height(snapshot_height).await;
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,7 @@ pub async fn process_token(
|
||||||
// Serialize the token-creation transaction and compute its txid
|
// Serialize the token-creation transaction and compute its txid
|
||||||
// before applying the balance-sheet and token-registry updates.
|
// before applying the balance-sheet and token-registry updates.
|
||||||
let txhash = transaction.unsigned_create_token.hash().await;
|
let txhash = transaction.unsigned_create_token.hash().await;
|
||||||
let txhash_bytes =
|
let txhash_bytes = decode(&txhash).map_err(|e| format!("Error decoding token txhash: {e}"))?;
|
||||||
decode(&txhash).map_err(|e| format!("Error decoding token txhash: {e}"))?;
|
|
||||||
let transaction_bytes = match transaction.to_bytes().await {
|
let transaction_bytes = match transaction.to_bytes().await {
|
||||||
Ok(bytes) => bytes,
|
Ok(bytes) => bytes,
|
||||||
Err(e) => return Err(e.to_string()),
|
Err(e) => return Err(e.to_string()),
|
||||||
|
|
@ -63,7 +62,11 @@ pub async fn process_token(
|
||||||
|
|
||||||
let origin_key = transaction.unsigned_create_token.ticker.clone();
|
let origin_key = transaction.unsigned_create_token.ticker.clone();
|
||||||
let origin_value = txhash.as_bytes();
|
let origin_value = txhash.as_bytes();
|
||||||
pending_effects.set_tree("token_origins", origin_key.into_bytes(), origin_value.to_vec());
|
pending_effects.set_tree(
|
||||||
|
"token_origins",
|
||||||
|
origin_key.into_bytes(),
|
||||||
|
origin_value.to_vec(),
|
||||||
|
);
|
||||||
pending_effects.append_tree(
|
pending_effects.append_tree(
|
||||||
"token_history",
|
"token_history",
|
||||||
transaction.unsigned_create_token.ticker.as_bytes().to_vec(),
|
transaction.unsigned_create_token.ticker.as_bytes().to_vec(),
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
pub(super) const WALLET_REGISTRY_TREE: &str = "wallet_registry";
|
pub(super) const WALLET_REGISTRY_TREE: &str = "wallet_registry";
|
||||||
pub(super) const WALLET_VANITY_ADDRESS_TREE: &str = "wallet_vanity_address";
|
pub(super) const WALLET_VANITY_ADDRESS_TREE: &str = "wallet_vanity_address";
|
||||||
pub(super) const WALLET_VANITY_OWNER_TREE: &str = "wallet_vanity_owner";
|
pub(super) const WALLET_VANITY_OWNER_TREE: &str = "wallet_vanity_owner";
|
||||||
pub(crate) const WALLET_VANITY_ROLLBACK_TREE: &str = "wallet_vanity_rollback";
|
pub(crate) const WALLET_VANITY_ROLLBACK_TREE: &str = "wallet_vanity_rollback";
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod mappings;
|
mod mappings;
|
||||||
mod storage;
|
mod storage;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
|
||||||
pub use helpers::{get_registered_pubkey, is_registered_short_address, short_address_exists};
|
pub use helpers::{get_registered_pubkey, is_registered_short_address, short_address_exists};
|
||||||
pub use mappings::{
|
pub use mappings::{
|
||||||
get_registered_vanity_for_owner, list_registered_wallets, require_canonical_registered_short_address,
|
get_registered_vanity_for_owner, list_registered_wallets,
|
||||||
resolve_canonical_registered_short_address, resolve_local_input_short_address,
|
require_canonical_registered_short_address, resolve_canonical_registered_short_address,
|
||||||
resolve_owner_from_vanity_address, resolve_pubkey_from_short_address, take_previous_vanity_for_txid,
|
resolve_local_input_short_address, resolve_owner_from_vanity_address,
|
||||||
};
|
resolve_pubkey_from_short_address, take_previous_vanity_for_txid,
|
||||||
pub use storage::{
|
};
|
||||||
register_or_update_vanity_address, register_short_address, remove_registered_vanity_for_owner,
|
pub use storage::{
|
||||||
store_previous_vanity_for_txid,
|
register_or_update_vanity_address, register_short_address, remove_registered_vanity_for_owner,
|
||||||
};
|
store_previous_vanity_for_txid,
|
||||||
pub use structs::{VanityRegistrationResult, WalletRegistrationResult};
|
};
|
||||||
|
pub use structs::{VanityRegistrationResult, WalletRegistrationResult};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::common::network_startup::get_listen_ip;
|
use crate::common::network_startup::get_listen_ip;
|
||||||
|
use crate::io;
|
||||||
use crate::rpc::client::handshake_message::prepare_handshake_message;
|
use crate::rpc::client::handshake_message::prepare_handshake_message;
|
||||||
use crate::rpc::client::handshake_processing::process_handshake_response;
|
use crate::rpc::client::handshake_processing::process_handshake_response;
|
||||||
use crate::rpc::client::structs::{Connect, Handshake};
|
use crate::rpc::client::structs::{Connect, Handshake};
|
||||||
use crate::rpc::command_maps::{MAX_RPC_REPLY_BYTES, RPC_REPLY};
|
use crate::rpc::command_maps::{MAX_RPC_REPLY_BYTES, RPC_REPLY};
|
||||||
use crate::rpc::handshake_constants::HANDSHAKE_RESPONSE_BYTES;
|
use crate::rpc::handshake_constants::HANDSHAKE_RESPONSE_BYTES;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use crate::io;
|
|
||||||
use crate::IpAddr;
|
use crate::IpAddr;
|
||||||
use crate::SocketAddr;
|
use crate::SocketAddr;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
|
use crate::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::TcpSocket;
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
pub async fn connect_and_handshake(params: Connect) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn connect_and_handshake(params: Connect) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::common::binary_conversions::ip_port_to_binary;
|
use crate::common::binary_conversions::ip_port_to_binary;
|
||||||
use crate::common::network_startup::get_ip_and_port;
|
use crate::common::network_startup::get_ip_and_port;
|
||||||
use crate::common::skein::skein_256_hash_data;
|
use crate::common::skein::skein_256_hash_data;
|
||||||
|
use crate::decode;
|
||||||
|
use crate::io;
|
||||||
use crate::rpc::commands::time::request_time;
|
use crate::rpc::commands::time::request_time;
|
||||||
use crate::rpc::handshake_constants::HANDSHAKE_REQUEST_BYTES;
|
use crate::rpc::handshake_constants::HANDSHAKE_REQUEST_BYTES;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::decode;
|
|
||||||
use crate::io;
|
|
||||||
|
|
||||||
pub async fn prepare_handshake_message(wallet: &Wallet, message: &str) -> io::Result<Vec<u8>> {
|
pub async fn prepare_handshake_message(wallet: &Wallet, message: &str) -> io::Result<Vec<u8>> {
|
||||||
// Client handshakes are assembled from the signed message, wallet identity, timestamp,
|
// Client handshakes are assembled from the signed message, wallet identity, timestamp,
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,46 @@
|
||||||
use crate::common::binary_conversions::binary_to_ip_port;
|
use crate::common::binary_conversions::binary_to_ip_port;
|
||||||
use crate::common::check_genesis::genesis_checkup;
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
|
use crate::common::network_startup::get_ip_and_port;
|
||||||
use crate::common::skein::skein_256_hash_data;
|
use crate::common::skein::skein_256_hash_data;
|
||||||
use crate::config::SETTINGS;
|
use crate::config::SETTINGS;
|
||||||
use crate::miner::flag::{clear_mining_stop_request, request_mining_stop, set_mining_state, set_node_mode, MiningState, NodeMode};
|
use crate::encode;
|
||||||
|
use crate::io;
|
||||||
|
use crate::log::{error, info, warn};
|
||||||
|
use crate::miner::flag::{
|
||||||
|
clear_mining_stop_request, request_mining_stop, set_mining_state, set_node_mode, MiningState,
|
||||||
|
NodeMode,
|
||||||
|
};
|
||||||
use crate::orphans::structs::OrphanCheckup2;
|
use crate::orphans::structs::OrphanCheckup2;
|
||||||
use crate::orphans::sync_check::sync_checkup;
|
use crate::orphans::sync_check::sync_checkup;
|
||||||
use crate::orphans::torrent_candidates::hydrate_torrent_candidates;
|
use crate::orphans::torrent_candidates::hydrate_torrent_candidates;
|
||||||
use crate::records::block_height::get_block_height::get_height;
|
use crate::records::block_height::get_block_height::get_height;
|
||||||
use crate::records::memory::connections::{set_reconnect_context, CONNECTIONS};
|
use crate::records::memory::connections::{set_reconnect_context, CONNECTIONS};
|
||||||
use crate::records::memory::enums::{ClientType, ConnectionType};
|
use crate::records::memory::enums::{ClientType, ConnectionType};
|
||||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
|
||||||
use crate::records::memory::network_mapping::NodeInfo;
|
use crate::records::memory::network_mapping::NodeInfo;
|
||||||
|
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||||
use crate::records::memory::structs::{Connection, StoreConnectionParams};
|
use crate::records::memory::structs::{Connection, StoreConnectionParams};
|
||||||
use crate::rpc::client::handshake::connect_and_handshake;
|
use crate::rpc::client::handshake::connect_and_handshake;
|
||||||
|
use crate::rpc::client::register_wallet::register_connected_wallet;
|
||||||
use crate::rpc::client::structs::{Connect, Handshake};
|
use crate::rpc::client::structs::{Connect, Handshake};
|
||||||
use crate::rpc::client::syncing::node_syncing;
|
use crate::rpc::client::syncing::node_syncing;
|
||||||
use crate::rpc::client::register_wallet::register_connected_wallet;
|
|
||||||
use crate::rpc::client::wallet_registry_sync::sync_wallet_registry;
|
use crate::rpc::client::wallet_registry_sync::sync_wallet_registry;
|
||||||
use crate::rpc::command_maps::RPC_RANDOM_NODE;
|
use crate::rpc::command_maps::RPC_RANDOM_NODE;
|
||||||
use crate::rpc::handshake_constants::{HANDSHAKE_ADDRESS_OFFSET, HANDSHAKE_MESSAGE_BYTES, HANDSHAKE_RESPONSE_BYTES, HANDSHAKE_SIGNATURE_OFFSET,};
|
use crate::rpc::handshake_constants::{
|
||||||
|
HANDSHAKE_ADDRESS_OFFSET, HANDSHAKE_MESSAGE_BYTES, HANDSHAKE_RESPONSE_BYTES,
|
||||||
|
HANDSHAKE_SIGNATURE_OFFSET,
|
||||||
|
};
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::rpc::server::rpc_command_loop::start_loop;
|
use crate::rpc::server::rpc_command_loop::start_loop;
|
||||||
use crate::common::network_startup::get_ip_and_port;
|
use crate::sled::Db;
|
||||||
use crate::startup::network_broadcast::announce_self_to_network;
|
use crate::startup::network_broadcast::announce_self_to_network;
|
||||||
use crate::startup::remote_height::request_remote_height;
|
use crate::startup::remote_height::request_remote_height;
|
||||||
|
use crate::timeout;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::log::{error, info, warn};
|
|
||||||
use crate::sled::Db;
|
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
use crate::encode;
|
|
||||||
use crate::io;
|
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::SocketAddr;
|
use crate::SocketAddr;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
use crate::timeout;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BootstrapParams {
|
pub struct BootstrapParams {
|
||||||
|
|
@ -104,7 +110,10 @@ pub async fn bootstrap_peer_discovery(mut params: BootstrapParams) -> Result<(),
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if Connection::get_stream_from_memory(&addr_string).await.is_some() {
|
if Connection::get_stream_from_memory(&addr_string)
|
||||||
|
.await
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
no_progress_count += 1;
|
no_progress_count += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::common::skein::skein_256_hash_bytes;
|
use crate::common::skein::skein_256_hash_bytes;
|
||||||
|
use crate::decode;
|
||||||
|
use crate::log::warn;
|
||||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||||
use crate::rpc::command_maps::RPC_REGISTER_WALLET;
|
use crate::rpc::command_maps::RPC_REGISTER_WALLET;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
|
use crate::timeout;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::log::warn;
|
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::decode;
|
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
use crate::timeout;
|
|
||||||
|
|
||||||
pub async fn register_connected_wallet(
|
pub async fn register_connected_wallet(
|
||||||
stream: Arc<Mutex<TcpStream>>,
|
stream: Arc<Mutex<TcpStream>>,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
use crate::common::check_genesis::genesis_checkup;
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
|
use crate::io;
|
||||||
|
use crate::log::{error, info, warn};
|
||||||
use crate::orphans::structs::OrphanCheckup2;
|
use crate::orphans::structs::OrphanCheckup2;
|
||||||
use crate::orphans::sync_check::sync_checkup;
|
use crate::orphans::sync_check::sync_checkup;
|
||||||
use crate::records::block_height::get_block_height::get_height;
|
use crate::records::block_height::get_block_height::get_height;
|
||||||
use crate::records::memory::response_channels::reserve_entry;
|
use crate::records::memory::response_channels::reserve_entry;
|
||||||
use crate::records::memory::response_channels::Command;
|
use crate::records::memory::response_channels::Command;
|
||||||
use crate::torrent::structs::Torrent;
|
|
||||||
use crate::torrent::torrenting_system::torrent_requests::{handle_response_and_save_torrent, send_request_torrent_message};
|
|
||||||
use crate::log::{error, info, warn};
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::timeout;
|
||||||
|
use crate::torrent::structs::Torrent;
|
||||||
|
use crate::torrent::torrenting_system::torrent_requests::{
|
||||||
|
handle_response_and_save_torrent, send_request_torrent_message,
|
||||||
|
};
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
use crate::io;
|
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
use crate::timeout;
|
|
||||||
|
|
||||||
pub async fn node_syncing(
|
pub async fn node_syncing(
|
||||||
stream: Arc<Mutex<TcpStream>>,
|
stream: Arc<Mutex<TcpStream>>,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
|
use crate::log::warn;
|
||||||
use crate::records::memory::response_channels::{reserve_entry, Command};
|
use crate::records::memory::response_channels::{reserve_entry, Command};
|
||||||
use crate::records::wallet_registry::{register_short_address, WalletRegistrationResult};
|
use crate::records::wallet_registry::{register_short_address, WalletRegistrationResult};
|
||||||
use crate::rpc::commands::wallet_registry_sync::WALLET_REGISTRY_RECORD_BYTES;
|
|
||||||
use crate::rpc::command_maps::RPC_WALLET_REGISTRY_SYNC;
|
use crate::rpc::command_maps::RPC_WALLET_REGISTRY_SYNC;
|
||||||
|
use crate::rpc::commands::wallet_registry_sync::WALLET_REGISTRY_RECORD_BYTES;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::log::warn;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::timeout;
|
||||||
use crate::wallets::structures::Wallet;
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Duration;
|
use crate::Duration;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
use crate::timeout;
|
|
||||||
|
|
||||||
pub async fn sync_wallet_registry(
|
pub async fn sync_wallet_registry(
|
||||||
stream: Arc<Mutex<TcpStream>>,
|
stream: Arc<Mutex<TcpStream>>,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::records::memory::response_channels::Command;
|
|
||||||
use crate::records::memory::network_mapping::NodeInfo;
|
|
||||||
use crate::records::memory::network_mapping::structs::{AddAddressParams, SignedNodeEdit};
|
use crate::records::memory::network_mapping::structs::{AddAddressParams, SignedNodeEdit};
|
||||||
|
use crate::records::memory::network_mapping::NodeInfo;
|
||||||
|
use crate::records::memory::response_channels::Command;
|
||||||
use crate::rpc::read_bytes_from_stream;
|
use crate::rpc::read_bytes_from_stream;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
|
|
@ -21,9 +21,11 @@ pub async fn add_network_node(
|
||||||
let (uid, _) =
|
let (uid, _) =
|
||||||
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
||||||
.await?;
|
.await?;
|
||||||
let address_bytes =
|
let address_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||||
read_bytes_from_stream::read_short_address_from_stream(connections_key, stream_locked.clone())
|
connections_key,
|
||||||
.await?;
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let address = Wallet::bytes_to_short_address(&address_bytes)
|
let address = Wallet::bytes_to_short_address(&address_bytes)
|
||||||
.ok_or_else(|| "error: Invalid short address bytes".to_string())?;
|
.ok_or_else(|| "error: Invalid short address bytes".to_string())?;
|
||||||
let ip =
|
let ip =
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::records::balance_sheet::get_wallet_balance::get_balance;
|
use crate::records::balance_sheet::get_wallet_balance::get_balance;
|
||||||
use crate::records::wallet_registry::resolve_canonical_registered_short_address;
|
use crate::records::wallet_registry::resolve_canonical_registered_short_address;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
pub async fn lookup_wallet_coin(db: &Db, address: String, coin: String) -> RpcResponse {
|
pub async fn lookup_wallet_coin(db: &Db, address: String, coin: String) -> RpcResponse {
|
||||||
// Return the saved confirmed balance for a specific address/asset pair.
|
// Return the saved confirmed balance for a specific address/asset pair.
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::common::nft_assets::nft_asset_parts;
|
use crate::common::nft_assets::nft_asset_parts;
|
||||||
|
use crate::log::error;
|
||||||
|
use crate::read_dir;
|
||||||
use crate::records::balance_sheet::pathing::{address_root_path, asset_name_from_relative_path};
|
use crate::records::balance_sheet::pathing::{address_root_path, asset_name_from_relative_path};
|
||||||
use crate::records::wallet_registry::resolve_canonical_registered_short_address;
|
use crate::records::wallet_registry::resolve_canonical_registered_short_address;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::log::error;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::AsyncReadExt;
|
use crate::AsyncReadExt;
|
||||||
use crate::File;
|
use crate::File;
|
||||||
use crate::Path;
|
use crate::Path;
|
||||||
use crate::read_dir;
|
|
||||||
|
|
||||||
pub async fn get_token_balances(db: &Db, address: String) -> RpcResponse {
|
pub async fn get_token_balances(db: &Db, address: String) -> RpcResponse {
|
||||||
// Walk the hierarchical balance-sheet tree for one address and emit
|
// Walk the hierarchical balance-sheet tree for one address and emit
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ pub async fn request_block(db: &Db, hash: &str) -> RpcResponse {
|
||||||
let hash_bytes = match decode(hash) {
|
let hash_bytes = match decode(hash) {
|
||||||
Ok(bytes) => bytes,
|
Ok(bytes) => bytes,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return RpcResponse::Binary(
|
return RpcResponse::Binary(format!("error: Failed to decode hash: {err}").into_bytes())
|
||||||
format!("error: Failed to decode hash: {err}").into_bytes(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -57,8 +55,6 @@ pub async fn request_block(db: &Db, hash: &str) -> RpcResponse {
|
||||||
format!("error: Failed to convert block to bytes: {err}").into_bytes(),
|
format!("error: Failed to convert block to bytes: {err}").into_bytes(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => RpcResponse::Binary(format!("error: Failed to load block: {err}").into_bytes()),
|
||||||
RpcResponse::Binary(format!("error: Failed to load block: {err}").into_bytes())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::decode;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::decode;
|
|
||||||
|
|
||||||
pub async fn lookup_by_hash(db: &Db, hash: &str) -> RpcResponse {
|
pub async fn lookup_by_hash(db: &Db, hash: &str) -> RpcResponse {
|
||||||
// Resolve the block hash through the block-hash index and then fetch
|
// Resolve the block hash through the block-hash index and then fetch
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
pub async fn block_peer(db: &Db, ip: String, signature: String, wallet_key: String) -> RpcResponse {
|
pub async fn block_peer(db: &Db, ip: String, signature: String, wallet_key: String) -> RpcResponse {
|
||||||
// Peer blocking is restricted to the local node owner, proven by a
|
// Peer blocking is restricted to the local node owner, proven by a
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::blocks::collateral::CollateralClaimTransaction;
|
use crate::blocks::collateral::CollateralClaimTransaction;
|
||||||
use crate::blocks::loan_payment::ContractPaymentTransaction;
|
use crate::blocks::loan_payment::ContractPaymentTransaction;
|
||||||
use crate::blocks::loans::LoanContractTransaction;
|
use crate::blocks::loans::LoanContractTransaction;
|
||||||
|
use crate::encode;
|
||||||
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::{DateTime, Datelike, Local, TimeZone, Utc};
|
use crate::{DateTime, Datelike, Local, TimeZone, Utc};
|
||||||
use crate::encode;
|
|
||||||
|
|
||||||
fn format_amount(value: u64) -> f64 {
|
fn format_amount(value: u64) -> f64 {
|
||||||
// Contract RPC output presents coin amounts as user-facing decimal values.
|
// Contract RPC output presents coin amounts as user-facing decimal values.
|
||||||
|
|
@ -103,8 +103,7 @@ async fn collect_contract_activity(
|
||||||
let mut collateral_claim: Option<CollateralClaimTransaction> = None;
|
let mut collateral_claim: Option<CollateralClaimTransaction> = None;
|
||||||
|
|
||||||
for entry in tree.iter() {
|
for entry in tree.iter() {
|
||||||
let (txid_bytes, _) =
|
let (txid_bytes, _) = entry.map_err(|e| format!("error: Failed to read txid tree: {e}"))?;
|
||||||
entry.map_err(|e| format!("error: Failed to read txid tree: {e}"))?;
|
|
||||||
let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, txid_bytes.to_vec()).await;
|
let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, txid_bytes.to_vec()).await;
|
||||||
if bytes.is_empty() {
|
if bytes.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -243,8 +242,7 @@ pub async fn contract_details(hash: Vec<u8>, db: &Db) -> RpcResponse {
|
||||||
pub async fn contract_details_by_address(address: String, db: &Db) -> RpcResponse {
|
pub async fn contract_details_by_address(address: String, db: &Db) -> RpcResponse {
|
||||||
// Return every saved contract where the address appears as either the
|
// Return every saved contract where the address appears as either the
|
||||||
// lender or borrower, each expanded into the same text summary view.
|
// lender or borrower, each expanded into the same text summary view.
|
||||||
let Some(address) = Wallet::normalize_to_short_address(&address)
|
let Some(address) = Wallet::normalize_to_short_address(&address) else {
|
||||||
else {
|
|
||||||
return RpcResponse::Binary(b"error: Invalid wallet address".to_vec());
|
return RpcResponse::Binary(b"error: Invalid wallet address".to_vec());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::records::memory::response_channels::Command;
|
|
||||||
use crate::records::memory::network_mapping::NodeInfo;
|
|
||||||
use crate::records::memory::network_mapping::structs::{DeleteAddressParams, SignedNodeEdit};
|
use crate::records::memory::network_mapping::structs::{DeleteAddressParams, SignedNodeEdit};
|
||||||
|
use crate::records::memory::network_mapping::NodeInfo;
|
||||||
|
use crate::records::memory::response_channels::Command;
|
||||||
use crate::rpc::read_bytes_from_stream;
|
use crate::rpc::read_bytes_from_stream;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
|
|
@ -21,9 +21,11 @@ pub async fn delete_network_node(
|
||||||
let (uid, _) =
|
let (uid, _) =
|
||||||
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
||||||
.await?;
|
.await?;
|
||||||
let address_bytes =
|
let address_bytes = read_bytes_from_stream::read_short_address_from_stream(
|
||||||
read_bytes_from_stream::read_short_address_from_stream(connections_key, stream_locked.clone())
|
connections_key,
|
||||||
.await?;
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let address = Wallet::bytes_to_short_address(&address_bytes)
|
let address = Wallet::bytes_to_short_address(&address_bytes)
|
||||||
.ok_or_else(|| "error: Invalid short address bytes".to_string())?;
|
.ok_or_else(|| "error: Invalid short address bytes".to_string())?;
|
||||||
let ip =
|
let ip =
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// The rpc commands module groups the request handlers that run after a client handshake succeeds.
|
// The rpc commands module groups the request handlers that run after a client handshake succeeds.
|
||||||
|
pub mod add_network_node;
|
||||||
pub mod address_coin_lookup;
|
pub mod address_coin_lookup;
|
||||||
pub mod address_complete_balance_sheet;
|
pub mod address_complete_balance_sheet;
|
||||||
pub mod add_network_node;
|
|
||||||
pub mod bad_rpc_call;
|
pub mod bad_rpc_call;
|
||||||
pub mod block_by_hash;
|
pub mod block_by_hash;
|
||||||
pub mod block_by_height;
|
pub mod block_by_height;
|
||||||
|
|
@ -11,10 +11,10 @@ pub mod block_headers;
|
||||||
pub mod block_height;
|
pub mod block_height;
|
||||||
pub mod block_peer_ip;
|
pub mod block_peer_ip;
|
||||||
pub mod contract;
|
pub mod contract;
|
||||||
pub mod difficulty;
|
|
||||||
pub mod delete_network_node;
|
pub mod delete_network_node;
|
||||||
pub mod latest_block;
|
pub mod difficulty;
|
||||||
pub mod largest_tx_fee;
|
pub mod largest_tx_fee;
|
||||||
|
pub mod latest_block;
|
||||||
pub mod memory_by_signature;
|
pub mod memory_by_signature;
|
||||||
pub mod network_info;
|
pub mod network_info;
|
||||||
pub mod nft_list;
|
pub mod nft_list;
|
||||||
|
|
@ -31,14 +31,14 @@ pub mod torrent;
|
||||||
pub mod torrent_by_block;
|
pub mod torrent_by_block;
|
||||||
pub mod torrent_candidates;
|
pub mod torrent_candidates;
|
||||||
pub mod transaction_by_txid;
|
pub mod transaction_by_txid;
|
||||||
|
pub mod transactions_by_address;
|
||||||
pub mod tx_count;
|
pub mod tx_count;
|
||||||
pub mod tx_count_from_mempool;
|
pub mod tx_count_from_mempool;
|
||||||
pub mod tx_submit;
|
pub mod tx_submit;
|
||||||
pub mod transactions_by_address;
|
|
||||||
pub mod unblock_peer_ip;
|
pub mod unblock_peer_ip;
|
||||||
pub mod validate_address;
|
pub mod validate_address;
|
||||||
pub mod validate_torrent;
|
|
||||||
pub mod validate_message;
|
pub mod validate_message;
|
||||||
|
pub mod validate_torrent;
|
||||||
pub mod wallet_register;
|
pub mod wallet_register;
|
||||||
pub mod wallet_registry_sync;
|
pub mod wallet_registry_sync;
|
||||||
pub mod wallet_vanity_lookup;
|
pub mod wallet_vanity_lookup;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::common::nft_assets::nft_asset_parts;
|
use crate::common::nft_assets::nft_asset_parts;
|
||||||
|
use crate::encode;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::encode;
|
|
||||||
|
|
||||||
pub async fn get_nfts(db: &Db) -> RpcResponse {
|
pub async fn get_nfts(db: &Db) -> RpcResponse {
|
||||||
// Serialize every NFT asset as origin hash, padded asset name, and
|
// Serialize every NFT asset as origin hash, padded asset name, and
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,16 @@ use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
use crate::common::binary_conversions::binary_to_string;
|
use crate::common::binary_conversions::binary_to_string;
|
||||||
use crate::common::nft_assets::{nft_asset_name, nft_asset_parts};
|
use crate::common::nft_assets::{nft_asset_name, nft_asset_parts};
|
||||||
use crate::records::balance_sheet::pathing::{balance_asset_segments, balance_root_path};
|
|
||||||
use crate::records::balance_sheet::tokens_to_lower::strip_spaces_and_lowercase;
|
|
||||||
use crate::rpc::commands::transaction_by_txid::{request_transaction_by_txid, request_transaction_by_txid_with_block};
|
|
||||||
use crate::rpc::responses::RpcResponse;
|
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::Db;
|
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::fs;
|
use crate::fs;
|
||||||
|
use crate::records::balance_sheet::pathing::{balance_asset_segments, balance_root_path};
|
||||||
|
use crate::records::balance_sheet::tokens_to_lower::strip_spaces_and_lowercase;
|
||||||
|
use crate::rpc::commands::transaction_by_txid::{
|
||||||
|
request_transaction_by_txid, request_transaction_by_txid_with_block,
|
||||||
|
};
|
||||||
|
use crate::rpc::responses::RpcResponse;
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
const ACTION_CREATE: u8 = 1;
|
const ACTION_CREATE: u8 = 1;
|
||||||
const ACTION_TRANSFER: u8 = 2;
|
const ACTION_TRANSFER: u8 = 2;
|
||||||
|
|
@ -102,7 +104,8 @@ async fn find_nft_origin(
|
||||||
let txid_tree = db.open_tree("txid").ok()?;
|
let txid_tree = db.open_tree("txid").ok()?;
|
||||||
for entry in txid_tree.iter() {
|
for entry in txid_tree.iter() {
|
||||||
let (txid_bytes, _) = entry.ok()?;
|
let (txid_bytes, _) = entry.ok()?;
|
||||||
let RpcResponse::Binary(tx_bytes) = request_transaction_by_txid(db, txid_bytes.to_vec()).await;
|
let RpcResponse::Binary(tx_bytes) =
|
||||||
|
request_transaction_by_txid(db, txid_bytes.to_vec()).await;
|
||||||
|
|
||||||
if tx_bytes.is_empty() || tx_bytes[0] != 4 {
|
if tx_bytes.is_empty() || tx_bytes[0] != 4 {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -173,7 +176,8 @@ async fn find_current_holder(asset_name: &str) -> String {
|
||||||
async fn build_history_entry(db: &Db, asset_name: &str, txid_bytes: &[u8]) -> Option<Vec<u8>> {
|
async fn build_history_entry(db: &Db, asset_name: &str, txid_bytes: &[u8]) -> Option<Vec<u8>> {
|
||||||
// Expand a raw NFT history txid into a fixed-width history entry that
|
// Expand a raw NFT history txid into a fixed-width history entry that
|
||||||
// captures the action, involved wallets, and any received asset details.
|
// captures the action, involved wallets, and any received asset details.
|
||||||
let RpcResponse::Binary(response) = request_transaction_by_txid_with_block(db, txid_bytes.to_vec()).await;
|
let RpcResponse::Binary(response) =
|
||||||
|
request_transaction_by_txid_with_block(db, txid_bytes.to_vec()).await;
|
||||||
|
|
||||||
if response.len() < 5 {
|
if response.len() < 5 {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -267,7 +271,8 @@ async fn build_history_entry(db: &Db, asset_name: &str, txid_bytes: &[u8]) -> Op
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let contract_hash = decode(&tx.unsigned_contract_payment.contract_hash).ok()?;
|
let contract_hash = decode(&tx.unsigned_contract_payment.contract_hash).ok()?;
|
||||||
let RpcResponse::Binary(contract_bytes) = request_transaction_by_txid(db, contract_hash).await;
|
let RpcResponse::Binary(contract_bytes) =
|
||||||
|
request_transaction_by_txid(db, contract_hash).await;
|
||||||
let contract = LoanContractTransaction::from_bytes(7, &contract_bytes)
|
let contract = LoanContractTransaction::from_bytes(7, &contract_bytes)
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
@ -285,7 +290,8 @@ async fn build_history_entry(db: &Db, asset_name: &str, txid_bytes: &[u8]) -> Op
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let contract_hash = decode(&tx.unsigned_collateral_claim.contract_hash).ok()?;
|
let contract_hash = decode(&tx.unsigned_collateral_claim.contract_hash).ok()?;
|
||||||
let RpcResponse::Binary(contract_bytes) = request_transaction_by_txid(db, contract_hash).await;
|
let RpcResponse::Binary(contract_bytes) =
|
||||||
|
request_transaction_by_txid(db, contract_hash).await;
|
||||||
let contract = LoanContractTransaction::from_bytes(7, &contract_bytes)
|
let contract = LoanContractTransaction::from_bytes(7, &contract_bytes)
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
@ -339,7 +345,8 @@ pub async fn lookup_nft_details(db: &Db, nft_name: String, item_number: u32) ->
|
||||||
return RpcResponse::Binary(b"error: NFT genesis not found".to_vec());
|
return RpcResponse::Binary(b"error: NFT genesis not found".to_vec());
|
||||||
};
|
};
|
||||||
|
|
||||||
let RpcResponse::Binary(genesis_response) = request_transaction_by_txid(db, genesis_bytes.clone()).await;
|
let RpcResponse::Binary(genesis_response) =
|
||||||
|
request_transaction_by_txid(db, genesis_bytes.clone()).await;
|
||||||
if genesis_response.is_empty() || genesis_response[0] != 4 {
|
if genesis_response.is_empty() || genesis_response[0] != 4 {
|
||||||
return RpcResponse::Binary(b"error: NFT genesis transaction not found".to_vec());
|
return RpcResponse::Binary(b"error: NFT genesis transaction not found".to_vec());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::common::check_genesis::genesis_checkup;
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
use crate::common::skein::skein_128_hash_bytes;
|
use crate::common::skein::skein_128_hash_bytes;
|
||||||
|
use crate::lazy_static;
|
||||||
|
use crate::log::{error, warn};
|
||||||
use crate::miner::flag::{is_reorganizing_mode, is_syncing_mode};
|
use crate::miner::flag::{is_reorganizing_mode, is_syncing_mode};
|
||||||
use crate::orphans::structs::OrphanCheckup2;
|
use crate::orphans::structs::OrphanCheckup2;
|
||||||
use crate::orphans::sync_check::sync_checkup;
|
use crate::orphans::sync_check::sync_checkup;
|
||||||
|
|
@ -8,17 +10,19 @@ use crate::records::memory::response_channels::Command;
|
||||||
use crate::rpc::read_bytes_from_stream;
|
use crate::rpc::read_bytes_from_stream;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::rpc::server::flood_protection::MAX_TORRENT_METADATA_BYTES;
|
use crate::rpc::server::flood_protection::MAX_TORRENT_METADATA_BYTES;
|
||||||
use crate::startup::remote_height::request_remote_height;
|
|
||||||
use crate::torrent::structs::Torrent;
|
|
||||||
use crate::torrent::create_metadata::broadcast_new_torrent_to_peers;
|
|
||||||
use crate::torrent::torrenting_system::torrent_requests::{setup_download_for_torrent, stage_and_verify_torrent};
|
|
||||||
use crate::torrent::torrenting_system::torrent_cache::{has_recent_torrent, remember_recent_torrent};
|
|
||||||
use crate::log::{error, warn};
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::startup::remote_height::request_remote_height;
|
||||||
|
use crate::torrent::create_metadata::broadcast_new_torrent_to_peers;
|
||||||
|
use crate::torrent::structs::Torrent;
|
||||||
|
use crate::torrent::torrenting_system::torrent_cache::{
|
||||||
|
has_recent_torrent, remember_recent_torrent,
|
||||||
|
};
|
||||||
|
use crate::torrent::torrenting_system::torrent_requests::{
|
||||||
|
setup_download_for_torrent, stage_and_verify_torrent,
|
||||||
|
};
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::AtomicBool;
|
use crate::AtomicBool;
|
||||||
use crate::AtomicOrdering;
|
use crate::AtomicOrdering;
|
||||||
use crate::lazy_static;
|
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
@ -156,8 +160,7 @@ pub async fn torrent_submission(
|
||||||
return TorrentSubmissionOutcome::Rejected(RpcResponse::Binary(msg));
|
return TorrentSubmissionOutcome::Rejected(RpcResponse::Binary(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
match stage_and_verify_torrent(height, db, torrent, wallet_key, process_now).await
|
match stage_and_verify_torrent(height, db, torrent, wallet_key, process_now).await {
|
||||||
{
|
|
||||||
Ok(stage_result) => {
|
Ok(stage_result) => {
|
||||||
let _ = remember_recent_torrent(&torrent_hash, height).await;
|
let _ = remember_recent_torrent(&torrent_hash, height).await;
|
||||||
if let Some((torrent, staged_path)) = stage_result {
|
if let Some((torrent, staged_path)) = stage_result {
|
||||||
|
|
@ -273,7 +276,8 @@ pub async fn receive_torrent(
|
||||||
) -> Result<(u32, RpcResponse), String> {
|
) -> Result<(u32, RpcResponse), String> {
|
||||||
let (uid, _) =
|
let (uid, _) =
|
||||||
read_bytes_from_stream::read_uid_from_stream(connections_key, stream.clone()).await?;
|
read_bytes_from_stream::read_uid_from_stream(connections_key, stream.clone()).await?;
|
||||||
let size = read_bytes_from_stream::read_u32_from_stream(connections_key, stream.clone()).await?;
|
let size =
|
||||||
|
read_bytes_from_stream::read_u32_from_stream(connections_key, stream.clone()).await?;
|
||||||
|
|
||||||
// The size includes the block-height field, so the remaining bytes
|
// The size includes the block-height field, so the remaining bytes
|
||||||
// are the torrent metadata that will be parsed and staged.
|
// are the torrent metadata that will be parsed and staged.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::records::memory::enums::ClientType;
|
|
||||||
use crate::records::memory::response_channels::{delete_entry, get_entry, is_retired_entry, Command};
|
|
||||||
use crate::rpc::commands::bad_rpc_call;
|
|
||||||
use crate::rpc::command_maps::MAX_RPC_REPLY_BYTES;
|
|
||||||
use crate::rpc::read_bytes_from_stream;
|
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
|
use crate::records::memory::enums::ClientType;
|
||||||
|
use crate::records::memory::response_channels::{
|
||||||
|
delete_entry, get_entry, is_retired_entry, Command,
|
||||||
|
};
|
||||||
|
use crate::rpc::command_maps::MAX_RPC_REPLY_BYTES;
|
||||||
|
use crate::rpc::commands::bad_rpc_call;
|
||||||
|
use crate::rpc::read_bytes_from_stream;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
|
|
@ -24,8 +26,8 @@ pub async fn route_reply(
|
||||||
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
read_bytes_from_stream::read_uid_from_stream(connections_key, stream_locked.clone())
|
||||||
.await?;
|
.await?;
|
||||||
let message_length =
|
let message_length =
|
||||||
read_bytes_from_stream::read_u32_from_stream(connections_key, stream_locked.clone())
|
read_bytes_from_stream::read_u32_from_stream(connections_key, stream_locked.clone()).await?
|
||||||
.await? as usize;
|
as usize;
|
||||||
if message_length > MAX_RPC_REPLY_BYTES {
|
if message_length > MAX_RPC_REPLY_BYTES {
|
||||||
bad_rpc_call::record(ip, client_type, db, wallet_key).await;
|
bad_rpc_call::record(ip, client_type, db, wallet_key).await;
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
|
@ -44,9 +46,7 @@ pub async fn route_reply(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if tx.send(buffer).await.is_err() {
|
if tx.send(buffer).await.is_err() {
|
||||||
warn!(
|
warn!("[rpc] reply receiver dropped before payload delivery: {uid:?}");
|
||||||
"[rpc] reply receiver dropped before payload delivery: {uid:?}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_entry(map, uid).await;
|
delete_entry(map, uid).await;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
use crate::common::binary_conversions::binary_to_string;
|
use crate::common::binary_conversions::binary_to_string;
|
||||||
|
use crate::fs;
|
||||||
use crate::records::balance_sheet::pathing::{balance_asset_segments, balance_root_path};
|
use crate::records::balance_sheet::pathing::{balance_asset_segments, balance_root_path};
|
||||||
use crate::records::balance_sheet::tokens_to_lower::strip_spaces_and_lowercase;
|
use crate::records::balance_sheet::tokens_to_lower::strip_spaces_and_lowercase;
|
||||||
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::{Db, Tree};
|
use crate::sled::{Db, Tree};
|
||||||
use crate::{decode, encode};
|
use crate::wallets::structures::Wallet;
|
||||||
use crate::fs;
|
|
||||||
use crate::PathBuf;
|
use crate::PathBuf;
|
||||||
|
use crate::{decode, encode};
|
||||||
|
|
||||||
fn parse_token_supply(value: &[u8]) -> Option<u64> {
|
fn parse_token_supply(value: &[u8]) -> Option<u64> {
|
||||||
// Token supply may be stored either as raw bytes or as a decimal string.
|
// Token supply may be stored either as raw bytes or as a decimal string.
|
||||||
|
|
@ -85,7 +85,8 @@ async fn find_origin_hash(
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let RpcResponse::Binary(tx_bytes) = request_transaction_by_txid(db, txid_bytes.to_vec()).await;
|
let RpcResponse::Binary(tx_bytes) =
|
||||||
|
request_transaction_by_txid(db, txid_bytes.to_vec()).await;
|
||||||
|
|
||||||
// The fallback only cares about create-token transactions because
|
// The fallback only cares about create-token transactions because
|
||||||
// those define the origin hash for a token.
|
// those define the origin hash for a token.
|
||||||
|
|
@ -221,7 +222,8 @@ pub async fn lookup_token_details(db: &Db, token_name: String) -> RpcResponse {
|
||||||
None => return RpcResponse::Binary(b"error: Token origin not found".to_vec()),
|
None => return RpcResponse::Binary(b"error: Token origin not found".to_vec()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let RpcResponse::Binary(tx_bytes) = request_transaction_by_txid(db, decode(&origin_hash).unwrap_or_default()).await;
|
let RpcResponse::Binary(tx_bytes) =
|
||||||
|
request_transaction_by_txid(db, decode(&origin_hash).unwrap_or_default()).await;
|
||||||
|
|
||||||
if tx_bytes.is_empty() {
|
if tx_bytes.is_empty() {
|
||||||
return RpcResponse::Binary(b"error: Token contract transaction not found".to_vec());
|
return RpcResponse::Binary(b"error: Token contract transaction not found".to_vec());
|
||||||
|
|
|
||||||
|
|
@ -1,211 +1,184 @@
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::common::skein::skein_128_hash_bytes;
|
use crate::common::skein::skein_128_hash_bytes;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::torrent::structs::Torrent;
|
use crate::sled::Db;
|
||||||
use crate::sled::Db;
|
use crate::torrent::structs::Torrent;
|
||||||
use crate::{AsyncReadExt, AsyncSeekExt, SeekFrom};
|
use crate::File;
|
||||||
use crate::{decode, encode};
|
use crate::PathBuf;
|
||||||
use crate::File;
|
use crate::{decode, encode};
|
||||||
use crate::Path;
|
use crate::{AsyncReadExt, AsyncSeekExt, SeekFrom};
|
||||||
use crate::PathBuf;
|
|
||||||
|
pub async fn request_block_piece(
|
||||||
fn remove_block_pieces_from_db(db: &Db, block_number: u32, info_hash: &str) {
|
db: &Db,
|
||||||
// When the canonical torrent exists, temporary cached pieces for that
|
block_number: u32,
|
||||||
// block are no longer needed and can be dropped from the piece cache.
|
requested_piece: u8,
|
||||||
let Ok(tree) = db.open_tree("block_pieces") else {
|
requested_info_hash: u128,
|
||||||
return;
|
) -> RpcResponse {
|
||||||
};
|
// Serve a block piece either from the temporary cached-piece tree or
|
||||||
let prefix = format!("{block_number}-{info_hash}-");
|
// by slicing it directly from the canonical block file using torrent metadata.
|
||||||
let iter = tree.range(prefix.as_bytes()..);
|
let tree = match db.open_tree("block_pieces") {
|
||||||
|
Ok(tree) => tree,
|
||||||
for (key, _value) in iter.flatten() {
|
Err(err) => {
|
||||||
if !key.starts_with(prefix.as_bytes()) {
|
let msg = format!("error: Failed to open block_pieces tree: {err}")
|
||||||
break;
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
}
|
}
|
||||||
let _ = tree.remove(key);
|
};
|
||||||
|
let requested_info_hash_hex = encode(requested_info_hash.to_le_bytes());
|
||||||
|
let key = format!("{block_number}-{requested_info_hash_hex}-{requested_piece}");
|
||||||
|
|
||||||
|
let (
|
||||||
|
_network_name,
|
||||||
|
_padded_base_coin,
|
||||||
|
block_ext,
|
||||||
|
torrent_path,
|
||||||
|
_wallet_path,
|
||||||
|
block_path,
|
||||||
|
_db_path,
|
||||||
|
_balance_path,
|
||||||
|
_log_path,
|
||||||
|
) = block_extension_and_paths();
|
||||||
|
let block_filename = PathBuf::from(&block_path)
|
||||||
|
.join(format!("{block_number}.{block_ext}"))
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
let torrent_filename = PathBuf::from(&torrent_path)
|
||||||
|
.join(format!("{block_number}.torrent"))
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
|
||||||
|
if let Some(piece_data) = tree.get(&key).ok().and_then(|result| result) {
|
||||||
|
// Cached pieces are used for in-progress downloads before the
|
||||||
|
// canonical torrent file is available locally. During an orphan
|
||||||
|
// fight this must be checked before the canonical file because a
|
||||||
|
// node can have cached pieces for a competing candidate at the
|
||||||
|
// same height.
|
||||||
|
RpcResponse::Binary(piece_data.to_vec())
|
||||||
|
} else if let Ok(mut torrent_file) = File::open(&torrent_filename).await {
|
||||||
|
let mut torrent_contents = Vec::new();
|
||||||
|
if let Err(err) = torrent_file.read_to_end(&mut torrent_contents).await {
|
||||||
|
let msg = format!("error: Failed to read torrent file: {err}")
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
let torrent = match Torrent::from_bytes(&torrent_contents).await {
|
||||||
|
Ok(torrent) => torrent,
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let torrent_info_hash_bytes = match decode(&torrent.info.info_hash) {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: Invalid torrent info hash: {err}")
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let torrent_info_hash = match <[u8; 16]>::try_from(torrent_info_hash_bytes.as_slice()) {
|
||||||
|
Ok(bytes) => u128::from_le_bytes(bytes),
|
||||||
|
Err(_) => {
|
||||||
|
let msg = "error: Invalid torrent info hash length"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if torrent_info_hash != requested_info_hash {
|
||||||
|
// A peer can ask for a specific candidate; reject the request
|
||||||
|
// if our canonical torrent is for a different info hash.
|
||||||
|
let msg = "error: Requested candidate not found"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
let pieces = torrent.info.pieces;
|
||||||
|
|
||||||
|
// Use the torrent piece map to locate the expected hash, read the
|
||||||
|
// matching byte range from the block file, and verify the piece hash.
|
||||||
|
if let Some(piece_object) = pieces
|
||||||
|
.iter()
|
||||||
|
.find(|piece| piece.contains_key(&requested_piece))
|
||||||
|
{
|
||||||
|
if let Some(expected_hash) = piece_object.get(&requested_piece) {
|
||||||
|
let piece_length = torrent.info.piece_length as u64;
|
||||||
|
if let Ok(mut block_file) = File::open(&block_filename).await {
|
||||||
|
let file_size = match block_file.metadata().await {
|
||||||
|
Ok(meta) => meta.len(),
|
||||||
|
Err(_) => {
|
||||||
|
let msg = "error: Error reading block file metadata"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_byte = (requested_piece as u64 - 1) * piece_length;
|
||||||
|
if start_byte >= file_size {
|
||||||
|
let msg = "error: Requested piece is out of bounds"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last piece may be shorter than the normal
|
||||||
|
// torrent piece length, so cap the read at EOF.
|
||||||
|
let piece_size = std::cmp::min(piece_length, file_size - start_byte) as usize;
|
||||||
|
let mut piece_data = vec![0u8; piece_size];
|
||||||
|
|
||||||
|
if block_file.seek(SeekFrom::Start(start_byte)).await.is_err() {
|
||||||
|
let msg = "error: Error seeking block file"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if block_file.read_exact(&mut piece_data).await.is_err() {
|
||||||
|
let msg = "error: Error reading block file contents"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let calculated_hash = skein_128_hash_bytes(&piece_data);
|
||||||
|
// Never serve a block slice that does not match the
|
||||||
|
// piece hash advertised in the torrent metadata.
|
||||||
|
if &calculated_hash == expected_hash {
|
||||||
|
return RpcResponse::Binary(piece_data);
|
||||||
|
} else {
|
||||||
|
let msg = "error: Hash mismatch".to_string().as_bytes().to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msg = "error: Block not found".to_string().as_bytes().to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msg = "error: Expected hash not found"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msg = "error: Requested piece is out of bounds"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msg = "error: piece not found".to_string().as_bytes().to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_block_piece(
|
|
||||||
db: &Db,
|
|
||||||
block_number: u32,
|
|
||||||
requested_piece: u8,
|
|
||||||
requested_info_hash: u128,
|
|
||||||
) -> RpcResponse {
|
|
||||||
// Serve a block piece either from the temporary cached-piece tree or
|
|
||||||
// by slicing it directly from the canonical block file using torrent metadata.
|
|
||||||
let tree = match db.open_tree("block_pieces") {
|
|
||||||
Ok(tree) => tree,
|
|
||||||
Err(err) => {
|
|
||||||
let msg = format!("error: Failed to open block_pieces tree: {err}")
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let requested_info_hash_hex = encode(requested_info_hash.to_le_bytes());
|
|
||||||
let key = format!(
|
|
||||||
"{block_number}-{requested_info_hash_hex}-{requested_piece}"
|
|
||||||
);
|
|
||||||
|
|
||||||
let (
|
|
||||||
_network_name,
|
|
||||||
_padded_base_coin,
|
|
||||||
block_ext,
|
|
||||||
torrent_path,
|
|
||||||
_wallet_path,
|
|
||||||
block_path,
|
|
||||||
_db_path,
|
|
||||||
_balance_path,
|
|
||||||
_log_path,
|
|
||||||
) = block_extension_and_paths();
|
|
||||||
let block_filename = PathBuf::from(&block_path)
|
|
||||||
.join(format!("{block_number}.{block_ext}"))
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned();
|
|
||||||
let torrent_filename = PathBuf::from(&torrent_path)
|
|
||||||
.join(format!("{block_number}.torrent"))
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned();
|
|
||||||
|
|
||||||
let file_exists = Path::new(&torrent_filename).exists();
|
|
||||||
let prefix = format!("{block_number}-{requested_info_hash_hex}-");
|
|
||||||
let pieces_exist = tree.range(prefix.as_bytes()..).peekable().peek().is_some();
|
|
||||||
|
|
||||||
// Once the canonical torrent exists, cached block pieces for the same
|
|
||||||
// height can be purged so the canonical file becomes the source of truth.
|
|
||||||
if file_exists && pieces_exist {
|
|
||||||
remove_block_pieces_from_db(db, block_number, &requested_info_hash_hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(piece_data) = tree.get(&key).ok().and_then(|result| result) {
|
|
||||||
// Cached pieces are used for in-progress downloads before the
|
|
||||||
// canonical torrent file is available locally.
|
|
||||||
RpcResponse::Binary(piece_data.to_vec())
|
|
||||||
} else if let Ok(mut torrent_file) = File::open(&torrent_filename).await {
|
|
||||||
let mut torrent_contents = Vec::new();
|
|
||||||
if let Err(err) = torrent_file.read_to_end(&mut torrent_contents).await {
|
|
||||||
let msg = format!("error: Failed to read torrent file: {err}")
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
let torrent = match Torrent::from_bytes(&torrent_contents).await {
|
|
||||||
Ok(torrent) => torrent,
|
|
||||||
Err(err) => {
|
|
||||||
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let torrent_info_hash_bytes = match decode(&torrent.info.info_hash) {
|
|
||||||
Ok(bytes) => bytes,
|
|
||||||
Err(err) => {
|
|
||||||
let msg = format!("error: Invalid torrent info hash: {err}")
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let torrent_info_hash = match <[u8; 16]>::try_from(torrent_info_hash_bytes.as_slice()) {
|
|
||||||
Ok(bytes) => u128::from_le_bytes(bytes),
|
|
||||||
Err(_) => {
|
|
||||||
let msg = "error: Invalid torrent info hash length"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if torrent_info_hash != requested_info_hash {
|
|
||||||
// A peer can ask for a specific candidate; reject the request
|
|
||||||
// if our canonical torrent is for a different info hash.
|
|
||||||
let msg = "error: Requested candidate not found"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
let pieces = torrent.info.pieces;
|
|
||||||
|
|
||||||
// Use the torrent piece map to locate the expected hash, read the
|
|
||||||
// matching byte range from the block file, and verify the piece hash.
|
|
||||||
if let Some(piece_object) = pieces
|
|
||||||
.iter()
|
|
||||||
.find(|piece| piece.contains_key(&requested_piece))
|
|
||||||
{
|
|
||||||
if let Some(expected_hash) = piece_object.get(&requested_piece) {
|
|
||||||
let piece_length = torrent.info.piece_length as u64;
|
|
||||||
if let Ok(mut block_file) = File::open(&block_filename).await {
|
|
||||||
let file_size = match block_file.metadata().await {
|
|
||||||
Ok(meta) => meta.len(),
|
|
||||||
Err(_) => {
|
|
||||||
let msg = "error: Error reading block file metadata"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_byte = (requested_piece as u64 - 1) * piece_length;
|
|
||||||
if start_byte >= file_size {
|
|
||||||
let msg = "error: Requested piece is out of bounds"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The last piece may be shorter than the normal
|
|
||||||
// torrent piece length, so cap the read at EOF.
|
|
||||||
let piece_size = std::cmp::min(piece_length, file_size - start_byte) as usize;
|
|
||||||
let mut piece_data = vec![0u8; piece_size];
|
|
||||||
|
|
||||||
if block_file.seek(SeekFrom::Start(start_byte)).await.is_err() {
|
|
||||||
let msg = "error: Error seeking block file"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if block_file.read_exact(&mut piece_data).await.is_err() {
|
|
||||||
let msg = "error: Error reading block file contents"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let calculated_hash = skein_128_hash_bytes(&piece_data);
|
|
||||||
// Never serve a block slice that does not match the
|
|
||||||
// piece hash advertised in the torrent metadata.
|
|
||||||
if &calculated_hash == expected_hash {
|
|
||||||
return RpcResponse::Binary(piece_data);
|
|
||||||
} else {
|
|
||||||
let msg = "error: Hash mismatch".to_string().as_bytes().to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = "error: Block not found".to_string().as_bytes().to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = "error: Expected hash not found"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = "error: Requested piece is out of bounds"
|
|
||||||
.to_string()
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = "error: piece not found".to_string().as_bytes().to_vec();
|
|
||||||
return RpcResponse::Binary(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,41 @@
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::read;
|
||||||
use crate::Path;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::read;
|
use crate::Path;
|
||||||
|
|
||||||
pub async fn request_block_torrent(height: &u32) -> RpcResponse {
|
pub async fn request_block_torrent(height: &u32) -> RpcResponse {
|
||||||
// Torrent files live alongside blocks under a predictable
|
// Torrent files live alongside blocks under a predictable
|
||||||
// `<height>.torrent` naming convention.
|
// `<height>.torrent` naming convention.
|
||||||
let filename = format!("{height}.torrent");
|
let filename = format!("{height}.torrent");
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
_block_ext,
|
_block_ext,
|
||||||
torrent_path,
|
torrent_path,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
_block_path,
|
_block_path,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
let file_path = Path::new(&torrent_path).join(&filename);
|
let file_path = Path::new(&torrent_path).join(&filename);
|
||||||
|
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
let msg = format!("error: Block {height} not found")
|
let msg = format!("error: Block {height} not found")
|
||||||
.to_string()
|
.to_string()
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
return RpcResponse::Binary(msg);
|
return RpcResponse::Binary(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
match read(&file_path).await {
|
match read(&file_path).await {
|
||||||
Ok(binary_data) => RpcResponse::Binary(binary_data),
|
Ok(binary_data) => RpcResponse::Binary(binary_data),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let msg = "error: Error reading torrent file"
|
let msg = "error: Error reading torrent file"
|
||||||
.to_string()
|
.to_string()
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
RpcResponse::Binary(msg)
|
RpcResponse::Binary(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,153 +1,153 @@
|
||||||
use crate::blocks::block::VRF_BLOCK_BYTES;
|
use crate::blocks::block::VRF_BLOCK_BYTES;
|
||||||
use crate::common::binary_conversions::binary_to_string;
|
use crate::common::binary_conversions::binary_to_string;
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
use crate::rpc::command_maps;
|
use crate::io;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::command_maps;
|
||||||
use crate::sled::Db;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::{AsyncReadExt, AsyncSeekExt, SeekFrom};
|
use crate::sled::Db;
|
||||||
use crate::File;
|
use crate::File;
|
||||||
use crate::io;
|
use crate::PathBuf;
|
||||||
use crate::PathBuf;
|
use crate::{AsyncReadExt, AsyncSeekExt, SeekFrom};
|
||||||
|
|
||||||
const HEADER_SIZE: u64 = VRF_BLOCK_BYTES as u64;
|
const HEADER_SIZE: u64 = VRF_BLOCK_BYTES as u64;
|
||||||
|
|
||||||
pub async fn request_transaction_by_txid(db: &Db, txid: Vec<u8>) -> RpcResponse {
|
pub async fn request_transaction_by_txid(db: &Db, txid: Vec<u8>) -> RpcResponse {
|
||||||
// Resolve the saved transaction bytes directly from the txid lookup
|
// Resolve the saved transaction bytes directly from the txid lookup
|
||||||
// tree and the referenced block file.
|
// tree and the referenced block file.
|
||||||
match lookup_transaction_location(db, txid).await {
|
match lookup_transaction_location(db, txid).await {
|
||||||
Ok((_block, _position, block_filename)) => {
|
Ok((_block, _position, block_filename)) => {
|
||||||
let bytes = calculate_offset(&block_filename, _position).await;
|
let bytes = calculate_offset(&block_filename, _position).await;
|
||||||
match bytes {
|
match bytes {
|
||||||
Some(vec) => RpcResponse::Binary(vec),
|
Some(vec) => RpcResponse::Binary(vec),
|
||||||
None => {
|
None => {
|
||||||
let msg = "error: Error parsing block".to_string().as_bytes().to_vec();
|
let msg = "error: Error parsing block".to_string().as_bytes().to_vec();
|
||||||
RpcResponse::Binary(msg)
|
RpcResponse::Binary(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(msg) => RpcResponse::Binary(msg.into_bytes()),
|
Err(msg) => RpcResponse::Binary(msg.into_bytes()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_transaction_by_txid_with_block(db: &Db, txid: Vec<u8>) -> RpcResponse {
|
pub async fn request_transaction_by_txid_with_block(db: &Db, txid: Vec<u8>) -> RpcResponse {
|
||||||
// Some callers need the block number alongside the raw transaction
|
// Some callers need the block number alongside the raw transaction
|
||||||
// bytes, so this variant prefixes the payload with the block height.
|
// bytes, so this variant prefixes the payload with the block height.
|
||||||
match lookup_transaction_location(db, txid).await {
|
match lookup_transaction_location(db, txid).await {
|
||||||
Ok((block, position, block_filename)) => {
|
Ok((block, position, block_filename)) => {
|
||||||
let bytes = calculate_offset(&block_filename, position).await;
|
let bytes = calculate_offset(&block_filename, position).await;
|
||||||
match bytes {
|
match bytes {
|
||||||
Some(vec) => {
|
Some(vec) => {
|
||||||
let mut response = Vec::with_capacity(4 + vec.len());
|
let mut response = Vec::with_capacity(4 + vec.len());
|
||||||
response.extend_from_slice(&(block as u32).to_le_bytes());
|
response.extend_from_slice(&(block as u32).to_le_bytes());
|
||||||
response.extend_from_slice(&vec);
|
response.extend_from_slice(&vec);
|
||||||
RpcResponse::Binary(response)
|
RpcResponse::Binary(response)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let msg = "error: Error parsing block".to_string().as_bytes().to_vec();
|
let msg = "error: Error parsing block".to_string().as_bytes().to_vec();
|
||||||
RpcResponse::Binary(msg)
|
RpcResponse::Binary(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(msg) => RpcResponse::Binary(msg.into_bytes()),
|
Err(msg) => RpcResponse::Binary(msg.into_bytes()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn lookup_transaction_location(db: &Db, txid: Vec<u8>) -> Result<(u64, u32, String), String> {
|
async fn lookup_transaction_location(db: &Db, txid: Vec<u8>) -> Result<(u64, u32, String), String> {
|
||||||
// The txid tree stores `block:index`, which is enough to locate the
|
// The txid tree stores `block:index`, which is enough to locate the
|
||||||
// transaction inside the saved block file on disk.
|
// transaction inside the saved block file on disk.
|
||||||
let tree = db.open_tree("txid").unwrap();
|
let tree = db.open_tree("txid").unwrap();
|
||||||
let value = match tree.get(txid) {
|
let value = match tree.get(txid) {
|
||||||
Ok(Some(result)) => result.to_vec(),
|
Ok(Some(result)) => result.to_vec(),
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
return Err("error: Key not found".to_string());
|
return Err("error: Key not found".to_string());
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err("error: Errpr retrieving value".to_string());
|
return Err("error: Errpr retrieving value".to_string());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let value_str = binary_to_string(value.to_vec());
|
let value_str = binary_to_string(value.to_vec());
|
||||||
|
|
||||||
// Stored txid locations are saved as ASCII `height:index`.
|
// Stored txid locations are saved as ASCII `height:index`.
|
||||||
let parts: Vec<&str> = value_str.split(':').collect();
|
let parts: Vec<&str> = value_str.split(':').collect();
|
||||||
|
|
||||||
let block: u64 = parts[0].parse().unwrap_or_default();
|
let block: u64 = parts[0].parse().unwrap_or_default();
|
||||||
|
|
||||||
let position: u32 = parts[1].parse().unwrap_or_default();
|
let position: u32 = parts[1].parse().unwrap_or_default();
|
||||||
|
|
||||||
let (
|
let (
|
||||||
_network_name,
|
_network_name,
|
||||||
_padded_base_coin,
|
_padded_base_coin,
|
||||||
block_ext,
|
block_ext,
|
||||||
_torrent_path,
|
_torrent_path,
|
||||||
_wallet_path,
|
_wallet_path,
|
||||||
block_path,
|
block_path,
|
||||||
_db_path,
|
_db_path,
|
||||||
_balance_path,
|
_balance_path,
|
||||||
_log_path,
|
_log_path,
|
||||||
) = block_extension_and_paths();
|
) = block_extension_and_paths();
|
||||||
|
|
||||||
let block_filename = PathBuf::from(block_path)
|
let block_filename = PathBuf::from(block_path)
|
||||||
.join(format!("{block}.{block_ext}"))
|
.join(format!("{block}.{block_ext}"))
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
Ok((block, position, block_filename))
|
Ok((block, position, block_filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_transaction_type(file_path: &str, position: u64) -> Option<u8> {
|
async fn read_transaction_type(file_path: &str, position: u64) -> Option<u8> {
|
||||||
// Transaction offsets are located by repeatedly reading the type byte
|
// Transaction offsets are located by repeatedly reading the type byte
|
||||||
// so the fixed encoded size for each saved transaction can be applied.
|
// so the fixed encoded size for each saved transaction can be applied.
|
||||||
let mut file = match File::open(file_path).await {
|
let mut file = match File::open(file_path).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(position)).await.ok()?;
|
file.seek(SeekFrom::Start(position)).await.ok()?;
|
||||||
|
|
||||||
let mut transaction_type_byte = [0u8; 1];
|
let mut transaction_type_byte = [0u8; 1];
|
||||||
file.read_exact(&mut transaction_type_byte).await.ok()?;
|
file.read_exact(&mut transaction_type_byte).await.ok()?;
|
||||||
|
|
||||||
Some(transaction_type_byte[0])
|
Some(transaction_type_byte[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn calculate_offset(file_path: &str, position: u32) -> Option<Vec<u8>> {
|
async fn calculate_offset(file_path: &str, position: u32) -> Option<Vec<u8>> {
|
||||||
// Walk forward through the serialized block body until the requested
|
// Walk forward through the serialized block body until the requested
|
||||||
// transaction index is reached, then read exactly that transaction.
|
// transaction index is reached, then read exactly that transaction.
|
||||||
let mut total_bytes_to_skip: u64 = HEADER_SIZE;
|
let mut total_bytes_to_skip: u64 = HEADER_SIZE;
|
||||||
|
|
||||||
let mut current_position: u32 = 1;
|
let mut current_position: u32 = 1;
|
||||||
|
|
||||||
let mut transaction_type = read_transaction_type(file_path, HEADER_SIZE).await?;
|
let mut transaction_type = read_transaction_type(file_path, HEADER_SIZE).await?;
|
||||||
|
|
||||||
while current_position < position {
|
while current_position < position {
|
||||||
// Transaction bodies are fixed-size by type, so the type byte at
|
// Transaction bodies are fixed-size by type, so the type byte at
|
||||||
// each offset tells us how far to jump to reach the next record.
|
// each offset tells us how far to jump to reach the next record.
|
||||||
let size = command_maps::get_bytes(transaction_type) as u64;
|
let size = command_maps::get_bytes(transaction_type) as u64;
|
||||||
|
|
||||||
total_bytes_to_skip += size;
|
total_bytes_to_skip += size;
|
||||||
|
|
||||||
transaction_type = read_transaction_type(file_path, total_bytes_to_skip).await?;
|
transaction_type = read_transaction_type(file_path, total_bytes_to_skip).await?;
|
||||||
|
|
||||||
current_position += 1;
|
current_position += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = command_maps::get_bytes(transaction_type) as u64;
|
let size = command_maps::get_bytes(transaction_type) as u64;
|
||||||
|
|
||||||
let mut file = match File::open(file_path).await {
|
let mut file = match File::open(file_path).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
file.seek(io::SeekFrom::Start(total_bytes_to_skip))
|
file.seek(io::SeekFrom::Start(total_bytes_to_skip))
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
let mut transaction_bytes = vec![0u8; size as usize];
|
let mut transaction_bytes = vec![0u8; size as usize];
|
||||||
file.read_exact(&mut transaction_bytes).await.ok()?;
|
file.read_exact(&mut transaction_bytes).await.ok()?;
|
||||||
|
|
||||||
// Returned bytes include the transaction type byte at the front so
|
// Returned bytes include the transaction type byte at the front so
|
||||||
// callers can parse the payload without extra lookup state.
|
// callers can parse the payload without extra lookup state.
|
||||||
Some(transaction_bytes)
|
Some(transaction_bytes)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ use crate::records::memory::enums::ClientType;
|
||||||
use crate::records::memory::response_channels::generate_uid;
|
use crate::records::memory::response_channels::generate_uid;
|
||||||
use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION;
|
use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::torrent::torrenting_system::get_nodes::get_nodes_from_memory;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::torrent::torrenting_system::get_nodes::get_nodes_from_memory;
|
||||||
|
|
||||||
async fn broadcast_tx(tx_bytes: Vec<u8>) {
|
async fn broadcast_tx(tx_bytes: Vec<u8>) {
|
||||||
// Broadcast newly accepted mempool transactions only to miner peers,
|
// Broadcast newly accepted mempool transactions only to miner peers,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::wallets::structures::Wallet;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
pub async fn unblock_peer(
|
pub async fn unblock_peer(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::rpc::read_bytes_from_stream;
|
use crate::rpc::read_bytes_from_stream;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::rpc::server::flood_protection::MAX_TORRENT_METADATA_BYTES;
|
use crate::rpc::server::flood_protection::MAX_TORRENT_METADATA_BYTES;
|
||||||
use crate::torrent::structs::Torrent;
|
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
use crate::torrent::structs::Torrent;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
use crate::TcpStream;
|
use crate::TcpStream;
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue