diff --git a/src/avalanche/avalanche.h b/src/avalanche/avalanche.h --- a/src/avalanche/avalanche.h +++ b/src/avalanche/avalanche.h @@ -24,6 +24,11 @@ */ static constexpr size_t AVALANCHE_DEFAULT_COOLDOWN = 100; +/** + * Is avalanche peer discovery enabled. + */ +static constexpr bool AVALANCHE_DEFAULT_SHARE_PROOFS = false; + /** * Global avalanche instance. */ diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1223,6 +1223,13 @@ "-enableavalanche", strprintf("Enable avalanche (default: %u)", AVALANCHE_DEFAULT_ENABLED), ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); + argsman.AddArg( + "-exchangeavaproofs", + strprintf("Exchange avalanche proofs when meeting an avalanche peer." + "If disabled, peers can only be added via the " + "addavalanchenode RPC command (default: %u)", + AVALANCHE_DEFAULT_SHARE_PROOFS), + ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg( "-avacooldown", strprintf("Mandatory cooldown between two avapoll (default: %u)", diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2744,7 +2744,8 @@ bool IsAvalancheMessageType(const std::string &msg_type) { return msg_type == NetMsgType::AVAHELLO || msg_type == NetMsgType::AVAPOLL || - msg_type == NetMsgType::AVARESPONSE; + msg_type == NetMsgType::AVARESPONSE || + msg_type == NetMsgType::AVAPROOF; } void PeerManager::ProcessMessage(const Config &config, CNode &pfrom, @@ -4122,6 +4123,28 @@ GetTime(), preferred); } + return; + } + + if (msg_type == NetMsgType::AVAPROOF) { + if (!gArgs.GetBoolArg("-exchangeavaproofs", + AVALANCHE_DEFAULT_SHARE_PROOFS)) { + return; + } + + auto proof = std::make_shared(); + vRecv >> *proof; + + if (pfrom.m_avalanche_state && + proof->getId() == + pfrom.m_avalanche_state->delegation.getProofId() && + g_avalanche->addNode(pfrom.GetId(), proof, + pfrom.m_avalanche_state->delegation)) { + LogPrint(BCLog::NET, "added avalanche node=%d\n", pfrom.GetId()); + } else if (g_avalanche->addProof(proof)) { + LogPrint(BCLog::NET, "added new proof id=%s\n", + proof->getId().GetHex()); + } return; } diff --git a/test/functional/abc_p2p_avalanche.py b/test/functional/abc_p2p_avalanche.py --- a/test/functional/abc_p2p_avalanche.py +++ b/test/functional/abc_p2p_avalanche.py @@ -22,9 +22,10 @@ CInv, FromHex, hash256, - msg_avahello, msg_avapoll, + msg_avahello, MSG_AVA_PROOF, + msg_avaproof, msg_getdata, msg_tcpavaresponse, NODE_AVALANCHE, @@ -57,6 +58,7 @@ self.avahello = None self.avaresponses = [] self.avapolls = [] + self.avaproof = None super().__init__() def peer_connect(self, *args, **kwargs): @@ -133,9 +135,26 @@ msg.hello.delegation = delegation msg.hello.sig = delegated_privkey.sign_schnorr(local_sighash) self.send_message(msg) - return delegation.proofid + def on_avaproof(self, message): + assert(self.avaproof is None) + self.avaproof = message + + def wait_for_avaproof(self, timeout=10): + wait_until( + lambda: self.avaproof is not None, + timeout=timeout, + lock=p2p_lock) + + with p2p_lock: + return self.avaproof + + def send_avaproof(self, proof_hex): + msg_proof = msg_avaproof() + msg_proof.proof = FromHex(AvalancheProof(), proof_hex) + self.send_message(msg_proof) + class AvalancheTest(BitcoinTestFramework): def set_test_params(self): @@ -374,6 +393,7 @@ self.restart_node(0, self.extra_args[0] + [ "-avaproof={}".format(proof), "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", + "-exchangeavaproofs=1", ]) assert_equal( @@ -404,6 +424,18 @@ bytes_to_wif(privkey.get_bytes()), delegated_key.get_pubkey().get_bytes().hex(), None) + good_interface.send_avahello(interface_delegation_hex, delegated_key) + expected_proofid = FromHex( + AvalancheProof(), + interface_proof_hex).proofid + good_interface.wait_for_getdata([expected_proofid]) + + self.log.info("Test that node adds an avalanche peer") + good_interface.send_avaproof(interface_proof_hex) + wait_until( + lambda: len(node.getavalanchepeerinfo()) > 0, + timeout=5, + lock=p2p_lock) self.log.info("Test that wrong avahello signature causes a ban") bad_interface = get_node() @@ -443,11 +475,8 @@ "Proof has been inv'ed recently, check it can be requested") good_interface.send_message(getdata) - def proof_received(peer): - with p2p_lock: - return peer.last_message.get( - "avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid - wait_until(lambda: proof_received(good_interface)) + avaproof = good_interface.wait_for_avaproof() + assert_equal(avaproof.proof.serialize().hex(), proof) # Restart the node self.restart_node(0, self.extra_args[0] + [ @@ -464,6 +493,13 @@ # Give enough time for the node to answer. Since we cannot check for a # non-event this is the best we can do time.sleep(2) + + def proof_received(peer): + with p2p_lock: + return peer.last_message.get( + "avaproof") and peer.last_message["avaproof"].proof.proofid == node_proofid + + wait_until(lambda: proof_received(good_interface)) assert not proof_received(peer) self.log.info("The proof is known for long enough to be requested") @@ -471,7 +507,8 @@ node.setmocktime(current_time + UNCONDITIONAL_RELAY_DELAY) peer.send_message(getdata) - wait_until(lambda: proof_received(peer)) + avaproof = peer.wait_for_avaproof() + assert_equal(avaproof.proof.serialize().hex(), proof) if __name__ == '__main__': diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -14,6 +14,7 @@ MAX_PROTOCOL_MESSAGE_LENGTH, msg_avahello, msg_avapoll, + msg_avaproof, msg_avaresponse, msg_getdata, msg_headers, @@ -289,6 +290,10 @@ ['Misbehaving', '(40 -> 60): unsolicited-avaresponse']): msg = msg_avaresponse() conn.send_and_ping(msg) + with self.nodes[0].assert_debug_log( + ['Misbehaving', '(60 -> 80): unsolicited-avaproof']): + msg = msg_avaproof() + conn.send_and_ping(msg) self.nodes[0].disconnect_p2ps() def test_resource_exhaustion(self):