diff --git a/src/avalanche.h b/src/avalanche.h --- a/src/avalanche.h +++ b/src/avalanche.h @@ -171,6 +171,7 @@ std::vector votes; public: + AvalancheResponse() : round(-1), cooldown(-1) {} AvalancheResponse(uint64_t roundIn, uint32_t cooldownIn, std::vector votesIn) : round(roundIn), cooldown(cooldownIn), votes(votesIn) {} diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3451,7 +3451,7 @@ unsigned int nCount = ReadCompactSize(vRecv); if (nCount > AVALANCHE_MAX_ELEMENT_POLL) { LOCK(cs_main); - Misbehaving(pfrom, 20, "too-many-poll"); + Misbehaving(pfrom, 20, "too-many-ava-poll"); return error("poll message size = %u", nCount); } @@ -3487,6 +3487,43 @@ return true; } + // Ignore avalanche requests while importing + if (strCommand == NetMsgType::AVARESPONSE && !fImporting && !fReindex && + g_avalanche && + gArgs.GetBoolArg("-enableavalanche", AVALANCHE_DEFAULT_ENABLED)) { + // As long as QUIC is not implemented, we need to sign response and + // verify response's signatures in order to avoid any manipulation of + // messages at the transport level. + CHashVerifier verifier(&vRecv); + AvalancheResponse response; + verifier >> response; + + { + std::array sig; + vRecv >> sig; + + // Unfortunately, the verify API require a vector. + std::vector vchSig{sig.begin(), sig.end()}; + if (!g_avalanche->getPubKey(pfrom->GetId()) + .VerifySchnorr(verifier.GetHash(), vchSig)) { + LOCK(cs_main); + Misbehaving(pfrom, 100, "invalid-ava-response-signature"); + return true; + } + } + + std::vector updates; + if (!g_avalanche->registerVotes(pfrom->GetId(), response, updates)) { + LOCK(cs_main); + Misbehaving(pfrom, 100, "invalid-ava-response-content"); + return true; + } + + // TODO: Actually process the updates. + + return true; + } + if (strCommand == NetMsgType::GETADDR) { // This asymmetric behavior for inbound and outbound connections was // introduced to prevent a fingerprinting attack: an attacker can send 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 @@ -7,9 +7,12 @@ from test_framework.mininode import P2PInterface, mininode_lock from test_framework.messages import ( + AvalancheResponse, AvalancheVote, CInv, msg_avapoll, + msg_tcpavaresponse, + TCPAvalancheResponse, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -41,6 +44,13 @@ with mininode_lock: self.avapolls.append(message.poll) + def send_avaresponse(self, round, votes, privkey): + response = AvalancheResponse(round, 0, votes) + sig = schnorr.sign(privkey, response.get_hash()) + msg = msg_tcpavaresponse() + msg.response = TCPAvalancheResponse(response, sig) + self.send_message(msg) + def send_poll(self, hashes): msg = msg_avapoll() msg.poll.round = self.round @@ -194,6 +204,7 @@ # Vote yes to everything votes.append(AvalancheVote(BLOCK_ACCEPTED, inv.hash)) + poll_node.send_avaresponse(poll.round, votes, privkey) return found_hash # Because the new tip is a deep reorg, the node should start to poll @@ -201,6 +212,10 @@ hash_to_find = int(fork_node.getbestblockhash(), 16) wait_until(lambda: can_find_block_in_poll(hash_to_find), timeout=5) + # To verify that responses are processed, do it a few more times. + for _ in range(10): + wait_until(lambda: can_find_block_in_poll(hash_to_find), timeout=5) + if __name__ == '__main__': AvalancheTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -840,9 +840,9 @@ class TCPAvalancheResponse(): __slots__ = ("response", "sig") - def __init__(self, response=AvalancheResponse()): + def __init__(self, response=AvalancheResponse(), sig=b"\0" * 64): self.response = response - self.sig = b"\0" * 64 + self.sig = sig def deserialize(self, f): self.response.deserialize(f)