added storage transactions
This commit is contained in:
parent
bb6862658f
commit
5c68786eef
|
|
@ -0,0 +1,537 @@
|
||||||
|
use blockchain::blocks::storage_key::{StorageKey, UnsignedStorageKey};
|
||||||
|
use blockchain::blocks::storage_values::{
|
||||||
|
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
|
||||||
|
UnsignedStorageBool, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16,
|
||||||
|
UnsignedStorageU32, UnsignedStorageU64, UnsignedStorageU8, STORAGE_KEY_HASH_BYTES,
|
||||||
|
STORAGE_STRING_VALUE_BYTES, STORAGE_VALUE_KEY_BYTES,
|
||||||
|
};
|
||||||
|
use blockchain::common::cli_prompts::{
|
||||||
|
prompt_hidden_nonempty, prompt_visible, prompt_visible_with_default, prompt_wallet_path,
|
||||||
|
};
|
||||||
|
use blockchain::common::types::{
|
||||||
|
STORAGE_BOOL_TYPE, STORAGE_KEY_FEE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
|
||||||
|
STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE, STORAGE_VALUE_FEE,
|
||||||
|
};
|
||||||
|
use blockchain::wallets::structures::Wallet;
|
||||||
|
use blockchain::{create_dir_all, decode, env, json, AsyncWriteExt, File, Utc};
|
||||||
|
|
||||||
|
const ZERO_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
|
||||||
|
fn usage() {
|
||||||
|
println!("Usage:");
|
||||||
|
println!(" ./data_storage_tx create_storage [txfee]");
|
||||||
|
println!(" ./data_storage_tx bool <storage_key_hash> <field_key> <true|false> [txfee]");
|
||||||
|
println!(" ./data_storage_tx u8|8bit <storage_key_hash> <field_key> <value> [txfee]");
|
||||||
|
println!(" ./data_storage_tx u16|16bit <storage_key_hash> <field_key> <value> [txfee]");
|
||||||
|
println!(" ./data_storage_tx u32|32bit <storage_key_hash> <field_key> <value> [txfee]");
|
||||||
|
println!(" ./data_storage_tx u64|64bit <storage_key_hash> <field_key> <value> [txfee]");
|
||||||
|
println!(" ./data_storage_tx u128|128bit <storage_key_hash> <field_key> <value> [txfee]");
|
||||||
|
println!(
|
||||||
|
" ./data_storage_tx string <storage_key_hash> <field_key> <value> [previous_hash] [txfee]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_fee(value: u64) -> f64 {
|
||||||
|
value as f64 / 100_000_000.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_fee(value: &str) -> Option<u64> {
|
||||||
|
let parsed = value.trim().parse::<f64>().ok()?;
|
||||||
|
if parsed.is_sign_negative() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((parsed * 100_000_000.0).round() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prompt_fee(default_fee: u64) -> u64 {
|
||||||
|
let default_display = format!("{:.8}", display_fee(default_fee));
|
||||||
|
loop {
|
||||||
|
let value =
|
||||||
|
prompt_visible_with_default("Please enter the transaction fee", &default_display).await;
|
||||||
|
if let Some(fee) = parse_fee(&value) {
|
||||||
|
return fee;
|
||||||
|
}
|
||||||
|
println!("Please enter a valid fee.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixed_field_key(input: &str) -> Option<String> {
|
||||||
|
let trimmed = input.trim();
|
||||||
|
if trimmed.is_empty() || trimmed.as_bytes().len() > STORAGE_VALUE_KEY_BYTES {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(format!("{trimmed:<STORAGE_VALUE_KEY_BYTES$}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixed_string_value(input: &str) -> Option<String> {
|
||||||
|
if input.as_bytes().len() > STORAGE_STRING_VALUE_BYTES {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(format!("{input:<STORAGE_STRING_VALUE_BYTES$}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_storage_key_hash(value: &str) -> bool {
|
||||||
|
matches!(decode(value.trim()), Ok(bytes) if bytes.len() == STORAGE_KEY_HASH_BYTES)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_previous_hash(value: &str) -> bool {
|
||||||
|
valid_storage_key_hash(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_storage_type(input: &str) -> Option<&'static str> {
|
||||||
|
match input.trim().to_lowercase().as_str() {
|
||||||
|
"create_storage" | "storage" | "storage_key" | "key" => Some("create_storage"),
|
||||||
|
"bool" | "boolean" => Some("bool"),
|
||||||
|
"u8" | "8bit" | "8" => Some("u8"),
|
||||||
|
"u16" | "16bit" | "16" => Some("u16"),
|
||||||
|
"u32" | "32bit" | "32" => Some("u32"),
|
||||||
|
"u64" | "64bit" | "64" => Some("u64"),
|
||||||
|
"u128" | "128bit" | "128" => Some("u128"),
|
||||||
|
"string" | "str" | "text" => Some("string"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prompt_storage_hash(args: &[String], index: usize) -> Option<String> {
|
||||||
|
let value = if args.len() > index {
|
||||||
|
args[index].clone()
|
||||||
|
} else {
|
||||||
|
prompt_visible("Please enter the storage key hash: ").await
|
||||||
|
};
|
||||||
|
if !valid_storage_key_hash(&value) {
|
||||||
|
println!("Storage key hash must be a 32-byte / 64-character hex string.");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(value.trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prompt_field_key(args: &[String], index: usize) -> Option<String> {
|
||||||
|
let value = if args.len() > index {
|
||||||
|
args[index].clone()
|
||||||
|
} else {
|
||||||
|
prompt_visible("Please enter the storage field key: ").await
|
||||||
|
};
|
||||||
|
let Some(key) = fixed_field_key(&value) else {
|
||||||
|
println!("Storage field key must be 1 to 50 bytes.");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_wallet() -> Option<Wallet> {
|
||||||
|
let wallet_path = prompt_wallet_path().await;
|
||||||
|
let decryption_key = prompt_hidden_nonempty(
|
||||||
|
"What is your wallet decryption key? ",
|
||||||
|
"Wallet key cannot be empty. Please try again.",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match Wallet::try_obtain_wallet(decryption_key, Some(&wallet_path)).await {
|
||||||
|
Ok(wallet) => Some(wallet),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Wallet decryption failed: {err}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_transaction(hash: &str, output: &serde_json::Value) -> Result<(), String> {
|
||||||
|
let output_str =
|
||||||
|
serde_json::to_string_pretty(output).map_err(|err| format!("JSON failed: {err}"))?;
|
||||||
|
let dir_path = "./transactions";
|
||||||
|
|
||||||
|
create_dir_all(dir_path)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to create directory: {err}"))?;
|
||||||
|
let file_path = format!("{dir_path}/{hash}.json");
|
||||||
|
let mut file = File::create(&file_path)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to create file: {err}"))?;
|
||||||
|
file.write_all(output_str.as_bytes())
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to write file: {err}"))?;
|
||||||
|
|
||||||
|
println!("{output_str}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_storage_key(args: &[String], timestamp: u32, wallet: &Wallet) {
|
||||||
|
let txfee = if args.len() > 2 {
|
||||||
|
match parse_fee(&args[2]) {
|
||||||
|
Some(fee) => fee,
|
||||||
|
None => {
|
||||||
|
println!("Please enter a valid fee.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prompt_fee(STORAGE_KEY_FEE).await
|
||||||
|
};
|
||||||
|
|
||||||
|
let address = wallet.saved.short_address.trim();
|
||||||
|
if !Wallet::short_address_validation(address) {
|
||||||
|
println!("storage key creator wallet invalid: {address}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unsigned = UnsignedStorageKey::new(STORAGE_KEY_TYPE, timestamp, address, txfee).await;
|
||||||
|
let transaction = match StorageKey::new(unsigned, &wallet.saved.private_key).await {
|
||||||
|
Ok(transaction) => transaction,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to sign storage key transaction: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hash = transaction.unsigned_storagekey.hash().await;
|
||||||
|
let output = json!({
|
||||||
|
"txtype": STORAGE_KEY_TYPE,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"address": address,
|
||||||
|
"txfee": txfee,
|
||||||
|
"hash": hash,
|
||||||
|
"signature": transaction.signature,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(err) = write_transaction(&hash, &output).await {
|
||||||
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! create_numeric_storage {
|
||||||
|
(
|
||||||
|
$args:expr,
|
||||||
|
$timestamp:expr,
|
||||||
|
$wallet:expr,
|
||||||
|
$storagekey:expr,
|
||||||
|
$key:expr,
|
||||||
|
$value_ty:ty,
|
||||||
|
$unsigned:ident,
|
||||||
|
$signed:ident,
|
||||||
|
$unsigned_field:ident,
|
||||||
|
$txtype:expr,
|
||||||
|
$label:expr
|
||||||
|
) => {{
|
||||||
|
let value_string = if $args.len() > 4 {
|
||||||
|
$args[4].clone()
|
||||||
|
} else {
|
||||||
|
prompt_visible(&format!("Please enter the {} value: ", $label)).await
|
||||||
|
};
|
||||||
|
let value: $value_ty = match value_string.trim().parse() {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_) => {
|
||||||
|
println!("Please enter a valid {} value.", $label);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let txfee = if $args.len() > 5 {
|
||||||
|
match parse_fee(&$args[5]) {
|
||||||
|
Some(fee) => fee,
|
||||||
|
None => {
|
||||||
|
println!("Please enter a valid fee.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prompt_fee(STORAGE_VALUE_FEE).await
|
||||||
|
};
|
||||||
|
let address = $wallet.saved.short_address.trim();
|
||||||
|
if !Wallet::short_address_validation(address) {
|
||||||
|
println!("storage value writer wallet invalid: {address}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let unsigned = $unsigned {
|
||||||
|
txtype: $txtype,
|
||||||
|
time: $timestamp,
|
||||||
|
storagekey: $storagekey,
|
||||||
|
key: $key,
|
||||||
|
value,
|
||||||
|
address: address.to_string(),
|
||||||
|
txfee,
|
||||||
|
};
|
||||||
|
let transaction = match $signed::new(unsigned, &$wallet.saved.private_key).await {
|
||||||
|
Ok(transaction) => transaction,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to sign storage value transaction: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let hash = transaction.$unsigned_field.hash().await;
|
||||||
|
let output = json!({
|
||||||
|
"txtype": $txtype,
|
||||||
|
"timestamp": $timestamp,
|
||||||
|
"storagekey": transaction.$unsigned_field.storagekey,
|
||||||
|
"key": transaction.$unsigned_field.key,
|
||||||
|
"value": transaction.$unsigned_field.value,
|
||||||
|
"address": address,
|
||||||
|
"txfee": txfee,
|
||||||
|
"hash": hash,
|
||||||
|
"signature": transaction.signature,
|
||||||
|
});
|
||||||
|
if let Err(err) = write_transaction(&hash, &output).await {
|
||||||
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_bool_storage(
|
||||||
|
args: &[String],
|
||||||
|
timestamp: u32,
|
||||||
|
wallet: &Wallet,
|
||||||
|
storagekey: String,
|
||||||
|
key: String,
|
||||||
|
) {
|
||||||
|
let value_string = if args.len() > 4 {
|
||||||
|
args[4].clone()
|
||||||
|
} else {
|
||||||
|
prompt_visible("Please enter the bool value (true/false): ").await
|
||||||
|
};
|
||||||
|
let value = match value_string.trim().to_lowercase().as_str() {
|
||||||
|
"true" | "1" | "yes" | "y" => true,
|
||||||
|
"false" | "0" | "no" | "n" => false,
|
||||||
|
_ => {
|
||||||
|
println!("Bool storage value must be true or false.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let txfee = if args.len() > 5 {
|
||||||
|
match parse_fee(&args[5]) {
|
||||||
|
Some(fee) => fee,
|
||||||
|
None => {
|
||||||
|
println!("Please enter a valid fee.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prompt_fee(STORAGE_VALUE_FEE).await
|
||||||
|
};
|
||||||
|
let address = wallet.saved.short_address.trim();
|
||||||
|
if !Wallet::short_address_validation(address) {
|
||||||
|
println!("storage value writer wallet invalid: {address}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unsigned = UnsignedStorageBool {
|
||||||
|
txtype: STORAGE_BOOL_TYPE,
|
||||||
|
time: timestamp,
|
||||||
|
storagekey,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
address: address.to_string(),
|
||||||
|
txfee,
|
||||||
|
};
|
||||||
|
let transaction = match StorageBool::new(unsigned, &wallet.saved.private_key).await {
|
||||||
|
Ok(transaction) => transaction,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to sign storage bool transaction: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let hash = transaction.unsigned_storagebool.hash().await;
|
||||||
|
let output = json!({
|
||||||
|
"txtype": STORAGE_BOOL_TYPE,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"storagekey": transaction.unsigned_storagebool.storagekey,
|
||||||
|
"key": transaction.unsigned_storagebool.key,
|
||||||
|
"value": transaction.unsigned_storagebool.value,
|
||||||
|
"address": address,
|
||||||
|
"txfee": txfee,
|
||||||
|
"hash": hash,
|
||||||
|
"signature": transaction.signature,
|
||||||
|
});
|
||||||
|
if let Err(err) = write_transaction(&hash, &output).await {
|
||||||
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_string_storage(
|
||||||
|
args: &[String],
|
||||||
|
timestamp: u32,
|
||||||
|
wallet: &Wallet,
|
||||||
|
storagekey: String,
|
||||||
|
key: String,
|
||||||
|
) {
|
||||||
|
let value_input = if args.len() > 4 {
|
||||||
|
args[4].clone()
|
||||||
|
} else {
|
||||||
|
prompt_visible("Please enter the string value: ").await
|
||||||
|
};
|
||||||
|
let Some(value) = fixed_string_value(&value_input) else {
|
||||||
|
println!("Storage string value must be 180 bytes or less.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let previous = if args.len() > 5 {
|
||||||
|
args[5].clone()
|
||||||
|
} else {
|
||||||
|
prompt_visible_with_default("Please enter the previous string tx hash", ZERO_HASH).await
|
||||||
|
};
|
||||||
|
if !valid_previous_hash(&previous) {
|
||||||
|
println!("Previous hash must be a 32-byte / 64-character hex string.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let txfee_arg_index = if args.len() > 6 { Some(6) } else { None };
|
||||||
|
let txfee = if let Some(index) = txfee_arg_index {
|
||||||
|
match parse_fee(&args[index]) {
|
||||||
|
Some(fee) => fee,
|
||||||
|
None => {
|
||||||
|
println!("Please enter a valid fee.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prompt_fee(STORAGE_VALUE_FEE).await
|
||||||
|
};
|
||||||
|
let address = wallet.saved.short_address.trim();
|
||||||
|
if !Wallet::short_address_validation(address) {
|
||||||
|
println!("storage value writer wallet invalid: {address}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unsigned = UnsignedStorageString {
|
||||||
|
txtype: STORAGE_STRING_TYPE,
|
||||||
|
time: timestamp,
|
||||||
|
storagekey,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
address: address.to_string(),
|
||||||
|
previous: previous.trim().to_string(),
|
||||||
|
txfee,
|
||||||
|
};
|
||||||
|
let transaction = match StorageString::new(unsigned, &wallet.saved.private_key).await {
|
||||||
|
Ok(transaction) => transaction,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to sign storage string transaction: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let hash = transaction.unsigned_storage.hash().await;
|
||||||
|
let output = json!({
|
||||||
|
"txtype": STORAGE_STRING_TYPE,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"storagekey": transaction.unsigned_storage.storagekey,
|
||||||
|
"key": transaction.unsigned_storage.key,
|
||||||
|
"value": transaction.unsigned_storage.value,
|
||||||
|
"address": address,
|
||||||
|
"previous": transaction.unsigned_storage.previous,
|
||||||
|
"txfee": txfee,
|
||||||
|
"hash": hash,
|
||||||
|
"signature": transaction.signature,
|
||||||
|
});
|
||||||
|
if let Err(err) = write_transaction(&hash, &output).await {
|
||||||
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let command = if args.len() > 1 {
|
||||||
|
args[1].clone()
|
||||||
|
} else {
|
||||||
|
prompt_visible("Please enter storage transaction type: ").await
|
||||||
|
};
|
||||||
|
let Some(storage_type) = normalize_storage_type(&command) else {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(wallet) = load_wallet().await else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let timestamp = Utc::now().timestamp() as u32;
|
||||||
|
|
||||||
|
if storage_type == "create_storage" {
|
||||||
|
create_storage_key(&args, timestamp, &wallet).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(storagekey) = prompt_storage_hash(&args, 2).await else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(key) = prompt_field_key(&args, 3).await else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match storage_type {
|
||||||
|
"bool" => create_bool_storage(&args, timestamp, &wallet, storagekey, key).await,
|
||||||
|
"u8" => {
|
||||||
|
create_numeric_storage!(
|
||||||
|
args,
|
||||||
|
timestamp,
|
||||||
|
wallet,
|
||||||
|
storagekey,
|
||||||
|
key,
|
||||||
|
u8,
|
||||||
|
UnsignedStorageU8,
|
||||||
|
StorageU8,
|
||||||
|
unsigned_storageu8,
|
||||||
|
STORAGE_U8_TYPE,
|
||||||
|
"u8"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"u16" => {
|
||||||
|
create_numeric_storage!(
|
||||||
|
args,
|
||||||
|
timestamp,
|
||||||
|
wallet,
|
||||||
|
storagekey,
|
||||||
|
key,
|
||||||
|
u16,
|
||||||
|
UnsignedStorageU16,
|
||||||
|
StorageU16,
|
||||||
|
unsigned_storageu16,
|
||||||
|
STORAGE_U16_TYPE,
|
||||||
|
"u16"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"u32" => {
|
||||||
|
create_numeric_storage!(
|
||||||
|
args,
|
||||||
|
timestamp,
|
||||||
|
wallet,
|
||||||
|
storagekey,
|
||||||
|
key,
|
||||||
|
u32,
|
||||||
|
UnsignedStorageU32,
|
||||||
|
StorageU32,
|
||||||
|
unsigned_storageu32,
|
||||||
|
STORAGE_U32_TYPE,
|
||||||
|
"u32"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"u64" => {
|
||||||
|
create_numeric_storage!(
|
||||||
|
args,
|
||||||
|
timestamp,
|
||||||
|
wallet,
|
||||||
|
storagekey,
|
||||||
|
key,
|
||||||
|
u64,
|
||||||
|
UnsignedStorageU64,
|
||||||
|
StorageU64,
|
||||||
|
unsigned_storageu64,
|
||||||
|
STORAGE_U64_TYPE,
|
||||||
|
"u64"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"u128" => {
|
||||||
|
create_numeric_storage!(
|
||||||
|
args,
|
||||||
|
timestamp,
|
||||||
|
wallet,
|
||||||
|
storagekey,
|
||||||
|
key,
|
||||||
|
u128,
|
||||||
|
UnsignedStorageU128,
|
||||||
|
StorageU128,
|
||||||
|
unsigned_storageu128,
|
||||||
|
STORAGE_U128_TYPE,
|
||||||
|
"u128"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"string" => create_string_storage(&args, timestamp, &wallet, storagekey, key).await,
|
||||||
|
_ => usage(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -343,6 +343,38 @@ impl Block {
|
||||||
let tx_bytes = vanity_tx.to_bytes().await?;
|
let tx_bytes = vanity_tx.to_bytes().await?;
|
||||||
buffer.extend_from_slice(&tx_bytes);
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
}
|
}
|
||||||
|
Transaction::StorageKey(storage_key_tx) => {
|
||||||
|
let tx_bytes = storage_key_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
|
Transaction::StorageBool(storage_tx) => {
|
||||||
|
let tx_bytes = storage_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
|
Transaction::StorageU8(storage_tx) => {
|
||||||
|
let tx_bytes = storage_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
|
Transaction::StorageU16(storage_tx) => {
|
||||||
|
let tx_bytes = storage_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
|
Transaction::StorageU32(storage_tx) => {
|
||||||
|
let tx_bytes = storage_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
|
Transaction::StorageU64(storage_tx) => {
|
||||||
|
let tx_bytes = storage_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
|
Transaction::StorageU128(storage_tx) => {
|
||||||
|
let tx_bytes = storage_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
|
Transaction::StorageString(storage_tx) => {
|
||||||
|
let tx_bytes = storage_tx.to_bytes().await?;
|
||||||
|
buffer.extend_from_slice(&tx_bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ pub mod loans;
|
||||||
pub mod marketing;
|
pub mod marketing;
|
||||||
pub mod nft;
|
pub mod nft;
|
||||||
pub mod rewards;
|
pub mod rewards;
|
||||||
|
pub mod storage_key;
|
||||||
|
pub mod storage_values;
|
||||||
pub mod swap;
|
pub mod swap;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
pub mod transfer;
|
pub mod transfer;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::blocks::marketing::MarketingTransaction;
|
use crate::blocks::marketing::MarketingTransaction;
|
||||||
use crate::blocks::nft::CreateNftTransaction;
|
use crate::blocks::nft::CreateNftTransaction;
|
||||||
use crate::blocks::rewards::RewardsTransaction;
|
use crate::blocks::rewards::RewardsTransaction;
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
|
||||||
|
};
|
||||||
use crate::blocks::swap::SwapTransaction;
|
use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
|
|
@ -28,6 +32,14 @@ pub const COLLATERAL_TYPE: u8 = 9;
|
||||||
pub const BURN_TYPE: u8 = 10;
|
pub const BURN_TYPE: u8 = 10;
|
||||||
pub const ISSUE_TOKEN_TYPE: u8 = 11;
|
pub const ISSUE_TOKEN_TYPE: u8 = 11;
|
||||||
pub const VANITY_ADDRESS_TYPE: u8 = 12;
|
pub const VANITY_ADDRESS_TYPE: u8 = 12;
|
||||||
|
pub const STORAGE_KEY_TYPE: u8 = 100;
|
||||||
|
pub const STORAGE_BOOL_TYPE: u8 = 101;
|
||||||
|
pub const STORAGE_U8_TYPE: u8 = 102;
|
||||||
|
pub const STORAGE_U16_TYPE: u8 = 103;
|
||||||
|
pub const STORAGE_U32_TYPE: u8 = 104;
|
||||||
|
pub const STORAGE_U64_TYPE: u8 = 105;
|
||||||
|
pub const STORAGE_U128_TYPE: u8 = 106;
|
||||||
|
pub const STORAGE_STRING_TYPE: u8 = 107;
|
||||||
|
|
||||||
// Coin and asset names are stored in fixed 15-byte fields.
|
// Coin and asset names are stored in fixed 15-byte fields.
|
||||||
pub const COIN_LENGTH: usize = 15;
|
pub const COIN_LENGTH: usize = 15;
|
||||||
|
|
@ -62,6 +74,10 @@ pub const COLLATERAL_FEE: u64 = 300000000; // cost to clain collateral, 3 CLC
|
||||||
pub const BURN_FEE: u64 = 10000; // cost to burn a token or NFT, 0.0001 CLC
|
pub const BURN_FEE: u64 = 10000; // cost to burn a token or NFT, 0.0001 CLC
|
||||||
pub const ISSUE_TOKEN_FEE: u64 = 10000000000; // cost to issue more of an existing token, 100 CLC
|
pub const ISSUE_TOKEN_FEE: u64 = 10000000000; // cost to issue more of an existing token, 100 CLC
|
||||||
pub const VANITY_ADDRESS_FEE: u64 = 500000000; // cost to register or update a vanity address, 5 CLC
|
pub const VANITY_ADDRESS_FEE: u64 = 500000000; // cost to register or update a vanity address, 5 CLC
|
||||||
|
pub const STORAGE_KEY_FEE: u64 = 50000000000; // cost to create an app storage key, 500 CLC
|
||||||
|
pub const STORAGE_VALUE_FEE: u64 = 100000000; // cost to write app storage data, 1 CLC
|
||||||
|
pub const STORAGE_KEY_INDEX_TREE: &str = "storage_key_index";
|
||||||
|
pub const STORAGE_VALUE_ROLLBACK_TREE: &str = "storage_value_rollback";
|
||||||
|
|
||||||
pub fn minimum_transfer_fee(value: u64, is_base_coin: bool) -> u64 {
|
pub fn minimum_transfer_fee(value: u64, is_base_coin: bool) -> u64 {
|
||||||
// Base-coin transfers pay the percentage fee using integer math.
|
// Base-coin transfers pay the percentage fee using integer math.
|
||||||
|
|
@ -92,4 +108,12 @@ pub enum Transaction {
|
||||||
Borrower(ContractPaymentTransaction),
|
Borrower(ContractPaymentTransaction),
|
||||||
Collateral(CollateralClaimTransaction),
|
Collateral(CollateralClaimTransaction),
|
||||||
Vanity(VanityAddressTransaction),
|
Vanity(VanityAddressTransaction),
|
||||||
|
StorageKey(StorageKey),
|
||||||
|
StorageBool(StorageBool),
|
||||||
|
StorageU8(StorageU8),
|
||||||
|
StorageU16(StorageU16),
|
||||||
|
StorageU32(StorageU32),
|
||||||
|
StorageU64(StorageU64),
|
||||||
|
StorageU128(StorageU128),
|
||||||
|
StorageString(StorageString),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ pub struct Settings {
|
||||||
pub pg_user: String,
|
pub pg_user: String,
|
||||||
pub pg_password: Option<String>,
|
pub pg_password: Option<String>,
|
||||||
pub pg_dbname: String,
|
pub pg_dbname: String,
|
||||||
|
pub storage_lookup_fee_per_byte: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
|
@ -197,6 +198,10 @@ impl Settings {
|
||||||
.ok_or("INCOMING_CONNECTIONS not found")?
|
.ok_or("INCOMING_CONNECTIONS not found")?
|
||||||
.parse::<u8>()?,
|
.parse::<u8>()?,
|
||||||
threads,
|
threads,
|
||||||
|
storage_lookup_fee_per_byte: conf
|
||||||
|
.get_from(Some("Settings"), "STORAGE_LOOKUP_FEE_PER_BYTE")
|
||||||
|
.unwrap_or("0")
|
||||||
|
.parse::<u64>()?,
|
||||||
pg_host: pg_section.get("host").unwrap_or("127.0.0.1").to_string(),
|
pg_host: pg_section.get("host").unwrap_or("127.0.0.1").to_string(),
|
||||||
pg_port: pg_section.get("port").unwrap_or("5432").parse::<u16>()?,
|
pg_port: pg_section.get("port").unwrap_or("5432").parse::<u16>()?,
|
||||||
pg_user: pg_section
|
pg_user: pg_section
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ use crate::orphans::undo_transactions::undo_issue_token::undo_issue_token_transa
|
||||||
use crate::orphans::undo_transactions::undo_loan_creation::undo_loan_creation_transaction;
|
use crate::orphans::undo_transactions::undo_loan_creation::undo_loan_creation_transaction;
|
||||||
use crate::orphans::undo_transactions::undo_marketing::undo_marketing_transaction;
|
use crate::orphans::undo_transactions::undo_marketing::undo_marketing_transaction;
|
||||||
use crate::orphans::undo_transactions::undo_rewards::undo_rewards_transaction;
|
use crate::orphans::undo_transactions::undo_rewards::undo_rewards_transaction;
|
||||||
|
use crate::orphans::undo_transactions::undo_storage_key::undo_storage_key_transaction;
|
||||||
|
use crate::orphans::undo_transactions::undo_storage_value::undo_storage_value_transaction;
|
||||||
use crate::orphans::undo_transactions::undo_swap::undo_swap_transaction;
|
use crate::orphans::undo_transactions::undo_swap::undo_swap_transaction;
|
||||||
use crate::orphans::undo_transactions::undo_transfer::undo_transfer_transaction;
|
use crate::orphans::undo_transactions::undo_transfer::undo_transfer_transaction;
|
||||||
use crate::orphans::undo_transactions::undo_vanity::undo_vanity_transaction;
|
use crate::orphans::undo_transactions::undo_vanity::undo_vanity_transaction;
|
||||||
|
|
@ -130,6 +132,30 @@ pub async fn undo_transactions(
|
||||||
.await?;
|
.await?;
|
||||||
rolled_back_transactions.push(Transaction::Vanity(vanity_tx));
|
rolled_back_transactions.push(Transaction::Vanity(vanity_tx));
|
||||||
}
|
}
|
||||||
|
Transaction::StorageKey(storage_key_tx) => {
|
||||||
|
undo_storage_key_transaction(
|
||||||
|
storage_key_tx.clone(),
|
||||||
|
&mining_receiver,
|
||||||
|
¶ms.db,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
rolled_back_transactions.push(Transaction::StorageKey(storage_key_tx));
|
||||||
|
}
|
||||||
|
Transaction::StorageBool(_)
|
||||||
|
| Transaction::StorageU8(_)
|
||||||
|
| Transaction::StorageU16(_)
|
||||||
|
| Transaction::StorageU32(_)
|
||||||
|
| Transaction::StorageU64(_)
|
||||||
|
| Transaction::StorageU128(_)
|
||||||
|
| Transaction::StorageString(_) => {
|
||||||
|
undo_storage_value_transaction(
|
||||||
|
transaction.clone(),
|
||||||
|
&mining_receiver,
|
||||||
|
¶ms.db,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
rolled_back_transactions.push(transaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The block miner loses one mined-block count when this block is removed.
|
// The block miner loses one mined-block count when this block is removed.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ pub mod undo_issue_token;
|
||||||
pub mod undo_loan_creation;
|
pub mod undo_loan_creation;
|
||||||
pub mod undo_marketing;
|
pub mod undo_marketing;
|
||||||
pub mod undo_rewards;
|
pub mod undo_rewards;
|
||||||
|
pub mod undo_storage_key;
|
||||||
|
pub mod undo_storage_value;
|
||||||
pub mod undo_swap;
|
pub mod undo_swap;
|
||||||
pub mod undo_transfer;
|
pub mod undo_transfer;
|
||||||
pub mod undo_vanity;
|
pub mod undo_vanity;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ use crate::blocks::loan_payment::ContractPaymentTransaction;
|
||||||
use crate::blocks::loans::LoanContractTransaction;
|
use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::blocks::marketing::MarketingTransaction;
|
use crate::blocks::marketing::MarketingTransaction;
|
||||||
use crate::blocks::nft::CreateNftTransaction;
|
use crate::blocks::nft::CreateNftTransaction;
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::blocks::storage_values::StorageValueTransaction;
|
||||||
use crate::blocks::swap::SwapTransaction;
|
use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
|
|
@ -307,6 +309,43 @@ pub async fn restore_vanity(transaction: &VanityAddressTransaction, db: &Db) {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn restore_storage_key(transaction: &StorageKey, db: &Db) {
|
||||||
|
let storage_key = &transaction.unsigned_storagekey;
|
||||||
|
let spendable = balance_checkup(
|
||||||
|
db,
|
||||||
|
0,
|
||||||
|
storage_key.txfee,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
&storage_key.address,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let signature = transaction.signature.clone();
|
||||||
|
|
||||||
|
restore_if_spendable_void(
|
||||||
|
"storage_key",
|
||||||
|
"unknown",
|
||||||
|
&[signature],
|
||||||
|
spendable,
|
||||||
|
|| async {
|
||||||
|
let _ = transaction.add_to_memory().await;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn restore_storage_value<T, F, Fut>(tx_label: &str, transaction: &T, db: &Db, insert: F)
|
||||||
|
where
|
||||||
|
T: StorageValueTransaction,
|
||||||
|
F: FnOnce() -> Fut,
|
||||||
|
Fut: std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>>,
|
||||||
|
{
|
||||||
|
let parts = transaction.storage_parts();
|
||||||
|
let spendable = balance_checkup(db, 0, parts.txfee, BASECOIN.clone(), parts.address).await;
|
||||||
|
let signature = parts.signature.to_string();
|
||||||
|
|
||||||
|
restore_if_spendable(tx_label, "unknown", &[signature], spendable, insert).await;
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db) {
|
pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db) {
|
||||||
match transaction {
|
match transaction {
|
||||||
Transaction::Transfer(tx) => restore_transfer(tx, db).await,
|
Transaction::Transfer(tx) => restore_transfer(tx, db).await,
|
||||||
|
|
@ -320,6 +359,40 @@ pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db)
|
||||||
Transaction::Borrower(tx) => restore_borrower(tx, db).await,
|
Transaction::Borrower(tx) => restore_borrower(tx, db).await,
|
||||||
Transaction::Collateral(tx) => restore_collateral(tx, db).await,
|
Transaction::Collateral(tx) => restore_collateral(tx, db).await,
|
||||||
Transaction::Vanity(tx) => restore_vanity(tx, db).await,
|
Transaction::Vanity(tx) => restore_vanity(tx, db).await,
|
||||||
|
Transaction::StorageKey(tx) => restore_storage_key(tx, db).await,
|
||||||
|
Transaction::StorageBool(tx) => {
|
||||||
|
restore_storage_value("storage_bool", tx, db, || async {
|
||||||
|
tx.add_to_memory().await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Transaction::StorageU8(tx) => {
|
||||||
|
restore_storage_value("storage_u8", tx, db, || async { tx.add_to_memory().await }).await
|
||||||
|
}
|
||||||
|
Transaction::StorageU16(tx) => {
|
||||||
|
restore_storage_value("storage_u16", tx, db, || async { tx.add_to_memory().await })
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Transaction::StorageU32(tx) => {
|
||||||
|
restore_storage_value("storage_u32", tx, db, || async { tx.add_to_memory().await })
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Transaction::StorageU64(tx) => {
|
||||||
|
restore_storage_value("storage_u64", tx, db, || async { tx.add_to_memory().await })
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Transaction::StorageU128(tx) => {
|
||||||
|
restore_storage_value("storage_u128", tx, db, || async {
|
||||||
|
tx.add_to_memory().await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Transaction::StorageString(tx) => {
|
||||||
|
restore_storage_value("storage_string", tx, db, || async {
|
||||||
|
tx.add_to_memory().await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
Transaction::Genesis(_) | Transaction::Rewards(_) => {}
|
Transaction::Genesis(_) | Transaction::Rewards(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::common::types::STORAGE_KEY_INDEX_TREE;
|
||||||
|
use crate::decode;
|
||||||
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
|
use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index;
|
||||||
|
use crate::sled::Db;
|
||||||
|
|
||||||
|
pub async fn undo_storage_key_transaction(
|
||||||
|
transaction: StorageKey,
|
||||||
|
mining_receiver: &str,
|
||||||
|
db: &Db,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let txfee = transaction.unsigned_storagekey.txfee;
|
||||||
|
let address = transaction.unsigned_storagekey.address.clone();
|
||||||
|
let txhash = transaction.unsigned_storagekey.hash().await;
|
||||||
|
let txhash_bytes = decode(&txhash)
|
||||||
|
.map_err(|err| format!("Could not decode storage key txhash during undo: {err}"))?;
|
||||||
|
|
||||||
|
let _ = balance_sheet_operation_with_db(db, mining_receiver, txfee, &BASECOIN, "subtraction");
|
||||||
|
let _ = balance_sheet_operation_with_db(db, &address, txfee, &BASECOIN, "addition");
|
||||||
|
|
||||||
|
let _ = db
|
||||||
|
.drop_tree(&txhash_bytes)
|
||||||
|
.map_err(|err| format!("Could not drop storage key tree during undo: {err}"))?;
|
||||||
|
let storage_key_index = db
|
||||||
|
.open_tree(STORAGE_KEY_INDEX_TREE)
|
||||||
|
.map_err(|err| format!("Could not open storage key index during undo: {err}"))?;
|
||||||
|
storage_key_index
|
||||||
|
.remove(&txhash_bytes)
|
||||||
|
.map_err(|err| format!("Could not remove storage key index during undo: {err}"))?;
|
||||||
|
|
||||||
|
let _ = remove_wallet_transaction_index(db, &[&address, mining_receiver], &txhash_bytes);
|
||||||
|
let txid_tree = db
|
||||||
|
.open_tree("txid")
|
||||||
|
.map_err(|err| format!("Could not open txid tree during storage key undo: {err}"))?;
|
||||||
|
txid_tree
|
||||||
|
.remove(txhash_bytes)
|
||||||
|
.map_err(|err| format!("Could not remove storage key txid mapping during undo: {err}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
storage_record_key, storage_string_record_key, StorageValueTransaction,
|
||||||
|
};
|
||||||
|
use crate::common::types::{Transaction, STORAGE_VALUE_ROLLBACK_TREE};
|
||||||
|
use crate::decode;
|
||||||
|
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||||
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
|
use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index;
|
||||||
|
use crate::sled::Db;
|
||||||
|
|
||||||
|
struct StorageUndoParts {
|
||||||
|
storagekey: String,
|
||||||
|
key: String,
|
||||||
|
address: String,
|
||||||
|
txfee: u64,
|
||||||
|
hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_undo_parts(transaction: &Transaction) -> Result<StorageUndoParts, String> {
|
||||||
|
let parts = match transaction {
|
||||||
|
Transaction::StorageBool(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU8(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU16(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU32(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU64(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU128(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageString(tx) => tx.storage_parts(),
|
||||||
|
_ => return Err("Unsupported storage value transaction undo.".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(StorageUndoParts {
|
||||||
|
storagekey: parts.storagekey.to_string(),
|
||||||
|
key: parts.key.to_string(),
|
||||||
|
address: parts.address.to_string(),
|
||||||
|
txfee: parts.txfee,
|
||||||
|
hash: parts.hash,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn undo_storage_value_transaction(
|
||||||
|
transaction: Transaction,
|
||||||
|
mining_receiver: &str,
|
||||||
|
db: &Db,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let parts = storage_undo_parts(&transaction)?;
|
||||||
|
let storage_tree_name = decode(&parts.storagekey)
|
||||||
|
.map_err(|err| format!("Could not decode storage tree key during undo: {err}"))?;
|
||||||
|
let txhash_bytes = decode(&parts.hash)
|
||||||
|
.map_err(|err| format!("Could not decode storage value txhash during undo: {err}"))?;
|
||||||
|
let storage_record_key = match &transaction {
|
||||||
|
Transaction::StorageString(tx) => storage_string_record_key(db, tx).await?,
|
||||||
|
_ => storage_record_key(&parts.address, &parts.key)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ =
|
||||||
|
balance_sheet_operation_with_db(db, mining_receiver, parts.txfee, &BASECOIN, "subtraction");
|
||||||
|
let _ = balance_sheet_operation_with_db(db, &parts.address, parts.txfee, &BASECOIN, "addition");
|
||||||
|
|
||||||
|
let rollback_tree = db
|
||||||
|
.open_tree(STORAGE_VALUE_ROLLBACK_TREE)
|
||||||
|
.map_err(|err| format!("Could not open storage value rollback tree during undo: {err}"))?;
|
||||||
|
let rollback_value = rollback_tree
|
||||||
|
.get(&txhash_bytes)
|
||||||
|
.map_err(|err| format!("Could not read storage value rollback during undo: {err}"))?
|
||||||
|
.ok_or_else(|| "Missing storage value rollback record.".to_string())?;
|
||||||
|
|
||||||
|
let storage_tree = db
|
||||||
|
.open_tree(storage_tree_name)
|
||||||
|
.map_err(|err| format!("Could not open storage tree during undo: {err}"))?;
|
||||||
|
match rollback_value.first().copied() {
|
||||||
|
Some(0) => {
|
||||||
|
storage_tree
|
||||||
|
.remove(storage_record_key)
|
||||||
|
.map_err(|err| format!("Could not remove storage value during undo: {err}"))?;
|
||||||
|
}
|
||||||
|
Some(1) => {
|
||||||
|
storage_tree
|
||||||
|
.insert(storage_record_key, &rollback_value[1..])
|
||||||
|
.map_err(|err| format!("Could not restore storage value during undo: {err}"))?;
|
||||||
|
}
|
||||||
|
_ => return Err("Invalid storage value rollback marker.".to_string()),
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback_tree
|
||||||
|
.remove(&txhash_bytes)
|
||||||
|
.map_err(|err| format!("Could not remove storage value rollback record: {err}"))?;
|
||||||
|
|
||||||
|
let _ = remove_wallet_transaction_index(db, &[&parts.address, mining_receiver], &txhash_bytes);
|
||||||
|
let txid_tree = db
|
||||||
|
.open_tree("txid")
|
||||||
|
.map_err(|err| format!("Could not open txid tree during storage value undo: {err}"))?;
|
||||||
|
txid_tree
|
||||||
|
.remove(txhash_bytes)
|
||||||
|
.map_err(|err| format!("Could not remove storage value txid mapping during undo: {err}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -186,10 +186,10 @@ async fn retry_dropped_outgoing(ip: String, port: u16) {
|
||||||
if err_string.contains(
|
if err_string.contains(
|
||||||
"The connection is already in the connection manager Please wait 10 minutes and try again",
|
"The connection is already in the connection manager Please wait 10 minutes and try again",
|
||||||
) {
|
) {
|
||||||
info!(
|
warn!(
|
||||||
"[reconnect] dropped peer {addr_string} is already in the connection manager; stopping reconnect attempts"
|
"[reconnect] dropped peer {addr_string} is still recorded by remote connection manager on attempt {attempt}/3; retrying"
|
||||||
);
|
);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
warn!(
|
warn!(
|
||||||
"[reconnect] failed to reconnect dropped peer {addr_string} on attempt {attempt}/3: {err_string}"
|
"[reconnect] failed to reconnect dropped peer {addr_string} on attempt {attempt}/3: {err_string}"
|
||||||
|
|
@ -965,10 +965,7 @@ pub async fn peer_accepts_live_relay(key: &str) -> bool {
|
||||||
&& connection_key.port == port
|
&& connection_key.port == port
|
||||||
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||||
{
|
{
|
||||||
Some(
|
Some(info.ready || (info.wallet_registry_synced && info.network_map_synced))
|
||||||
info.ready
|
|
||||||
|| (info.wallet_registry_synced && info.network_map_synced),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ pub async fn signature_exists(signature: &str, hash: &str) -> Result<bool> {
|
||||||
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)
|
||||||
|
OR EXISTS (SELECT 1 FROM storage_key sk WHERE sk.signature = $1 AND sk.hash = $2 AND sk.processed = false)
|
||||||
|
OR EXISTS (SELECT 1 FROM storage_value sv WHERE sv.signature = $1 AND sv.hash = $2 AND sv.processed = false)
|
||||||
THEN 1
|
THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END AS signature_found;
|
END AS signature_found;
|
||||||
|
|
@ -74,6 +76,10 @@ pub async fn transaction_by_signature(signature: &str) -> RpcResponse {
|
||||||
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
|
||||||
|
UNION ALL
|
||||||
|
SELECT original FROM storage_key WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT original FROM storage_value WHERE signature = $1 AND processed = false LIMIT 1
|
||||||
) AS subquery LIMIT 1
|
) AS subquery LIMIT 1
|
||||||
"#,
|
"#,
|
||||||
&[&signature],
|
&[&signature],
|
||||||
|
|
@ -129,6 +135,10 @@ pub async fn transactions_by_address(db: &Db, address: &str) -> RpcResponse {
|
||||||
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
|
||||||
|
UNION ALL
|
||||||
|
SELECT original FROM storage_key WHERE address = ANY($1) AND processed = false
|
||||||
|
UNION ALL
|
||||||
|
SELECT original FROM storage_value WHERE address = ANY($1) AND processed = false
|
||||||
) AS subquery;
|
) AS subquery;
|
||||||
"#,
|
"#,
|
||||||
&[&addresses],
|
&[&addresses],
|
||||||
|
|
@ -198,6 +208,12 @@ pub async fn latest_pending_txids_by_address(db: &Db, address: &str, limit: usiz
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT hash, time, id AS source_id FROM collateral_claim
|
SELECT hash, time, id AS source_id FROM collateral_claim
|
||||||
WHERE address = ANY($1) AND processed = false
|
WHERE address = ANY($1) AND processed = false
|
||||||
|
UNION ALL
|
||||||
|
SELECT hash, time, id AS source_id FROM storage_key
|
||||||
|
WHERE address = ANY($1) AND processed = false
|
||||||
|
UNION ALL
|
||||||
|
SELECT hash, time, id AS source_id FROM storage_value
|
||||||
|
WHERE address = ANY($1) AND processed = false
|
||||||
) AS pending
|
) AS pending
|
||||||
ORDER BY hash, time DESC, source_id DESC
|
ORDER BY hash, time DESC, source_id DESC
|
||||||
) AS deduped
|
) AS deduped
|
||||||
|
|
@ -256,6 +272,10 @@ pub async fn pending_transaction_by_txid(txid: &[u8]) -> Option<Vec<u8>> {
|
||||||
SELECT original FROM loan_payment WHERE hash = $1 AND processed = false
|
SELECT original FROM loan_payment WHERE hash = $1 AND processed = false
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT original FROM collateral_claim WHERE hash = $1 AND processed = false
|
SELECT original FROM collateral_claim WHERE hash = $1 AND processed = false
|
||||||
|
UNION ALL
|
||||||
|
SELECT original FROM storage_key WHERE hash = $1 AND processed = false
|
||||||
|
UNION ALL
|
||||||
|
SELECT original FROM storage_value WHERE hash = $1 AND processed = false
|
||||||
) AS subquery LIMIT 1
|
) AS subquery LIMIT 1
|
||||||
"#,
|
"#,
|
||||||
&[&hash],
|
&[&hash],
|
||||||
|
|
@ -301,6 +321,10 @@ pub async fn largest_fee() -> RpcResponse {
|
||||||
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
|
||||||
|
UNION ALL
|
||||||
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM storage_key
|
||||||
|
UNION ALL
|
||||||
|
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM storage_value
|
||||||
) AS combined_max_txids;
|
) AS combined_max_txids;
|
||||||
"#,
|
"#,
|
||||||
&[],
|
&[],
|
||||||
|
|
@ -481,6 +505,8 @@ pub async fn get_basecoin_balance(
|
||||||
+ 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(sk.fee) FROM storage_key sk WHERE sk.address = ANY($1) AND sk.processed = false), 0)
|
||||||
|
+ COALESCE((SELECT SUM(sv.fee) FROM storage_value sv WHERE sv.address = ANY($1) AND sv.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)
|
||||||
|
|
@ -575,6 +601,10 @@ pub async fn total_transactions() -> RpcResponse {
|
||||||
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
|
||||||
|
UNION ALL
|
||||||
|
SELECT COUNT(*) AS row_count FROM storage_key
|
||||||
|
UNION ALL
|
||||||
|
SELECT COUNT(*) AS row_count FROM storage_value
|
||||||
) AS combined;
|
) AS combined;
|
||||||
"#,
|
"#,
|
||||||
&[],
|
&[],
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,19 @@ enum SelectedMempoolTransaction {
|
||||||
contract_hash: String,
|
contract_hash: String,
|
||||||
hash: String,
|
hash: String,
|
||||||
},
|
},
|
||||||
|
StorageKey {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
address: String,
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
StorageValue {
|
||||||
|
id: i64,
|
||||||
|
fee: i64,
|
||||||
|
address: String,
|
||||||
|
hash: String,
|
||||||
|
original: Vec<u8>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
|
@ -176,6 +189,8 @@ impl SelectedMempoolTransaction {
|
||||||
SelectedMempoolTransaction::Lender { .. } => "loan_contract",
|
SelectedMempoolTransaction::Lender { .. } => "loan_contract",
|
||||||
SelectedMempoolTransaction::Borrower { .. } => "loan_payment",
|
SelectedMempoolTransaction::Borrower { .. } => "loan_payment",
|
||||||
SelectedMempoolTransaction::Collateral { .. } => "collateral_claim",
|
SelectedMempoolTransaction::Collateral { .. } => "collateral_claim",
|
||||||
|
SelectedMempoolTransaction::StorageKey { .. } => "storage_key",
|
||||||
|
SelectedMempoolTransaction::StorageValue { .. } => "storage_value",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,7 +206,9 @@ impl SelectedMempoolTransaction {
|
||||||
| SelectedMempoolTransaction::Swap { id, .. }
|
| SelectedMempoolTransaction::Swap { id, .. }
|
||||||
| SelectedMempoolTransaction::Lender { id, .. }
|
| SelectedMempoolTransaction::Lender { id, .. }
|
||||||
| SelectedMempoolTransaction::Borrower { id, .. }
|
| SelectedMempoolTransaction::Borrower { id, .. }
|
||||||
| SelectedMempoolTransaction::Collateral { id, .. } => *id,
|
| SelectedMempoolTransaction::Collateral { id, .. }
|
||||||
|
| SelectedMempoolTransaction::StorageKey { id, .. }
|
||||||
|
| SelectedMempoolTransaction::StorageValue { id, .. } => *id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -383,6 +400,8 @@ async fn delete_processed_before_or_at(block_number: u32, limit: i64) -> Result<
|
||||||
delete_processed_rows_limited("loan_contract", bn, limit).await?;
|
delete_processed_rows_limited("loan_contract", bn, limit).await?;
|
||||||
delete_processed_rows_limited("loan_payment", bn, limit).await?;
|
delete_processed_rows_limited("loan_payment", bn, limit).await?;
|
||||||
delete_processed_rows_limited("collateral_claim", bn, limit).await?;
|
delete_processed_rows_limited("collateral_claim", bn, limit).await?;
|
||||||
|
delete_processed_rows_limited("storage_key", bn, limit).await?;
|
||||||
|
delete_processed_rows_limited("storage_value", bn, limit).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ pub async fn mark_selected_transactions_processed(
|
||||||
bn,
|
bn,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
mark_rows_by_ids("storage_key", &ids_for_table(batch, "storage_key"), bn).await?;
|
||||||
|
mark_rows_by_ids("storage_value", &ids_for_table(batch, "storage_value"), bn).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +70,8 @@ pub async fn restore_selected_transactions_processed(batch: &SelectedMempoolBatc
|
||||||
&ids_for_table(batch, "collateral_claim"),
|
&ids_for_table(batch, "collateral_claim"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
unmark_rows_by_ids("storage_key", &ids_for_table(batch, "storage_key")).await?;
|
||||||
|
unmark_rows_by_ids("storage_value", &ids_for_table(batch, "storage_value")).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +100,8 @@ pub async fn restore_processed_by_signatures(signatures: &[String]) -> Result<bo
|
||||||
restored += unmark_by_signatures("loan_contract", "signature2", signatures).await?;
|
restored += unmark_by_signatures("loan_contract", "signature2", signatures).await?;
|
||||||
restored += unmark_by_signatures("loan_payment", "signature", signatures).await?;
|
restored += unmark_by_signatures("loan_payment", "signature", signatures).await?;
|
||||||
restored += unmark_by_signatures("collateral_claim", "signature", signatures).await?;
|
restored += unmark_by_signatures("collateral_claim", "signature", signatures).await?;
|
||||||
|
restored += unmark_by_signatures("storage_key", "signature", signatures).await?;
|
||||||
|
restored += unmark_by_signatures("storage_value", "signature", signatures).await?;
|
||||||
|
|
||||||
Ok(restored > 0)
|
Ok(restored > 0)
|
||||||
}
|
}
|
||||||
|
|
@ -221,6 +227,18 @@ pub async fn mark_processed_by_signatures(signatures: &[String], block_number: u
|
||||||
signatures,
|
signatures,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
pg_execute_block_signatures(
|
||||||
|
"UPDATE storage_key SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
|
bn,
|
||||||
|
signatures,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
pg_execute_block_signatures(
|
||||||
|
"UPDATE storage_value SET processed=true, processed_block_number=$1 WHERE signature = ANY($2)",
|
||||||
|
bn,
|
||||||
|
signatures,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -273,6 +291,16 @@ pub async fn delete_by_signatures(signatures: &[String]) -> Result<()> {
|
||||||
signatures,
|
signatures,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
pg_execute_signatures(
|
||||||
|
"DELETE FROM storage_key WHERE signature = ANY($1)",
|
||||||
|
signatures,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
pg_execute_signatures(
|
||||||
|
"DELETE FROM storage_value WHERE signature = ANY($1)",
|
||||||
|
signatures,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,31 @@ pub async fn setup_mempool() -> Result<()> {
|
||||||
original BYTEA NOT NULL
|
original BYTEA NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS storage_key (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
time INTEGER NOT NULL,
|
||||||
|
fee BIGINT NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
hash VARCHAR(64) NOT NULL,
|
||||||
|
signature TEXT NOT NULL,
|
||||||
|
processed bool DEFAULT false,
|
||||||
|
processed_block_number INTEGER DEFAULT NULL,
|
||||||
|
original BYTEA NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS storage_value (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
txtype SMALLINT NOT NULL,
|
||||||
|
time INTEGER NOT NULL,
|
||||||
|
fee BIGINT NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
hash VARCHAR(64) NOT NULL,
|
||||||
|
signature TEXT NOT NULL,
|
||||||
|
processed bool DEFAULT false,
|
||||||
|
processed_block_number INTEGER DEFAULT 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;
|
||||||
|
|
@ -314,6 +339,10 @@ pub async fn setup_mempool() -> Result<()> {
|
||||||
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;
|
||||||
|
ALTER TABLE storage_key ALTER COLUMN address TYPE TEXT;
|
||||||
|
ALTER TABLE storage_key ALTER COLUMN signature TYPE TEXT;
|
||||||
|
ALTER TABLE storage_value ALTER COLUMN address TYPE TEXT;
|
||||||
|
ALTER TABLE storage_value 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
|
||||||
|
|
@ -372,6 +401,14 @@ pub async fn setup_mempool() -> Result<()> {
|
||||||
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;
|
||||||
|
|
||||||
|
DELETE FROM storage_key a
|
||||||
|
USING storage_key b
|
||||||
|
WHERE a.id > b.id AND a.signature = b.signature;
|
||||||
|
|
||||||
|
DELETE FROM storage_value a
|
||||||
|
USING storage_value b
|
||||||
|
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
|
||||||
|
|
@ -426,6 +463,14 @@ pub async fn setup_mempool() -> Result<()> {
|
||||||
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);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS storage_key_pick_idx ON storage_key (processed, fee DESC, time ASC, id ASC);
|
||||||
|
CREATE INDEX IF NOT EXISTS storage_key_sig_idx ON storage_key (signature);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS storage_key_sig_unique_idx ON storage_key (signature);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS storage_value_pick_idx ON storage_value (processed, fee DESC, time ASC, id ASC);
|
||||||
|
CREATE INDEX IF NOT EXISTS storage_value_sig_idx ON storage_value (signature);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS storage_value_sig_unique_idx ON storage_value (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.
|
||||||
|
|
@ -457,7 +502,9 @@ pub async fn clear_mempool() -> Result<()> {
|
||||||
swap,
|
swap,
|
||||||
loan_contract,
|
loan_contract,
|
||||||
loan_payment,
|
loan_payment,
|
||||||
collateral_claim
|
collateral_claim,
|
||||||
|
storage_key,
|
||||||
|
storage_value
|
||||||
RESTART IDENTITY;
|
RESTART IDENTITY;
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,87 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
storage_record_key, storage_string_record_key, StorageBool, StorageString, StorageU128,
|
||||||
|
StorageU16, StorageU32, StorageU64, StorageU8, StorageValueTransaction,
|
||||||
|
};
|
||||||
|
use crate::common::types::{
|
||||||
|
Transaction, STORAGE_BOOL_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE,
|
||||||
|
STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE,
|
||||||
|
};
|
||||||
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
||||||
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
||||||
|
|
||||||
|
struct SelectedStorageValueParts {
|
||||||
|
storagekey: String,
|
||||||
|
key: String,
|
||||||
|
address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn storage_value_transaction_from_original(original: &[u8]) -> Result<Transaction> {
|
||||||
|
let Some((&txtype, body)) = original.split_first() else {
|
||||||
|
return Err(anyhow!("Empty storage value transaction bytes"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match txtype {
|
||||||
|
STORAGE_BOOL_TYPE => Ok(Transaction::StorageBool(
|
||||||
|
StorageBool::from_bytes(txtype, body).await?,
|
||||||
|
)),
|
||||||
|
STORAGE_U8_TYPE => Ok(Transaction::StorageU8(
|
||||||
|
StorageU8::from_bytes(txtype, body).await?,
|
||||||
|
)),
|
||||||
|
STORAGE_U16_TYPE => Ok(Transaction::StorageU16(
|
||||||
|
StorageU16::from_bytes(txtype, body).await?,
|
||||||
|
)),
|
||||||
|
STORAGE_U32_TYPE => Ok(Transaction::StorageU32(
|
||||||
|
StorageU32::from_bytes(txtype, body).await?,
|
||||||
|
)),
|
||||||
|
STORAGE_U64_TYPE => Ok(Transaction::StorageU64(
|
||||||
|
StorageU64::from_bytes(txtype, body).await?,
|
||||||
|
)),
|
||||||
|
STORAGE_U128_TYPE => Ok(Transaction::StorageU128(
|
||||||
|
StorageU128::from_bytes(txtype, body).await?,
|
||||||
|
)),
|
||||||
|
STORAGE_STRING_TYPE => Ok(Transaction::StorageString(
|
||||||
|
StorageString::from_bytes(txtype, body).await?,
|
||||||
|
)),
|
||||||
|
_ => Err(anyhow!(
|
||||||
|
"Unsupported storage value transaction type {txtype}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_storage_value_parts(transaction: &Transaction) -> Result<SelectedStorageValueParts> {
|
||||||
|
let parts = match transaction {
|
||||||
|
Transaction::StorageBool(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU8(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU16(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU32(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU64(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU128(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageString(tx) => tx.storage_parts(),
|
||||||
|
_ => return Err(anyhow!("Unsupported storage value transaction")),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(SelectedStorageValueParts {
|
||||||
|
storagekey: parts.storagekey.to_string(),
|
||||||
|
key: parts.key.to_string(),
|
||||||
|
address: parts.address.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_storage_value_bytes(transaction: &Transaction) -> Result<Vec<u8>> {
|
||||||
|
match transaction {
|
||||||
|
Transaction::StorageBool(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU8(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU16(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU32(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU64(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU128(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageString(tx) => tx.storage_value_bytes(),
|
||||||
|
_ => Err("Unsupported storage value transaction".to_string()),
|
||||||
|
}
|
||||||
|
.map_err(|err| anyhow!(err))
|
||||||
|
}
|
||||||
|
|
||||||
fn index_selected_wallet_transaction(
|
fn index_selected_wallet_transaction(
|
||||||
pending_effects: &mut PendingEffects,
|
pending_effects: &mut PendingEffects,
|
||||||
addresses: &[&str],
|
addresses: &[&str],
|
||||||
|
|
@ -209,6 +289,44 @@ pub async fn select_transactions_for_block(limit: i64) -> Result<SelectedMempool
|
||||||
signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
||||||
FROM collateral_claim
|
FROM collateral_claim
|
||||||
WHERE processed = false
|
WHERE processed = false
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'storage_key'::TEXT AS kind, id, fee AS priority_fee, time,
|
||||||
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
||||||
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
||||||
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
||||||
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
||||||
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2,
|
||||||
|
NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1, NULL::BIGINT AS value2,
|
||||||
|
NULL::TEXT AS sender1, NULL::BIGINT AS tip1, NULL::BIGINT AS tip2,
|
||||||
|
NULL::TEXT AS sender2, NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount,
|
||||||
|
NULL::TEXT AS lender, NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount,
|
||||||
|
NULL::TEXT AS borrower, NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash,
|
||||||
|
address, hash, signature AS signature1, NULL::TEXT AS signature2,
|
||||||
|
NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
||||||
|
FROM storage_key
|
||||||
|
WHERE processed = false
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'storage_value'::TEXT AS kind, id, fee AS priority_fee, time,
|
||||||
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
||||||
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
||||||
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
||||||
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
||||||
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2,
|
||||||
|
NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1, NULL::BIGINT AS value2,
|
||||||
|
NULL::TEXT AS sender1, NULL::BIGINT AS tip1, NULL::BIGINT AS tip2,
|
||||||
|
NULL::TEXT AS sender2, NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount,
|
||||||
|
NULL::TEXT AS lender, NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount,
|
||||||
|
NULL::TEXT AS borrower, NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash,
|
||||||
|
address, hash, signature AS signature1, NULL::TEXT AS signature2,
|
||||||
|
NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
||||||
|
FROM storage_value
|
||||||
|
WHERE processed = false
|
||||||
) AS combined
|
) AS combined
|
||||||
ORDER BY priority_fee DESC, time ASC, kind ASC, id ASC
|
ORDER BY priority_fee DESC, time ASC, kind ASC, id ASC
|
||||||
LIMIT $1
|
LIMIT $1
|
||||||
|
|
@ -329,6 +447,19 @@ pub async fn select_transactions_for_block(limit: i64) -> Result<SelectedMempool
|
||||||
contract_hash: required_string(&row, "contract_hash")?,
|
contract_hash: required_string(&row, "contract_hash")?,
|
||||||
hash: row.get("hash"),
|
hash: row.get("hash"),
|
||||||
},
|
},
|
||||||
|
"storage_key" => SelectedMempoolTransaction::StorageKey {
|
||||||
|
id: row.get("id"),
|
||||||
|
fee: row.get("priority_fee"),
|
||||||
|
address: required_string(&row, "address")?,
|
||||||
|
hash: row.get("hash"),
|
||||||
|
},
|
||||||
|
"storage_value" => SelectedMempoolTransaction::StorageValue {
|
||||||
|
id: row.get("id"),
|
||||||
|
fee: row.get("priority_fee"),
|
||||||
|
address: required_string(&row, "address")?,
|
||||||
|
hash: row.get("hash"),
|
||||||
|
original: row.get("original"),
|
||||||
|
},
|
||||||
_ => return Err(anyhow!("Unsupported mempool kind {kind}")),
|
_ => return Err(anyhow!("Unsupported mempool kind {kind}")),
|
||||||
};
|
};
|
||||||
transactions.push(transaction);
|
transactions.push(transaction);
|
||||||
|
|
@ -946,6 +1077,77 @@ pub async fn apply_selected_transaction_math(
|
||||||
&decode(hash)?,
|
&decode(hash)?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
SelectedMempoolTransaction::StorageKey {
|
||||||
|
fee, address, hash, ..
|
||||||
|
} => {
|
||||||
|
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
||||||
|
add_balance_change_bytes(
|
||||||
|
&mut balance_changes,
|
||||||
|
miner_address.clone(),
|
||||||
|
base_coin.clone(),
|
||||||
|
*fee,
|
||||||
|
);
|
||||||
|
let txhash_bytes = decode(hash)?;
|
||||||
|
pending_effects.create_tree(txhash_bytes.clone());
|
||||||
|
pending_effects.set_tree(
|
||||||
|
"txid",
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
format!("{block_number}:{tx_index}").into_bytes(),
|
||||||
|
);
|
||||||
|
index_selected_wallet_transaction(
|
||||||
|
pending_effects,
|
||||||
|
&[address],
|
||||||
|
block_number,
|
||||||
|
tx_index,
|
||||||
|
&txhash_bytes,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
SelectedMempoolTransaction::StorageValue {
|
||||||
|
fee,
|
||||||
|
address,
|
||||||
|
hash,
|
||||||
|
original,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let transaction = storage_value_transaction_from_original(original).await?;
|
||||||
|
let parts = selected_storage_value_parts(&transaction)?;
|
||||||
|
let storage_tree = decode(&parts.storagekey)?;
|
||||||
|
let txhash_bytes = decode(hash)?;
|
||||||
|
let storage_record_key = match &transaction {
|
||||||
|
Transaction::StorageString(tx) => storage_string_record_key(db, tx)
|
||||||
|
.await
|
||||||
|
.map_err(|err| anyhow!(err))?,
|
||||||
|
_ => storage_record_key(&parts.address, &parts.key)
|
||||||
|
.map_err(|err| anyhow!(err))?,
|
||||||
|
};
|
||||||
|
let value_bytes = selected_storage_value_bytes(&transaction)?;
|
||||||
|
|
||||||
|
pending_effects.set_dynamic_tree(
|
||||||
|
storage_tree,
|
||||||
|
storage_record_key,
|
||||||
|
value_bytes,
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
);
|
||||||
|
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
||||||
|
add_balance_change_bytes(
|
||||||
|
&mut balance_changes,
|
||||||
|
miner_address.clone(),
|
||||||
|
base_coin.clone(),
|
||||||
|
*fee,
|
||||||
|
);
|
||||||
|
pending_effects.set_tree(
|
||||||
|
"txid",
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
format!("{block_number}:{tx_index}").into_bytes(),
|
||||||
|
);
|
||||||
|
index_selected_wallet_transaction(
|
||||||
|
pending_effects,
|
||||||
|
&[address],
|
||||||
|
block_number,
|
||||||
|
tx_index,
|
||||||
|
&txhash_bytes,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1028,6 +1230,8 @@ pub async fn delete_selected_transactions(batch: &SelectedMempoolBatch) -> Resul
|
||||||
let lender_ids = ids_for_table(batch, "loan_contract");
|
let lender_ids = ids_for_table(batch, "loan_contract");
|
||||||
let borrower_ids = ids_for_table(batch, "loan_payment");
|
let borrower_ids = ids_for_table(batch, "loan_payment");
|
||||||
let collateral_ids = ids_for_table(batch, "collateral_claim");
|
let collateral_ids = ids_for_table(batch, "collateral_claim");
|
||||||
|
let storage_key_ids = ids_for_table(batch, "storage_key");
|
||||||
|
let storage_value_ids = ids_for_table(batch, "storage_value");
|
||||||
|
|
||||||
delete_rows("transfer", &transfer_ids).await?;
|
delete_rows("transfer", &transfer_ids).await?;
|
||||||
delete_rows("token", &token_ids).await?;
|
delete_rows("token", &token_ids).await?;
|
||||||
|
|
@ -1040,6 +1244,8 @@ pub async fn delete_selected_transactions(batch: &SelectedMempoolBatch) -> Resul
|
||||||
delete_rows("loan_contract", &lender_ids).await?;
|
delete_rows("loan_contract", &lender_ids).await?;
|
||||||
delete_rows("loan_payment", &borrower_ids).await?;
|
delete_rows("loan_payment", &borrower_ids).await?;
|
||||||
delete_rows("collateral_claim", &collateral_ids).await?;
|
delete_rows("collateral_claim", &collateral_ids).await?;
|
||||||
|
delete_rows("storage_key", &storage_key_ids).await?;
|
||||||
|
delete_rows("storage_value", &storage_value_ids).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ pub mod previous_difficulty;
|
||||||
pub mod rewards_tx;
|
pub mod rewards_tx;
|
||||||
pub mod save;
|
pub mod save;
|
||||||
pub mod save_flags;
|
pub mod save_flags;
|
||||||
|
pub mod storage_key_tx;
|
||||||
|
pub mod storage_value_tx;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
pub mod swap_tx;
|
pub mod swap_tx;
|
||||||
pub mod token_provenance;
|
pub mod token_provenance;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ use crate::records::record_chain::marketing_tx::process_marketing;
|
||||||
use crate::records::record_chain::nft_tx::process_nft;
|
use crate::records::record_chain::nft_tx::process_nft;
|
||||||
use crate::records::record_chain::pending_effects::PendingEffects;
|
use crate::records::record_chain::pending_effects::PendingEffects;
|
||||||
use crate::records::record_chain::rewards_tx::process_rewards;
|
use crate::records::record_chain::rewards_tx::process_rewards;
|
||||||
|
use crate::records::record_chain::storage_key_tx::process_storage_key;
|
||||||
|
use crate::records::record_chain::storage_value_tx::process_storage_value;
|
||||||
use crate::records::record_chain::swap_tx::process_swap;
|
use crate::records::record_chain::swap_tx::process_swap;
|
||||||
use crate::records::record_chain::token_tx::process_token;
|
use crate::records::record_chain::token_tx::process_token;
|
||||||
use crate::records::record_chain::transfer_tx::process_transfer;
|
use crate::records::record_chain::transfer_tx::process_transfer;
|
||||||
|
|
@ -188,6 +190,36 @@ pub async fn handle_transactions(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
Transaction::StorageKey(storage_key_transaction) => {
|
||||||
|
process_storage_key(
|
||||||
|
storage_key_transaction,
|
||||||
|
binary_data.clone(),
|
||||||
|
db,
|
||||||
|
index_mutex.clone(),
|
||||||
|
miner.clone(),
|
||||||
|
block_header_number,
|
||||||
|
pending_effects,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Transaction::StorageBool(_)
|
||||||
|
| Transaction::StorageU8(_)
|
||||||
|
| Transaction::StorageU16(_)
|
||||||
|
| Transaction::StorageU32(_)
|
||||||
|
| Transaction::StorageU64(_)
|
||||||
|
| Transaction::StorageU128(_)
|
||||||
|
| Transaction::StorageString(_) => {
|
||||||
|
process_storage_value(
|
||||||
|
transaction,
|
||||||
|
binary_data.clone(),
|
||||||
|
db,
|
||||||
|
index_mutex.clone(),
|
||||||
|
miner.clone(),
|
||||||
|
block_header_number,
|
||||||
|
pending_effects,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
Ok(new_binary_data) => {
|
Ok(new_binary_data) => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::common::types::{STORAGE_KEY_INDEX_TREE, STORAGE_VALUE_ROLLBACK_TREE};
|
||||||
use crate::log::error;
|
use crate::log::error;
|
||||||
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::wallet_registry::{
|
use crate::records::wallet_registry::{
|
||||||
|
|
@ -41,6 +42,12 @@ pub enum PendingEffect {
|
||||||
key: Vec<u8>,
|
key: Vec<u8>,
|
||||||
value: Vec<u8>,
|
value: Vec<u8>,
|
||||||
},
|
},
|
||||||
|
DynamicTreeSet {
|
||||||
|
tree: Vec<u8>,
|
||||||
|
key: Vec<u8>,
|
||||||
|
value: Vec<u8>,
|
||||||
|
rollback_key: Vec<u8>,
|
||||||
|
},
|
||||||
TreeRemove {
|
TreeRemove {
|
||||||
tree: &'static str,
|
tree: &'static str,
|
||||||
key: Vec<u8>,
|
key: Vec<u8>,
|
||||||
|
|
@ -56,6 +63,9 @@ pub enum PendingEffect {
|
||||||
key: Vec<u8>,
|
key: Vec<u8>,
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
},
|
},
|
||||||
|
TreeCreate {
|
||||||
|
name: Vec<u8>,
|
||||||
|
},
|
||||||
TokenSupplyAdd {
|
TokenSupplyAdd {
|
||||||
ticker: String,
|
ticker: String,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
|
|
@ -108,6 +118,21 @@ impl PendingEffects {
|
||||||
self.push(PendingEffect::TreeSet { tree, key, value });
|
self.push(PendingEffect::TreeSet { tree, key, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_dynamic_tree(
|
||||||
|
&mut self,
|
||||||
|
tree: Vec<u8>,
|
||||||
|
key: Vec<u8>,
|
||||||
|
value: Vec<u8>,
|
||||||
|
rollback_key: Vec<u8>,
|
||||||
|
) {
|
||||||
|
self.push(PendingEffect::DynamicTreeSet {
|
||||||
|
tree,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
rollback_key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_tree(&mut self, tree: &'static str, key: Vec<u8>) {
|
pub fn remove_tree(&mut self, tree: &'static str, key: Vec<u8>) {
|
||||||
self.push(PendingEffect::TreeRemove { tree, key });
|
self.push(PendingEffect::TreeRemove { tree, key });
|
||||||
}
|
}
|
||||||
|
|
@ -131,6 +156,10 @@ impl PendingEffects {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_tree(&mut self, name: Vec<u8>) {
|
||||||
|
self.push(PendingEffect::TreeCreate { name });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_token_supply(&mut self, ticker: &str, amount: u64) {
|
pub fn add_token_supply(&mut self, ticker: &str, amount: u64) {
|
||||||
self.push(PendingEffect::TokenSupplyAdd {
|
self.push(PendingEffect::TokenSupplyAdd {
|
||||||
ticker: ticker.to_string(),
|
ticker: ticker.to_string(),
|
||||||
|
|
@ -230,6 +259,13 @@ enum AppliedEffect {
|
||||||
key: Vec<u8>,
|
key: Vec<u8>,
|
||||||
previous: Option<Vec<u8>>,
|
previous: Option<Vec<u8>>,
|
||||||
},
|
},
|
||||||
|
DynamicTreeMutation {
|
||||||
|
tree: Vec<u8>,
|
||||||
|
key: Vec<u8>,
|
||||||
|
previous: Option<Vec<u8>>,
|
||||||
|
rollback_key: Vec<u8>,
|
||||||
|
previous_rollback: Option<Vec<u8>>,
|
||||||
|
},
|
||||||
Composite(Vec<AppliedEffect>),
|
Composite(Vec<AppliedEffect>),
|
||||||
VanityUpdate {
|
VanityUpdate {
|
||||||
owner_address: String,
|
owner_address: String,
|
||||||
|
|
@ -237,6 +273,9 @@ enum AppliedEffect {
|
||||||
rollback_key: Vec<u8>,
|
rollback_key: Vec<u8>,
|
||||||
previous_rollback: Option<Vec<u8>>,
|
previous_rollback: Option<Vec<u8>>,
|
||||||
},
|
},
|
||||||
|
TreeCreated {
|
||||||
|
name: Vec<u8>,
|
||||||
|
},
|
||||||
Noop,
|
Noop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,6 +309,34 @@ fn apply_effect(db: &Db, effect: &PendingEffect) -> Result<AppliedEffect, String
|
||||||
previous,
|
previous,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
PendingEffect::DynamicTreeSet {
|
||||||
|
tree,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
rollback_key,
|
||||||
|
} => {
|
||||||
|
let previous = set_dynamic_tree_value(db, tree, key, value)?;
|
||||||
|
let rollback_tree = db
|
||||||
|
.open_tree(STORAGE_VALUE_ROLLBACK_TREE)
|
||||||
|
.map_err(|err| format!("Failed to open storage value rollback tree: {err}"))?;
|
||||||
|
let previous_rollback = rollback_tree
|
||||||
|
.get(rollback_key)
|
||||||
|
.map_err(|err| format!("Failed to read previous storage rollback value: {err}"))?
|
||||||
|
.map(|value| value.to_vec());
|
||||||
|
rollback_tree
|
||||||
|
.insert(
|
||||||
|
rollback_key,
|
||||||
|
encode_storage_rollback_value(previous.as_deref()),
|
||||||
|
)
|
||||||
|
.map_err(|err| format!("Failed to write storage rollback value: {err}"))?;
|
||||||
|
Ok(AppliedEffect::DynamicTreeMutation {
|
||||||
|
tree: tree.clone(),
|
||||||
|
key: key.clone(),
|
||||||
|
previous,
|
||||||
|
rollback_key: rollback_key.clone(),
|
||||||
|
previous_rollback,
|
||||||
|
})
|
||||||
|
}
|
||||||
PendingEffect::TreeRemove { tree, key } => {
|
PendingEffect::TreeRemove { tree, key } => {
|
||||||
let tree_handle = db
|
let tree_handle = db
|
||||||
.open_tree(tree)
|
.open_tree(tree)
|
||||||
|
|
@ -314,6 +381,27 @@ fn apply_effect(db: &Db, effect: &PendingEffect) -> Result<AppliedEffect, String
|
||||||
previous,
|
previous,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
PendingEffect::TreeCreate { name } => {
|
||||||
|
let index_tree = db
|
||||||
|
.open_tree(STORAGE_KEY_INDEX_TREE)
|
||||||
|
.map_err(|err| format!("Failed to open storage key index tree: {err}"))?;
|
||||||
|
if index_tree
|
||||||
|
.contains_key(name)
|
||||||
|
.map_err(|err| format!("Failed to check storage key index tree: {err}"))?
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"Storage tree already exists for key {}",
|
||||||
|
crate::encode(name)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
db.open_tree(name)
|
||||||
|
.map_err(|err| format!("Failed to create storage tree: {err}"))?;
|
||||||
|
if let Err(err) = index_tree.insert(name, b"1".as_slice()) {
|
||||||
|
let _ = db.drop_tree(name);
|
||||||
|
return Err(format!("Failed to index storage tree: {err}"));
|
||||||
|
}
|
||||||
|
Ok(AppliedEffect::TreeCreated { name: name.clone() })
|
||||||
|
}
|
||||||
PendingEffect::TokenSupplyAdd { ticker, amount } => {
|
PendingEffect::TokenSupplyAdd { ticker, amount } => {
|
||||||
let tree = db
|
let tree = db
|
||||||
.open_tree("tokens")
|
.open_tree("tokens")
|
||||||
|
|
@ -410,6 +498,21 @@ fn rollback_effect(db: &Db, effect: AppliedEffect) -> Result<(), String> {
|
||||||
key,
|
key,
|
||||||
previous,
|
previous,
|
||||||
} => restore_tree_value(db, tree, &key, previous),
|
} => restore_tree_value(db, tree, &key, previous),
|
||||||
|
AppliedEffect::DynamicTreeMutation {
|
||||||
|
tree,
|
||||||
|
key,
|
||||||
|
previous,
|
||||||
|
rollback_key,
|
||||||
|
previous_rollback,
|
||||||
|
} => {
|
||||||
|
restore_dynamic_tree_value(db, &tree, &key, previous)?;
|
||||||
|
restore_tree_value(
|
||||||
|
db,
|
||||||
|
STORAGE_VALUE_ROLLBACK_TREE,
|
||||||
|
&rollback_key,
|
||||||
|
previous_rollback,
|
||||||
|
)
|
||||||
|
}
|
||||||
AppliedEffect::Composite(mut effects) => {
|
AppliedEffect::Composite(mut effects) => {
|
||||||
let mut first_error = None;
|
let mut first_error = None;
|
||||||
while let Some(effect) = effects.pop() {
|
while let Some(effect) = effects.pop() {
|
||||||
|
|
@ -438,6 +541,17 @@ fn rollback_effect(db: &Db, effect: AppliedEffect) -> Result<(), String> {
|
||||||
previous_rollback,
|
previous_rollback,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
AppliedEffect::TreeCreated { name } => {
|
||||||
|
let index_tree = db.open_tree(STORAGE_KEY_INDEX_TREE).map_err(|err| {
|
||||||
|
format!("Failed to open storage key index tree during rollback: {err}")
|
||||||
|
})?;
|
||||||
|
index_tree.remove(&name).map_err(|err| {
|
||||||
|
format!("Failed to remove storage key index during rollback: {err}")
|
||||||
|
})?;
|
||||||
|
db.drop_tree(&name).map(|_| ()).map_err(|err| {
|
||||||
|
format!("Failed to drop created storage tree during rollback: {err}")
|
||||||
|
})
|
||||||
|
}
|
||||||
AppliedEffect::Noop => Ok(()),
|
AppliedEffect::Noop => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -461,6 +575,37 @@ fn set_tree_value(
|
||||||
Ok(previous)
|
Ok(previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_dynamic_tree_value(
|
||||||
|
db: &Db,
|
||||||
|
tree: &[u8],
|
||||||
|
key: &[u8],
|
||||||
|
value: &[u8],
|
||||||
|
) -> Result<Option<Vec<u8>>, String> {
|
||||||
|
let tree_handle = db
|
||||||
|
.open_tree(tree)
|
||||||
|
.map_err(|err| format!("Failed to open dynamic storage tree: {err}"))?;
|
||||||
|
let previous = tree_handle
|
||||||
|
.get(key)
|
||||||
|
.map_err(|err| format!("Failed to read previous dynamic storage value: {err}"))?
|
||||||
|
.map(|value| value.to_vec());
|
||||||
|
tree_handle
|
||||||
|
.insert(key, value)
|
||||||
|
.map_err(|err| format!("Failed to write dynamic storage value: {err}"))?;
|
||||||
|
Ok(previous)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_storage_rollback_value(previous: Option<&[u8]>) -> Vec<u8> {
|
||||||
|
match previous {
|
||||||
|
Some(value) => {
|
||||||
|
let mut encoded = Vec::with_capacity(value.len() + 1);
|
||||||
|
encoded.push(1);
|
||||||
|
encoded.extend_from_slice(value);
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
None => vec![0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn append_tree_value(
|
fn append_tree_value(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
tree: &'static str,
|
tree: &'static str,
|
||||||
|
|
@ -506,6 +651,30 @@ fn restore_tree_value(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn restore_dynamic_tree_value(
|
||||||
|
db: &Db,
|
||||||
|
tree: &[u8],
|
||||||
|
key: &[u8],
|
||||||
|
previous: Option<Vec<u8>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let tree_handle = db
|
||||||
|
.open_tree(tree)
|
||||||
|
.map_err(|err| format!("Failed to open dynamic storage tree during rollback: {err}"))?;
|
||||||
|
match previous {
|
||||||
|
Some(value) => {
|
||||||
|
tree_handle
|
||||||
|
.insert(key, value)
|
||||||
|
.map_err(|err| format!("Failed to restore dynamic storage value: {err}"))?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
tree_handle.remove(key).map_err(|err| {
|
||||||
|
format!("Failed to remove dynamic storage value during rollback: {err}")
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn read_supply(bytes: &[u8]) -> Result<u64, String> {
|
fn read_supply(bytes: &[u8]) -> Result<u64, String> {
|
||||||
if bytes.len() != 8 {
|
if bytes.len() != 8 {
|
||||||
return Err("Invalid token supply bytes".to_string());
|
return Err("Invalid token supply bytes".to_string());
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::decode;
|
||||||
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
|
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
||||||
|
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::Arc;
|
||||||
|
use crate::Mutex;
|
||||||
|
|
||||||
|
pub async fn process_storage_key(
|
||||||
|
transaction: &StorageKey,
|
||||||
|
mut binary_data: Vec<u8>,
|
||||||
|
_db: &Db,
|
||||||
|
index_counter: Arc<Mutex<&mut usize>>,
|
||||||
|
miner: String,
|
||||||
|
block_header_number: u32,
|
||||||
|
pending_effects: &mut PendingEffects,
|
||||||
|
) -> Result<Vec<u8>, String> {
|
||||||
|
let mut index = index_counter.lock().await;
|
||||||
|
**index += 1;
|
||||||
|
|
||||||
|
let txhash = transaction.unsigned_storagekey.hash().await;
|
||||||
|
let txhash_bytes =
|
||||||
|
decode(&txhash).map_err(|err| format!("Could not decode storage key txhash: {err}"))?;
|
||||||
|
let transaction_bytes = transaction
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage key transaction: {err}"))?;
|
||||||
|
binary_data.extend(transaction_bytes);
|
||||||
|
|
||||||
|
pending_effects.create_tree(txhash_bytes.clone());
|
||||||
|
pending_effects.add_balance(
|
||||||
|
&miner,
|
||||||
|
transaction.unsigned_storagekey.txfee,
|
||||||
|
&BASECOIN,
|
||||||
|
BalanceOperand::Addition,
|
||||||
|
);
|
||||||
|
pending_effects.add_balance(
|
||||||
|
&transaction.unsigned_storagekey.address,
|
||||||
|
transaction.unsigned_storagekey.txfee,
|
||||||
|
&BASECOIN,
|
||||||
|
BalanceOperand::Subtraction,
|
||||||
|
);
|
||||||
|
|
||||||
|
let txvalue = format!("{block_header_number}:{}", **index);
|
||||||
|
pending_effects.set_tree("txid", txhash_bytes.clone(), txvalue.into_bytes());
|
||||||
|
index_wallet_transaction(
|
||||||
|
pending_effects,
|
||||||
|
&[&transaction.unsigned_storagekey.address],
|
||||||
|
block_header_number,
|
||||||
|
**index as u32,
|
||||||
|
&txhash_bytes,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(binary_data)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
storage_record_key, storage_string_record_key, StorageValueTransaction,
|
||||||
|
};
|
||||||
|
use crate::common::types::Transaction;
|
||||||
|
use crate::decode;
|
||||||
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
|
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
||||||
|
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::Arc;
|
||||||
|
use crate::Mutex;
|
||||||
|
|
||||||
|
struct OwnedStorageParts {
|
||||||
|
storagekey: String,
|
||||||
|
key: String,
|
||||||
|
address: String,
|
||||||
|
txfee: u64,
|
||||||
|
hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn storage_transaction_bytes(transaction: &Transaction) -> Result<Vec<u8>, String> {
|
||||||
|
match transaction {
|
||||||
|
Transaction::StorageBool(tx) => tx
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage bool transaction: {err}")),
|
||||||
|
Transaction::StorageU8(tx) => tx
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage u8 transaction: {err}")),
|
||||||
|
Transaction::StorageU16(tx) => tx
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage u16 transaction: {err}")),
|
||||||
|
Transaction::StorageU32(tx) => tx
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage u32 transaction: {err}")),
|
||||||
|
Transaction::StorageU64(tx) => tx
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage u64 transaction: {err}")),
|
||||||
|
Transaction::StorageU128(tx) => tx
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage u128 transaction: {err}")),
|
||||||
|
Transaction::StorageString(tx) => tx
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Could not serialize storage string transaction: {err}")),
|
||||||
|
_ => Err("Unsupported storage value transaction.".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_parts(transaction: &Transaction) -> Result<OwnedStorageParts, String> {
|
||||||
|
let parts = match transaction {
|
||||||
|
Transaction::StorageBool(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU8(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU16(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU32(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU64(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageU128(tx) => tx.storage_parts(),
|
||||||
|
Transaction::StorageString(tx) => tx.storage_parts(),
|
||||||
|
_ => return Err("Unsupported storage value transaction.".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(OwnedStorageParts {
|
||||||
|
storagekey: parts.storagekey.to_string(),
|
||||||
|
key: parts.key.to_string(),
|
||||||
|
address: parts.address.to_string(),
|
||||||
|
txfee: parts.txfee,
|
||||||
|
hash: parts.hash,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_value_bytes(transaction: &Transaction) -> Result<Vec<u8>, String> {
|
||||||
|
match transaction {
|
||||||
|
Transaction::StorageBool(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU8(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU16(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU32(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU64(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageU128(tx) => tx.storage_value_bytes(),
|
||||||
|
Transaction::StorageString(tx) => tx.storage_value_bytes(),
|
||||||
|
_ => Err("Unsupported storage value transaction.".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_storage_value(
|
||||||
|
transaction: &Transaction,
|
||||||
|
mut binary_data: Vec<u8>,
|
||||||
|
db: &Db,
|
||||||
|
index_counter: Arc<Mutex<&mut usize>>,
|
||||||
|
miner: String,
|
||||||
|
block_header_number: u32,
|
||||||
|
pending_effects: &mut PendingEffects,
|
||||||
|
) -> Result<Vec<u8>, String> {
|
||||||
|
let mut index = index_counter.lock().await;
|
||||||
|
**index += 1;
|
||||||
|
|
||||||
|
let parts = storage_parts(transaction)?;
|
||||||
|
let storage_tree = decode(&parts.storagekey)
|
||||||
|
.map_err(|err| format!("Could not decode storage tree key: {err}"))?;
|
||||||
|
let txhash_bytes =
|
||||||
|
decode(&parts.hash).map_err(|err| format!("Could not decode storage txhash: {err}"))?;
|
||||||
|
|
||||||
|
let storage_record_key = match transaction {
|
||||||
|
Transaction::StorageString(tx) => storage_string_record_key(db, tx).await?,
|
||||||
|
_ => storage_record_key(&parts.address, &parts.key)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_bytes = storage_value_bytes(transaction)?;
|
||||||
|
let transaction_bytes = storage_transaction_bytes(transaction).await?;
|
||||||
|
binary_data.extend(transaction_bytes);
|
||||||
|
|
||||||
|
pending_effects.set_dynamic_tree(
|
||||||
|
storage_tree,
|
||||||
|
storage_record_key,
|
||||||
|
value_bytes,
|
||||||
|
txhash_bytes.clone(),
|
||||||
|
);
|
||||||
|
pending_effects.add_balance(&miner, parts.txfee, &BASECOIN, BalanceOperand::Addition);
|
||||||
|
pending_effects.add_balance(
|
||||||
|
&parts.address,
|
||||||
|
parts.txfee,
|
||||||
|
&BASECOIN,
|
||||||
|
BalanceOperand::Subtraction,
|
||||||
|
);
|
||||||
|
|
||||||
|
let txvalue = format!("{block_header_number}:{}", **index);
|
||||||
|
pending_effects.set_tree("txid", txhash_bytes.clone(), txvalue.into_bytes());
|
||||||
|
index_wallet_transaction(
|
||||||
|
pending_effects,
|
||||||
|
&[&parts.address],
|
||||||
|
block_header_number,
|
||||||
|
**index as u32,
|
||||||
|
&txhash_bytes,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(binary_data)
|
||||||
|
}
|
||||||
|
|
@ -8,14 +8,19 @@ use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::blocks::marketing::MarketingTransaction;
|
use crate::blocks::marketing::MarketingTransaction;
|
||||||
use crate::blocks::nft::CreateNftTransaction;
|
use crate::blocks::nft::CreateNftTransaction;
|
||||||
use crate::blocks::rewards::RewardsTransaction;
|
use crate::blocks::rewards::RewardsTransaction;
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
|
||||||
|
};
|
||||||
use crate::blocks::swap::SwapTransaction;
|
use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
use crate::blocks::vanity::VanityAddressTransaction;
|
use crate::blocks::vanity::VanityAddressTransaction;
|
||||||
use crate::common::types::{
|
use crate::common::types::{
|
||||||
Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
||||||
GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, SWAP_TYPE,
|
GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, STORAGE_BOOL_TYPE,
|
||||||
TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE, STORAGE_U32_TYPE,
|
||||||
|
STORAGE_U64_TYPE, STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||||
};
|
};
|
||||||
use crate::rpc::command_maps::get_bytes;
|
use crate::rpc::command_maps::get_bytes;
|
||||||
|
|
||||||
|
|
@ -192,6 +197,78 @@ pub async fn load_block_from_binary(binary_data: &[u8]) -> Result<Block, String>
|
||||||
i += body_len;
|
i += body_len;
|
||||||
vanity
|
vanity
|
||||||
}
|
}
|
||||||
|
STORAGE_KEY_TYPE => {
|
||||||
|
let storage_key = Transaction::StorageKey(
|
||||||
|
StorageKey::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage_key
|
||||||
|
}
|
||||||
|
STORAGE_BOOL_TYPE => {
|
||||||
|
let storage = Transaction::StorageBool(
|
||||||
|
StorageBool::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U8_TYPE => {
|
||||||
|
let storage = Transaction::StorageU8(
|
||||||
|
StorageU8::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U16_TYPE => {
|
||||||
|
let storage = Transaction::StorageU16(
|
||||||
|
StorageU16::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U32_TYPE => {
|
||||||
|
let storage = Transaction::StorageU32(
|
||||||
|
StorageU32::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U64_TYPE => {
|
||||||
|
let storage = Transaction::StorageU64(
|
||||||
|
StorageU64::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U128_TYPE => {
|
||||||
|
let storage = Transaction::StorageU128(
|
||||||
|
StorageU128::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_STRING_TYPE => {
|
||||||
|
let storage = Transaction::StorageString(
|
||||||
|
StorageString::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("Unsupported transaction type: {txtype}"));
|
return Err(format!("Unsupported transaction type: {txtype}"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,225 +1,302 @@
|
||||||
use crate::blocks::block::{Block, VrfBlock, VRF_BLOCK_BYTES};
|
use crate::blocks::block::{Block, VrfBlock, VRF_BLOCK_BYTES};
|
||||||
use crate::blocks::burn::BurnTransaction;
|
use crate::blocks::burn::BurnTransaction;
|
||||||
use crate::blocks::collateral::CollateralClaimTransaction;
|
use crate::blocks::collateral::CollateralClaimTransaction;
|
||||||
use crate::blocks::genesis::GenesisTransaction;
|
use crate::blocks::genesis::GenesisTransaction;
|
||||||
use crate::blocks::issue_token::IssueTokenTransaction;
|
use crate::blocks::issue_token::IssueTokenTransaction;
|
||||||
use crate::blocks::loan_payment::ContractPaymentTransaction;
|
use crate::blocks::loan_payment::ContractPaymentTransaction;
|
||||||
use crate::blocks::loans::LoanContractTransaction;
|
use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::blocks::marketing::MarketingTransaction;
|
use crate::blocks::marketing::MarketingTransaction;
|
||||||
use crate::blocks::nft::CreateNftTransaction;
|
use crate::blocks::nft::CreateNftTransaction;
|
||||||
use crate::blocks::rewards::RewardsTransaction;
|
use crate::blocks::rewards::RewardsTransaction;
|
||||||
use crate::blocks::swap::SwapTransaction;
|
use crate::blocks::storage_key::StorageKey;
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::storage_values::{
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
|
||||||
use crate::blocks::vanity::VanityAddressTransaction;
|
};
|
||||||
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::common::types::{
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, SWAP_TYPE,
|
use crate::blocks::vanity::VanityAddressTransaction;
|
||||||
TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||||||
};
|
use crate::common::types::{
|
||||||
use crate::fs;
|
Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
||||||
use crate::rpc::command_maps::get_bytes;
|
GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, STORAGE_BOOL_TYPE,
|
||||||
use crate::PathBuf;
|
STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE, STORAGE_U32_TYPE,
|
||||||
|
STORAGE_U64_TYPE, STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||||
// The transaction body helpers keep the block parser aligned with the command map sizes.
|
};
|
||||||
fn transaction_body_len(txtype: u8) -> Result<usize, String> {
|
use crate::fs;
|
||||||
let total_len = get_bytes(txtype);
|
use crate::rpc::command_maps::get_bytes;
|
||||||
if total_len <= 1 {
|
use crate::PathBuf;
|
||||||
return Err(format!("Unknown transaction type: {txtype}"));
|
|
||||||
}
|
// The transaction body helpers keep the block parser aligned with the command map sizes.
|
||||||
|
fn transaction_body_len(txtype: u8) -> Result<usize, String> {
|
||||||
// get_bytes includes the transaction type byte; parser bodies start after
|
let total_len = get_bytes(txtype);
|
||||||
// that byte has already been consumed.
|
if total_len <= 1 {
|
||||||
Ok(total_len - 1)
|
return Err(format!("Unknown transaction type: {txtype}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_body_slice(
|
// get_bytes includes the transaction type byte; parser bodies start after
|
||||||
binary_data: &[u8],
|
// that byte has already been consumed.
|
||||||
start: usize,
|
Ok(total_len - 1)
|
||||||
body_len: usize,
|
}
|
||||||
) -> Result<&[u8], String> {
|
|
||||||
// Slice with bounds checking so truncated block files fail cleanly instead
|
fn transaction_body_slice(
|
||||||
// of panicking during transaction parsing.
|
binary_data: &[u8],
|
||||||
binary_data
|
start: usize,
|
||||||
.get(start..start + body_len)
|
body_len: usize,
|
||||||
.ok_or_else(|| format!("Truncated transaction body at offset {start}"))
|
) -> Result<&[u8], String> {
|
||||||
}
|
// Slice with bounds checking so truncated block files fail cleanly instead
|
||||||
|
// of panicking during transaction parsing.
|
||||||
pub async fn load_block(block_number: u32) -> Result<Block, String> {
|
binary_data
|
||||||
// Blocks are loaded from disk by height, then split back into the header and
|
.get(start..start + body_len)
|
||||||
// variable-length transaction payloads.
|
.ok_or_else(|| format!("Truncated transaction body at offset {start}"))
|
||||||
let (
|
}
|
||||||
_network_name,
|
|
||||||
_padded_base_coin,
|
pub async fn load_block(block_number: u32) -> Result<Block, String> {
|
||||||
block_ext,
|
// Blocks are loaded from disk by height, then split back into the header and
|
||||||
_torrent_path,
|
// variable-length transaction payloads.
|
||||||
_wallet_path,
|
let (
|
||||||
block_path,
|
_network_name,
|
||||||
_db_path,
|
_padded_base_coin,
|
||||||
_balance_path,
|
block_ext,
|
||||||
_log_path,
|
_torrent_path,
|
||||||
) = block_extension_and_paths();
|
_wallet_path,
|
||||||
let file_name = PathBuf::from(block_path)
|
block_path,
|
||||||
.join(format!("{block_number}.{block_ext}"))
|
_db_path,
|
||||||
.to_string_lossy()
|
_balance_path,
|
||||||
.into_owned();
|
_log_path,
|
||||||
|
) = block_extension_and_paths();
|
||||||
// Load the full block because this path reconstructs both the header and
|
let file_name = PathBuf::from(block_path)
|
||||||
// every transaction for validation or inspection.
|
.join(format!("{block_number}.{block_ext}"))
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
|
||||||
|
// Load the full block because this path reconstructs both the header and
|
||||||
|
// every transaction for validation or inspection.
|
||||||
let binary_data = match fs::read(&file_name) {
|
let binary_data = match fs::read(&file_name) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(format!("Unable to read block {block_number}: {err:?}"));
|
return Err(format!("Unable to read block {block_number}: {err:?}"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if binary_data.len() < VRF_BLOCK_BYTES {
|
if binary_data.len() < VRF_BLOCK_BYTES {
|
||||||
return Err("Unable to load block: binary data shorter than VrfBlock header".to_string());
|
return Err("Unable to load block: binary data shorter than VrfBlock header".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let vrf_block = VrfBlock::from_bytes(&binary_data[0..VRF_BLOCK_BYTES])
|
let vrf_block = VrfBlock::from_bytes(&binary_data[0..VRF_BLOCK_BYTES])
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let mut i = VRF_BLOCK_BYTES;
|
let mut i = VRF_BLOCK_BYTES;
|
||||||
let mut transactions: Vec<Transaction> = Vec::new();
|
let mut transactions: Vec<Transaction> = Vec::new();
|
||||||
|
|
||||||
while i < binary_data.len() {
|
while i < binary_data.len() {
|
||||||
// Each stored transaction begins with its type byte, followed by the fixed-size
|
// Each stored transaction begins with its type byte, followed by the fixed-size
|
||||||
// body for that transaction family.
|
// body for that transaction family.
|
||||||
let txtype = binary_data[i];
|
let txtype = binary_data[i];
|
||||||
i += 1;
|
i += 1;
|
||||||
let body_len = transaction_body_len(txtype)?;
|
let body_len = transaction_body_len(txtype)?;
|
||||||
let body = transaction_body_slice(&binary_data, i, body_len)?;
|
let body = transaction_body_slice(&binary_data, i, body_len)?;
|
||||||
let transaction = match txtype {
|
let transaction = match txtype {
|
||||||
GENESIS_TYPE => {
|
GENESIS_TYPE => {
|
||||||
let genesis = Transaction::Genesis(
|
let genesis = Transaction::Genesis(
|
||||||
GenesisTransaction::from_bytes(txtype, body)
|
GenesisTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
genesis
|
genesis
|
||||||
}
|
}
|
||||||
REWARDS_TYPE => {
|
REWARDS_TYPE => {
|
||||||
let rewards = Transaction::Rewards(
|
let rewards = Transaction::Rewards(
|
||||||
RewardsTransaction::from_bytes(txtype, body)
|
RewardsTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
rewards
|
rewards
|
||||||
}
|
}
|
||||||
TRANSFER_TYPE => {
|
TRANSFER_TYPE => {
|
||||||
let transfer = Transaction::Transfer(
|
let transfer = Transaction::Transfer(
|
||||||
TransferTransaction::from_bytes(txtype, body)
|
TransferTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
transfer
|
transfer
|
||||||
}
|
}
|
||||||
BURN_TYPE => {
|
BURN_TYPE => {
|
||||||
let burn = Transaction::Burn(
|
let burn = Transaction::Burn(
|
||||||
BurnTransaction::from_bytes(txtype, body)
|
BurnTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
burn
|
burn
|
||||||
}
|
}
|
||||||
CREATE_TOKEN_TYPE => {
|
CREATE_TOKEN_TYPE => {
|
||||||
let create_token = Transaction::Token(
|
let create_token = Transaction::Token(
|
||||||
CreateTokenTransaction::from_bytes(txtype, body)
|
CreateTokenTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
create_token
|
create_token
|
||||||
}
|
}
|
||||||
ISSUE_TOKEN_TYPE => {
|
ISSUE_TOKEN_TYPE => {
|
||||||
let issue_token = Transaction::IssueToken(
|
let issue_token = Transaction::IssueToken(
|
||||||
IssueTokenTransaction::from_bytes(txtype, body)
|
IssueTokenTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
issue_token
|
issue_token
|
||||||
}
|
}
|
||||||
CREATE_NFT_TYPE => {
|
CREATE_NFT_TYPE => {
|
||||||
let create_nft = Transaction::Nft(
|
let create_nft = Transaction::Nft(
|
||||||
CreateNftTransaction::from_bytes(txtype, body)
|
CreateNftTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
create_nft
|
create_nft
|
||||||
}
|
}
|
||||||
MARKETING_TYPE => {
|
MARKETING_TYPE => {
|
||||||
let marketing = Transaction::Marketing(
|
let marketing = Transaction::Marketing(
|
||||||
MarketingTransaction::from_bytes(txtype, body)
|
MarketingTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
marketing
|
marketing
|
||||||
}
|
}
|
||||||
SWAP_TYPE => {
|
SWAP_TYPE => {
|
||||||
let swap = Transaction::Swap(
|
let swap = Transaction::Swap(
|
||||||
SwapTransaction::from_bytes(txtype, body)
|
SwapTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
swap
|
swap
|
||||||
}
|
}
|
||||||
LENDER_TYPE => {
|
LENDER_TYPE => {
|
||||||
let loan = Transaction::Lender(
|
let loan = Transaction::Lender(
|
||||||
LoanContractTransaction::from_bytes(txtype, body)
|
LoanContractTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
loan
|
loan
|
||||||
}
|
}
|
||||||
BORROWER_TYPE => {
|
BORROWER_TYPE => {
|
||||||
let payment = Transaction::Borrower(
|
let payment = Transaction::Borrower(
|
||||||
ContractPaymentTransaction::from_bytes(txtype, body)
|
ContractPaymentTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
payment
|
payment
|
||||||
}
|
}
|
||||||
COLLATERAL_TYPE => {
|
COLLATERAL_TYPE => {
|
||||||
let collateral = Transaction::Collateral(
|
let collateral = Transaction::Collateral(
|
||||||
CollateralClaimTransaction::from_bytes(txtype, body)
|
CollateralClaimTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
collateral
|
collateral
|
||||||
}
|
}
|
||||||
VANITY_ADDRESS_TYPE => {
|
VANITY_ADDRESS_TYPE => {
|
||||||
let vanity = Transaction::Vanity(
|
let vanity = Transaction::Vanity(
|
||||||
VanityAddressTransaction::from_bytes(txtype, body)
|
VanityAddressTransaction::from_bytes(txtype, body)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?,
|
||||||
);
|
);
|
||||||
i += body_len;
|
i += body_len;
|
||||||
vanity
|
vanity
|
||||||
}
|
}
|
||||||
_ => {
|
STORAGE_KEY_TYPE => {
|
||||||
return Err(format!("Unsupported transaction type: {txtype}"));
|
let storage_key = Transaction::StorageKey(
|
||||||
}
|
StorageKey::from_bytes(txtype, body)
|
||||||
};
|
.await
|
||||||
transactions.push(transaction);
|
.map_err(|e| e.to_string())?,
|
||||||
}
|
);
|
||||||
|
i += body_len;
|
||||||
let block = Block {
|
storage_key
|
||||||
vrf_block,
|
}
|
||||||
transactions,
|
STORAGE_BOOL_TYPE => {
|
||||||
};
|
let storage = Transaction::StorageBool(
|
||||||
|
StorageBool::from_bytes(txtype, body)
|
||||||
Ok(block)
|
.await
|
||||||
}
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U8_TYPE => {
|
||||||
|
let storage = Transaction::StorageU8(
|
||||||
|
StorageU8::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U16_TYPE => {
|
||||||
|
let storage = Transaction::StorageU16(
|
||||||
|
StorageU16::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U32_TYPE => {
|
||||||
|
let storage = Transaction::StorageU32(
|
||||||
|
StorageU32::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U64_TYPE => {
|
||||||
|
let storage = Transaction::StorageU64(
|
||||||
|
StorageU64::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_U128_TYPE => {
|
||||||
|
let storage = Transaction::StorageU128(
|
||||||
|
StorageU128::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
STORAGE_STRING_TYPE => {
|
||||||
|
let storage = Transaction::StorageString(
|
||||||
|
StorageString::from_bytes(txtype, body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
|
);
|
||||||
|
i += body_len;
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(format!("Unsupported transaction type: {txtype}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
transactions.push(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = Block {
|
||||||
|
vrf_block,
|
||||||
|
transactions,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(block)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::blocks::marketing::MarketingTransaction;
|
use crate::blocks::marketing::MarketingTransaction;
|
||||||
use crate::blocks::nft::CreateNftTransaction;
|
use crate::blocks::nft::CreateNftTransaction;
|
||||||
use crate::blocks::rewards::RewardsTransaction;
|
use crate::blocks::rewards::RewardsTransaction;
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
|
||||||
|
};
|
||||||
use crate::blocks::swap::SwapTransaction;
|
use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
|
|
@ -62,6 +66,8 @@ pub const RPC_TOKEN_CATALOG: u8 = 48;
|
||||||
pub const RPC_COLLATERAL_STATUS: u8 = 49;
|
pub const RPC_COLLATERAL_STATUS: u8 = 49;
|
||||||
pub const RPC_NETWORK_MONITOR_ADD: u8 = 50;
|
pub const RPC_NETWORK_MONITOR_ADD: u8 = 50;
|
||||||
pub const RPC_NETWORK_MONITOR_REMOVE: u8 = 51;
|
pub const RPC_NETWORK_MONITOR_REMOVE: u8 = 51;
|
||||||
|
pub const RPC_STORAGE_LOOKUP_COST: u8 = 52;
|
||||||
|
pub const RPC_STORAGE_LOOKUP: u8 = 53;
|
||||||
pub const RPC_REPLY: u8 = 255;
|
pub const RPC_REPLY: u8 = 255;
|
||||||
pub const MAX_RPC_REPLY_BYTES: usize = 64 * 1024 * 1024;
|
pub const MAX_RPC_REPLY_BYTES: usize = 64 * 1024 * 1024;
|
||||||
|
|
||||||
|
|
@ -85,6 +91,14 @@ pub fn get_bytes(tx_type: u8) -> usize {
|
||||||
10 => BurnTransaction::BYTE_LENGTH,
|
10 => BurnTransaction::BYTE_LENGTH,
|
||||||
11 => IssueTokenTransaction::BYTE_LENGTH,
|
11 => IssueTokenTransaction::BYTE_LENGTH,
|
||||||
12 => VanityAddressTransaction::BYTE_LENGTH,
|
12 => VanityAddressTransaction::BYTE_LENGTH,
|
||||||
|
100 => StorageKey::BYTE_LENGTH,
|
||||||
|
101 => StorageBool::BYTE_LENGTH,
|
||||||
|
102 => StorageU8::BYTE_LENGTH,
|
||||||
|
103 => StorageU16::BYTE_LENGTH,
|
||||||
|
104 => StorageU32::BYTE_LENGTH,
|
||||||
|
105 => StorageU64::BYTE_LENGTH,
|
||||||
|
106 => StorageU128::BYTE_LENGTH,
|
||||||
|
107 => StorageString::BYTE_LENGTH,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,14 @@ async fn miner_earnings_from_transactions(
|
||||||
}
|
}
|
||||||
Transaction::Collateral(tx) => earnings.add_fee(tx.unsigned_collateral_claim.txfee)?,
|
Transaction::Collateral(tx) => earnings.add_fee(tx.unsigned_collateral_claim.txfee)?,
|
||||||
Transaction::Vanity(tx) => earnings.add_fee(tx.unsigned_vanity_address.txfee)?,
|
Transaction::Vanity(tx) => earnings.add_fee(tx.unsigned_vanity_address.txfee)?,
|
||||||
|
Transaction::StorageKey(tx) => earnings.add_fee(tx.unsigned_storagekey.txfee)?,
|
||||||
|
Transaction::StorageBool(tx) => earnings.add_fee(tx.unsigned_storagebool.txfee)?,
|
||||||
|
Transaction::StorageU8(tx) => earnings.add_fee(tx.unsigned_storageu8.txfee)?,
|
||||||
|
Transaction::StorageU16(tx) => earnings.add_fee(tx.unsigned_storageu16.txfee)?,
|
||||||
|
Transaction::StorageU32(tx) => earnings.add_fee(tx.unsigned_storageu32.txfee)?,
|
||||||
|
Transaction::StorageU64(tx) => earnings.add_fee(tx.unsigned_storageu64.txfee)?,
|
||||||
|
Transaction::StorageU128(tx) => earnings.add_fee(tx.unsigned_storageu128.txfee)?,
|
||||||
|
Transaction::StorageString(tx) => earnings.add_fee(tx.unsigned_storage.txfee)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +154,7 @@ async fn miner_earnings_for_block(db: &Db, block_height: u32) -> Result<Vec<u8>,
|
||||||
fn signature_count(transaction_type: u8) -> usize {
|
fn signature_count(transaction_type: u8) -> usize {
|
||||||
match transaction_type {
|
match transaction_type {
|
||||||
6 | 7 => 2,
|
6 | 7 => 2,
|
||||||
2..=5 | 8..=12 => 1,
|
2..=5 | 8..=12 | 100..=107 => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ pub mod random_node;
|
||||||
pub mod receive_torrent;
|
pub mod receive_torrent;
|
||||||
pub mod request_valid_nodes;
|
pub mod request_valid_nodes;
|
||||||
pub mod route_reply;
|
pub mod route_reply;
|
||||||
|
pub mod storage_lookup;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod token_list;
|
pub mod token_list;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,372 @@
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
trim_storage_component, STORAGE_KEY_HASH_BYTES, STORAGE_STRING_CHUNK_LIMIT,
|
||||||
|
STORAGE_STRING_STORED_HASH_BYTES, STORAGE_VALUE_KEY_BYTES,
|
||||||
|
};
|
||||||
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
|
use crate::common::types::{STORAGE_KEY_INDEX_TREE, TRANSFER_TYPE};
|
||||||
|
use crate::config::SETTINGS;
|
||||||
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
|
use crate::records::memory::response_channels::Command;
|
||||||
|
use crate::rpc::commands::tx_submit::save_and_submit;
|
||||||
|
use crate::rpc::responses::RpcResponse;
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
use crate::{encode, json, to_string, Arc, Mutex, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct StorageLookupEntry {
|
||||||
|
key: String,
|
||||||
|
value_hex: String,
|
||||||
|
value_text: Option<String>,
|
||||||
|
bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct StorageLookupString {
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
chunks: usize,
|
||||||
|
bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct StorageLookupPayload {
|
||||||
|
storage_key: String,
|
||||||
|
data_key: String,
|
||||||
|
address: String,
|
||||||
|
records: Vec<StorageLookupEntry>,
|
||||||
|
strings: Vec<StorageLookupString>,
|
||||||
|
data_bytes: usize,
|
||||||
|
fee_per_byte: u64,
|
||||||
|
total_cost: u64,
|
||||||
|
node_payment_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_response<T: Serialize>(value: &T) -> RpcResponse {
|
||||||
|
match to_string(value) {
|
||||||
|
Ok(json) => RpcResponse::Binary(json.into_bytes()),
|
||||||
|
Err(err) => RpcResponse::Binary(format!(r#"{{"error":"{err}"}}"#).into_bytes()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(message: &str) -> RpcResponse {
|
||||||
|
json_response(&json!({ "error": message }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trimmed_key(data_key: &[u8]) -> String {
|
||||||
|
trim_storage_component(&String::from_utf8_lossy(data_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_record_prefix(address: &str) -> Result<Vec<u8>, String> {
|
||||||
|
Wallet::short_address_to_bytes(address)
|
||||||
|
.ok_or_else(|| "Invalid storage lookup address.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_record_key(address: &str, key: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
|
let mut record_key = storage_record_prefix(address)?;
|
||||||
|
record_key.extend_from_slice(key);
|
||||||
|
Ok(record_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_key(prefix_len: usize, key: &[u8]) -> String {
|
||||||
|
String::from_utf8_lossy(&key[prefix_len..])
|
||||||
|
.trim_end_matches(|ch| ch == '\0' || ch == ' ')
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_value(value: &[u8]) -> Option<String> {
|
||||||
|
let text = String::from_utf8(value.to_vec()).ok()?;
|
||||||
|
if text
|
||||||
|
.chars()
|
||||||
|
.any(|ch| ch.is_control() && ch != '\n' && ch != '\r' && ch != '\t')
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_chunk_text(value: &[u8]) -> String {
|
||||||
|
if value.len() <= STORAGE_STRING_STORED_HASH_BYTES * 2 {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
String::from_utf8_lossy(&value[STORAGE_STRING_STORED_HASH_BYTES * 2..]).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storage_key_exists(db: &Db, storage_key: &[u8]) -> Result<bool, String> {
|
||||||
|
if storage_key.len() != STORAGE_KEY_HASH_BYTES {
|
||||||
|
return Err("Storage key must be 32 bytes.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let index_tree = db
|
||||||
|
.open_tree(STORAGE_KEY_INDEX_TREE)
|
||||||
|
.map_err(|err| format!("Failed to open storage key index: {err}"))?;
|
||||||
|
index_tree
|
||||||
|
.contains_key(storage_key)
|
||||||
|
.map_err(|err| format!("Failed to check storage key index: {err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_exact_record(
|
||||||
|
db: &Db,
|
||||||
|
storage_key: &[u8],
|
||||||
|
address: &str,
|
||||||
|
data_key: &[u8],
|
||||||
|
) -> Result<Vec<StorageLookupEntry>, String> {
|
||||||
|
let storage_tree = db
|
||||||
|
.open_tree(storage_key)
|
||||||
|
.map_err(|err| format!("Failed to open storage tree: {err}"))?;
|
||||||
|
let record_key = storage_record_key(address, data_key)?;
|
||||||
|
let Some(value) = storage_tree
|
||||||
|
.get(&record_key)
|
||||||
|
.map_err(|err| format!("Failed to read storage value: {err}"))?
|
||||||
|
else {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(vec![StorageLookupEntry {
|
||||||
|
key: trimmed_key(data_key),
|
||||||
|
value_hex: encode(&value),
|
||||||
|
value_text: text_value(&value),
|
||||||
|
bytes: value.len(),
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_string_records(
|
||||||
|
db: &Db,
|
||||||
|
storage_key: &[u8],
|
||||||
|
address: &str,
|
||||||
|
base_key: &str,
|
||||||
|
) -> Result<(Vec<StorageLookupEntry>, Vec<StorageLookupString>), String> {
|
||||||
|
if base_key.is_empty() {
|
||||||
|
return Ok((Vec::new(), Vec::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let storage_tree = db
|
||||||
|
.open_tree(storage_key)
|
||||||
|
.map_err(|err| format!("Failed to open storage tree: {err}"))?;
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
let mut assembled = String::new();
|
||||||
|
let mut total_bytes = 0usize;
|
||||||
|
|
||||||
|
for index in 0..STORAGE_STRING_CHUNK_LIMIT {
|
||||||
|
let chunk_key = format!("{base_key}_{index:02}");
|
||||||
|
let record_key = storage_record_key(address, chunk_key.as_bytes())?;
|
||||||
|
let Some(value) = storage_tree
|
||||||
|
.get(&record_key)
|
||||||
|
.map_err(|err| format!("Failed to read storage string value: {err}"))?
|
||||||
|
else {
|
||||||
|
if index == 0 {
|
||||||
|
return Ok((Vec::new(), Vec::new()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
let chunk_text = string_chunk_text(&value);
|
||||||
|
assembled.push_str(&chunk_text);
|
||||||
|
total_bytes = total_bytes.saturating_add(value.len());
|
||||||
|
entries.push(StorageLookupEntry {
|
||||||
|
key: chunk_key,
|
||||||
|
value_hex: encode(&value),
|
||||||
|
value_text: Some(chunk_text),
|
||||||
|
bytes: value.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let strings = if entries.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
vec![StorageLookupString {
|
||||||
|
key: base_key.to_string(),
|
||||||
|
value: assembled,
|
||||||
|
chunks: entries.len(),
|
||||||
|
bytes: total_bytes,
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((entries, strings))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_all_records(
|
||||||
|
db: &Db,
|
||||||
|
storage_key: &[u8],
|
||||||
|
address: &str,
|
||||||
|
) -> Result<(Vec<StorageLookupEntry>, Vec<StorageLookupString>), String> {
|
||||||
|
let storage_tree = db
|
||||||
|
.open_tree(storage_key)
|
||||||
|
.map_err(|err| format!("Failed to open storage tree: {err}"))?;
|
||||||
|
let prefix = storage_record_prefix(address)?;
|
||||||
|
let prefix_len = prefix.len();
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
for item in storage_tree.scan_prefix(&prefix) {
|
||||||
|
let (key, value) = item.map_err(|err| format!("Failed to scan storage tree: {err}"))?;
|
||||||
|
entries.push(StorageLookupEntry {
|
||||||
|
key: display_key(prefix_len, key.as_ref()),
|
||||||
|
value_hex: encode(&value),
|
||||||
|
value_text: text_value(&value),
|
||||||
|
bytes: value.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut strings = Vec::new();
|
||||||
|
let mut grouped = std::collections::BTreeMap::<String, Vec<&StorageLookupEntry>>::new();
|
||||||
|
for entry in &entries {
|
||||||
|
if let Some(base_key) = entry.key.strip_suffix("_00") {
|
||||||
|
grouped.entry(base_key.to_string()).or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for entry in &entries {
|
||||||
|
let Some((base_key, suffix)) = entry.key.rsplit_once('_') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if suffix.len() == 2 && suffix.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
|
if let Some(chunks) = grouped.get_mut(base_key) {
|
||||||
|
chunks.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (base_key, mut chunks) in grouped {
|
||||||
|
chunks.sort_by(|left, right| left.key.cmp(&right.key));
|
||||||
|
let mut value = String::new();
|
||||||
|
let mut bytes = 0usize;
|
||||||
|
for chunk in &chunks {
|
||||||
|
bytes = bytes.saturating_add(chunk.bytes);
|
||||||
|
if let Ok(raw) = crate::decode(&chunk.value_hex) {
|
||||||
|
value.push_str(&string_chunk_text(&raw));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strings.push(StorageLookupString {
|
||||||
|
key: base_key,
|
||||||
|
value,
|
||||||
|
chunks: chunks.len(),
|
||||||
|
bytes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((entries, strings))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_payload(
|
||||||
|
db: &Db,
|
||||||
|
storage_key: Vec<u8>,
|
||||||
|
data_key: Vec<u8>,
|
||||||
|
address: String,
|
||||||
|
wallet: Arc<Wallet>,
|
||||||
|
) -> Result<StorageLookupPayload, String> {
|
||||||
|
if data_key.len() != STORAGE_VALUE_KEY_BYTES {
|
||||||
|
return Err("Storage data key must be 50 bytes.".to_string());
|
||||||
|
}
|
||||||
|
if !Wallet::short_address_validation(&address) {
|
||||||
|
return Err("Invalid lookup wallet address.".to_string());
|
||||||
|
}
|
||||||
|
if !storage_key_exists(db, &storage_key)? {
|
||||||
|
return Err("Storage key does not exist.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let data_key_text = trimmed_key(&data_key);
|
||||||
|
let (mut records, strings) = if data_key_text.eq_ignore_ascii_case("all") {
|
||||||
|
lookup_all_records(db, &storage_key, &address)?
|
||||||
|
} else {
|
||||||
|
let mut records = lookup_exact_record(db, &storage_key, &address, &data_key)?;
|
||||||
|
let (mut string_records, strings) =
|
||||||
|
lookup_string_records(db, &storage_key, &address, &data_key_text)?;
|
||||||
|
records.append(&mut string_records);
|
||||||
|
(records, strings)
|
||||||
|
};
|
||||||
|
|
||||||
|
records.sort_by(|left, right| left.key.cmp(&right.key));
|
||||||
|
let data_bytes = records.iter().map(|entry| entry.bytes).sum::<usize>();
|
||||||
|
let fee_per_byte = SETTINGS.storage_lookup_fee_per_byte;
|
||||||
|
let total_cost = (data_bytes as u64).saturating_mul(fee_per_byte);
|
||||||
|
|
||||||
|
Ok(StorageLookupPayload {
|
||||||
|
storage_key: encode(storage_key),
|
||||||
|
data_key: data_key_text,
|
||||||
|
address,
|
||||||
|
records,
|
||||||
|
strings,
|
||||||
|
data_bytes,
|
||||||
|
fee_per_byte,
|
||||||
|
total_cost,
|
||||||
|
node_payment_address: wallet.saved.short_address.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn lookup_cost(
|
||||||
|
db: &Db,
|
||||||
|
storage_key: Vec<u8>,
|
||||||
|
data_key: Vec<u8>,
|
||||||
|
address: String,
|
||||||
|
wallet: Arc<Wallet>,
|
||||||
|
) -> RpcResponse {
|
||||||
|
match lookup_payload(db, storage_key, data_key, address, wallet) {
|
||||||
|
Ok(payload) => json_response(&payload),
|
||||||
|
Err(err) => error_response(&err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn lookup_store(
|
||||||
|
db: &Db,
|
||||||
|
storage_key: Vec<u8>,
|
||||||
|
data_key: Vec<u8>,
|
||||||
|
address: String,
|
||||||
|
txtype: u8,
|
||||||
|
tx: Vec<u8>,
|
||||||
|
wallet: Arc<Wallet>,
|
||||||
|
map: Arc<Mutex<Command>>,
|
||||||
|
) -> RpcResponse {
|
||||||
|
let payload = match lookup_payload(db, storage_key, data_key, address, wallet.clone()) {
|
||||||
|
Ok(payload) => payload,
|
||||||
|
Err(err) => return error_response(&err),
|
||||||
|
};
|
||||||
|
|
||||||
|
if payload.total_cost == 0 {
|
||||||
|
return json_response(&json!({ "payment_required": false, "data": payload }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if txtype != TRANSFER_TYPE {
|
||||||
|
return error_response("Storage lookup payment must be a transfer transaction.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let transfer = match TransferTransaction::from_bytes(txtype, &tx).await {
|
||||||
|
Ok(transfer) => transfer,
|
||||||
|
Err(err) => return error_response(&format!("Invalid storage lookup payment: {err}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let unsigned = &transfer.unsigned_transfer;
|
||||||
|
let sender_is_node = unsigned.sender == wallet.saved.short_address;
|
||||||
|
if sender_is_node {
|
||||||
|
let hash = unsigned.hash().await;
|
||||||
|
if Wallet::verify_transaction_with_public_key(
|
||||||
|
&hash,
|
||||||
|
&transfer.signature,
|
||||||
|
&wallet.saved.public_key,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return json_response(&json!({ "payment_required": false, "data": payload }));
|
||||||
|
}
|
||||||
|
return error_response("Node wallet storage lookup signature is invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if unsigned.receiver != wallet.saved.short_address {
|
||||||
|
return error_response("Storage lookup payment receiver must be this node wallet.");
|
||||||
|
}
|
||||||
|
if !unsigned.coin.trim().eq_ignore_ascii_case(BASECOIN.trim()) {
|
||||||
|
return error_response("Storage lookup payment must use the base coin.");
|
||||||
|
}
|
||||||
|
if unsigned.value < payload.total_cost {
|
||||||
|
return error_response("Storage lookup payment value is below the required cost.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let submit_result = save_and_submit(txtype, tx, db, map).await;
|
||||||
|
let RpcResponse::Binary(bytes) = submit_result;
|
||||||
|
let message = String::from_utf8_lossy(&bytes);
|
||||||
|
if !message.contains("successful_broadcast: true") {
|
||||||
|
return error_response(&format!(
|
||||||
|
"Storage lookup payment was not accepted: {message}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_response(&json!({ "payment_required": true, "payment_accepted": true, "data": payload }))
|
||||||
|
}
|
||||||
|
|
@ -5,13 +5,19 @@ use crate::blocks::loan_payment::ContractPaymentTransaction;
|
||||||
use crate::blocks::loans::LoanContractTransaction;
|
use crate::blocks::loans::LoanContractTransaction;
|
||||||
use crate::blocks::marketing::MarketingTransaction;
|
use crate::blocks::marketing::MarketingTransaction;
|
||||||
use crate::blocks::nft::CreateNftTransaction;
|
use crate::blocks::nft::CreateNftTransaction;
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
|
||||||
|
};
|
||||||
use crate::blocks::swap::SwapTransaction;
|
use crate::blocks::swap::SwapTransaction;
|
||||||
use crate::blocks::token::CreateTokenTransaction;
|
use crate::blocks::token::CreateTokenTransaction;
|
||||||
use crate::blocks::transfer::TransferTransaction;
|
use crate::blocks::transfer::TransferTransaction;
|
||||||
use crate::blocks::vanity::VanityAddressTransaction;
|
use crate::blocks::vanity::VanityAddressTransaction;
|
||||||
use crate::common::types::{
|
use crate::common::types::{
|
||||||
BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
|
||||||
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, STORAGE_BOOL_TYPE, STORAGE_KEY_TYPE,
|
||||||
|
STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE,
|
||||||
|
STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
|
||||||
};
|
};
|
||||||
use crate::records::memory::connections::get_client_type_from_memory;
|
use crate::records::memory::connections::get_client_type_from_memory;
|
||||||
use crate::records::memory::enums::ClientType;
|
use crate::records::memory::enums::ClientType;
|
||||||
|
|
@ -59,6 +65,50 @@ async fn broadcast_tx(tx_bytes: Vec<u8>, map: Arc<Mutex<Command>>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! submit_storage_value {
|
||||||
|
($tx_type:ty, $txtype:expr, $tx:expr, $db:expr, $map:expr) => {
|
||||||
|
match <$tx_type>::from_bytes($txtype, &$tx).await {
|
||||||
|
Ok(storage_value) => match storage_value.verify($db).await {
|
||||||
|
Ok(existing) => {
|
||||||
|
if !existing.is_empty() {
|
||||||
|
let msg = "successful_broadcast: false already_in_mempool"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
match storage_value.to_bytes().await {
|
||||||
|
Ok(storage_value_bytes) => match storage_value.add_to_memory().await {
|
||||||
|
Ok(_) => {
|
||||||
|
broadcast_tx(storage_value_bytes, $map.clone()).await;
|
||||||
|
let msg =
|
||||||
|
"successful_broadcast: true".to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn save_and_submit(
|
pub async fn save_and_submit(
|
||||||
txtype: u8,
|
txtype: u8,
|
||||||
tx: Vec<u8>,
|
tx: Vec<u8>,
|
||||||
|
|
@ -507,6 +557,52 @@ pub async fn save_and_submit(
|
||||||
RpcResponse::Binary(msg)
|
RpcResponse::Binary(msg)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
STORAGE_KEY_TYPE => match StorageKey::from_bytes(txtype, &tx).await {
|
||||||
|
Ok(storage_key) => match storage_key.verify(db).await {
|
||||||
|
Ok(existing) => {
|
||||||
|
if !existing.is_empty() {
|
||||||
|
let msg = "successful_broadcast: false already_in_mempool"
|
||||||
|
.to_string()
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
return RpcResponse::Binary(msg);
|
||||||
|
}
|
||||||
|
match storage_key.to_bytes().await {
|
||||||
|
Ok(storage_key_bytes) => match storage_key.add_to_memory().await {
|
||||||
|
Ok(_) => {
|
||||||
|
broadcast_tx(storage_key_bytes, map.clone()).await;
|
||||||
|
let msg =
|
||||||
|
"successful_broadcast: true".to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
let msg = format!("error: {err}").to_string().as_bytes().to_vec();
|
||||||
|
RpcResponse::Binary(msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
STORAGE_BOOL_TYPE => submit_storage_value!(StorageBool, txtype, tx, db, map),
|
||||||
|
STORAGE_U8_TYPE => submit_storage_value!(StorageU8, txtype, tx, db, map),
|
||||||
|
STORAGE_U16_TYPE => submit_storage_value!(StorageU16, txtype, tx, db, map),
|
||||||
|
STORAGE_U32_TYPE => submit_storage_value!(StorageU32, txtype, tx, db, map),
|
||||||
|
STORAGE_U64_TYPE => submit_storage_value!(StorageU64, txtype, tx, db, map),
|
||||||
|
STORAGE_U128_TYPE => submit_storage_value!(StorageU128, txtype, tx, db, map),
|
||||||
|
STORAGE_STRING_TYPE => submit_storage_value!(StorageString, txtype, tx, db, map),
|
||||||
_ => {
|
_ => {
|
||||||
let msg = "error: No such transaction type"
|
let msg = "error: No such transaction type"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|
|
||||||
|
|
@ -1005,6 +1005,104 @@ pub async fn start_loop(
|
||||||
.send(&stream_locked, Some(&connections_key), uid)
|
.send(&stream_locked, Some(&connections_key), uid)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
52 => {
|
||||||
|
// request byte count and node fee for storage data lookup
|
||||||
|
let (uid, _) = read_bytes_from_stream::read_uid_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let storage_key = read_bytes_from_stream::read_usize_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
32,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let data_key = read_bytes_from_stream::read_usize_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
50,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let address = read_bytes_from_stream::read_short_address_string_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let result = commands::storage_lookup::lookup_cost(
|
||||||
|
&db,
|
||||||
|
storage_key,
|
||||||
|
data_key,
|
||||||
|
address,
|
||||||
|
wallet.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
result
|
||||||
|
.send(&stream_locked, Some(&connections_key), uid)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
53 => {
|
||||||
|
// paid storage data lookup
|
||||||
|
let (uid, _) = read_bytes_from_stream::read_uid_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let storage_key = read_bytes_from_stream::read_usize_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
32,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let data_key = read_bytes_from_stream::read_usize_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
50,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let address = read_bytes_from_stream::read_short_address_string_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let txtype = read_bytes_from_stream::read_u8_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let txsize = command_maps::get_bytes(txtype);
|
||||||
|
if txsize == 0 {
|
||||||
|
let result = responses::RpcResponse::Binary(
|
||||||
|
b"{\"error\":\"No such transaction type\"}".to_vec(),
|
||||||
|
);
|
||||||
|
result
|
||||||
|
.send(&stream_locked, Some(&connections_key), uid)
|
||||||
|
.await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let tx = read_bytes_from_stream::read_usize_from_stream(
|
||||||
|
&connections_key,
|
||||||
|
txsize - 1,
|
||||||
|
stream_locked.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let result = commands::storage_lookup::lookup_store(
|
||||||
|
&db,
|
||||||
|
storage_key,
|
||||||
|
data_key,
|
||||||
|
address,
|
||||||
|
txtype,
|
||||||
|
tx,
|
||||||
|
wallet.clone(),
|
||||||
|
map.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
result
|
||||||
|
.send(&stream_locked, Some(&connections_key), uid)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
255 => {
|
255 => {
|
||||||
commands::route_reply::route_reply(
|
commands::route_reply::route_reply(
|
||||||
&connections_key,
|
&connections_key,
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,20 @@ use crate::blocks::loan_payment::{ContractPaymentTransaction, UnsignedContractPa
|
||||||
use crate::blocks::loans::{LoanContractTransaction, UnsignedLoanContractTransaction};
|
use crate::blocks::loans::{LoanContractTransaction, UnsignedLoanContractTransaction};
|
||||||
use crate::blocks::marketing::{MarketingTransaction, UnsignedMarketingTransaction};
|
use crate::blocks::marketing::{MarketingTransaction, UnsignedMarketingTransaction};
|
||||||
use crate::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction};
|
use crate::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction};
|
||||||
|
use crate::blocks::storage_key::{StorageKey, UnsignedStorageKey};
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
|
||||||
|
UnsignedStorageBool, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16,
|
||||||
|
UnsignedStorageU32, UnsignedStorageU64, UnsignedStorageU8,
|
||||||
|
};
|
||||||
use crate::blocks::swap::{SwapTransaction, UnsignedSwapTransaction};
|
use crate::blocks::swap::{SwapTransaction, UnsignedSwapTransaction};
|
||||||
use crate::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction};
|
use crate::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction};
|
||||||
use crate::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction};
|
use crate::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction};
|
||||||
use crate::blocks::vanity::{UnsignedVanityAddressTransaction, VanityAddressTransaction};
|
use crate::blocks::vanity::{UnsignedVanityAddressTransaction, VanityAddressTransaction};
|
||||||
|
use crate::common::types::{
|
||||||
|
STORAGE_BOOL_TYPE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE,
|
||||||
|
STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE,
|
||||||
|
};
|
||||||
use crate::records::memory::response_channels::Byte3;
|
use crate::records::memory::response_channels::Byte3;
|
||||||
use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION;
|
use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION;
|
||||||
use crate::{from_str, Value};
|
use crate::{from_str, Value};
|
||||||
|
|
@ -22,6 +32,76 @@ fn read_u32_field(transaction: &Value, primary: &str, fallback: &str) -> Option<
|
||||||
.map(|value| value as u32)
|
.map(|value| value as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn storage_payload<'a>(transaction: &'a Value, field: &str) -> &'a Value {
|
||||||
|
transaction.get(field).unwrap_or(transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_transaction_type(transaction: &Value) -> Option<u8> {
|
||||||
|
transaction
|
||||||
|
.get("txtype")
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storagekey")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storagebool")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storageu8")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storageu16")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storageu32")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storageu64")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storageu128")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
transaction
|
||||||
|
.get("unsigned_storage")
|
||||||
|
.and_then(|value| value.get("txtype"))
|
||||||
|
.and_then(|value| value.as_u64())
|
||||||
|
})
|
||||||
|
.map(|value| value as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u128_field(transaction: &Value, field: &str) -> Option<u128> {
|
||||||
|
let value = transaction.get(field)?;
|
||||||
|
if let Some(value) = value.as_u64() {
|
||||||
|
return Some(value as u128);
|
||||||
|
}
|
||||||
|
if let Some(value) = value.as_str() {
|
||||||
|
return value.trim().parse::<u128>().ok();
|
||||||
|
}
|
||||||
|
value.to_string().parse::<u128>().ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn create_transaction_request(
|
pub async fn create_transaction_request(
|
||||||
command_input: String,
|
command_input: String,
|
||||||
hashkey: Byte3,
|
hashkey: Byte3,
|
||||||
|
|
@ -44,7 +124,7 @@ pub async fn create_transaction_request(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_transaction_bytes(transaction: &Value) -> Result<Vec<u8>, String> {
|
async fn create_transaction_bytes(transaction: &Value) -> Result<Vec<u8>, String> {
|
||||||
let txtype = transaction["txtype"].as_u64().ok_or("Missing txtype.")? as u8;
|
let txtype = read_transaction_type(transaction).ok_or("Missing txtype.")?;
|
||||||
|
|
||||||
// Each transaction type is rebuilt through the same unsigned -> signed -> bytes
|
// Each transaction type is rebuilt through the same unsigned -> signed -> bytes
|
||||||
// flow used elsewhere so the standalone tool stays aligned with node serialization.
|
// flow used elsewhere so the standalone tool stays aligned with node serialization.
|
||||||
|
|
@ -451,6 +531,276 @@ async fn create_transaction_bytes(transaction: &Value) -> Result<Vec<u8>, String
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to serialize vanity transaction: {e}"))?
|
.map_err(|e| format!("Failed to serialize vanity transaction: {e}"))?
|
||||||
}
|
}
|
||||||
|
STORAGE_KEY_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storagekey");
|
||||||
|
let unsigned_storagekey = UnsignedStorageKey::new(
|
||||||
|
txtype,
|
||||||
|
read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key creator wallet address.")?,
|
||||||
|
payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
StorageKey::load(
|
||||||
|
unsigned_storagekey,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-key transaction: {e}"))?
|
||||||
|
}
|
||||||
|
STORAGE_BOOL_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storagebool");
|
||||||
|
let unsigned_storagebool = UnsignedStorageBool {
|
||||||
|
txtype,
|
||||||
|
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
storagekey: payload["storagekey"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key hash.")?
|
||||||
|
.to_string(),
|
||||||
|
key: payload["key"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage field key.")?
|
||||||
|
.to_string(),
|
||||||
|
value: payload["value"].as_bool().ok_or("Missing bool value.")?,
|
||||||
|
address: payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage writer wallet address.")?
|
||||||
|
.to_string(),
|
||||||
|
txfee: payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageBool::load(
|
||||||
|
unsigned_storagebool,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-bool transaction: {e}"))?
|
||||||
|
}
|
||||||
|
STORAGE_U8_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storageu8");
|
||||||
|
let unsigned_storageu8 = UnsignedStorageU8 {
|
||||||
|
txtype,
|
||||||
|
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
storagekey: payload["storagekey"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key hash.")?
|
||||||
|
.to_string(),
|
||||||
|
key: payload["key"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage field key.")?
|
||||||
|
.to_string(),
|
||||||
|
value: payload["value"].as_u64().ok_or("Missing u8 value.")? as u8,
|
||||||
|
address: payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage writer wallet address.")?
|
||||||
|
.to_string(),
|
||||||
|
txfee: payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageU8::load(
|
||||||
|
unsigned_storageu8,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-u8 transaction: {e}"))?
|
||||||
|
}
|
||||||
|
STORAGE_U16_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storageu16");
|
||||||
|
let unsigned_storageu16 = UnsignedStorageU16 {
|
||||||
|
txtype,
|
||||||
|
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
storagekey: payload["storagekey"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key hash.")?
|
||||||
|
.to_string(),
|
||||||
|
key: payload["key"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage field key.")?
|
||||||
|
.to_string(),
|
||||||
|
value: payload["value"].as_u64().ok_or("Missing u16 value.")? as u16,
|
||||||
|
address: payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage writer wallet address.")?
|
||||||
|
.to_string(),
|
||||||
|
txfee: payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageU16::load(
|
||||||
|
unsigned_storageu16,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-u16 transaction: {e}"))?
|
||||||
|
}
|
||||||
|
STORAGE_U32_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storageu32");
|
||||||
|
let unsigned_storageu32 = UnsignedStorageU32 {
|
||||||
|
txtype,
|
||||||
|
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
storagekey: payload["storagekey"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key hash.")?
|
||||||
|
.to_string(),
|
||||||
|
key: payload["key"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage field key.")?
|
||||||
|
.to_string(),
|
||||||
|
value: payload["value"].as_u64().ok_or("Missing u32 value.")? as u32,
|
||||||
|
address: payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage writer wallet address.")?
|
||||||
|
.to_string(),
|
||||||
|
txfee: payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageU32::load(
|
||||||
|
unsigned_storageu32,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-u32 transaction: {e}"))?
|
||||||
|
}
|
||||||
|
STORAGE_U64_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storageu64");
|
||||||
|
let unsigned_storageu64 = UnsignedStorageU64 {
|
||||||
|
txtype,
|
||||||
|
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
storagekey: payload["storagekey"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key hash.")?
|
||||||
|
.to_string(),
|
||||||
|
key: payload["key"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage field key.")?
|
||||||
|
.to_string(),
|
||||||
|
value: payload["value"].as_u64().ok_or("Missing u64 value.")?,
|
||||||
|
address: payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage writer wallet address.")?
|
||||||
|
.to_string(),
|
||||||
|
txfee: payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageU64::load(
|
||||||
|
unsigned_storageu64,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-u64 transaction: {e}"))?
|
||||||
|
}
|
||||||
|
STORAGE_U128_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storageu128");
|
||||||
|
let unsigned_storageu128 = UnsignedStorageU128 {
|
||||||
|
txtype,
|
||||||
|
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
storagekey: payload["storagekey"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key hash.")?
|
||||||
|
.to_string(),
|
||||||
|
key: payload["key"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage field key.")?
|
||||||
|
.to_string(),
|
||||||
|
value: read_u128_field(payload, "value").ok_or("Missing u128 value.")?,
|
||||||
|
address: payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage writer wallet address.")?
|
||||||
|
.to_string(),
|
||||||
|
txfee: payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageU128::load(
|
||||||
|
unsigned_storageu128,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-u128 transaction: {e}"))?
|
||||||
|
}
|
||||||
|
STORAGE_STRING_TYPE => {
|
||||||
|
let payload = storage_payload(transaction, "unsigned_storage");
|
||||||
|
let unsigned_storage = UnsignedStorageString {
|
||||||
|
txtype,
|
||||||
|
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
|
||||||
|
storagekey: payload["storagekey"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage key hash.")?
|
||||||
|
.to_string(),
|
||||||
|
key: payload["key"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage field key.")?
|
||||||
|
.to_string(),
|
||||||
|
value: payload["value"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing string value.")?
|
||||||
|
.to_string(),
|
||||||
|
address: payload["address"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing storage writer wallet address.")?
|
||||||
|
.to_string(),
|
||||||
|
previous: payload["previous"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing previous string hash.")?
|
||||||
|
.to_string(),
|
||||||
|
txfee: payload["txfee"]
|
||||||
|
.as_u64()
|
||||||
|
.ok_or("Missing or invalid txfee.")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
StorageString::load(
|
||||||
|
unsigned_storage,
|
||||||
|
transaction["signature"]
|
||||||
|
.as_str()
|
||||||
|
.ok_or("Missing transaction signature.")?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to serialize storage-string transaction: {e}"))?
|
||||||
|
}
|
||||||
_ => return Err("No such transaction type".to_string()),
|
_ => return Err("No such transaction type".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,10 +90,21 @@ async fn attempt_bootstrap_connections(
|
||||||
Err(err) => err.to_string(),
|
Err(err) => err.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// A peer can reject us because it already has this connection recorded.
|
// During outage recovery, a peer may already have the connection
|
||||||
// In that case retrying other bootstrap peers would not fix the local duplicate state.
|
// recorded from an overlapping reconnect path. That is not a fatal
|
||||||
if err_string.contains("The connection is already in the connection manager Please wait 10 minutes and try again") {
|
// bootstrap-recovery condition; try the next configured peer.
|
||||||
return Err(err_string);
|
if err_string.contains(
|
||||||
|
"The connection is already in the connection manager Please wait 10 minutes and try again",
|
||||||
|
) {
|
||||||
|
if context == "startup" {
|
||||||
|
error!("Error connecting to {server}: {err_string}");
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"[reconnect] bootstrap recovery skipped duplicate peer {server}: {err_string}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
last_error = Some(err_string.clone());
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if context == "startup" {
|
if context == "startup" {
|
||||||
error!("Error connecting to {server}: {err_string}");
|
error!("Error connecting to {server}: {err_string}");
|
||||||
|
|
|
||||||
|
|
@ -439,6 +439,136 @@ async fn transaction_balance_view(
|
||||||
check_saved_txid: true,
|
check_saved_txid: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Transaction::StorageKey(tx) => {
|
||||||
|
let storage_key = &tx.unsigned_storagekey;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage_key.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage_key.txfee,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage_key.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Transaction::StorageBool(tx) => {
|
||||||
|
let storage = &tx.unsigned_storagebool;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage.txfee,
|
||||||
|
);
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Transaction::StorageU8(tx) => {
|
||||||
|
let storage = &tx.unsigned_storageu8;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage.txfee,
|
||||||
|
);
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Transaction::StorageU16(tx) => {
|
||||||
|
let storage = &tx.unsigned_storageu16;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage.txfee,
|
||||||
|
);
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Transaction::StorageU32(tx) => {
|
||||||
|
let storage = &tx.unsigned_storageu32;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage.txfee,
|
||||||
|
);
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Transaction::StorageU64(tx) => {
|
||||||
|
let storage = &tx.unsigned_storageu64;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage.txfee,
|
||||||
|
);
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Transaction::StorageU128(tx) => {
|
||||||
|
let storage = &tx.unsigned_storageu128;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage.txfee,
|
||||||
|
);
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Transaction::StorageString(tx) => {
|
||||||
|
let storage = &tx.unsigned_storage;
|
||||||
|
let mut debits = Vec::new();
|
||||||
|
add_debit(
|
||||||
|
&mut debits,
|
||||||
|
&storage.address,
|
||||||
|
BASECOIN.clone(),
|
||||||
|
storage.txfee,
|
||||||
|
);
|
||||||
|
Ok(TransactionBalanceView {
|
||||||
|
hash: storage.hash().await,
|
||||||
|
signatures: vec![tx.signature.clone()],
|
||||||
|
debits,
|
||||||
|
check_saved_txid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ pub mod verify_issue_token;
|
||||||
pub mod verify_lender;
|
pub mod verify_lender;
|
||||||
pub mod verify_marketing;
|
pub mod verify_marketing;
|
||||||
pub mod verify_rewards;
|
pub mod verify_rewards;
|
||||||
|
pub mod verify_storage_key;
|
||||||
|
pub mod verify_storage_values;
|
||||||
pub mod verify_swap;
|
pub mod verify_swap;
|
||||||
pub mod verify_transfer;
|
pub mod verify_transfer;
|
||||||
pub mod verify_vanity;
|
pub mod verify_vanity;
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,110 @@ async fn verify_transaction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Transaction::StorageKey(storage_key_tx) = &transaction {
|
||||||
|
match storage_key_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage key transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Transaction::StorageBool(storage_tx) = &transaction {
|
||||||
|
match storage_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage bool transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Transaction::StorageU8(storage_tx) = &transaction {
|
||||||
|
match storage_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage u8 transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Transaction::StorageU16(storage_tx) = &transaction {
|
||||||
|
match storage_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage u16 transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Transaction::StorageU32(storage_tx) = &transaction {
|
||||||
|
match storage_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage u32 transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Transaction::StorageU64(storage_tx) = &transaction {
|
||||||
|
match storage_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage u64 transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Transaction::StorageU128(storage_tx) = &transaction {
|
||||||
|
match storage_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage u128 transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Transaction::StorageString(storage_tx) = &transaction {
|
||||||
|
match storage_tx.verify(db).await {
|
||||||
|
Ok(value) => {
|
||||||
|
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Validation failed for storage string transaction: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(
|
Err(
|
||||||
"Validation failed: unsupported transaction variant reached verification dispatch."
|
"Validation failed: unsupported transaction variant reached verification dispatch."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
use crate::blocks::storage_key::StorageKey;
|
||||||
|
use crate::common::types::{STORAGE_KEY_FEE, STORAGE_KEY_TYPE};
|
||||||
|
use crate::encode;
|
||||||
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
|
use crate::records::wallet_registry::{
|
||||||
|
require_canonical_registered_short_address, resolve_pubkey_from_short_address,
|
||||||
|
};
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
|
||||||
|
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
|
||||||
|
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
|
impl StorageKey {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
// Storage key transactions create a new app-scoped sled tree.
|
||||||
|
// The transaction hash becomes the tree name, so the unsigned
|
||||||
|
// payload must use the storage-key transaction type.
|
||||||
|
let storage_key = &self.unsigned_storagekey;
|
||||||
|
if storage_key.txtype != STORAGE_KEY_TYPE {
|
||||||
|
return Err("Storage key transaction type is invalid.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transactions already present in the mempool can short-circuit
|
||||||
|
// deeper verification and return their stored signature.
|
||||||
|
let hash = storage_key.hash().await;
|
||||||
|
if memcheck(&self.signature, &hash).await {
|
||||||
|
return Ok(self.signature.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only canonical registered short addresses can create storage
|
||||||
|
// keys. Vanity aliases are resolved by callers before they reach
|
||||||
|
// this fixed transaction format.
|
||||||
|
let address = &storage_key.address;
|
||||||
|
if !Wallet::short_address_validation(address) {
|
||||||
|
return Err("Storage key wallet address is invalid.".to_string());
|
||||||
|
}
|
||||||
|
require_canonical_registered_short_address(db, address, "Storage key wallet address")?;
|
||||||
|
|
||||||
|
// The registered public key for the creating wallet is the
|
||||||
|
// authority for the storage-key signature.
|
||||||
|
let sender_pubkey = resolve_pubkey_from_short_address(db, address)
|
||||||
|
.map_err(|_| "Storage key wallet address is not registered.".to_string())?
|
||||||
|
.ok_or_else(|| "Storage key wallet address is not registered.".to_string())?;
|
||||||
|
let sender_pubkey_hex = encode(&sender_pubkey);
|
||||||
|
if !Wallet::verify_transaction_with_public_key(&hash, &self.signature, &sender_pubkey_hex)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err("Invalid signature for the Storage Key Transaction.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage keys are intentionally expensive because they create a
|
||||||
|
// new app namespace in the chain's local record store.
|
||||||
|
let txfee = storage_key.txfee;
|
||||||
|
if txfee < STORAGE_KEY_FEE {
|
||||||
|
return Err(format!(
|
||||||
|
"Storage key transaction fee is below the minimum required fee of {STORAGE_KEY_FEE}."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating the storage namespace is fee-only, so the sender needs
|
||||||
|
// enough base coin to cover the storage-key fee.
|
||||||
|
if !balance_checkup(db, 0, txfee, BASECOIN.clone(), address).await {
|
||||||
|
return Err("Insuficient funds for this Storage Key Transaction!".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saved-chain duplicates are rejected by txid even if the mempool
|
||||||
|
// did not already contain the transaction.
|
||||||
|
if !db_hex_verification(db, "txid", &hash).await {
|
||||||
|
return Err("This transaction already exists.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verification returns no auxiliary cleanup marker for this
|
||||||
|
// transaction type.
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
use crate::blocks::storage_values::{
|
||||||
|
storage_string_record_key, StorageBool, StorageString, StorageU128, StorageU16, StorageU32,
|
||||||
|
StorageU64, StorageU8, StorageValueTransaction, STORAGE_KEY_HASH_BYTES,
|
||||||
|
STORAGE_STRING_VALUE_BYTES, STORAGE_VALUE_KEY_BYTES,
|
||||||
|
};
|
||||||
|
use crate::common::types::{
|
||||||
|
STORAGE_BOOL_TYPE, STORAGE_KEY_INDEX_TREE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
|
||||||
|
STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE, STORAGE_VALUE_FEE,
|
||||||
|
};
|
||||||
|
use crate::decode;
|
||||||
|
use crate::encode;
|
||||||
|
use crate::records::memory::mempool::BASECOIN;
|
||||||
|
use crate::records::wallet_registry::{
|
||||||
|
require_canonical_registered_short_address, resolve_pubkey_from_short_address,
|
||||||
|
};
|
||||||
|
use crate::sled::Db;
|
||||||
|
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
|
||||||
|
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
|
||||||
|
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
|
||||||
|
use crate::wallets::structures::Wallet;
|
||||||
|
|
||||||
|
async fn verify_storage_value<T: StorageValueTransaction>(
|
||||||
|
tx: &T,
|
||||||
|
db: &Db,
|
||||||
|
expected_type: u8,
|
||||||
|
label: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
// All concrete storage-value transactions share these fields:
|
||||||
|
// transaction type, storage namespace, field key, wallet address,
|
||||||
|
// fee, hash, and signature.
|
||||||
|
let parts = tx.storage_parts();
|
||||||
|
|
||||||
|
// Match the rest of transaction verification: if this exact signed
|
||||||
|
// transaction is already in the mempool, return before doing deeper
|
||||||
|
// database checks.
|
||||||
|
if memcheck(parts.signature, &parts.hash).await {
|
||||||
|
return Ok(parts.signature.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The shared verifier is called by each concrete storage type, so
|
||||||
|
// confirm the embedded transaction type matches that caller.
|
||||||
|
if parts.txtype != expected_type {
|
||||||
|
return Err(format!("{label} transaction type is invalid."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The storage key is the 32-byte hash of a confirmed StorageKey
|
||||||
|
// transaction. It is decoded here before checking the storage-key index.
|
||||||
|
let storagekey_bytes = decode(parts.storagekey)
|
||||||
|
.map_err(|err| format!("{label} storage key hash is invalid: {err}"))?;
|
||||||
|
if storagekey_bytes.len() != STORAGE_KEY_HASH_BYTES {
|
||||||
|
return Err(format!("{label} storage key hash length is invalid."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage field keys are fixed-width so all value transactions have a
|
||||||
|
// deterministic byte layout. String storage may later map this logical
|
||||||
|
// key to key_00 through key_19 in sled.
|
||||||
|
if parts.key.as_bytes().len() != STORAGE_VALUE_KEY_BYTES {
|
||||||
|
return Err(format!("{label} storage field key length is invalid."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage values may only write into a namespace created by a prior
|
||||||
|
// StorageKey transaction. Checking the index avoids accidentally
|
||||||
|
// creating a dynamic sled tree during verification.
|
||||||
|
let storage_index = db
|
||||||
|
.open_tree(STORAGE_KEY_INDEX_TREE)
|
||||||
|
.map_err(|err| format!("Could not open storage key index: {err}"))?;
|
||||||
|
if !storage_index
|
||||||
|
.contains_key(&storagekey_bytes)
|
||||||
|
.map_err(|err| format!("Could not check storage key index: {err}"))?
|
||||||
|
{
|
||||||
|
return Err(format!("{label} storage key does not exist."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage writes are owned by the wallet address in the transaction,
|
||||||
|
// and must use the canonical registered short address form.
|
||||||
|
if !Wallet::short_address_validation(parts.address) {
|
||||||
|
return Err(format!("{label} wallet address is invalid."));
|
||||||
|
}
|
||||||
|
require_canonical_registered_short_address(db, parts.address, "Storage value wallet address")?;
|
||||||
|
|
||||||
|
// The registered public key for the writing wallet must verify the
|
||||||
|
// signature over the exact unsigned storage-value payload.
|
||||||
|
let sender_pubkey = resolve_pubkey_from_short_address(db, parts.address)
|
||||||
|
.map_err(|_| format!("{label} wallet address is not registered."))?
|
||||||
|
.ok_or_else(|| format!("{label} wallet address is not registered."))?;
|
||||||
|
let sender_pubkey_hex = encode(&sender_pubkey);
|
||||||
|
if !Wallet::verify_transaction_with_public_key(&parts.hash, parts.signature, &sender_pubkey_hex)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err(format!("Invalid signature for the {label} Transaction."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every storage value write pays the fixed storage-value fee. The fee is
|
||||||
|
// independent of the stored type so bool/u8/string all share one rule.
|
||||||
|
if parts.txfee < STORAGE_VALUE_FEE {
|
||||||
|
return Err(format!(
|
||||||
|
"{label} transaction fee is below the minimum required fee of {STORAGE_VALUE_FEE}."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage writes do not transfer an asset value, but the sender must
|
||||||
|
// have enough base coin to cover the storage-value fee.
|
||||||
|
if !balance_checkup(db, 0, parts.txfee, BASECOIN.clone(), parts.address).await {
|
||||||
|
return Err(format!("Insuficient funds for this {label} Transaction!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saved-chain duplicates are rejected by txid even if the mempool did
|
||||||
|
// not already contain the transaction.
|
||||||
|
if !db_hex_verification(db, "txid", &parts.hash).await {
|
||||||
|
return Err("This transaction already exists.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verification returns no auxiliary cleanup marker for these
|
||||||
|
// transaction types.
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageBool {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
verify_storage_value(self, db, STORAGE_BOOL_TYPE, "Storage Bool").await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageU8 {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
verify_storage_value(self, db, STORAGE_U8_TYPE, "Storage U8").await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageU16 {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
verify_storage_value(self, db, STORAGE_U16_TYPE, "Storage U16").await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageU32 {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
verify_storage_value(self, db, STORAGE_U32_TYPE, "Storage U32").await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageU64 {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
verify_storage_value(self, db, STORAGE_U64_TYPE, "Storage U64").await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageU128 {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
verify_storage_value(self, db, STORAGE_U128_TYPE, "Storage U128").await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageString {
|
||||||
|
pub async fn verify(&self, db: &Db) -> Result<String, String> {
|
||||||
|
// String storage has extra chain-linking rules before it can use
|
||||||
|
// the common storage-value verifier.
|
||||||
|
let parts = self.storage_parts();
|
||||||
|
|
||||||
|
// Existing mempool entries can still short-circuit before walking
|
||||||
|
// the previous-hash string chain.
|
||||||
|
if memcheck(parts.signature, &parts.hash).await {
|
||||||
|
return Ok(parts.signature.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The on-chain string chunk is fixed at 180 bytes. Shorter text is
|
||||||
|
// padded by the creator and trimmed only when saved into sled.
|
||||||
|
if self.unsigned_storage.value.as_bytes().len() != STORAGE_STRING_VALUE_BYTES {
|
||||||
|
return Err("Storage String value length is invalid.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A zero previous hash starts a string at key_00. Any non-zero
|
||||||
|
// previous hash must point to an existing StorageString transaction.
|
||||||
|
let previous_bytes = decode(&self.unsigned_storage.previous)
|
||||||
|
.map_err(|err| format!("Storage String previous hash is invalid: {err}"))?;
|
||||||
|
if previous_bytes.len() != STORAGE_KEY_HASH_BYTES {
|
||||||
|
return Err("Storage String previous hash length is invalid.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-zero previous links must already exist on-chain so a string
|
||||||
|
// chain cannot point to missing history.
|
||||||
|
if previous_bytes.iter().any(|byte| *byte != 0)
|
||||||
|
&& db_hex_verification(db, "txid", &self.unsigned_storage.previous).await
|
||||||
|
{
|
||||||
|
return Err("Storage String previous transaction does not exist.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This validates the previous chain belongs to the same storage
|
||||||
|
// namespace, same wallet, and same logical key, then derives the
|
||||||
|
// concrete sled key such as key_00, key_01, ... key_19.
|
||||||
|
storage_string_record_key(db, self).await?;
|
||||||
|
|
||||||
|
// After string-specific checks pass, reuse the same ownership,
|
||||||
|
// signature, fee, balance, and duplicate checks as other values.
|
||||||
|
verify_storage_value(self, db, STORAGE_STRING_TYPE, "Storage String").await
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue