diff --git a/src/avalanche/avalanche.h b/src/avalanche/avalanche.h --- a/src/avalanche/avalanche.h +++ b/src/avalanche/avalanche.h @@ -30,6 +30,12 @@ */ static constexpr size_t AVALANCHE_DEFAULT_CONFLICTING_PROOF_COOLDOWN = 60; +/** + * Peer replacement cooldown time default value in seconds. + * Minimal delay before a peer can be replaced due to a conflicting proof. + */ +static constexpr size_t AVALANCHE_DEFAULT_PEER_REPLACEMENT_COOLDOWN = 60 * 60; + /** * Is proof replacement enabled by default. */ diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1276,6 +1276,11 @@ "in seconds (default: %u)", AVALANCHE_DEFAULT_CONFLICTING_PROOF_COOLDOWN), ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE); + argsman.AddArg("-avalanchepeerreplacementcooldown", + strprintf("Mandatory cooldown before a peer can be replaced " + "in seconds (default: %u)", + AVALANCHE_DEFAULT_PEER_REPLACEMENT_COOLDOWN), + ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE); argsman.AddArg("-enableavalancheproofreplacement", strprintf("Enable avalanche proof replacement (default: %u)", AVALANCHE_DEFAULT_PROOF_REPLACEMENT_ENABLED), diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4458,6 +4458,7 @@ auto rejectionMode = avalanche::PeerManager::RejectionMode::DEFAULT; + auto nextCooldownTimePoint = GetTime(); switch (u.getStatus()) { case avalanche::VoteStatus::Invalid: rejectionMode = @@ -4476,8 +4477,12 @@ proofid.GetHex()); } break; - case avalanche::VoteStatus::Accepted: case avalanche::VoteStatus::Finalized: + nextCooldownTimePoint += + std::chrono::seconds(gArgs.GetArg( + "-avalanchepeerreplacementcooldown", + AVALANCHE_DEFAULT_PEER_REPLACEMENT_COOLDOWN)); + case avalanche::VoteStatus::Accepted: LogPrint(BCLog::AVALANCHE, "Avalanche accepted proof %s, status %d\n", proofid.GetHex(), uint8_t(u.getStatus())); @@ -4487,7 +4492,17 @@ proof, avalanche::PeerManager:: RegistrationMode::FORCE_ACCEPT); - return pm.isBoundToPeer(proofid); + + return pm.forPeer( + proofid, + [&](const avalanche::Peer &peer) { + pm.updateNextPossibleConflictTime( + peer.peerid, + nextCooldownTimePoint); + // Only fail if the peer was not + // created + return true; + }); })) { LogPrint(BCLog::AVALANCHE, "ERROR: Failed to accept proof: %s\n", diff --git a/test/functional/abc_p2p_avalanche_proof_voting.py b/test/functional/abc_p2p_avalanche_proof_voting.py --- a/test/functional/abc_p2p_avalanche_proof_voting.py +++ b/test/functional/abc_p2p_avalanche_proof_voting.py @@ -29,9 +29,10 @@ self.setup_clean_chain = True self.num_nodes = 1 self.conflicting_proof_cooldown = 100 + self.peer_replacement_cooldown = 2000 self.extra_args = [ ['-enableavalanche=1', '-enableavalancheproofreplacement=1', - f'-avalancheconflictingproofcooldown={self.conflicting_proof_cooldown}', '-avacooldown=0'], + f'-avalancheconflictingproofcooldown={self.conflicting_proof_cooldown}', f'-avalanchepeerreplacementcooldown={self.peer_replacement_cooldown}', '-avacooldown=0'], ] self.supports_cli = False @@ -190,9 +191,19 @@ self.wait_until(lambda: accept_proof(proofid_seq30)) assert proofid_seq40 not in get_proof_ids(node) - self.log.info("Test proof rejection") + self.log.info("Test the peer replacement rate limit") peer = get_ava_p2p_interface(node) + + send_proof(peer, proof_seq50) + with node.assert_debug_log(["Not polling the avalanche proof (cooldown-not-elapsed)"]): + send_proof(peer, proof_seq50) + + mock_time += self.peer_replacement_cooldown + node.setmocktime(mock_time) + + self.log.info("Test proof rejection") + send_proof(peer, proof_seq50) self.wait_until(lambda: proofid_seq50 in get_proof_ids(node)) assert proofid_seq40 not in get_proof_ids(node)