diff --git a/src/libknot/nameserver/ns_proc_query.c b/src/libknot/nameserver/ns_proc_query.c
index 0775a38ae07a16c263319a00434679b8ee5cd77b..259bb6ab189f0658da5dbe936d44544de55aaae0 100644
--- a/src/libknot/nameserver/ns_proc_query.c
+++ b/src/libknot/nameserver/ns_proc_query.c
@@ -396,7 +396,6 @@ static int in_zone_name_found(knot_pkt_t *pkt, const knot_dname_t **name,
 		return DELEG;
 	}
 
-	/*! \todo Implement normal answer. */
 	int added = 0; /*! \todo useless */
 	int ret = ns_put_answer(qdata->node, pkt->zone->contents, *name, qtype, pkt, &added, 0 /*! \todo check from pkt */);
 
@@ -435,7 +434,7 @@ static int in_zone_name_not_found(knot_pkt_t *pkt, const knot_dname_t **name,
 {
 	dbg_ns("%s(%p, %p, %p)\n", __func__, pkt, name, qdata);
 
-	// else check for a wildcard child
+	/* Name is covered by wildcard. */
 	const knot_node_t *wildcard_node = knot_node_wildcard_child(qdata->encloser);
 	if (wildcard_node) {
 		dbg_ns("%s: name %p covered by wildcard\n", __func__, *name);
@@ -445,7 +444,7 @@ static int in_zone_name_not_found(knot_pkt_t *pkt, const knot_dname_t **name,
 		return in_zone_name_found(pkt, name, qdata);
 	}
 
-	// DNAME?
+	/* Name is under DNAME, use it for substitution. */
 	knot_rrset_t *dname_rrset = knot_node_get_rrset(qdata->encloser, KNOT_RRTYPE_DNAME);
 	if (dname_rrset != NULL
 	    && knot_rrset_rdata_rr_count(dname_rrset) > 0) {
@@ -463,7 +462,7 @@ static int in_zone_name_not_found(knot_pkt_t *pkt, const knot_dname_t **name,
 	return MISS;
 }
 
-static int in_zone_process_name(int state, const knot_dname_t **name,
+static int in_zone_solve_name(int state, const knot_dname_t **name,
                                 knot_pkt_t *pkt, struct query_data *qdata)
 {
 	dbg_ns("%s(%d, %p, %p, %p)\n", __func__, state, name, pkt, qdata);
@@ -484,29 +483,21 @@ static int in_zone_process_name(int state, const knot_dname_t **name,
 	}
 }
 
-static int in_zone_answer(knot_pkt_t *resp, struct query_data *qdata)
+static int in_zone_solve_answer(const knot_dname_t **qname,
+                                    knot_pkt_t *pkt, struct query_data *qdata)
 {
-	dbg_ns("%s(%p, %p)\n", __func__, resp, qdata);
-
-	/* Write answer RRs for QNAME. */
-	dbg_ns("%s: writing %p ANSWER\n", __func__, resp);
-	knot_pkt_begin(resp, KNOT_ANSWER);
-
-	const knot_dname_t *qname = knot_pkt_qname(resp);
-
 	/* Get answer to QNAME. */
-	int ret = KNOT_EOK;
-	int state = in_zone_process_name(BEGIN, &qname, resp, qdata);
+	int state = in_zone_solve_name(BEGIN, qname, pkt, qdata);
 
 	/* Is authoritative answer unless referral.
 	 * Must check before we chase the CNAME chain. */
 	if (state != DELEG) {
-		knot_wire_set_aa(resp->wire);
+		knot_wire_set_aa(pkt->wire);
 	}
 
 	/* Additional resolving for CNAME/DNAME chain. */
 	while (state == FOLLOW) {
-		state = in_zone_process_name(state, &qname, resp, qdata);
+		state = in_zone_solve_name(state, qname, pkt, qdata);
 		/* Chain lead to NXDOMAIN, this is okay since
 		 * the first CNAME/DNAME is a valid answer. */
 		if (state == MISS) {
@@ -514,38 +505,60 @@ static int in_zone_answer(knot_pkt_t *resp, struct query_data *qdata)
 		}
 	}
 
-	/* Resolve AUTHORITY. */
-	dbg_ns("%s: writing %p AUTHORITY\n", __func__, resp);
-	knot_pkt_begin(resp, KNOT_AUTHORITY);
+	return state;
+}
+
+static int in_zone_solve_authority(int state, const knot_dname_t **qname,
+                                   knot_pkt_t *pkt, struct query_data *qdata)
+{
+	int ret = KNOT_ERROR;
+
 	switch (state) {
 	case HIT:    /* Positive response, add (optional) AUTHORITY NS. */
-		ret = ns_put_authority_ns(resp->zone->contents, resp);
+		ret = ns_put_authority_ns(pkt->zone->contents, pkt);
 		dbg_ns("%s: putting authority NS = %d\n", __func__, ret);
 		break;
 	case MISS:   /* MISS, set NXDOMAIN RCODE. */
 		qdata->rcode = KNOT_RCODE_NXDOMAIN;
 		dbg_ns("%s: answer is NXDOMAIN\n", __func__);
 	case NODATA: /* NODATA or NXDOMAIN, append AUTHORITY SOA. */
-		ret = ns_put_authority_soa(resp->zone->contents, resp);
+		ret = ns_put_authority_soa(pkt->zone->contents, pkt);
 		dbg_ns("%s: putting authority SOA = %d\n", __func__, ret);
 		break;
 	case DELEG:  /* Referral response. */ /*! \todo DS + NS */
-		ret = ns_referral(qdata->node, resp->zone->contents, qname, resp, knot_pkt_qtype(resp));
+		ret = ns_referral(qdata->node, pkt->zone->contents, *qname, pkt, knot_pkt_qtype(pkt));
 		break;
 	case ERROR:
 		dbg_ns("%s: failed to resolve qname\n", __func__);
-		ret = KNOT_ERROR;
 		break;
 	default:
 		dbg_ns("%s: invalid state after qname processing = %d\n",
 		       __func__, state);
 		assert(0);
 		qdata->rcode = KNOT_RCODE_SERVFAIL;
-		ret = KNOT_ERROR;
 		break;
 	}
 
-	/* Check errors after AUTHORITY processing. */
+	return ret;
+}
+
+static int in_zone_answer(knot_pkt_t *resp, struct query_data *qdata)
+{
+	dbg_ns("%s(%p, %p)\n", __func__, resp, qdata);
+
+	/* Write answer RRs for QNAME. */
+	dbg_ns("%s: writing %p ANSWER\n", __func__, resp);
+	knot_pkt_begin(resp, KNOT_ANSWER);
+
+	const knot_dname_t *qname = knot_pkt_qname(resp);
+
+	/* Get answer to QNAME. */
+	int state = in_zone_solve_answer(&qname, resp, qdata);
+
+	/* Resolve AUTHORITY. */
+	dbg_ns("%s: writing %p AUTHORITY\n", __func__, resp);
+	knot_pkt_begin(resp, KNOT_AUTHORITY);
+	int ret = in_zone_solve_authority(state, &qname, resp, qdata);
 	if (ret != KNOT_EOK) {
 		return NS_PROC_FAIL;
 
@@ -553,20 +566,11 @@ static int in_zone_answer(knot_pkt_t *resp, struct query_data *qdata)
 
 	// add all missing NSECs/NSEC3s for wildcard nodes
 	/*! \todo Make function accept query_data with zone+wcnodes */
-	if (ns_put_nsec_nsec3_wildcard_nodes(resp, resp->zone->contents) != KNOT_EOK) {
-		return NS_PROC_FAIL;
-	}
 
 	/* Resolve ADDITIONAL. */
 	dbg_ns("%s: writing %p ADDITIONAL\n", __func__, resp);
 	knot_pkt_begin(resp, KNOT_ADDITIONAL);
-
-	/* No ADDITIONAL for referral response. */
-	if (state != DELEG) {
-		ret = ns_put_additional(resp);
-	}
-
-	/* Check errors after ADDITIONAL processing. */
+	ret = ns_put_additional(resp);
 	if (ret != KNOT_EOK) {
 		return NS_PROC_FAIL;