201 lines
8.4 KiB
Rust
201 lines
8.4 KiB
Rust
|
|
use crate::blocks::swap::SwapTransaction;
|
||
|
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
||
|
|
use crate::common::nft_assets::nft_asset_name;
|
||
|
|
use crate::common::types::{COIN_LENGTH, SWAP_FEE};
|
||
|
|
use crate::encode;
|
||
|
|
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::time_checks::is_within_30_days;
|
||
|
|
use crate::verifications::async_funcs::checks::verify_db::{
|
||
|
|
db_bytes_verification, db_hex_verification,
|
||
|
|
};
|
||
|
|
use crate::wallets::structures::Wallet;
|
||
|
|
use crate::Utc;
|
||
|
|
|
||
|
|
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)
|
||
|
|
&& Wallet::short_address_validation(&self.unsigned_swap.sender2))
|
||
|
|
{
|
||
|
|
return Err("Sender1 or Sender2 Wallet Address is Invalid.".to_string());
|
||
|
|
}
|
||
|
|
require_canonical_registered_short_address(
|
||
|
|
db,
|
||
|
|
&self.unsigned_swap.sender1,
|
||
|
|
"Sender1 Wallet Address",
|
||
|
|
)?;
|
||
|
|
require_canonical_registered_short_address(
|
||
|
|
db,
|
||
|
|
&self.unsigned_swap.sender2,
|
||
|
|
"Sender2 Wallet Address",
|
||
|
|
)?;
|
||
|
|
|
||
|
|
// Sender1 must sign the same unsigned swap payload that sender2
|
||
|
|
// signs, proving agreement to the offered side of the swap.
|
||
|
|
let sender1_pubkey = resolve_pubkey_from_short_address(db, &self.unsigned_swap.sender1)
|
||
|
|
.map_err(|_| "Sender1 Wallet Address is not registered.".to_string())?
|
||
|
|
.ok_or_else(|| "Sender1 Wallet Address is not registered.".to_string())?;
|
||
|
|
let sender1_pubkey_hex = encode(&sender1_pubkey);
|
||
|
|
if !Wallet::verify_transaction_with_public_key(&hash, &self.signature1, &sender1_pubkey_hex)
|
||
|
|
.await
|
||
|
|
{
|
||
|
|
return Err("Invalid signature1 the RewardsTransaction.".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sender2's registered public key verifies the counterparty
|
||
|
|
// signature on the same swap hash.
|
||
|
|
let sender2_pubkey = resolve_pubkey_from_short_address(db, &self.unsigned_swap.sender2)
|
||
|
|
.map_err(|_| "Sender2 Wallet Address is not registered.".to_string())?
|
||
|
|
.ok_or_else(|| "Sender2 Wallet Address is not registered.".to_string())?;
|
||
|
|
let sender2_pubkey_hex = encode(&sender2_pubkey);
|
||
|
|
if !Wallet::verify_transaction_with_public_key(&hash, &self.signature2, &sender2_pubkey_hex)
|
||
|
|
.await
|
||
|
|
{
|
||
|
|
return Err("Invalid signature2 the RewardsTransaction.".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Asset names use the fixed padded coin-length format.
|
||
|
|
if self.unsigned_swap.ticker1.len() != COIN_LENGTH
|
||
|
|
|| self.unsigned_swap.ticker2.len() != COIN_LENGTH
|
||
|
|
{
|
||
|
|
return Err(
|
||
|
|
"Ticker lengths are invalid. Consider padding with empty spaces".to_string(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build concrete asset names for NFT series swaps before checking
|
||
|
|
// balances and registry existence.
|
||
|
|
let asset1 = nft_asset_name(&self.unsigned_swap.ticker1, self.unsigned_swap.nft_series1);
|
||
|
|
let asset2 = nft_asset_name(&self.unsigned_swap.ticker2, self.unsigned_swap.nft_series2);
|
||
|
|
|
||
|
|
// The base coin name comes from the active network settings so
|
||
|
|
// mainnet and testnet validate against their own ticker.
|
||
|
|
let network_base_coin = block_extension_and_paths().1.trim().to_lowercase();
|
||
|
|
|
||
|
|
// Numbered NFT swaps are single-item transfers, while non-series
|
||
|
|
// assets must already exist as a token, NFT, or base coin.
|
||
|
|
if self.unsigned_swap.nft_series1 > 0 {
|
||
|
|
if self.unsigned_swap.value1 != 1 {
|
||
|
|
return Err("Series NFTs must swap exactly 1 item.".to_string());
|
||
|
|
}
|
||
|
|
if !db_bytes_verification(db, "nfts", &asset1).await {
|
||
|
|
return Err("Ticker1 NFT item does not exist.".to_string());
|
||
|
|
}
|
||
|
|
} else if !(self.unsigned_swap.ticker1.trim().to_lowercase() == network_base_coin
|
||
|
|
|| db_bytes_verification(db, "tokens", &self.unsigned_swap.ticker1).await
|
||
|
|
|| db_bytes_verification(db, "nfts", &self.unsigned_swap.ticker1).await)
|
||
|
|
{
|
||
|
|
return Err("Ticker1 does not exist.".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
if self.unsigned_swap.nft_series2 > 0 {
|
||
|
|
if self.unsigned_swap.value2 != 1 {
|
||
|
|
return Err("Series NFTs must swap exactly 1 item.".to_string());
|
||
|
|
}
|
||
|
|
if !db_bytes_verification(db, "nfts", &asset2).await {
|
||
|
|
return Err("Ticker2 NFT item does not exist.".to_string());
|
||
|
|
}
|
||
|
|
} else if !(self.unsigned_swap.ticker2.trim().to_lowercase() == network_base_coin
|
||
|
|
|| db_bytes_verification(db, "tokens", &self.unsigned_swap.ticker2).await
|
||
|
|
|| db_bytes_verification(db, "nfts", &self.unsigned_swap.ticker2).await)
|
||
|
|
{
|
||
|
|
return Err("Ticker2 does not exist.".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Each signer pays the fixed swap fee for their side of the trade.
|
||
|
|
if self.unsigned_swap.txfee1 < SWAP_FEE {
|
||
|
|
return Err(format!(
|
||
|
|
"Swap sender1 transaction fee is below the minimum required fee of {SWAP_FEE}."
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
if self.unsigned_swap.txfee2 < SWAP_FEE {
|
||
|
|
return Err(format!(
|
||
|
|
"Swap sender2 transaction fee is below the minimum required fee of {SWAP_FEE}."
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Each side must be able to cover the offered amount plus any
|
||
|
|
// asset-denominated tip and the base-coin transaction fee.
|
||
|
|
let full_value1 = self.unsigned_swap.value1 + self.unsigned_swap.tip1;
|
||
|
|
if !balance_checkup(
|
||
|
|
db,
|
||
|
|
full_value1,
|
||
|
|
self.unsigned_swap.txfee1,
|
||
|
|
asset1.clone(),
|
||
|
|
&self.unsigned_swap.sender1,
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
{
|
||
|
|
return Err("Insuficient funds for this Swap Transaction!".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
let full_value2 = self.unsigned_swap.value2 + self.unsigned_swap.tip2;
|
||
|
|
if !balance_checkup(
|
||
|
|
db,
|
||
|
|
full_value2,
|
||
|
|
self.unsigned_swap.txfee2,
|
||
|
|
asset2,
|
||
|
|
&self.unsigned_swap.sender2,
|
||
|
|
)
|
||
|
|
.await
|
||
|
|
{
|
||
|
|
return Err("Insuficient funds for this Swap Transaction!".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Swap offers are bounded both by signature age and by the
|
||
|
|
// explicit offer-expiration window carried in the transaction.
|
||
|
|
if !is_within_30_days(self.unsigned_swap.timestamp).await {
|
||
|
|
return Err(
|
||
|
|
"Timestamp is to old. Transactions must be broadcast within 30 days of signing."
|
||
|
|
.to_string(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
let now = Utc::now().timestamp() as u32;
|
||
|
|
let offer_expiration = self.unsigned_swap.offer_expiration;
|
||
|
|
let timestamp = self.unsigned_swap.timestamp;
|
||
|
|
|
||
|
|
if offer_expiration < timestamp {
|
||
|
|
return Err(
|
||
|
|
"Offer expiration cannot be earlier than the transaction timestamp.".to_string(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if offer_expiration > timestamp.saturating_add(30 * 24 * 60 * 60) {
|
||
|
|
return Err(
|
||
|
|
"Offer expiration cannot be more than 30 days after the transaction timestamp."
|
||
|
|
.to_string(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if now > offer_expiration {
|
||
|
|
return Err("This swap offer has expired.".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Saved-chain duplicates are rejected by txid even if the mempool
|
||
|
|
// did not already contain the transaction.
|
||
|
|
let tree = "txid";
|
||
|
|
if !db_hex_verification(db, tree, &hash).await {
|
||
|
|
return Err("This transaction already exists.".to_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verification returns no auxiliary cleanup marker for this transaction type.
|
||
|
|
let sign = "";
|
||
|
|
Ok(sign.to_string())
|
||
|
|
}
|
||
|
|
}
|