protocol_proposal.rs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::sync::Arc;
  19. use async_trait::async_trait;
  20. use log::debug;
  21. use smol::Executor;
  22. use tinyjson::JsonValue;
  23. use darkfi::{
  24. impl_p2p_message,
  25. net::{
  26. ChannelPtr, Message, MessageSubscription, P2pPtr, ProtocolBase, ProtocolBasePtr,
  27. ProtocolJobsManager, ProtocolJobsManagerPtr,
  28. },
  29. rpc::jsonrpc::JsonSubscriber,
  30. util::encoding::base64,
  31. validator::{consensus::Proposal, ValidatorPtr},
  32. Error, Result,
  33. };
  34. use darkfi_serial::{serialize_async, SerialDecodable, SerialEncodable};
  35. use crate::proto::{ForkSyncRequest, ForkSyncResponse, COMMS_TIMEOUT};
  36. /// Auxiliary [`Proposal`] wrapper structure used for messaging.
  37. #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
  38. pub struct ProposalMessage(pub Proposal);
  39. impl_p2p_message!(ProposalMessage, "proposal");
  40. pub struct ProtocolProposal {
  41. proposal_sub: MessageSubscription<ProposalMessage>,
  42. jobsman: ProtocolJobsManagerPtr,
  43. validator: ValidatorPtr,
  44. p2p: P2pPtr,
  45. channel: ChannelPtr,
  46. subscriber: JsonSubscriber,
  47. miner: bool,
  48. sync_p2p: Option<P2pPtr>,
  49. }
  50. impl ProtocolProposal {
  51. pub async fn init(
  52. channel: ChannelPtr,
  53. validator: ValidatorPtr,
  54. p2p: P2pPtr,
  55. subscriber: JsonSubscriber,
  56. miner: bool,
  57. sync_p2p: Option<P2pPtr>,
  58. ) -> Result<ProtocolBasePtr> {
  59. debug!(
  60. target: "darkfid::proto::protocol_proposal::init",
  61. "Adding ProtocolProposal to the protocol registry"
  62. );
  63. let msg_subsystem = channel.message_subsystem();
  64. msg_subsystem.add_dispatch::<ProposalMessage>().await;
  65. msg_subsystem.add_dispatch::<ForkSyncRequest>().await;
  66. msg_subsystem.add_dispatch::<ForkSyncResponse>().await;
  67. let proposal_sub = channel.subscribe_msg::<ProposalMessage>().await?;
  68. Ok(Arc::new(Self {
  69. proposal_sub,
  70. jobsman: ProtocolJobsManager::new("ProposalProtocol", channel.clone()),
  71. validator,
  72. p2p,
  73. channel,
  74. subscriber,
  75. miner,
  76. sync_p2p,
  77. }))
  78. }
  79. async fn handle_receive_proposal(self: Arc<Self>) -> Result<()> {
  80. debug!(target: "darkfid::proto::protocol_proposal::handle_receive_proposal", "START");
  81. let exclude_list = vec![self.channel.address().clone()];
  82. loop {
  83. let proposal = match self.proposal_sub.receive().await {
  84. Ok(v) => v,
  85. Err(e) => {
  86. debug!(
  87. target: "darkfid::proto::protocol_proposal::handle_receive_proposal",
  88. "recv fail: {}",
  89. e
  90. );
  91. continue
  92. }
  93. };
  94. // Check if node has finished syncing its blockchain
  95. if !*self.validator.synced.read().await {
  96. debug!(
  97. target: "darkfid::proto::protocol_proposal::handle_receive_proposal",
  98. "Node still syncing blockchain, skipping..."
  99. );
  100. continue
  101. }
  102. // Check if node is connected to the miners network
  103. if self.miner {
  104. debug!(
  105. target: "darkfid::proto::protocol_proposal::handle_receive_proposal",
  106. "Node is connected to the miners network, skipping..."
  107. );
  108. continue
  109. }
  110. let proposal_copy = (*proposal).clone();
  111. match self.validator.consensus.append_proposal(&proposal_copy.0).await {
  112. Ok(()) => {
  113. self.p2p.broadcast_with_exclude(&proposal_copy, &exclude_list).await;
  114. if let Some(sync_p2p) = self.sync_p2p.as_ref() {
  115. sync_p2p.broadcast_with_exclude(&proposal_copy, &exclude_list).await;
  116. }
  117. let enc_prop =
  118. JsonValue::String(base64::encode(&serialize_async(&proposal_copy).await));
  119. self.subscriber.notify(vec![enc_prop].into()).await;
  120. continue
  121. }
  122. Err(e) => {
  123. debug!(
  124. target: "darkfid::proto::protocol_proposal::handle_receive_proposal",
  125. "append_proposal fail: {}",
  126. e
  127. );
  128. match e {
  129. Error::ExtendedChainIndexNotFound => { /* Do nothing */ }
  130. _ => continue,
  131. }
  132. }
  133. };
  134. // If proposal fork chain was not found, we ask our peer for its sequence
  135. debug!(target: "darkfid::proto::protocol_proposal::handle_receive_proposal", "Asking peer for fork sequence");
  136. let last = self.validator.blockchain.last()?;
  137. let request = ForkSyncRequest { tip: last.1, fork_tip: Some(proposal_copy.0.hash) };
  138. let proposals_response_sub = self.channel.subscribe_msg::<ForkSyncResponse>().await?;
  139. self.channel.send(&request).await?;
  140. // Node waits for response
  141. let Ok(response) = proposals_response_sub.receive_with_timeout(COMMS_TIMEOUT).await
  142. else {
  143. continue
  144. };
  145. // Verify and store retrieved proposals
  146. debug!(target: "darkfid::proto::protocol_proposal::handle_receive_proposal", "Processing received proposals");
  147. // Response should not be empty
  148. if response.proposals.is_empty() {
  149. debug!(target: "darkfid::proto::protocol_proposal::handle_receive_proposal", "Peer responded with empty sequence");
  150. continue
  151. }
  152. // Sequence length must correspond to requested height
  153. if response.proposals.len() as u64 != proposal_copy.0.block.header.height - last.0 {
  154. debug!(target: "darkfid::proto::protocol_proposal::handle_receive_proposal", "Response sequence length is erroneous");
  155. continue
  156. }
  157. // First proposal must extend canonical
  158. if response.proposals[0].block.header.previous != last.1 {
  159. debug!(target: "darkfid::proto::protocol_proposal::handle_receive_proposal", "Response sequence doesn't extend canonical");
  160. continue
  161. }
  162. // Last proposal must be the same as the one requested
  163. if response.proposals.last().unwrap().hash != proposal_copy.0.hash {
  164. debug!(target: "darkfid::proto::protocol_proposal::handle_receive_proposal", "Response sequence doesn't correspond to requested tip");
  165. continue
  166. }
  167. for proposal in &response.proposals {
  168. self.validator.consensus.append_proposal(proposal).await?;
  169. // Notify subscriber
  170. let enc_prop = JsonValue::String(base64::encode(&serialize_async(proposal).await));
  171. self.subscriber.notify(vec![enc_prop].into()).await;
  172. }
  173. }
  174. }
  175. }
  176. #[async_trait]
  177. impl ProtocolBase for ProtocolProposal {
  178. async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> {
  179. debug!(target: "darkfid::proto::protocol_proposal::start", "START");
  180. self.jobsman.clone().start(executor.clone());
  181. self.jobsman.clone().spawn(self.clone().handle_receive_proposal(), executor.clone()).await;
  182. debug!(target: "darkfid::proto::protocol_proposal::start", "END");
  183. Ok(())
  184. }
  185. fn name(&self) -> &'static str {
  186. "ProtocolProposal"
  187. }
  188. }