From 800729bad7f4674eb8a7dd08c0d056a3b8314b46 Mon Sep 17 00:00:00 2001 From: viraladmin <00purple@gmail.com> Date: Wed, 15 Jul 2026 18:18:13 -0600 Subject: [PATCH] added singed storage types --- src/bin/data_storage_tx.rs | 103 +++++++- src/bin/delete_storage_tx.rs | 154 +++++++++++ src/blocks/block.rs | 24 ++ src/blocks/mod.rs | 1 + src/common/types.rs | 16 +- src/orphans/undo_block_transactions.rs | 17 +- src/orphans/undo_transactions/mod.rs | 1 + .../undo_transactions/restore_mempool.rs | 36 +++ .../undo_transactions/undo_delete_key.rs | 68 +++++ .../undo_transactions/undo_storage_value.rs | 5 + src/records/memory/mempool/selection.rs | 89 +++++-- src/records/record_chain/delete_key_tx.rs | 60 +++++ src/records/record_chain/mod.rs | 1 + .../record_chain/parse_transactions.rs | 19 +- src/records/record_chain/pending_effects.rs | 111 ++++++++ src/records/record_chain/storage_value_tx.rs | 30 +++ .../unpack_block/load_by_binary_data.rs | 66 ++++- .../unpack_block/load_by_block_number.rs | 66 ++++- src/rpc/command_maps.rs | 10 +- src/rpc/commands/address_history.rs | 8 +- src/rpc/commands/tx_submit.rs | 15 +- src/standalone_tools/transaction_creator.rs | 249 +++++++++++++++++- .../async_funcs/checks/block_balance.rs | 91 +++++++ src/verifications/async_funcs/mod.rs | 1 + src/verifications/async_funcs/transactions.rs | 78 ++++++ .../async_funcs/verify_delete_key.rs | 98 +++++++ .../async_funcs/verify_storage_values.rs | 40 ++- 27 files changed, 1407 insertions(+), 50 deletions(-) create mode 100644 src/bin/delete_storage_tx.rs create mode 100644 src/orphans/undo_transactions/undo_delete_key.rs create mode 100644 src/records/record_chain/delete_key_tx.rs create mode 100644 src/verifications/async_funcs/verify_delete_key.rs diff --git a/src/bin/data_storage_tx.rs b/src/bin/data_storage_tx.rs index c264361..7da8c7b 100644 --- a/src/bin/data_storage_tx.rs +++ b/src/bin/data_storage_tx.rs @@ -1,7 +1,9 @@ use contractless::blocks::storage_key::{StorageKey, UnsignedStorageKey}; use contractless::blocks::storage_values::{ - StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, - UnsignedStorageBool, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16, + StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString, + StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, UnsignedStorageBool, + UnsignedStorageI128, UnsignedStorageI16, UnsignedStorageI32, UnsignedStorageI64, + UnsignedStorageI8, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16, UnsignedStorageU32, UnsignedStorageU64, UnsignedStorageU8, STORAGE_KEY_HASH_BYTES, STORAGE_STRING_VALUE_BYTES, STORAGE_VALUE_KEY_BYTES, }; @@ -9,7 +11,8 @@ use contractless::common::cli_prompts::{ prompt_hidden_nonempty, prompt_visible, prompt_visible_with_default, prompt_wallet_path, }; use contractless::common::types::{ - STORAGE_BOOL_TYPE, STORAGE_KEY_FEE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, + STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE, + STORAGE_I8_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 contractless::wallets::structures::Wallet; @@ -29,6 +32,11 @@ fn usage() { println!(" ./data_storage_tx u32|32bit [txfee]"); println!(" ./data_storage_tx u64|64bit [txfee]"); println!(" ./data_storage_tx u128|128bit [txfee]"); + println!(" ./data_storage_tx i8 [txfee]"); + println!(" ./data_storage_tx i16 [txfee]"); + println!(" ./data_storage_tx i32 [txfee]"); + println!(" ./data_storage_tx i64 [txfee]"); + println!(" ./data_storage_tx i128 [txfee]"); println!( " ./data_storage_tx string [previous_hash|0] [txfee]" ); @@ -105,6 +113,11 @@ fn normalize_storage_type(input: &str) -> Option<&'static str> { "u32" | "32bit" | "32" => Some("u32"), "u64" | "64bit" | "64" => Some("u64"), "u128" | "128bit" | "128" => Some("u128"), + "i8" | "signed8" | "i8bit" => Some("i8"), + "i16" | "signed16" | "i16bit" => Some("i16"), + "i32" | "signed32" | "i32bit" => Some("i32"), + "i64" | "signed64" | "i64bit" => Some("i64"), + "i128" | "signed128" | "i128bit" => Some("i128"), "string" | "str" | "text" => Some("string"), _ => None, } @@ -275,12 +288,17 @@ macro_rules! create_numeric_storage { } }; let hash = transaction.$unsigned_field.hash().await; + let output_value = if $label.ends_with("128") { + serde_json::Value::String(transaction.$unsigned_field.value.to_string()) + } else { + json!(transaction.$unsigned_field.value) + }; let output = json!({ "txtype": $txtype, "timestamp": $timestamp, "storagekey": transaction.$unsigned_field.storagekey, "key": transaction.$unsigned_field.key, - "value": transaction.$unsigned_field.value, + "value": output_value, "address": address, "txfee": txfee, "hash": hash, @@ -453,7 +471,7 @@ async fn main() { args[1].clone() } else { prompt_visible( - "Please enter storage transaction type (create_storage, bool, u8, u16, u32, u64, u128, string): ", + "Please enter storage transaction type (create_storage, bool, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, string): ", ) .await }; @@ -556,6 +574,81 @@ async fn main() { "u128" ) } + "i8" => { + create_numeric_storage!( + args, + timestamp, + wallet, + storagekey, + key, + i8, + UnsignedStorageI8, + StorageI8, + unsigned_storagei8, + STORAGE_I8_TYPE, + "i8" + ) + } + "i16" => { + create_numeric_storage!( + args, + timestamp, + wallet, + storagekey, + key, + i16, + UnsignedStorageI16, + StorageI16, + unsigned_storagei16, + STORAGE_I16_TYPE, + "i16" + ) + } + "i32" => { + create_numeric_storage!( + args, + timestamp, + wallet, + storagekey, + key, + i32, + UnsignedStorageI32, + StorageI32, + unsigned_storagei32, + STORAGE_I32_TYPE, + "i32" + ) + } + "i64" => { + create_numeric_storage!( + args, + timestamp, + wallet, + storagekey, + key, + i64, + UnsignedStorageI64, + StorageI64, + unsigned_storagei64, + STORAGE_I64_TYPE, + "i64" + ) + } + "i128" => { + create_numeric_storage!( + args, + timestamp, + wallet, + storagekey, + key, + i128, + UnsignedStorageI128, + StorageI128, + unsigned_storagei128, + STORAGE_I128_TYPE, + "i128" + ) + } "string" => create_string_storage(&args, timestamp, &wallet, storagekey, key).await, _ => usage(), } diff --git a/src/bin/delete_storage_tx.rs b/src/bin/delete_storage_tx.rs new file mode 100644 index 0000000..4b6375c --- /dev/null +++ b/src/bin/delete_storage_tx.rs @@ -0,0 +1,154 @@ +use contractless::blocks::delete_key::{DeleteKey, UnsignedDeleteKey}; +use contractless::blocks::storage_values::{STORAGE_KEY_HASH_BYTES, STORAGE_VALUE_KEY_BYTES}; +use contractless::common::cli_prompts::{ + prompt_hidden_nonempty, prompt_visible, prompt_visible_with_default, prompt_wallet_path, +}; +use contractless::common::types::{DELETE_KEY_TYPE, STORAGE_VALUE_FEE}; +use contractless::wallets::structures::Wallet; +use contractless::{create_dir_all, decode, env, json, AsyncWriteExt, File, Utc}; + +fn display_fee(value: u64) -> f64 { + value as f64 / 100_000_000.0 +} + +fn parse_fee(value: &str) -> Option { + let parsed = value.trim().parse::().ok()?; + if parsed.is_sign_negative() { + return None; + } + Some((parsed * 100_000_000.0).round() as u64) +} + +fn valid_storage_key_hash(value: &str) -> bool { + matches!(decode(value.trim()), Ok(bytes) if bytes.len() == STORAGE_KEY_HASH_BYTES) +} + +fn fixed_field_key(input: &str) -> Option { + let trimmed = input.trim(); + if trimmed.is_empty() || trimmed.as_bytes().len() > STORAGE_VALUE_KEY_BYTES { + return None; + } + Some(format!("{trimmed: Result<(), String> { + let output = serde_json::to_string_pretty(output) + .map_err(|err| format!("Failed to format transaction JSON: {err}"))?; + create_dir_all("./transactions") + .await + .map_err(|err| format!("Failed to create transactions directory: {err}"))?; + let path = format!("./transactions/{hash}.json"); + let mut file = File::create(&path) + .await + .map_err(|err| format!("Failed to create transaction file: {err}"))?; + file.write_all(output.as_bytes()) + .await + .map_err(|err| format!("Failed to write transaction file: {err}"))?; + println!("{output}"); + Ok(()) +} + +#[tokio::main] +async fn main() { + let args: Vec = env::args().collect(); + let storagekey = if args.len() > 1 { + args[1].trim().to_string() + } else { + prompt_visible("Please enter the storage key hash (32-byte / 64-character hex): ") + .await + .trim() + .to_string() + }; + if !valid_storage_key_hash(&storagekey) { + eprintln!("Storage key hash must be a 32-byte / 64-character hex string."); + return; + } + + let key_input = if args.len() > 2 { + args[2].clone() + } else { + prompt_visible( + "Please enter the data key to delete (a base key deletes its scalar value and _00 through _19 chunks): ", + ) + .await + }; + let Some(key) = fixed_field_key(&key_input) else { + eprintln!("Data key must be 1 to 50 bytes."); + return; + }; + + let txfee = if args.len() > 3 { + match parse_fee(&args[3]) { + Some(fee) => fee, + None => { + eprintln!("Please enter a valid transaction fee."); + return; + } + } + } else { + let default = format!("{:.8}", display_fee(STORAGE_VALUE_FEE)); + loop { + let value = + prompt_visible_with_default("Please enter the delete transaction fee", &default) + .await; + if let Some(fee) = parse_fee(&value) { + break fee; + } + println!("Please enter a valid transaction fee."); + } + }; + if txfee < STORAGE_VALUE_FEE { + eprintln!( + "Delete transaction fee must be at least {:.8}.", + display_fee(STORAGE_VALUE_FEE) + ); + return; + } + + let wallet_path = prompt_wallet_path().await; + let wallet_key = prompt_hidden_nonempty( + "What is your wallet decryption key? ", + "Wallet key cannot be empty. Please try again.", + ) + .await; + let wallet = match Wallet::try_obtain_wallet(wallet_key, Some(&wallet_path)).await { + Ok(wallet) => wallet, + Err(err) => { + eprintln!("Wallet decryption failed: {err}"); + return; + } + }; + + let address = wallet.saved.short_address.trim().to_string(); + let timestamp = Utc::now().timestamp() as u32; + let unsigned = UnsignedDeleteKey { + txtype: DELETE_KEY_TYPE, + time: timestamp, + storagekey, + key, + address: address.clone(), + txfee, + }; + let transaction = match DeleteKey::new(unsigned.clone(), &wallet.saved.private_key).await { + Ok(transaction) => transaction, + Err(err) => { + eprintln!("Failed to sign delete-key transaction: {err}"); + return; + } + }; + let hash = unsigned.hash().await; + let output = json!({ + "txtype": DELETE_KEY_TYPE, + "timestamp": timestamp, + "storagekey": &unsigned.storagekey, + "key": &unsigned.key, + "address": address, + "txfee": txfee, + "hash": hash, + "unsigned_deletekey": unsigned, + "signature": transaction.signature, + }); + if let Err(err) = write_transaction(&hash, &output).await { + eprintln!("{err}"); + } +} diff --git a/src/blocks/block.rs b/src/blocks/block.rs index 2e67a57..3513bbc 100644 --- a/src/blocks/block.rs +++ b/src/blocks/block.rs @@ -375,6 +375,30 @@ impl Block { let tx_bytes = storage_tx.to_bytes().await?; buffer.extend_from_slice(&tx_bytes); } + Transaction::StorageI8(storage_tx) => { + let tx_bytes = storage_tx.to_bytes().await?; + buffer.extend_from_slice(&tx_bytes); + } + Transaction::StorageI16(storage_tx) => { + let tx_bytes = storage_tx.to_bytes().await?; + buffer.extend_from_slice(&tx_bytes); + } + Transaction::StorageI32(storage_tx) => { + let tx_bytes = storage_tx.to_bytes().await?; + buffer.extend_from_slice(&tx_bytes); + } + Transaction::StorageI64(storage_tx) => { + let tx_bytes = storage_tx.to_bytes().await?; + buffer.extend_from_slice(&tx_bytes); + } + Transaction::StorageI128(storage_tx) => { + let tx_bytes = storage_tx.to_bytes().await?; + buffer.extend_from_slice(&tx_bytes); + } + Transaction::DeleteKey(delete_key_tx) => { + let tx_bytes = delete_key_tx.to_bytes().await?; + buffer.extend_from_slice(&tx_bytes); + } } } Ok(buffer) diff --git a/src/blocks/mod.rs b/src/blocks/mod.rs index 9197851..c2c65f0 100644 --- a/src/blocks/mod.rs +++ b/src/blocks/mod.rs @@ -1,6 +1,7 @@ pub mod block; pub mod burn; pub mod collateral; +pub mod delete_key; pub mod genesis; pub mod issue_token; pub mod loan_payment; diff --git a/src/common/types.rs b/src/common/types.rs index d55f056..8e09f83 100644 --- a/src/common/types.rs +++ b/src/common/types.rs @@ -1,5 +1,6 @@ use crate::blocks::burn::BurnTransaction; use crate::blocks::collateral::CollateralClaimTransaction; +use crate::blocks::delete_key::DeleteKey; use crate::blocks::genesis::GenesisTransaction; use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::loan_payment::ContractPaymentTransaction; @@ -9,7 +10,8 @@ use crate::blocks::nft::CreateNftTransaction; use crate::blocks::rewards::RewardsTransaction; use crate::blocks::storage_key::StorageKey; use crate::blocks::storage_values::{ - StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, + StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString, + StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, }; use crate::blocks::swap::SwapTransaction; use crate::blocks::token::CreateTokenTransaction; @@ -40,6 +42,12 @@ 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; +pub const STORAGE_I8_TYPE: u8 = 108; +pub const STORAGE_I16_TYPE: u8 = 109; +pub const STORAGE_I32_TYPE: u8 = 110; +pub const STORAGE_I64_TYPE: u8 = 111; +pub const STORAGE_I128_TYPE: u8 = 112; +pub const DELETE_KEY_TYPE: u8 = 113; // Coin and asset names are stored in fixed 15-byte fields. pub const COIN_LENGTH: usize = 15; @@ -116,4 +124,10 @@ pub enum Transaction { StorageU64(StorageU64), StorageU128(StorageU128), StorageString(StorageString), + StorageI8(StorageI8), + StorageI16(StorageI16), + StorageI32(StorageI32), + StorageI64(StorageI64), + StorageI128(StorageI128), + DeleteKey(DeleteKey), } diff --git a/src/orphans/undo_block_transactions.rs b/src/orphans/undo_block_transactions.rs index b12f122..350b5ee 100644 --- a/src/orphans/undo_block_transactions.rs +++ b/src/orphans/undo_block_transactions.rs @@ -9,6 +9,7 @@ use crate::orphans::undo_transactions::undo_burn::undo_burn_transaction; use crate::orphans::undo_transactions::undo_collateral::undo_collateral_transaction; use crate::orphans::undo_transactions::undo_create_nft::undo_create_nft_transaction; use crate::orphans::undo_transactions::undo_create_token::undo_create_token_transaction; +use crate::orphans::undo_transactions::undo_delete_key::undo_delete_key_transaction; use crate::orphans::undo_transactions::undo_issue_token::undo_issue_token_transaction; use crate::orphans::undo_transactions::undo_loan_creation::undo_loan_creation_transaction; use crate::orphans::undo_transactions::undo_marketing::undo_marketing_transaction; @@ -147,7 +148,12 @@ pub async fn undo_transactions( | Transaction::StorageU32(_) | Transaction::StorageU64(_) | Transaction::StorageU128(_) - | Transaction::StorageString(_) => { + | Transaction::StorageString(_) + | Transaction::StorageI8(_) + | Transaction::StorageI16(_) + | Transaction::StorageI32(_) + | Transaction::StorageI64(_) + | Transaction::StorageI128(_) => { undo_storage_value_transaction( transaction.clone(), &mining_receiver, @@ -156,6 +162,15 @@ pub async fn undo_transactions( .await?; rolled_back_transactions.push(transaction); } + Transaction::DeleteKey(delete_key_tx) => { + undo_delete_key_transaction( + delete_key_tx.clone(), + &mining_receiver, + ¶ms.db, + ) + .await?; + rolled_back_transactions.push(Transaction::DeleteKey(delete_key_tx)); + } } } // The block miner loses one mined-block count when this block is removed. diff --git a/src/orphans/undo_transactions/mod.rs b/src/orphans/undo_transactions/mod.rs index aa60578..db60591 100644 --- a/src/orphans/undo_transactions/mod.rs +++ b/src/orphans/undo_transactions/mod.rs @@ -4,6 +4,7 @@ pub mod undo_burn; pub mod undo_collateral; pub mod undo_create_nft; pub mod undo_create_token; +pub mod undo_delete_key; pub mod undo_issue_token; pub mod undo_loan_creation; pub mod undo_marketing; diff --git a/src/orphans/undo_transactions/restore_mempool.rs b/src/orphans/undo_transactions/restore_mempool.rs index 2a17fac..0cfeca7 100644 --- a/src/orphans/undo_transactions/restore_mempool.rs +++ b/src/orphans/undo_transactions/restore_mempool.rs @@ -1,5 +1,6 @@ use crate::blocks::burn::BurnTransaction; use crate::blocks::collateral::CollateralClaimTransaction; +use crate::blocks::delete_key::DeleteKey; use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::loan_payment::ContractPaymentTransaction; use crate::blocks::loans::LoanContractTransaction; @@ -346,6 +347,19 @@ where restore_if_spendable(tx_label, "unknown", &[signature], spendable, insert).await; } +async fn restore_delete_key(transaction: &DeleteKey, db: &Db) { + let delete = &transaction.unsigned_deletekey; + let spendable = balance_checkup(db, 0, delete.txfee, BASECOIN.clone(), &delete.address).await; + restore_if_spendable( + "delete_key", + "unknown", + std::slice::from_ref(&transaction.signature), + spendable, + || async { transaction.add_to_memory().await }, + ) + .await; +} + pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db) { match transaction { Transaction::Transfer(tx) => restore_transfer(tx, db).await, @@ -393,6 +407,28 @@ pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db) }) .await } + Transaction::StorageI8(tx) => { + restore_storage_value("storage_i8", tx, db, || async { tx.add_to_memory().await }).await + } + Transaction::StorageI16(tx) => { + restore_storage_value("storage_i16", tx, db, || async { tx.add_to_memory().await }) + .await + } + Transaction::StorageI32(tx) => { + restore_storage_value("storage_i32", tx, db, || async { tx.add_to_memory().await }) + .await + } + Transaction::StorageI64(tx) => { + restore_storage_value("storage_i64", tx, db, || async { tx.add_to_memory().await }) + .await + } + Transaction::StorageI128(tx) => { + restore_storage_value("storage_i128", tx, db, || async { + tx.add_to_memory().await + }) + .await + } + Transaction::DeleteKey(tx) => restore_delete_key(tx, db).await, Transaction::Genesis(_) | Transaction::Rewards(_) => {} } } diff --git a/src/orphans/undo_transactions/undo_delete_key.rs b/src/orphans/undo_transactions/undo_delete_key.rs new file mode 100644 index 0000000..f2b3bee --- /dev/null +++ b/src/orphans/undo_transactions/undo_delete_key.rs @@ -0,0 +1,68 @@ +use crate::blocks::delete_key::{delete_record_keys, delete_rollback_key, DeleteKey}; +use crate::common::types::STORAGE_VALUE_ROLLBACK_TREE; +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::{decode, sled::Db}; + +pub async fn undo_delete_key_transaction( + transaction: DeleteKey, + mining_receiver: &str, + db: &Db, +) -> Result<(), String> { + let delete = &transaction.unsigned_deletekey; + let storage_tree_name = decode(&delete.storagekey) + .map_err(|err| format!("Could not decode delete-key storage tree during undo: {err}"))?; + let txhash = delete.hash().await; + let txhash_bytes = decode(&txhash) + .map_err(|err| format!("Could not decode delete-key txhash during undo: {err}"))?; + let targets = delete_record_keys(&delete.address, &delete.key)?; + + let _ = balance_sheet_operation_with_db( + db, + mining_receiver, + delete.txfee, + &BASECOIN, + "subtraction", + ); + let _ = + balance_sheet_operation_with_db(db, &delete.address, delete.txfee, &BASECOIN, "addition"); + + let rollback_tree = db + .open_tree(STORAGE_VALUE_ROLLBACK_TREE) + .map_err(|err| format!("Could not open delete-key rollback tree: {err}"))?; + let storage_tree = db + .open_tree(storage_tree_name) + .map_err(|err| format!("Could not open storage tree during delete-key undo: {err}"))?; + + for (target_index, target) in targets.into_iter().enumerate() { + let rollback_key = delete_rollback_key(&txhash_bytes, target_index)?; + let rollback_value = rollback_tree + .get(&rollback_key) + .map_err(|err| format!("Could not read delete-key rollback value: {err}"))? + .ok_or_else(|| "Missing delete-key rollback record.".to_string())?; + match rollback_value.first().copied() { + Some(0) => { + storage_tree + .remove(&target) + .map_err(|err| format!("Could not preserve absent deleted value: {err}"))?; + } + Some(1) => { + storage_tree + .insert(&target, &rollback_value[1..]) + .map_err(|err| format!("Could not restore deleted storage value: {err}"))?; + } + _ => return Err("Invalid delete-key rollback marker.".to_string()), + } + rollback_tree + .remove(rollback_key) + .map_err(|err| format!("Could not remove delete-key rollback record: {err}"))?; + } + + let _ = remove_wallet_transaction_index(db, &[&delete.address, mining_receiver], &txhash_bytes); + db.open_tree("txid") + .map_err(|err| format!("Could not open txid tree during delete-key undo: {err}"))? + .remove(txhash_bytes) + .map_err(|err| format!("Could not remove delete-key txid mapping: {err}"))?; + Ok(()) +} diff --git a/src/orphans/undo_transactions/undo_storage_value.rs b/src/orphans/undo_transactions/undo_storage_value.rs index debdd71..71f1594 100644 --- a/src/orphans/undo_transactions/undo_storage_value.rs +++ b/src/orphans/undo_transactions/undo_storage_value.rs @@ -25,6 +25,11 @@ fn storage_undo_parts(transaction: &Transaction) -> Result tx.storage_parts(), Transaction::StorageU128(tx) => tx.storage_parts(), Transaction::StorageString(tx) => tx.storage_parts(), + Transaction::StorageI8(tx) => tx.storage_parts(), + Transaction::StorageI16(tx) => tx.storage_parts(), + Transaction::StorageI32(tx) => tx.storage_parts(), + Transaction::StorageI64(tx) => tx.storage_parts(), + Transaction::StorageI128(tx) => tx.storage_parts(), _ => return Err("Unsupported storage value transaction undo.".to_string()), }; diff --git a/src/records/memory/mempool/selection.rs b/src/records/memory/mempool/selection.rs index 0c0e567..99b9e8d 100644 --- a/src/records/memory/mempool/selection.rs +++ b/src/records/memory/mempool/selection.rs @@ -1,11 +1,14 @@ use super::*; +use crate::blocks::delete_key::{delete_record_keys, delete_rollback_key, DeleteKey}; use crate::blocks::storage_values::{ - storage_record_key, storage_string_record_key, StorageBool, StorageString, StorageU128, - StorageU16, StorageU32, StorageU64, StorageU8, StorageValueTransaction, + storage_record_key, storage_string_record_key, StorageBool, StorageI128, StorageI16, + StorageI32, StorageI64, StorageI8, 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, + Transaction, DELETE_KEY_TYPE, STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, + STORAGE_I32_TYPE, STORAGE_I64_TYPE, STORAGE_I8_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::wallet_tx_index::index_wallet_transaction; @@ -43,6 +46,24 @@ async fn storage_value_transaction_from_original(original: &[u8]) -> Result Ok(Transaction::StorageString( StorageString::from_bytes(txtype, body).await?, )), + STORAGE_I8_TYPE => Ok(Transaction::StorageI8( + StorageI8::from_bytes(txtype, body).await?, + )), + STORAGE_I16_TYPE => Ok(Transaction::StorageI16( + StorageI16::from_bytes(txtype, body).await?, + )), + STORAGE_I32_TYPE => Ok(Transaction::StorageI32( + StorageI32::from_bytes(txtype, body).await?, + )), + STORAGE_I64_TYPE => Ok(Transaction::StorageI64( + StorageI64::from_bytes(txtype, body).await?, + )), + STORAGE_I128_TYPE => Ok(Transaction::StorageI128( + StorageI128::from_bytes(txtype, body).await?, + )), + DELETE_KEY_TYPE => Ok(Transaction::DeleteKey( + DeleteKey::from_bytes(txtype, body).await?, + )), _ => Err(anyhow!( "Unsupported storage value transaction type {txtype}" )), @@ -58,6 +79,11 @@ fn selected_storage_value_parts(transaction: &Transaction) -> Result tx.storage_parts(), Transaction::StorageU128(tx) => tx.storage_parts(), Transaction::StorageString(tx) => tx.storage_parts(), + Transaction::StorageI8(tx) => tx.storage_parts(), + Transaction::StorageI16(tx) => tx.storage_parts(), + Transaction::StorageI32(tx) => tx.storage_parts(), + Transaction::StorageI64(tx) => tx.storage_parts(), + Transaction::StorageI128(tx) => tx.storage_parts(), _ => return Err(anyhow!("Unsupported storage value transaction")), }; @@ -77,6 +103,11 @@ fn selected_storage_value_bytes(transaction: &Transaction) -> Result> { Transaction::StorageU64(tx) => tx.storage_value_bytes(), Transaction::StorageU128(tx) => tx.storage_value_bytes(), Transaction::StorageString(tx) => tx.storage_value_bytes(), + Transaction::StorageI8(tx) => tx.storage_value_bytes(), + Transaction::StorageI16(tx) => tx.storage_value_bytes(), + Transaction::StorageI32(tx) => tx.storage_value_bytes(), + Transaction::StorageI64(tx) => tx.storage_value_bytes(), + Transaction::StorageI128(tx) => tx.storage_value_bytes(), _ => Err("Unsupported storage value transaction".to_string()), } .map_err(|err| anyhow!(err)) @@ -1110,24 +1141,40 @@ pub async fn apply_selected_transaction_math( .. } => { 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(), - ); + if let Transaction::DeleteKey(tx) = &transaction { + let delete = &tx.unsigned_deletekey; + let storage_tree = decode(&delete.storagekey)?; + for (target_index, target) in delete_record_keys(&delete.address, &delete.key) + .map_err(|err| anyhow!(err))? + .into_iter() + .enumerate() + { + pending_effects.remove_dynamic_tree( + storage_tree.clone(), + target, + delete_rollback_key(&txhash_bytes, target_index) + .map_err(|err| anyhow!(err))?, + ); + } + } else { + let parts = selected_storage_value_parts(&transaction)?; + let storage_tree = decode(&parts.storagekey)?; + 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, diff --git a/src/records/record_chain/delete_key_tx.rs b/src/records/record_chain/delete_key_tx.rs new file mode 100644 index 0000000..77e38b9 --- /dev/null +++ b/src/records/record_chain/delete_key_tx.rs @@ -0,0 +1,60 @@ +use crate::blocks::delete_key::{delete_record_keys, delete_rollback_key, DeleteKey}; +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::{Arc, Mutex}; + +pub async fn process_delete_key( + transaction: &DeleteKey, + mut binary_data: Vec, + index_counter: Arc>, + miner: String, + block_header_number: u32, + pending_effects: &mut PendingEffects, +) -> Result, String> { + let mut index = index_counter.lock().await; + **index += 1; + + let delete = &transaction.unsigned_deletekey; + let storage_tree = decode(&delete.storagekey) + .map_err(|err| format!("Could not decode delete-key storage tree: {err}"))?; + let txhash = delete.hash().await; + let txhash_bytes = + decode(&txhash).map_err(|err| format!("Could not decode delete-key txhash: {err}"))?; + let targets = delete_record_keys(&delete.address, &delete.key)?; + + binary_data.extend( + transaction + .to_bytes() + .await + .map_err(|err| format!("Could not serialize delete-key transaction: {err}"))?, + ); + + for (target_index, target) in targets.into_iter().enumerate() { + pending_effects.remove_dynamic_tree( + storage_tree.clone(), + target, + delete_rollback_key(&txhash_bytes, target_index)?, + ); + } + pending_effects.add_balance(&miner, delete.txfee, &BASECOIN, BalanceOperand::Addition); + pending_effects.add_balance( + &delete.address, + delete.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, + &[&delete.address], + block_header_number, + **index as u32, + &txhash_bytes, + )?; + + Ok(binary_data) +} diff --git a/src/records/record_chain/mod.rs b/src/records/record_chain/mod.rs index 366bea7..c521d6e 100644 --- a/src/records/record_chain/mod.rs +++ b/src/records/record_chain/mod.rs @@ -3,6 +3,7 @@ pub mod add_payments_db; pub mod borrower_tx; pub mod burn_tx; pub mod collateral_tx; +pub mod delete_key_tx; pub mod genesis_tx; pub mod header_number; pub mod issue_token_tx; diff --git a/src/records/record_chain/parse_transactions.rs b/src/records/record_chain/parse_transactions.rs index e52e5b3..c481881 100644 --- a/src/records/record_chain/parse_transactions.rs +++ b/src/records/record_chain/parse_transactions.rs @@ -3,6 +3,7 @@ use crate::common::types::Transaction; use crate::records::record_chain::borrower_tx::process_borrower; use crate::records::record_chain::burn_tx::process_burn; use crate::records::record_chain::collateral_tx::process_collateral; +use crate::records::record_chain::delete_key_tx::process_delete_key; use crate::records::record_chain::genesis_tx::process_genesis; use crate::records::record_chain::issue_token_tx::process_issue_token; use crate::records::record_chain::lender_tx::process_lender; @@ -208,7 +209,12 @@ pub async fn handle_transactions( | Transaction::StorageU32(_) | Transaction::StorageU64(_) | Transaction::StorageU128(_) - | Transaction::StorageString(_) => { + | Transaction::StorageString(_) + | Transaction::StorageI8(_) + | Transaction::StorageI16(_) + | Transaction::StorageI32(_) + | Transaction::StorageI64(_) + | Transaction::StorageI128(_) => { process_storage_value( transaction, binary_data.clone(), @@ -220,6 +226,17 @@ pub async fn handle_transactions( ) .await } + Transaction::DeleteKey(delete_key_transaction) => { + process_delete_key( + delete_key_transaction, + binary_data.clone(), + index_mutex.clone(), + miner.clone(), + block_header_number, + pending_effects, + ) + .await + } }; match result { Ok(new_binary_data) => { diff --git a/src/records/record_chain/pending_effects.rs b/src/records/record_chain/pending_effects.rs index aaee549..04f95ce 100644 --- a/src/records/record_chain/pending_effects.rs +++ b/src/records/record_chain/pending_effects.rs @@ -48,6 +48,11 @@ pub enum PendingEffect { value: Vec, rollback_key: Vec, }, + DynamicTreeRemove { + tree: Vec, + key: Vec, + rollback_key: Vec, + }, TreeRemove { tree: &'static str, key: Vec, @@ -133,6 +138,14 @@ impl PendingEffects { }); } + pub fn remove_dynamic_tree(&mut self, tree: Vec, key: Vec, rollback_key: Vec) { + self.push(PendingEffect::DynamicTreeRemove { + tree, + key, + rollback_key, + }); + } + pub fn remove_tree(&mut self, tree: &'static str, key: Vec) { self.push(PendingEffect::TreeRemove { tree, key }); } @@ -337,6 +350,39 @@ fn apply_effect(db: &Db, effect: &PendingEffect) -> Result { + let tree_handle = db + .open_tree(tree) + .map_err(|err| format!("Failed to open dynamic storage tree: {err}"))?; + let previous = tree_handle + .remove(key) + .map_err(|err| format!("Failed to remove dynamic storage value: {err}"))? + .map(|value| value.to_vec()); + 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 } => { let tree_handle = db .open_tree(tree) @@ -606,6 +652,71 @@ fn encode_storage_rollback_value(previous: Option<&[u8]>) -> Vec { } } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dynamic_tree_remove_is_reversible() { + let db = crate::sled::Config::new() + .temporary(true) + .open() + .expect("temporary sled database should open"); + let tree_name = vec![7_u8; 32]; + let record_key = b"wallet-and-data-key".to_vec(); + let record_value = b"stored-value".to_vec(); + let rollback_key = vec![9_u8; 33]; + db.open_tree(tree_name.clone()) + .unwrap() + .insert(&record_key, record_value.as_slice()) + .unwrap(); + + let mut pending = PendingEffects::default(); + pending.remove_dynamic_tree(tree_name.clone(), record_key.clone(), rollback_key.clone()); + let mut applied = pending.apply(&db).expect("remove effect should apply"); + + assert!(db + .open_tree(tree_name.clone()) + .unwrap() + .get(&record_key) + .unwrap() + .is_none()); + assert_eq!( + db.open_tree(STORAGE_VALUE_ROLLBACK_TREE) + .unwrap() + .get(&rollback_key) + .unwrap() + .unwrap() + .as_ref(), + [1_u8] + .iter() + .chain(record_value.iter()) + .copied() + .collect::>() + .as_slice() + ); + + applied + .rollback(&db) + .expect("remove effect should roll back"); + assert_eq!( + db.open_tree(tree_name) + .unwrap() + .get(record_key) + .unwrap() + .unwrap() + .as_ref(), + record_value.as_slice() + ); + assert!(db + .open_tree(STORAGE_VALUE_ROLLBACK_TREE) + .unwrap() + .get(rollback_key) + .unwrap() + .is_none()); + } +} + fn append_tree_value( db: &Db, tree: &'static str, diff --git a/src/records/record_chain/storage_value_tx.rs b/src/records/record_chain/storage_value_tx.rs index bd891b6..8a95465 100644 --- a/src/records/record_chain/storage_value_tx.rs +++ b/src/records/record_chain/storage_value_tx.rs @@ -48,6 +48,26 @@ async fn storage_transaction_bytes(transaction: &Transaction) -> Result, .to_bytes() .await .map_err(|err| format!("Could not serialize storage string transaction: {err}")), + Transaction::StorageI8(tx) => tx + .to_bytes() + .await + .map_err(|err| format!("Could not serialize storage i8 transaction: {err}")), + Transaction::StorageI16(tx) => tx + .to_bytes() + .await + .map_err(|err| format!("Could not serialize storage i16 transaction: {err}")), + Transaction::StorageI32(tx) => tx + .to_bytes() + .await + .map_err(|err| format!("Could not serialize storage i32 transaction: {err}")), + Transaction::StorageI64(tx) => tx + .to_bytes() + .await + .map_err(|err| format!("Could not serialize storage i64 transaction: {err}")), + Transaction::StorageI128(tx) => tx + .to_bytes() + .await + .map_err(|err| format!("Could not serialize storage i128 transaction: {err}")), _ => Err("Unsupported storage value transaction.".to_string()), } } @@ -61,6 +81,11 @@ fn storage_parts(transaction: &Transaction) -> Result Transaction::StorageU64(tx) => tx.storage_parts(), Transaction::StorageU128(tx) => tx.storage_parts(), Transaction::StorageString(tx) => tx.storage_parts(), + Transaction::StorageI8(tx) => tx.storage_parts(), + Transaction::StorageI16(tx) => tx.storage_parts(), + Transaction::StorageI32(tx) => tx.storage_parts(), + Transaction::StorageI64(tx) => tx.storage_parts(), + Transaction::StorageI128(tx) => tx.storage_parts(), _ => return Err("Unsupported storage value transaction.".to_string()), }; @@ -82,6 +107,11 @@ fn storage_value_bytes(transaction: &Transaction) -> Result, String> { Transaction::StorageU64(tx) => tx.storage_value_bytes(), Transaction::StorageU128(tx) => tx.storage_value_bytes(), Transaction::StorageString(tx) => tx.storage_value_bytes(), + Transaction::StorageI8(tx) => tx.storage_value_bytes(), + Transaction::StorageI16(tx) => tx.storage_value_bytes(), + Transaction::StorageI32(tx) => tx.storage_value_bytes(), + Transaction::StorageI64(tx) => tx.storage_value_bytes(), + Transaction::StorageI128(tx) => tx.storage_value_bytes(), _ => Err("Unsupported storage value transaction.".to_string()), } } diff --git a/src/records/unpack_block/load_by_binary_data.rs b/src/records/unpack_block/load_by_binary_data.rs index 44a670e..8c8ce21 100644 --- a/src/records/unpack_block/load_by_binary_data.rs +++ b/src/records/unpack_block/load_by_binary_data.rs @@ -1,6 +1,7 @@ use crate::blocks::block::{Block, VrfBlock, VRF_BLOCK_BYTES}; use crate::blocks::burn::BurnTransaction; use crate::blocks::collateral::CollateralClaimTransaction; +use crate::blocks::delete_key::DeleteKey; use crate::blocks::genesis::GenesisTransaction; use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::loan_payment::ContractPaymentTransaction; @@ -10,7 +11,8 @@ use crate::blocks::nft::CreateNftTransaction; use crate::blocks::rewards::RewardsTransaction; use crate::blocks::storage_key::StorageKey; use crate::blocks::storage_values::{ - StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, + StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString, + StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, }; use crate::blocks::swap::SwapTransaction; use crate::blocks::token::CreateTokenTransaction; @@ -18,9 +20,11 @@ use crate::blocks::transfer::TransferTransaction; use crate::blocks::vanity::VanityAddressTransaction; use crate::common::types::{ Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE, - GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_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, + DELETE_KEY_TYPE, GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, + STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE, + STORAGE_I8_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; @@ -269,6 +273,60 @@ pub async fn load_block_from_binary(binary_data: &[u8]) -> Result i += body_len; storage } + STORAGE_I8_TYPE => { + let storage = Transaction::StorageI8( + StorageI8::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I16_TYPE => { + let storage = Transaction::StorageI16( + StorageI16::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I32_TYPE => { + let storage = Transaction::StorageI32( + StorageI32::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I64_TYPE => { + let storage = Transaction::StorageI64( + StorageI64::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I128_TYPE => { + let storage = Transaction::StorageI128( + StorageI128::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + DELETE_KEY_TYPE => { + let delete_key = Transaction::DeleteKey( + DeleteKey::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + delete_key + } _ => { return Err(format!("Unsupported transaction type: {txtype}")); } diff --git a/src/records/unpack_block/load_by_block_number.rs b/src/records/unpack_block/load_by_block_number.rs index c9e05df..745a724 100644 --- a/src/records/unpack_block/load_by_block_number.rs +++ b/src/records/unpack_block/load_by_block_number.rs @@ -1,6 +1,7 @@ use crate::blocks::block::{Block, VrfBlock, VRF_BLOCK_BYTES}; use crate::blocks::burn::BurnTransaction; use crate::blocks::collateral::CollateralClaimTransaction; +use crate::blocks::delete_key::DeleteKey; use crate::blocks::genesis::GenesisTransaction; use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::loan_payment::ContractPaymentTransaction; @@ -10,7 +11,8 @@ use crate::blocks::nft::CreateNftTransaction; use crate::blocks::rewards::RewardsTransaction; use crate::blocks::storage_key::StorageKey; use crate::blocks::storage_values::{ - StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, + StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString, + StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, }; use crate::blocks::swap::SwapTransaction; use crate::blocks::token::CreateTokenTransaction; @@ -19,9 +21,11 @@ use crate::blocks::vanity::VanityAddressTransaction; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::types::{ Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE, - GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_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, + DELETE_KEY_TYPE, GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, + STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE, + STORAGE_I8_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::fs; use crate::rpc::command_maps::get_bytes; @@ -286,6 +290,60 @@ pub async fn load_block(block_number: u32) -> Result { i += body_len; storage } + STORAGE_I8_TYPE => { + let storage = Transaction::StorageI8( + StorageI8::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I16_TYPE => { + let storage = Transaction::StorageI16( + StorageI16::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I32_TYPE => { + let storage = Transaction::StorageI32( + StorageI32::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I64_TYPE => { + let storage = Transaction::StorageI64( + StorageI64::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + STORAGE_I128_TYPE => { + let storage = Transaction::StorageI128( + StorageI128::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + storage + } + DELETE_KEY_TYPE => { + let delete_key = Transaction::DeleteKey( + DeleteKey::from_bytes(txtype, body) + .await + .map_err(|e| e.to_string())?, + ); + i += body_len; + delete_key + } _ => { return Err(format!("Unsupported transaction type: {txtype}")); } diff --git a/src/rpc/command_maps.rs b/src/rpc/command_maps.rs index 704fba1..2721dbc 100644 --- a/src/rpc/command_maps.rs +++ b/src/rpc/command_maps.rs @@ -1,5 +1,6 @@ use crate::blocks::burn::BurnTransaction; use crate::blocks::collateral::CollateralClaimTransaction; +use crate::blocks::delete_key::DeleteKey; use crate::blocks::genesis::GenesisTransaction; use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::loan_payment::ContractPaymentTransaction; @@ -9,7 +10,8 @@ use crate::blocks::nft::CreateNftTransaction; use crate::blocks::rewards::RewardsTransaction; use crate::blocks::storage_key::StorageKey; use crate::blocks::storage_values::{ - StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, + StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString, + StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, }; use crate::blocks::swap::SwapTransaction; use crate::blocks::token::CreateTokenTransaction; @@ -100,6 +102,12 @@ pub fn get_bytes(tx_type: u8) -> usize { 105 => StorageU64::BYTE_LENGTH, 106 => StorageU128::BYTE_LENGTH, 107 => StorageString::BYTE_LENGTH, + 108 => StorageI8::BYTE_LENGTH, + 109 => StorageI16::BYTE_LENGTH, + 110 => StorageI32::BYTE_LENGTH, + 111 => StorageI64::BYTE_LENGTH, + 112 => StorageI128::BYTE_LENGTH, + 113 => DeleteKey::BYTE_LENGTH, _ => 0, } } diff --git a/src/rpc/commands/address_history.rs b/src/rpc/commands/address_history.rs index 1712a84..2dbf5d4 100644 --- a/src/rpc/commands/address_history.rs +++ b/src/rpc/commands/address_history.rs @@ -136,6 +136,12 @@ async fn miner_earnings_from_transactions( 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)?, + Transaction::StorageI8(tx) => earnings.add_fee(tx.unsigned_storagei8.txfee)?, + Transaction::StorageI16(tx) => earnings.add_fee(tx.unsigned_storagei16.txfee)?, + Transaction::StorageI32(tx) => earnings.add_fee(tx.unsigned_storagei32.txfee)?, + Transaction::StorageI64(tx) => earnings.add_fee(tx.unsigned_storagei64.txfee)?, + Transaction::StorageI128(tx) => earnings.add_fee(tx.unsigned_storagei128.txfee)?, + Transaction::DeleteKey(tx) => earnings.add_fee(tx.unsigned_deletekey.txfee)?, } } @@ -154,7 +160,7 @@ async fn miner_earnings_for_block(db: &Db, block_height: u32) -> Result, fn signature_count(transaction_type: u8) -> usize { match transaction_type { 6 | 7 => 2, - 2..=5 | 8..=12 | 100..=107 => 1, + 2..=5 | 8..=12 | 100..=113 => 1, _ => 0, } } diff --git a/src/rpc/commands/tx_submit.rs b/src/rpc/commands/tx_submit.rs index afb9a4c..302b627 100644 --- a/src/rpc/commands/tx_submit.rs +++ b/src/rpc/commands/tx_submit.rs @@ -1,5 +1,6 @@ use crate::blocks::burn::BurnTransaction; use crate::blocks::collateral::CollateralClaimTransaction; +use crate::blocks::delete_key::DeleteKey; use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::loan_payment::ContractPaymentTransaction; use crate::blocks::loans::LoanContractTransaction; @@ -7,15 +8,17 @@ use crate::blocks::marketing::MarketingTransaction; use crate::blocks::nft::CreateNftTransaction; use crate::blocks::storage_key::StorageKey; use crate::blocks::storage_values::{ - StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, + StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString, + StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, }; use crate::blocks::swap::SwapTransaction; use crate::blocks::token::CreateTokenTransaction; use crate::blocks::transfer::TransferTransaction; use crate::blocks::vanity::VanityAddressTransaction; use crate::common::types::{ - BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE, - ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, STORAGE_BOOL_TYPE, STORAGE_KEY_TYPE, + BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE, DELETE_KEY_TYPE, + ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, + STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE, STORAGE_I8_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, }; @@ -605,6 +608,12 @@ pub async fn save_and_submit( 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), + STORAGE_I8_TYPE => submit_storage_value!(StorageI8, txtype, tx, db, map), + STORAGE_I16_TYPE => submit_storage_value!(StorageI16, txtype, tx, db, map), + STORAGE_I32_TYPE => submit_storage_value!(StorageI32, txtype, tx, db, map), + STORAGE_I64_TYPE => submit_storage_value!(StorageI64, txtype, tx, db, map), + STORAGE_I128_TYPE => submit_storage_value!(StorageI128, txtype, tx, db, map), + DELETE_KEY_TYPE => submit_storage_value!(DeleteKey, txtype, tx, db, map), _ => { let msg = "error: No such transaction type" .to_string() diff --git a/src/standalone_tools/transaction_creator.rs b/src/standalone_tools/transaction_creator.rs index c0a88c7..df7fec0 100644 --- a/src/standalone_tools/transaction_creator.rs +++ b/src/standalone_tools/transaction_creator.rs @@ -1,5 +1,6 @@ use crate::blocks::burn::{BurnTransaction, UnsignedBurnTransaction}; use crate::blocks::collateral::{CollateralClaimTransaction, UnsignedCollateralClaimTransaction}; +use crate::blocks::delete_key::{DeleteKey, UnsignedDeleteKey}; use crate::blocks::issue_token::{IssueTokenTransaction, UnsignedIssueTokenTransaction}; use crate::blocks::loan_payment::{ContractPaymentTransaction, UnsignedContractPaymentTransaction}; use crate::blocks::loans::{LoanContractTransaction, UnsignedLoanContractTransaction}; @@ -7,8 +8,10 @@ use crate::blocks::marketing::{MarketingTransaction, UnsignedMarketingTransactio 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, + StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString, + StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, UnsignedStorageBool, + UnsignedStorageI128, UnsignedStorageI16, UnsignedStorageI32, UnsignedStorageI64, + UnsignedStorageI8, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16, UnsignedStorageU32, UnsignedStorageU64, UnsignedStorageU8, }; use crate::blocks::swap::{SwapTransaction, UnsignedSwapTransaction}; @@ -16,8 +19,9 @@ use crate::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransactio use crate::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction}; 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, + DELETE_KEY_TYPE, STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, + STORAGE_I64_TYPE, STORAGE_I8_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::rpc::command_maps::RPC_SUBMIT_TRANSACTION; @@ -88,6 +92,28 @@ fn read_transaction_type(transaction: &Value) -> Option { .and_then(|value| value.get("txtype")) .and_then(|value| value.as_u64()) }) + .or_else(|| { + transaction + .get("unsigned_deletekey") + .and_then(|value| value.get("txtype")) + .and_then(|value| value.as_u64()) + }) + .or_else(|| { + [ + "unsigned_storagei8", + "unsigned_storagei16", + "unsigned_storagei32", + "unsigned_storagei64", + "unsigned_storagei128", + ] + .iter() + .find_map(|field| { + transaction + .get(field) + .and_then(|value| value.get("txtype")) + .and_then(|value| value.as_u64()) + }) + }) .map(|value| value as u8) } @@ -102,6 +128,14 @@ fn read_u128_field(transaction: &Value, field: &str) -> Option { value.to_string().parse::().ok() } +fn read_i128_field(transaction: &Value, field: &str) -> Option { + let value = transaction.get(field)?; + if let Some(value) = value.as_i64() { + return Some(value as i128); + } + value.as_str()?.trim().parse::().ok() +} + pub async fn create_transaction_request( command_input: String, hashkey: Byte3, @@ -801,6 +835,213 @@ async fn create_transaction_bytes(transaction: &Value) -> Result, String .await .map_err(|e| format!("Failed to serialize storage-string transaction: {e}"))? } + STORAGE_I8_TYPE => { + let payload = storage_payload(transaction, "unsigned_storagei8"); + let unsigned = UnsignedStorageI8 { + 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: i8::try_from(read_i128_field(payload, "value").ok_or("Missing i8 value.")?) + .map_err(|_| "Invalid i8 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.")?, + }; + StorageI8::load( + unsigned, + transaction["signature"] + .as_str() + .ok_or("Missing transaction signature.")?, + ) + .await + .to_bytes() + .await + .map_err(|e| format!("Failed to serialize storage-i8 transaction: {e}"))? + } + STORAGE_I16_TYPE => { + let payload = storage_payload(transaction, "unsigned_storagei16"); + let unsigned = UnsignedStorageI16 { + 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: i16::try_from( + read_i128_field(payload, "value").ok_or("Missing i16 value.")?, + ) + .map_err(|_| "Invalid i16 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.")?, + }; + StorageI16::load( + unsigned, + transaction["signature"] + .as_str() + .ok_or("Missing transaction signature.")?, + ) + .await + .to_bytes() + .await + .map_err(|e| format!("Failed to serialize storage-i16 transaction: {e}"))? + } + STORAGE_I32_TYPE => { + let payload = storage_payload(transaction, "unsigned_storagei32"); + let unsigned = UnsignedStorageI32 { + 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: i32::try_from( + read_i128_field(payload, "value").ok_or("Missing i32 value.")?, + ) + .map_err(|_| "Invalid i32 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.")?, + }; + StorageI32::load( + unsigned, + transaction["signature"] + .as_str() + .ok_or("Missing transaction signature.")?, + ) + .await + .to_bytes() + .await + .map_err(|e| format!("Failed to serialize storage-i32 transaction: {e}"))? + } + STORAGE_I64_TYPE => { + let payload = storage_payload(transaction, "unsigned_storagei64"); + let unsigned = UnsignedStorageI64 { + 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: i64::try_from( + read_i128_field(payload, "value").ok_or("Missing i64 value.")?, + ) + .map_err(|_| "Invalid i64 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.")?, + }; + StorageI64::load( + unsigned, + transaction["signature"] + .as_str() + .ok_or("Missing transaction signature.")?, + ) + .await + .to_bytes() + .await + .map_err(|e| format!("Failed to serialize storage-i64 transaction: {e}"))? + } + STORAGE_I128_TYPE => { + let payload = storage_payload(transaction, "unsigned_storagei128"); + let unsigned = UnsignedStorageI128 { + 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_i128_field(payload, "value").ok_or("Missing i128 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.")?, + }; + StorageI128::load( + unsigned, + transaction["signature"] + .as_str() + .ok_or("Missing transaction signature.")?, + ) + .await + .to_bytes() + .await + .map_err(|e| format!("Failed to serialize storage-i128 transaction: {e}"))? + } + DELETE_KEY_TYPE => { + let payload = storage_payload(transaction, "unsigned_deletekey"); + let unsigned = UnsignedDeleteKey { + 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(), + address: payload["address"] + .as_str() + .ok_or("Missing storage owner wallet address.")? + .to_string(), + txfee: payload["txfee"] + .as_u64() + .ok_or("Missing or invalid txfee.")?, + }; + DeleteKey::load( + unsigned, + transaction["signature"] + .as_str() + .ok_or("Missing transaction signature.")?, + ) + .await + .to_bytes() + .await + .map_err(|e| format!("Failed to serialize delete-key transaction: {e}"))? + } _ => return Err("No such transaction type".to_string()), }; diff --git a/src/verifications/async_funcs/checks/block_balance.rs b/src/verifications/async_funcs/checks/block_balance.rs index d211c08..6fe1daf 100644 --- a/src/verifications/async_funcs/checks/block_balance.rs +++ b/src/verifications/async_funcs/checks/block_balance.rs @@ -569,6 +569,97 @@ async fn transaction_balance_view( check_saved_txid: true, }) } + Transaction::StorageI8(tx) => { + let storage = &tx.unsigned_storagei8; + 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::StorageI16(tx) => { + let storage = &tx.unsigned_storagei16; + 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::StorageI32(tx) => { + let storage = &tx.unsigned_storagei32; + 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::StorageI64(tx) => { + let storage = &tx.unsigned_storagei64; + 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::StorageI128(tx) => { + let storage = &tx.unsigned_storagei128; + 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::DeleteKey(tx) => { + let delete = &tx.unsigned_deletekey; + let mut debits = Vec::new(); + add_debit(&mut debits, &delete.address, BASECOIN.clone(), delete.txfee); + Ok(TransactionBalanceView { + hash: delete.hash().await, + signatures: vec![tx.signature.clone()], + debits, + check_saved_txid: true, + }) + } } } diff --git a/src/verifications/async_funcs/mod.rs b/src/verifications/async_funcs/mod.rs index 44c785f..426040b 100644 --- a/src/verifications/async_funcs/mod.rs +++ b/src/verifications/async_funcs/mod.rs @@ -11,6 +11,7 @@ pub mod verify_burn; pub mod verify_collateral; pub mod verify_create_nft; pub mod verify_create_token; +pub mod verify_delete_key; pub mod verify_genesis; pub mod verify_issue_token; pub mod verify_lender; diff --git a/src/verifications/async_funcs/transactions.rs b/src/verifications/async_funcs/transactions.rs index 03bfade..dbec3dc 100644 --- a/src/verifications/async_funcs/transactions.rs +++ b/src/verifications/async_funcs/transactions.rs @@ -355,6 +355,84 @@ async fn verify_transaction( } } } + if let Transaction::StorageI8(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 i8 transaction: {err}" + )); + } + } + } + if let Transaction::StorageI16(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 i16 transaction: {err}" + )); + } + } + } + if let Transaction::StorageI32(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 i32 transaction: {err}" + )); + } + } + } + if let Transaction::StorageI64(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 i64 transaction: {err}" + )); + } + } + } + if let Transaction::StorageI128(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 i128 transaction: {err}" + )); + } + } + } + if let Transaction::DeleteKey(delete_key_tx) = &transaction { + match delete_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 delete-key transaction: {err}" + )); + } + } + } Err( "Validation failed: unsupported transaction variant reached verification dispatch." .to_string(), diff --git a/src/verifications/async_funcs/verify_delete_key.rs b/src/verifications/async_funcs/verify_delete_key.rs new file mode 100644 index 0000000..2609174 --- /dev/null +++ b/src/verifications/async_funcs/verify_delete_key.rs @@ -0,0 +1,98 @@ +use crate::blocks::delete_key::{delete_record_keys, DeleteKey}; +use crate::blocks::storage_values::{STORAGE_KEY_HASH_BYTES, STORAGE_VALUE_KEY_BYTES}; +use crate::common::types::{DELETE_KEY_TYPE, STORAGE_KEY_INDEX_TREE, STORAGE_VALUE_FEE}; +use crate::records::memory::mempool::BASECOIN; +use crate::records::wallet_registry::{ + require_canonical_registered_short_address, resolve_pubkey_from_short_address, +}; +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; +use crate::{decode, encode, sled::Db}; + +impl DeleteKey { + pub async fn verify(&self, db: &Db) -> Result { + let delete = &self.unsigned_deletekey; + let hash = delete.hash().await; + + // Existing mempool entries short-circuit before the storage-state checks. + if memcheck(&self.signature, &hash).await { + return Ok(self.signature.clone()); + } + if delete.txtype != DELETE_KEY_TYPE { + return Err("Delete-key transaction type is invalid.".to_string()); + } + + // The namespace must be a previously confirmed StorageKey transaction. + let storagekey = decode(&delete.storagekey) + .map_err(|err| format!("Delete-key storage hash is invalid: {err}"))?; + if storagekey.len() != STORAGE_KEY_HASH_BYTES { + return Err("Delete-key storage hash length is invalid.".to_string()); + } + if delete.key.as_bytes().len() != STORAGE_VALUE_KEY_BYTES { + return Err("Delete-key field key length is invalid.".to_string()); + } + 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) + .map_err(|err| format!("Could not check storage key index: {err}"))? + { + return Err("Delete-key storage key does not exist.".to_string()); + } + + // Only the registered owner of this address-scoped data can delete it. + if !Wallet::short_address_validation(&delete.address) { + return Err("Delete-key wallet address is invalid.".to_string()); + } + require_canonical_registered_short_address( + db, + &delete.address, + "Delete-key wallet address", + )?; + let public_key = resolve_pubkey_from_short_address(db, &delete.address) + .map_err(|_| "Delete-key wallet address is not registered.".to_string())? + .ok_or_else(|| "Delete-key wallet address is not registered.".to_string())?; + if !Wallet::verify_transaction_with_public_key(&hash, &self.signature, &encode(public_key)) + .await + { + return Err("Invalid signature for the Delete Key Transaction.".to_string()); + } + + if delete.txfee < STORAGE_VALUE_FEE { + return Err(format!( + "Delete-key transaction fee is below the minimum required fee of {STORAGE_VALUE_FEE}." + )); + } + if !balance_checkup(db, 0, delete.txfee, BASECOIN.clone(), &delete.address).await { + return Err("Insuficient funds for this Delete Key Transaction!".to_string()); + } + if !db_hex_verification(db, "txid", &hash).await { + return Err("This transaction already exists.".to_string()); + } + + // Reject no-op deletes. Base keys inspect the scalar and all 20 string chunks; + // explicit _00 through _19 keys inspect only that exact chunk. + let tree = db + .open_tree(&storagekey) + .map_err(|err| format!("Could not open storage tree: {err}"))?; + let targets = delete_record_keys(&delete.address, &delete.key)?; + let mut found = false; + for target in targets { + if tree + .contains_key(target) + .map_err(|err| format!("Could not inspect storage record: {err}"))? + { + found = true; + break; + } + } + if !found { + return Err("Delete-key target does not exist.".to_string()); + } + + Ok(String::new()) + } +} diff --git a/src/verifications/async_funcs/verify_storage_values.rs b/src/verifications/async_funcs/verify_storage_values.rs index 0999d02..757ce0c 100644 --- a/src/verifications/async_funcs/verify_storage_values.rs +++ b/src/verifications/async_funcs/verify_storage_values.rs @@ -1,10 +1,12 @@ 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, + storage_string_record_key, StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, + StorageI8, 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_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE, + STORAGE_I8_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; @@ -151,6 +153,36 @@ impl StorageU128 { } } +impl StorageI8 { + pub async fn verify(&self, db: &Db) -> Result { + verify_storage_value(self, db, STORAGE_I8_TYPE, "Storage I8").await + } +} + +impl StorageI16 { + pub async fn verify(&self, db: &Db) -> Result { + verify_storage_value(self, db, STORAGE_I16_TYPE, "Storage I16").await + } +} + +impl StorageI32 { + pub async fn verify(&self, db: &Db) -> Result { + verify_storage_value(self, db, STORAGE_I32_TYPE, "Storage I32").await + } +} + +impl StorageI64 { + pub async fn verify(&self, db: &Db) -> Result { + verify_storage_value(self, db, STORAGE_I64_TYPE, "Storage I64").await + } +} + +impl StorageI128 { + pub async fn verify(&self, db: &Db) -> Result { + verify_storage_value(self, db, STORAGE_I128_TYPE, "Storage I128").await + } +} + impl StorageString { pub async fn verify(&self, db: &Db) -> Result { // String storage has extra chain-linking rules before it can use