diff --git a/src/avalanche/peermanager.h b/src/avalanche/peermanager.h --- a/src/avalanche/peermanager.h +++ b/src/avalanche/peermanager.h @@ -144,6 +144,7 @@ REJECTED, COOLDOWN_NOT_ELAPSED, DANGLING, + MISSING_UTXO, }; class ProofRegistrationState : public ValidationState { diff --git a/src/avalanche/peermanager.cpp b/src/avalanche/peermanager.cpp --- a/src/avalanche/peermanager.cpp +++ b/src/avalanche/peermanager.cpp @@ -255,6 +255,12 @@ return invalidate(ProofRegistrationResult::ORPHAN, "orphan-proof"); } + if (validationState.GetResult() == + ProofValidationResult::MISSING_UTXO) { + return invalidate(ProofRegistrationResult::MISSING_UTXO, + "utxo-missing-or-spent"); + } + // Reject invalid proof. return invalidate(ProofRegistrationResult::INVALID, "invalid-proof"); } diff --git a/src/avalanche/test/peermanager_tests.cpp b/src/avalanche/test/peermanager_tests.cpp --- a/src/avalanche/test/peermanager_tests.cpp +++ b/src/avalanche/test/peermanager_tests.cpp @@ -2039,4 +2039,17 @@ gArgs.ClearForcedArg("-enableavalancheproofreplacement"); } +BOOST_AUTO_TEST_CASE(register_proof_missing_utxo) { + ChainstateManager &chainman = *Assert(m_node.chainman); + avalanche::PeerManager pm(chainman); + + CKey key = CKey::MakeCompressedKey(); + auto proof = + buildProofWithOutpoints(key, {{TxId(GetRandHash()), 0}}, 10 * COIN); + + ProofRegistrationState state; + BOOST_CHECK(!pm.registerProof(proof, state)); + BOOST_CHECK(state.GetResult() == ProofRegistrationResult::MISSING_UTXO); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -7120,6 +7120,12 @@ return false; } + if (state.GetResult() == avalanche::ProofRegistrationResult::MISSING_UTXO) { + // This is possible that a proof contains a utxo we don't know yet, so + // don't ban for this. + return false; + } + if (!g_avalanche->addProofToReconcile(proof)) { LogPrint(BCLog::AVALANCHE, "Not polling the avalanche proof (%s): peer=%d, proofid %s\n", diff --git a/test/functional/abc_p2p_proof_inventory.py b/test/functional/abc_p2p_proof_inventory.py --- a/test/functional/abc_p2p_proof_inventory.py +++ b/test/functional/abc_p2p_proof_inventory.py @@ -9,7 +9,12 @@ import time from test_framework.address import ADDRESS_ECREG_UNSPENDABLE -from test_framework.avatools import gen_proof, get_proof_ids, wait_for_proof +from test_framework.avatools import ( + avalanche_proof_from_hex, + gen_proof, + get_proof_ids, + wait_for_proof, +) from test_framework.messages import ( MSG_AVA_PROOF, MSG_TYPE_MASK, @@ -129,11 +134,29 @@ _, bad_proof = self.generate_proof(node) bad_proof.stakes = [] + privkey = node.get_deterministic_priv_key().key + missing_stake = node.buildavalancheproof( + 1, 0, privkey, [{ + 'txid': '0' * 64, + 'vout': 0, + 'amount': 10000000, + 'height': 42, + 'iscoinbase': False, + 'privatekey': privkey, + }] + ) + self.restart_node(0, ['-enableavalanche=1']) peer = node.add_p2p_connection(P2PInterface()) - msg = msg_avaproof() + + # Sending a proof with a missing utxo doesn't trigger a ban + msg.proof = avalanche_proof_from_hex(missing_stake) + with node.assert_debug_log(["received: avaproof"], ["Misbehaving"]): + peer.send_message(msg) + peer.sync_with_ping() + msg.proof = bad_proof with node.assert_debug_log([ 'Misbehaving',