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?;
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ pub mod loans;
|
|||
pub mod marketing;
|
||||
pub mod nft;
|
||||
pub mod rewards;
|
||||
pub mod storage_key;
|
||||
pub mod storage_values;
|
||||
pub mod swap;
|
||||
pub mod token;
|
||||
pub mod transfer;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ use crate::blocks::loans::LoanContractTransaction;
|
|||
use crate::blocks::marketing::MarketingTransaction;
|
||||
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,
|
||||
};
|
||||
use crate::blocks::swap::SwapTransaction;
|
||||
use crate::blocks::token::CreateTokenTransaction;
|
||||
use crate::blocks::transfer::TransferTransaction;
|
||||
|
|
@ -28,6 +32,14 @@ pub const COLLATERAL_TYPE: u8 = 9;
|
|||
pub const BURN_TYPE: u8 = 10;
|
||||
pub const ISSUE_TOKEN_TYPE: u8 = 11;
|
||||
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.
|
||||
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 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 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 {
|
||||
// Base-coin transfers pay the percentage fee using integer math.
|
||||
|
|
@ -92,4 +108,12 @@ pub enum Transaction {
|
|||
Borrower(ContractPaymentTransaction),
|
||||
Collateral(CollateralClaimTransaction),
|
||||
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_password: Option<String>,
|
||||
pub pg_dbname: String,
|
||||
pub storage_lookup_fee_per_byte: u64,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
|
|
@ -197,6 +198,10 @@ impl Settings {
|
|||
.ok_or("INCOMING_CONNECTIONS not found")?
|
||||
.parse::<u8>()?,
|
||||
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_port: pg_section.get("port").unwrap_or("5432").parse::<u16>()?,
|
||||
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_marketing::undo_marketing_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_transfer::undo_transfer_transaction;
|
||||
use crate::orphans::undo_transactions::undo_vanity::undo_vanity_transaction;
|
||||
|
|
@ -130,6 +132,30 @@ pub async fn undo_transactions(
|
|||
.await?;
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ pub mod undo_issue_token;
|
|||
pub mod undo_loan_creation;
|
||||
pub mod undo_marketing;
|
||||
pub mod undo_rewards;
|
||||
pub mod undo_storage_key;
|
||||
pub mod undo_storage_value;
|
||||
pub mod undo_swap;
|
||||
pub mod undo_transfer;
|
||||
pub mod undo_vanity;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use crate::blocks::loan_payment::ContractPaymentTransaction;
|
|||
use crate::blocks::loans::LoanContractTransaction;
|
||||
use crate::blocks::marketing::MarketingTransaction;
|
||||
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::token::CreateTokenTransaction;
|
||||
use crate::blocks::transfer::TransferTransaction;
|
||||
|
|
@ -307,6 +309,43 @@ pub async fn restore_vanity(transaction: &VanityAddressTransaction, db: &Db) {
|
|||
.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) {
|
||||
match transaction {
|
||||
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::Collateral(tx) => restore_collateral(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(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
"The connection is already in the connection manager Please wait 10 minutes and try again",
|
||||
) {
|
||||
info!(
|
||||
"[reconnect] dropped peer {addr_string} is already in the connection manager; stopping reconnect attempts"
|
||||
warn!(
|
||||
"[reconnect] dropped peer {addr_string} is still recorded by remote connection manager on attempt {attempt}/3; retrying"
|
||||
);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
warn!(
|
||||
"[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
|
||||
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||
{
|
||||
Some(
|
||||
info.ready
|
||||
|| (info.wallet_registry_synced && info.network_map_synced),
|
||||
)
|
||||
Some(info.ready || (info.wallet_registry_synced && info.network_map_synced))
|
||||
} else {
|
||||
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_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 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
|
||||
ELSE 0
|
||||
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
|
||||
UNION ALL
|
||||
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
|
||||
"#,
|
||||
&[&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
|
||||
UNION ALL
|
||||
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;
|
||||
"#,
|
||||
&[&addresses],
|
||||
|
|
@ -198,6 +208,12 @@ pub async fn latest_pending_txids_by_address(db: &Db, address: &str, limit: usiz
|
|||
UNION ALL
|
||||
SELECT hash, time, id AS source_id FROM collateral_claim
|
||||
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
|
||||
ORDER BY hash, time DESC, source_id DESC
|
||||
) 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
|
||||
UNION ALL
|
||||
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
|
||||
"#,
|
||||
&[&hash],
|
||||
|
|
@ -301,6 +321,10 @@ pub async fn largest_fee() -> RpcResponse {
|
|||
SELECT CAST(MAX(fee) AS BIGINT) AS fee FROM loan_payment
|
||||
UNION ALL
|
||||
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;
|
||||
"#,
|
||||
&[],
|
||||
|
|
@ -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(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(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(lp.fee) FROM loan_payment lp WHERE lp.address = ANY($1) AND lp.processed = false), 0)
|
||||
+ COALESCE((SELECT SUM(lc.loan_amount)
|
||||
|
|
@ -575,6 +601,10 @@ pub async fn total_transactions() -> RpcResponse {
|
|||
SELECT COUNT(*) AS row_count FROM loan_payment
|
||||
UNION ALL
|
||||
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;
|
||||
"#,
|
||||
&[],
|
||||
|
|
|
|||
|
|
@ -152,6 +152,19 @@ enum SelectedMempoolTransaction {
|
|||
contract_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)]
|
||||
|
|
@ -176,6 +189,8 @@ impl SelectedMempoolTransaction {
|
|||
SelectedMempoolTransaction::Lender { .. } => "loan_contract",
|
||||
SelectedMempoolTransaction::Borrower { .. } => "loan_payment",
|
||||
SelectedMempoolTransaction::Collateral { .. } => "collateral_claim",
|
||||
SelectedMempoolTransaction::StorageKey { .. } => "storage_key",
|
||||
SelectedMempoolTransaction::StorageValue { .. } => "storage_value",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +206,9 @@ impl SelectedMempoolTransaction {
|
|||
| SelectedMempoolTransaction::Swap { id, .. }
|
||||
| SelectedMempoolTransaction::Lender { 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_payment", 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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ pub async fn mark_selected_transactions_processed(
|
|||
bn,
|
||||
)
|
||||
.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(())
|
||||
}
|
||||
|
|
@ -68,6 +70,8 @@ pub async fn restore_selected_transactions_processed(batch: &SelectedMempoolBatc
|
|||
&ids_for_table(batch, "collateral_claim"),
|
||||
)
|
||||
.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(())
|
||||
}
|
||||
|
|
@ -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_payment", "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)
|
||||
}
|
||||
|
|
@ -221,6 +227,18 @@ pub async fn mark_processed_by_signatures(signatures: &[String], block_number: u
|
|||
signatures,
|
||||
)
|
||||
.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(())
|
||||
}
|
||||
|
|
@ -273,6 +291,16 @@ pub async fn delete_by_signatures(signatures: &[String]) -> Result<()> {
|
|||
signatures,
|
||||
)
|
||||
.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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,6 +279,31 @@ pub async fn setup_mempool() -> Result<()> {
|
|||
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 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;
|
||||
|
|
@ -314,6 +339,10 @@ pub async fn setup_mempool() -> Result<()> {
|
|||
ALTER TABLE loan_payment ALTER COLUMN signature TYPE TEXT;
|
||||
ALTER TABLE collateral_claim ALTER COLUMN address 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
|
||||
|
|
@ -372,6 +401,14 @@ pub async fn setup_mempool() -> Result<()> {
|
|||
DELETE FROM collateral_claim a
|
||||
USING collateral_claim b
|
||||
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
|
||||
|
|
@ -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_sig_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
|
||||
// pending copy of each transaction.
|
||||
|
|
@ -457,7 +502,9 @@ pub async fn clear_mempool() -> Result<()> {
|
|||
swap,
|
||||
loan_contract,
|
||||
loan_payment,
|
||||
collateral_claim
|
||||
collateral_claim,
|
||||
storage_key,
|
||||
storage_value
|
||||
RESTART IDENTITY;
|
||||
"#,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,87 @@
|
|||
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::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(
|
||||
pending_effects: &mut PendingEffects,
|
||||
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
|
||||
FROM collateral_claim
|
||||
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
|
||||
ORDER BY priority_fee DESC, time ASC, kind ASC, id ASC
|
||||
LIMIT $1
|
||||
|
|
@ -329,6 +447,19 @@ pub async fn select_transactions_for_block(limit: i64) -> Result<SelectedMempool
|
|||
contract_hash: required_string(&row, "contract_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}")),
|
||||
};
|
||||
transactions.push(transaction);
|
||||
|
|
@ -946,6 +1077,77 @@ pub async fn apply_selected_transaction_math(
|
|||
&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 borrower_ids = ids_for_table(batch, "loan_payment");
|
||||
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("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_payment", &borrower_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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub mod previous_difficulty;
|
|||
pub mod rewards_tx;
|
||||
pub mod save;
|
||||
pub mod save_flags;
|
||||
pub mod storage_key_tx;
|
||||
pub mod storage_value_tx;
|
||||
pub mod structs;
|
||||
pub mod swap_tx;
|
||||
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::pending_effects::PendingEffects;
|
||||
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::token_tx::process_token;
|
||||
use crate::records::record_chain::transfer_tx::process_transfer;
|
||||
|
|
@ -188,6 +190,36 @@ pub async fn handle_transactions(
|
|||
)
|
||||
.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 {
|
||||
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::records::balance_sheet::operations::balance_sheet_operation_with_db;
|
||||
use crate::records::wallet_registry::{
|
||||
|
|
@ -41,6 +42,12 @@ pub enum PendingEffect {
|
|||
key: Vec<u8>,
|
||||
value: Vec<u8>,
|
||||
},
|
||||
DynamicTreeSet {
|
||||
tree: Vec<u8>,
|
||||
key: Vec<u8>,
|
||||
value: Vec<u8>,
|
||||
rollback_key: Vec<u8>,
|
||||
},
|
||||
TreeRemove {
|
||||
tree: &'static str,
|
||||
key: Vec<u8>,
|
||||
|
|
@ -56,6 +63,9 @@ pub enum PendingEffect {
|
|||
key: Vec<u8>,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
TreeCreate {
|
||||
name: Vec<u8>,
|
||||
},
|
||||
TokenSupplyAdd {
|
||||
ticker: String,
|
||||
amount: u64,
|
||||
|
|
@ -108,6 +118,21 @@ impl PendingEffects {
|
|||
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>) {
|
||||
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) {
|
||||
self.push(PendingEffect::TokenSupplyAdd {
|
||||
ticker: ticker.to_string(),
|
||||
|
|
@ -230,6 +259,13 @@ enum AppliedEffect {
|
|||
key: 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>),
|
||||
VanityUpdate {
|
||||
owner_address: String,
|
||||
|
|
@ -237,6 +273,9 @@ enum AppliedEffect {
|
|||
rollback_key: Vec<u8>,
|
||||
previous_rollback: Option<Vec<u8>>,
|
||||
},
|
||||
TreeCreated {
|
||||
name: Vec<u8>,
|
||||
},
|
||||
Noop,
|
||||
}
|
||||
|
||||
|
|
@ -270,6 +309,34 @@ fn apply_effect(db: &Db, effect: &PendingEffect) -> Result<AppliedEffect, String
|
|||
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 } => {
|
||||
let tree_handle = db
|
||||
.open_tree(tree)
|
||||
|
|
@ -314,6 +381,27 @@ fn apply_effect(db: &Db, effect: &PendingEffect) -> Result<AppliedEffect, String
|
|||
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 } => {
|
||||
let tree = db
|
||||
.open_tree("tokens")
|
||||
|
|
@ -410,6 +498,21 @@ fn rollback_effect(db: &Db, effect: AppliedEffect) -> Result<(), String> {
|
|||
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) => {
|
||||
let mut first_error = None;
|
||||
while let Some(effect) = effects.pop() {
|
||||
|
|
@ -438,6 +541,17 @@ fn rollback_effect(db: &Db, effect: AppliedEffect) -> Result<(), String> {
|
|||
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(()),
|
||||
}
|
||||
}
|
||||
|
|
@ -461,6 +575,37 @@ fn set_tree_value(
|
|||
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(
|
||||
db: &Db,
|
||||
tree: &'static str,
|
||||
|
|
@ -506,6 +651,30 @@ fn restore_tree_value(
|
|||
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> {
|
||||
if bytes.len() != 8 {
|
||||
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::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,
|
||||
};
|
||||
use crate::blocks::swap::SwapTransaction;
|
||||
use crate::blocks::token::CreateTokenTransaction;
|
||||
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, SWAP_TYPE,
|
||||
TRANSFER_TYPE, VANITY_ADDRESS_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,
|
||||
};
|
||||
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;
|
||||
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}"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ use crate::blocks::loans::LoanContractTransaction;
|
|||
use crate::blocks::marketing::MarketingTransaction;
|
||||
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,
|
||||
};
|
||||
use crate::blocks::swap::SwapTransaction;
|
||||
use crate::blocks::token::CreateTokenTransaction;
|
||||
use crate::blocks::transfer::TransferTransaction;
|
||||
|
|
@ -15,8 +19,9 @@ 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, SWAP_TYPE,
|
||||
TRANSFER_TYPE, VANITY_ADDRESS_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,
|
||||
};
|
||||
use crate::fs;
|
||||
use crate::rpc::command_maps::get_bytes;
|
||||
|
|
@ -209,6 +214,78 @@ pub async fn load_block(block_number: u32) -> Result<Block, String> {
|
|||
i += body_len;
|
||||
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}"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ use crate::blocks::loans::LoanContractTransaction;
|
|||
use crate::blocks::marketing::MarketingTransaction;
|
||||
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,
|
||||
};
|
||||
use crate::blocks::swap::SwapTransaction;
|
||||
use crate::blocks::token::CreateTokenTransaction;
|
||||
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_NETWORK_MONITOR_ADD: u8 = 50;
|
||||
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 MAX_RPC_REPLY_BYTES: usize = 64 * 1024 * 1024;
|
||||
|
||||
|
|
@ -85,6 +91,14 @@ pub fn get_bytes(tx_type: u8) -> usize {
|
|||
10 => BurnTransaction::BYTE_LENGTH,
|
||||
11 => IssueTokenTransaction::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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,14 @@ async fn miner_earnings_from_transactions(
|
|||
}
|
||||
Transaction::Collateral(tx) => earnings.add_fee(tx.unsigned_collateral_claim.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 {
|
||||
match transaction_type {
|
||||
6 | 7 => 2,
|
||||
2..=5 | 8..=12 => 1,
|
||||
2..=5 | 8..=12 | 100..=107 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub mod random_node;
|
|||
pub mod receive_torrent;
|
||||
pub mod request_valid_nodes;
|
||||
pub mod route_reply;
|
||||
pub mod storage_lookup;
|
||||
pub mod structs;
|
||||
pub mod time;
|
||||
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::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,
|
||||
};
|
||||
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, 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::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(
|
||||
txtype: u8,
|
||||
tx: Vec<u8>,
|
||||
|
|
@ -507,6 +557,52 @@ pub async fn save_and_submit(
|
|||
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"
|
||||
.to_string()
|
||||
|
|
|
|||
|
|
@ -1005,6 +1005,104 @@ pub async fn start_loop(
|
|||
.send(&stream_locked, Some(&connections_key), uid)
|
||||
.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 => {
|
||||
commands::route_reply::route_reply(
|
||||
&connections_key,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,20 @@ use crate::blocks::loan_payment::{ContractPaymentTransaction, UnsignedContractPa
|
|||
use crate::blocks::loans::{LoanContractTransaction, UnsignedLoanContractTransaction};
|
||||
use crate::blocks::marketing::{MarketingTransaction, UnsignedMarketingTransaction};
|
||||
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::token::{CreateTokenTransaction, UnsignedCreateTokenTransaction};
|
||||
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,
|
||||
};
|
||||
use crate::records::memory::response_channels::Byte3;
|
||||
use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION;
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
command_input: String,
|
||||
hashkey: Byte3,
|
||||
|
|
@ -44,7 +124,7 @@ pub async fn create_transaction_request(
|
|||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
.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()),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -90,10 +90,21 @@ async fn attempt_bootstrap_connections(
|
|||
Err(err) => err.to_string(),
|
||||
};
|
||||
|
||||
// A peer can reject us because it already has this connection recorded.
|
||||
// In that case retrying other bootstrap peers would not fix the local duplicate state.
|
||||
if err_string.contains("The connection is already in the connection manager Please wait 10 minutes and try again") {
|
||||
return Err(err_string);
|
||||
// During outage recovery, a peer may already have the connection
|
||||
// recorded from an overlapping reconnect path. That is not a fatal
|
||||
// bootstrap-recovery condition; try the next configured peer.
|
||||
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" {
|
||||
error!("Error connecting to {server}: {err_string}");
|
||||
|
|
|
|||
|
|
@ -439,6 +439,136 @@ async fn transaction_balance_view(
|
|||
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_marketing;
|
||||
pub mod verify_rewards;
|
||||
pub mod verify_storage_key;
|
||||
pub mod verify_storage_values;
|
||||
pub mod verify_swap;
|
||||
pub mod verify_transfer;
|
||||
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(
|
||||
"Validation failed: unsupported transaction variant reached verification dispatch."
|
||||
.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