startup sync bug fixes
This commit is contained in:
parent
a40ec3a06e
commit
f651fda2dd
|
|
@ -4,6 +4,8 @@ use crate::records::block_height::get_block_height::get_height;
|
||||||
use crate::records::memory::network_mapping::NodeInfo;
|
use crate::records::memory::network_mapping::NodeInfo;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
|
const REWARD_MATURITY_BLOCKS: u8 = 100;
|
||||||
|
|
||||||
pub async fn calculate_block_reward(block_height: u32) -> u64 {
|
pub async fn calculate_block_reward(block_height: u32) -> u64 {
|
||||||
// Apply the fixed halving schedule based on the block
|
// Apply the fixed halving schedule based on the block
|
||||||
// height currently being mined or verified.
|
// height currently being mined or verified.
|
||||||
|
|
@ -37,7 +39,14 @@ pub async fn create_rewards_transaction(
|
||||||
|
|
||||||
// New miners must first prove participation before receiving
|
// New miners must first prove participation before receiving
|
||||||
// the block subsidy, so early mined blocks pay a zero reward.
|
// the block subsidy, so early mined blocks pay a zero reward.
|
||||||
let value = if NodeInfo::get_mined_count(short_address).await < 100 {
|
let value = if !NodeInfo::chain_mined_count_at_least(
|
||||||
|
short_address,
|
||||||
|
get_height(db),
|
||||||
|
REWARD_MATURITY_BLOCKS,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
0_u64
|
0_u64
|
||||||
} else {
|
} else {
|
||||||
calculate_block_reward(block_height).await
|
calculate_block_reward(block_height).await
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::records::memory::torrent_status::{
|
||||||
};
|
};
|
||||||
use crate::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
use crate::records::unpack_block::load_by_binary_data::load_block_from_binary;
|
||||||
use crate::records::unpack_block::unpack_header::load_block_header;
|
use crate::records::unpack_block::unpack_header::load_block_header;
|
||||||
|
use crate::rpc::client::block_hash_vote::request_block_hash_at_height;
|
||||||
use crate::torrent::structs::{DownloadSave, Torrent};
|
use crate::torrent::structs::{DownloadSave, Torrent};
|
||||||
use crate::torrent::torrenting_system::create_file::combine_pieces;
|
use crate::torrent::torrenting_system::create_file::combine_pieces;
|
||||||
use crate::torrent::torrenting_system::download_locks::acquire_candidate_download;
|
use crate::torrent::torrenting_system::download_locks::acquire_candidate_download;
|
||||||
|
|
@ -104,6 +105,19 @@ async fn candidate_attaches_before_rollback(
|
||||||
// Metadata may choose a candidate, but only downloaded block bytes can
|
// Metadata may choose a candidate, but only downloaded block bytes can
|
||||||
// prove the rollback is safe.
|
// prove the rollback is safe.
|
||||||
torrent.verify(height, ¶ms.db, wallet).await?;
|
torrent.verify(height, ¶ms.db, wallet).await?;
|
||||||
|
let peer_canonical_hash = request_block_hash_at_height(
|
||||||
|
params.stream.clone(),
|
||||||
|
params.map.clone(),
|
||||||
|
params.connections_key.clone(),
|
||||||
|
height,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
if peer_canonical_hash != torrent.info.block_hash {
|
||||||
|
return Err(format!(
|
||||||
|
"Staged candidate is not peer canonical at height {height}."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let _download_guard = acquire_candidate_download(height, &torrent.info.info_hash, true).await?;
|
let _download_guard = acquire_candidate_download(height, &torrent.info.info_hash, true).await?;
|
||||||
|
|
||||||
let verification_service = global_verification_service()
|
let verification_service = global_verification_service()
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,14 @@ pub async fn orphan_window_check(params: CheckUp, wallet: Arc<Wallet>) -> Result
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let shared_tip = params.local_height.min(params.remote_height);
|
||||||
|
let shared_window_floor = include_recheck_floor(shared_tip.saturating_sub(10));
|
||||||
|
|
||||||
if height_diff == 0 {
|
if height_diff == 0 {
|
||||||
// same height means compare the last ten blocks directly
|
// same height means compare the last ten blocks directly
|
||||||
let start_check = params.local_height;
|
let start_check = shared_tip;
|
||||||
let original_start_check = params.local_height;
|
let original_start_check = shared_tip;
|
||||||
let stop_check = include_recheck_floor(params.local_height.saturating_sub(10));
|
let stop_check = shared_window_floor;
|
||||||
let orphan_checkup_params = OrphanCheckup {
|
let orphan_checkup_params = OrphanCheckup {
|
||||||
start_check,
|
start_check,
|
||||||
stop_check,
|
stop_check,
|
||||||
|
|
@ -42,12 +45,9 @@ pub async fn orphan_window_check(params: CheckUp, wallet: Arc<Wallet>) -> Result
|
||||||
} else if height_diff <= 10 && params.local_height > params.remote_height {
|
} else if height_diff <= 10 && params.local_height > params.remote_height {
|
||||||
// if the local chain is slightly ahead, begin comparison from
|
// if the local chain is slightly ahead, begin comparison from
|
||||||
// the remote height and only search within the overlap window
|
// the remote height and only search within the overlap window
|
||||||
let start_check = params.remote_height;
|
let start_check = shared_tip;
|
||||||
let original_start_check = params.remote_height;
|
let original_start_check = shared_tip;
|
||||||
// The farther apart the tips are, the less backward overlap remains
|
let stop_check = shared_window_floor;
|
||||||
// inside the ten-block correction window.
|
|
||||||
let stop_check =
|
|
||||||
include_recheck_floor(params.remote_height.saturating_sub(10 - height_diff));
|
|
||||||
let orphan_checkup_params = OrphanCheckup {
|
let orphan_checkup_params = OrphanCheckup {
|
||||||
start_check,
|
start_check,
|
||||||
stop_check,
|
stop_check,
|
||||||
|
|
@ -65,12 +65,9 @@ pub async fn orphan_window_check(params: CheckUp, wallet: Arc<Wallet>) -> Result
|
||||||
} else if height_diff <= 10 && params.local_height < params.remote_height {
|
} else if height_diff <= 10 && params.local_height < params.remote_height {
|
||||||
// if the remote chain is slightly ahead, start at the local tip
|
// if the remote chain is slightly ahead, start at the local tip
|
||||||
// and search backward only within the valid orphan range
|
// and search backward only within the valid orphan range
|
||||||
let start_check = params.local_height;
|
let start_check = shared_tip;
|
||||||
let original_start_check = params.local_height;
|
let original_start_check = shared_tip;
|
||||||
// Search only the portion of local history that could still be
|
let stop_check = shared_window_floor;
|
||||||
// replaced by staged remote candidates.
|
|
||||||
let stop_check =
|
|
||||||
include_recheck_floor(params.local_height.saturating_sub(10 - height_diff));
|
|
||||||
let orphan_checkup_params = OrphanCheckup {
|
let orphan_checkup_params = OrphanCheckup {
|
||||||
start_check,
|
start_check,
|
||||||
stop_check,
|
stop_check,
|
||||||
|
|
|
||||||
|
|
@ -244,10 +244,16 @@ async fn sync_checkup_pass(params: &OrphanCheckup2, wallet: Arc<Wallet>) -> Resu
|
||||||
match orphan_window_check(checkup_params, wallet.clone()).await {
|
match orphan_window_check(checkup_params, wallet.clone()).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if should_retry_staged_candidate(&err)
|
let height_after_window_check = get_height(¶ms.db);
|
||||||
&& get_height(¶ms.db) < height_before_window_check
|
let rolled_back = height_after_window_check < height_before_window_check;
|
||||||
{
|
if rolled_back {
|
||||||
replay_waiting = true;
|
if should_retry_staged_candidate(&err) {
|
||||||
|
replay_waiting = true;
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"orphan window adoption failed after rollback: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
error!("[orphan] orphan window check error: {err}");
|
error!("[orphan] orphan window check error: {err}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -884,6 +884,34 @@ pub async fn mark_peer_operational(key: &str, map: Arc<Mutex<Command>>) -> bool
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn peer_is_operational(key: &str) -> bool {
|
||||||
|
let Some((ip, port)) = split_ip_port_key(key) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let ip_bytes = ip_to_binary(&ip);
|
||||||
|
|
||||||
|
CONNECTIONS
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|connection| {
|
||||||
|
connection
|
||||||
|
.connection_map
|
||||||
|
.iter()
|
||||||
|
.find_map(|(connection_key, info)| {
|
||||||
|
if connection_key.ip == ip_bytes
|
||||||
|
&& connection_key.port == port
|
||||||
|
&& ClientType::from_bytes(&info.client_type) == Some(ClientType::Miner)
|
||||||
|
{
|
||||||
|
Some(info.ready)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn live_miner_peer_streams() -> Vec<(String, Arc<Mutex<TcpStream>>)> {
|
pub async fn live_miner_peer_streams() -> Vec<(String, Arc<Mutex<TcpStream>>)> {
|
||||||
// Snapshot consensus and recovery checks vote only across currently
|
// Snapshot consensus and recovery checks vote only across currently
|
||||||
// connected miner peers, regardless of incoming/outgoing direction.
|
// connected miner peers, regardless of incoming/outgoing direction.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ use crate::records::memory::response_channels::reserve_transient_entry_with_cont
|
||||||
|
|
||||||
const ONE_HOUR_MILLIS: u64 = 3_600_000;
|
const ONE_HOUR_MILLIS: u64 = 3_600_000;
|
||||||
|
|
||||||
|
fn connection_key_ip(connections_key: &str) -> Option<&str> {
|
||||||
|
connections_key.rsplit_once(':').map(|(ip, _)| ip)
|
||||||
|
}
|
||||||
|
|
||||||
impl NodeInfo {
|
impl NodeInfo {
|
||||||
pub async fn import_signed_mapping_address(
|
pub async fn import_signed_mapping_address(
|
||||||
db: &Db,
|
db: &Db,
|
||||||
|
|
@ -216,9 +220,15 @@ impl NodeInfo {
|
||||||
return RpcResponse::Binary(b"Error: Invalid network address".to_vec());
|
return RpcResponse::Binary(b"Error: Invalid network address".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locally initiated edits are re-signed with the local wallet and
|
let unsigned_self_announcement = edit.modified_by.is_empty()
|
||||||
// current timestamp so they can be propagated as fresh node events.
|
&& (edit.ip == remote_ip
|
||||||
if edit.ip == remote_ip {
|
|| connection_key_ip(&connections_key)
|
||||||
|
.map(|connection_ip| edit.ip == connection_ip)
|
||||||
|
.unwrap_or(false));
|
||||||
|
|
||||||
|
// Unsigned self-announcements are sponsored by the receiving node
|
||||||
|
// after the claimed IP is tied to the live peer connection.
|
||||||
|
if unsigned_self_announcement {
|
||||||
edit.modified_timestamp = current_timestamp;
|
edit.modified_timestamp = current_timestamp;
|
||||||
edit.modified_by = wallet.saved.short_address.clone();
|
edit.modified_by = wallet.saved.short_address.clone();
|
||||||
edit.modified_signature =
|
edit.modified_signature =
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
use crate::records::unpack_block::unpack_header::load_block_header;
|
use crate::records::unpack_block::unpack_header::load_block_header;
|
||||||
|
|
||||||
impl NodeInfo {
|
impl NodeInfo {
|
||||||
|
|
@ -37,6 +38,30 @@ impl NodeInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn chain_mined_count_at_least(
|
||||||
|
address: &str,
|
||||||
|
through_height: u32,
|
||||||
|
threshold: u8,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
if threshold == 0 {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mined_count = 0_u8;
|
||||||
|
let start_height = if through_height > 0 { 1 } else { 0 };
|
||||||
|
for block_number in start_height..=through_height {
|
||||||
|
let header = load_block_header(block_number).await?;
|
||||||
|
if header.unmined_block.miner == address {
|
||||||
|
mined_count = mined_count.saturating_add(1);
|
||||||
|
if mined_count >= threshold {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn set_deleted_block_from_mapping(address: &str, deleted_block: u32) {
|
pub async fn set_deleted_block_from_mapping(address: &str, deleted_block: u32) {
|
||||||
let mut map = ADDRESS_MAP.lock().await;
|
let mut map = ADDRESS_MAP.lock().await;
|
||||||
if let Some(node_info) = map.get_mut(address) {
|
if let Some(node_info) = map.get_mut(address) {
|
||||||
|
|
@ -67,6 +92,16 @@ impl NodeInfo {
|
||||||
let current_height = get_height(db);
|
let current_height = get_height(db);
|
||||||
let mut mined_counts: HashMap<String, u8> = HashMap::new();
|
let mut mined_counts: HashMap<String, u8> = HashMap::new();
|
||||||
|
|
||||||
|
if current_height == 0 && !genesis_checkup().await {
|
||||||
|
let mut map = ADDRESS_MAP.lock().await;
|
||||||
|
for node_info in map.values_mut() {
|
||||||
|
node_info.blocks_mined = 0;
|
||||||
|
}
|
||||||
|
drop(map);
|
||||||
|
Self::persist_recovery_snapshot("mined rebuild without genesis").await;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let start_height = if current_height > 0 { 1 } else { 0 };
|
let start_height = if current_height > 0 { 1 } else { 0 };
|
||||||
for block_number in start_height..=current_height {
|
for block_number in start_height..=current_height {
|
||||||
let header = load_block_header(block_number).await?;
|
let header = load_block_header(block_number).await?;
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,13 @@ pub struct BootstrapParams {
|
||||||
|
|
||||||
pub fn spawn_bootstrap_peer_discovery(params: BootstrapParams) {
|
pub fn spawn_bootstrap_peer_discovery(params: BootstrapParams) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
let run_startup_sync = params.run_startup_sync;
|
||||||
if let Err(e) = bootstrap_peer_discovery(params).await {
|
if let Err(e) = bootstrap_peer_discovery(params).await {
|
||||||
set_node_mode(NodeMode::Normal);
|
if !run_startup_sync {
|
||||||
clear_mining_stop_request();
|
set_node_mode(NodeMode::Normal);
|
||||||
set_mining_state(MiningState::Idle);
|
clear_mining_stop_request();
|
||||||
|
set_mining_state(MiningState::Idle);
|
||||||
|
}
|
||||||
eprintln!("[bootstrap] error: {e}");
|
eprintln!("[bootstrap] error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -233,7 +236,7 @@ pub async fn bootstrap_peer_discovery(mut params: BootstrapParams) -> Result<(),
|
||||||
};
|
};
|
||||||
match sync_checkup(orphan_checkup_params, params.wallet.clone()).await {
|
match sync_checkup(orphan_checkup_params, params.wallet.clone()).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) => warn!("[sync] Post-sync orphan check error: {err}"),
|
Err(err) => return Err(format!("Post-sync orphan check error: {err}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,19 @@ pub async fn node_syncing(
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("[sync] error saving block: height={local_height} err={err}");
|
warn!("[sync] error saving block: height={local_height} err={err}");
|
||||||
|
|
||||||
|
if err.contains("Invalid reward for the Rewards Transaction")
|
||||||
|
|| err.contains("This address is not eligable to mine")
|
||||||
|
|| err.contains("This miner address is not registered")
|
||||||
|
|| err.contains("Miner wallet address is not registered")
|
||||||
|
{
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"sync rejected canonical peer block at height {local_height}: {err}"
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh local chain state before triggering orphan
|
// Refresh local chain state before triggering orphan
|
||||||
// handling so the reconciliation logic starts from the
|
// handling so the reconciliation logic starts from the
|
||||||
// real saved height.
|
// real saved height.
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
use crate::common::check_genesis::genesis_checkup;
|
use crate::common::check_genesis::genesis_checkup;
|
||||||
use crate::common::skein::skein_128_hash_bytes;
|
use crate::common::skein::skein_128_hash_bytes;
|
||||||
use crate::log::{error, warn};
|
use crate::log::{error, info, warn};
|
||||||
use crate::miner::flag::{is_normal_mode, is_reorganizing_mode, is_syncing_mode};
|
use crate::miner::flag::{
|
||||||
|
clear_mining_stop_request, is_normal_mode, is_reorganizing_mode, is_syncing_mode,
|
||||||
|
request_mining_stop, set_mining_state, set_node_mode, wait_for_mining_idle, MiningState,
|
||||||
|
NodeMode,
|
||||||
|
};
|
||||||
use crate::orphans::checkup_state::{
|
use crate::orphans::checkup_state::{
|
||||||
finish_orphan_check, request_orphan_recheck, try_begin_orphan_check,
|
finish_orphan_check, request_orphan_recheck, try_begin_orphan_check,
|
||||||
};
|
};
|
||||||
use crate::orphans::structs::OrphanCheckup2;
|
use crate::orphans::structs::OrphanCheckup2;
|
||||||
use crate::orphans::sync_check::sync_checkup;
|
use crate::orphans::sync_check::sync_checkup;
|
||||||
use crate::records::block_height::get_block_height::get_height;
|
use crate::records::block_height::get_block_height::get_height;
|
||||||
|
use crate::records::memory::connections::{get_client_type_from_memory, peer_is_operational};
|
||||||
|
use crate::records::memory::enums::ClientType;
|
||||||
use crate::records::memory::response_channels::Command;
|
use crate::records::memory::response_channels::Command;
|
||||||
|
use crate::rpc::client::syncing::node_syncing;
|
||||||
use crate::rpc::read_bytes_from_stream;
|
use crate::rpc::read_bytes_from_stream;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::rpc::server::flood_protection::MAX_TORRENT_METADATA_BYTES;
|
use crate::rpc::server::flood_protection::MAX_TORRENT_METADATA_BYTES;
|
||||||
|
|
@ -26,6 +33,8 @@ use crate::wallets::structures::Wallet;
|
||||||
use crate::Arc;
|
use crate::Arc;
|
||||||
use crate::Mutex;
|
use crate::Mutex;
|
||||||
|
|
||||||
|
const LIVE_SYNC_HEIGHT_GAP: u32 = 10;
|
||||||
|
|
||||||
pub fn should_trigger_orphan_check(error: &str) -> bool {
|
pub fn should_trigger_orphan_check(error: &str) -> bool {
|
||||||
// These errors mean the incoming torrent may belong to a competing
|
// These errors mean the incoming torrent may belong to a competing
|
||||||
// branch, so a targeted orphan check is worth attempting.
|
// branch, so a targeted orphan check is worth attempting.
|
||||||
|
|
@ -53,6 +62,72 @@ fn within_orphan_window(local_height: u32, incoming_height: u32) -> bool {
|
||||||
local_height.abs_diff(incoming_height) <= 10
|
local_height.abs_diff(incoming_height) <= 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn run_live_catchup_sync(
|
||||||
|
stream: Arc<Mutex<crate::TcpStream>>,
|
||||||
|
db: &Db,
|
||||||
|
wallet: Arc<Wallet>,
|
||||||
|
map: Arc<Mutex<Command>>,
|
||||||
|
connections_key: String,
|
||||||
|
local_height: u32,
|
||||||
|
remote_height: u32,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
info!(
|
||||||
|
"[sync] live catch-up started: local_height={local_height} remote_height={remote_height}"
|
||||||
|
);
|
||||||
|
set_node_mode(NodeMode::Syncing);
|
||||||
|
request_mining_stop();
|
||||||
|
wait_for_mining_idle().await;
|
||||||
|
|
||||||
|
let sync_result = node_syncing(
|
||||||
|
stream.clone(),
|
||||||
|
db,
|
||||||
|
remote_height,
|
||||||
|
map.clone(),
|
||||||
|
true,
|
||||||
|
wallet.clone(),
|
||||||
|
connections_key.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Live catch-up sync failed: {err}"));
|
||||||
|
|
||||||
|
if sync_result.is_ok() {
|
||||||
|
let post_sync_local_height = get_height(db);
|
||||||
|
let post_sync_remote_height =
|
||||||
|
match request_remote_height(stream.clone(), map.clone(), connections_key.clone()).await
|
||||||
|
{
|
||||||
|
Ok(height) => height,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("[sync] live catch-up failed to refresh post-sync remote height: {err}");
|
||||||
|
remote_height
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if post_sync_remote_height != post_sync_local_height {
|
||||||
|
let orphan_checkup_params = OrphanCheckup2 {
|
||||||
|
stream,
|
||||||
|
db: db.clone(),
|
||||||
|
local_height: post_sync_local_height,
|
||||||
|
remote_height: post_sync_remote_height,
|
||||||
|
recheck_from_height: Some(post_sync_local_height.min(post_sync_remote_height)),
|
||||||
|
map,
|
||||||
|
node_syncing: true,
|
||||||
|
connections_key,
|
||||||
|
};
|
||||||
|
if let Err(err) = sync_checkup(orphan_checkup_params, wallet).await {
|
||||||
|
warn!("[sync] live catch-up post-sync orphan check error: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_node_mode(NodeMode::Normal);
|
||||||
|
clear_mining_stop_request();
|
||||||
|
set_mining_state(MiningState::Idle);
|
||||||
|
if sync_result.is_ok() {
|
||||||
|
info!("[sync] live catch-up complete, normal mode restored");
|
||||||
|
}
|
||||||
|
sync_result
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn trigger_orphan_check(
|
pub async fn trigger_orphan_check(
|
||||||
reason: &str,
|
reason: &str,
|
||||||
incoming_height: u32,
|
incoming_height: u32,
|
||||||
|
|
@ -87,20 +162,37 @@ pub async fn trigger_orphan_check(
|
||||||
warn!(
|
warn!(
|
||||||
"[broadcast] triggering orphan check: reason={reason} local_height={local_height} remote_height={remote_height}"
|
"[broadcast] triggering orphan check: reason={reason} local_height={local_height} remote_height={remote_height}"
|
||||||
);
|
);
|
||||||
let orphan_checkup_params = OrphanCheckup2 {
|
|
||||||
stream,
|
let result = if remote_height > local_height.saturating_add(LIVE_SYNC_HEIGHT_GAP) {
|
||||||
db: db.clone(),
|
run_live_catchup_sync(
|
||||||
local_height,
|
stream,
|
||||||
remote_height,
|
db,
|
||||||
recheck_from_height: Some(incoming_height),
|
wallet,
|
||||||
map,
|
map,
|
||||||
node_syncing: false,
|
connections_key,
|
||||||
connections_key,
|
local_height,
|
||||||
};
|
remote_height,
|
||||||
match sync_checkup(orphan_checkup_params, wallet).await {
|
)
|
||||||
Ok(()) => {}
|
.await
|
||||||
Err(err) => error!("[broadcast] orphan check error: {err}"),
|
} else {
|
||||||
|
let orphan_checkup_params = OrphanCheckup2 {
|
||||||
|
stream,
|
||||||
|
db: db.clone(),
|
||||||
|
local_height,
|
||||||
|
remote_height,
|
||||||
|
recheck_from_height: Some(incoming_height),
|
||||||
|
map,
|
||||||
|
node_syncing: false,
|
||||||
|
connections_key,
|
||||||
|
};
|
||||||
|
sync_checkup(orphan_checkup_params, wallet)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Orphan check failed: {err}"))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Err(err) = result {
|
||||||
|
error!("[broadcast] orphan/sync recovery error: {err}");
|
||||||
|
}
|
||||||
finish_orphan_check();
|
finish_orphan_check();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,6 +399,22 @@ pub async fn receive_torrent(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if get_client_type_from_memory(connections_key).await == Some(ClientType::Miner)
|
||||||
|
&& !peer_is_operational(connections_key).await
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"[broadcast] ignored torrent from non-operational peer: peer={connections_key} height={block_number}"
|
||||||
|
);
|
||||||
|
return Ok((
|
||||||
|
uid,
|
||||||
|
RpcResponse::Binary(
|
||||||
|
"Torrent ignored from non-operational peer."
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let outcome = torrent_submission(
|
let outcome = torrent_submission(
|
||||||
block_number,
|
block_number,
|
||||||
torrent_bytes,
|
torrent_bytes,
|
||||||
|
|
|
||||||
|
|
@ -135,9 +135,9 @@ async fn sync_incoming_peer_before_operational(
|
||||||
node_syncing: true,
|
node_syncing: true,
|
||||||
connections_key: connections_key.to_string(),
|
connections_key: connections_key.to_string(),
|
||||||
};
|
};
|
||||||
if let Err(err) = sync_checkup(orphan_checkup_params, wallet).await {
|
sync_checkup(orphan_checkup_params, wallet)
|
||||||
warn!("[sync] Incoming post-sync orphan check error: {err}");
|
.await
|
||||||
}
|
.map_err(|err| format!("Incoming post-sync orphan check error: {err}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,12 @@ pub async fn announce_self_to_network(
|
||||||
Some(bytes) => bytes,
|
Some(bytes) => bytes,
|
||||||
None => return Err("local node address was invalid".to_string()),
|
None => return Err("local node address was invalid".to_string()),
|
||||||
};
|
};
|
||||||
let modified_by_bytes = vec![0u8; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
|
||||||
let time = Utc::now().timestamp_millis() as u64;
|
let time = Utc::now().timestamp_millis() as u64;
|
||||||
let modified_timestamp_bytes = time.to_le_bytes();
|
let modified_timestamp_bytes = time.to_le_bytes();
|
||||||
// Self-announcement is intentionally unsigned. The receiving node
|
// Self-announcement is intentionally unsigned. The receiving node
|
||||||
// adopts it by re-signing the membership edit with its own wallet.
|
// sponsors it by signing the membership edit after confirming the
|
||||||
|
// announcement belongs to the live peer connection.
|
||||||
|
let modified_by_bytes = vec![0u8; Wallet::SHORT_ADDRESS_BYTES_LENGTH];
|
||||||
let modified_signature_bytes = vec![0u8; Wallet::SIGNATURE_LENGTH];
|
let modified_signature_bytes = vec![0u8; Wallet::SIGNATURE_LENGTH];
|
||||||
|
|
||||||
let mut message: Vec<u8> = Vec::with_capacity(
|
let mut message: Vec<u8> = Vec::with_capacity(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ use crate::records::wallet_registry::is_registered_short_address;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
|
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
|
||||||
|
|
||||||
|
const REWARD_MATURITY_BLOCKS: u8 = 100;
|
||||||
|
|
||||||
impl RewardsTransaction {
|
impl RewardsTransaction {
|
||||||
pub async fn verify(&self, miner: String, db: &Db) -> Result<String, String> {
|
pub async fn verify(&self, miner: String, db: &Db) -> Result<String, String> {
|
||||||
// Rewards are tied to the next block height and only begin after
|
// Rewards are tied to the next block height and only begin after
|
||||||
|
|
@ -23,7 +25,13 @@ impl RewardsTransaction {
|
||||||
|
|
||||||
// New miners receive zero reward until their mined-count history
|
// New miners receive zero reward until their mined-count history
|
||||||
// reaches the maturity threshold.
|
// reaches the maturity threshold.
|
||||||
let reward_value = if NodeInfo::get_mined_count(&miner).await < 100 {
|
let reward_value = if !NodeInfo::chain_mined_count_at_least(
|
||||||
|
&miner,
|
||||||
|
previous_height,
|
||||||
|
REWARD_MATURITY_BLOCKS,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
0_u64
|
0_u64
|
||||||
} else {
|
} else {
|
||||||
calculate_block_reward(previous_height + 1).await
|
calculate_block_reward(previous_height + 1).await
|
||||||
|
|
@ -32,7 +40,12 @@ impl RewardsTransaction {
|
||||||
// The unsigned reward value must exactly match the deterministic
|
// The unsigned reward value must exactly match the deterministic
|
||||||
// reward calculation for the block being created.
|
// reward calculation for the block being created.
|
||||||
if value != reward_value {
|
if value != reward_value {
|
||||||
return Err("Invalid reward for the Rewards Transaction.".to_string());
|
return Err(format!(
|
||||||
|
"Invalid reward for the Rewards Transaction. miner={miner} height={} actual={} expected={}",
|
||||||
|
previous_height + 1,
|
||||||
|
value,
|
||||||
|
reward_value
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recompute the reward txid after the value check so duplicate
|
// Recompute the reward txid after the value check so duplicate
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue