| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- /* 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 <https://www.gnu.org/licenses/>.
- */
- 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::<ForkSyncResponse>().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(())
- }
|