/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use log::{debug, error, warn}; use tinyjson::JsonValue; use darkfi::{ net::P2pPtr, rpc::jsonrpc::JsonSubscriber, util::encoding::base64, validator::{consensus::Proposal, ValidatorPtr}, Error, Result, }; use darkfi_serial::serialize_async; use crate::proto::{ForkSyncRequest, ForkSyncResponse, ProposalMessage}; /// Background task to handle unknown proposals. pub async fn handle_unknown_proposal( validator: ValidatorPtr, p2p: P2pPtr, subscriber: JsonSubscriber, channel: u32, proposal: Proposal, ) -> Result<()> { // If proposal fork chain was not found, we ask our peer for its sequence debug!(target: "darkfid::task::handle_unknown_proposal", "Asking peer for fork sequence"); let Some(channel) = p2p.get_channel(channel) else { error!(target: "darkfid::task::handle_unknown_proposal", "Channel {channel} wasn't found."); return Ok(()) }; // Communication setup let Ok(response_sub) = channel.subscribe_msg::().await else { error!(target: "darkfid::task::handle_unknown_proposal", "Failure during `ForkSyncResponse` communication setup with peer: {channel:?}"); return Ok(()) }; // Grab last known block to create the request and execute it let last = match validator.blockchain.last() { Ok(l) => l, Err(e) => { debug!(target: "darkfid::task::handle_unknown_proposal", "Blockchain last retriaval failed: {e}"); return Ok(()) } }; let request = ForkSyncRequest { tip: last.1, fork_tip: Some(proposal.hash) }; if let Err(e) = channel.send(&request).await { debug!(target: "darkfid::task::handle_unknown_proposal", "Channel send failed: {e}"); return Ok(()) }; // Node waits for response let response = match response_sub .receive_with_timeout(p2p.settings().read().await.outbound_connect_timeout) .await { Ok(r) => r, Err(e) => { debug!(target: "darkfid::task::handle_unknown_proposal", "Asking peer for fork sequence failed: {e}"); return Ok(()) } }; debug!(target: "darkfid::task::handle_unknown_proposal", "Peer response: {response:?}"); // Verify and store retrieved proposals debug!(target: "darkfid::task::handle_unknown_proposal", "Processing received proposals"); // Response should not be empty if response.proposals.is_empty() { warn!(target: "darkfid::task::handle_unknown_proposal", "Peer responded with empty sequence, node might be out of sync!"); return Ok(()) } // Sequence length must correspond to requested height if response.proposals.len() as u32 != proposal.block.header.height - last.0 { debug!(target: "darkfid::task::handle_unknown_proposal", "Response sequence length is erroneous"); return Ok(()) } // First proposal must extend canonical if response.proposals[0].block.header.previous != last.1 { debug!(target: "darkfid::task::handle_unknown_proposal", "Response sequence doesn't extend canonical"); return Ok(()) } // Last proposal must be the same as the one requested if response.proposals.last().unwrap().hash != proposal.hash { debug!(target: "darkfid::task::handle_unknown_proposal", "Response sequence doesn't correspond to requested tip"); return Ok(()) } // Process response proposals for proposal in &response.proposals { // Append proposal match validator.append_proposal(proposal).await { Ok(()) => { /* Do nothing */ } // Skip already existing proposals Err(Error::ProposalAlreadyExists) => continue, Err(e) => { error!( target: "darkfid::task::handle_unknown_proposal", "Error while appending response proposal: {e}" ); break; } }; // Broadcast proposal to rest nodes let message = ProposalMessage(proposal.clone()); p2p.broadcast_with_exclude(&message, &[channel.address().clone()]).await; // Notify subscriber let enc_prop = JsonValue::String(base64::encode(&serialize_async(proposal).await)); subscriber.notify(vec![enc_prop].into()).await; } Ok(()) }