diff --git a/src/records/memory/mempool/selection.rs b/src/records/memory/mempool/selection.rs index 4a6539a..574c326 100644 --- a/src/records/memory/mempool/selection.rs +++ b/src/records/memory/mempool/selection.rs @@ -399,7 +399,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[sender, receiver, &miner], + &[sender, receiver], block_number, tx_index, &decode(hash)?, @@ -466,7 +466,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[creator, &miner], + &[creator], block_number, tx_index, &decode(hash)?, @@ -504,7 +504,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[creator, &miner], + &[creator], block_number, tx_index, &decode(hash)?, @@ -539,7 +539,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[address, &miner], + &[address], block_number, tx_index, &decode(hash)?, @@ -612,7 +612,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[creator, &miner], + &[creator], block_number, tx_index, &decode(hash)?, @@ -638,7 +638,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[advertiser, &miner], + &[advertiser], block_number, tx_index, &decode(hash)?, @@ -668,7 +668,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[address, &miner], + &[address], block_number, tx_index, &decode(hash)?, @@ -748,7 +748,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[sender1, sender2, &miner], + &[sender1, sender2], block_number, tx_index, &decode(hash)?, @@ -817,7 +817,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[lender, borrower, &miner], + &[lender, borrower], block_number, tx_index, &decode(hash)?, @@ -886,7 +886,7 @@ pub async fn apply_selected_transaction_math( let lender_address = binary_to_string(lender.clone()); index_selected_wallet_transaction( pending_effects, - &[address, &lender_address, &miner], + &[address, &lender_address], block_number, tx_index, &decode(hash)?, @@ -941,7 +941,7 @@ pub async fn apply_selected_transaction_math( ); index_selected_wallet_transaction( pending_effects, - &[address, &miner], + &[address], block_number, tx_index, &decode(hash)?, diff --git a/src/records/record_chain/borrower_tx.rs b/src/records/record_chain/borrower_tx.rs index 08a62f9..596a190 100644 --- a/src/records/record_chain/borrower_tx.rs +++ b/src/records/record_chain/borrower_tx.rs @@ -109,11 +109,7 @@ pub async fn process_borrower( pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[ - &transaction.unsigned_contract_payment.address, - &lender, - &miner, - ], + &[&transaction.unsigned_contract_payment.address, &lender], block_header_number, **index as u32, &txhash_bytes, diff --git a/src/records/record_chain/burn_tx.rs b/src/records/record_chain/burn_tx.rs index ffc550c..cab7ae3 100644 --- a/src/records/record_chain/burn_tx.rs +++ b/src/records/record_chain/burn_tx.rs @@ -73,7 +73,7 @@ pub async fn process_burn( pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[&transaction.unsigned_burn.address, &miner], + &[&transaction.unsigned_burn.address], block_header_number, **index as u32, &txhash_bytes, diff --git a/src/records/record_chain/collateral_tx.rs b/src/records/record_chain/collateral_tx.rs index 0dd3007..6403f02 100644 --- a/src/records/record_chain/collateral_tx.rs +++ b/src/records/record_chain/collateral_tx.rs @@ -99,7 +99,7 @@ pub async fn process_collateral( pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[&transaction.unsigned_collateral_claim.address, &miner], + &[&transaction.unsigned_collateral_claim.address], block_header_number, **index as u32, &txhash_bytes, diff --git a/src/records/record_chain/issue_token_tx.rs b/src/records/record_chain/issue_token_tx.rs index eb36d3b..b6c821e 100644 --- a/src/records/record_chain/issue_token_tx.rs +++ b/src/records/record_chain/issue_token_tx.rs @@ -70,7 +70,7 @@ pub async fn process_issue_token( pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[&transaction.unsigned_issue_token.creator, &miner], + &[&transaction.unsigned_issue_token.creator], block_header_number, **index as u32, &txkey, diff --git a/src/records/record_chain/lender_tx.rs b/src/records/record_chain/lender_tx.rs index 9a93357..625bd26 100644 --- a/src/records/record_chain/lender_tx.rs +++ b/src/records/record_chain/lender_tx.rs @@ -81,7 +81,6 @@ pub async fn process_lender( &[ &transaction.unsigned_loan_contract.lender, &transaction.unsigned_loan_contract.borrower, - &miner, ], block_header_number, **index as u32, diff --git a/src/records/record_chain/marketing_tx.rs b/src/records/record_chain/marketing_tx.rs index edfc469..e45fc58 100644 --- a/src/records/record_chain/marketing_tx.rs +++ b/src/records/record_chain/marketing_tx.rs @@ -54,7 +54,7 @@ pub async fn process_marketing( pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[&transaction.unsigned_marketing.advertiser, &miner], + &[&transaction.unsigned_marketing.advertiser], block_header_number, **index as u32, &txhash_bytes, diff --git a/src/records/record_chain/nft_tx.rs b/src/records/record_chain/nft_tx.rs index 13fd1be..4414fba 100644 --- a/src/records/record_chain/nft_tx.rs +++ b/src/records/record_chain/nft_tx.rs @@ -100,7 +100,7 @@ pub async fn process_nft( pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[&transaction.unsigned_create_nft.creator, &miner], + &[&transaction.unsigned_create_nft.creator], block_header_number, **index as u32, &txhash_bytes, diff --git a/src/records/record_chain/rewards_tx.rs b/src/records/record_chain/rewards_tx.rs index 71d1f48..c6de6b2 100644 --- a/src/records/record_chain/rewards_tx.rs +++ b/src/records/record_chain/rewards_tx.rs @@ -6,7 +6,7 @@ use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::block_height::get_block_height::get_height; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::PendingEffects; -use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction_direct; use crate::records::unpack_block::load_by_block_number::load_block; use crate::sled::Db; use crate::Arc; @@ -21,6 +21,8 @@ struct ImmatureReward { block_height: u32, miner: String, amount: u64, + txid: Vec, + entry_index: u32, } lazy_static! { @@ -63,6 +65,13 @@ fn credit_matured_reward(db: &Db, reward: ImmatureReward) -> Result<(), String> .map_err(|e| format!("Failed to finalize reward balance: {e}"))?; } + index_wallet_transaction_direct( + db, + &reward.miner, + reward.block_height, + reward.entry_index, + &reward.txid, + )?; mark_reward_credit_applied(db, reward.block_height) } @@ -71,6 +80,8 @@ pub async fn record_saved_reward( block_height: u32, miner: String, amount: u64, + txid: Vec, + entry_index: u32, ) -> Result<(), String> { let matured = { let mut rewards = IMMATURE_REWARDS.lock().await; @@ -79,6 +90,8 @@ pub async fn record_saved_reward( block_height, miner, amount, + txid, + entry_index, }); if rewards.len() > IMMATURE_REWARD_WINDOW { @@ -112,12 +125,18 @@ pub async fn rebuild_immature_reward_window(db: &Db) -> Result<(), String> { }; let miner = block.vrf_block.unmined_block.miner; - for transaction in block.transactions { + for (position, transaction) in block.transactions.into_iter().enumerate() { if let Transaction::Rewards(rewards) = transaction { + let txid = decode(rewards.unsigned.hash().await) + .map_err(|e| format!("Error decoding rebuilt reward txid key: {e}"))?; + let entry_index = u32::try_from(position + 1) + .map_err(|_| "Reward transaction index overflowed".to_string())?; rebuilt.push_back(ImmatureReward { block_height, miner, amount: rewards.unsigned.value, + txid, + entry_index, }); break; } @@ -138,7 +157,7 @@ pub async fn process_rewards( mut binary_data: Vec, _db: &Db, index_counter: Arc>, - miner: String, + _miner: String, block_header_number: u32, pending_effects: &mut PendingEffects, ) -> Result, String> { @@ -157,16 +176,9 @@ pub async fn process_rewards( binary_data.extend(transaction_bytes); // Reward processing records the txid location for later lookup by - // block/index, but does not make the reward spendable yet. + // block/index, but does not index it into wallet history until maturity. let txkey = decode(txhash).map_err(|e| format!("Error decoding rewards txid key: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); - pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); - index_wallet_transaction( - pending_effects, - &[&miner], - block_header_number, - **index as u32, - &txkey, - )?; + pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); Ok(binary_data) } diff --git a/src/records/record_chain/save.rs b/src/records/record_chain/save.rs index f400dbb..18088b2 100644 --- a/src/records/record_chain/save.rs +++ b/src/records/record_chain/save.rs @@ -15,7 +15,7 @@ use crate::records::memory::chain_state::{ cached_tip_hash, cached_tip_header, update_chain_state_after_save, }; use crate::records::memory::mempool::{ - apply_selected_transaction_math, ensure_db_connection, mark_processed_by_signatures, + apply_selected_transaction_math, mark_processed_by_signatures, mark_selected_transactions_processed, restore_processed_by_signatures, restore_selected_transactions_processed, select_transactions_for_block, spawn_processed_cleanup, stream_selected_transaction_originals, @@ -75,10 +75,6 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> { } } - ensure_db_connection() - .await - .map_err(|err| format!("Postgres reconnect failed before block save: {err}"))?; - let header_bytes = block .vrf_block .to_bytes() @@ -150,13 +146,17 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> { ) .await?; - let saved_reward_amount = block - .transactions - .iter() - .find_map(|transaction| match transaction { - Transaction::Rewards(rewards) => Some(rewards.unsigned.value), - _ => None, - }); + let mut saved_reward = None; + for (position, transaction) in block.transactions.iter().enumerate() { + if let Transaction::Rewards(rewards) = transaction { + let txid = decode(rewards.unsigned.hash().await) + .map_err(|e| format!("Error decoding saved reward txid key: {e}"))?; + let entry_index = u32::try_from(position + 1) + .map_err(|_| "Reward transaction index overflowed".to_string())?; + saved_reward = Some((rewards.unsigned.value, txid, entry_index)); + break; + } + } let start_index = { let index = index_mutex.lock().await; @@ -219,8 +219,16 @@ pub async fn save_block(params: SaveBlockParams) -> Result<(), String> { .await?; } - if let Some(amount) = saved_reward_amount { - if let Err(err) = record_saved_reward(&db, block_header_number, miner.clone(), amount).await + if let Some((amount, txid, entry_index)) = saved_reward { + if let Err(err) = record_saved_reward( + &db, + block_header_number, + miner.clone(), + amount, + txid, + entry_index, + ) + .await { error!( "Failed to update immature reward window for block {block_header_number}: {err}" diff --git a/src/records/record_chain/swap_tx.rs b/src/records/record_chain/swap_tx.rs index 732fcdf..3a8bf8b 100644 --- a/src/records/record_chain/swap_tx.rs +++ b/src/records/record_chain/swap_tx.rs @@ -126,7 +126,6 @@ pub async fn process_swap( &[ &transaction.unsigned_swap.sender1, &transaction.unsigned_swap.sender2, - &miner, ], block_header_number, **index as u32, diff --git a/src/records/record_chain/token_tx.rs b/src/records/record_chain/token_tx.rs index 6216f63..bd41a5a 100644 --- a/src/records/record_chain/token_tx.rs +++ b/src/records/record_chain/token_tx.rs @@ -81,7 +81,7 @@ pub async fn process_token( pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[&transaction.unsigned_create_token.creator, &miner], + &[&transaction.unsigned_create_token.creator], block_header_number, **index as u32, &txkey, diff --git a/src/records/record_chain/transfer_tx.rs b/src/records/record_chain/transfer_tx.rs index 6330762..61c66f8 100644 --- a/src/records/record_chain/transfer_tx.rs +++ b/src/records/record_chain/transfer_tx.rs @@ -75,7 +75,6 @@ pub async fn process_transfer( &[ &transaction.unsigned_transfer.sender, &transaction.unsigned_transfer.receiver, - &miner, ], block_header_number, **index as u32, diff --git a/src/records/record_chain/vanity_tx.rs b/src/records/record_chain/vanity_tx.rs index 5f54e12..e55ea1f 100644 --- a/src/records/record_chain/vanity_tx.rs +++ b/src/records/record_chain/vanity_tx.rs @@ -59,7 +59,7 @@ pub async fn process_vanity( pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); index_wallet_transaction( pending_effects, - &[owner_address, &miner], + &[owner_address], block_header_number, **index as u32, &txkey, diff --git a/src/records/record_chain/wallet_tx_index.rs b/src/records/record_chain/wallet_tx_index.rs index 06668d0..b44439f 100644 --- a/src/records/record_chain/wallet_tx_index.rs +++ b/src/records/record_chain/wallet_tx_index.rs @@ -45,6 +45,22 @@ pub fn index_wallet_transaction( Ok(()) } +pub fn index_wallet_transaction_direct( + db: &Db, + address: &str, + block_height: u32, + entry_index: u32, + txid: &[u8], +) -> Result<(), String> { + let key = wallet_tx_index_key(address, block_height, entry_index)?; + let tree = db + .open_tree(WALLET_TX_INDEX_TREE) + .map_err(|err| format!("Failed to open wallet transaction index: {err}"))?; + tree.insert(key, txid.to_vec()) + .map_err(|err| format!("Failed to insert wallet transaction index: {err}"))?; + Ok(()) +} + fn txid_block_height(db: &Db, txid: &[u8]) -> Result, String> { let tree = db .open_tree("txid") diff --git a/src/rpc/commands/receive_torrent.rs b/src/rpc/commands/receive_torrent.rs index 9044a58..be79518 100644 --- a/src/rpc/commands/receive_torrent.rs +++ b/src/rpc/commands/receive_torrent.rs @@ -34,6 +34,11 @@ use crate::Arc; use crate::Mutex; const LIVE_SYNC_HEIGHT_GAP: u32 = 10; +const STALE_NEXT_HEIGHT_ERROR: &str = "Incoming block is no longer the next expected height."; + +fn is_stale_next_height_error(error: &str) -> bool { + error.contains(STALE_NEXT_HEIGHT_ERROR) +} pub fn should_trigger_orphan_check(error: &str) -> bool { // These errors mean the incoming torrent may belong to a competing @@ -41,7 +46,6 @@ pub fn should_trigger_orphan_check(error: &str) -> bool { error.contains("Incorrect previous_block_hash.") || error.contains("Candidate parent is not current chain parent.") || error.contains("Difficulty mismatch with the blockchain data.") - || error.contains("Incoming block is no longer the next expected height.") || error.contains("Unable to read block") } @@ -299,6 +303,12 @@ pub async fn torrent_submission( .await; } Err(e) => { + if is_stale_next_height_error(&e) { + info!( + "[broadcast] ignored stale downloaded block: height={height} err={e}" + ); + return; + } error!("[broadcast] setup_download error: height={height} err={e}"); if should_trigger_orphan_check(&e) { trigger_orphan_check( diff --git a/src/torrent/torrenting_system/save_block.rs b/src/torrent/torrenting_system/save_block.rs index 1720bc5..1e2f89a 100644 --- a/src/torrent/torrenting_system/save_block.rs +++ b/src/torrent/torrenting_system/save_block.rs @@ -1,9 +1,8 @@ use crate::common::check_genesis::genesis_checkup; use crate::common::skein::skein_128_hash_bytes; -use crate::log::{error, warn}; +use crate::log::{error, info, warn}; use crate::miner::flag::is_reorganizing_mode; use crate::records::block_height::get_block_height::get_height; -use crate::records::memory::mempool::ensure_db_connection; use crate::records::record_chain::save::save_block; use crate::records::record_chain::structs::{SaveBlockParams, SaveType}; use crate::records::unpack_block::load_by_binary_data::load_block_from_binary; @@ -98,10 +97,6 @@ async fn verify_and_save_block_payload( return Err("Candidate previous hash does not match torrent metadata.".to_string()); } - ensure_db_connection() - .await - .map_err(|err| format!("Postgres reconnect failed before block verification: {err}"))?; - // Run full block verification before allowing the chain save path to persist the downloaded block. let signatures = match loaded_block .verify(¶ms.db, params.verification_service.clone()) @@ -178,7 +173,7 @@ async fn ensure_next_expected_height(params: &DownloadSave) -> Result<(), String }; if params.block_number < expected_height { - warn!( + info!( "[download] block is no longer next expected height: block_number={} current_height={} expected_height={}", params.block_number, current_height, diff --git a/src/verifications/async_funcs/verify_block.rs b/src/verifications/async_funcs/verify_block.rs index f02f5cc..35826bd 100644 --- a/src/verifications/async_funcs/verify_block.rs +++ b/src/verifications/async_funcs/verify_block.rs @@ -17,7 +17,7 @@ use crate::wallets::structures::Wallet; use crate::Arc; use crate::Utc; -const ALLOWED_FUTURE_BLOCK_SECONDS: u32 = 1; +const ALLOWED_FUTURE_BLOCK_SECONDS: u32 = 3; impl Block { pub async fn verify( @@ -82,7 +82,7 @@ impl Block { // validate hash Self::validate_hash_difficulty(db, hash).await?; - // Allow the same one-second clock skew tolerated during handshake, + // Allow the same three-second clock skew tolerated during handshake, // while the parent timestamp check below still enforces block spacing. if timestamp > current_timestamp.saturating_add(ALLOWED_FUTURE_BLOCK_SECONDS) { return Err("Timestamp in the block is in the future.".to_string()); @@ -148,7 +148,7 @@ impl Block { } // validate that the current timestamp is greater the previous - // block timestamp plus 2 seconds + // block timestamp plus 1 second let current_timestamp = Utc::now().timestamp() as u32; if current_timestamp < previous_block.unmined_block.timestamp + 1 { return Err("Mining to quickly".to_string());