Kaynağa Gözat

darkfid: defined loose metering configuration and max bytes limits for protocol messages

skoupidi 1 yıl önce
ebeveyn
işleme
87c2198552

+ 0 - 2
bin/darkfid/src/proto/mod.rs

@@ -27,8 +27,6 @@ use darkfi::{
 };
 use log::info;
 
-// TODO: Protocal functions need to be protected so peers can't spam us.
-
 /// Block proposal broadcast protocol
 mod protocol_proposal;
 pub use protocol_proposal::{ProposalMessage, ProtocolProposalHandler, ProtocolProposalHandlerPtr};

+ 17 - 3
bin/darkfid/src/proto/protocol_proposal.rs

@@ -26,7 +26,7 @@ use tinyjson::JsonValue;
 use darkfi::{
     impl_p2p_message,
     net::{
-        metering::{MeteringConfiguration, DEFAULT_METERING_CONFIGURATION},
+        metering::MeteringConfiguration,
         protocol::protocol_generic::{
             ProtocolGenericAction, ProtocolGenericHandler, ProtocolGenericHandlerPtr,
         },
@@ -35,7 +35,7 @@ use darkfi::{
     },
     rpc::jsonrpc::JsonSubscriber,
     system::{ExecutorPtr, StoppableTask, StoppableTaskPtr},
-    util::encoding::base64,
+    util::{encoding::base64, time::NanoTimestamp},
     validator::{consensus::Proposal, ValidatorPtr},
     Error, Result,
 };
@@ -47,7 +47,21 @@ use crate::task::handle_unknown_proposal;
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ProposalMessage(pub Proposal);
 
-impl_p2p_message!(ProposalMessage, "proposal", 0, 0, DEFAULT_METERING_CONFIGURATION);
+// TODO: Fine tune
+// Since messages are asynchronous we will define loose rules to prevent spamming.
+// Each message score will be 1, with a threshold of 50 and expiry time of 5.
+// We are not limiting `Proposal` size.
+impl_p2p_message!(
+    ProposalMessage,
+    "proposal",
+    0,
+    1,
+    MeteringConfiguration {
+        threshold: 50,
+        sleep_step: 500,
+        expiry_time: NanoTimestamp::from_secs(5),
+    }
+);
 
 /// Atomic pointer to the `ProtocolProposal` handler.
 pub type ProtocolProposalHandlerPtr = Arc<ProtocolProposalHandler>;

+ 61 - 24
bin/darkfid/src/proto/protocol_sync.rs

@@ -25,7 +25,7 @@ use darkfi::{
     blockchain::{BlockInfo, Header, HeaderHash},
     impl_p2p_message,
     net::{
-        metering::{MeteringConfiguration, DEFAULT_METERING_CONFIGURATION},
+        metering::MeteringConfiguration,
         protocol::protocol_generic::{
             ProtocolGenericAction, ProtocolGenericHandler, ProtocolGenericHandlerPtr,
         },
@@ -33,14 +33,27 @@ use darkfi::{
         Message, P2pPtr,
     },
     system::ExecutorPtr,
+    util::time::NanoTimestamp,
     validator::{consensus::Proposal, ValidatorPtr},
     Error, Result,
 };
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
-// Constant defining how many blocks we send during syncing.
+// Constant defining max elements we send in vectors during syncing.
 pub const BATCH: usize = 20;
 
+// TODO: Fine tune
+// Protocol metering configuration.
+// Since all messages are synchronous(request -> response) we will define
+// strict rules to prevent spamming.
+// Each message score will be 1, with a threshold of 20 and expiry time of 5.
+// Check ../tests/metering.rs for each message max bytes definition.
+const PROTOCOL_SYNC_METERING_CONFIGURATION: MeteringConfiguration = MeteringConfiguration {
+    threshold: 20,
+    sleep_step: 500,
+    expiry_time: NanoTimestamp::from_secs(5),
+};
+
 /// Structure represening a request to ask a node for their current
 /// canonical(confirmed) tip block hash, if they are synced. We also
 /// include our own tip, so they can verify we follow the same sequence.
@@ -50,7 +63,7 @@ pub struct TipRequest {
     pub tip: HeaderHash,
 }
 
-impl_p2p_message!(TipRequest, "tiprequest", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(TipRequest, "tiprequest", 32, 1, PROTOCOL_SYNC_METERING_CONFIGURATION);
 
 /// Structure representing the response to `TipRequest`,
 /// containing a boolean flag to indicate if we are synced,
@@ -65,7 +78,7 @@ pub struct TipResponse {
     pub hash: Option<HeaderHash>,
 }
 
-impl_p2p_message!(TipResponse, "tipresponse", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(TipResponse, "tipresponse", 39, 1, PROTOCOL_SYNC_METERING_CONFIGURATION);
 
 /// Structure represening a request to ask a node for up to `BATCH` headers before
 /// the provided header height.
@@ -75,7 +88,13 @@ pub struct HeaderSyncRequest {
     pub height: u32,
 }
 
-impl_p2p_message!(HeaderSyncRequest, "headersyncrequest", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(
+    HeaderSyncRequest,
+    "headersyncrequest",
+    4,
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
+);
 
 /// Structure representing the response to `HeaderSyncRequest`,
 /// containing up to `BATCH` headers before the requested block height.
@@ -85,7 +104,13 @@ pub struct HeaderSyncResponse {
     pub headers: Vec<Header>,
 }
 
-impl_p2p_message!(HeaderSyncResponse, "headersyncresponse", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(
+    HeaderSyncResponse,
+    "headersyncresponse",
+    1701,
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
+);
 
 /// Structure represening a request to ask a node for up to`BATCH` blocks
 /// of provided headers.
@@ -95,7 +120,7 @@ pub struct SyncRequest {
     pub headers: Vec<HeaderHash>,
 }
 
-impl_p2p_message!(SyncRequest, "syncrequest", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(SyncRequest, "syncrequest", 641, 1, PROTOCOL_SYNC_METERING_CONFIGURATION);
 
 /// Structure representing the response to `SyncRequest`,
 /// containing up to `BATCH` blocks after the requested block height.
@@ -105,7 +130,7 @@ pub struct SyncResponse {
     pub blocks: Vec<BlockInfo>,
 }
 
-impl_p2p_message!(SyncResponse, "syncresponse", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(SyncResponse, "syncresponse", 0, 1, PROTOCOL_SYNC_METERING_CONFIGURATION);
 
 /// Structure represening a request to ask a node a fork sequence.
 /// If we include a specific fork tip, they have to return its sequence,
@@ -120,17 +145,17 @@ pub struct ForkSyncRequest {
     pub fork_tip: Option<HeaderHash>,
 }
 
-impl_p2p_message!(ForkSyncRequest, "forksyncrequest", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(ForkSyncRequest, "forksyncrequest", 65, 1, PROTOCOL_SYNC_METERING_CONFIGURATION);
 
 /// Structure representing the response to `ForkSyncRequest`,
-/// containing the requested fork sequence.
+/// containing the requested fork sequence, up to `BATCH` proposals.
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ForkSyncResponse {
     /// Response fork proposals
     pub proposals: Vec<Proposal>,
 }
 
-impl_p2p_message!(ForkSyncResponse, "forksyncresponse", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(ForkSyncResponse, "forksyncresponse", 0, 1, PROTOCOL_SYNC_METERING_CONFIGURATION);
 
 /// Structure represening a request to ask a node a fork header for the
 /// requested height. The fork is identified by the provided header hash.
@@ -145,9 +170,9 @@ pub struct ForkHeaderHashRequest {
 impl_p2p_message!(
     ForkHeaderHashRequest,
     "forkheaderhashrequest",
-    0,
-    0,
-    DEFAULT_METERING_CONFIGURATION
+    36,
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
 );
 
 /// Structure representing the response to `ForkHeaderHashRequest`,
@@ -161,9 +186,9 @@ pub struct ForkHeaderHashResponse {
 impl_p2p_message!(
     ForkHeaderHashResponse,
     "forkheaderhashresponse",
-    0,
-    0,
-    DEFAULT_METERING_CONFIGURATION
+    33,
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
 );
 
 /// Structure represening a request to ask a node for up to `BATCH`
@@ -177,7 +202,13 @@ pub struct ForkHeadersRequest {
     pub fork_header: HeaderHash,
 }
 
-impl_p2p_message!(ForkHeadersRequest, "forkheadersrequest", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(
+    ForkHeadersRequest,
+    "forkheadersrequest",
+    673,
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
+);
 
 /// Structure representing the response to `ForkHeadersRequest`,
 /// containing up to `BATCH` fork headers.
@@ -187,7 +218,13 @@ pub struct ForkHeadersResponse {
     pub headers: Vec<Header>,
 }
 
-impl_p2p_message!(ForkHeadersResponse, "forkheadersresponse", 0, 0, DEFAULT_METERING_CONFIGURATION);
+impl_p2p_message!(
+    ForkHeadersResponse,
+    "forkheadersresponse",
+    1701,
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
+);
 
 /// Structure represening a request to ask a node for up to `BATCH`
 /// fork proposals for provided header hashes.  The fork is identified
@@ -203,9 +240,9 @@ pub struct ForkProposalsRequest {
 impl_p2p_message!(
     ForkProposalsRequest,
     "forkproposalsrequest",
-    0,
-    0,
-    DEFAULT_METERING_CONFIGURATION
+    673,
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
 );
 
 /// Structure representing the response to `ForkProposalsRequest`,
@@ -220,8 +257,8 @@ impl_p2p_message!(
     ForkProposalsResponse,
     "forkproposalsresponse",
     0,
-    0,
-    DEFAULT_METERING_CONFIGURATION
+    1,
+    PROTOCOL_SYNC_METERING_CONFIGURATION
 );
 
 /// Atomic pointer to the `ProtocolSync` handler.

+ 184 - 0
bin/darkfid/src/tests/metering.rs

@@ -0,0 +1,184 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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 darkfi::blockchain::Header;
+use darkfi_serial::serialize;
+
+use crate::proto::{
+    ForkHeaderHashRequest, ForkHeaderHashResponse, ForkHeadersRequest, ForkHeadersResponse,
+    ForkProposalsRequest, ForkSyncRequest, HeaderSyncRequest, HeaderSyncResponse, SyncRequest,
+    TipRequest, TipResponse, BATCH,
+};
+
+#[test]
+fn darkfid_protocols_metering() {
+    // Known constant bytes lengths
+    const BOOL_LEN: usize = 1;
+    const OPTION_LEN: usize = 1;
+    const U32_LEN: usize = 4;
+    const VARINT_LEN: usize = 1;
+    const HEADER_HASH_LEN: usize = 32;
+    // Header = U8_LEN + HEADER_HASH_LEN + U32_LEN + U64_LEN + U64_LEN + (U8_LEN * 32) =
+    // 1 + 32 + 4 + 8 + 8 + (1 * 32) = 53 + 32 = 85
+    const HEADER_LEN: usize = 85;
+
+    // Generate a dummy `Header`.
+    // Its bytes vector length is constant.
+    let header = Header::default();
+    assert_eq!(serialize(&header).len(), HEADER_LEN);
+
+    // Its hash bytes vector length is constant.
+    let header_hash = header.hash();
+    assert_eq!(serialize(&header_hash).len(), HEADER_HASH_LEN);
+
+    // Protocol sync `TipRequest` message has constant bytes length
+    let tip_request = TipRequest { tip: header_hash };
+    assert_eq!(serialize(&tip_request).len(), HEADER_HASH_LEN);
+
+    // Protocol sync `TipResponse` message has constant bytes length,
+    // based on its structure.
+    let tip_response = TipResponse { synced: false, height: None, hash: None };
+    // Length = BOOL_LEN + OPTION_LEN + OPTION_LEN = 1 + 1 + 1 = 3
+    assert_eq!(serialize(&tip_response).len(), BOOL_LEN + OPTION_LEN + OPTION_LEN);
+    let tip_response = TipResponse { synced: false, height: Some(42), hash: None };
+    // Length = BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN = 1 + 1 + 4 + 1 = 7
+    assert_eq!(serialize(&tip_response).len(), BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN);
+    let tip_response = TipResponse { synced: false, height: None, hash: Some(header_hash) };
+    // Length = BOOL_LEN + OPTION_LEN + OPTION_LEN + HEADER_HASH_LEN = 1 + 1 + 1 + 32 = 35
+    assert_eq!(
+        serialize(&tip_response).len(),
+        BOOL_LEN + OPTION_LEN + OPTION_LEN + HEADER_HASH_LEN
+    );
+    let tip_response = TipResponse { synced: false, height: Some(42), hash: Some(header_hash) };
+    // Length = BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN + HEADER_HASH_LEN =
+    // 1 + 1 + 4 + 1 + 32 = 39
+    assert_eq!(
+        serialize(&tip_response).len(),
+        BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN + HEADER_HASH_LEN
+    );
+    let tip_response = TipResponse { synced: true, height: None, hash: None };
+    // Length = BOOL_LEN + OPTION_LEN + OPTION_LEN = 1 + 1 + 1 = 3
+    assert_eq!(serialize(&tip_response).len(), BOOL_LEN + OPTION_LEN + OPTION_LEN);
+    let tip_response = TipResponse { synced: true, height: Some(42), hash: None };
+    // Length = BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN = 1 + 1 + 4 + 1 = 7
+    assert_eq!(serialize(&tip_response).len(), BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN);
+    let tip_response = TipResponse { synced: true, height: None, hash: Some(header_hash) };
+    // Length = BOOL_LEN + OPTION_LEN + OPTION_LEN + HEADER_HASH_LEN = 1 + 1 + 1 + 32 = 35
+    assert_eq!(
+        serialize(&tip_response).len(),
+        BOOL_LEN + OPTION_LEN + OPTION_LEN + HEADER_HASH_LEN
+    );
+    let tip_response = TipResponse { synced: true, height: Some(42), hash: Some(header_hash) };
+    // Length = BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN + HEADER_HASH_LEN =
+    // 1 + 1 + 4 + 1 + 32 = 39
+    assert_eq!(
+        serialize(&tip_response).len(),
+        BOOL_LEN + OPTION_LEN + U32_LEN + OPTION_LEN + HEADER_HASH_LEN
+    );
+
+    // Protocol sync `HeaderSyncRequest` message has constant bytes length
+    let header_sync_request = HeaderSyncRequest { height: 42 };
+    // Length = 4
+    assert_eq!(serialize(&header_sync_request).len(), U32_LEN);
+
+    // Protocol sync `HeaderSyncResponse` is limited by `BATCH` so it has a
+    // constant max bytes length limit.
+    let header_sync_response = HeaderSyncResponse { headers: vec![header.clone(); BATCH] };
+    // When we serialize a `Vec`, its length is encoded as a `VarInt`.
+    // Based on length size/type, this can add from 1(u8) to 8(u64) bytes.
+    // Since `BATCH` is 20, its `VarInt` will be represented as a u8,
+    // adding an extra byte.
+    // Length = (BATCH * HEADER_LEN) + VARINT_LEN = (20 * 85) + 1 = 1700 + 1 = 1701
+    assert_eq!(serialize(&header_sync_response).len(), (BATCH * HEADER_LEN) + VARINT_LEN);
+
+    // Protocol sync `SyncRequest` is limited by `BATCH` so it has a
+    // constant max bytes length limit.
+    let sync_request = SyncRequest { headers: vec![header_hash; BATCH] };
+    // Don't forget the extra byte from `Vec` length.
+    // Length = (BATCH * HEADER_HASH_LEN) + VARINT_LEN = (20 * 32) + 1 = 640 + 1 = 641
+    assert_eq!(serialize(&sync_request).len(), (BATCH * HEADER_HASH_LEN) + VARINT_LEN);
+
+    // Protocol sync `SyncResponse` is limited by `BATCH` so it can have a
+    // constant max bytes length limit, but we are not limiting `BlockInfo` size.
+
+    // Protocol sync `ForkSyncRequest` message has constant bytes length,
+    // based on its structure.
+    let fork_sync_request = ForkSyncRequest { tip: header_hash, fork_tip: None };
+    // Length = HEADER_HASH_LEN + OPTION_LEN = 32 + 1 = 33
+    assert_eq!(serialize(&fork_sync_request).len(), HEADER_HASH_LEN + OPTION_LEN);
+    let fork_sync_request = ForkSyncRequest { tip: header_hash, fork_tip: Some(header_hash) };
+    // Length = HEADER_HASH_LEN + OPTION_LEN + HEADER_HASH_LEN = 32 + 1 + 32 = 65
+    assert_eq!(serialize(&fork_sync_request).len(), HEADER_HASH_LEN + OPTION_LEN + HEADER_HASH_LEN);
+
+    // Protocol sync `ForkSyncResponse` is limited by `BATCH` so it can have a
+    // constant max bytes length limit, but we are not limiting `Proposal` size.
+
+    // Protocol sync `ForkHeaderHashRequest` message has constant bytes length
+    let fork_header_hash_request = ForkHeaderHashRequest { height: 42, fork_header: header_hash };
+    // Length = U32_LEN + HEADER_HASH_LEN = 4 + 32 = 36
+    assert_eq!(serialize(&fork_header_hash_request).len(), U32_LEN + HEADER_HASH_LEN);
+
+    // Protocol sync `ForkHeaderHashResponse` message has constant bytes length,
+    // based on its structure.
+    let fork_header_hash_response = ForkHeaderHashResponse { fork_header: None };
+    // Length = OPTION_LEN = 1
+    assert_eq!(serialize(&fork_header_hash_response).len(), OPTION_LEN);
+    let fork_header_hash_response = ForkHeaderHashResponse { fork_header: Some(header_hash) };
+    // Length = OPTION_LEN + HEADER_HASH_LEN = 1 + 32 = 33
+    assert_eq!(serialize(&fork_header_hash_response).len(), OPTION_LEN + HEADER_HASH_LEN);
+
+    // Protocol sync `ForkHeadersRequest` is limited by `BATCH` so it has a
+    // constant max bytes length limit.
+    let fork_headers_request =
+        ForkHeadersRequest { headers: vec![header_hash; BATCH], fork_header: header_hash };
+    // Don't forget the extra byte from `Vec` length.
+    // Length = (BATCH * HEADER_HASH_LEN) + VARINT_LEN + HEADER_HASH_LEN =
+    // (20 * 32) + 1 + 32 = 640 + 33 = 673
+    assert_eq!(
+        serialize(&fork_headers_request).len(),
+        (BATCH * HEADER_HASH_LEN) + VARINT_LEN + HEADER_HASH_LEN
+    );
+
+    // Protocol sync `ForkHeadersResponse` is limited by `BATCH` so it has a
+    // constant max bytes length limit.
+    let fork_headers_response = ForkHeadersResponse { headers: vec![header; BATCH] };
+    // Don't forget the extra byte from `Vec` length.
+    // Length = (BATCH * HEADER_LEN) + VARINT_LEN = (20 * 85) + 1 = 1700 + 1 = 1701
+    assert_eq!(serialize(&fork_headers_response).len(), (BATCH * HEADER_LEN) + VARINT_LEN);
+
+    // Protocol sync `ForkProposalsRequest` is limited by `BATCH` so it has a
+    // constant max bytes length limit.
+    let fork_proposals_request =
+        ForkProposalsRequest { headers: vec![header_hash; BATCH], fork_header: header_hash };
+    // Don't forget the extra byte from `Vec` length.
+    // Length = (BATCH * HEADER_HASH_LEN) + VARINT_LEN + HEADER_HASH_LEN =
+    // (20 * 32) + 1 + 32 = 640 + 33 = 673
+    assert_eq!(
+        serialize(&fork_proposals_request).len(),
+        (BATCH * HEADER_HASH_LEN) + VARINT_LEN + HEADER_HASH_LEN
+    );
+
+    // Protocol sync `ForkProposalsResponse` is limited by `BATCH` so it can have a
+    // constant max bytes length limit, but we are not limiting `Proposal` size.
+
+    // Protocol proposal `ProposalMessage` can have a constant max bytes length limit,
+    // but we are not limiting `Proposal` size.
+
+    // Protocol tx `Transaction` can have constant max bytes length limit,
+    // but we are not limiting `Transaction` size.
+}

+ 2 - 0
bin/darkfid/src/tests/mod.rs

@@ -36,6 +36,8 @@ mod sync_forks;
 
 mod unproposed_txs;
 
+mod metering;
+
 async fn sync_blocks_real(ex: Arc<Executor<'static>>) -> Result<()> {
     init_logger();
 

+ 18 - 4
src/tx/mod.rs

@@ -230,13 +230,27 @@ impl std::fmt::Debug for Transaction {
 }
 
 #[cfg(feature = "net")]
-use crate::net::{
-    metering::{MeteringConfiguration, DEFAULT_METERING_CONFIGURATION},
-    Message,
+use crate::{
+    net::{metering::MeteringConfiguration, Message},
+    util::time::NanoTimestamp,
 };
 
 #[cfg(feature = "net")]
-crate::impl_p2p_message!(Transaction, "tx", 0, 0, DEFAULT_METERING_CONFIGURATION);
+// TODO: Fine tune
+// Since messages are asynchronous we will define loose rules to prevent spamming.
+// Each message score will be 1, with a threshold of 100 and expiry time of 5.
+// We are not limiting `Transaction` size.
+crate::impl_p2p_message!(
+    Transaction,
+    "tx",
+    0,
+    1,
+    MeteringConfiguration {
+        threshold: 100,
+        sleep_step: 500,
+        expiry_time: NanoTimestamp::from_secs(5),
+    }
+);
 
 /// Calls tree bounds definitions
 // TODO: increase min to 2 when fees are implement