fixed bug in prevalidation mempool lookup

This commit is contained in:
viraladmin 2026-07-21 10:47:28 -06:00
parent ac7c0f0558
commit 8d0007eb1a
30 changed files with 230 additions and 199 deletions

View File

@ -19,7 +19,7 @@ GPT-5.6 SOL assisted with:
- Hardening network-map monitoring and connection-state handling.
- Implementing and reviewing on-chain data-storage transaction support.
- Integrating data storage, address data, NFTs, loans, assets, and transaction history into the GUI wallet.
- Improving GUI wallet cache synchronization, cache deletion safety, and Windows file-access coordination.
- Improving [GUI wallet](https://contractless.dev/contractless/Contractless-GUI-Wallet) cache synchronization, cache deletion safety, and Windows file-access coordination.
- Reviewing Rust transaction validation, mempool handling, block saving, and chain-reorganization paths.
- Identifying behavioral inconsistencies between local wallet caches and RPC responses.
- Expanding and reorganizing user documentation for installation, wallets, transactions, validation tools, loans, swaps, NFTs, fees, and data storage.
@ -90,5 +90,3 @@ Use the release package that matches your operating system and follow the matchi
## Current Status
Contractless is still under active testnet development. Interfaces, transaction tools, docs, and node behavior may continue to change while the chain is tested.
If you are interested in running a testnet node, start with the installation guide for your operating system, then review the configuration and wallet tools documentation.

View File

@ -50,6 +50,10 @@ pub struct VrfBlock {
pub struct Block {
pub vrf_block: VrfBlock,
pub transactions: Vec<Transaction>,
// Exact transaction slices are retained for downloaded/stored blocks so
// mempool reuse can compare bytes without serializing or hashing again.
#[serde(skip)]
pub original_transactions: Vec<Vec<u8>>,
}
impl UnminedBlock {

View File

@ -131,3 +131,39 @@ pub enum Transaction {
StorageI128(StorageI128),
DeleteKey(DeleteKey),
}
impl Transaction {
// Return the signature used by the indexed mempool lookup. Two-party
// transactions use the final signer because valid submitted swaps and
// loan contracts must already contain both signatures.
pub fn mempool_signature(&self) -> Option<&str> {
match self {
Transaction::Genesis(_) | Transaction::Rewards(_) => None,
Transaction::Transfer(tx) => Some(&tx.signature),
Transaction::Burn(tx) => Some(&tx.signature),
Transaction::Token(tx) => Some(&tx.signature),
Transaction::IssueToken(tx) => Some(&tx.signature),
Transaction::Nft(tx) => Some(&tx.signature),
Transaction::Marketing(tx) => Some(&tx.signature),
Transaction::Swap(tx) => Some(&tx.signature2),
Transaction::Lender(tx) => Some(&tx.signature2),
Transaction::Borrower(tx) => Some(&tx.signature),
Transaction::Collateral(tx) => Some(&tx.signature),
Transaction::Vanity(tx) => Some(&tx.signature),
Transaction::StorageKey(tx) => Some(&tx.signature),
Transaction::StorageBool(tx) => Some(&tx.signature),
Transaction::StorageU8(tx) => Some(&tx.signature),
Transaction::StorageU16(tx) => Some(&tx.signature),
Transaction::StorageU32(tx) => Some(&tx.signature),
Transaction::StorageU64(tx) => Some(&tx.signature),
Transaction::StorageU128(tx) => Some(&tx.signature),
Transaction::StorageString(tx) => Some(&tx.signature),
Transaction::StorageI8(tx) => Some(&tx.signature),
Transaction::StorageI16(tx) => Some(&tx.signature),
Transaction::StorageI32(tx) => Some(&tx.signature),
Transaction::StorageI64(tx) => Some(&tx.signature),
Transaction::StorageI128(tx) => Some(&tx.signature),
Transaction::DeleteKey(tx) => Some(&tx.signature),
}
}
}

View File

@ -133,6 +133,7 @@ async fn create_genesis_block(
let block = Block {
vrf_block,
transactions: vec![Transaction::Genesis(signed_genesis_transaction.clone())],
original_transactions: Vec::new(),
};
// Reuse normal block verification before saving so genesis

View File

@ -285,6 +285,7 @@ pub async fn mine_block_internal(
let new_block = Block {
vrf_block,
transactions: vec![Transaction::Rewards(rewards_transaction)],
original_transactions: Vec::new(),
};
// Return only candidates that pass local verification.

View File

@ -1,36 +1,37 @@
use super::*;
pub async fn signature_exists(signature: &str, hash: &str) -> Result<bool> {
pub async fn exact_transaction_exists(signature: &str, original: &[u8]) -> Result<bool> {
let client_handle = db_client().await?;
let client = client_handle.as_ref();
// Check every mempool table because the signature column names differ by
// transaction type, especially for two-party swaps and loans.
// Use the indexed signature to locate a candidate row, then compare the
// complete serialized transaction so every byte must match what was
// previously validated and admitted to the mempool.
let row = client
.query_one(
r#"
SELECT
CASE
WHEN EXISTS (SELECT 1 FROM transfer a WHERE a.signature = $1 AND a.hash = $2 AND a.processed = false)
OR EXISTS (SELECT 1 FROM token b WHERE b.signature = $1 AND b.hash = $2 AND b.processed = false)
OR EXISTS (SELECT 1 FROM issue_token c WHERE c.signature = $1 AND c.hash = $2 AND c.processed = false)
OR EXISTS (SELECT 1 FROM burn d WHERE d.signature = $1 AND d.hash = $2 AND d.processed = false)
OR EXISTS (SELECT 1 FROM nft e WHERE e.signature = $1 AND e.hash = $2 AND e.processed = false)
OR EXISTS (SELECT 1 FROM marketing f WHERE f.signature = $1 AND f.hash = $2 AND f.processed = false)
OR EXISTS (SELECT 1 FROM vanity_address va WHERE va.signature = $1 AND va.hash = $2 AND va.processed = false)
OR EXISTS (SELECT 1 FROM swap g WHERE g.signature1 = $1 AND g.hash = $2 AND g.processed = false)
OR EXISTS (SELECT 1 FROM swap h WHERE h.signature2 = $1 AND h.hash = $2 AND h.processed = false)
OR EXISTS (SELECT 1 FROM loan_contract i WHERE i.signature1 = $1 AND i.hash = $2 AND i.processed = false)
OR EXISTS (SELECT 1 FROM loan_contract 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)
WHEN EXISTS (SELECT 1 FROM transfer a WHERE a.signature = $1 AND a.original = $2 AND a.processed = false)
OR EXISTS (SELECT 1 FROM token b WHERE b.signature = $1 AND b.original = $2 AND b.processed = false)
OR EXISTS (SELECT 1 FROM issue_token c WHERE c.signature = $1 AND c.original = $2 AND c.processed = false)
OR EXISTS (SELECT 1 FROM burn d WHERE d.signature = $1 AND d.original = $2 AND d.processed = false)
OR EXISTS (SELECT 1 FROM nft e WHERE e.signature = $1 AND e.original = $2 AND e.processed = false)
OR EXISTS (SELECT 1 FROM marketing f WHERE f.signature = $1 AND f.original = $2 AND f.processed = false)
OR EXISTS (SELECT 1 FROM vanity_address va WHERE va.signature = $1 AND va.original = $2 AND va.processed = false)
OR EXISTS (SELECT 1 FROM swap g WHERE g.signature1 = $1 AND g.original = $2 AND g.processed = false)
OR EXISTS (SELECT 1 FROM swap h WHERE h.signature2 = $1 AND h.original = $2 AND h.processed = false)
OR EXISTS (SELECT 1 FROM loan_contract i WHERE i.signature1 = $1 AND i.original = $2 AND i.processed = false)
OR EXISTS (SELECT 1 FROM loan_contract j WHERE j.signature2 = $1 AND j.original = $2 AND j.processed = false)
OR EXISTS (SELECT 1 FROM loan_payment k WHERE k.signature = $1 AND k.original = $2 AND k.processed = false)
OR EXISTS (SELECT 1 FROM collateral_claim l WHERE l.signature = $1 AND l.original = $2 AND l.processed = false)
OR EXISTS (SELECT 1 FROM storage_key sk WHERE sk.signature = $1 AND sk.original = $2 AND sk.processed = false)
OR EXISTS (SELECT 1 FROM storage_value sv WHERE sv.signature = $1 AND sv.original = $2 AND sv.processed = false)
THEN 1
ELSE 0
END AS signature_found;
"#,
&[&signature, &hash],
&[&signature, &original],
)
.await?;

View File

@ -225,9 +225,10 @@ mod schema;
mod selection;
pub use lookups::{
get_basecoin_balance, get_coin_balance, get_pending_payments_for_contract, largest_fee,
latest_pending_txids_by_address, pending_transaction_by_txid, signature_exists,
total_transactions, transaction_by_signature, transactions_by_address,
exact_transaction_exists, get_basecoin_balance, get_coin_balance,
get_pending_payments_for_contract, largest_fee, latest_pending_txids_by_address,
pending_transaction_by_txid, total_transactions, transaction_by_signature,
transactions_by_address,
};
pub use processing::{
delete_by_signatures, delete_unprocessed_by_address, mark_processed_by_signatures,

View File

@ -64,10 +64,12 @@ pub async fn load_block_from_binary(binary_data: &[u8]) -> Result<Block, String>
.map_err(|e| e.to_string())?;
let mut i = VRF_BLOCK_BYTES;
let mut transactions: Vec<Transaction> = Vec::new();
let mut original_transactions = Vec::new();
while i < binary_data.len() {
// Each transaction starts with its type byte, followed by the fixed-length body
// defined for that transaction family.
let transaction_start = i;
let txtype = binary_data[i];
i += 1;
let body_len = transaction_body_len(txtype)?;
@ -332,11 +334,13 @@ pub async fn load_block_from_binary(binary_data: &[u8]) -> Result<Block, String>
}
};
transactions.push(transaction);
original_transactions.push(binary_data[transaction_start..i].to_vec());
}
let block = Block {
vrf_block,
transactions,
original_transactions,
};
Ok(block)

View File

@ -92,10 +92,12 @@ pub async fn load_block(block_number: u32) -> Result<Block, String> {
.map_err(|e| e.to_string())?;
let mut i = VRF_BLOCK_BYTES;
let mut transactions: Vec<Transaction> = Vec::new();
let mut original_transactions = Vec::new();
while i < binary_data.len() {
// Each stored transaction begins with its type byte, followed by the fixed-size
// body for that transaction family.
let transaction_start = i;
let txtype = binary_data[i];
i += 1;
let body_len = transaction_body_len(txtype)?;
@ -349,11 +351,13 @@ pub async fn load_block(block_number: u32) -> Result<Block, String> {
}
};
transactions.push(transaction);
original_transactions.push(binary_data[transaction_start..i].to_vec());
}
let block = Block {
vrf_block,
transactions,
original_transactions,
};
Ok(block)

View File

@ -22,13 +22,16 @@ use crate::common::types::{
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::encode;
use crate::records::memory::connections::get_client_type_from_memory;
use crate::records::memory::enums::ClientType;
use crate::records::memory::mempool::exact_transaction_exists;
use crate::records::memory::response_channels::{reserve_transient_entry_with_context, Command};
use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION;
use crate::rpc::responses::RpcResponse;
use crate::sled::Db;
use crate::torrent::torrenting_system::get_nodes::get_live_node_broadcast_peers;
use crate::wallets::structures::Wallet;
use crate::Arc;
use crate::Mutex;
use crate::OnceLock;
@ -124,6 +127,27 @@ pub async fn save_and_submit(
// the declared transaction type.
// In each branch, a non-empty verifier response means the transaction
// already exists in the mempool and should not be rebroadcast.
// All submitted transaction families end with their final signature.
// Use that indexed signature to locate a candidate mempool row, then
// require the complete original bytes to match before reporting a duplicate.
if tx.len() >= Wallet::SIGNATURE_LENGTH {
let signature = encode(&tx[tx.len() - Wallet::SIGNATURE_LENGTH..]);
let mut original = Vec::with_capacity(1 + tx.len());
original.push(txtype);
original.extend_from_slice(&tx);
match exact_transaction_exists(&signature, &original).await {
Ok(true) => {
return RpcResponse::Binary(
b"successful_broadcast: false already_in_mempool".to_vec(),
);
}
Ok(false) => {}
Err(err) => {
return RpcResponse::Binary(format!("error: {err}").into_bytes());
}
}
}
match txtype {
TRANSFER_TYPE => match TransferTransaction::from_bytes(txtype, &tx).await {
Ok(transfer) => match transfer.verify(db).await {

View File

@ -4,9 +4,7 @@ use crate::common::nft_assets::nft_asset_name;
use crate::common::types::Transaction;
use crate::decode;
use crate::records::balance_sheet::get_wallet_balance::get_balance_with_db;
use crate::records::memory::mempool::{
get_basecoin_balance, get_coin_balance, signature_exists, BASECOIN,
};
use crate::records::memory::mempool::{get_basecoin_balance, get_coin_balance, BASECOIN};
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
use crate::rpc::responses::RpcResponse;
use crate::sled::Db;
@ -172,23 +170,6 @@ fn add_amount_to_total(
Ok(())
}
async fn transaction_is_pending(view: &TransactionBalanceView) -> Result<bool, String> {
for signature in &view.signatures {
if signature.is_empty() {
continue;
}
if signature_exists(signature, &view.hash)
.await
.map_err(|err| err.to_string())?
{
return Ok(true);
}
}
Ok(false)
}
async fn loan_contract_for_hash(
db: &Db,
contract_hash: &str,
@ -678,13 +659,13 @@ async fn pending_reserved_amount(db: &Db, address: &str, coin: &str) -> Result<u
pub(crate) async fn prepare_transaction_balance_reservation(
db: &Db,
transaction: &Transaction,
already_reserved_in_mempool: bool,
) -> Result<TransactionBalanceReservation, String> {
let view = transaction_balance_view(db, transaction).await?;
if view.check_saved_txid && saved_txid_exists(db, &view.hash)? {
return Err("This transaction already exists.".to_string());
}
let already_reserved_in_mempool = transaction_is_pending(&view).await?;
let loan_payment = if let Transaction::Borrower(tx) = transaction {
let payment = &tx.unsigned_contract_payment;
let contract = loan_contract_for_hash(db, &payment.contract_hash).await?;

View File

@ -1,6 +1,5 @@
// These lower-level checks are reused by the higher-level async verification routines.
pub mod balance_check;
pub mod block_balance;
pub mod mempool_check;
pub mod time_checks;
pub mod verify_db;

View File

@ -1,9 +1,11 @@
use crate::common::types::Transaction;
use crate::records::memory::mempool::exact_transaction_exists;
use crate::sled::Db;
use crate::verifications::async_funcs::checks::block_balance::{
prepare_transaction_balance_reservation, BlockBalanceTracker,
};
use crate::verifications::sync_funcs::transaction_verify_loop::COUNTER;
use crate::verifications::verification_types::VerifiableTransaction;
use crate::Arc;
use crate::AtomicBool;
use crate::AtomicOrdering;
@ -11,7 +13,7 @@ use crate::Mutex;
pub async fn verify_transactions(
miner: String,
transactions: Vec<Transaction>,
transactions: Vec<VerifiableTransaction>,
db: &Db,
stop_flag: Arc<AtomicBool>,
balance_tracker: Arc<Mutex<BlockBalanceTracker>>,
@ -78,15 +80,41 @@ pub async fn verify_transactions(
// normalize failures into a consistent error string.
async fn verify_transaction(
miner: String,
transaction: Transaction,
verifiable: VerifiableTransaction,
db: &Db,
balance_tracker: Arc<Mutex<BlockBalanceTracker>>,
block_timestamp: u32,
) -> Result<String, String> {
let transaction = verifiable.transaction;
let already_in_mempool = match (
transaction.mempool_signature(),
verifiable.original.as_deref(),
) {
(Some(signature), Some(original)) if !signature.is_empty() => {
exact_transaction_exists(signature, original)
.await
.map_err(|err| err.to_string())?
}
_ => false,
};
// Exact mempool bytes prove ordinary transactions already passed their
// complete type-specific validation. Collateral claims remain time-aware
// and must recheck their schedule against the containing block timestamp.
if already_in_mempool && !matches!(&transaction, Transaction::Collateral(_)) {
let signature = transaction
.mempool_signature()
.unwrap_or_default()
.to_string();
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool).await?;
return Ok(signature);
}
if let Transaction::Genesis(genesis_tx) = &transaction {
match genesis_tx.verify().await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -105,7 +133,13 @@ async fn verify_transaction(
"You cannot include more than 1 reward in a mined block.".to_string()
);
} else {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(
db,
&transaction,
balance_tracker,
already_in_mempool,
)
.await?;
return Ok(value);
}
}
@ -117,7 +151,8 @@ async fn verify_transaction(
if let Transaction::Transfer(transfer_tx) = &transaction {
match transfer_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -128,7 +163,8 @@ async fn verify_transaction(
if let Transaction::Burn(burn_tx) = &transaction {
match burn_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -139,7 +175,8 @@ async fn verify_transaction(
if let Transaction::Token(create_token_tx) = &transaction {
match create_token_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -152,7 +189,8 @@ async fn verify_transaction(
if let Transaction::IssueToken(issue_token_tx) = &transaction {
match issue_token_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -165,7 +203,8 @@ async fn verify_transaction(
if let Transaction::Nft(create_nft_tx) = &transaction {
match create_nft_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -178,7 +217,8 @@ async fn verify_transaction(
if let Transaction::Marketing(marketing_tx) = &transaction {
match marketing_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -191,7 +231,8 @@ async fn verify_transaction(
if let Transaction::Swap(swap_tx) = &transaction {
match swap_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -202,7 +243,8 @@ async fn verify_transaction(
if let Transaction::Lender(loan_creation_tx) = &transaction {
match loan_creation_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -215,7 +257,8 @@ async fn verify_transaction(
if let Transaction::Borrower(loan_payment_tx) = &transaction {
match loan_payment_tx.verify_for_block(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -226,9 +269,13 @@ async fn verify_transaction(
}
}
if let Transaction::Collateral(collateral_claim_tx) = &transaction {
match collateral_claim_tx.verify_at(db, block_timestamp).await {
match collateral_claim_tx
.verify_at(db, block_timestamp, already_in_mempool)
.await
{
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -241,7 +288,8 @@ async fn verify_transaction(
if let Transaction::Vanity(vanity_tx) = &transaction {
match vanity_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -254,7 +302,8 @@ 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?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -267,7 +316,8 @@ async fn verify_transaction(
if let Transaction::StorageBool(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -280,7 +330,8 @@ async fn verify_transaction(
if let Transaction::StorageU8(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -293,7 +344,8 @@ async fn verify_transaction(
if let Transaction::StorageU16(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -306,7 +358,8 @@ async fn verify_transaction(
if let Transaction::StorageU32(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -319,7 +372,8 @@ async fn verify_transaction(
if let Transaction::StorageU64(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -332,7 +386,8 @@ async fn verify_transaction(
if let Transaction::StorageU128(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -345,7 +400,8 @@ async fn verify_transaction(
if let Transaction::StorageString(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -358,7 +414,8 @@ async fn verify_transaction(
if let Transaction::StorageI8(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -371,7 +428,8 @@ async fn verify_transaction(
if let Transaction::StorageI16(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -384,7 +442,8 @@ async fn verify_transaction(
if let Transaction::StorageI32(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -397,7 +456,8 @@ async fn verify_transaction(
if let Transaction::StorageI64(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -410,7 +470,8 @@ async fn verify_transaction(
if let Transaction::StorageI128(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -423,7 +484,8 @@ async fn verify_transaction(
if let Transaction::DeleteKey(delete_key_tx) = &transaction {
match delete_key_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
reserve_verified_transaction(db, &transaction, balance_tracker, already_in_mempool)
.await?;
return Ok(value);
}
Err(err) => {
@ -443,10 +505,12 @@ async fn reserve_verified_transaction(
db: &Db,
transaction: &Transaction,
balance_tracker: Arc<Mutex<BlockBalanceTracker>>,
already_in_mempool: bool,
) -> Result<(), String> {
// Build the balance reservation while this worker still has the
// transaction in hand, then lock only the shared in-block totals.
let reservation = prepare_transaction_balance_reservation(db, transaction).await?;
let reservation =
prepare_transaction_balance_reservation(db, transaction, already_in_mempool).await?;
let mut tracker = balance_tracker.lock().await;
tracker.reserve(reservation)
}

View File

@ -1,7 +1,6 @@
use crate::blocks::block::{Block, UnminedBlock};
use crate::common::binary_conversions::hex_to_u64;
use crate::common::check_genesis::genesis_checkup;
use crate::common::types::Transaction;
use crate::encode;
use crate::miner::fairness::fairness_difficulty;
use crate::records::block_height::get_block_height::get_height;
@ -13,6 +12,7 @@ use crate::records::unpack_block::unpack_header::load_block_header;
use crate::records::wallet_registry::resolve_pubkey_from_short_address;
use crate::sled::Db;
use crate::verifications::verification_service::VerificationService;
use crate::verifications::verification_types::VerifiableTransaction;
use crate::wallets::structures::Wallet;
use crate::Arc;
use crate::Utc;
@ -97,9 +97,20 @@ impl Block {
.await?;
}
let transactions = self
.transactions
.iter()
.cloned()
.enumerate()
.map(|(index, transaction)| VerifiableTransaction {
transaction,
original: self.original_transactions.get(index).cloned(),
})
.collect();
let results = Self::send_transactions_to_validate(
miner.to_string(),
self.transactions.clone(),
transactions,
db.clone(),
verification_service,
timestamp,
@ -181,7 +192,7 @@ impl Block {
async fn send_transactions_to_validate(
miner: String,
transactions: Vec<Transaction>,
transactions: Vec<VerifiableTransaction>,
db: crate::sled::Db,
verification_service: Arc<VerificationService>,
block_timestamp: u32,

View File

@ -9,7 +9,6 @@ use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
use crate::rpc::responses::RpcResponse;
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::verifications::async_funcs::total_payments::get_total_payments;
use crate::wallets::structures::Wallet;
@ -32,12 +31,6 @@ impl ContractPaymentTransaction {
let hash = self.unsigned_contract_payment.hash().await;
let signature = &self.signature;
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
if memcheck(signature, &hash).await {
return Ok(signature.to_string());
}
// Loan payments must come from a valid wallet address and carry a
// valid signature from the payer.
let address = &self.unsigned_contract_payment.address;

View File

@ -9,7 +9,6 @@ use crate::records::wallet_registry::{
};
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_bytes_verification, db_hex_verification,
};
@ -21,12 +20,6 @@ impl BurnTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
let hash = self.unsigned_burn.hash().await;
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
if memcheck(&self.signature, &hash).await {
return Ok(self.signature.clone());
}
// Burn transactions are anchored to the sender wallet and the
// signed burn payload before any asset-specific rules apply.
if !Wallet::short_address_validation(&self.unsigned_burn.address) {

View File

@ -10,7 +10,6 @@ use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
use crate::rpc::responses::RpcResponse;
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::verifications::async_funcs::total_payments::get_total_payments;
use crate::wallets::structures::Wallet;
@ -19,19 +18,25 @@ use crate::{decode, encode};
impl CollateralClaimTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
self.verify_internal(db, Utc::now().timestamp() as u32, true)
self.verify_internal(db, Utc::now().timestamp() as u32, false)
.await
}
pub async fn verify_at(&self, db: &Db, as_of_timestamp: u32) -> Result<String, String> {
self.verify_internal(db, as_of_timestamp, false).await
pub async fn verify_at(
&self,
db: &Db,
as_of_timestamp: u32,
already_in_mempool: bool,
) -> Result<String, String> {
self.verify_internal(db, as_of_timestamp, already_in_mempool)
.await
}
async fn verify_internal(
&self,
db: &Db,
as_of_timestamp: u32,
allow_mempool_short_circuit: bool,
already_in_mempool: bool,
) -> Result<String, String> {
let hash = self.unsigned_collateral_claim.hash().await;
let signature = &self.signature;
@ -42,13 +47,6 @@ impl CollateralClaimTransaction {
);
}
let already_in_mempool = memcheck(signature, &hash).await;
// Submission can reuse an already verified mempool entry. Block
// validation still checks the schedule against the block timestamp.
if allow_mempool_short_circuit && already_in_mempool {
return Ok(signature.to_string());
}
// Collateral claims are valid only when the claimant signs the
// claim transaction with the same wallet in the payload.
if !Wallet::short_address_validation(&self.unsigned_collateral_claim.address) {

View File

@ -10,7 +10,6 @@ use crate::records::wallet_registry::{
use crate::sled::Db;
use crate::verifications::async_funcs::asset_rules::is_reserved_base_coin_ticker;
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
use crate::wallets::structures::Wallet;
use crate::Cid;
@ -38,12 +37,6 @@ impl CreateNftTransaction {
return Err("NFT name is reserved for the base coin.".to_string());
}
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
if memcheck(signature, hash).await {
return Ok(signature.to_string());
}
// NFT creation is anchored to a valid creator wallet and a valid
// signature over the unsigned NFT payload.
let creator = &self.unsigned_create_nft.creator;

View File

@ -9,7 +9,6 @@ use crate::records::wallet_registry::{
use crate::sled::Db;
use crate::verifications::async_funcs::asset_rules::is_reserved_base_coin_ticker;
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
use crate::verifications::async_funcs::checks::verify_db::{
db_bytes_verification, db_hex_verification,
};
@ -17,10 +16,7 @@ use crate::wallets::structures::Wallet;
impl CreateTokenTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
let hash = self.unsigned_create_token.hash().await;
let signature = &self.signature;
// Token tickers use the fixed padded coin-length format.
let ticker_length = self.unsigned_create_token.ticker.len();
@ -38,10 +34,6 @@ impl CreateTokenTransaction {
return Err("Ticker is reserved for the base coin.".to_string());
}
if memcheck(signature, &hash).await {
return Ok(self.signature.clone());
}
// Token creation is anchored to the creator's wallet signature.
if !Wallet::short_address_validation(&self.unsigned_create_token.creator) {
return Err("Sender Wallet Address is Invalid.".to_string());

View File

@ -6,7 +6,6 @@ use crate::records::wallet_registry::{
require_canonical_registered_short_address, resolve_pubkey_from_short_address,
};
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
use crate::wallets::structures::Wallet;
use crate::{decode, encode, sled::Db};
@ -16,10 +15,6 @@ impl DeleteKey {
let delete = &self.unsigned_deletekey;
let hash = delete.hash().await;
// Existing mempool entries short-circuit before the storage-state checks.
if memcheck(&self.signature, &hash).await {
return Ok(self.signature.clone());
}
if delete.txtype != DELETE_KEY_TYPE {
return Err("Delete-key transaction type is invalid.".to_string());
}

View File

@ -12,7 +12,6 @@ use crate::rpc::responses::RpcResponse;
use crate::sled::Db;
use crate::verifications::async_funcs::asset_rules::reserved_base_coins;
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_bytes_verification, db_hex_verification,
};
@ -50,13 +49,6 @@ fn token_hard_limit(db: &Db, ticker: &str) -> Option<u8> {
impl IssueTokenTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
let hash = self.unsigned_issue_token.hash().await;
let signature = &self.signature;
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
if memcheck(signature, &hash).await {
return Ok(self.signature.clone());
}
// Issuance is anchored to the creator's wallet signature.
if !Wallet::short_address_validation(&self.unsigned_issue_token.creator) {

View File

@ -11,7 +11,6 @@ use crate::records::wallet_registry::{
};
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::time_checks::is_within_30_days;
use crate::verifications::async_funcs::checks::verify_db::{
db_bytes_verification, db_hex_verification,
@ -21,13 +20,6 @@ use crate::wallets::structures::Wallet;
impl LoanContractTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
let calculated_hash = &self.unsigned_loan_contract.hash().await;
let signature2 = &self.signature2;
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
if memcheck(signature2, calculated_hash).await {
return Ok(signature2.to_string());
};
// Loan contracts require valid lender and borrower wallet addresses.
let lender = &self.unsigned_loan_contract.lender;

View File

@ -7,20 +7,13 @@ use crate::records::wallet_registry::{
};
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 MarketingTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
let hash = &self.unsigned_marketing.hash().await;
let signature = &self.signature;
if memcheck(signature, hash).await {
return Ok(signature.to_string());
}
// Marketing transactions must come from a valid advertiser wallet
// and carry a valid signature from that same wallet.
let advertiser = &self.unsigned_marketing.advertiser;

View File

@ -7,7 +7,6 @@ use crate::records::wallet_registry::{
};
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;
@ -21,13 +20,7 @@ impl StorageKey {
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.

View File

@ -17,7 +17,6 @@ use crate::records::wallet_registry::{
};
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;
@ -32,13 +31,6 @@ async fn verify_storage_value<T: StorageValueTransaction>(
// 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 {
@ -187,14 +179,6 @@ 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 {

View File

@ -9,7 +9,6 @@ use crate::records::wallet_registry::{
};
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::time_checks::is_within_30_days;
use crate::verifications::async_funcs::checks::verify_db::{
db_bytes_verification, db_hex_verification,
@ -36,13 +35,7 @@ fn validate_swap_tip(value: u64, tip: u64, is_nft: bool, sender: &str) -> Result
impl SwapTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
let hash = self.unsigned_swap.hash().await;
if memcheck(&self.signature2, &hash).await {
return Ok(self.signature2.clone());
}
// Both swap participants must provide valid wallet addresses and
// matching signatures over the shared unsigned swap payload.
if !(Wallet::short_address_validation(&self.unsigned_swap.sender1)

View File

@ -10,7 +10,6 @@ use crate::records::wallet_registry::{
};
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_bytes_verification, db_hex_verification,
};
@ -35,12 +34,6 @@ fn is_zero_burn_address(address: &str) -> bool {
impl TransferTransaction {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
let hash = self.unsigned_transfer.hash().await;
// Transactions that are already sitting in the mempool can skip
// the deeper verification work and return their stored signature.
if memcheck(&self.signature, &hash).await {
return Ok(self.signature.clone());
}
// Validate both wallet addresses and the signed transfer payload
// before checking balances and asset-specific rules.
if !(Wallet::short_address_validation(&self.unsigned_transfer.sender)

View File

@ -7,7 +7,6 @@ use crate::records::wallet_registry::{
};
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;
@ -17,12 +16,6 @@ impl VanityAddressTransaction {
// address-registration payload, so recompute that hash first.
let hash = self.unsigned_vanity_address.hash().await;
// Transactions already present in the mempool can short-circuit
// the deeper verification path and reuse their stored signature.
if memcheck(&self.signature, &hash).await {
return Ok(self.signature.clone());
}
// The paying wallet must already be a valid short address for the
// current network before registry ownership is checked.
let address = &self.unsigned_vanity_address.address;

View File

@ -1,11 +1,10 @@
use crate::common::types::Transaction;
use crate::rayon::ThreadPool;
use crate::rayon::ThreadPoolBuilder;
use crate::verifications::async_funcs::checks::block_balance::BlockBalanceTracker;
use crate::verifications::async_funcs::transactions::verify_transactions;
use crate::verifications::sync_funcs::transaction_verify_loop::COUNTER;
use crate::verifications::verification_types::{
VerificationChunkJob, VerificationChunkResult, VerificationJob,
VerifiableTransaction, VerificationChunkJob, VerificationChunkResult, VerificationJob,
};
use crate::Arc;
use crate::AtomicBool;
@ -75,7 +74,7 @@ impl VerificationService {
pub async fn verify_block_transactions(
&self,
miner: String,
transactions: Vec<Transaction>,
transactions: Vec<VerifiableTransaction>,
db: crate::sled::Db,
block_timestamp: u32,
) -> Result<Vec<String>, String> {

View File

@ -1,10 +1,16 @@
use crate::common::types::Transaction;
use crate::sled::Db;
#[derive(Clone)]
pub struct VerifiableTransaction {
pub transaction: Transaction,
pub original: Option<Vec<u8>>,
}
// VerificationJob represents one full transaction batch submitted to the verification service.
pub struct VerificationJob {
pub miner: String,
pub transactions: Vec<Transaction>,
pub transactions: Vec<VerifiableTransaction>,
pub db: Db,
pub block_timestamp: u32,
}
@ -12,7 +18,7 @@ pub struct VerificationJob {
// VerificationChunkJob is the per-worker slice created from a larger verification job.
pub struct VerificationChunkJob {
pub miner: String,
pub transactions: Vec<Transaction>,
pub transactions: Vec<VerifiableTransaction>,
pub db: Db,
pub block_timestamp: u32,
}