def test_request_invalid_once(self, context):
        node = self.nodes[0]
        privkey = ECKey()
        privkey.generate()

        # Build an invalid proof (no stake)
        no_stake_hex = node.buildavalancheproof(
            42, 2000000000, bytes_to_wif(privkey.get_bytes()), [])
        no_stake = FromHex(LegacyAvalancheProof(), no_stake_hex)
        assert_raises_rpc_error(-8, "The proof is invalid: no-stake",
                                node.verifyavalancheproof, no_stake_hex)

        # Send the proof
        msg = msg_avaproof()
        msg.proof = no_stake
        node.p2ps[0].send_message(msg)

        # Check we get banned
        node.p2ps[0].wait_for_disconnect()

        # Now that the node knows the proof is invalid, it should not be
        # requested anymore
        node.p2ps[1].send_message(
            msg_inv([CInv(t=context.inv_type, h=no_stake.proofid)]))

        # Give enough time for the node to eventually request the proof
        node.setmocktime(
            int(time.time()) + context.constants.getdata_interval + 1)
        node.p2ps[1].sync_with_ping()

        assert all(p.getdata_count == 0 for p in node.p2ps[1:])
    def test_receive_proof(self):
        self.log.info("Test a peer is created on proof reception")

        node = self.nodes[0]
        _, proof = self.gen_proof(node)

        peer = node.add_p2p_connection(P2PInterface())

        msg = msg_avaproof()
        msg.proof = proof
        peer.send_message(msg)

        wait_until(lambda: proof.proofid in get_proof_ids(node))

        self.log.info("Test receiving a proof with missing utxo is orphaned")

        privkey = ECKey()
        privkey.generate()
        orphan_hex = node.buildavalancheproof(
            42, 2000000000,
            privkey.get_pubkey().get_bytes().hex(),
            [{
                'txid': '0' * 64,
                'vout': 0,
                'amount': 10e6,
                'height': 42,
                'iscoinbase': False,
                'privatekey': bytes_to_wif(privkey.get_bytes()),
            }])

        orphan = FromHex(AvalancheProof(), orphan_hex)
        orphan_proofid = "{:064x}".format(orphan.proofid)

        msg = msg_avaproof()
        msg.proof = orphan
        peer.send_message(msg)

        wait_for_proof(node, orphan_proofid, expect_orphan=True)
    def test_ban_invalid_proof(self):
        node = self.nodes[0]
        _, bad_proof = self.gen_proof(node)
        bad_proof.stakes = []

        peer = node.add_p2p_connection(P2PInterface())

        msg = msg_avaproof()
        msg.proof = bad_proof
        with node.assert_debug_log([
                'Misbehaving',
                'invalid-avaproof',
        ]):
            peer.send_message(msg)
            peer.wait_for_disconnect()
    def run_test(self):
        # peers that we expect to be protected from eviction
        protected_peers = set()
        current_peer = -1
        node = self.nodes[0]
        blocks = node.generatetoaddress(
            101,
            node.get_deterministic_priv_key().address)

        self.log.info(
            "Create 4 peers and protect them from eviction by sending us a block"
        )
        for _ in range(4):
            block_peer = node.add_p2p_connection(SlowP2PDataStore())
            current_peer += 1
            block_peer.sync_with_ping()
            best_block = node.getbestblockhash()
            tip = int(best_block, 16)
            best_block_time = node.getblock(best_block)['time']
            block = create_block(tip,
                                 create_coinbase(node.getblockcount() + 1),
                                 best_block_time + 1)
            block.solve()
            block_peer.send_blocks_and_test([block], node, success=True)
            protected_peers.add(current_peer)

        self.log.info(
            "Create 4 peers and protect them from eviction by sending us a proof"
        )
        privkey = ECKey()
        privkey.generate()
        wif_privkey = bytes_to_wif(privkey.get_bytes())
        pubkey = privkey.get_pubkey()

        stakes = create_coinbase_stakes(node, blocks,
                                        node.get_deterministic_priv_key().key)

        for i in range(4):
            proof_peer = node.add_p2p_connection(SlowP2PDataStore())
            current_peer += 1
            proof_peer.sync_with_ping()

            proof = node.buildavalancheproof(42, 2000000000, wif_privkey,
                                             [stakes[i]])

            avaproof_msg = msg_avaproof()
            avaproof_msg.proof = FromHex(LegacyAvalancheProof(), proof)
            proof_peer.send_message(avaproof_msg)
            protected_peers.add(current_peer)

        self.log.info(
            "Create 5 slow-pinging peers, making them eviction candidates")
        for _ in range(5):
            node.add_p2p_connection(SlowP2PInterface())
            current_peer += 1

        self.log.info(
            "Create 4 peers and protect them from eviction by sending us a tx")
        for i in range(4):
            txpeer = node.add_p2p_connection(SlowP2PInterface())
            current_peer += 1
            txpeer.sync_with_ping()

            prevtx = node.getblock(node.getblockhash(i + 1), 2)['tx'][0]
            rawtx = node.createrawtransaction(
                inputs=[{
                    'txid': prevtx['txid'],
                    'vout': 0
                }],
                outputs=[{
                    node.get_deterministic_priv_key().address:
                    50000000 - 1250.00
                }],
            )
            sigtx = node.signrawtransactionwithkey(
                hexstring=rawtx,
                privkeys=[node.get_deterministic_priv_key().key],
                prevtxs=[{
                    'txid':
                    prevtx['txid'],
                    'vout':
                    0,
                    'amount':
                    prevtx['vout'][0]['value'],
                    'scriptPubKey':
                    prevtx['vout'][0]['scriptPubKey']['hex'],
                }],
            )['hex']
            txpeer.send_message(msg_tx(FromHex(CTransaction(), sigtx)))
            protected_peers.add(current_peer)

        self.log.info(
            "Create 8 peers and protect them from eviction by having faster pings"
        )
        for _ in range(8):
            fastpeer = node.add_p2p_connection(P2PInterface())
            current_peer += 1
            self.wait_until(lambda: "ping" in fastpeer.last_message,
                            timeout=10)

        self.log.info(
            "Create 128 peers and protect them from eviction by sending an avahello message"
        )

        proof = node.buildavalancheproof(42, 2000000000, wif_privkey,
                                         [stakes[0]])
        proof_obj = FromHex(LegacyAvalancheProof(), proof)
        delegation = node.delegateavalancheproof(
            f"{proof_obj.limited_proofid:064x}",
            bytes_to_wif(privkey.get_bytes()),
            pubkey.get_bytes().hex(),
        )

        for _ in range(128):
            avapeer = node.add_p2p_connection(SlowAvaP2PInterface())
            current_peer += 1
            avapeer.sync_with_ping()
            avapeer.send_avahello(delegation, privkey)

        # Make sure by asking the node what the actual min pings are
        peerinfo = node.getpeerinfo()
        pings = {}
        for i in range(len(peerinfo)):
            pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[
                i] else 1000000
        sorted_pings = sorted(pings.items(), key=lambda x: x[1])

        # Usually the 8 fast peers are protected. In rare case of unreliable pings,
        # one of the slower peers might have a faster min ping though.
        for i in range(8):
            protected_peers.add(sorted_pings[i][0])

        self.log.info("Create peer that triggers the eviction mechanism")
        node.add_p2p_connection(SlowP2PInterface())

        # One of the non-protected peers must be evicted. We can't be sure which one because
        # 4 peers are protected via netgroup, which is identical for all peers,
        # and the eviction mechanism doesn't preserve the order of identical
        # elements.
        evicted_peers = []
        for i in range(len(node.p2ps)):
            if not node.p2ps[i].is_connected:
                evicted_peers.append(i)

        self.log.info("Test that one peer was evicted")
        self.log.debug("{} evicted peer: {}".format(len(evicted_peers),
                                                    set(evicted_peers)))
        assert_equal(len(evicted_peers), 1)

        self.log.info("Test that no peer expected to be protected was evicted")
        self.log.debug("{} protected peers: {}".format(len(protected_peers),
                                                       protected_peers))
        assert evicted_peers[0] not in protected_peers